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
17 changes: 11 additions & 6 deletions Dashboard/Controls/DefaultTraceContent.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

<Grid Grid.Row="1">
<DataGrid x:Name="DefaultTraceEventsDataGrid" AutoGenerateColumns="False" IsReadOnly="True"
RowHeight="28" GridLinesVisibility="Horizontal" CanUserResizeColumns="True" ContextMenu="{DynamicResource DataGridContextMenu}"
GridLinesVisibility="Horizontal" CanUserResizeColumns="True" ContextMenu="{DynamicResource DataGridContextMenu}"
ScrollViewer.CanContentScroll="True" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding EventTime, StringFormat='{}{0:yyyy-MM-dd HH:mm:ss}'}" Width="150">
Expand Down Expand Up @@ -103,7 +103,7 @@
<TabItem Header="Trace Analysis">
<Grid>
<DataGrid x:Name="TraceAnalysisDataGrid" AutoGenerateColumns="False" IsReadOnly="True"
RowHeight="28" GridLinesVisibility="Horizontal" CanUserResizeColumns="True" ContextMenu="{DynamicResource DataGridContextMenu}"
GridLinesVisibility="Horizontal" CanUserResizeColumns="True" ContextMenu="{DynamicResource DataGridContextMenu}"
ScrollViewer.CanContentScroll="True" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding CollectionTime, StringFormat='{}{0:yyyy-MM-dd HH:mm:ss}'}" Width="150">
Expand Down Expand Up @@ -170,14 +170,19 @@
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding SqlText}" Width="400">
<DataGridTextColumn.Header>
<DataGridTemplateColumn Width="400">
<DataGridTemplateColumn.Header>
<StackPanel Orientation="Horizontal">
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="SqlText" Click="TraceAnalysisFilter_Click" Margin="0,0,4,0"/>
<TextBlock Text="SQL Text" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding SqlText}" TextWrapping="Wrap" MaxHeight="90" Margin="5,2" ToolTip="{Binding SqlText}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<TextBlock x:Name="TraceAnalysisNoDataMessage" Style="{DynamicResource NoDataMessage}"/>
Expand Down
116 changes: 84 additions & 32 deletions Dashboard/Controls/QueryPerformanceContent.xaml

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions Dashboard/Controls/QueryPerformanceContent.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1499,6 +1499,37 @@ private void ApplyLrqPatternsFilters()
LongRunningQueryPatternsDataGrid.ItemsSource = filteredData;
}

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

if (LongRunningQueryPatternsDataGrid.SelectedItem is LongRunningQueryPatternItem item)
{
if (string.IsNullOrEmpty(item.DatabaseName) || string.IsNullOrEmpty(item.QueryPattern))
{
MessageBox.Show(
"Unable to show history: missing database name or query pattern.",
"Information",
MessageBoxButton.OK,
MessageBoxImage.Information
);
return;
}

var historyWindow = new TracePatternHistoryWindow(
_databaseService,
item.DatabaseName,
item.QueryPattern,
_lrqPatternsHoursBack,
_lrqPatternsFromDate,
_lrqPatternsToDate
);
historyWindow.Owner = Window.GetWindow(this);
historyWindow.ShowDialog();
}
}

#endregion

#region Performance Trends
Expand Down
28 changes: 28 additions & 0 deletions Dashboard/Models/TracePatternDetailItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) 2025 Darling Data, LLC
// Licensed under the MIT License

using System;

namespace PerformanceMonitorDashboard.Models
{
public class TracePatternDetailItem
{
public long AnalysisId { get; set; }
public DateTime CollectionTime { get; set; }
public string EventName { get; set; } = string.Empty;
public string? DatabaseName { get; set; }
public string? LoginName { get; set; }
public string? ApplicationName { get; set; }
public string? HostName { get; set; }
public int? Spid { get; set; }
public long? DurationMs { get; set; }
public long? CpuMs { get; set; }
public long? Reads { get; set; }
public long? Writes { get; set; }
public long? RowCounts { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
public string? SqlText { get; set; }
public long? ObjectId { get; set; }
}
}
15 changes: 10 additions & 5 deletions Dashboard/ServerTab.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@
<TabItem Header="Blocking">
<Grid>
<DataGrid x:Name="BlockingEventsDataGrid" AutoGenerateColumns="False" IsReadOnly="True"
RowHeight="28" GridLinesVisibility="Horizontal" CanUserResizeColumns="True"
GridLinesVisibility="Horizontal" CanUserResizeColumns="True"
RowStyle="{StaticResource DefaultRowStyle}"
ScrollViewer.CanContentScroll="True" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto">
<DataGrid.Columns>
Expand Down Expand Up @@ -670,14 +670,19 @@
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding QueryText}" Width="300">
<DataGridTextColumn.Header>
<DataGridTemplateColumn Width="300">
<DataGridTemplateColumn.Header>
<StackPanel Orientation="Horizontal">
<Button Style="{DynamicResource ColumnFilterButtonStyle}" Tag="QueryText" Click="BlockingEventsFilter_Click" Margin="0,0,4,0"/>
<TextBlock Text="Query Text" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding QueryText}" TextWrapping="Wrap" MaxHeight="90" Margin="5,2" ToolTip="{Binding QueryText}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Binding="{Binding TransactionName}" Width="100">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
Expand Down
113 changes: 113 additions & 0 deletions Dashboard/Services/DatabaseService.QueryPerformance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1469,6 +1469,119 @@ ORDER BY
return items;
}

