@@ -27,7 +27,6 @@ public partial class CorrelatedTimelineLanesControl : UserControl
2727 private DatabaseService ? _dataService ;
2828 private SqlServerBaselineProvider ? _baselineProvider ;
2929 private CorrelatedCrosshairManager ? _crosshairManager ;
30- private bool _isRefreshing ;
3130
3231 public CorrelatedTimelineLanesControl ( )
3332 {
@@ -66,176 +65,168 @@ public void Initialize(DatabaseService dataService, SqlServerBaselineProvider? b
6665 public async Task RefreshAsync ( int hoursBack , DateTime ? fromDate , DateTime ? toDate ,
6766 ( DateTime From , DateTime To ) ? comparisonRange = null )
6867 {
69- if ( _dataService == null || _isRefreshing ) return ;
70- _isRefreshing = true ;
68+ if ( _dataService == null ) return ;
69+
70+ _crosshairManager ? . PrepareForRefresh ( ) ;
71+
72+ var cpuTask = _dataService . GetCpuUtilizationAsync ( hoursBack , fromDate , toDate ) ;
73+ var waitTask = _dataService . GetTotalWaitStatsTrendAsync ( hoursBack , fromDate , toDate ) ;
74+ var blockingTask = _dataService . GetBlockedSessionTrendAsync ( hoursBack , fromDate , toDate ) ;
75+ var deadlockTask = _dataService . GetDeadlockTrendAsync ( hoursBack , fromDate , toDate ) ;
76+ var memoryTask = _dataService . GetMemoryStatsAsync ( hoursBack , fromDate , toDate ) ;
77+ var fileIoTask = _dataService . GetFileIoLatencyTimeSeriesAsync ( false , hoursBack , fromDate , toDate ) ;
78+
79+ // Fetch baselines for band rendering if provider is available
80+ var referenceTime = fromDate ?? DateTime . UtcNow . AddHours ( - hoursBack ) ;
81+ Task < BaselineBucket ? > ? cpuBaselineTask = null ;
82+ Task < BaselineBucket ? > ? waitBaselineTask = null ;
83+ Task < BaselineBucket ? > ? ioBaselineTask = null ;
84+ Task < BaselineBucket ? > ? blockingBaselineTask = null ;
85+ Task < BaselineBucket ? > ? deadlockBaselineTask = null ;
86+
87+ if ( _baselineProvider != null )
88+ {
89+ cpuBaselineTask = GetBaselineAsync ( SqlServerMetricNames . Cpu , referenceTime ) ;
90+ waitBaselineTask = GetBaselineAsync ( SqlServerMetricNames . WaitStats , referenceTime ) ;
91+ ioBaselineTask = GetBaselineAsync ( SqlServerMetricNames . IoLatency , referenceTime ) ;
92+ blockingBaselineTask = GetBaselineAsync ( SqlServerMetricNames . Blocking , referenceTime ) ;
93+ deadlockBaselineTask = GetBaselineAsync ( SqlServerMetricNames . Deadlock , referenceTime ) ;
94+ }
7195
7296 try
7397 {
74- _crosshairManager ? . PrepareForRefresh ( ) ;
75-
76- var cpuTask = _dataService . GetCpuUtilizationAsync ( hoursBack , fromDate , toDate ) ;
77- var waitTask = _dataService . GetTotalWaitStatsTrendAsync ( hoursBack , fromDate , toDate ) ;
78- var blockingTask = _dataService . GetBlockedSessionTrendAsync ( hoursBack , fromDate , toDate ) ;
79- var deadlockTask = _dataService . GetDeadlockTrendAsync ( hoursBack , fromDate , toDate ) ;
80- var memoryTask = _dataService . GetMemoryStatsAsync ( hoursBack , fromDate , toDate ) ;
81- var fileIoTask = _dataService . GetFileIoLatencyTimeSeriesAsync ( false , hoursBack , fromDate , toDate ) ;
82-
83- // Fetch baselines for band rendering if provider is available
84- var referenceTime = fromDate ?? DateTime . UtcNow . AddHours ( - hoursBack ) ;
85- Task < BaselineBucket ? > ? cpuBaselineTask = null ;
86- Task < BaselineBucket ? > ? waitBaselineTask = null ;
87- Task < BaselineBucket ? > ? ioBaselineTask = null ;
88- Task < BaselineBucket ? > ? blockingBaselineTask = null ;
89- Task < BaselineBucket ? > ? deadlockBaselineTask = null ;
90-
91- if ( _baselineProvider != null )
92- {
93- cpuBaselineTask = GetBaselineAsync ( SqlServerMetricNames . Cpu , referenceTime ) ;
94- waitBaselineTask = GetBaselineAsync ( SqlServerMetricNames . WaitStats , referenceTime ) ;
95- ioBaselineTask = GetBaselineAsync ( SqlServerMetricNames . IoLatency , referenceTime ) ;
96- blockingBaselineTask = GetBaselineAsync ( SqlServerMetricNames . Blocking , referenceTime ) ;
97- deadlockBaselineTask = GetBaselineAsync ( SqlServerMetricNames . Deadlock , referenceTime ) ;
98- }
98+ var tasks = new List < Task > { cpuTask , waitTask , blockingTask , deadlockTask , memoryTask , fileIoTask } ;
99+ if ( cpuBaselineTask != null ) tasks . Add ( cpuBaselineTask ) ;
100+ if ( waitBaselineTask != null ) tasks . Add ( waitBaselineTask ) ;
101+ if ( ioBaselineTask != null ) tasks . Add ( ioBaselineTask ) ;
102+ if ( blockingBaselineTask != null ) tasks . Add ( blockingBaselineTask ) ;
103+ if ( deadlockBaselineTask != null ) tasks . Add ( deadlockBaselineTask ) ;
104+ await Task . WhenAll ( tasks ) ;
105+ }
106+ catch ( Exception ex )
107+ {
108+ Debug . WriteLine ( $ "CorrelatedLanes: Data fetch failed: { ex . Message } ") ;
109+ }
99110
100- try
101- {
102- var tasks = new List < Task > { cpuTask , waitTask , blockingTask , deadlockTask , memoryTask , fileIoTask } ;
103- if ( cpuBaselineTask != null ) tasks . Add ( cpuBaselineTask ) ;
104- if ( waitBaselineTask != null ) tasks . Add ( waitBaselineTask ) ;
105- if ( ioBaselineTask != null ) tasks . Add ( ioBaselineTask ) ;
106- if ( blockingBaselineTask != null ) tasks . Add ( blockingBaselineTask ) ;
107- if ( deadlockBaselineTask != null ) tasks . Add ( deadlockBaselineTask ) ;
108- await Task . WhenAll ( tasks ) ;
109- }
110- catch ( Exception ex )
111- {
112- Debug . WriteLine ( $ "CorrelatedLanes: Data fetch failed: { ex . Message } ") ;
113- }
111+ var cpuBaseline = cpuBaselineTask is { IsCompletedSuccessfully : true } ? cpuBaselineTask . Result : null ;
112+ var waitBaseline = waitBaselineTask is { IsCompletedSuccessfully : true } ? waitBaselineTask . Result : null ;
113+ var ioBaseline = ioBaselineTask is { IsCompletedSuccessfully : true } ? ioBaselineTask . Result : null ;
114+ var blockingBaseline = blockingBaselineTask is { IsCompletedSuccessfully : true } ? blockingBaselineTask . Result : null ;
115+ var deadlockBaseline = deadlockBaselineTask is { IsCompletedSuccessfully : true } ? deadlockBaselineTask . Result : null ;
116+ var blockingLaneBaseline = blockingBaseline ?? deadlockBaseline ;
117+
118+ // minAnomalyValue: absolute floor below which dots/arrows are suppressed even if outside band.
119+ // Prevents "1% CPU above 0.5% baseline" false alarms on idle servers.
120+ if ( cpuTask . IsCompletedSuccessfully )
121+ UpdateLane ( CpuChart , "CPU %" ,
122+ cpuTask . Result . OrderBy ( d => d . SampleTime )
123+ . Select ( d => ( d . SampleTime . ToOADate ( ) , ( double ) d . SqlServerCpuUtilization ) ) . ToList ( ) ,
124+ "#4FC3F7" , 0 , 105 , cpuBaseline , minAnomalyValue : 10 ) ;
125+ else
126+ ShowEmpty ( CpuChart , "CPU %" ) ;
114127
115- var cpuBaseline = cpuBaselineTask is { IsCompletedSuccessfully : true } ? cpuBaselineTask . Result : null ;
116- var waitBaseline = waitBaselineTask is { IsCompletedSuccessfully : true } ? waitBaselineTask . Result : null ;
117- var ioBaseline = ioBaselineTask is { IsCompletedSuccessfully : true } ? ioBaselineTask . Result : null ;
118- var blockingBaseline = blockingBaselineTask is { IsCompletedSuccessfully : true } ? blockingBaselineTask . Result : null ;
119- var deadlockBaseline = deadlockBaselineTask is { IsCompletedSuccessfully : true } ? deadlockBaselineTask . Result : null ;
120- var blockingLaneBaseline = blockingBaseline ?? deadlockBaseline ;
121-
122- // minAnomalyValue: absolute floor below which dots/arrows are suppressed even if outside band.
123- // Prevents "1% CPU above 0.5% baseline" false alarms on idle servers.
124- if ( cpuTask . IsCompletedSuccessfully )
125- UpdateLane ( CpuChart , "CPU %" ,
126- cpuTask . Result . OrderBy ( d => d . SampleTime )
127- . Select ( d => ( d . SampleTime . ToOADate ( ) , ( double ) d . SqlServerCpuUtilization ) ) . ToList ( ) ,
128- "#4FC3F7" , 0 , 105 , cpuBaseline , minAnomalyValue : 10 ) ;
129- else
130- ShowEmpty ( CpuChart , "CPU %" ) ;
131-
132- if ( waitTask . IsCompletedSuccessfully )
133- UpdateLane ( WaitStatsChart , "Wait ms/sec" ,
134- waitTask . Result . Select ( d => ( d . CollectionTime . ToOADate ( ) , ( double ) d . WaitTimeMsPerSecond ) ) . ToList ( ) ,
135- "#FFB74D" , baseline : waitBaseline , minAnomalyValue : 100 ) ;
136- else
137- ShowEmpty ( WaitStatsChart , "Wait ms/sec" ) ;
138-
139- try
140- {
141- var blockingData = blockingTask . IsCompletedSuccessfully
142- ? blockingTask . Result
143- . GroupBy ( d => d . CollectionTime )
144- . OrderBy ( g => g . Key )
145- . Select ( g => ( g . Key . ToOADate ( ) , ( double ) g . Sum ( x => x . BlockedCount ) ) )
146- . ToList ( )
147- : new List < ( double , double ) > ( ) ;
148- var deadlockData = deadlockTask . IsCompletedSuccessfully
149- ? deadlockTask . Result
150- . Select ( d => ( d . CollectionTime . ToOADate ( ) , ( double ) d . BlockedCount ) )
151- . ToList ( )
152- : new List < ( double , double ) > ( ) ;
153- UpdateBlockingLane ( blockingData , deadlockData , blockingLaneBaseline ) ;
154- }
155- catch ( Exception ex )
156- {
157- Debug . WriteLine ( $ "CorrelatedLanes: Blocking lane failed: { ex } ") ;
158- ShowEmpty ( BlockingChart , "Blocking & Deadlocking" ) ;
159- }
128+ if ( waitTask . IsCompletedSuccessfully )
129+ UpdateLane ( WaitStatsChart , "Wait ms/sec" ,
130+ waitTask . Result . Select ( d => ( d . CollectionTime . ToOADate ( ) , ( double ) d . WaitTimeMsPerSecond ) ) . ToList ( ) ,
131+ "#FFB74D" , baseline : waitBaseline , minAnomalyValue : 100 ) ;
132+ else
133+ ShowEmpty ( WaitStatsChart , "Wait ms/sec" ) ;
160134
161- if ( memoryTask . IsCompletedSuccessfully )
162- UpdateLane ( MemoryChart , "Buffer Pool MB" ,
163- memoryTask . Result . Select ( d => ( d . CollectionTime . ToOADate ( ) , ( double ) d . TotalMemoryMb ) ) . ToList ( ) ,
164- "#CE93D8" ) ;
165- else
166- ShowEmpty ( MemoryChart , "Buffer Pool MB" ) ;
135+ try
136+ {
137+ var blockingData = blockingTask . IsCompletedSuccessfully
138+ ? blockingTask . Result
139+ . GroupBy ( d => d . CollectionTime )
140+ . OrderBy ( g => g . Key )
141+ . Select ( g => ( g . Key . ToOADate ( ) , ( double ) g . Sum ( x => x . BlockedCount ) ) )
142+ . ToList ( )
143+ : new List < ( double , double ) > ( ) ;
144+ var deadlockData = deadlockTask . IsCompletedSuccessfully
145+ ? deadlockTask . Result
146+ . Select ( d => ( d . CollectionTime . ToOADate ( ) , ( double ) d . BlockedCount ) )
147+ . ToList ( )
148+ : new List < ( double , double ) > ( ) ;
149+ UpdateBlockingLane ( blockingData , deadlockData , blockingLaneBaseline ) ;
150+ }
151+ catch ( Exception ex )
152+ {
153+ Debug . WriteLine ( $ "CorrelatedLanes: Blocking lane failed: { ex } ") ;
154+ ShowEmpty ( BlockingChart , "Blocking & Deadlocking" ) ;
155+ }
156+
157+ if ( memoryTask . IsCompletedSuccessfully )
158+ UpdateLane ( MemoryChart , "Buffer Pool MB" ,
159+ memoryTask . Result . Select ( d => ( d . CollectionTime . ToOADate ( ) , ( double ) d . TotalMemoryMb ) ) . ToList ( ) ,
160+ "#CE93D8" ) ;
161+ else
162+ ShowEmpty ( MemoryChart , "Buffer Pool MB" ) ;
163+
164+ if ( fileIoTask . IsCompletedSuccessfully )
165+ {
166+ var ioGrouped = fileIoTask . Result
167+ . GroupBy ( d => d . CollectionTime )
168+ . OrderBy ( g => g . Key )
169+ . Select ( g => ( g . Key . ToOADate ( ) , ( double ) g . Average ( x => x . ReadLatencyMs ) ) )
170+ . ToList ( ) ;
171+ UpdateLane ( FileIoChart , "I/O ms" , ioGrouped , "#81C784" , baseline : ioBaseline , minAnomalyValue : 2 ) ;
172+ }
173+ else
174+ ShowEmpty ( FileIoChart , "I/O ms" ) ;
175+
176+ // Comparison overlay — fetch reference period data and render as ghost lines
177+ if ( comparisonRange . HasValue )
178+ {
179+ var refFrom = comparisonRange . Value . From ;
180+ var refTo = comparisonRange . Value . To ;
181+ var timeShift = ( fromDate ?? DateTime . UtcNow . AddHours ( - hoursBack ) ) - refFrom ;
182+
183+ var refCpuTask = _dataService . GetCpuUtilizationAsync ( 0 , refFrom , refTo ) ;
184+ var refWaitTask = _dataService . GetTotalWaitStatsTrendAsync ( 0 , refFrom , refTo ) ;
185+ var refBlockingTask = _dataService . GetBlockedSessionTrendAsync ( 0 , refFrom , refTo ) ;
186+ var refMemoryTask = _dataService . GetMemoryStatsAsync ( 0 , refFrom , refTo ) ;
187+ var refIoTask = _dataService . GetFileIoLatencyTimeSeriesAsync ( false , 0 , refFrom , refTo ) ;
167188
168- if ( fileIoTask . IsCompletedSuccessfully )
189+ try { await Task . WhenAll ( refCpuTask , refWaitTask , refBlockingTask , refMemoryTask , refIoTask ) ; }
190+ catch ( Exception ex ) { Debug . WriteLine ( $ "CorrelatedLanes: Comparison fetch failed: { ex . Message } ") ; }
191+
192+ if ( refCpuTask . IsCompletedSuccessfully )
193+ AddGhostLine ( CpuChart , refCpuTask . Result
194+ . Select ( d => ( d . SampleTime . Add ( timeShift ) . ToOADate ( ) , ( double ) d . SqlServerCpuUtilization ) ) . ToList ( ) , "#4FC3F7" ) ;
195+
196+ if ( refWaitTask . IsCompletedSuccessfully )
197+ AddGhostLine ( WaitStatsChart , refWaitTask . Result
198+ . Select ( d => ( d . CollectionTime . Add ( timeShift ) . ToOADate ( ) , ( double ) d . WaitTimeMsPerSecond ) ) . ToList ( ) , "#FFB74D" ) ;
199+
200+ if ( refBlockingTask . IsCompletedSuccessfully )
169201 {
170- var ioGrouped = fileIoTask . Result
202+ var refBlocking = refBlockingTask . Result
171203 . GroupBy ( d => d . CollectionTime )
172204 . OrderBy ( g => g . Key )
173- . Select ( g => ( g . Key . ToOADate ( ) , ( double ) g . Average ( x => x . ReadLatencyMs ) ) )
205+ . Select ( g => ( g . Key . Add ( timeShift ) . ToOADate ( ) , ( double ) g . Sum ( x => x . BlockedCount ) ) )
174206 . ToList ( ) ;
175- UpdateLane ( FileIoChart , "I/O ms" , ioGrouped , "#81C784" , baseline : ioBaseline , minAnomalyValue : 2 ) ;
207+ if ( refBlocking . Count > 0 )
208+ AddGhostLine ( BlockingChart , refBlocking , "#E57373" ) ;
176209 }
177- else
178- ShowEmpty ( FileIoChart , "I/O ms" ) ;
179210
180- // Comparison overlay — fetch reference period data and render as ghost lines
181- if ( comparisonRange . HasValue )
211+ if ( refMemoryTask . IsCompletedSuccessfully )
212+ AddGhostLine ( MemoryChart , refMemoryTask . Result
213+ . Select ( d => ( d . CollectionTime . Add ( timeShift ) . ToOADate ( ) , ( double ) d . TotalMemoryMb ) ) . ToList ( ) , "#CE93D8" ) ;
214+
215+ if ( refIoTask . IsCompletedSuccessfully )
182216 {
183- var refFrom = comparisonRange . Value . From ;
184- var refTo = comparisonRange . Value . To ;
185- var timeShift = ( fromDate ?? DateTime . UtcNow . AddHours ( - hoursBack ) ) - refFrom ;
186-
187- var refCpuTask = _dataService . GetCpuUtilizationAsync ( 0 , refFrom , refTo ) ;
188- var refWaitTask = _dataService . GetTotalWaitStatsTrendAsync ( 0 , refFrom , refTo ) ;
189- var refBlockingTask = _dataService . GetBlockedSessionTrendAsync ( 0 , refFrom , refTo ) ;
190- var refMemoryTask = _dataService . GetMemoryStatsAsync ( 0 , refFrom , refTo ) ;
191- var refIoTask = _dataService . GetFileIoLatencyTimeSeriesAsync ( false , 0 , refFrom , refTo ) ;
192-
193- try { await Task . WhenAll ( refCpuTask , refWaitTask , refBlockingTask , refMemoryTask , refIoTask ) ; }
194- catch ( Exception ex ) { Debug . WriteLine ( $ "CorrelatedLanes: Comparison fetch failed: { ex . Message } ") ; }
195-
196- if ( refCpuTask . IsCompletedSuccessfully )
197- AddGhostLine ( CpuChart , refCpuTask . Result
198- . Select ( d => ( d . SampleTime . Add ( timeShift ) . ToOADate ( ) , ( double ) d . SqlServerCpuUtilization ) ) . ToList ( ) , "#4FC3F7" ) ;
199-
200- if ( refWaitTask . IsCompletedSuccessfully )
201- AddGhostLine ( WaitStatsChart , refWaitTask . Result
202- . Select ( d => ( d . CollectionTime . Add ( timeShift ) . ToOADate ( ) , ( double ) d . WaitTimeMsPerSecond ) ) . ToList ( ) , "#FFB74D" ) ;
203-
204- if ( refBlockingTask . IsCompletedSuccessfully )
205- {
206- var refBlocking = refBlockingTask . Result
207- . GroupBy ( d => d . CollectionTime )
208- . OrderBy ( g => g . Key )
209- . Select ( g => ( g . Key . Add ( timeShift ) . ToOADate ( ) , ( double ) g . Sum ( x => x . BlockedCount ) ) )
210- . ToList ( ) ;
211- if ( refBlocking . Count > 0 )
212- AddGhostLine ( BlockingChart , refBlocking , "#E57373" ) ;
213- }
214-
215- if ( refMemoryTask . IsCompletedSuccessfully )
216- AddGhostLine ( MemoryChart , refMemoryTask . Result
217- . Select ( d => ( d . CollectionTime . Add ( timeShift ) . ToOADate ( ) , ( double ) d . TotalMemoryMb ) ) . ToList ( ) , "#CE93D8" ) ;
218-
219- if ( refIoTask . IsCompletedSuccessfully )
220- {
221- var refIo = refIoTask . Result
222- . GroupBy ( d => d . CollectionTime )
223- . OrderBy ( g => g . Key )
224- . Select ( g => ( g . Key . Add ( timeShift ) . ToOADate ( ) , ( double ) g . Average ( x => x . ReadLatencyMs ) ) )
225- . ToList ( ) ;
226- AddGhostLine ( FileIoChart , refIo , "#81C784" ) ;
227- }
228-
229- _crosshairManager ? . SetComparisonLabel ( ComparisonLabel ( comparisonRange . Value , fromDate , hoursBack ) ) ;
217+ var refIo = refIoTask . Result
218+ . GroupBy ( d => d . CollectionTime )
219+ . OrderBy ( g => g . Key )
220+ . Select ( g => ( g . Key . Add ( timeShift ) . ToOADate ( ) , ( double ) g . Average ( x => x . ReadLatencyMs ) ) )
221+ . ToList ( ) ;
222+ AddGhostLine ( FileIoChart , refIo , "#81C784" ) ;
230223 }
231224
232- _crosshairManager ? . ReattachVLines ( ) ;
233- SyncXAxes ( hoursBack , fromDate , toDate ) ;
234- }
235- finally
236- {
237- _isRefreshing = false ;
225+ _crosshairManager ? . SetComparisonLabel ( ComparisonLabel ( comparisonRange . Value , fromDate , hoursBack ) ) ;
238226 }
227+
228+ _crosshairManager ? . ReattachVLines ( ) ;
229+ SyncXAxes ( hoursBack , fromDate , toDate ) ;
239230 }
240231
241232 /// <summary>
0 commit comments