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
31 changes: 23 additions & 8 deletions Darling/Darling.Tests/PgTargetAnomalyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,14 @@ namespace Darling.Tests;
[Collection("live-postgres")]
public sealed class PgTargetAnomalyTests
{
/* The five v1 metrics (lane 9) plus the WAL-volume arm lane 15 filled (#3691) — each served by exactly one
PgTargetBaselineProvider arm ending in the one scaffold. Lane 11's I/O arm beside it; lane 12's replay-lag arm is pinned by its own tests. */
private static readonly string[] s_pgMetricNames =
[
MetricNames.PgTps, MetricNames.PgSessionCount, MetricNames.PgDeadlockRate, MetricNames.PgWaitMsPerSec, MetricNames.PgCpu,
/* v2 (#3691) lane 11: the I/O read-latency arm, filled. */
MetricNames.PgIoReadLatency,
MetricNames.PgWalBytesPerSec,
];

private static readonly (string Metric, string Table)[] s_metricTables =
Expand All @@ -76,14 +79,15 @@ private static readonly (string Metric, string Table)[] s_metricTables =
(MetricNames.PgIoReadLatency, "pg_io_stats"),
/* v2 (#3691): lane 12's replay-lag point series — its scaffold / dialect pins live in PgTargetReplicationTests. */
(MetricNames.PgReplayLagBytes, "pg_replication_stats"),
(MetricNames.PgWalBytesPerSec, "pg_write_stats"),
];

/* ───────────────────────── the baselines ───────────────────────── */

