-
Notifications
You must be signed in to change notification settings - Fork 761
/
ResourceMonitorService.cs
148 lines (124 loc) · 5.47 KB
/
ResourceMonitorService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring;
/// <summary>
/// The implementation of <see cref="IResourceMonitor"/> that computes average resource utilization over a configured period of time.
/// </summary>
/// <remarks>
/// The class also acts as a hosted singleton, intended to be used to manage the
/// background process of periodically inspecting and monitoring the utilization
/// of an enclosing system.
/// </remarks>
internal sealed class ResourceMonitorService : BackgroundService, IResourceMonitor
{
/// <summary>
/// The data source.
/// </summary>
private readonly ISnapshotProvider _provider;
/// <summary>
/// The publishers to use with the data we are tracking.
/// </summary>
private readonly IResourceUtilizationPublisher[] _publishers;
/// <summary>
/// Logger to be used in this class.
/// </summary>
private readonly ILogger<ResourceMonitorService> _logger;
private readonly TimeProvider _timeProvider;
/// <summary>
/// Circular buffer for storing samples.
/// </summary>
private readonly CircularBuffer<Snapshot> _snapshotsStore;
private readonly TimeSpan _samplingInterval;
private readonly TimeSpan _publishingWindow;
private readonly TimeSpan _collectionWindow;
public ResourceMonitorService(
ISnapshotProvider provider,
ILogger<ResourceMonitorService> logger,
IOptions<ResourceMonitoringOptions> options,
IEnumerable<IResourceUtilizationPublisher> publishers)
: this(provider, logger, options, publishers, TimeProvider.System)
{
}
internal ResourceMonitorService(
ISnapshotProvider provider,
ILogger<ResourceMonitorService> logger,
IOptions<ResourceMonitoringOptions> options,
IEnumerable<IResourceUtilizationPublisher> publishers,
TimeProvider timeProvider)
{
_provider = provider;
_logger = logger;
_timeProvider = timeProvider;
var optionsValue = Throw.IfMemberNull(options, options.Value);
_publishingWindow = optionsValue.PublishingWindow;
_samplingInterval = optionsValue.SamplingInterval;
_collectionWindow = optionsValue.CollectionWindow;
_publishers = publishers.ToArray();
var bufferSize = (int)(_collectionWindow.TotalMilliseconds / _samplingInterval.TotalMilliseconds);
var firstSnapshot = _provider.GetSnapshot();
_snapshotsStore = new CircularBuffer<Snapshot>(bufferSize + 1, firstSnapshot);
}
/// <inheritdoc />
public ResourceUtilization GetUtilization(TimeSpan window)
{
_ = Throw.IfLessThanOrEqual(window.Ticks, 0);
_ = Throw.IfGreaterThan(window.Ticks, _collectionWindow.Ticks);
var samplesToRead = (int)(window.Ticks / _samplingInterval.Ticks) + 1;
(Snapshot first, Snapshot last) t;
lock (_snapshotsStore)
{
t = _snapshotsStore.GetFirstAndLastFromWindow(samplesToRead);
}
return Calculator.CalculateUtilization(t.first, t.last, _provider.Resources);
}
[SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentionally Consume All. Allow no escapes.")]
internal async Task PublishUtilizationAsync(CancellationToken cancellationToken)
{
var u = GetUtilization(_publishingWindow);
foreach (var publisher in _publishers)
{
try
{
await publisher.PublishAsync(u, cancellationToken).ConfigureAwait(false);
}
catch (Exception e)
{
// By Design: Swallow the exception, as they're non-actionable in this code path.
// Prioritize app reliability over error visibility
Log.HandlePublishUtilizationException(_logger, e, publisher.GetType().FullName!);
}
}
}
[SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentionally Consume All. Allow no escapes.")]
[SuppressMessage("Blocker Bug", "S2190:Loops and recursions should not be infinite", Justification = "Terminate when Delay throws an exception on cancellation")]
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
while (true)
{
await _timeProvider.Delay(_samplingInterval, cancellationToken).ConfigureAwait(false);
try
{
var snapshot = _provider.GetSnapshot();
_snapshotsStore.Add(snapshot);
Log.SnapshotReceived(_logger, snapshot.TotalTimeSinceStart, snapshot.KernelTimeSinceStart, snapshot.UserTimeSinceStart, snapshot.MemoryUsageInBytes);
}
catch (Exception e)
{
// By Design: Swallow the exception, as they're non-actionable in this code path.
// Prioritize app reliability over error visibility
Log.HandledGatherStatisticsException(_logger, e);
}
await PublishUtilizationAsync(cancellationToken).ConfigureAwait(false);
}
}
}