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
380 changes: 380 additions & 0 deletions Lite.Tests/AlertBadgeServerOffsetTests.cs

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion Lite/Controls/ServerTab.Charts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1484,7 +1484,10 @@ private void RevertChartAxes(ScottPlot.WPF.WpfPlot chart)
return;
}

var (hoursBack, fromDate, toDate) = GetCurrentWindow();
/* Re-pins the axes onto the window the plotted data was read over, so it takes the same offset
those reads take — the selected tab's. This runs from a chart on the visible tab, where that
is this tab. */
var (hoursBack, fromDate, toDate) = GetCurrentWindow(ServerTimeHelper.UtcOffsetMinutes);
DateTime rangeEnd = toDate ?? DateTime.UtcNow.AddMinutes(UtcOffsetMinutes);
DateTime rangeStart = fromDate ?? rangeEnd.AddHours(-hoursBack);
chart.Plot.Axes.SetLimitsX(rangeStart.ToOADate(), rangeEnd.ToOADate());
Expand Down
39 changes: 30 additions & 9 deletions Lite/Controls/ServerTab.Refresh.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,15 @@ the global database filter is applied client-side on the parsed per-process rows
/// window. A preset leaves from/to null (charts fall back to now − hoursBack); a valid custom range
/// converts the local picker dates/times to server time.
/// </summary>
private (int hoursBack, DateTime? fromDate, DateTime? toDate) GetCurrentWindow()
/// <param name="utcOffsetMinutes">
/// Whose server time the returned bounds are in. Every read given this window converts the bounds back
/// out to UTC using an offset of its own, and the two have to be the same server's or the pair stops
/// cancelling — so this argument is chosen to match the read being fed, not chosen once for the tab.
/// <c>ServerTimeHelper.UtcOffsetMinutes</c> for the sub-tab reads, which take their offset from the
/// selected tab; this tab's own <c>UtcOffsetMinutes</c> for the badge read, which takes its offset from
/// the server it names.
/// </param>
private (int hoursBack, DateTime? fromDate, DateTime? toDate) GetCurrentWindow(int utcOffsetMinutes)
{
var hoursBack = GetHoursBack();

Expand All @@ -69,8 +77,8 @@ the global database filter is applied client-side on the parsed per-process rows
var toLocal = GetDateTimeFromPickers(ToDatePicker!, ToHourCombo, ToMinuteCombo);
if (fromLocal.HasValue && toLocal.HasValue)
{
fromDate = ServerTimeHelper.DisplayTimeToServerTime(fromLocal.Value, ServerTimeHelper.CurrentDisplayMode);
toDate = ServerTimeHelper.DisplayTimeToServerTime(toLocal.Value, ServerTimeHelper.CurrentDisplayMode);
fromDate = ServerTimeHelper.DisplayTimeToServerTime(fromLocal.Value, ServerTimeHelper.CurrentDisplayMode, utcOffsetMinutes);
toDate = ServerTimeHelper.DisplayTimeToServerTime(toLocal.Value, ServerTimeHelper.CurrentDisplayMode, utcOffsetMinutes);
}
}

Expand All @@ -82,7 +90,9 @@ private async System.Threading.Tasks.Task RefreshAllDataAsync()
if (_isRefreshing) return;
_isRefreshing = true;

var (hoursBack, fromDate, toDate) = GetCurrentWindow();
/* The selected tab's offset, because the sub-tab reads below convert back out to UTC with that
same offset and the two applications have to name one server to cancel. */
var (hoursBack, fromDate, toDate) = GetCurrentWindow(ServerTimeHelper.UtcOffsetMinutes);

