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
9 changes: 8 additions & 1 deletion Dashboard/Controls/ResourceMetricsContent.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,14 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Wait Stats Over Time" FontWeight="Bold" HorizontalAlignment="Center" Margin="0,5"/>
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,5">
<TextBlock Text="Wait Stats Over Time" FontWeight="Bold" VerticalAlignment="Center" Margin="0,0,12,0"/>
<TextBlock Text="Metric:" VerticalAlignment="Center" Margin="0,0,4,0" FontSize="11"/>
<ComboBox x:Name="WaitStatsMetricCombo" SelectedIndex="0" SelectionChanged="WaitStatsMetric_SelectionChanged" FontSize="11" Padding="4,2">
<ComboBoxItem Content="Wait Time (ms/sec)"/>
<ComboBoxItem Content="Avg Wait Time (ms/wait)"/>
</ComboBox>
</StackPanel>
<ScottPlot:WpfPlot Grid.Row="1" x:Name="WaitStatsDetailChart"/>
</Grid>
</Border>
Expand Down
19 changes: 15 additions & 4 deletions Dashboard/Controls/ResourceMetricsContent.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1880,6 +1880,12 @@ private async void WaitType_CheckChanged(object sender, RoutedEventArgs e)
await UpdateWaitStatsDetailChartAsync();
}

private void WaitStatsMetric_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_allWaitStatsDetailData != null)
LoadWaitStatsDetailChart(_allWaitStatsDetailData, _waitStatsDetailHoursBack, _waitStatsDetailFromDate, _waitStatsDetailToDate);
}

