Skip to content

Commit 98d5e56

Browse files
Merge pull request #279 from erikdarlingdata/feature/issue-273-trace-pattern-drilldown
Issue #273: Trace pattern drill-down, missing columns, query text tooltips
2 parents 06f2580 + 6c7b38a commit 98d5e56

9 files changed

Lines changed: 901 additions & 43 deletions

Dashboard/Controls/DefaultTraceContent.xaml

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434
<Grid Grid.Row="1">
3535
<DataGrid x:Name="DefaultTraceEventsDataGrid" AutoGenerateColumns="False" IsReadOnly="True"
36-
RowHeight="28" GridLinesVisibility="Horizontal" CanUserResizeColumns="True" ContextMenu="{DynamicResource DataGridContextMenu}"
36+
GridLinesVisibility="Horizontal" CanUserResizeColumns="True" ContextMenu="{DynamicResource DataGridContextMenu}"
3737
ScrollViewer.CanContentScroll="True" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto">
3838
<DataGrid.Columns>
3939
<DataGridTextColumn Binding="{Binding EventTime, StringFormat='{}{0:yyyy-MM-dd HH:mm:ss}'}" Width="150">
@@ -103,7 +103,7 @@
103103
<TabItem Header="Trace Analysis">
104104
<Grid>
105105
<DataGrid x:Name="TraceAnalysisDataGrid" AutoGenerateColumns="False" IsReadOnly="True"
106-
RowHeight="28" GridLinesVisibility="Horizontal" CanUserResizeColumns="True" ContextMenu="{DynamicResource DataGridContextMenu}"
106+
GridLinesVisibility="Horizontal" CanUserResizeColumns="True" ContextMenu="{DynamicResource DataGridContextMenu}"
107107
ScrollViewer.CanContentScroll="True" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto">
108108
<DataGrid.Columns>
109109
<DataGridTextColumn Binding="{Binding CollectionTime, StringFormat='{}{0:yyyy-MM-dd HH:mm:ss}'}" Width="150">
@@ -170,14 +170,19 @@
170170
</StackPanel>
171171
</DataGridTextColumn.Header>
172172
</DataGridTextColumn>
173-
<DataGridTextColumn Binding="{Binding SqlText}" Width="400">
174-
<DataGridTextColumn.Header>
173+
<DataGridTemplateColumn Width="400">
174+
<DataGridTemplateColumn.Header>
175175
<StackPanel Orientation="Horizontal">
176176
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="SqlText" Click="TraceAnalysisFilter_Click" Margin="0,0,4,0"/>
177177
<TextBlock Text="SQL Text" FontWeight="Bold" VerticalAlignment="Center"/>
178178
</StackPanel>
179-
</DataGridTextColumn.Header>
180-
</DataGridTextColumn>
179+
</DataGridTemplateColumn.Header>
180+
<DataGridTemplateColumn.CellTemplate>
181+
<DataTemplate>
182+
<TextBlock Text="{Binding SqlText}" TextWrapping="Wrap" MaxHeight="90" Margin="5,2" ToolTip="{Binding SqlText}"/>
183+
</DataTemplate>
184+
</DataGridTemplateColumn.CellTemplate>
185+
</DataGridTemplateColumn>
181186
</DataGrid.Columns>
182187
</DataGrid>
183188
<TextBlock x:Name="TraceAnalysisNoDataMessage" Style="{DynamicResource NoDataMessage}"/>

Dashboard/Controls/QueryPerformanceContent.xaml

Lines changed: 84 additions & 32 deletions
Large diffs are not rendered by default.

Dashboard/Controls/QueryPerformanceContent.xaml.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,6 +1499,37 @@ private void ApplyLrqPatternsFilters()
14991499
LongRunningQueryPatternsDataGrid.ItemsSource = filteredData;
15001500
}
15011501

1502+
private void LongRunningQueryPatternsDataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
1503+
{
1504+
if (!TabHelpers.IsDoubleClickOnRow((DependencyObject)e.OriginalSource)) return;
1505+
if (_databaseService == null) return;
1506+
1507+
if (LongRunningQueryPatternsDataGrid.SelectedItem is LongRunningQueryPatternItem item)
1508+
{
1509+
if (string.IsNullOrEmpty(item.DatabaseName) || string.IsNullOrEmpty(item.QueryPattern))
1510+
{
1511+
MessageBox.Show(
1512+
"Unable to show history: missing database name or query pattern.",
1513+
"Information",
1514+
MessageBoxButton.OK,
1515+
MessageBoxImage.Information
1516+
);
1517+
return;
1518+
}
1519+
1520+
var historyWindow = new TracePatternHistoryWindow(
1521+
_databaseService,
1522+
item.DatabaseName,
1523+
item.QueryPattern,
1524+
_lrqPatternsHoursBack,
1525+
_lrqPatternsFromDate,
1526+
_lrqPatternsToDate
1527+
);
1528+
historyWindow.Owner = Window.GetWindow(this);
1529+
historyWindow.ShowDialog();
1530+
}
1531+
}
1532+
15021533
#endregion
15031534

