Skip to content

Commit 7021b7d

Browse files
Merge pull request #294 from erikdarlingdata/feature/issue-281-memory-grant-charts
Issue #281 Gap 3: Min/max extremes, Query Store gaps, collector health
2 parents d429c93 + c1d5151 commit 7021b7d

7 files changed

Lines changed: 258 additions & 33 deletions

File tree

Dashboard/Controls/QueryPerformanceContent.xaml

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,22 @@
727727
</StackPanel>
728728
</DataGridTextColumn.Header>
729729
</DataGridTextColumn>
730+
<DataGridTextColumn Binding="{Binding MinPhysicalReads, StringFormat='{}{0:N0}'}" ElementStyle="{StaticResource NumericCell}" Width="110">
731+
<DataGridTextColumn.Header>
732+
<StackPanel Orientation="Horizontal">
733+
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="MinPhysicalReads" Click="QueryStatsFilter_Click" Margin="0,0,4,0"/>
734+
<TextBlock Text="Min Phys Reads" FontWeight="Bold" VerticalAlignment="Center"/>
735+
</StackPanel>
736+
</DataGridTextColumn.Header>
737+
</DataGridTextColumn>
738+
<DataGridTextColumn Binding="{Binding MaxPhysicalReads, StringFormat='{}{0:N0}'}" ElementStyle="{StaticResource NumericCell}" Width="110">
739+
<DataGridTextColumn.Header>
740+
<StackPanel Orientation="Horizontal">
741+
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="MaxPhysicalReads" Click="QueryStatsFilter_Click" Margin="0,0,4,0"/>
742+
<TextBlock Text="Max Phys Reads" FontWeight="Bold" VerticalAlignment="Center"/>
743+
</StackPanel>
744+
</DataGridTextColumn.Header>
745+
</DataGridTextColumn>
730746
<DataGridTextColumn Binding="{Binding TotalLogicalWrites, StringFormat='{}{0:N0}'}" ElementStyle="{StaticResource NumericCell}" Width="100">
731747
<DataGridTextColumn.Header>
732748
<StackPanel Orientation="Horizontal">
@@ -1348,6 +1364,14 @@
13481364
</StackPanel>
13491365
</DataGridTextColumn.Header>
13501366
</DataGridTextColumn>
1367+
<DataGridTextColumn Binding="{Binding MinMemoryMb, StringFormat='{}{0:N2}'}" ElementStyle="{StaticResource NumericCell}" Width="100">
1368+
<DataGridTextColumn.Header>
1369+
<StackPanel Orientation="Horizontal">
1370+
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="MinMemoryMb" Click="QueryStoreFilter_Click" Margin="0,0,4,0"/>
1371+
<TextBlock Text="Min Mem (MB)" FontWeight="Bold" VerticalAlignment="Center"/>
1372+
</StackPanel>
1373+
</DataGridTextColumn.Header>
1374+
</DataGridTextColumn>
13511375
<DataGridTextColumn Binding="{Binding MaxMemoryMb, StringFormat='{}{0:N2}'}" ElementStyle="{StaticResource NumericCell}" Width="100">
13521376
<DataGridTextColumn.Header>
13531377
<StackPanel Orientation="Horizontal">
@@ -1364,6 +1388,14 @@
13641388
</StackPanel>
13651389
</DataGridTextColumn.Header>
13661390
</DataGridTextColumn>
1391+
<DataGridTextColumn Binding="{Binding MinTempdbMb, StringFormat='{}{0:N2}'}" ElementStyle="{StaticResource NumericCell}" Width="100">
1392+
<DataGridTextColumn.Header>
1393+
<StackPanel Orientation="Horizontal">
1394+
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="MinTempdbMb" Click="QueryStoreFilter_Click" Margin="0,0,4,0"/>
1395+
<TextBlock Text="Min TempDB (MB)" FontWeight="Bold" VerticalAlignment="Center"/>
1396+
</StackPanel>
1397+
</DataGridTextColumn.Header>
1398+
</DataGridTextColumn>
13671399
<DataGridTextColumn Binding="{Binding MaxTempdbMb, StringFormat='{}{0:N2}'}" ElementStyle="{StaticResource NumericCell}" Width="100">
13681400
<DataGridTextColumn.Header>
13691401
<StackPanel Orientation="Horizontal">
@@ -1404,6 +1436,62 @@
14041436
</StackPanel>
14051437
</DataGridTextColumn.Header>
14061438
</DataGridTextColumn>
1439+
<DataGridTextColumn Binding="{Binding PlanForcingType}" Width="100">
1440+
<DataGridTextColumn.Header>
1441+
<StackPanel Orientation="Horizontal">
1442+
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="PlanForcingType" Click="QueryStoreFilter_Click" Margin="0,0,4,0"/>
1443+
<TextBlock Text="Forcing Type" FontWeight="Bold" VerticalAlignment="Center"/>
1444+
</StackPanel>
1445+
</DataGridTextColumn.Header>
1446+
</DataGridTextColumn>
1447+
<DataGridTextColumn Binding="{Binding MinClrTimeMs, StringFormat='{}{0:N2}'}" ElementStyle="{StaticResource NumericCell}" Width="100">
1448+
<DataGridTextColumn.Header>
1449+
<StackPanel Orientation="Horizontal">
1450+
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="MinClrTimeMs" Click="QueryStoreFilter_Click" Margin="0,0,4,0"/>
1451+
<TextBlock Text="Min CLR (ms)" FontWeight="Bold" VerticalAlignment="Center"/>
1452+
</StackPanel>
1453+
</DataGridTextColumn.Header>
1454+
</DataGridTextColumn>
1455+
<DataGridTextColumn Binding="{Binding MaxClrTimeMs, StringFormat='{}{0:N2}'}" ElementStyle="{StaticResource NumericCell}" Width="100">
1456+
<DataGridTextColumn.Header>
1457+
<StackPanel Orientation="Horizontal">
1458+
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="MaxClrTimeMs" Click="QueryStoreFilter_Click" Margin="0,0,4,0"/>
1459+
<TextBlock Text="Max CLR (ms)" FontWeight="Bold" VerticalAlignment="Center"/>
1460+
</StackPanel>
1461+
</DataGridTextColumn.Header>
1462+
</DataGridTextColumn>
1463+
<DataGridTextColumn Binding="{Binding MinNumPhysicalIoReads, StringFormat='{}{0:N0}'}" ElementStyle="{StaticResource NumericCell}" Width="110">
1464+
<DataGridTextColumn.Header>
1465+
<StackPanel Orientation="Horizontal">
1466+
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="MinNumPhysicalIoReads" Click="QueryStoreFilter_Click" Margin="0,0,4,0"/>
1467+
<TextBlock Text="Min Phys IO Reads" FontWeight="Bold" VerticalAlignment="Center"/>
1468+
</StackPanel>
1469+
</DataGridTextColumn.Header>
1470+
</DataGridTextColumn>
1471+
<DataGridTextColumn Binding="{Binding MaxNumPhysicalIoReads, StringFormat='{}{0:N0}'}" ElementStyle="{StaticResource NumericCell}" Width="110">
1472+
<DataGridTextColumn.Header>
1473+
<StackPanel Orientation="Horizontal">
1474+
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="MaxNumPhysicalIoReads" Click="QueryStoreFilter_Click" Margin="0,0,4,0"/>
1475+
<TextBlock Text="Max Phys IO Reads" FontWeight="Bold" VerticalAlignment="Center"/>
1476+
</StackPanel>
1477+
</DataGridTextColumn.Header>
1478+
</DataGridTextColumn>
1479+
<DataGridTextColumn Binding="{Binding MinLogBytesUsed, StringFormat='{}{0:N0}'}" ElementStyle="{StaticResource NumericCell}" Width="100">
1480+
<DataGridTextColumn.Header>
1481+
<StackPanel Orientation="Horizontal">
1482+
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="MinLogBytesUsed" Click="QueryStoreFilter_Click" Margin="0,0,4,0"/>
1483+
<TextBlock Text="Min Log Bytes" FontWeight="Bold" VerticalAlignment="Center"/>
1484+
</StackPanel>
1485+
</DataGridTextColumn.Header>
1486+
</DataGridTextColumn>
1487+
<DataGridTextColumn Binding="{Binding MaxLogBytesUsed, StringFormat='{}{0:N0}'}" ElementStyle="{StaticResource NumericCell}" Width="100">
1488+
<DataGridTextColumn.Header>
1489+
<StackPanel Orientation="Horizontal">
1490+
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="MaxLogBytesUsed" Click="QueryStoreFilter_Click" Margin="0,0,4,0"/>
1491+
<TextBlock Text="Max Log Bytes" FontWeight="Bold" VerticalAlignment="Center"/>
1492+
</StackPanel>
1493+
</DataGridTextColumn.Header>
1494+
</DataGridTextColumn>
14071495
<DataGridTextColumn Binding="{Binding CompatibilityLevel}" ElementStyle="{StaticResource NumericCell}" Width="80">
14081496
<DataGridTextColumn.Header>
14091497
<StackPanel Orientation="Horizontal">