[Fact]
public void TheSixPgMetricNames_ArePgPrefixed_ServedOnlyByThePgTargetProvider_AndEndInTheOneScaffold()
public void TheServedPgMetricNames_ArePgPrefixed_ServedOnlyByThePgTargetProvider_AndEndInTheOneScaffold()
{
Assert.Equal(6, s_pgMetricNames.Distinct(StringComparer.Ordinal).Count());
Assert.Equal(7, s_pgMetricNames.Distinct(StringComparer.Ordinal).Count());
foreach (var metric in s_pgMetricNames)
{
Assert.StartsWith("pg_", metric, StringComparison.Ordinal);
Expand Down Expand Up @@ -115,22 +119,24 @@ public void TheSixPgMetricNames_ArePgPrefixed_ServedOnlyByThePgTargetProvider_An
/// answers null (the same answer as "no arm", so the shared reader reports no baseline rather than a bucket built
/// from nothing), the root detector awaits each stub after the five v1 detectors, and every stub file carries
/// the exact marker the content briefs quote (a filled family keeps it, as v1's did). The wave-2 name has no arm
/// at all. Lane 11 (I/O read latency) has landed: its metric moved to <see cref="s_pgMetricNames"/> and out of
/// the inert loop; <c>PgTargetIoTests</c> pins the filled arm's shape.
/// at all. Lanes 11 (I/O read latency), 12 (replay lag) and 15 (WAL volume) have landed: their metrics moved out
/// of the inert loop (11 and 15 into <see cref="s_pgMetricNames"/>); <c>PgTargetIoTests</c>, <c>PgTargetReplicationTests</c>
/// and <c>PgTargetWriteTests</c> pin the filled arms' shapes.
/// </summary>
[Fact]
public void TheV2BaselinesAndDetectors_AreReachableInertStubs_EachNamingItsLane()
{
/* Lanes 11 and 12 filled pg_io_read_latency and pg_replay_lag_bytes (each arm pinned in its own family's tests);
the WAL stub and the wave-2 name still answer null. */
foreach (var metric in new[] { MetricNames.PgWalBytesPerSec, MetricNames.PgAutovacuumWorkers })
/* Lanes 11, 12 and 15 filled pg_io_read_latency, pg_replay_lag_bytes and pg_wal_bytes_per_sec (each arm pinned in
its own family's tests; the served ones also in s_pgMetricNames above); the wave-2 name still answers null. */
foreach (var metric in new[] { MetricNames.PgAutovacuumWorkers })
{
Assert.StartsWith("pg_", metric, StringComparison.Ordinal);
Assert.DoesNotContain(metric, s_pgMetricNames);
Assert.Null(PgBaselineProvider.GetBaselineQuery(metric));
Assert.Null(PgTargetBaselineProvider.GetPgTargetBaselineQuery(metric));
}
Assert.NotNull(PgTargetBaselineProvider.GetPgTargetBaselineQuery(MetricNames.PgReplayLagBytes));
Assert.Contains(MetricNames.PgWalBytesPerSec, s_pgMetricNames);

var provider = RepoFile.ReadRepoFile("Darling", "PerformanceMonitor.Darling.Analysis", "PgTargetBaselineProvider.cs");
Assert.Contains("MetricNames.PgIoReadLatency => IoReadLatencyBaselineQuery(),", provider, StringComparison.Ordinal);
Expand Down Expand Up @@ -250,6 +256,8 @@ public void PgBaselineSupply_DefaultRetentionCoversTheWindow_NoSourceIsInTheFour
/* v2 (#3691) lane 11 */
PgTargetAnomalyDetector.IoLatencyWindowSql,
PgTargetAnomalyDetector.ReplayLagWindowSql, /* lane 12 (#3691) */
/* v2 (#3691) lane 15 */
PgTargetAnomalyDetector.WalVolumeWindowSql,
];

[Fact]
Expand Down Expand Up @@ -283,6 +291,9 @@ public void EveryDetectorRead_IsPgDialect_ServerScoped_WindowBound_OverCollector
Assert.Contains("FROM pg_wait_stats", PgTargetAnomalyDetector.WaitContribWindowSql, StringComparison.Ordinal);
Assert.Contains("FROM pg_io_stats", PgTargetAnomalyDetector.IoLatencyWindowSql, StringComparison.Ordinal);
Assert.Contains("FROM pg_replication_stats", PgTargetAnomalyDetector.ReplayLagWindowSql, StringComparison.Ordinal);
/* Lane 15: the WAL window read IS the collector's read, by alias — one differencing for fact, detector and bucket. */
Assert.Contains("FROM pg_write_stats", PgTargetAnomalyDetector.WalVolumeWindowSql, StringComparison.Ordinal);
Assert.Equal(PgTargetFactCollector.PgTargetWalVolumeSql, PgTargetAnomalyDetector.WalVolumeWindowSql);

/* The window reads share the fact reads' shapes. */
Assert.Contains("stats_reset", PgTargetBaselineProvider.GetPgTargetBaselineQuery(MetricNames.PgTps)!, StringComparison.Ordinal);
Expand Down Expand Up @@ -586,7 +597,7 @@ public void EveryPgConstantInAnomalyThresholds_CarriesALineageMarker()
.Select(f => f.Name)
.ToList();
Assert.Equal(
new[] { "PgCpuFallbackPct", "PgCpuFloorPct", "PgDeadlockRateFallbackPerHour", "PgDeadlockRateFloorPerHour", "PgIoLatencyFallbackMs", "PgIoLatencyFloorMs", "PgRatioAnomalyThreshold", "PgSessionCountFallback", "PgSessionCountFloor", "PgTpsFallback", "PgTpsFloor", "PgWaitProfileFallbackMsPerSec" },
new[] { "PgCpuFallbackPct", "PgCpuFloorPct", "PgDeadlockRateFallbackPerHour", "PgDeadlockRateFloorPerHour", "PgIoLatencyFallbackMs", "PgIoLatencyFloorMs", "PgRatioAnomalyThreshold", "PgSessionCountFallback", "PgSessionCountFloor", "PgTpsFallback", "PgTpsFloor", "PgWaitProfileFallbackMsPerSec", "PgWalBytesFallbackPerSec", "PgWalBytesFloorPerSec" },
pgConstants.Order(StringComparer.Ordinal).ToArray());

var lines = RepoFile.ReadRepoFile("PerformanceMonitor.Analysis", "Baselines", "AnomalyThresholds.cs").Replace("\r\n", "\n", StringComparison.Ordinal).Split('\n');
Expand All @@ -611,6 +622,10 @@ public void EveryPgConstantInAnomalyThresholds_CarriesALineageMarker()
Assert.True(AnomalyThresholds.PgSessionCountFallback > AnomalyThresholds.PgSessionCountFloor);
Assert.True(AnomalyThresholds.PgDeadlockRateFallbackPerHour > AnomalyThresholds.PgDeadlockRateFloorPerHour);
Assert.True(AnomalyThresholds.PgIoLatencyFallbackMs > AnomalyThresholds.PgIoLatencyFloorMs);
/* Lane 15: 1 MiB/s and 16 MiB/s, in BYTES per second (the unit the fact, the window read and the bucket share). */
Assert.Equal(1024.0 * 1024.0, AnomalyThresholds.PgWalBytesFloorPerSec);
Assert.Equal(16.0 * 1024.0 * 1024.0, AnomalyThresholds.PgWalBytesFallbackPerSec);
Assert.True(AnomalyThresholds.PgWalBytesFallbackPerSec > AnomalyThresholds.PgWalBytesFloorPerSec);
}

/* ───────────────────────── the advice ───────────────────────── */
Expand Down
28 changes: 21 additions & 7 deletions Darling/Darling.Tests/PgTargetKnobsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -395,12 +395,16 @@ public void TheEdges_ReadTheDestinationsVerdict_NeverASizeOfTheirOwn()
Assert.Equal(PgTargetFactKeys.ConfigMaxWalSize, Assert.Single(graph.GetActiveEdges(PgTargetFactKeys.CheckpointPressure, atDefault)).Destination);
Assert.Equal(PgTargetFactKeys.ConfigSharedBuffers, Assert.Single(graph.GetActiveEdges(PgTargetFactKeys.BufferCachePressure, atDefault)).Destination);

/* The v2 leading edge is declared and inert until the shift fact exists. Lane 12 (#3691) declared a second
destination for the shift — PG_SLOT_RETENTION, the slot retaining the WAL the shift produced — so the
checkpoint edge is pinned by membership, not as the only one. */
/* Lane 15 (#3691): the WAL-volume leading edge into CHECKPOINT pressure is an AMPLIFIER and a reconciler FOLD,
not an edge — PG_WAL_VOLUME_SHIFT is a base-0 context fact (no absolute bar; graded only through its anomaly),
so lane 2's inert shift → pressure edge was dead by construction and is gone; the anomaly is not re-keyed onto
it either (PgTargetRelationshipGraph.Write.cs says why). PgTargetWriteTests pins the amplifier and the fold.
Lane 12 (#3691) declared the shift → PG_SLOT_RETENTION edge from its side; it is pinned by membership here and
carries the same base-0 caveat (reported on #3691). */
var shiftEdges = graph.GetAllEdges(PgTargetFactKeys.WalVolumeShift);
Assert.Contains(shiftEdges, e => e.Destination == PgTargetFactKeys.CheckpointPressure);
Assert.DoesNotContain(shiftEdges, e => e.Destination == PgTargetFactKeys.CheckpointPressure);
Assert.Contains(shiftEdges, e => e.Destination == PgTargetFactKeys.SlotRetention);
Assert.Empty(graph.GetAllEdges(PgTargetFactKeys.AnomalyWalVolume));
Assert.Equal(PgTargetFactKeys.ConfigMaxWalSize, Assert.Single(graph.GetActiveEdges(PgTargetFactKeys.CheckpointPressure, atDefault)).Destination);
}