try
{
Expand All @@ -99,9 +109,11 @@ skip the heavy sub-tab data refresh and just keep the alert badge current. Mark
{
_refreshPendingWhileHidden = true;
}
/* Always keep alert badge current even when Blocking tab is not visible */
/* Always keep alert badge current even when Blocking tab is not visible. Deliberately not
given the window above: that one is in the SELECTED tab's server time, and this runs on
every tab's timer regardless of which is selected. It derives its own. */
if (MainTabControl.SelectedIndex != 8)
await RefreshAlertCountsAsync(hoursBack, fromDate, toDate);
await RefreshAlertCountsAsync();

/* #1591: same reasoning as the alert badge above — a permission-denied collector is only visible on
the Collection Health tab, which is precisely why it went unnoticed. Badge it from every tab. */
Expand Down Expand Up @@ -152,12 +164,21 @@ private async System.Threading.Tasks.Task RefreshVisibleTabAsync(int hoursBack,
/// <summary>
/// Lightweight alert-only refresh — fetches blocking + deadlock counts and fires AlertCountsChanged.
/// Runs on every timer tick when the Blocking tab is NOT visible so the tab badge stays current.
///
/// <para>Derives its own window instead of taking the caller's, and derives it in THIS tab's
/// <c>UtcOffsetMinutes</c> rather than in the selected tab's. Every other windowed read here sits
/// behind the <c>IsVisible</c> gate, where this tab is the selected tab and the two offsets are the
/// same value; the badge is the one that runs for a background tab, whose server can be in a
/// different zone from the one on screen. The same offset goes into the picker conversion and into
/// <see cref="LocalDataService.GetAlertCountsAsync"/>, which is what keeps the two applications
/// cancelling in the UTC and Local display modes while leaving the ServerTime default correct.</para>
/// </summary>
private async System.Threading.Tasks.Task RefreshAlertCountsAsync(int hoursBack, DateTime? fromDate, DateTime? toDate)
private async System.Threading.Tasks.Task RefreshAlertCountsAsync()
{
try
{
var (blockingCount, deadlockCount, latestEventTime) = await Task.Run(() => _dataService.GetAlertCountsAsync(_serverId, hoursBack, fromDate, toDate));
var (hoursBack, fromDate, toDate) = GetCurrentWindow(UtcOffsetMinutes);
var (blockingCount, deadlockCount, latestEventTime) = await Task.Run(() => _dataService.GetAlertCountsAsync(_serverId, hoursBack, fromDate, toDate, UtcOffsetMinutes));
AlertCountsChanged?.Invoke(blockingCount, deadlockCount, latestEventTime);
}
catch (Exception ex)
Expand Down Expand Up @@ -521,7 +542,7 @@ private async System.Threading.Tasks.Task RefreshBlockingAsync(int hoursBack, Da
break;
}
/* Always keep alert badge current when Blocking tab is visible */
await RefreshAlertCountsAsync(hoursBack, fromDate, toDate);
await RefreshAlertCountsAsync();
return;
}

Expand Down
2 changes: 1 addition & 1 deletion Lite/Services/LocalDataService.AlertHistory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public async Task<List<AlertHistoryRow>> GetAlertHistoryAsync(int hoursBack = 24
/* Both edges, not just the lower one: the row cap is applied by the database, so trimming
after the read would spend the whole LIMIT on rows newer than an as_of anchor and hand back an
empty window that looks exactly like a quiet one. */
var (cutoff, until) = GetTimeRange(hoursBack, null, null, asOfUtc);
var (cutoff, until) = GetTimeRange(hoursBack, null, null, asOfUtc, utcOffsetMinutes: 0);

if (serverId.HasValue)
{
Expand Down
37 changes: 25 additions & 12 deletions Lite/Services/LocalDataService.Blocking.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public async Task<List<DeadlockRow>> GetRecentDeadlocksAsync(int serverId, int h
using var connection = await OpenConnectionAsync();
using var command = connection.CreateCommand();

var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc);
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc, SelectedServerTabUtcOffsetMinutes);

command.CommandText = @"
SELECT
Expand Down Expand Up @@ -125,7 +125,7 @@ public async Task<List<TimeSliceBucket>> GetActiveQuerySlicerDataAsync(
using var connection = await OpenConnectionAsync();
using var command = connection.CreateCommand();

var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate);
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc: null, SelectedServerTabUtcOffsetMinutes);
var dbClause = BuildDbInClause(databaseNames, "database_name", 4, out var dbValues);

command.CommandText = @"
Expand Down Expand Up @@ -179,7 +179,7 @@ public async Task<List<QuerySnapshotRow>> GetLatestQuerySnapshotsAsync(int serve
using var connection = await OpenConnectionAsync();
using var command = connection.CreateCommand();

var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc);
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc, SelectedServerTabUtcOffsetMinutes);
var dbClause = BuildDbInClause(databaseNames, "database_name", 4, out var dbValues);

command.CommandText = @"
Expand Down Expand Up @@ -267,12 +267,25 @@ AND query_text NOT LIKE 'WAITFOR%'
/// Gets lightweight blocking + deadlock counts and latest event time for alert badge updates.
/// Much cheaper than fetching full rows with XML — just COUNT(*) and MAX(time).
/// </summary>
public async Task<(int blockingCount, int deadlockCount, DateTime? latestEventTime)> GetAlertCountsAsync(int serverId, int hoursBack = 24, DateTime? fromDate = null, DateTime? toDate = null)
/// <param name="utcOffsetMinutes">
/// The UTC offset of <paramref name="serverId"/> itself, not of whichever server tab the desktop has
/// selected. This read is the one on the badge path, which runs on every tab's own timer whether or
/// not that tab is visible, so the server it names and the server the desktop is showing are
/// routinely different ones.
///
/// <para>Required, and required to be the SAME offset the caller used to convert
/// <paramref name="fromDate"/>/<paramref name="toDate"/> out of the display mode. Those two
/// conversions cancel in <c>TimeDisplayMode.UTC</c> and <c>LocalTime</c> and only the one here
/// applies in <c>ServerTime</c>; sourcing them from different servers leaves a residue in every
/// mode. <c>ServerTab.RefreshAlertCountsAsync</c> derives both from the tab's own
/// <c>UtcOffsetMinutes</c>.</para>
/// </param>
public async Task<(int blockingCount, int deadlockCount, DateTime? latestEventTime)> GetAlertCountsAsync(int serverId, int hoursBack, DateTime? fromDate, DateTime? toDate, int utcOffsetMinutes)
{
using var connection = await OpenConnectionAsync();
using var command = connection.CreateCommand();

var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate);
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc: null, utcOffsetMinutes);