15041535
#region Performance Trends
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) 2025 Darling Data, LLC
2+
// Licensed under the MIT License
3+
4+
using System;
5+
6+
namespace PerformanceMonitorDashboard.Models
7+
{
8+
public class TracePatternDetailItem
9+
{
10+
public long AnalysisId { get; set; }
11+
public DateTime CollectionTime { get; set; }
12+
public string EventName { get; set; } = string.Empty;
13+
public string? DatabaseName { get; set; }
14+
public string? LoginName { get; set; }
15+
public string? ApplicationName { get; set; }
16+
public string? HostName { get; set; }
17+
public int? Spid { get; set; }
18+
public long? DurationMs { get; set; }
19+
public long? CpuMs { get; set; }
20+
public long? Reads { get; set; }
21+
public long? Writes { get; set; }
22+
public long? RowCounts { get; set; }
23+
public DateTime? StartTime { get; set; }
24+
public DateTime? EndTime { get; set; }
25+
public string? SqlText { get; set; }
26+
public long? ObjectId { get; set; }
27+
}
28+
}

Dashboard/ServerTab.xaml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@
554554
<TabItem Header="Blocking">
555555
<Grid>
556556
<DataGrid x:Name="BlockingEventsDataGrid" AutoGenerateColumns="False" IsReadOnly="True"
557-
RowHeight="28" GridLinesVisibility="Horizontal" CanUserResizeColumns="True"
557+
GridLinesVisibility="Horizontal" CanUserResizeColumns="True"
558558
RowStyle="{StaticResource DefaultRowStyle}"
559559
ScrollViewer.CanContentScroll="True" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto">
560560
<DataGrid.Columns>
@@ -670,14 +670,19 @@
670670
</StackPanel>
671671
</DataGridTextColumn.Header>
672672
</DataGridTextColumn>
673-
<DataGridTextColumn Binding="{Binding QueryText}" Width="300">
674-
<DataGridTextColumn.Header>
673+
<DataGridTemplateColumn Width="300">
674+
<DataGridTemplateColumn.Header>
675675
<StackPanel Orientation="Horizontal">
676676
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="QueryText" Click="BlockingEventsFilter_Click" Margin="0,0,4,0"/>
677677
<TextBlock Text="Query Text" FontWeight="Bold" VerticalAlignment="Center"/>
678678
</StackPanel>
679-
</DataGridTextColumn.Header>
680-
</DataGridTextColumn>
679+
</DataGridTemplateColumn.Header>
680+
<DataGridTemplateColumn.CellTemplate>
681+
<DataTemplate>
682+
<TextBlock Text="{Binding QueryText}" TextWrapping="Wrap" MaxHeight="90" Margin="5,2" ToolTip="{Binding QueryText}"/>
683+
</DataTemplate>
684+
</DataGridTemplateColumn.CellTemplate>
685+
</DataGridTemplateColumn>
681686
<DataGridTextColumn Binding="{Binding TransactionName}" Width="100">
682687
<DataGridTextColumn.Header>
683688
<StackPanel Orientation="Horizontal">

Dashboard/Services/DatabaseService.QueryPerformance.cs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1469,6 +1469,119 @@ ORDER BY
14691469
return items;
14701470
}
14711471

