234 lines
8.5 KiB
C#
234 lines
8.5 KiB
C#
using System.Diagnostics.Metrics;
|
|
using System.Linq;
|
|
using FluentAssertions;
|
|
using StellaOps.Findings.Ledger.Observability;
|
|
using Xunit;
|
|
|
|
|
|
using StellaOps.TestKit;
|
|
namespace StellaOps.Findings.Ledger.Tests;
|
|
|
|
public class LedgerMetricsTests
|
|
{
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ProjectionLagGauge_RecordsLatestPerTenant()
|
|
{
|
|
using var listener = CreateListener();
|
|
var measurements = new List<(double Value, KeyValuePair<string, object?>[] Tags)>();
|
|
|
|
listener.SetMeasurementEventCallback<double>((instrument, measurement, tags, state) =>
|
|
{
|
|
if (instrument.Name == "ledger_projection_lag_seconds")
|
|
{
|
|
measurements.Add((measurement, tags.ToArray()));
|
|
}
|
|
});
|
|
|
|
LedgerMetrics.RecordProjectionLag(TimeSpan.FromSeconds(42), "tenant-a");
|
|
|
|
listener.RecordObservableInstruments();
|
|
|
|
var measurement = measurements.Should().ContainSingle().Subject;
|
|
measurement.Value.Should().BeApproximately(42, precision: 0.001);
|
|
measurement.Tags.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
|
|
.Should().Contain(new KeyValuePair<string, object?>("tenant", "tenant-a"));
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void MerkleAnchorDuration_EmitsHistogramMeasurement()
|
|
{
|
|
using var listener = CreateListener();
|
|
var measurements = new List<(double Value, KeyValuePair<string, object?>[] Tags)>();
|
|
|
|
listener.SetMeasurementEventCallback<double>((instrument, measurement, tags, state) =>
|
|
{
|
|
if (instrument.Name == "ledger_merkle_anchor_duration_seconds")
|
|
{
|
|
measurements.Add((measurement, tags.ToArray()));
|
|
}
|
|
});
|
|
|
|
LedgerMetrics.RecordMerkleAnchorDuration(TimeSpan.FromSeconds(1.5), "tenant-b", 10);
|
|
|
|
var measurement = measurements.Should().ContainSingle().Subject;
|
|
measurement.Value.Should().BeApproximately(1.5, precision: 0.001);
|
|
measurement.Tags.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
|
|
.Should().Contain(new KeyValuePair<string, object?>("tenant", "tenant-b"));
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void MerkleAnchorFailure_IncrementsCounter()
|
|
{
|
|
using var listener = CreateListener();
|
|
var measurements = new List<(long Value, KeyValuePair<string, object?>[] Tags)>();
|
|
|
|
listener.SetMeasurementEventCallback<long>((instrument, measurement, tags, state) =>
|
|
{
|
|
if (instrument.Name == "ledger_merkle_anchor_failures_total")
|
|
{
|
|
measurements.Add((measurement, tags.ToArray()));
|
|
}
|
|
});
|
|
|
|
LedgerMetrics.RecordMerkleAnchorFailure("tenant-c", "persist_failure");
|
|
|
|
var measurement = measurements.Should().ContainSingle().Subject;
|
|
measurement.Value.Should().Be(1);
|
|
var tags = measurement.Tags.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
|
tags.Should().Contain(new KeyValuePair<string, object?>("tenant", "tenant-c"));
|
|
tags.Should().Contain(new KeyValuePair<string, object?>("reason", "persist_failure"));
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void AttachmentFailure_IncrementsCounter()
|
|
{
|
|
using var listener = CreateListener();
|
|
var measurements = new List<(long Value, KeyValuePair<string, object?>[] Tags)>();
|
|
|
|
listener.SetMeasurementEventCallback<long>((instrument, measurement, tags, state) =>
|
|
{
|
|
if (instrument.Name == "ledger_attachments_encryption_failures_total")
|
|
{
|
|
measurements.Add((measurement, tags.ToArray()));
|
|
}
|
|
});
|
|
|
|
LedgerMetrics.RecordAttachmentFailure("tenant-d", "encrypt");
|
|
|
|
var measurement = measurements.Should().ContainSingle().Subject;
|
|
measurement.Value.Should().Be(1);
|
|
var tags = measurement.Tags.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
|
tags.Should().Contain(new KeyValuePair<string, object?>("tenant", "tenant-d"));
|
|
tags.Should().Contain(new KeyValuePair<string, object?>("stage", "encrypt"));
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void BacklogGauge_ReflectsOutstandingQueue()
|
|
{
|
|
using var listener = CreateListener();
|
|
var measurements = new List<(long Value, KeyValuePair<string, object?>[] Tags)>();
|
|
|
|
// Reset
|
|
LedgerMetrics.DecrementBacklog("tenant-q");
|
|
|
|
LedgerMetrics.IncrementBacklog("tenant-q");
|
|
LedgerMetrics.IncrementBacklog("tenant-q");
|
|
LedgerMetrics.DecrementBacklog("tenant-q");
|
|
|
|
listener.SetMeasurementEventCallback<long>((instrument, measurement, tags, state) =>
|
|
{
|
|
if (instrument.Name == "ledger_ingest_backlog_events")
|
|
{
|
|
measurements.Add((measurement, tags.ToArray()));
|
|
}
|
|
});
|
|
|
|
listener.RecordObservableInstruments();
|
|
|
|
var measurement = measurements.Should().ContainSingle().Subject;
|
|
measurement.Value.Should().Be(1);
|
|
measurement.Tags.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
|
|
.Should().Contain(new KeyValuePair<string, object?>("tenant", "tenant-q"));
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ProjectionRebuildHistogram_RecordsScenarioTags()
|
|
{
|
|
using var listener = CreateListener();
|
|
var measurements = new List<(double Value, KeyValuePair<string, object?>[] Tags)>();
|
|
|
|
listener.SetMeasurementEventCallback<double>((instrument, measurement, tags, state) =>
|
|
{
|
|
if (instrument.Name == "ledger_projection_rebuild_seconds")
|
|
{
|
|
measurements.Add((measurement, tags.ToArray()));
|
|
}
|
|
});
|
|
|
|
LedgerMetrics.RecordProjectionRebuild(TimeSpan.FromSeconds(3.2), "tenant-r", "replay");
|
|
|
|
var measurement = measurements.Should().ContainSingle().Subject;
|
|
measurement.Value.Should().BeApproximately(3.2, 0.001);
|
|
var tags = measurement.Tags.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
|
tags.Should().Contain(new KeyValuePair<string, object?>("tenant", "tenant-r"));
|
|
tags.Should().Contain(new KeyValuePair<string, object?>("scenario", "replay"));
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void DbConnectionsGauge_TracksRoleCounts()
|
|
{
|
|
using var listener = CreateListener();
|
|
var measurements = new List<(long Value, KeyValuePair<string, object?>[] Tags)>();
|
|
|
|
// Reset
|
|
LedgerMetrics.DecrementDbConnection("writer");
|
|
|
|
LedgerMetrics.IncrementDbConnection("writer");
|
|
|
|
listener.SetMeasurementEventCallback<long>((instrument, measurement, tags, state) =>
|
|
{
|
|
if (instrument.Name == "ledger_db_connections_active")
|
|
{
|
|
measurements.Add((measurement, tags.ToArray()));
|
|
}
|
|
});
|
|
|
|
listener.RecordObservableInstruments();
|
|
|
|
var measurement = measurements.Should().ContainSingle().Subject;
|
|
measurement.Value.Should().Be(1);
|
|
measurement.Tags.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
|
|
.Should().Contain(new KeyValuePair<string, object?>("role", "writer"));
|
|
|
|
LedgerMetrics.DecrementDbConnection("writer");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void VersionInfoGauge_EmitsConstantOne()
|
|
{
|
|
using var listener = CreateListener();
|
|
var measurements = new List<(long Value, KeyValuePair<string, object?>[] Tags)>();
|
|
|
|
listener.SetMeasurementEventCallback<long>((instrument, measurement, tags, state) =>
|
|
{
|
|
if (instrument.Name == "ledger_app_version_info")
|
|
{
|
|
measurements.Add((measurement, tags.ToArray()));
|
|
}
|
|
});
|
|
|
|
listener.RecordObservableInstruments();
|
|
|
|
var measurement = measurements.Should().ContainSingle().Subject;
|
|
measurement.Value.Should().Be(1);
|
|
var tags = measurement.Tags.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
|
tags.Should().ContainKey("version");
|
|
tags.Should().ContainKey("git_sha");
|
|
}
|
|
|
|
private static MeterListener CreateListener()
|
|
{
|
|
var listener = new MeterListener
|
|
{
|
|
InstrumentPublished = (instrument, l) =>
|
|
{
|
|
if (instrument.Meter.Name == "StellaOps.Findings.Ledger")
|
|
{
|
|
l.EnableMeasurementEvents(instrument);
|
|
}
|
|
}
|
|
};
|
|
|
|
listener.Start();
|
|
return listener;
|
|
}
|
|
}
|