Files
git.stella-ops.org/src/Findings/__Tests/StellaOps.Findings.Ledger.Tests/LedgerMetricsTests.cs

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;
}
}