Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ These are the client libraries that instrument developer tooling and send teleme
| `agoda-devfeedback-rsbuild` | npm | `POST /vite` | [devfeedback-js](https://github.com/agoda-com/devfeedback-js) |
| JUnit reporter | Maven | `POST /junit` | [java-local-metrics](https://github.com/agoda-com/java-local-metrics) |
| ScalaTest reporter | sbt | `POST /scala/scalatest` | [java-local-metrics](https://github.com/agoda-com/java-local-metrics) |
| Gradle build metrics plugin | Gradle | `POST /gradle` | [kotlin-local-metrics](https://github.com/agoda-com/kotlin-local-metrics) |
| Ktor startup metrics plugin | Gradle | `POST /ktor` | [kotlin-local-metrics](https://github.com/agoda-com/kotlin-local-metrics) |
| Talaiot Gradle plugin | Gradle | `POST /gradletalaiot` | [Talaiot](https://github.com/cdsap/Talaiot) (external) |
| Jest reporter | npm | `POST /jest` | [testresults-collector](https://github.com/agoda-com/testresults-collector) |
| Vitest reporter | npm | `POST /vitest` | [testresults-collector](https://github.com/agoda-com/testresults-collector) |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Text.Json.Serialization;

namespace Agoda.DevExTelemetry.Core.Models.Ingest;

public class GradleBuildMetricPayload : JvmBuildMetricPayload
{
[JsonPropertyName("ide")]
public string? Ide { get; set; }

[JsonPropertyName("buildKind")]
public string? BuildKind { get; set; }

[JsonPropertyName("requestedTasks")]
public List<string>? RequestedTasks { get; set; }

[JsonPropertyName("taskCount")]
public int TaskCount { get; set; }

[JsonPropertyName("executedTaskCount")]
public int ExecutedTaskCount { get; set; }

[JsonPropertyName("upToDateTaskCount")]
public int UpToDateTaskCount { get; set; }

[JsonPropertyName("fromCacheTaskCount")]
public int FromCacheTaskCount { get; set; }

[JsonPropertyName("failedTaskCount")]
public int FailedTaskCount { get; set; }

[JsonPropertyName("compileTaskCount")]
public int CompileTaskCount { get; set; }

[JsonPropertyName("compileTimeMs")]
public long CompileTimeMs { get; set; }

[JsonPropertyName("taskTimeMs")]
public long TaskTimeMs { get; set; }

[JsonPropertyName("projects")]
public List<GradleProjectMetricPayload>? Projects { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Text.Json.Serialization;

namespace Agoda.DevExTelemetry.Core.Models.Ingest;

public class GradleProjectMetricPayload
{
[JsonPropertyName("projectPath")]
public string? ProjectPath { get; set; }

[JsonPropertyName("compileTaskCount")]
public int CompileTaskCount { get; set; }

[JsonPropertyName("compileTimeMs")]
public long CompileTimeMs { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Agoda.DevExTelemetry.Core.Models.Ingest;

public abstract class JvmBuildMetricPayload
{
[JsonPropertyName("id")]
public string? Id { get; set; }

[JsonPropertyName("metricsVersion")]
public string? MetricsVersion { get; set; }

[JsonPropertyName("userName")]
public string? UserName { get; set; }

[JsonPropertyName("cpuCount")]
public int CpuCount { get; set; }

[JsonPropertyName("hostname")]
public string? Hostname { get; set; }

[JsonPropertyName("platform")]
public string? Platform { get; set; }

[JsonPropertyName("os")]
public string? Os { get; set; }

[JsonPropertyName("timeTaken")]
public string? TimeTaken { get; set; }

[JsonPropertyName("branch")]
public string? Branch { get; set; }

[JsonPropertyName("commitSha")]
public string? CommitSha { get; set; }

[JsonPropertyName("type")]
public string? Type { get; set; }

[JsonPropertyName("projectName")]
public string? ProjectName { get; set; }

[JsonPropertyName("repository")]
public string? Repository { get; set; }

[JsonPropertyName("repositoryName")]
public string? RepositoryName { get; set; }

[JsonPropertyName("date")]
public string? Date { get; set; }

[JsonPropertyName("isDebuggerAttached")]
public bool IsDebuggerAttached { get; set; }

[JsonExtensionData]
public Dictionary<string, JsonElement>? AdditionalData { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Text.Json.Serialization;

namespace Agoda.DevExTelemetry.Core.Models.Ingest;

public class KtorStartupMetricPayload : JvmBuildMetricPayload
{
[JsonPropertyName("tags")]
public Dictionary<string, string>? Tags { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,24 @@ namespace Agoda.DevExTelemetry.IntegrationTests;
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
private readonly DatabaseProvider _provider;
private readonly bool _enableRawPayloadStorage;
private SqliteConnection? _sqliteConnection;
private string? _pgDatabaseName;

public CustomWebApplicationFactory(DatabaseProvider provider = DatabaseProvider.Sqlite)
public CustomWebApplicationFactory(
DatabaseProvider provider = DatabaseProvider.Sqlite,
bool enableRawPayloadStorage = false)
{
_provider = provider;
_enableRawPayloadStorage = enableRawPayloadStorage;
}

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseSetting(
"DataRetention:EnableRawPayloadStorage",
_enableRawPayloadStorage.ToString());

if (_provider == DatabaseProvider.Sqlite)
{
_sqliteConnection = new SqliteConnection("Data Source=:memory:");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using System.Net;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using NUnit.Framework;
using Shouldly;

namespace Agoda.DevExTelemetry.IntegrationTests;

[TestFixture(DatabaseProvider.Sqlite)]
[TestFixture(DatabaseProvider.PostgreSql)]
public class GradleBuildMetricIngestTests
{
private readonly DatabaseProvider _provider;
private CustomWebApplicationFactory _factory = null!;
private HttpClient _client = null!;

public GradleBuildMetricIngestTests(DatabaseProvider provider) => _provider = provider;

[SetUp]
public void SetUp()
{
_factory = new CustomWebApplicationFactory(_provider, enableRawPayloadStorage: true);
_client = _factory.CreateClient();
}

[TearDown]
public void TearDown()
{
_client.Dispose();
_factory.Dispose();
}

[Test]
public async Task POST_Gradle_WithFullContract_QueuesCompleteBuildMetric()
{
var payload = TestFixtures.CreateGradleBuildMetricPayload();
var response = await TestFixtures.PostJsonAsync(_client, "/gradle", payload);

response.StatusCode.ShouldBe(HttpStatusCode.OK);
await _factory.DrainBackgroundQueuesAsync();

using var db = _factory.CreateDbContext();
var metric = await db.BuildMetrics.SingleAsync();
metric.MetricType.ShouldBe("Gradle");
metric.TimeTakenMs.ShouldBe(1842);
metric.BuildCategory.ShouldBe("API");
metric.ReloadType.ShouldBeNull();
metric.UserName.ShouldBe("developer");
metric.CpuCount.ShouldBe(12);
metric.Hostname.ShouldBe("workstation");
metric.Platform.ShouldBe("Linux");
metric.Os.ShouldBe("Linux 6.8.0 amd64");
metric.Branch.ShouldBe("feature/build-metrics");
metric.ProjectName.ShouldBe("sample-service");
metric.Repository.ShouldBe("https://example.com/group/sample-service.git");
metric.RepositoryName.ShouldBe("sample-service");
metric.ToolVersion.ShouldBe("0.1.0");
metric.CommitSha.ShouldBe("6f9c4bd4a2d0f5cfa3fa8e199cc0c630f56a9898");
metric.IsDebuggerAttached.ShouldBeFalse();
metric.ExecutionEnvironment.ShouldBe("Local");
metric.SourceEndpoint.ShouldBe("/gradle");

using var extraData = JsonDocument.Parse(metric.ExtraData!);
extraData.RootElement.GetProperty("date").GetString()
.ShouldBe("2026-07-23T07:30:00Z");
extraData.RootElement.GetProperty("ide").GetString().ShouldBe("IntelliJ IDEA");
extraData.RootElement.GetProperty("buildKind").GetString().ShouldBe("incremental");
extraData.RootElement.GetProperty("requestedTasks")[0].GetString().ShouldBe("build");
extraData.RootElement.GetProperty("taskCount").GetInt32().ShouldBe(42);
extraData.RootElement.GetProperty("executedTaskCount").GetInt32().ShouldBe(14);
extraData.RootElement.GetProperty("upToDateTaskCount").GetInt32().ShouldBe(25);
extraData.RootElement.GetProperty("fromCacheTaskCount").GetInt32().ShouldBe(3);
extraData.RootElement.GetProperty("failedTaskCount").GetInt32().ShouldBe(0);
extraData.RootElement.GetProperty("compileTaskCount").GetInt32().ShouldBe(8);
extraData.RootElement.GetProperty("compileTimeMs").GetInt64().ShouldBe(913);
extraData.RootElement.GetProperty("taskTimeMs").GetInt64().ShouldBe(3276);
extraData.RootElement.GetProperty("projects")[1]
.GetProperty("projectPath").GetString().ShouldBe(":api");

var rawPayload = await db.RawPayloads.SingleAsync();
rawPayload.Endpoint.ShouldBe("/gradle");
rawPayload.PayloadJson.ShouldContain("\"projects\"");
rawPayload.PayloadJson.ShouldContain("\"compileTimeMs\":533");
}

[Test]
public async Task POST_Gradle_WithNullableGitFields_AcceptsPayload()
{
var payload = TestFixtures.CreateGradleBuildMetricPayload(
branch: null, commitSha: null, repository: null, repositoryName: null);

var response = await TestFixtures.PostJsonAsync(_client, "/gradle", payload);

response.StatusCode.ShouldBe(HttpStatusCode.OK);
await _factory.DrainBackgroundQueuesAsync();
using var db = _factory.CreateDbContext();
var metric = await db.BuildMetrics.SingleAsync();
metric.Branch.ShouldBeEmpty();
metric.CommitSha.ShouldBeNull();
metric.Repository.ShouldBeEmpty();
metric.RepositoryName.ShouldBeEmpty();
}

[TestCase("NotGradle", "1842")]
[TestCase("gradle", "1842")]
[TestCase("Gradle", null)]
[TestCase("Gradle", "")]
[TestCase("Gradle", "1.5")]
[TestCase("Gradle", "-1")]
[TestCase("Gradle", "9223372036854775808")]
public async Task POST_Gradle_WithInvalidContract_ReturnsBadRequestWithoutPersisting(
string type, string? timeTaken)
{
var payload = TestFixtures.CreateGradleBuildMetricPayload(type, timeTaken);

var response = await TestFixtures.PostJsonAsync(_client, "/gradle", payload);

response.StatusCode.ShouldBe(HttpStatusCode.BadRequest);
await _factory.DrainBackgroundQueuesAsync();
using var db = _factory.CreateDbContext();
(await db.BuildMetrics.CountAsync()).ShouldBe(0);
(await db.RawPayloads.CountAsync()).ShouldBe(0);
}
}
Loading
Loading