Skip to content

Commit 4bdbc6d

Browse files
Merge pull request #285 from erikdarlingdata/feature/issue-281-memory-grant-charts
Issue #281 Gaps 1+2: Memory grant charts + File I/O throughput/latency/queued overlay
2 parents 9b9161f + 02296a4 commit 4bdbc6d

24 files changed

Lines changed: 562 additions & 596 deletions

.github/sql/ci_validate_installation.sql

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ IF SCHEMA_ID(N'report') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: sche
3030
PRINT '';
3131

3232
/*
33-
Procedures in collect schema (37)
33+
Procedures in collect schema (36)
3434
*/
3535
PRINT 'Checking collect procedures...';
3636

@@ -65,7 +65,6 @@ IF OBJECT_ID(N'collect.tempdb_stats_collector', N'P') IS NULL BEGIN SE
6565
IF OBJECT_ID(N'collect.plan_cache_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.plan_cache_stats_collector'; END; SET @checked += 1;
6666
IF OBJECT_ID(N'collect.session_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.session_stats_collector'; END; SET @checked += 1;
6767
IF OBJECT_ID(N'collect.waiting_tasks_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.waiting_tasks_collector'; END; SET @checked += 1;
68-
IF OBJECT_ID(N'collect.session_wait_stats_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.session_wait_stats_collector'; END; SET @checked += 1;
6968
IF OBJECT_ID(N'collect.server_configuration_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.server_configuration_collector'; END; SET @checked += 1;
7069
IF OBJECT_ID(N'collect.database_configuration_collector', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.database_configuration_collector'; END; SET @checked += 1;
7170
IF OBJECT_ID(N'collect.configuration_issues_analyzer', N'P') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: collect.configuration_issues_analyzer'; END; SET @checked += 1;
@@ -142,7 +141,6 @@ IF OBJECT_ID(N'report.blocking_chain_analysis', N'V') IS NULL BEGIN
142141
IF OBJECT_ID(N'report.tempdb_contention_analysis', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.tempdb_contention_analysis'; END; SET @checked += 1;
143142
IF OBJECT_ID(N'report.parameter_sensitivity_detection', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.parameter_sensitivity_detection'; END; SET @checked += 1;
144143
IF OBJECT_ID(N'report.scheduler_cpu_analysis', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.scheduler_cpu_analysis'; END; SET @checked += 1;
145-
IF OBJECT_ID(N'report.session_wait_analysis', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.session_wait_analysis'; END; SET @checked += 1;
146144
IF OBJECT_ID(N'report.critical_issues', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.critical_issues'; END; SET @checked += 1;
147145
IF OBJECT_ID(N'report.memory_usage_trends', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.memory_usage_trends'; END; SET @checked += 1;
148146
IF OBJECT_ID(N'report.running_jobs', N'V') IS NULL BEGIN SET @missing += 1; PRINT ' MISSING: report.running_jobs'; END; SET @checked += 1;
@@ -182,7 +180,7 @@ WHERE OBJECT_SCHEMA_NAME(t.object_id) = N'config';
182180
PRINT ' collect schema tables: ' + CONVERT(varchar(10), @collect_tables);
183181
PRINT ' config schema tables: ' + CONVERT(varchar(10), @config_tables);
184182

185-
IF @collect_tables < 20 BEGIN SET @missing += 1; PRINT ' MISSING: expected >= 20 collect tables, found ' + CONVERT(varchar(10), @collect_tables); END; SET @checked += 1;
183+
IF @collect_tables < 19 BEGIN SET @missing += 1; PRINT ' MISSING: expected >= 20 collect tables, found ' + CONVERT(varchar(10), @collect_tables); END; SET @checked += 1;
186184
IF @config_tables < 5 BEGIN SET @missing += 1; PRINT ' MISSING: expected >= 5 config tables, found ' + CONVERT(varchar(10), @config_tables); END; SET @checked += 1;
187185

188186
PRINT '';

Dashboard/Controls/ResourceMetricsContent.xaml

Lines changed: 62 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -201,38 +201,77 @@
201201
</Grid>
202202
</TabItem>
203203

204-
<!-- File I/O Latency Sub-Tab - User DB only, TempDB moved to TempDB Stats -->
205-
<TabItem Header="File I/O Latency">
206-
<Grid Margin="5">
207-
<Grid.RowDefinitions>
208-
<RowDefinition Height="*"/>
209-
<RowDefinition Height="*"/>
210-
</Grid.RowDefinitions>
211-
212-
<!-- User DB Read Latency -->
213-
<Border Grid.Row="0" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1" Margin="0,0,0,2">
214-
<Grid>
204+
<!-- File I/O Sub-Tab -->
205+
<TabItem Header="File I/O">
206+
<TabControl Background="Transparent" BorderThickness="0">
207+
<!-- File I/O Latency sub-tab -->
208+
<TabItem Header="File I/O Latency">
209+
<Grid Margin="5">
215210
<Grid.RowDefinitions>
216-
<RowDefinition Height="Auto"/>
211+
<RowDefinition Height="*"/>
217212
<RowDefinition Height="*"/>
218213
</Grid.RowDefinitions>
219-
<TextBlock Grid.Row="0" Text="User DB Read Latency (ms)" FontWeight="Bold" FontSize="11" Margin="5,3" Foreground="{DynamicResource ForegroundBrush}"/>
220-
<ScottPlot:WpfPlot x:Name="UserDbReadLatencyChart" Grid.Row="1"/>
214+
215+
<!-- User DB Read Latency -->
216+
<Border Grid.Row="0" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1" Margin="0,0,0,2">
217+
<Grid>
218+
<Grid.RowDefinitions>
219+
<RowDefinition Height="Auto"/>
220+
<RowDefinition Height="*"/>
221+
</Grid.RowDefinitions>
222+
<TextBlock Grid.Row="0" Text="User DB Read Latency (ms)" FontWeight="Bold" FontSize="11" Margin="5,3" Foreground="{DynamicResource ForegroundBrush}"/>
223+
<ScottPlot:WpfPlot x:Name="UserDbReadLatencyChart" Grid.Row="1"/>
224+
</Grid>
225+
</Border>
226+
227+
<!-- User DB Write Latency -->
228+
<Border Grid.Row="1" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1" Margin="0,2,0,0">
229+
<Grid>
230+
<Grid.RowDefinitions>
231+
<RowDefinition Height="Auto"/>
232+
<RowDefinition Height="*"/>
233+
</Grid.RowDefinitions>
234+
<TextBlock Grid.Row="0" Text="User DB Write Latency (ms)" FontWeight="Bold" FontSize="11" Margin="5,3" Foreground="{DynamicResource ForegroundBrush}"/>
235+
<ScottPlot:WpfPlot x:Name="UserDbWriteLatencyChart" Grid.Row="1"/>
236+
</Grid>
237+
</Border>
221238
</Grid>
222-
</Border>
239+
</TabItem>
223240

224-
<!-- User DB Write Latency -->
225-
<Border Grid.Row="1" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1" Margin="0,2,0,0">
226-
<Grid>
241+
<!-- File I/O Throughput sub-tab -->
242+
<TabItem Header="File I/O Throughput">
243+
<Grid Margin="5">
227244
<Grid.RowDefinitions>
228-
<RowDefinition Height="Auto"/>
245+
<RowDefinition Height="*"/>
229246
<RowDefinition Height="*"/>
230247
</Grid.RowDefinitions>
231-
<TextBlock Grid.Row="0" Text="User DB Write Latency (ms)" FontWeight="Bold" FontSize="11" Margin="5,3" Foreground="{DynamicResource ForegroundBrush}"/>
232-
<ScottPlot:WpfPlot x:Name="UserDbWriteLatencyChart" Grid.Row="1"/>
248+
249+
<!-- User DB Read Throughput -->
250+
<Border Grid.Row="0" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1" Margin="0,0,0,2">
251+
<Grid>
252+
<Grid.RowDefinitions>
253+
<RowDefinition Height="Auto"/>
254+
<RowDefinition Height="*"/>
255+
</Grid.RowDefinitions>
256+
<TextBlock Grid.Row="0" Text="User DB Read Throughput (MB/s)" FontWeight="Bold" FontSize="11" Margin="5,3" Foreground="{DynamicResource ForegroundBrush}"/>
257+
<ScottPlot:WpfPlot x:Name="FileIoReadThroughputChart" Grid.Row="1"/>
258+
</Grid>
259+
</Border>
260+
261+
<!-- User DB Write Throughput -->
262+
<Border Grid.Row="1" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1" Margin="0,2,0,0">
263+
<Grid>
264+
<Grid.RowDefinitions>
265+
<RowDefinition Height="Auto"/>
266+
<RowDefinition Height="*"/>
267+
</Grid.RowDefinitions>
268+
<TextBlock Grid.Row="0" Text="User DB Write Throughput (MB/s)" FontWeight="Bold" FontSize="11" Margin="5,3" Foreground="{DynamicResource ForegroundBrush}"/>
269+
<ScottPlot:WpfPlot x:Name="FileIoWriteThroughputChart" Grid.Row="1"/>
270+
</Grid>
271+
</Border>
233272
</Grid>
234-
</Border>
235-
</Grid>
273+
</TabItem>
274+
</TabControl>
236275
</TabItem>
237276

238277
<!-- Perfmon Counters Sub-Tab -->

Dashboard/Controls/ResourceMetricsContent.xaml.cs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ public partial class ResourceMetricsContent : UserControl
8686
private Helpers.ChartHoverHelper? _spinlockStatsHover;
8787
private Helpers.ChartHoverHelper? _fileIoReadHover;
8888
private Helpers.ChartHoverHelper? _fileIoWriteHover;
89+
private Helpers.ChartHoverHelper? _fileIoReadThroughputHover;
90+
private Helpers.ChartHoverHelper? _fileIoWriteThroughputHover;
8991
private Helpers.ChartHoverHelper? _perfmonHover;
9092
private Helpers.ChartHoverHelper? _waitStatsHover;
9193
private Helpers.ChartHoverHelper? _tempdbStatsHover;
@@ -111,6 +113,8 @@ public ResourceMetricsContent()
111113
_spinlockStatsHover = new Helpers.ChartHoverHelper(SpinlockStatsChart, "collisions/sec");
112114
_fileIoReadHover = new Helpers.ChartHoverHelper(UserDbReadLatencyChart, "ms");
113115
_fileIoWriteHover = new Helpers.ChartHoverHelper(UserDbWriteLatencyChart, "ms");
116+
_fileIoReadThroughputHover = new Helpers.ChartHoverHelper(FileIoReadThroughputChart, "MB/s");
117+
_fileIoWriteThroughputHover = new Helpers.ChartHoverHelper(FileIoWriteThroughputChart, "MB/s");
114118
_perfmonHover = new Helpers.ChartHoverHelper(PerfmonCountersChart, "");
115119
_waitStatsHover = new Helpers.ChartHoverHelper(WaitStatsDetailChart, "ms/sec");
116120
_tempdbStatsHover = new Helpers.ChartHoverHelper(TempdbStatsChart, "MB");
@@ -146,6 +150,10 @@ private void SetupChartContextMenus()
146150
// File I/O Latency charts
147151
TabHelpers.SetupChartContextMenu(UserDbReadLatencyChart, "UserDB_Read_Latency", "collect.file_io_stats");
148152
TabHelpers.SetupChartContextMenu(UserDbWriteLatencyChart, "UserDB_Write_Latency", "collect.file_io_stats");
153+
154+
// File I/O Throughput charts
155+
TabHelpers.SetupChartContextMenu(FileIoReadThroughputChart, "UserDB_Read_Throughput", "collect.file_io_stats");
156+
TabHelpers.SetupChartContextMenu(FileIoWriteThroughputChart, "UserDB_Write_Throughput", "collect.file_io_stats");
149157
TabHelpers.SetupChartContextMenu(TempDbLatencyChart, "TempDB_Latency", "collect.file_io_stats");
150158

151159
// Server Utilization Trends charts
@@ -225,6 +233,7 @@ await Task.WhenAll(
225233
RefreshTempdbStatsAsync(),
226234
RefreshSessionStatsAsync(),
227235
LoadFileIoLatencyChartsAsync(),
236+
LoadFileIoThroughputChartsAsync(),
228237
RefreshServerTrendsAsync(),
229238
RefreshPerfmonCountersTabAsync(),
230239
RefreshWaitStatsDetailTabAsync()
@@ -837,11 +846,11 @@ private async Task LoadFileIoLatencyChartsAsync()
837846

838847
// Load User DB data only - TempDB latency moved to TempDB Stats tab
839848
var userDbData = await _databaseService.GetFileIoLatencyTimeSeriesAsync(isTempDb: false, _fileIoHoursBack, _fileIoFromDate, _fileIoToDate);
840-
LoadFileIoChart(UserDbReadLatencyChart, userDbData, d => d.ReadLatencyMs, "Read Latency (ms)", colors, xMin, xMax, _fileIoReadHover);
841-
LoadFileIoChart(UserDbWriteLatencyChart, userDbData, d => d.WriteLatencyMs, "Write Latency (ms)", colors, xMin, xMax, _fileIoWriteHover);
849+
LoadFileIoChart(UserDbReadLatencyChart, userDbData, d => d.ReadLatencyMs, "Read Latency (ms)", colors, xMin, xMax, _fileIoReadHover, d => d.ReadQueuedLatencyMs);
850+
LoadFileIoChart(UserDbWriteLatencyChart, userDbData, d => d.WriteLatencyMs, "Write Latency (ms)", colors, xMin, xMax, _fileIoWriteHover, d => d.WriteQueuedLatencyMs);
842851
}
843852

844-
private void LoadFileIoChart(ScottPlot.WPF.WpfPlot chart, List<FileIoLatencyTimeSeriesItem> data, Func<FileIoLatencyTimeSeriesItem, decimal> latencySelector, string yLabel, ScottPlot.Color[] colors, double xMin, double xMax, Helpers.ChartHoverHelper? hover = null)
853+
private void LoadFileIoChart(ScottPlot.WPF.WpfPlot chart, List<FileIoLatencyTimeSeriesItem> data, Func<FileIoLatencyTimeSeriesItem, decimal> latencySelector, string yLabel, ScottPlot.Color[] colors, double xMin, double xMax, Helpers.ChartHoverHelper? hover = null, Func<FileIoLatencyTimeSeriesItem, decimal>? queuedSelector = null)
845854
{
846855
DateTime rangeStart = DateTime.FromOADate(xMin);
847856
DateTime rangeEnd = DateTime.FromOADate(xMax);
@@ -856,6 +865,9 @@ private void LoadFileIoChart(ScottPlot.WPF.WpfPlot chart, List<FileIoLatencyTime
856865
TabHelpers.ApplyDarkModeToChart(chart);
857866
hover?.Clear();
858867

868+
// Check if any queued data exists (only render overlay if there's real data)
869+
bool hasQueuedData = queuedSelector != null && data != null && data.Any(d => queuedSelector(d) > 0);
870+
859871
if (data != null && data.Count > 0)
860872
{
861873
// Get all unique time points for gap filling
@@ -879,13 +891,31 @@ private void LoadFileIoChart(ScottPlot.WPF.WpfPlot chart, List<FileIoLatencyTime
879891
var scatter = chart.Plot.Add.Scatter(xs, ys);
880892
scatter.LineWidth = 2;
881893
scatter.MarkerSize = 5;
882-
scatter.Color = colors[colorIndex % colors.Length];
894+
var color = colors[colorIndex % colors.Length];
895+
scatter.Color = color;
883896

884897
// Use just the filename for legend (not database.filename which is redundant)
885898
var fileName = fileData.First().FileName;
886899
scatter.LegendText = fileName;
887900
hover?.Add(scatter, fileName);
888901

902+
// Add queued I/O overlay as dashed line with same color
903+
if (hasQueuedData)
904+
{
905+
var queuedValues = fileData.Select(d => (double)queuedSelector!(d));
906+
if (queuedValues.Any(v => v > 0))
907+
{
908+
var (qxs, qys) = TabHelpers.FillTimeSeriesGaps(timePoints, queuedValues);
909+
var queuedScatter = chart.Plot.Add.Scatter(qxs, qys);
910+
queuedScatter.LineWidth = 2;
911+
queuedScatter.MarkerSize = 0;
912+
queuedScatter.Color = color;
913+
queuedScatter.LinePattern = ScottPlot.LinePattern.Dashed;
914+
queuedScatter.LegendText = $"{fileName} (queued)";
915+
hover?.Add(queuedScatter, $"{fileName} (queued)");
916+
}
917+
}
918+
889919
colorIndex++;
890920
}
891921
}
@@ -913,6 +943,22 @@ private void LoadFileIoChart(ScottPlot.WPF.WpfPlot chart, List<FileIoLatencyTime
913943
chart.Refresh();
914944
}
915945

946+
private async Task LoadFileIoThroughputChartsAsync()
947+
{
948+
if (_databaseService == null) return;
949+
950+
DateTime rangeEnd = _fileIoToDate ?? Helpers.ServerTimeHelper.ServerNow;
951+
DateTime rangeStart = _fileIoFromDate ?? rangeEnd.AddHours(-_fileIoHoursBack);
952+
double xMin = rangeStart.ToOADate();
953+
double xMax = rangeEnd.ToOADate();
954+
955+
var colors = TabHelpers.ChartColors;
956+
957+
var throughputData = await _databaseService.GetFileIoThroughputTimeSeriesAsync(isTempDb: false, _fileIoHoursBack, _fileIoFromDate, _fileIoToDate);
958+
LoadFileIoChart(FileIoReadThroughputChart, throughputData, d => d.ReadThroughputMbPerSec, "Read Throughput (MB/s)", colors, xMin, xMax, _fileIoReadThroughputHover);
959+
LoadFileIoChart(FileIoWriteThroughputChart, throughputData, d => d.WriteThroughputMbPerSec, "Write Throughput (MB/s)", colors, xMin, xMax, _fileIoWriteThroughputHover);
960+
}
961+
916962
#endregion
917963

918964
#region Server Trends Tab

Dashboard/Models/FileIoLatencyTimeSeriesItem.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,9 @@ public class FileIoLatencyTimeSeriesItem
2020
public decimal WriteLatencyMs { get; set; }
2121
public long ReadCount { get; set; }
2222
public long WriteCount { get; set; }
23+
public decimal ReadQueuedLatencyMs { get; set; }
24+
public decimal WriteQueuedLatencyMs { get; set; }
25+
public decimal ReadThroughputMbPerSec { get; set; }
26+
public decimal WriteThroughputMbPerSec { get; set; }
2327
}
2428
}

0 commit comments

Comments
 (0)