private void RefreshWaitTypeListOrder()
{
if (_waitTypeItems == null) return;
Expand Down Expand Up @@ -2050,9 +2056,12 @@ private void LoadWaitStatsDetailChart(List<WaitStatsDataPoint>? data, int hoursB
WaitStatsDetailChart.Plot.Axes.Remove(existingWaitStatsPanel);
_legendPanels[WaitStatsDetailChart] = null;
}
bool useAvgPerWait = WaitStatsMetricCombo?.SelectedIndex == 1;

WaitStatsDetailChart.Plot.Clear();
TabHelpers.ApplyDarkModeToChart(WaitStatsDetailChart);
_waitStatsHover?.Clear();
if (_waitStatsHover != null) _waitStatsHover.Unit = useAvgPerWait ? "ms/wait" : "ms/sec";

if (data == null || data.Count == 0 || _waitTypeItems == null)
{
Expand All @@ -2067,7 +2076,6 @@ private void LoadWaitStatsDetailChart(List<WaitStatsDataPoint>? data, int hoursB
WaitStatsDetailChart.Refresh();
return;
}

var colors = TabHelpers.ChartColors;

// Get all time points across all wait types for gap filling
Expand All @@ -2080,15 +2088,18 @@ private void LoadWaitStatsDetailChart(List<WaitStatsDataPoint>? data, int hoursB
.GroupBy(d => d.CollectionTime)
.Select(g => new {
CollectionTime = g.Key,
WaitTimeMsPerSecond = g.Sum(x => x.WaitTimeMsPerSecond)
WaitTimeMsPerSecond = g.Sum(x => x.WaitTimeMsPerSecond),
AvgMsPerWait = g.Average(x => x.AvgMsPerWait)
})
.OrderBy(d => d.CollectionTime)
.ToList();

if (waitTypeData.Count >= 1)
{
var timePoints = waitTypeData.Select(d => d.CollectionTime);
var values = waitTypeData.Select(d => (double)d.WaitTimeMsPerSecond);
var values = useAvgPerWait
? waitTypeData.Select(d => (double)d.AvgMsPerWait)
: waitTypeData.Select(d => (double)d.WaitTimeMsPerSecond);
var (xs, ys) = TabHelpers.FillTimeSeriesGaps(timePoints, values);

var scatter = WaitStatsDetailChart.Plot.Add.Scatter(xs, ys);
Expand Down Expand Up @@ -2124,7 +2135,7 @@ private void LoadWaitStatsDetailChart(List<WaitStatsDataPoint>? data, int hoursB
WaitStatsDetailChart.Plot.Axes.DateTimeTicksBottom();
WaitStatsDetailChart.Plot.Axes.SetLimitsX(xMin, xMax);
TabHelpers.SetChartYLimitsWithLegendPadding(WaitStatsDetailChart);
WaitStatsDetailChart.Plot.YLabel("Wait Time (ms/sec)");
WaitStatsDetailChart.Plot.YLabel(useAvgPerWait ? "Avg Wait Time (ms/wait)" : "Wait Time (ms/sec)");
WaitStatsDetailChart.Plot.HideGrid();
TabHelpers.LockChartVerticalAxis(WaitStatsDetailChart);
WaitStatsDetailChart.Refresh();
Expand Down
4 changes: 3 additions & 1 deletion Dashboard/Helpers/ChartHoverHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal sealed class ChartHoverHelper
private readonly List<(ScottPlot.Plottables.Scatter Scatter, string Label)> _scatters = new();
private readonly Popup _popup;
private readonly TextBlock _text;
private readonly string _unit;
private string _unit;
private DateTime _lastUpdate;

public ChartHoverHelper(ScottPlot.WPF.WpfPlot chart, string unit)
Expand Down Expand Up @@ -53,6 +53,8 @@ public ChartHoverHelper(ScottPlot.WPF.WpfPlot chart, string unit)
chart.MouseLeave += OnMouseLeave;
}

public string Unit { get => _unit; set => _unit = value; }

public void Clear() => _scatters.Clear();

public void Add(ScottPlot.Plottables.Scatter scatter, string label) =>
Expand Down
1 change: 1 addition & 0 deletions Dashboard/Models/WaitStatsDataPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ public class WaitStatsDataPoint
public string WaitType { get; set; } = string.Empty;
public decimal WaitTimeMsPerSecond { get; set; }
public decimal SignalWaitTimeMsPerSecond { get; set; }
public decimal AvgMsPerWait { get; set; }
}
}
54 changes: 52 additions & 2 deletions Dashboard/Services/DatabaseService.ResourceMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1723,6 +1723,14 @@ PARTITION BY
ORDER BY
ws.collection_time
),
waiting_tasks_delta =
ws.waiting_tasks_count - LAG(ws.waiting_tasks_count, 1, ws.waiting_tasks_count) OVER
(
PARTITION BY
ws.wait_type
ORDER BY
ws.collection_time
),
interval_seconds =
DATEDIFF
(
Expand Down Expand Up @@ -1754,6 +1762,12 @@ ELSE 0
WHEN wd.interval_seconds > 0
THEN CAST(wd.signal_wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds
ELSE 0
END,
avg_ms_per_wait =
CASE
WHEN wd.waiting_tasks_delta > 0
THEN CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.waiting_tasks_delta
ELSE 0
END
FROM wait_deltas AS wd
WHERE wd.wait_time_ms_delta > 0
Expand Down Expand Up @@ -1788,6 +1802,14 @@ PARTITION BY
ORDER BY
ws.collection_time
),
waiting_tasks_delta =
ws.waiting_tasks_count - LAG(ws.waiting_tasks_count, 1, ws.waiting_tasks_count) OVER
(
PARTITION BY
ws.wait_type
ORDER BY
ws.collection_time
),
interval_seconds =
DATEDIFF
(
Expand Down Expand Up @@ -1818,6 +1840,12 @@ ELSE 0
WHEN wd.interval_seconds > 0
THEN CAST(wd.signal_wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds
ELSE 0
END,
avg_ms_per_wait =
CASE
WHEN wd.waiting_tasks_delta > 0
THEN CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.waiting_tasks_delta
ELSE 0
END
FROM wait_deltas AS wd
WHERE wd.wait_time_ms_delta > 0
Expand All @@ -1840,7 +1868,8 @@ ORDER BY
CollectionTime = reader.GetDateTime(0),
WaitType = reader.IsDBNull(1) ? string.Empty : reader.GetString(1),
WaitTimeMsPerSecond = reader.IsDBNull(2) ? 0m : Convert.ToDecimal(reader.GetValue(2), CultureInfo.InvariantCulture),
SignalWaitTimeMsPerSecond = reader.IsDBNull(3) ? 0m : Convert.ToDecimal(reader.GetValue(3), CultureInfo.InvariantCulture)
SignalWaitTimeMsPerSecond = reader.IsDBNull(3) ? 0m : Convert.ToDecimal(reader.GetValue(3), CultureInfo.InvariantCulture),
AvgMsPerWait = reader.IsDBNull(4) ? 0m : Convert.ToDecimal(reader.GetValue(4), CultureInfo.InvariantCulture)
});
}

Expand Down Expand Up @@ -1890,6 +1919,12 @@ ORDER BY ws.collection_time
PARTITION BY ws.wait_type
ORDER BY ws.collection_time
),
waiting_tasks_delta =
ws.waiting_tasks_count - LAG(ws.waiting_tasks_count, 1, ws.waiting_tasks_count) OVER
(
PARTITION BY ws.wait_type
ORDER BY ws.collection_time
),
interval_seconds =
DATEDIFF(SECOND, LAG(ws.collection_time, 1, ws.collection_time) OVER
(
Expand All @@ -1911,6 +1946,10 @@ THEN CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds
signal_wait_time_ms_per_second =
CASE WHEN wd.interval_seconds > 0
THEN CAST(wd.signal_wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds
ELSE 0 END,
avg_ms_per_wait =
CASE WHEN wd.waiting_tasks_delta > 0
THEN CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.waiting_tasks_delta
ELSE 0 END
FROM wait_deltas AS wd
WHERE wd.wait_time_ms_delta > 0
Expand Down Expand Up @@ -1939,6 +1978,12 @@ ORDER BY ws.collection_time
PARTITION BY ws.wait_type
ORDER BY ws.collection_time
),
waiting_tasks_delta =
ws.waiting_tasks_count - LAG(ws.waiting_tasks_count, 1, ws.waiting_tasks_count) OVER
(
PARTITION BY ws.wait_type
ORDER BY ws.collection_time
),
interval_seconds =
DATEDIFF(SECOND, LAG(ws.collection_time, 1, ws.collection_time) OVER
(
Expand All @@ -1959,6 +2004,10 @@ THEN CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds
signal_wait_time_ms_per_second =
CASE WHEN wd.interval_seconds > 0
THEN CAST(wd.signal_wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds
ELSE 0 END,
avg_ms_per_wait =
CASE WHEN wd.waiting_tasks_delta > 0
THEN CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.waiting_tasks_delta
ELSE 0 END
FROM wait_deltas AS wd
WHERE wd.wait_time_ms_delta > 0
Expand All @@ -1982,7 +2031,8 @@ WHERE wd.wait_time_ms_delta > 0
CollectionTime = reader.GetDateTime(0),
WaitType = reader.IsDBNull(1) ? string.Empty : reader.GetString(1),
WaitTimeMsPerSecond = reader.IsDBNull(2) ? 0m : Convert.ToDecimal(reader.GetValue(2), CultureInfo.InvariantCulture),
SignalWaitTimeMsPerSecond = reader.IsDBNull(3) ? 0m : Convert.ToDecimal(reader.GetValue(3), CultureInfo.InvariantCulture)
SignalWaitTimeMsPerSecond = reader.IsDBNull(3) ? 0m : Convert.ToDecimal(reader.GetValue(3), CultureInfo.InvariantCulture),
AvgMsPerWait = reader.IsDBNull(4) ? 0m : Convert.ToDecimal(reader.GetValue(4), CultureInfo.InvariantCulture)
});
}

Expand Down
15 changes: 14 additions & 1 deletion Lite/Controls/ServerTab.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,20 @@
</Grid>
</Border>

<ScottPlot:WpfPlot Grid.Column="1" x:Name="WaitStatsChart"/>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="8,4,0,0">
<TextBlock Text="Metric:" VerticalAlignment="Center" Margin="0,0,6,0" FontSize="11"/>
<ComboBox x:Name="WaitStatsMetricCombo" SelectedIndex="0" SelectionChanged="WaitStatsMetric_SelectionChanged" FontSize="11" Padding="4,2">
<ComboBoxItem Content="Wait Time (ms/sec)"/>
<ComboBoxItem Content="Avg Wait Time (ms/wait)"/>
</ComboBox>
</StackPanel>
<ScottPlot:WpfPlot Grid.Row="1" x:Name="WaitStatsChart"/>
</Grid>
</Grid>
</TabItem>

Expand Down
18 changes: 14 additions & 4 deletions Lite/Controls/ServerTab.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1139,6 +1139,11 @@ private void WaitTypeClearAll_Click(object sender, RoutedEventArgs e)
_ = UpdateWaitStatsChartFromPickerAsync();
}

private void WaitStatsMetric_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
_ = UpdateWaitStatsChartFromPickerAsync();
}