public async Task<List<TracePatternDetailItem>> GetTracePatternHistoryAsync(string databaseName, string queryPattern, int hoursBack = 24, DateTime? fromDate = null, DateTime? toDate = null)
{
var items = new List<TracePatternDetailItem>();

await using var tc = await OpenThrottledConnectionAsync();
var connection = tc.Connection;

string timeFilter = fromDate.HasValue && toDate.HasValue
? "ta.end_time >= @from_date AND ta.end_time <= @to_date"
: "ta.end_time >= DATEADD(HOUR, @hours_back, SYSDATETIME())";

/* Trace events can appear in multiple collection cycles because the trace file
retains events until it rolls over. Deduplicate by partitioning on the event's
natural key (end_time + duration + cpu + reads) and keeping only the first row. */
string query = $@"
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

WITH
numbered AS
(
SELECT
ta.analysis_id,
ta.collection_time,
ta.event_name,
ta.database_name,
ta.login_name,
ta.application_name,
ta.host_name,
ta.spid,
ta.duration_ms,
ta.cpu_ms,
ta.reads,
ta.writes,
ta.row_counts,
ta.start_time,
ta.end_time,
sql_text = LEFT(ta.sql_text, 4000),
ta.object_id,
rn = ROW_NUMBER() OVER
(
PARTITION BY
ta.end_time,
ta.duration_ms,
ta.cpu_ms,
ta.reads,
ta.spid
ORDER BY
ta.collection_time
)
FROM collect.trace_analysis AS ta
WHERE ta.database_name = @database_name
AND LEFT(ta.sql_text, 200) = @query_pattern
AND {timeFilter}
)
SELECT
analysis_id,
collection_time,
event_name,
database_name,
login_name,
application_name,
host_name,
spid,
duration_ms,
cpu_ms,
reads,
writes,
row_counts,
start_time,
end_time,
sql_text,
object_id
FROM numbered
WHERE rn = 1
ORDER BY
end_time DESC;";

using var command = new SqlCommand(query, connection);
command.CommandTimeout = 120;
command.Parameters.Add(new SqlParameter("@database_name", SqlDbType.NVarChar, 128) { Value = databaseName });
command.Parameters.Add(new SqlParameter("@query_pattern", SqlDbType.NVarChar, 200) { Value = queryPattern });
command.Parameters.Add(new SqlParameter("@hours_back", SqlDbType.Int) { Value = -hoursBack });
if (fromDate.HasValue) command.Parameters.Add(new SqlParameter("@from_date", SqlDbType.DateTime2) { Value = fromDate.Value });
if (toDate.HasValue) command.Parameters.Add(new SqlParameter("@to_date", SqlDbType.DateTime2) { Value = toDate.Value });

using var reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
items.Add(new TracePatternDetailItem
{
AnalysisId = reader.GetInt64(0),
CollectionTime = reader.GetDateTime(1),
EventName = reader.IsDBNull(2) ? string.Empty : reader.GetString(2),
DatabaseName = reader.IsDBNull(3) ? null : reader.GetString(3),
LoginName = reader.IsDBNull(4) ? null : reader.GetString(4),
ApplicationName = reader.IsDBNull(5) ? null : reader.GetString(5),
HostName = reader.IsDBNull(6) ? null : reader.GetString(6),
Spid = reader.IsDBNull(7) ? null : reader.GetInt32(7),
DurationMs = reader.IsDBNull(8) ? null : reader.GetInt64(8),
CpuMs = reader.IsDBNull(9) ? null : reader.GetInt64(9),
Reads = reader.IsDBNull(10) ? null : reader.GetInt64(10),
Writes = reader.IsDBNull(11) ? null : reader.GetInt64(11),
RowCounts = reader.IsDBNull(12) ? null : reader.GetInt64(12),
StartTime = reader.IsDBNull(13) ? null : reader.GetDateTime(13),
EndTime = reader.IsDBNull(14) ? null : reader.GetDateTime(14),
SqlText = reader.IsDBNull(15) ? null : reader.GetString(15),
ObjectId = reader.IsDBNull(16) ? null : reader.GetInt64(16)
});
}

return items;
}

public async Task<List<BlockingDeadlockStatsItem>> GetBlockingDeadlockStatsAsync(int hoursBack = 24, DateTime? fromDate = null, DateTime? toDate = null)
{
var items = new List<BlockingDeadlockStatsItem>();
Expand Down
4 changes: 4 additions & 0 deletions Dashboard/Themes/DarkTheme.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,10 @@
<Setter Property="BorderBrush" Value="{StaticResource BorderLightBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="10,8"/>
<Setter Property="MaxWidth" Value="600"/>
</Style>

<!-- Apply DarkToolTip as the default for all tooltips -->
<Style TargetType="ToolTip" BasedOn="{StaticResource DarkToolTip}"/>

</ResourceDictionary>
Loading
Loading