Dashboard/Models/QueryStatsItem.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ public class QueryStatsItem
3434
public long? AvgLogicalReads { get; set; }
3535
public long? AvgLogicalWrites { get; set; }
3636
public long? AvgPhysicalReads { get; set; }
37+
public long? MinPhysicalReads { get; set; }
38+
public long? MaxPhysicalReads { get; set; }
3739
public long? AvgRows { get; set; }
3840
public long? MinRows { get; set; }
3941
public long? MaxRows { get; set; }

Dashboard/Models/QueryStoreItem.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,23 @@ public class QueryStoreItem
6464
public bool IsForcedPlan { get; set; }
6565
public short? CompatibilityLevel { get; set; }
6666

67+
// Plan forcing details
68+
public long? ForceFailureCount { get; set; }
69+
public string? LastForceFailureReasonDesc { get; set; }
70+
public string? PlanForcingType { get; set; }
71+
72+
// CLR time (pre-calculated in ms)
73+
public double? MinClrTimeMs { get; set; }
74+
public double? MaxClrTimeMs { get; set; }
75+
76+
// Physical IO reads (memory-optimized tables, SQL 2017+)
77+
public long? MinNumPhysicalIoReads { get; set; }
78+
public long? MaxNumPhysicalIoReads { get; set; }
79+
80+
// Log bytes used (SQL 2017+)
81+
public long? MinLogBytesUsed { get; set; }
82+
public long? MaxLogBytesUsed { get; set; }
83+
6784
// Handle
6885
public string? QueryPlanHash { get; set; }
6986

