11
11
12
12
namespace System . Diagnostics . Metrics
13
13
{
14
- [ UnsupportedOSPlatform ( "browser" ) ]
15
14
[ SecuritySafeCritical ]
16
15
internal sealed class AggregationManager
17
16
{
@@ -29,6 +28,9 @@ internal sealed class AggregationManager
29
28
private readonly ConcurrentDictionary < Instrument , InstrumentState > _instrumentStates = new ( ) ;
30
29
private readonly CancellationTokenSource _cts = new ( ) ;
31
30
private Thread ? _collectThread ;
31
+ #if OS_ISBROWSER_SUPPORT
32
+ private Timer ? _pollingTimer ;
33
+ #endif
32
34
private readonly MeterListener _listener ;
33
35
private int _currentTimeSeries ;
34
36
private int _currentHistograms ;
@@ -43,6 +45,9 @@ internal sealed class AggregationManager
43
45
private readonly Action _timeSeriesLimitReached ;
44
46
private readonly Action _histogramLimitReached ;
45
47
private readonly Action < Exception > _observableInstrumentCallbackError ;
48
+ private DateTime _startTime ;
49
+ private DateTime _intervalStartTime ;
50
+ private DateTime _nextIntervalStartTime ;
46
51
47
52
public AggregationManager (
48
53
int maxTimeSeries ,
@@ -160,15 +165,28 @@ public void Start()
160
165
Debug . Assert ( _collectThread == null && ! _cts . IsCancellationRequested ) ;
161
166
Debug . Assert ( CollectionPeriod . TotalSeconds >= MinCollectionTimeSecs ) ;
162
167
163
- // This explicitly uses a Thread and not a Task so that metrics still work
164
- // even when an app is experiencing thread-pool starvation. Although we
165
- // can't make in-proc metrics robust to everything, this is a common enough
166
- // problem in .NET apps that it feels worthwhile to take the precaution.
167
- _collectThread = new Thread ( ( ) => CollectWorker ( _cts . Token ) ) ;
168
- _collectThread . IsBackground = true ;
169
- _collectThread . Name = "MetricsEventSource CollectWorker" ;
170
- _collectThread . Start ( ) ;
168
+ _intervalStartTime = _nextIntervalStartTime = _startTime = DateTime . UtcNow ;
169
+ #if OS_ISBROWSER_SUPPORT
170
+ if ( OperatingSystem . IsBrowser ( ) )
171
+ {
172
+ TimeSpan delayTime = CalculateDelayTime ( CollectionPeriod . TotalSeconds ) ;
173
+ _pollingTimer = new Timer ( CollectOnTimer , null , ( int ) delayTime . TotalMilliseconds , 0 ) ;
174
+ }
175
+ else
176
+ #endif
177
+ {
178
+ // This explicitly uses a Thread and not a Task so that metrics still work
179
+ // even when an app is experiencing thread-pool starvation. Although we
180
+ // can't make in-proc metrics robust to everything, this is a common enough
181
+ // problem in .NET apps that it feels worthwhile to take the precaution.
182
+ _collectThread = new Thread ( CollectWorker ) ;
183
+ _collectThread . IsBackground = true ;
184
+ _collectThread . Name = "MetricsEventSource CollectWorker" ;
185
+ #pragma warning disable CA1416 // 'Thread.Start' is unsupported on: 'browser', there the actual implementation is in AggregationManager.Wasm.cs
186
+ _collectThread . Start ( ) ;
187
+ #pragma warning restore CA1416
171
188
189
+ }
172
190
_listener . Start ( ) ;
173
191
_initialInstrumentEnumerationComplete ( ) ;
174
192
}
@@ -187,57 +205,64 @@ public void Update()
187
205
_initialInstrumentEnumerationComplete ( ) ;
188
206
}
189
207
190
- private void CollectWorker ( CancellationToken cancelToken )
208
+ private TimeSpan CalculateDelayTime ( double collectionIntervalSecs )
209
+ {
210
+ _intervalStartTime = _nextIntervalStartTime ;
211
+
212
+ // intervals end at startTime + X*collectionIntervalSecs. Under normal
213
+ // circumstance X increases by 1 each interval, but if the time it
214
+ // takes to do collection is very large then we might need to skip
215
+ // ahead multiple intervals to catch back up.
216
+ //
217
+ DateTime now = DateTime . UtcNow ;
218
+ double secsSinceStart = ( now - _startTime ) . TotalSeconds ;
219
+ double alignUpSecsSinceStart = Math . Ceiling ( secsSinceStart / collectionIntervalSecs ) *
220
+ collectionIntervalSecs ;
221
+ _nextIntervalStartTime = _startTime . AddSeconds ( alignUpSecsSinceStart ) ;
222
+
223
+ // The delay timer precision isn't exact. We might have a situation
224
+ // where in the previous loop iterations intervalStartTime=20.00,
225
+ // nextIntervalStartTime=21.00, the timer was supposed to delay for 1s but
226
+ // it exited early so we looped around and DateTime.Now=20.99.
227
+ // Aligning up from DateTime.Now would give us 21.00 again so we also need to skip
228
+ // forward one time interval
229
+ DateTime minNextInterval = _intervalStartTime . AddSeconds ( collectionIntervalSecs ) ;
230
+ if ( _nextIntervalStartTime <= minNextInterval )
231
+ {
232
+ _nextIntervalStartTime = minNextInterval ;
233
+ }
234
+ return _nextIntervalStartTime - now ;
235
+ }
236
+
237
+ private void CollectWorker ( )
191
238
{
192
239
try
193
240
{
194
241
double collectionIntervalSecs = - 1 ;
242
+ CancellationToken cancelToken ;
195
243
lock ( this )
196
244
{
197
245
collectionIntervalSecs = CollectionPeriod . TotalSeconds ;
246
+ cancelToken = _cts . Token ;
198
247
}
199
248
Debug . Assert ( collectionIntervalSecs >= MinCollectionTimeSecs ) ;
200
249
201
250
DateTime startTime = DateTime . UtcNow ;
202
251
DateTime intervalStartTime = startTime ;
203
- while ( ! cancelToken . IsCancellationRequested )
252
+ while ( ! _cts . Token . IsCancellationRequested )
204
253
{
205
- // intervals end at startTime + X*collectionIntervalSecs. Under normal
206
- // circumstance X increases by 1 each interval, but if the time it
207
- // takes to do collection is very large then we might need to skip
208
- // ahead multiple intervals to catch back up.
209
- //
210
- DateTime now = DateTime . UtcNow ;
211
- double secsSinceStart = ( now - startTime ) . TotalSeconds ;
212
- double alignUpSecsSinceStart = Math . Ceiling ( secsSinceStart / collectionIntervalSecs ) *
213
- collectionIntervalSecs ;
214
- DateTime nextIntervalStartTime = startTime . AddSeconds ( alignUpSecsSinceStart ) ;
215
-
216
- // The delay timer precision isn't exact. We might have a situation
217
- // where in the previous loop iterations intervalStartTime=20.00,
218
- // nextIntervalStartTime=21.00, the timer was supposed to delay for 1s but
219
- // it exited early so we looped around and DateTime.Now=20.99.
220
- // Aligning up from DateTime.Now would give us 21.00 again so we also need to skip
221
- // forward one time interval
222
- DateTime minNextInterval = intervalStartTime . AddSeconds ( collectionIntervalSecs ) ;
223
- if ( nextIntervalStartTime <= minNextInterval )
224
- {
225
- nextIntervalStartTime = minNextInterval ;
226
- }
227
-
228
254
// pause until the interval is complete
229
- TimeSpan delayTime = nextIntervalStartTime - now ;
255
+ TimeSpan delayTime = CalculateDelayTime ( collectionIntervalSecs ) ;
230
256
if ( cancelToken . WaitHandle . WaitOne ( delayTime ) )
231
257
{
232
258
// don't do collection if timer may not have run to completion
233
259
break ;
234
260
}
235
261
236
262
// collect statistics for the completed interval
237
- _beginCollection ( intervalStartTime , nextIntervalStartTime ) ;
263
+ _beginCollection ( _intervalStartTime , _nextIntervalStartTime ) ;
238
264
Collect ( ) ;
239
- _endCollection ( intervalStartTime , nextIntervalStartTime ) ;
240
- intervalStartTime = nextIntervalStartTime ;
265
+ _endCollection ( _intervalStartTime , _nextIntervalStartTime ) ;
241
266
}
242
267
}
243
268
catch ( Exception e )
@@ -246,12 +271,49 @@ private void CollectWorker(CancellationToken cancelToken)
246
271
}
247
272
}
248
273
274
+ #if OS_ISBROWSER_SUPPORT
275
+ private void CollectOnTimer ( object ? _ )
276
+ {
277
+ try
278
+ {
279
+ // this is single-threaded so we don't need to lock
280
+ CancellationToken cancelToken = _cts . Token ;
281
+ double collectionIntervalSecs = CollectionPeriod . TotalSeconds ;
282
+
283
+ if ( cancelToken . IsCancellationRequested )
284
+ {
285
+ return ;
286
+ }
287
+
288
+ // collect statistics for the completed interval
289
+ _beginCollection ( _intervalStartTime , _nextIntervalStartTime ) ;
290
+ Collect ( ) ;
291
+ _endCollection ( _intervalStartTime , _nextIntervalStartTime ) ;
292
+
293
+ TimeSpan delayTime = CalculateDelayTime ( collectionIntervalSecs ) ;
294
+ // schedule the next collection
295
+ _pollingTimer ! . Change ( ( int ) delayTime . TotalMilliseconds , 0 ) ;
296
+ }
297
+ catch ( Exception e )
298
+ {
299
+ _collectionError ( e ) ;
300
+ }
301
+ }
302
+ #endif
303
+
249
304
public void Dispose ( )
250
305
{
251
306
_cts . Cancel ( ) ;
252
- if ( _collectThread != null )
307
+ #if OS_ISBROWSER_SUPPORT
308
+ if ( OperatingSystem . IsBrowser ( ) )
309
+ {
310
+ _pollingTimer ? . Dispose ( ) ;
311
+ _pollingTimer = null ;
312
+ }
313
+ else
314
+ #endif
253
315
{
254
- _collectThread . Join ( ) ;
316
+ _collectThread ? . Join ( ) ;
255
317
_collectThread = null ;
256
318
}
257
319
_listener . Dispose ( ) ;
0 commit comments