From 89e6dccc75e220c92d72b0ab0e727992924aec6c Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Sat, 3 Oct 2026 12:05:19 -0400 Subject: [PATCH 1/7] Darling Viewer: the CPU Scheduler, Latch, Spinlock and Running Jobs grids show their snapshot time, and the latch and spinlock trends say where their data starts (part of #4966) --- .../Darling.Tests/ViewerLatchSpinlockTests.cs | 6 +- .../ViewerSnapshotTimeLabelTests.cs | 43 ++++++++++++ .../ViewerSnapshotTimeLiveTests.cs | 63 +++++++++++++++++ .../ViewerSnapshotTimePinTests.cs | 68 +++++++++++++++++++ .../ViewerDataService.LatchSpinlock.cs | 30 ++++++-- .../ViewerServerTab.CpuScheduler.cs | 1 + .../ViewerServerTab.LatchSpinlock.cs | 12 +++- .../ViewerServerTab.RunningJobs.cs | 2 + .../ViewerServerTab.SnapshotTime.cs | 34 ++++++++++ .../ViewerServerTab.xaml | 58 +++++++++++++--- .../ViewerTimeHelper.cs | 11 +++ 11 files changed, 308 insertions(+), 20 deletions(-) create mode 100644 Darling/Darling.Tests/ViewerSnapshotTimeLabelTests.cs create mode 100644 Darling/Darling.Tests/ViewerSnapshotTimeLiveTests.cs create mode 100644 Darling/Darling.Tests/ViewerSnapshotTimePinTests.cs create mode 100644 Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.SnapshotTime.cs diff --git a/Darling/Darling.Tests/ViewerLatchSpinlockTests.cs b/Darling/Darling.Tests/ViewerLatchSpinlockTests.cs index 16c04b20c3..6a869c4981 100644 --- a/Darling/Darling.Tests/ViewerLatchSpinlockTests.cs +++ b/Darling/Darling.Tests/ViewerLatchSpinlockTests.cs @@ -101,21 +101,21 @@ public void SnapshotSql_CarriesTheStoredInterval_SelectsDeltasAsStored(string wh public void SnapshotRows_NullTheDeltasOnTheMarker_AndSpellTheInterval() { var marker = new LatchStatsSnapshotRow("BUFFER", 0, 0, 0, - DeltaSeriesShaping.ReadableDelta(0, 0), DeltaSeriesShaping.ReadableDelta(0, 0), 0); + DeltaSeriesShaping.ReadableDelta(0, 0), DeltaSeriesShaping.ReadableDelta(0, 0), 0, DateTime.UnixEpoch); Assert.True(marker.IsUnknowable); Assert.Null(marker.DeltaWaitTimeMs); Assert.Null(marker.DeltaWaitingRequestsCount); Assert.Equal("restart / first sample", marker.IntervalDisplay); var measured = new SpinlockStatsSnapshotRow("LOCK_HASH", 900, 0, 0, 0, 0, - DeltaSeriesShaping.ReadableDelta(400, 120), DeltaSeriesShaping.ReadableDelta(0, 120), 120); + DeltaSeriesShaping.ReadableDelta(400, 120), DeltaSeriesShaping.ReadableDelta(0, 120), 120, DateTime.UnixEpoch); Assert.False(measured.IsUnknowable); Assert.Equal(400L, measured.DeltaCollisions); Assert.Equal(0L, measured.DeltaSpins); // a measured zero stays a zero Assert.Equal("120", measured.IntervalDisplay); var preV127 = new LatchStatsSnapshotRow("LOG_MANAGER", 0, 0, 0, - DeltaSeriesShaping.ReadableDelta(100, null), DeltaSeriesShaping.ReadableDelta(4000, null), null); + DeltaSeriesShaping.ReadableDelta(100, null), DeltaSeriesShaping.ReadableDelta(4000, null), null, DateTime.UnixEpoch); Assert.False(preV127.IsUnknowable); Assert.Equal(4000L, preV127.DeltaWaitTimeMs); Assert.Equal("not stored", preV127.IntervalDisplay); diff --git a/Darling/Darling.Tests/ViewerSnapshotTimeLabelTests.cs b/Darling/Darling.Tests/ViewerSnapshotTimeLabelTests.cs new file mode 100644 index 0000000000..4830302c1c --- /dev/null +++ b/Darling/Darling.Tests/ViewerSnapshotTimeLabelTests.cs @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2026 Erik Darling, Darling Data LLC + * + * This file is part of the SQL Server Performance Monitor. + * + * Licensed under the MIT License. See LICENSE file in the project root for full license information. + */ + +using System; +using System.Globalization; +using System.Threading; +using PerformanceMonitor.Darling.Viewer; +using Xunit; + +namespace Darling.Tests; + +/// The "Snapshot at <time>" label a newest-snapshot surface shows instead of a data-start note (#4966). +public sealed class ViewerSnapshotTimeLabelTests +{ + [Fact] + public void TheLabel_IsTheDisplayZonesWallTime_ToTheSecond() + { + var zone = TimeZoneInfo.CreateCustomTimeZone("t", TimeSpan.FromHours(-5), "t", "t"); + var text = ViewerTimeHelper.FormatSnapshotLabel(new DateTime(2026, 3, 4, 15, 6, 7, DateTimeKind.Utc), zone); + Assert.Equal("Snapshot at 2026-03-04 10:06:07", text); + } + + [Fact] + public void TheLabel_IsOnTheInvariantCulture_WhateverTheThreadsCalendar() + { + var saved = CultureInfo.CurrentCulture; + try + { + CultureInfo.CurrentCulture = new CultureInfo("th-TH"); + var text = ViewerTimeHelper.FormatSnapshotLabel(new DateTime(2026, 3, 4, 15, 6, 7, DateTimeKind.Utc), TimeZoneInfo.Utc); + Assert.Equal("Snapshot at 2026-03-04 15:06:07", text); + } + finally + { + CultureInfo.CurrentCulture = saved; + } + } +} diff --git a/Darling/Darling.Tests/ViewerSnapshotTimeLiveTests.cs b/Darling/Darling.Tests/ViewerSnapshotTimeLiveTests.cs new file mode 100644 index 0000000000..9784d79c7c --- /dev/null +++ b/Darling/Darling.Tests/ViewerSnapshotTimeLiveTests.cs @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2026 Erik Darling, Darling Data LLC + * + * This file is part of the SQL Server Performance Monitor. + * + * Licensed under the MIT License. See LICENSE file in the project root for full license information. + */ + +using System; +using System.Threading; +using System.Threading.Tasks; +using Npgsql; +using PerformanceMonitor.Darling.Storage; +using PerformanceMonitor.Darling.Viewer; +using Xunit; + +namespace Darling.Tests; + +/// The latch and spinlock snapshot reads against a real store return the collection time of the snapshot they rendered (#4966). +[Collection("viewer-time-statics")] +public sealed class ViewerSnapshotTimeLiveTests +{ + private const int ServerId = -496660; + + [Fact] + public async Task TheLatchAndSpinlockSnapshots_CarryTheNewestCollectionTime_AgainstDevPostgres() + { + var ct = TestContext.Current.CancellationToken; + await using var scratch = await QueryGridSeed.OpenScratchAsync(ct); + var end = QueryGridSeed.NowToTheMinute(); + var older = end.AddHours(-3); + var newest = end.AddHours(-1); + await using (var connection = new NpgsqlConnection(scratch.ConnectionString)) + { + await connection.OpenAsync(ct); + await PgMigrations.MigrateAsync(connection, ct); + await EventGridSeed.AddServerAsync(connection, ServerId, "snapshot-time", end.AddDays(-2), "latch_stats", end, clockOffsetMinutes: null, ct); + long id = 1; + foreach (var at in new[] { older, newest }) + { + await ExecAsync(connection, $"INSERT INTO latch_stats (collection_id, collection_time, server_id, server_name, latch_class, waiting_requests_count, wait_time_ms, max_wait_time_ms, delta_waiting_requests_count, delta_wait_time_ms, delta_max_wait_time_ms, sample_interval_seconds) VALUES ({id++}, @t, {ServerId}, 'snapshot-time', 'BUFFER', 10, 20, 5, 1, 2, 1, 60)", at, ct); + await ExecAsync(connection, $"INSERT INTO spinlock_stats (collection_id, collection_time, server_id, server_name, spinlock_name, collisions, spins, spins_per_collision, sleep_time, backoffs, delta_collisions, delta_spins, delta_sleep_time, delta_backoffs, sample_interval_seconds) VALUES ({id++}, @t, {ServerId}, 'snapshot-time', 'LOCK_HASH', 10, 20, 2, 0, 0, 1, 2, 0, 0, 60)", at, ct); + } + } + + var viewer = new ViewerDataService(scratch.ConnectionString); + var latch = await viewer.GetLatchStatsSnapshotAsync(ServerId, end.AddDays(-7), end, ct); + var spinlock = await viewer.GetSpinlockStatsSnapshotAsync(ServerId, end.AddDays(-7), end, ct); + + Assert.Single(latch); + Assert.Equal(newest, latch[0].CollectionTime); + Assert.Single(spinlock); + Assert.Equal(newest, spinlock[0].CollectionTime); + Assert.Empty(await viewer.GetLatchStatsSnapshotAsync(ServerId + 1, end.AddDays(-7), end, ct)); + } + + private static async Task ExecAsync(NpgsqlConnection connection, string sql, DateTime at, CancellationToken ct) + { + await using var command = new NpgsqlCommand(sql, connection); + command.Parameters.AddWithValue("t", DateTime.SpecifyKind(at, DateTimeKind.Unspecified)); + await command.ExecuteNonQueryAsync(ct); + } +} diff --git a/Darling/Darling.Tests/ViewerSnapshotTimePinTests.cs b/Darling/Darling.Tests/ViewerSnapshotTimePinTests.cs new file mode 100644 index 0000000000..c9779bc6ca --- /dev/null +++ b/Darling/Darling.Tests/ViewerSnapshotTimePinTests.cs @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2026 Erik Darling, Darling Data LLC + * + * This file is part of the SQL Server Performance Monitor. + * + * Licensed under the MIT License. See LICENSE file in the project root for full license information. + */ + +using System; +using System.Text.RegularExpressions; +using Xunit; +using static Darling.Tests.RepoFile; + +namespace Darling.Tests; + +/// How the CPU Scheduler, Latch, Spinlock and Running Jobs grids are wired to show their snapshot's time (#4966). These read +/// the source, as the other data-start wiring pins do; the label control's behavior is the STA fact in +/// ViewerSnapshotTimeLabelStaTests. +public sealed class ViewerSnapshotTimePinTests +{ + private static string ViewerFile(string file) => + ReadRepoFile("Darling", "PerformanceMonitor.Darling.Viewer", file).ReplaceLineEndings("\n"); + + [Theory] + [InlineData("ViewerServerTab.CpuScheduler.cs", "ShowSnapshotTime(CpuSchedulerSnapshotTime, snapshotTask.Result?.CollectionTime)")] + [InlineData("ViewerServerTab.RunningJobs.cs", "ShowSnapshotTime(RunningJobsSnapshotTime, jobs.Count == 0 ? null : jobs[0].CollectionTime)")] + [InlineData("ViewerServerTab.LatchSpinlock.cs", "ShowSnapshotTime(LatchStatsSnapshotTime, latchSnapshotTask.Result.Count == 0 ? null : latchSnapshotTask.Result[0].CollectionTime)")] + [InlineData("ViewerServerTab.LatchSpinlock.cs", "ShowSnapshotTime(SpinlockStatsSnapshotTime, spinlockSnapshotTask.Result.Count == 0 ? null : spinlockSnapshotTask.Result[0].CollectionTime)")] + public void EachLoad_SetsItsLabelFromTheSnapshotItRendered(string file, string call) + { + var source = ViewerFile(file); + Assert.Contains(call, source, StringComparison.Ordinal); + } + + [Fact] + public void TheLatchLoad_ProbesBothCollectors_OutsideTheJoin_AndCountsThemInTheFanOut() + { + var source = ViewerFile("ViewerServerTab.LatchSpinlock.cs"); + Assert.Contains("ViewerReadFanOut.Of(6)", source, StringComparison.Ordinal); + var join = Regex.Match(source, @"await Task\.WhenAll\(([^;]*)\);").Groups[1].Value; + Assert.DoesNotContain("DataStartTask", join, StringComparison.Ordinal); + Assert.True(source.IndexOf("GetLatchStatsDataStartAsync", StringComparison.Ordinal) < source.IndexOf("await Task.WhenAll", StringComparison.Ordinal)); + Assert.Contains("DataStartOrNullAsync(latchDataStartTask", source, StringComparison.Ordinal); + Assert.Contains("DataStartOrNullAsync(spinlockDataStartTask", source, StringComparison.Ordinal); + var data = ViewerFile("ViewerDataService.LatchSpinlock.cs"); + Assert.Contains("ForCollectorTable(\"latch_stats\")", data, StringComparison.Ordinal); + Assert.Contains("ForCollectorTable(\"spinlock_stats\")", data, StringComparison.Ordinal); + } + + [Theory] + [InlineData("LatchSnapshotSql")] + [InlineData("SpinlockSnapshotSql")] + public void TheSnapshotSql_ReturnsTheCollectionTimeAsItsLastColumn(string constant) + { + var sql = constant == "LatchSnapshotSql" ? PerformanceMonitor.Darling.Viewer.ViewerDataService.LatchSnapshotSql : PerformanceMonitor.Darling.Viewer.ViewerDataService.SpinlockSnapshotSql; + Assert.Matches(@"sample_interval_seconds,\s+collection_time\s+FROM v_", sql); + } + + [Fact] + public void TheXaml_DeclaresAMutedLabelAboveEachGrid() + { + var xaml = ViewerFile("ViewerServerTab.xaml"); + foreach (var name in new[] { "CpuSchedulerSnapshotTime", "RunningJobsSnapshotTime", "LatchStatsSnapshotTime", "SpinlockStatsSnapshotTime" }) + { + Assert.Matches($@"]*FontSize=""11""\s+Foreground=""\{{DynamicResource ForegroundMutedBrush\}}""", xaml); + } + } +} diff --git a/Darling/PerformanceMonitor.Darling.Viewer/ViewerDataService.LatchSpinlock.cs b/Darling/PerformanceMonitor.Darling.Viewer/ViewerDataService.LatchSpinlock.cs index 71ca2f50c7..319734d08a 100644 --- a/Darling/PerformanceMonitor.Darling.Viewer/ViewerDataService.LatchSpinlock.cs +++ b/Darling/PerformanceMonitor.Darling.Viewer/ViewerDataService.LatchSpinlock.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Npgsql; using PerformanceMonitor.Common; +using PerformanceMonitor.Darling.Storage; namespace PerformanceMonitor.Darling.Viewer; @@ -36,7 +37,8 @@ public sealed record LatchStatsSnapshotRow( long MaxWaitTimeMs, long? DeltaWaitingRequestsCount, long? DeltaWaitTimeMs, - int? SampleIntervalSeconds) + int? SampleIntervalSeconds, + DateTime CollectionTime) { /// True when the row's deltas are the unknowable marker — a stored interval of exactly 0 (the /// FileIoStatsRow / resource-semaphore idiom). @@ -63,7 +65,8 @@ public sealed record SpinlockStatsSnapshotRow( long Backoffs, long? DeltaCollisions, long? DeltaSpins, - int? SampleIntervalSeconds) + int? SampleIntervalSeconds, + DateTime CollectionTime) { /// True when the row's deltas are the unknowable marker — a stored interval of exactly 0. public bool IsUnknowable => SampleIntervalSeconds == 0; @@ -144,7 +147,8 @@ FROM v_latch_stats max_wait_time_ms, delta_waiting_requests_count, delta_wait_time_ms, - sample_interval_seconds + sample_interval_seconds, + collection_time FROM v_latch_stats WHERE server_id = $1 AND collection_time = (SELECT mx FROM latest) @@ -220,7 +224,8 @@ FROM v_spinlock_stats backoffs, delta_collisions, delta_spins, - sample_interval_seconds + sample_interval_seconds, + collection_time FROM v_spinlock_stats WHERE server_id = $1 AND collection_time = (SELECT mx FROM latest) @@ -278,7 +283,8 @@ deltas stand. The interval column is integer in the store (V127), read as such. reader.IsDBNull(3) ? 0 : reader.GetInt64(3), DeltaSeriesShaping.ReadableDelta(reader.IsDBNull(4) ? 0 : reader.GetInt64(4), interval), DeltaSeriesShaping.ReadableDelta(reader.IsDBNull(5) ? 0 : reader.GetInt64(5), interval), - interval)); + interval, + reader.GetDateTime(7))); } return result; @@ -334,9 +340,21 @@ public async Task> GetSpinlockStatsSnapshotAsync( reader.IsDBNull(5) ? 0 : reader.GetInt64(5), DeltaSeriesShaping.ReadableDelta(reader.IsDBNull(6) ? 0 : reader.GetInt64(6), interval), DeltaSeriesShaping.ReadableDelta(reader.IsDBNull(7) ? 0 : reader.GetInt64(7), interval), - interval)); + interval, + reader.GetDateTime(9))); } return result; } + + /// Where this server's latch_stats coverage starts for the window (the shared probe), so the trend chart can say so + /// when an empty stretch would otherwise draw as a flat zero (#4966). + public Task GetLatchStatsDataStartAsync(int serverId, DateTime startUtc, DateTime endUtc, CancellationToken cancellationToken = default) => + DataWindowFloor.GetForServerAsync(_dataSource, DataWindowFloor.Source.ForCollectorTable("latch_stats"), serverId, startUtc, endUtc, + ViewerCommandDeadlines.CurrentInteractiveReadSeconds, cancellationToken); + + /// The spinlock_stats twin of . + public Task GetSpinlockStatsDataStartAsync(int serverId, DateTime startUtc, DateTime endUtc, CancellationToken cancellationToken = default) => + DataWindowFloor.GetForServerAsync(_dataSource, DataWindowFloor.Source.ForCollectorTable("spinlock_stats"), serverId, startUtc, endUtc, + ViewerCommandDeadlines.CurrentInteractiveReadSeconds, cancellationToken); } diff --git a/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.CpuScheduler.cs b/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.CpuScheduler.cs index 3812c27a77..13b584aedf 100644 --- a/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.CpuScheduler.cs +++ b/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.CpuScheduler.cs @@ -58,6 +58,7 @@ private async Task LoadCpuSchedulerAsync() RenderCpuSchedulerChart(trendTask.Result); CpuSchedulerGrid.ItemsSource = CpuSchedulerMetrics.BuildMetrics(snapshotTask.Result); + ShowSnapshotTime(CpuSchedulerSnapshotTime, snapshotTask.Result?.CollectionTime); var gap = CpuSchedulerGapNote(_server.ServerName, _server.EngineEdition, _server.EngineKind); CpuSchedulerNoDataMessage.Text = gap ?? ""; diff --git a/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.LatchSpinlock.cs b/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.LatchSpinlock.cs index a8f479d4b6..ebe7cc9a66 100644 --- a/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.LatchSpinlock.cs +++ b/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.LatchSpinlock.cs @@ -62,19 +62,29 @@ private async Task LoadLatchSpinlockAsync() { var (startUtc, endUtc) = GetWindowUtc(); - using var readFanOut = ViewerReadFanOut.Of(4); + /* Six reads in flight: the four feeds below and the two charts' data-start probes (#4966). */ + using var readFanOut = ViewerReadFanOut.Of(6); var latchTrendTask = _dataService.GetLatchStatsTrendAsync(_server.ServerId, startUtc, endUtc); var latchSnapshotTask = _dataService.GetLatchStatsSnapshotAsync(_server.ServerId, startUtc, endUtc); var spinlockTrendTask = _dataService.GetSpinlockStatsTrendAsync(_server.ServerId, startUtc, endUtc); var spinlockSnapshotTask = _dataService.GetSpinlockStatsSnapshotAsync(_server.ServerId, startUtc, endUtc); + /* #4966: each chart draws a flat zero over an empty stretch, so each says where its collector's coverage starts. The probes stay OUT + of the join below: a probe that throws costs the chart's note (DataStartOrNullAsync catches it), never the rows. */ + var latchDataStartTask = _dataService.GetLatchStatsDataStartAsync(_server.ServerId, startUtc, endUtc); + var spinlockDataStartTask = _dataService.GetSpinlockStatsDataStartAsync(_server.ServerId, startUtc, endUtc); + await Task.WhenAll(latchTrendTask, latchSnapshotTask, spinlockTrendTask, spinlockSnapshotTask); RenderLatchStatsChart(latchTrendTask.Result); LatchStatsGrid.ItemsSource = latchSnapshotTask.Result; + ShowSnapshotTime(LatchStatsSnapshotTime, latchSnapshotTask.Result.Count == 0 ? null : latchSnapshotTask.Result[0].CollectionTime); + UpdateTruncationBanner(LatchStatsTruncationBanner, await DataStartOrNullAsync(latchDataStartTask, "Latch Stats"), startUtc); RenderSpinlockStatsChart(spinlockTrendTask.Result); SpinlockStatsGrid.ItemsSource = spinlockSnapshotTask.Result; + ShowSnapshotTime(SpinlockStatsSnapshotTime, spinlockSnapshotTask.Result.Count == 0 ? null : spinlockSnapshotTask.Result[0].CollectionTime); + UpdateTruncationBanner(SpinlockStatsTruncationBanner, await DataStartOrNullAsync(spinlockDataStartTask, "Spinlock Stats"), startUtc); } private void RenderLatchStatsChart(List data) diff --git a/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.RunningJobs.cs b/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.RunningJobs.cs index 8b8bb335de..0e8a5d411c 100644 --- a/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.RunningJobs.cs +++ b/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.RunningJobs.cs @@ -41,6 +41,8 @@ priced against contention that has already finished. */ readFanOut.Release(); _runningJobsFilterMgr!.UpdateData(jobs); + /* The grid is one collection's rows, so any row's CollectionTime (naive UTC, the display-zone frame, not StartTimeLocal's) names it. */ + ShowSnapshotTime(RunningJobsSnapshotTime, jobs.Count == 0 ? null : jobs[0].CollectionTime); await ShowEngineGapAsync(RunningJobsNoDataMessage, "running_jobs", jobs.Count); RunningJobsMsdbWarning.Visibility = ShouldShowMsdbBanner(status) ? Visibility.Visible : Visibility.Collapsed; diff --git a/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.SnapshotTime.cs b/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.SnapshotTime.cs new file mode 100644 index 0000000000..9d6f3ecc50 --- /dev/null +++ b/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.SnapshotTime.cs @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2026 Erik Darling, Darling Data LLC + * + * This file is part of the SQL Server Performance Monitor. + * + * Licensed under the MIT License. See LICENSE file in the project root for full license information. + */ + +using System; +using System.Windows; +using System.Windows.Controls; + +namespace PerformanceMonitor.Darling.Viewer; + +public partial class ViewerServerTab +{ + /// + /// Sets the muted "Snapshot at <time>" label above a newest-snapshot grid (#4966): the surface shows the collection it + /// rendered instead of a data-start note. An empty snapshot ( null) hides the label and leaves the + /// grid's own empty text to speak. + /// + internal static void ShowSnapshotTime(TextBlock label, DateTime? collectionUtc) + { + if (collectionUtc is not DateTime utc) + { + label.Text = ""; + label.Visibility = Visibility.Collapsed; + return; + } + + label.Text = ViewerTimeHelper.FormatSnapshotLabel(utc); + label.Visibility = Visibility.Visible; + } +} diff --git a/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.xaml b/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.xaml index a0e608054f..8b16de845d 100644 --- a/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.xaml +++ b/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.xaml @@ -2127,10 +2127,14 @@ + - ": the collection the metric grid shows (#4966). --> + +