Expand Down Expand Up @@ -764,12 +768,22 @@ await PlantWriteStatsAsync(connection, ChurnServerId, ChurnServerName, windowSta
Assert.Contains(story.GetProperty("next_tools").EnumerateArray(), t => t.GetProperty("tool").GetString() == "get_pg_write_stats");
}

/* ── the facts read: the pressure fact carries its unmeasured-lineage stamp and both amplifiers. */
/* ── the facts read: the pressure fact carries its unmeasured-lineage stamp and both amplifiers, and beside it
(lane 15, #3691) the WAL-volume CONTEXT fact states the planted rate — 64 MiB a minute ≈ 1.12 MB/s mean
and peak alike, base 0, threshold_lineage 1 (no bar was chosen). */
var factsJson = await DarlingMcpTools.GetAnalysisFacts(service, postgres, ChurnServerName, 4, PgTargetSources.WriteSource, as_of: asOf);
using (var doc = JsonDocument.Parse(factsJson))
{
var fact = Assert.Single(doc.RootElement.GetProperty("facts").EnumerateArray());
Assert.Equal(PgTargetFactKeys.CheckpointPressure, fact.GetProperty("key").GetString());
var writeFacts = doc.RootElement.GetProperty("facts").EnumerateArray().ToList();
Assert.Equal(2, writeFacts.Count);
var shift = writeFacts.Single(f => f.GetProperty("key").GetString() == PgTargetFactKeys.WalVolumeShift);
Assert.Equal(0, shift.GetProperty("base_severity").GetDouble());
Assert.Equal(64.0 * 1024 * 1024 / 60, shift.GetProperty("value").GetDouble(), precision: 0);
Assert.Equal(1, shift.GetProperty("metadata").GetProperty("wal_tracked").GetDouble());
Assert.Equal(1, shift.GetProperty("metadata").GetProperty("threshold_lineage").GetDouble());
Assert.Equal(240, shift.GetProperty("metadata").GetProperty("rated_samples").GetDouble());

var fact = writeFacts.Single(f => f.GetProperty("key").GetString() == PgTargetFactKeys.CheckpointPressure);
var metadata = fact.GetProperty("metadata");
Assert.Equal(0, metadata.GetProperty("threshold_lineage").GetDouble());
Assert.Equal(240, metadata.GetProperty("checkpoints_requested").GetDouble());
Expand Down
Loading
Loading