Skip to content

Commit ef4ed0c

Browse files
Issue #281 Gap 2: File I/O throughput charts, file-level latency, queued I/O overlay
**New features (both apps):** - Add "File I/O Throughput" sub-tab with read/write MB/s charts per file - Restructure "File I/O" tab into "File I/O Latency" and "File I/O Throughput" sub-tabs - Add queued I/O overlay (dashed lines) to latency charts showing OS queue wait time **Lite-specific:** - Switch File I/O latency charts from database-level to file-level (top 10 by activity) - Add io_stall_queued_read/write columns to DuckDB schema (v15 migration) - Update collector to collect queued stall data from sys.dm_io_virtual_file_stats - Use DuckDB LAG() window function for throughput MB/s interval calculation **Dashboard-specific:** - Fix double-brace interpolation bug in GetFileIoThroughputTimeSeriesAsync ({{dbFilter}}/{{dateFilter}} produced literal text instead of interpolating variables) **CI fix:** - Update 50_configuration_issues_analyzer.sql and 97_test_procedures.sql to remove references to warning columns dropped from collect.memory_grant_stats in Gap 1 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9995376 commit ef4ed0c

12 files changed

Lines changed: 550 additions & 82 deletions

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)