private void WaitType_CheckChanged(object sender, RoutedEventArgs e)
{
if (_isUpdatingWaitTypeSelection) return;
Expand All @@ -1158,6 +1163,9 @@ private async System.Threading.Tasks.Task UpdateWaitStatsChartFromPickerAsync()

if (selected.Count == 0) { WaitStatsChart.Refresh(); return; }

bool useAvgPerWait = WaitStatsMetricCombo?.SelectedIndex == 1;
if (_waitStatsHover != null) _waitStatsHover.Unit = useAvgPerWait ? "ms/wait" : "ms/sec";

var hoursBack = GetHoursBack();
DateTime? fromDate = null;
DateTime? toDate = null;
Expand All @@ -1179,14 +1187,16 @@ private async System.Threading.Tasks.Task UpdateWaitStatsChartFromPickerAsync()
if (trend.Count == 0) continue;

var times = trend.Select(t => t.CollectionTime.AddMinutes(UtcOffsetMinutes).ToOADate()).ToArray();
var waitTime = trend.Select(t => t.WaitTimeMsPerSecond).ToArray();
var values = useAvgPerWait
? trend.Select(t => t.AvgMsPerWait).ToArray()
: trend.Select(t => t.WaitTimeMsPerSecond).ToArray();

var plot = WaitStatsChart.Plot.Add.Scatter(times, waitTime);
var plot = WaitStatsChart.Plot.Add.Scatter(times, values);
plot.LegendText = selected[i].DisplayName;
plot.Color = ScottPlot.Color.FromHex(SeriesColors[i % SeriesColors.Length]);
_waitStatsHover?.Add(plot, selected[i].DisplayName);

if (waitTime.Length > 0) globalMax = Math.Max(globalMax, waitTime.Max());
if (values.Length > 0) globalMax = Math.Max(globalMax, values.Max());
}