1472+
public async Task<List<TracePatternDetailItem>> GetTracePatternHistoryAsync(string databaseName, string queryPattern, int hoursBack = 24, DateTime? fromDate = null, DateTime? toDate = null)
1473+
{
1474+
var items = new List<TracePatternDetailItem>();
1475+
1476+
await using var tc = await OpenThrottledConnectionAsync();
1477+
var connection = tc.Connection;
1478+
1479+
string timeFilter = fromDate.HasValue && toDate.HasValue
1480+
? "ta.end_time >= @from_date AND ta.end_time <= @to_date"
1481+
: "ta.end_time >= DATEADD(HOUR, @hours_back, SYSDATETIME())";
1482+
1483+
/* Trace events can appear in multiple collection cycles because the trace file
1484+
retains events until it rolls over. Deduplicate by partitioning on the event's
1485+
natural key (end_time + duration + cpu + reads) and keeping only the first row. */
1486+
string query = $@"
1487+
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
1488+
1489+
WITH
1490+
numbered AS
1491+
(
1492+
SELECT
1493+
ta.analysis_id,
1494+
ta.collection_time,
1495+
ta.event_name,
1496+
ta.database_name,
1497+
ta.login_name,
1498+
ta.application_name,
1499+
ta.host_name,
1500+
ta.spid,
1501+
ta.duration_ms,
1502+
ta.cpu_ms,
1503+
ta.reads,
1504+
ta.writes,
1505+
ta.row_counts,
1506+
ta.start_time,
1507+
ta.end_time,
1508+
sql_text = LEFT(ta.sql_text, 4000),
1509+
ta.object_id,
1510+
rn = ROW_NUMBER() OVER
1511+
(
1512+
PARTITION BY
1513+
ta.end_time,
1514+
ta.duration_ms,
1515+
ta.cpu_ms,
1516+
ta.reads,
1517+
ta.spid
1518+
ORDER BY
1519+
ta.collection_time
1520+
)
1521+
FROM collect.trace_analysis AS ta
1522+
WHERE ta.database_name = @database_name
1523+
AND LEFT(ta.sql_text, 200) = @query_pattern
1524+
AND {timeFilter}
1525+
)
1526+
SELECT
1527+
analysis_id,
1528+
collection_time,
1529+
event_name,
1530+
database_name,
1531+
login_name,
1532+
application_name,
1533+
host_name,
1534+
spid,
1535+
duration_ms,
1536+
cpu_ms,
1537+
reads,
1538+
writes,
1539+
row_counts,
1540+
start_time,
1541+
end_time,
1542+
sql_text,
1543+
object_id
1544+
FROM numbered
1545+
WHERE rn = 1
1546+
ORDER BY
1547+
end_time DESC;";
1548+
1549+
using var command = new SqlCommand(query, connection);
1550+
command.CommandTimeout = 120;
1551+
command.Parameters.Add(new SqlParameter("@database_name", SqlDbType.NVarChar, 128) { Value = databaseName });
1552+
command.Parameters.Add(new SqlParameter("@query_pattern", SqlDbType.NVarChar, 200) { Value = queryPattern });
1553+
command.Parameters.Add(new SqlParameter("@hours_back", SqlDbType.Int) { Value = -hoursBack });
1554+
if (fromDate.HasValue) command.Parameters.Add(new SqlParameter("@from_date", SqlDbType.DateTime2) { Value = fromDate.Value });
1555+
if (toDate.HasValue) command.Parameters.Add(new SqlParameter("@to_date", SqlDbType.DateTime2) { Value = toDate.Value });
1556+
1557+
using var reader = await command.ExecuteReaderAsync();
1558+
while (await reader.ReadAsync())
1559+
{
1560+
items.Add(new TracePatternDetailItem
1561+
{
1562+
AnalysisId = reader.GetInt64(0),
1563+
CollectionTime = reader.GetDateTime(1),
1564+
EventName = reader.IsDBNull(2) ? string.Empty : reader.GetString(2),
1565+
DatabaseName = reader.IsDBNull(3) ? null : reader.GetString(3),
1566+
LoginName = reader.IsDBNull(4) ? null : reader.GetString(4),
1567+
ApplicationName = reader.IsDBNull(5) ? null : reader.GetString(5),
1568+
HostName = reader.IsDBNull(6) ? null : reader.GetString(6),
1569+
Spid = reader.IsDBNull(7) ? null : reader.GetInt32(7),
1570+
DurationMs = reader.IsDBNull(8) ? null : reader.GetInt64(8),
1571+
CpuMs = reader.IsDBNull(9) ? null : reader.GetInt64(9),
1572+
Reads = reader.IsDBNull(10) ? null : reader.GetInt64(10),
1573+
Writes = reader.IsDBNull(11) ? null : reader.GetInt64(11),
1574+
RowCounts = reader.IsDBNull(12) ? null : reader.GetInt64(12),
1575+
StartTime = reader.IsDBNull(13) ? null : reader.GetDateTime(13),
1576+
EndTime = reader.IsDBNull(14) ? null : reader.GetDateTime(14),
1577+
SqlText = reader.IsDBNull(15) ? null : reader.GetString(15),
1578+
ObjectId = reader.IsDBNull(16) ? null : reader.GetInt64(16)
1579+
});
1580+
}
1581+
1582+
return items;
1583+
}
1584+
14721585
public async Task<List<BlockingDeadlockStatsItem>> GetBlockingDeadlockStatsAsync(int hoursBack = 24, DateTime? fromDate = null, DateTime? toDate = null)
14731586
{
14741587
var items = new List<BlockingDeadlockStatsItem>();

Dashboard/Themes/DarkTheme.xaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,6 +1214,10 @@
12141214
<Setter Property="BorderBrush" Value="{StaticResource BorderLightBrush}"/>
12151215
<Setter Property="BorderThickness" Value="1"/>
12161216
<Setter Property="Padding" Value="10,8"/>
1217+
<Setter Property="MaxWidth" Value="600"/>
12171218
</Style>
12181219

1220+
<!-- Apply DarkToolTip as the default for all tooltips -->
1221+
<Style TargetType="ToolTip" BasedOn="{StaticResource DarkToolTip}"/>
1222+
12191223
</ResourceDictionary>

0 commit comments

Comments
 (0)