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
43 changes: 42 additions & 1 deletion Dashboard/Controls/QueryPerformanceContent.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -1417,7 +1417,8 @@
<DataGrid x:Name="QueryStoreRegressionsDataGrid" AutoGenerateColumns="False" IsReadOnly="True"
RowHeight="35" GridLinesVisibility="Horizontal" CanUserResizeColumns="True"
ScrollViewer.CanContentScroll="True" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto"
RowStyle="{DynamicResource DefaultRowStyle}">
RowStyle="{DynamicResource DefaultRowStyle}"
MouseDoubleClick="QueryStoreRegressionsDataGrid_MouseDoubleClick">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding LastExecutionTime, StringFormat='{}{0:yyyy-MM-dd HH:mm:ss}'}" Width="150">
<DataGridTextColumn.Header>
Expand Down Expand Up @@ -1451,6 +1452,46 @@
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding AdditionalDurationMs, StringFormat='{}{0:N0}'}" ElementStyle="{StaticResource NumericCell}" Width="110">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="AdditionalDurationMs" Click="QsRegressionsFilter_Click" Margin="0,0,4,0"/>
<TextBlock Text="Total Impact (ms)" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding BaselineExecCount, StringFormat='{}{0:N0}'}" ElementStyle="{StaticResource NumericCell}" Width="90">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="BaselineExecCount" Click="QsRegressionsFilter_Click" Margin="0,0,4,0"/>
<TextBlock Text="Base Execs" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding RecentExecCount, StringFormat='{}{0:N0}'}" ElementStyle="{StaticResource NumericCell}" Width="90">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="RecentExecCount" Click="QsRegressionsFilter_Click" Margin="0,0,4,0"/>
<TextBlock Text="Recent Execs" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding BaselinePlanCount}" ElementStyle="{StaticResource NumericCell}" Width="80">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="BaselinePlanCount" Click="QsRegressionsFilter_Click" Margin="0,0,4,0"/>
<TextBlock Text="Base Plans" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding RecentPlanCount}" ElementStyle="{StaticResource NumericCell}" Width="80">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="RecentPlanCount" Click="QsRegressionsFilter_Click" Margin="0,0,4,0"/>
<TextBlock Text="Recent Plans" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding DurationRegressionPercent, StringFormat='{}{0:N2}%'}" ElementStyle="{StaticResource NumericCell}" Width="110">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
Expand Down
32 changes: 32 additions & 0 deletions Dashboard/Controls/QueryPerformanceContent.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,38 @@ private void QueryStoreDataGrid_MouseDoubleClick(object sender, MouseButtonEvent
}
}

private void QueryStoreRegressionsDataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (!TabHelpers.IsDoubleClickOnRow((DependencyObject)e.OriginalSource)) return;
if (_databaseService == null) return;

if (QueryStoreRegressionsDataGrid.SelectedItem is QueryStoreRegressionItem item)
{
if (string.IsNullOrEmpty(item.DatabaseName) || item.QueryId <= 0)
{
MessageBox.Show(
"Unable to show history: missing database name or query ID.",
"Information",
MessageBoxButton.OK,
MessageBoxImage.Information
);
return;
}

var historyWindow = new QueryExecutionHistoryWindow(
_databaseService,
item.DatabaseName,
item.QueryId,
"Query Store",
_queryStoreHoursBack,
_queryStoreFromDate,
_queryStoreToDate
);
historyWindow.Owner = Window.GetWindow(this);
historyWindow.ShowDialog();
}
}

private void ProcStatsDataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (!TabHelpers.IsDoubleClickOnRow((DependencyObject)e.OriginalSource)) return;
Expand Down
5 changes: 5 additions & 0 deletions Dashboard/Models/QueryStoreRegressionItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public class QueryStoreRegressionItem
public decimal BaselineReads { get; set; }
public decimal RecentReads { get; set; }
public decimal IoRegressionPercent { get; set; }
public decimal AdditionalDurationMs { get; set; }
public long BaselineExecCount { get; set; }
public long RecentExecCount { get; set; }
public int BaselinePlanCount { get; set; }
public int RecentPlanCount { get; set; }
public string Severity { get; set; } = string.Empty;
public string QueryTextSample { get; set; } = string.Empty;
public DateTime? LastExecutionTime { get; set; }
Expand Down
4 changes: 2 additions & 2 deletions Dashboard/ProcedureHistoryWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="2*" MinHeight="250"/>
<RowDefinition Height="3*"/>
<RowDefinition Height="3*" MinHeight="300"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>

Expand Down
2 changes: 1 addition & 1 deletion Dashboard/ProcedureHistoryWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ private void UpdateChart()
scatter.Color = color;

// Sparse data: show only markers to avoid misleading interpolated lines
if (dates.Length <= 10)
if (dates.Length <= 1)
{
scatter.LineWidth = 0;
scatter.MarkerSize = 8;
Expand Down
4 changes: 2 additions & 2 deletions Dashboard/QueryExecutionHistoryWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="2*" MinHeight="250"/>
<RowDefinition Height="3*"/>
<RowDefinition Height="3*" MinHeight="300"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>

Expand Down
14 changes: 9 additions & 5 deletions Dashboard/QueryExecutionHistoryWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ private void UpdateChart()
};

int colorIndex = 0;
var scatterSeries = new List<(ScottPlot.Plottables.Scatter Scatter, string Label)>();