@@ -73,10 +90,12 @@ public class QueryStoreItem
7390

7491
// Display helpers - memory in MB (8KB pages * 8 / 1024)
7592
public double? AvgMemoryMb => AvgMemoryPages.HasValue ? AvgMemoryPages.Value * 8.0 / 1024.0 : null;
93+
public double? MinMemoryMb => MinMemoryPages.HasValue ? MinMemoryPages.Value * 8.0 / 1024.0 : null;
7694
public double? MaxMemoryMb => MaxMemoryPages.HasValue ? MaxMemoryPages.Value * 8.0 / 1024.0 : null;
7795

7896
// Tempdb in MB
7997
public double? AvgTempdbMb => AvgTempdbPages.HasValue ? AvgTempdbPages.Value * 8.0 / 1024.0 : null;
98+
public double? MinTempdbMb => MinTempdbPages.HasValue ? MinTempdbPages.Value * 8.0 / 1024.0 : null;
8099
public double? MaxTempdbMb => MaxTempdbPages.HasValue ? MaxTempdbPages.Value * 8.0 / 1024.0 : null;
81100

82101
// Property aliases for XAML binding compatibility

Dashboard/Services/DatabaseService.QueryPerformance.cs

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,8 @@ ELSE QUOTENAME(MAX(pl.schema_name)) + N'.' + QUOTENAME(MAX(pl.object_name))
786786
avg_logical_writes = SUM(pl.total_logical_writes) / NULLIF(SUM(pl.execution_count), 0),
787787
total_physical_reads = SUM(pl.total_physical_reads),
788788
avg_physical_reads = SUM(pl.total_physical_reads) / NULLIF(SUM(pl.execution_count), 0),
789+
min_physical_reads = MIN(pl.min_physical_reads),
790+
max_physical_reads = MAX(pl.max_physical_reads),
789791
total_rows = SUM(pl.total_rows),
790792
avg_rows = SUM(pl.total_rows) / NULLIF(SUM(pl.execution_count), 0),
791793
min_rows = MIN(pl.min_rows),
@@ -849,22 +851,24 @@ USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE')
849851
AvgLogicalWrites = reader.IsDBNull(18) ? null : reader.GetInt64(18),
850852
TotalPhysicalReads = reader.IsDBNull(19) ? 0 : reader.GetInt64(19),
851853
AvgPhysicalReads = reader.IsDBNull(20) ? null : reader.GetInt64(20),
852-
TotalRows = reader.IsDBNull(21) ? 0 : reader.GetInt64(21),
853-
AvgRows = reader.IsDBNull(22) ? null : reader.GetInt64(22),
854-
MinRows = reader.IsDBNull(23) ? null : reader.GetInt64(23),
855-
MaxRows = reader.IsDBNull(24) ? null : reader.GetInt64(24),
856-
MinDop = reader.IsDBNull(25) ? null : reader.GetInt16(25),
857-
MaxDop = reader.IsDBNull(26) ? null : reader.GetInt16(26),
858-
MinGrantKb = reader.IsDBNull(27) ? null : reader.GetInt64(27),
859-
MaxGrantKb = reader.IsDBNull(28) ? null : reader.GetInt64(28),
860-
TotalSpills = reader.IsDBNull(29) ? 0 : reader.GetInt64(29),
861-
MinSpills = reader.IsDBNull(30) ? null : reader.GetInt64(30),
862-
MaxSpills = reader.IsDBNull(31) ? null : reader.GetInt64(31),
863-
QueryText = reader.IsDBNull(32) ? null : reader.GetString(32),
864-
QueryPlanXml = reader.IsDBNull(33) ? null : reader.GetString(33),
865-
QueryPlanHash = reader.IsDBNull(34) ? null : reader.GetString(34),
866-
SqlHandle = reader.IsDBNull(35) ? null : reader.GetString(35),
867-
PlanHandle = reader.IsDBNull(36) ? null : reader.GetString(36)
854+
MinPhysicalReads = reader.IsDBNull(21) ? null : reader.GetInt64(21),
855+
MaxPhysicalReads = reader.IsDBNull(22) ? null : reader.GetInt64(22),
856+
TotalRows = reader.IsDBNull(23) ? 0 : reader.GetInt64(23),
857+
AvgRows = reader.IsDBNull(24) ? null : reader.GetInt64(24),
858+
MinRows = reader.IsDBNull(25) ? null : reader.GetInt64(25),
859+
MaxRows = reader.IsDBNull(26) ? null : reader.GetInt64(26),
860+
MinDop = reader.IsDBNull(27) ? null : reader.GetInt16(27),
861+
MaxDop = reader.IsDBNull(28) ? null : reader.GetInt16(28),
862+
MinGrantKb = reader.IsDBNull(29) ? null : reader.GetInt64(29),
863+
MaxGrantKb = reader.IsDBNull(30) ? null : reader.GetInt64(30),
864+
TotalSpills = reader.IsDBNull(31) ? 0 : reader.GetInt64(31),
865+
MinSpills = reader.IsDBNull(32) ? null : reader.GetInt64(32),
866+
MaxSpills = reader.IsDBNull(33) ? null : reader.GetInt64(33),
867+
QueryText = reader.IsDBNull(34) ? null : reader.GetString(34),
868+
QueryPlanXml = reader.IsDBNull(35) ? null : reader.GetString(35),
869+
QueryPlanHash = reader.IsDBNull(36) ? null : reader.GetString(36),
870+
SqlHandle = reader.IsDBNull(37) ? null : reader.GetString(37),
871+
PlanHandle = reader.IsDBNull(38) ? null : reader.GetString(38)
868872
});
869873
}
870874