/* blocking_count prefers the blocked-process-report; falls back to the always-on DMV snapshot when
BPR captured nothing (AWS RDS). latest_event_time includes DMV blocking recency too. */
Expand Down Expand Up @@ -318,7 +331,7 @@ public async Task<List<BlockedProcessReportRow>> GetRecentBlockedProcessReportsA
using var connection = await OpenConnectionAsync();
using var command = connection.CreateCommand();

var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc);
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc, SelectedServerTabUtcOffsetMinutes);
var dbClause = BuildDbInClause(databaseNames, "database_name", 4, out var dbValues);

command.CommandText = @"
Expand Down Expand Up @@ -557,7 +570,7 @@ public async Task<List<TimeSliceBucket>> GetBlockingSlicerDataAsync(
{
using var connection = await OpenConnectionAsync();
using var command = connection.CreateCommand();
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate);
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc: null, SelectedServerTabUtcOffsetMinutes);
var dbClause = BuildDbInClause(databaseNames, "database_name", 4, out var dbValues);

/* BPR buckets, falling back to the always-on DMV snapshot only when BPR has no buckets in the
Expand Down Expand Up @@ -627,7 +640,7 @@ public async Task<List<TimeSliceBucket>> GetDeadlockSlicerDataAsync(
{
using var connection = await OpenConnectionAsync();
using var command = connection.CreateCommand();
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate);
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc: null, SelectedServerTabUtcOffsetMinutes);

command.CommandText = @"
SELECT
Expand Down Expand Up @@ -670,7 +683,7 @@ public async Task<List<TrendPoint>> GetBlockingTrendAsync(int serverId, int hour
using var connection = await OpenConnectionAsync();
using var command = connection.CreateCommand();

var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc);
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc, SelectedServerTabUtcOffsetMinutes);
var dbClause = BuildDbInClause(databaseNames, "database_name", 4, out var dbValues);

/* Use blocked_process_reports from XE session - more reliable than point-in-time snapshots
Expand Down Expand Up @@ -722,7 +735,7 @@ public async Task<List<TrendPoint>> GetDeadlockTrendAsync(int serverId, int hour
using var connection = await OpenConnectionAsync();
using var command = connection.CreateCommand();

var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc);
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc, SelectedServerTabUtcOffsetMinutes);

command.CommandText = @"
SELECT
Expand Down Expand Up @@ -823,7 +836,7 @@ private async Task<List<CollectorCaptureCount>> GetCaptureCountsAsync(
using var connection = await OpenConnectionAsync();
using var command = connection.CreateCommand();

var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc);
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc, SelectedServerTabUtcOffsetMinutes);

command.CommandText = @"
SELECT
Expand Down Expand Up @@ -891,7 +904,7 @@ public async Task<List<LockWaitTrendPoint>> GetLockWaitTrendAsync(int serverId,
using var connection = await OpenConnectionAsync();
using var command = connection.CreateCommand();

var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc);
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc, SelectedServerTabUtcOffsetMinutes);

command.CommandText = @"
WITH raw AS
Expand Down
4 changes: 2 additions & 2 deletions Lite/Services/LocalDataService.BlockingStats.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public async Task<List<BlockingDurationStatsPoint>> GetBlockingDurationStatsAsyn
using var connection = await OpenConnectionAsync();
using var command = connection.CreateCommand();

var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc);
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc, SelectedServerTabUtcOffsetMinutes);
var dbClause = BuildDbInClause(databaseNames, "database_name", 4, out var dbValues);

/* BPR per-minute severity buckets, falling back to the always-on DMV snapshot only when BPR has none
Expand Down Expand Up @@ -152,7 +152,7 @@ public async Task<List<DeadlockSeverityStatsPoint>> GetDeadlockSeverityStatsAsyn
using (var connection = await OpenConnectionAsync())
using (var command = connection.CreateCommand())
{
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc);
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc, SelectedServerTabUtcOffsetMinutes);

command.CommandText = @"
SELECT
Expand Down
2 changes: 1 addition & 1 deletion Lite/Services/LocalDataService.CollectionHealth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ public async Task<List<CollectionLogRow>> GetRecentCollectionLogAsync(int server
using var connection = await OpenConnectionAsync();
using var command = connection.CreateCommand();

var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc);
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc, SelectedServerTabUtcOffsetMinutes);

command.CommandText = @"
SELECT
Expand Down
6 changes: 3 additions & 3 deletions Lite/Services/LocalDataService.ConfigChanges.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public async Task<List<ServerConfigChangeRow>> GetServerConfigChangesAsync(
int serverId, int hoursBack = 24, DateTime? fromDate = null, DateTime? toDate = null, DateTime? asOfUtc = null)
{
using var _q = TimeQuery("GetServerConfigChangesAsync", "v_server_config snapshot diff");
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc);
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc, SelectedServerTabUtcOffsetMinutes);
var snapshots = await ReadServerConfigSnapshotsAsync(serverId, endTime);
return ConfigChangeDiff.DiffServerConfigChanges(snapshots, startTime, endTime)
.Select(c => new ServerConfigChangeRow(c))
Expand All @@ -121,7 +121,7 @@ public async Task<List<DatabaseConfigChangeRow>> GetDatabaseConfigChangesAsync(
int serverId, int hoursBack = 24, DateTime? fromDate = null, DateTime? toDate = null, IReadOnlyList<string>? databaseNames = null, DateTime? asOfUtc = null)
{
using var _q = TimeQuery("GetDatabaseConfigChangesAsync", "v_database_config snapshot diff");
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc);
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc, SelectedServerTabUtcOffsetMinutes);
var snapshots = await ReadDatabaseConfigSnapshotsAsync(serverId, endTime, databaseNames);
return ConfigChangeDiff.DiffDatabaseConfigChanges(snapshots, startTime, endTime)
.Select(c => new DatabaseConfigChangeRow(c))
Expand All @@ -135,7 +135,7 @@ public async Task<List<TraceFlagChangeRow>> GetTraceFlagChangesAsync(
int serverId, int hoursBack = 24, DateTime? fromDate = null, DateTime? toDate = null, DateTime? asOfUtc = null)
{
using var _q = TimeQuery("GetTraceFlagChangesAsync", "v_trace_flags snapshot diff");
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc);
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc, SelectedServerTabUtcOffsetMinutes);
var snapshots = await ReadTraceFlagSnapshotsAsync(serverId, endTime);
return ConfigChangeDiff.DiffTraceFlagChanges(snapshots, startTime, endTime)
.Select(c => new TraceFlagChangeRow(c))
Expand Down
4 changes: 2 additions & 2 deletions Lite/Services/LocalDataService.CpuScheduler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public async Task<List<CpuSchedulerTrendPoint>> GetCpuSchedulerTrendAsync(int se
using var connection = await OpenConnectionAsync();
using var command = connection.CreateCommand();

var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate);
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc: null, SelectedServerTabUtcOffsetMinutes);

command.CommandText = @"
SELECT
Expand Down Expand Up @@ -78,7 +78,7 @@ FROM v_cpu_scheduler_stats
using var connection = await OpenConnectionAsync();
using var command = connection.CreateCommand();

var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc);
var (startTime, endTime) = GetTimeRange(hoursBack, fromDate, toDate, asOfUtc, SelectedServerTabUtcOffsetMinutes);

command.CommandText = @"
SELECT
Expand Down
Loading
Loading