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
14 changes: 9 additions & 5 deletions Dashboard/Analysis/FactScorer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,10 @@ private static double ScoreBadActorFact(Fact fact)
/// </summary>
private static double ScoreAnomalyFact(Fact fact)
{
if (fact.Key.StartsWith("ANOMALY_CPU_SPIKE") || fact.Key.StartsWith("ANOMALY_READ_LATENCY")
|| fact.Key.StartsWith("ANOMALY_WRITE_LATENCY"))
if ( fact.Key.StartsWith("ANOMALY_CPU_SPIKE" , StringComparison.OrdinalIgnoreCase)
|| fact.Key.StartsWith("ANOMALY_READ_LATENCY" , StringComparison.OrdinalIgnoreCase)
|| fact.Key.StartsWith("ANOMALY_WRITE_LATENCY", StringComparison.OrdinalIgnoreCase)
)
{
// Deviation-based scoring: 2σ = 0.5, 4σ = 1.0
var deviation = fact.Metadata.GetValueOrDefault("deviation_sigma");
Expand All @@ -319,15 +321,17 @@ private static double ScoreAnomalyFact(Fact fact)
return base_score * confidence;
}

if (fact.Key.StartsWith("ANOMALY_WAIT_"))
if (fact.Key.StartsWith("ANOMALY_WAIT_", StringComparison.OrdinalIgnoreCase))
{
// Ratio-based scoring: 5x = 0.5, 20x = 1.0
var ratio = fact.Metadata.GetValueOrDefault("ratio");
if (ratio < 5) return 0.0;
return 0.5 + 0.5 * Math.Min((ratio - 5.0) / 15.0, 1.0);
}

if (fact.Key.StartsWith("ANOMALY_BLOCKING_SPIKE") || fact.Key.StartsWith("ANOMALY_DEADLOCK_SPIKE"))
if ( fact.Key.StartsWith("ANOMALY_BLOCKING_SPIKE", StringComparison.OrdinalIgnoreCase)
|| fact.Key.StartsWith("ANOMALY_DEADLOCK_SPIKE", StringComparison.OrdinalIgnoreCase)
)
{
// Ratio-based: 3x = 0.5, 10x = 1.0
var ratio = fact.Metadata.GetValueOrDefault("ratio");
Expand Down Expand Up @@ -859,7 +863,7 @@ private static (double concerning, double? critical)? GetWaitThresholds(string w
/// <summary>
/// An amplifier definition: a named predicate that boosts severity when matched.
/// </summary>
internal class AmplifierDefinition
internal sealed class AmplifierDefinition
{
public string Description { get; set; } = string.Empty;
public double Boost { get; set; }
Expand Down
13 changes: 9 additions & 4 deletions Dashboard/Analysis/SqlServerDrillDownCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,15 @@ public async Task EnrichFindingsAsync(List<AnalysisFinding> findings, AnalysisCo
if (pathKeys.Contains("QUERY_SPILLS"))
await CollectTopSpillingQueries(finding, context);

if (pathKeys.Contains("IO_READ_LATENCY_MS") || pathKeys.Contains("IO_WRITE_LATENCY_MS"))
if ( pathKeys.Contains("IO_READ_LATENCY_MS")
|| pathKeys.Contains("IO_WRITE_LATENCY_MS")
)
await CollectFileLatencyBreakdown(finding, context);

if (pathKeys.Contains("LCK") || pathKeys.Contains("LCK_M_S") || pathKeys.Contains("LCK_M_IS"))
if ( pathKeys.Contains("LCK")
|| pathKeys.Contains("LCK_M_S")
|| pathKeys.Contains("LCK_M_IS")
)
await CollectLockModeBreakdown(finding, context);

if (pathKeys.Contains("DB_CONFIG"))
Expand All @@ -77,7 +82,7 @@ public async Task EnrichFindingsAsync(List<AnalysisFinding> findings, AnalysisCo
if (pathKeys.Contains("MEMORY_GRANT_PENDING"))
await CollectPendingGrants(finding, context);

if (pathKeys.Any(k => k.StartsWith("BAD_ACTOR_")))
if (pathKeys.Any(k => k.StartsWith("BAD_ACTOR_", StringComparison.OrdinalIgnoreCase)))
await CollectBadActorDetail(finding, context);

// Plan analysis: for findings with top queries, analyze their cached plans
Expand Down Expand Up @@ -618,7 +623,7 @@ private async Task CollectPlanAnalysis(AnalysisFinding finding, AnalysisContext

// Only analyze plans for bad actor findings (1 plan each).
// Skip top_cpu_queries (5 plans would be too heavy).
if (!finding.RootFactKey.StartsWith("BAD_ACTOR_")) return;
if (!finding.RootFactKey.StartsWith("BAD_ACTOR_", StringComparison.OrdinalIgnoreCase)) return;

var queryHash = finding.RootFactKey.Replace("BAD_ACTOR_", "");
if (string.IsNullOrEmpty(queryHash)) return;
Expand Down
8 changes: 4 additions & 4 deletions Dashboard/Analysis/SqlServerFactCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1671,15 +1671,15 @@ private static void GroupParallelismWaits(List<Fact> facts, AnalysisContext cont
/// </summary>
private static bool IsGeneralLockWait(string waitType)
{
if (!waitType.StartsWith("LCK_M_")) return false;
if (!waitType.StartsWith("LCK_M_", StringComparison.OrdinalIgnoreCase)) return false;

// Keep individual: reader/writer locks
if (waitType is "LCK_M_S" or "LCK_M_IS") return false;

// Keep individual: range locks (serializable/repeatable read)
if (waitType.StartsWith("LCK_M_RS_") ||
waitType.StartsWith("LCK_M_RIn_") ||
waitType.StartsWith("LCK_M_RX_")) return false;
if (waitType.StartsWith("LCK_M_RS_", StringComparison.OrdinalIgnoreCase) ||
waitType.StartsWith("LCK_M_RIn_", StringComparison.OrdinalIgnoreCase) ||
waitType.StartsWith("LCK_M_RX_", StringComparison.OrdinalIgnoreCase)) return false;

// Everything else (X, U, IX, SIX, BU, IU, UIX, etc.) -> group
return true;
Expand Down
1 change: 0 additions & 1 deletion Dashboard/Controls/FinOpsContent.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using Microsoft.Win32;
using PerformanceMonitorDashboard.Helpers;
Expand Down
2 changes: 1 addition & 1 deletion Dashboard/Controls/PlanViewerControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ private WpfPath CreateElbowConnector(PlanNode parent, PlanNode child)
};
}

private object BuildEdgeTooltipContent(PlanNode child)
private Border BuildEdgeTooltipContent(PlanNode child)
{
var grid = new Grid { MinWidth = 240 };
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
Expand Down
6 changes: 3 additions & 3 deletions Dashboard/Dashboard.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@
<PackageReference Include="CredentialManagement" Version="1.0.2" />
<PackageReference Include="Hardcodet.NotifyIcon.Wpf" Version="2.0.1" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.4" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.5" />
<PackageReference Include="ScottPlot.WPF" Version="5.1.57" />
<PackageReference Include="ModelContextProtocol" Version="0.7.0-preview.1" />
<PackageReference Include="ModelContextProtocol.AspNetCore" Version="0.7.0-preview.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.5" />
<PackageReference Include="Velopack" Version="0.0.1298" />
</ItemGroup>

Expand Down
14 changes: 8 additions & 6 deletions Dashboard/Mcp/McpAnalysisTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -433,15 +433,17 @@ public static System.Collections.Generic.List<object> GetForStoryPath(string sto
{
if (!ByFactKey.TryGetValue(key, out var recommendations))
{
if (key.StartsWith("BAD_ACTOR_"))
if (key.StartsWith("BAD_ACTOR_", StringComparison.OrdinalIgnoreCase))
ByFactKey.TryGetValue("BAD_ACTOR", out recommendations);
else if (key.StartsWith("ANOMALY_CPU"))
else if (key.StartsWith("ANOMALY_CPU", StringComparison.OrdinalIgnoreCase))
ByFactKey.TryGetValue("ANOMALY_CPU", out recommendations);
else if (key.StartsWith("ANOMALY_WAIT_"))
else if (key.StartsWith("ANOMALY_WAIT_", StringComparison.OrdinalIgnoreCase))
ByFactKey.TryGetValue("ANOMALY_WAIT", out recommendations);
else if (key.StartsWith("ANOMALY_BLOCKING") || key.StartsWith("ANOMALY_DEADLOCK"))
else if (key.StartsWith("ANOMALY_BLOCKING", StringComparison.OrdinalIgnoreCase)
|| key.StartsWith("ANOMALY_DEADLOCK", StringComparison.OrdinalIgnoreCase))
ByFactKey.TryGetValue("ANOMALY_BLOCKING", out recommendations);
else if (key.StartsWith("ANOMALY_READ") || key.StartsWith("ANOMALY_WRITE"))
else if (key.StartsWith("ANOMALY_READ", StringComparison.OrdinalIgnoreCase)
|| key.StartsWith("ANOMALY_WRITE", StringComparison.OrdinalIgnoreCase))
ByFactKey.TryGetValue("ANOMALY_IO", out recommendations);
if (recommendations == null) continue;
}
Expand All @@ -459,7 +461,7 @@ public static System.Collections.Generic.List<object> GetForStoryPath(string sto
}
}

internal record ToolRecommendation(
internal sealed record ToolRecommendation(
string Tool,
string Reason,
System.Collections.Generic.Dictionary<string, string>? SuggestedParams = null);
4 changes: 2 additions & 2 deletions Dashboard/Services/PlanAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@
private static void CheckForTableVariables(PlanNode node, bool isModification,
ref bool hasTableVar, ref bool modifiesTableVar)
{
if (!string.IsNullOrEmpty(node.ObjectName) && node.ObjectName.StartsWith("@"))
if (!string.IsNullOrEmpty(node.ObjectName) && node.ObjectName.StartsWith("@", StringComparison.OrdinalIgnoreCase))
{
hasTableVar = true;
if (isModification && (node.PhysicalOp.Contains("Insert", StringComparison.OrdinalIgnoreCase)
Expand Down Expand Up @@ -573,7 +573,7 @@
"Leading wildcard LIKE prevents an index seek — SQL Server must scan every row. If substring search performance is critical, consider a full-text index or a trigram-based approach.",
"CASE expression in predicate" =>
"CASE expression in a predicate prevents an index seek. Rewrite using separate WHERE clauses combined with OR, or split into multiple queries.",
_ when nonSargableReason.StartsWith("Function call") =>
_ when nonSargableReason.StartsWith("Function call", StringComparison.OrdinalIgnoreCase) =>
$"{nonSargableReason} prevents an index seek. Remove the function from the column side — apply it to the parameter instead, or create a computed column with the expression and index that.",
_ =>
$"{nonSargableReason} prevents an index seek, forcing a scan."
Expand Down Expand Up @@ -841,7 +841,7 @@
// Rule 28: Row Count Spool — NOT IN with nullable column
// Pattern: Row Count Spool with high rewinds, child scan has IS NULL predicate,
// and statement text contains NOT IN
if (node.PhysicalOp.Contains("Row Count Spool"))

Check warning on line 844 in Dashboard/Services/PlanAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 844 in Dashboard/Services/PlanAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 844 in Dashboard/Services/PlanAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 844 in Dashboard/Services/PlanAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 844 in Dashboard/Services/PlanAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 844 in Dashboard/Services/PlanAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
{
var rewinds = node.HasActualStats ? (double)node.ActualRewinds : node.EstimateRewinds;
if (rewinds > 10000 && HasNotInPattern(node, stmt))
Expand Down
Loading
Loading