foreach (var planGroup in planGroups)
{
Expand All @@ -197,10 +198,12 @@ private void UpdateChart()
var color = colors[colorIndex % colors.Length];
var scatter = HistoryChart.Plot.Add.Scatter(dates, values);
scatter.Color = color;
scatter.LegendText = $"Plan {planGroup.Key}";
var label = $"Plan {planGroup.Key}";
scatter.LegendText = label;

// Sparse data: show only markers to avoid misleading interpolated lines
if (dates.Length <= 10)
// Sparse data: use total dataset size, not per-plan size, since
// data is split across plan groups
if (_historyData.Count <= 1)
{
scatter.LineWidth = 0;
scatter.MarkerSize = 8;
Expand All @@ -211,6 +214,7 @@ private void UpdateChart()
scatter.MarkerSize = 4;
}

scatterSeries.Add((scatter, label));
colorIndex++;
}

Expand All @@ -228,8 +232,8 @@ private void UpdateChart()
else
_chartHover.Unit = unit;
_chartHover.Clear();
foreach (var p in HistoryChart.Plot.GetPlottables().OfType<ScottPlot.Plottables.Scatter>())
_chartHover.Add(p, p.LegendText ?? "");
foreach (var (s, l) in scatterSeries)
_chartHover.Add(s, l);

// Update legend text
ChartLegendText.Text = planGroups.Count > 1
Expand Down
4 changes: 2 additions & 2 deletions Dashboard/QueryStatsHistoryWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="2*" MinHeight="250"/>
<RowDefinition Height="3*"/>
<RowDefinition Height="3*" MinHeight="300"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>

Expand Down
2 changes: 1 addition & 1 deletion Dashboard/QueryStatsHistoryWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ private void UpdateChart()
scatter.Color = color;

// Sparse data: show only markers to avoid misleading interpolated lines
if (dates.Length <= 10)
if (dates.Length <= 1)
{
scatter.LineWidth = 0;
scatter.MarkerSize = 8;
Expand Down
30 changes: 21 additions & 9 deletions Dashboard/Services/DatabaseService.QueryPerformance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1284,9 +1284,12 @@ public async Task<List<QueryStoreRegressionItem>> GetQueryStoreRegressionsAsync(
var connection = tc.Connection;

/*
report.query_store_regressions is now an inline TVF requiring parameters:
- @start_date: divides baseline from recent (baseline = data BEFORE this date)
- @end_date: end of recent period (recent = data between start_date and end_date)
report.query_store_regressions inline TVF:
- Bounded baseline (mirror window before @start_date, same duration as recent)
- Weighted averages (execution-count weighted)
- Multi-metric detection (duration, CPU, or reads >25%)
- Absolute minimums (baseline >= 1ms duration or >= 100 reads)
- Ranked by additional_duration_ms (total extra time = delta * exec count)
*/
string query = @"
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
Expand All @@ -1303,17 +1306,22 @@ public async Task<List<QueryStoreRegressionItem>> GetQueryStoreRegressionsAsync(
qsr.baseline_reads,
qsr.recent_reads,
qsr.io_regression_percent,
qsr.additional_duration_ms,
qsr.baseline_exec_count,
qsr.recent_exec_count,
qsr.baseline_plan_count,
qsr.recent_plan_count,
qsr.severity,
qsr.query_text_sample,
qsr.last_execution_time
FROM report.query_store_regressions(@start_date, @end_date) AS qsr
ORDER BY
qsr.duration_regression_percent DESC;";
qsr.additional_duration_ms DESC;";

using var command = new SqlCommand(query, connection);
command.CommandTimeout = 120;

/*Calculate the time window - baseline is everything before start_date, recent is start_date to end_date*/
/*Calculate the time window - baseline is mirror window before start_date, recent is start_date to end_date*/
DateTime startDate;
DateTime endDate;

Expand All @@ -1337,7 +1345,6 @@ ORDER BY
using var reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
/*Use Convert.ToDecimal for robustness - TVF may return bigint or decimal depending on query store aggregations*/
items.Add(new QueryStoreRegressionItem
{
DatabaseName = reader.GetString(0),
Expand All @@ -1351,9 +1358,14 @@ ORDER BY
BaselineReads = reader.IsDBNull(8) ? 0 : Convert.ToDecimal(reader.GetValue(8), CultureInfo.InvariantCulture),
RecentReads = reader.IsDBNull(9) ? 0 : Convert.ToDecimal(reader.GetValue(9), CultureInfo.InvariantCulture),
IoRegressionPercent = reader.IsDBNull(10) ? 0 : Convert.ToDecimal(reader.GetValue(10), CultureInfo.InvariantCulture),
Severity = reader.IsDBNull(11) ? string.Empty : reader.GetString(11),
QueryTextSample = reader.IsDBNull(12) ? string.Empty : reader.GetString(12),
LastExecutionTime = reader.IsDBNull(13) ? null : reader.GetDateTime(13)
AdditionalDurationMs = reader.IsDBNull(11) ? 0 : Convert.ToDecimal(reader.GetValue(11), CultureInfo.InvariantCulture),
BaselineExecCount = reader.IsDBNull(12) ? 0 : Convert.ToInt64(reader.GetValue(12)),
RecentExecCount = reader.IsDBNull(13) ? 0 : Convert.ToInt64(reader.GetValue(13)),
BaselinePlanCount = reader.IsDBNull(14) ? 0 : Convert.ToInt32(reader.GetValue(14)),
RecentPlanCount = reader.IsDBNull(15) ? 0 : Convert.ToInt32(reader.GetValue(15)),
Severity = reader.IsDBNull(16) ? string.Empty : reader.GetString(16),
QueryTextSample = reader.IsDBNull(17) ? string.Empty : reader.GetString(17),
LastExecutionTime = reader.IsDBNull(18) ? null : reader.GetDateTime(18)
});
}

Expand Down
Loading