@@ -1098,7 +1102,16 @@ public async Task<List<QueryStoreItem>> GetQueryStoreDataAsync(int hoursBack = 2
10981102
is_forced_plan = MAX(CONVERT(tinyint, qsd.is_forced_plan)),
10991103
compatibility_level = MAX(qsd.compatibility_level),
11001104
query_sql_text = CONVERT(nvarchar(max), MAX(qsd.query_sql_text)),
1101-
query_plan_hash = CONVERT(nvarchar(20), MAX(qsd.query_plan_hash), 1)
1105+
query_plan_hash = CONVERT(nvarchar(20), MAX(qsd.query_plan_hash), 1),
1106+
force_failure_count = SUM(qsd.force_failure_count),
1107+
last_force_failure_reason_desc = MAX(qsd.last_force_failure_reason_desc),
1108+
plan_forcing_type = MAX(qsd.plan_forcing_type),
1109+
min_clr_time_ms = MIN(qsd.min_clr_time) / 1000.0,
1110+
max_clr_time_ms = MAX(qsd.max_clr_time) / 1000.0,
1111+
min_num_physical_io_reads = MIN(qsd.min_num_physical_io_reads),
1112+
max_num_physical_io_reads = MAX(qsd.max_num_physical_io_reads),
1113+
min_log_bytes_used = MIN(qsd.min_log_bytes_used),
1114+
max_log_bytes_used = MAX(qsd.max_log_bytes_used)
11021115
FROM collect.query_store_data AS qsd
11031116
WHERE (
11041117
(@useCustomDates = 0 AND qsd.server_last_execution_time >= DATEADD(HOUR, -@hoursBack, SYSDATETIME()))
@@ -1172,7 +1185,16 @@ USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE')
11721185
IsForcedPlan = !reader.IsDBNull(35) && reader.GetByte(35) == 1,
11731186
CompatibilityLevel = reader.IsDBNull(36) ? null : reader.GetInt16(36),
11741187
QuerySqlText = reader.IsDBNull(37) ? null : reader.GetString(37),
1175-
QueryPlanHash = reader.IsDBNull(38) ? null : reader.GetString(38)
1188+
QueryPlanHash = reader.IsDBNull(38) ? null : reader.GetString(38),
1189+
ForceFailureCount = reader.IsDBNull(39) ? null : reader.GetInt64(39),
1190+
LastForceFailureReasonDesc = reader.IsDBNull(40) ? null : reader.GetString(40),
1191+
PlanForcingType = reader.IsDBNull(41) ? null : reader.GetString(41),
1192+
MinClrTimeMs = reader.IsDBNull(42) ? null : Convert.ToDouble(reader.GetValue(42), CultureInfo.InvariantCulture),
1193+
MaxClrTimeMs = reader.IsDBNull(43) ? null : Convert.ToDouble(reader.GetValue(43), CultureInfo.InvariantCulture),
1194+
MinNumPhysicalIoReads = reader.IsDBNull(44) ? null : reader.GetInt64(44),
1195+
MaxNumPhysicalIoReads = reader.IsDBNull(45) ? null : reader.GetInt64(45),
1196+
MinLogBytesUsed = reader.IsDBNull(46) ? null : reader.GetInt64(46),
1197+
MaxLogBytesUsed = reader.IsDBNull(47) ? null : reader.GetInt64(47)
11761198
// QueryPlanXml is fetched on-demand via GetQueryStorePlanXmlAsync
11771199
});
11781200
}

0 commit comments

Comments
 (0)