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
5 changes: 4 additions & 1 deletion Darling/Darling.Tests/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,10 @@
}
},
"performancemonitor.analysis": {
"type": "Project"
"type": "Project",
"dependencies": {
"PerformanceMonitor.Common": "[3.10.0, )"
}
},
"performancemonitor.collectors": {
"type": "Project",
Expand Down
47 changes: 21 additions & 26 deletions Darling/PerformanceMonitor.Darling.Viewer/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -464,17 +464,20 @@
"performancemonitor.alerting": {
"type": "Project",
"dependencies": {
"PerformanceMonitor.Common": "[3.9.0, )",
"PerformanceMonitor.Notifications": "[3.9.0, )"
"PerformanceMonitor.Common": "[3.10.0, )",
"PerformanceMonitor.Notifications": "[3.10.0, )"
}
},
"performancemonitor.analysis": {
"type": "Project"
"type": "Project",
"dependencies": {
"PerformanceMonitor.Common": "[3.10.0, )"
}
},
"performancemonitor.collectors": {
"type": "Project",
"dependencies": {
"PerformanceMonitor.Common": "[3.9.0, )"
"PerformanceMonitor.Common": "[3.10.0, )"
}
},
"performancemonitor.common": {
Expand All @@ -491,45 +494,43 @@
"Microsoft.Data.SqlClient": "[7.1.1, )",
"Microsoft.Data.SqlClient.Extensions.Azure": "[7.1.1, )",
"ModelContextProtocol": "[2.2.0, )",
"PerformanceMonitor.Analysis": "[3.9.0, )",
"PerformanceMonitor.Collectors": "[3.9.0, )",
"PerformanceMonitor.Common": "[3.9.0, )",
"PerformanceMonitor.Darling.Storage": "[3.9.0, )",
"PerformanceMonitor.Notifications": "[3.9.0, )",
"PerformanceMonitor.PlanAnalysis": "[3.9.0, )",
"System.Security.Cryptography.ProtectedData": "[10.0.11, )"
"PerformanceMonitor.Analysis": "[3.10.0, )",
"PerformanceMonitor.Collectors": "[3.10.0, )",
"PerformanceMonitor.Common": "[3.10.0, )",
"PerformanceMonitor.Darling.Storage": "[3.10.0, )",
"PerformanceMonitor.Notifications": "[3.10.0, )",
"PerformanceMonitor.PlanAnalysis": "[3.10.0, )"
}
},
"performancemonitor.darling.storage": {
"type": "Project",
"dependencies": {
"Npgsql": "[10.0.3, )",
"PerformanceMonitor.Analysis": "[3.9.0, )",
"PerformanceMonitor.Collectors": "[3.9.0, )",
"PerformanceMonitor.Common": "[3.9.0, )",
"PerformanceMonitor.PlanAnalysis": "[3.9.0, )"
"PerformanceMonitor.Analysis": "[3.10.0, )",
"PerformanceMonitor.Collectors": "[3.10.0, )",
"PerformanceMonitor.Common": "[3.10.0, )",
"PerformanceMonitor.PlanAnalysis": "[3.10.0, )"
}
},
"performancemonitor.notifications": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[10.0.12, )",
"PerformanceMonitor.Analysis": "[3.9.0, )"
"PerformanceMonitor.Analysis": "[3.10.0, )"
}
},
"performancemonitor.plananalysis": {
"type": "Project",
"dependencies": {
"Microsoft.Data.SqlClient": "[7.1.1, )",
"PerformanceMonitor.Common": "[3.9.0, )",
"System.Security.Cryptography.ProtectedData": "[10.0.11, )"
"PerformanceMonitor.Common": "[3.10.0, )"
}
},
"performancemonitor.ui": {
"type": "Project",
"dependencies": {
"PerformanceMonitor.Common": "[3.9.0, )",
"PerformanceMonitor.PlanAnalysis": "[3.9.0, )",
"PerformanceMonitor.Common": "[3.10.0, )",
"PerformanceMonitor.PlanAnalysis": "[3.10.0, )",
"ScottPlot.WPF": "[5.1.59, )"
}
},
Expand Down Expand Up @@ -599,12 +600,6 @@
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "10.0.0"
}
},
"System.Security.Cryptography.ProtectedData": {
"type": "CentralTransitive",
"requested": "[10.0.12, )",
"resolved": "10.0.11",
"contentHash": "PNoxCTPb+Tlux+GJyq4c89ddYdpioVSqfGx8pqOF6shCSKwUNNctXhQtRkCICjbJmGrJJsW8NY52kVYO/b8mlQ=="
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,88 +17,126 @@
namespace PerformanceMonitorLite.Tests;

/// <summary>
/// #5711: an all-zero <c>query_hash</c> is "the engine did not say", never a query, and the two grouping sites over
/// #5711 / #5758: an all-zero <c>query_hash</c> is "the engine did not say", never a query, and the two grouping sites over
/// <c>query_snapshots.query_hash</c> (the same-statement pileup detector and the long-running-query alert's incident
/// key) must not pool unrelated requests under it.
///
/// <para><b>The parity half.</b> <c>PerformanceMonitor.Analysis</c> cannot reference <c>PerformanceMonitor.Common</c>
/// (a ProjectReference changes the committed lock files of the deprecated Dashboard projects too), so the detector
/// carries a copy of <see cref="QueryHashIdentity"/>'s hash test and statement fingerprint. Two copies of one
/// definition fail only after BOTH drift (#3684) unless they are run over one corpus, which is what
/// <see cref="TheDetectorsCopy_AgreesWithTheCommonDefinition_OverOneCorpus"/> does.</para>
/// <para>The rule has one definition, <see cref="QueryHashIdentity"/> in <c>PerformanceMonitor.Common</c>; the detector's
/// <see cref="SameStatementPileupDetector.StatementIdentity"/> calls it (#5758). These tests pin that definition's
/// cases (the corpus below, with each expected identity written out) and the detector's row path over the same corpus.</para>
/// </summary>
public class QueryHashIdentityParityTests
public class QueryHashIdentityTests
{
private const string Zero = "0x0000000000000000";
private const string RealHash = "0x8A2F19C4BE77D301";
private const string Marker = "-- statement text withheld (#4348)";

/// <summary>Hashes: NULL, empty, blanks, the zero spellings, a real hash in two cases, padded.</summary>
/// <summary>
/// Hashes: NULL, empty, blanks, the zero spellings, a real hash in two cases, padded. Each with whether it is unknown
/// and, when known, its identity (trimmed, lower-cased).
/// </summary>
public static IEnumerable<object?[]> Hashes() =>
new[] { null, "", " ", "0", "00", "0x", "0X0", Zero, "0X0000000000000000", " 0x0000000000000000 ", RealHash, RealHash.ToLowerInvariant(), " " + RealHash + " ", "0x0000000000000001", "x0" }
.Select(h => new object?[] { h });
[
[null, true, null],
["", true, null],
[" ", true, null],
["0", true, null],
["00", true, null],
["0x", false, "0x"],
["0X0", true, null],
[Zero, true, null],
["0X0000000000000000", true, null],
[" 0x0000000000000000 ", true, null],
[RealHash, false, "0x8a2f19c4be77d301"],
[RealHash.ToLowerInvariant(), false, "0x8a2f19c4be77d301"],
[" " + RealHash + " ", false, "0x8a2f19c4be77d301"],
["0x0000000000000001", false, "0x0000000000000001"],
["x0", false, "x0"],
];

/// <summary>Statement text: whitespace, case, comments, literals, the withheld marker, empty.</summary>
public static readonly string?[] Texts =
/// <summary>
/// Statement text: whitespace, case, comments, literals, the withheld marker, empty; each with its fingerprint
/// (NULL for no usable text and for the marker). Texts that differ only in case or whitespace share a fingerprint.
/// </summary>
public static IEnumerable<object?[]> Texts() =>
[
null,
"",
" \t\r\n ",
"select 1",
"SELECT 1",
"select\r\n1",
" select 1 ",
"select 2",
"SELECT * FROM dbo.Orders WHERE OrderId = 42",
"select * from dbo.orders where orderid = 43",
"select * from dbo.orders /* hint */ where orderid = 42",
"select 'A' + 'b'",
"select 'a' + 'B'",
Marker,
" " + Marker + " ",
Marker + " ",
"-- statement text withheld",
"BACKUP DATABASE [tenant_shared] TO DISK = N'nul'",
"DBCC CHECKDB (N'tenant_shared') WITH NO_INFOMSGS",
"café",
"CAFÉ",
[null, null],
["", null],
[" \t\r\n ", null],
["select 1", "text:822ae07d4783158b"],
["SELECT 1", "text:822ae07d4783158b"],
["select\r\n1", "text:822ae07d4783158b"],
[" select 1 ", "text:822ae07d4783158b"],
["select 2", "text:3cf988cc782b44bc"],
["SELECT * FROM dbo.Orders WHERE OrderId = 42", "text:d1b5d6fb3619562c"],
["select * from dbo.orders where orderid = 43", "text:ead1ef3092666a9b"],
["select * from dbo.orders /* hint */ where orderid = 42", "text:f6e81ab16a1fa8dd"],
["select 'A' + 'b'", "text:f4150cb87bc18753"],
["select 'a' + 'B'", "text:f4150cb87bc18753"],
[Marker, null],
[" " + Marker + " ", null],
[Marker + " ", null],
["-- statement text withheld", "text:b269cb30b8cebcbd"],
["BACKUP DATABASE [tenant_shared] TO DISK = N'nul'", "text:74738d493704fd0c"],
["DBCC CHECKDB (N'tenant_shared') WITH NO_INFOMSGS", "text:41d3551a3531440e"],
["café", "text:850f7dc43910ff89"],
["CAFÉ", "text:850f7dc43910ff89"],
];

private static SameStatementPileupDetector.SnapshotRow Row(string? hash, string? text) =>
new(DateTime.UtcNow, 1, hash, text, "db", "running", null, null, 30_000, 100, 1_000, 0, text);

[Theory]
[MemberData(nameof(Hashes))]
public void IsUnknown_AndKnownHash_FollowTheHashCorpus(string? hash, bool unknown, string? identity)
{
Assert.Equal(unknown, QueryHashIdentity.IsUnknown(hash));
Assert.Equal(unknown ? null : hash!.Trim(), QueryHashIdentity.KnownHash(hash));
Assert.Equal(identity is null, unknown);
}

[Theory]
[MemberData(nameof(Texts))]
public void StatementFingerprint_FollowsTheTextCorpus(string? text, string? fingerprint) =>
Assert.Equal(fingerprint, QueryHashIdentity.StatementFingerprint(text));

/// <summary>
/// Every (hash, text) pair in the corpus keys identically through the detector's copy and through
/// <see cref="QueryHashIdentity.Identity"/> — which covers the hash test (<c>IsUnknown</c> against the detector's
/// <c>IsUnknownHash</c>) and the fingerprint (<c>StatementFingerprint</c> against the detector's text hash) at
/// once, including the shapes that mint NULL.
/// Every (hash, text) pair in the corpus has the expected identity through <see cref="QueryHashIdentity.Identity"/>
/// and through the detector's row path: the real hash (trimmed, lower-cased) when the hash is known, else the
/// text fingerprint, else NULL (the shapes that mint NULL included).
/// </summary>
[Fact]
public void TheDetectorsCopy_AgreesWithTheCommonDefinition_OverOneCorpus()
public void Identity_AndTheDetectorsRowPath_FollowTheCorpus()
{
var compared = 0;
foreach (var hashRow in Hashes())
{
var hash = (string?)hashRow[0];
foreach (var text in Texts)
var knownIdentity = (string?)hashRow[2];
foreach (var textRow in Texts())
{
var text = (string?)textRow[0];
var expected = knownIdentity ?? (string?)textRow[1];
var identity = QueryHashIdentity.Identity(hash, text);
var detector = SameStatementPileupDetector.StatementIdentity(Row(hash, text));
var common = QueryHashIdentity.Identity(hash, text);
Assert.True(string.Equals(common, detector, StringComparison.Ordinal),
$"hash={hash ?? "<null>"} text={text ?? "<null>"}: Common says {common ?? "<null>"}, the detector says {detector ?? "<null>"}");
var where = $"hash={hash ?? "<null>"} text={text ?? "<null>"}";
Assert.True(string.Equals(expected, identity, StringComparison.Ordinal),
$"{where}: expected {expected ?? "<null>"}, Identity says {identity ?? "<null>"}");
Assert.True(string.Equals(expected, detector, StringComparison.Ordinal),
$"{where}: expected {expected ?? "<null>"}, the detector says {detector ?? "<null>"}");
compared++;
}
}

Assert.Equal(Hashes().Count() * Texts.Length, compared);
Assert.Equal(Hashes().Count() * Texts().Count(), compared);
}

/// <summary>The corpus is not vacuous: it reaches a hash identity, a text fingerprint, and a NULL.</summary>
[Fact]
public void TheCorpus_ReachesEveryOutcome()
{
var identities = Hashes()
.SelectMany(h => Texts.Select(t => SameStatementPileupDetector.StatementIdentity(Row((string?)h[0], t))))
.SelectMany(h => Texts().Select(t => SameStatementPileupDetector.StatementIdentity(Row((string?)h[0], (string?)t[0]))))
.ToList();

Assert.Contains(identities, i => i is null);
Expand Down
5 changes: 4 additions & 1 deletion Lite.Tests/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,10 @@
}
},
"performancemonitor.analysis": {
"type": "Project"
"type": "Project",
"dependencies": {
"PerformanceMonitor.Common": "[3.10.0, )"
}
},
"performancemonitor.collectors": {
"type": "Project",
Expand Down
39 changes: 12 additions & 27 deletions Lite/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -428,8 +428,7 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12",
"Microsoft.Extensions.Logging": "10.0.12",
"Microsoft.Extensions.Logging.Abstractions": "10.0.12",
"Microsoft.Extensions.Options": "10.0.12",
"System.Diagnostics.EventLog": "10.0.12"
"Microsoft.Extensions.Options": "10.0.12"
}
},
"Microsoft.Extensions.Logging.EventSource": {
Expand Down Expand Up @@ -731,11 +730,6 @@
"System.Memory.Data": "10.0.1"
}
},
"System.Diagnostics.EventLog": {
"type": "Transitive",
"resolved": "10.0.12",
"contentHash": "sOZM+VyRj1pg/ItlHsvrSbjws6oGqZveTzTNBGZ9ziwRDyKsOF3ub6vhvOdxotCIqtlnyHAIwP5sLNvgiZC+pQ=="
},
"System.IdentityModel.Tokens.Jwt": {
"type": "Transitive",
"resolved": "8.16.0",
Expand All @@ -758,17 +752,20 @@
"performancemonitor.alerting": {
"type": "Project",
"dependencies": {
"PerformanceMonitor.Common": "[3.9.0, )",
"PerformanceMonitor.Notifications": "[3.9.0, )"
"PerformanceMonitor.Common": "[3.10.0, )",
"PerformanceMonitor.Notifications": "[3.10.0, )"
}
},
"performancemonitor.analysis": {
"type": "Project"
"type": "Project",
"dependencies": {
"PerformanceMonitor.Common": "[3.10.0, )"
}
},
"performancemonitor.collectors": {
"type": "Project",
"dependencies": {
"PerformanceMonitor.Common": "[3.9.0, )"
"PerformanceMonitor.Common": "[3.10.0, )"
}
},
"performancemonitor.common": {
Expand All @@ -783,22 +780,21 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[10.0.12, )",
"PerformanceMonitor.Analysis": "[3.9.0, )"
"PerformanceMonitor.Analysis": "[3.10.0, )"
}
},
"performancemonitor.plananalysis": {
"type": "Project",
"dependencies": {
"Microsoft.Data.SqlClient": "[7.1.1, )",
"PerformanceMonitor.Common": "[3.9.0, )",
"System.Security.Cryptography.ProtectedData": "[10.0.11, )"
"PerformanceMonitor.Common": "[3.10.0, )"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, but the body under-describes this regeneration. It says only that "Lite's lock drops a stale System.Diagnostics.EventLog entry"; the rewrite actually drops four things the body does not name:

  • "System.Security.Cryptography.ProtectedData": "[10.0.11, )" out of this performancemonitor.plananalysis dependency list (this hunk) — in Lite/, deprecated/Dashboard/ and Darling/PerformanceMonitor.Darling.Viewer/;
  • the System.Security.Cryptography.ProtectedData CentralTransitive entry (requested [10.0.12, ), resolved 10.0.11) in those same three;
  • System.Diagnostics.EventLog out of Microsoft.Extensions.Logging.EventLog's dependency list and as a Transitive entry in deprecated/Dashboard/ too, not just Lite — and in Lite from the net10.0-windows7.0/win-x64 section as well.

The first two read oddly on their face, because PerformanceMonitor.PlanAnalysis/PerformanceMonitor.PlanAnalysis.csproj:26 still declares that package directly:

<PackageReference Include="System.Security.Cryptography.ProtectedData" VersionOverride="10.0.11" />

I do not think this is a defect: Lite.Tests/packages.lock.json, Darling/Darling.Tests/packages.lock.json, deprecated/Dashboard.Tests/packages.lock.json and tools/ui-smoke-wpf/packages.lock.json ALREADY carry the pruned shape on the base branch (their performancemonitor.plananalysis entries list only Microsoft.Data.SqlClient + PerformanceMonitor.Common, and neither package appears anywhere in them), so the pruning is what the pinned SDK's restore produces today and these three files were simply the stragglers — the same reason they were still on [3.9.0, ) while their siblings were on [3.10.0, ). Worth saying out loud in the body, though, since "drops a stale EventLog entry" reads as one incidental line and it is actually two packages across three files, one of them still declared in a csproj.

Nothing here is checkable from a read-only tree (no SDK in the review environment), so the lock-files job in build.yml:912-920 is the only witness.

}
},
"performancemonitor.ui": {
"type": "Project",
"dependencies": {
"PerformanceMonitor.Common": "[3.9.0, )",
"PerformanceMonitor.PlanAnalysis": "[3.9.0, )",
"PerformanceMonitor.Common": "[3.10.0, )",
"PerformanceMonitor.PlanAnalysis": "[3.10.0, )",
"ScottPlot.WPF": "[5.1.59, )"
}
},
Expand Down Expand Up @@ -832,12 +828,6 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12"
}
},
"System.Security.Cryptography.ProtectedData": {
"type": "CentralTransitive",
"requested": "[10.0.12, )",
"resolved": "10.0.11",
"contentHash": "PNoxCTPb+Tlux+GJyq4c89ddYdpioVSqfGx8pqOF6shCSKwUNNctXhQtRkCICjbJmGrJJsW8NY52kVYO/b8mlQ=="
}
},
"net10.0-windows7.0/win-x64": {
Expand Down Expand Up @@ -908,11 +898,6 @@
"type": "Transitive",
"resolved": "3.119.0",
"contentHash": "IwC9yx36lOdXVT2DjgmWHl1qkVspfj8ctd4+li8CNnvqdfaTolXCOh6TLznURcPAvzatx9K/tLOB7zT6T8EA9w=="
},
"System.Diagnostics.EventLog": {
"type": "Transitive",
"resolved": "10.0.12",
"contentHash": "sOZM+VyRj1pg/ItlHsvrSbjws6oGqZveTzTNBGZ9ziwRDyKsOF3ub6vhvOdxotCIqtlnyHAIwP5sLNvgiZC+pQ=="
}
}
}
Expand Down
Loading
Loading