WaitStatsChart.Plot.Axes.DateTimeTicksBottom();
Expand All @@ -1203,7 +1213,7 @@ private async System.Threading.Tasks.Task UpdateWaitStatsChartFromPickerAsync()
}
WaitStatsChart.Plot.Axes.SetLimitsX(rangeStart.ToOADate(), rangeEnd.ToOADate());
ReapplyAxisColors(WaitStatsChart);
WaitStatsChart.Plot.YLabel("Wait Time (ms/sec)");
WaitStatsChart.Plot.YLabel(useAvgPerWait ? "Avg Wait Time (ms/wait)" : "Wait Time (ms/sec)");
SetChartYLimitsWithLegendPadding(WaitStatsChart, 0, globalMax > 0 ? globalMax : 100);
ShowChartLegend(WaitStatsChart);
WaitStatsChart.Refresh();
Expand Down
4 changes: 3 additions & 1 deletion Lite/Helpers/ChartHoverHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal sealed class ChartHoverHelper
private readonly List<(ScottPlot.Plottables.Scatter Scatter, string Label)> _scatters = new();
private readonly Popup _popup;
private readonly TextBlock _text;
private readonly string _unit;
private string _unit;
private DateTime _lastUpdate;

public ChartHoverHelper(ScottPlot.WPF.WpfPlot chart, string unit)
Expand Down Expand Up @@ -53,6 +53,8 @@ public ChartHoverHelper(ScottPlot.WPF.WpfPlot chart, string unit)
chart.MouseLeave += OnMouseLeave;
}

public string Unit { get => _unit; set => _unit = value; }

public void Clear() => _scatters.Clear();

public void Add(ScottPlot.Plottables.Scatter scatter, string label) =>
Expand Down
10 changes: 8 additions & 2 deletions Lite/Services/LocalDataService.WaitStats.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ WITH raw AS
collection_time,
delta_wait_time_ms,
delta_signal_wait_time_ms,
delta_waiting_tasks,
date_diff('second', LAG(collection_time) OVER (ORDER BY collection_time), collection_time) AS interval_seconds
FROM wait_stats
WHERE server_id = $1
Expand All @@ -122,7 +123,8 @@ FROM wait_stats
SELECT
collection_time,
CASE WHEN interval_seconds > 0 THEN CAST(delta_wait_time_ms AS DOUBLE) / interval_seconds ELSE 0 END AS wait_time_ms_per_second,
CASE WHEN interval_seconds > 0 THEN CAST(delta_signal_wait_time_ms AS DOUBLE) / interval_seconds ELSE 0 END AS signal_wait_time_ms_per_second
CASE WHEN interval_seconds > 0 THEN CAST(delta_signal_wait_time_ms AS DOUBLE) / interval_seconds ELSE 0 END AS signal_wait_time_ms_per_second,
CASE WHEN delta_waiting_tasks > 0 THEN CAST(delta_wait_time_ms AS DOUBLE) / delta_waiting_tasks ELSE 0 END AS avg_ms_per_wait
FROM raw
ORDER BY collection_time";

Expand All @@ -139,7 +141,8 @@ FROM raw
{
CollectionTime = reader.GetDateTime(0),
WaitTimeMsPerSecond = reader.IsDBNull(1) ? 0 : reader.GetDouble(1),
SignalWaitTimeMsPerSecond = reader.IsDBNull(2) ? 0 : reader.GetDouble(2)
SignalWaitTimeMsPerSecond = reader.IsDBNull(2) ? 0 : reader.GetDouble(2),
AvgMsPerWait = reader.IsDBNull(3) ? 0 : reader.GetDouble(3)
});
}

Expand All @@ -155,6 +158,8 @@ public class WaitStatsRow
public long TotalSignalWaitTimeMs { get; set; }
public long ResourceWaitTimeMs => TotalWaitTimeMs - TotalSignalWaitTimeMs;
public long SampleCount { get; set; }
public double AvgWaitMsPerTask => TotalWaitingTasks > 0 ? (double)TotalWaitTimeMs / TotalWaitingTasks : 0;
public string AvgWaitMsFormatted => AvgWaitMsPerTask < 0.1 ? "< 0.1 ms" : $"{AvgWaitMsPerTask:F1} ms";
public string TotalWaitTimeFormatted => FormatMs(TotalWaitTimeMs);
public string SignalWaitTimeFormatted => FormatMs(TotalSignalWaitTimeMs);
public string ResourceWaitTimeFormatted => FormatMs(ResourceWaitTimeMs);
Expand All @@ -181,4 +186,5 @@ public class WaitStatsTrendPoint
public DateTime CollectionTime { get; set; }
public double WaitTimeMsPerSecond { get; set; }
public double SignalWaitTimeMsPerSecond { get; set; }
public double AvgMsPerWait { get; set; }
}