< Summary

Information
Class: AmbientServices.PressureMonitor
Assembly: AmbientServices
File(s): /home/runner/work/AmbientServices/AmbientServices/AmbientServices/Services/PressureMonitor.cs
Tag: 332_35464845198
Line coverage
100%
Covered lines: 47
Uncovered lines: 0
Coverable lines: 47
Total lines: 244
Line coverage: 100%
Branch coverage
100%
Covered branches: 32
Total branches: 32
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%66100%
get_InternalPressure()100%11100%
get_ExternalPressure()100%11100%
get_OverallPressure()100%11100%
.ctor(...)100%11100%
OnTimerCallback(...)100%1010100%
Max(...)100%11100%
Max(...)100%66100%
Dispose(...)100%1010100%
Dispose()100%11100%

File(s)

/home/runner/work/AmbientServices/AmbientServices/AmbientServices/Services/PressureMonitor.cs

#LineLine coverage
 1using System;
 2using System.Collections.Concurrent;
 3using System.Collections.Generic;
 4using System.Linq;
 5using System.Threading;
 6
 7namespace AmbientServices;
 8
 9#pragma warning disable CA1510
 10/// <summary>
 11/// An interface that abstracts an external pressure point.
 12/// </summary>
 13/// <remarks>
 14/// <pitch>Implement this to contribute one measured source of load — CPU, thread pool, memory, a database, a throttled 
 15/// <pledge>
 16/// <see cref="Pressure"/> returns the current load as a proportion between 0.0 (idle) and 1.0 (saturated), comparable a
 17/// <see cref="Name"/> is stable and unique among registered pressure points — it is the registration key.
 18/// </pledge>
 19/// </remarks>
 20public interface IPressurePoint
 21{
 22    /// <summary>
 23    /// Gets the name of the pressure point, used for the performance counter instance and status reports.
 24    /// </summary>
 25    string Name { get; }
 26    /// <summary>
 27    /// Gets the pressure value (between 0.0 and 1.0).
 28    /// </summary>
 29    float Pressure { get; }
 30}
 31/// <summary>
 32/// A static class that manages registrations of <see cref="IPressurePoint"/> instances for internal pressures.
 33/// </summary>
 34/// <remarks>
 35/// <pitch>The process-wide registry for pressure points measuring this process's own resources (CPU, threads, memory), 
 36/// <pledge>Registration is first-in-wins by <see cref="IPressurePoint.Name"/> and permanent — there is no deregistratio
 37/// <plan>A static <see cref="ConcurrentDictionary{TKey,TValue}"/> keyed by pressure point name.</plan>
 38/// </remarks>
 39public static class InternalPressurePoints
 40{
 41    private static readonly ConcurrentDictionary<string, IPressurePoint> _PressurePoints = new();
 42
 43    /// <summary>
 44    /// Gets an enumeration of the pressure points
 45    /// </summary>
 46    public static IEnumerable<IPressurePoint> List => _PressurePoints.Select(p => p.Value);
 47
 48    /// <summary>
 49    /// Registers the specified <see cref="IPressurePoint"/> for inclusion in <see cref="PressureMonitor.ExternalPressur
 50    /// </summary>
 51    /// <param name="pp">The <see cref="IPressurePoint"/> to register.</param>
 52    /// <returns>true if the pressure point was successfully registered, false if there was already a pressure point wit
 53    public static bool Register(IPressurePoint pp)
 54    {
 55        if (pp == null) throw new ArgumentNullException(nameof(pp));
 56        return _PressurePoints.TryAdd(pp.Name, pp);
 57    }
 58}
 59/// <summary>
 60/// A static class that manages registrations of <see cref="IPressurePoint"/> instances for external pressures.
 61/// </summary>
 62/// <remarks>
 63/// <pitch>The process-wide registry for pressure points measuring systems outside this process (databases, throttled de
 64/// <pledge>Registration is first-in-wins by <see cref="IPressurePoint.Name"/> and permanent — there is no deregistratio
 65/// <plan>A static <see cref="ConcurrentDictionary{TKey,TValue}"/> keyed by pressure point name.</plan>
 66/// </remarks>
 67public static class ExternalPressurePoints
 68{
 69    private static readonly ConcurrentDictionary<string, IPressurePoint> _PressurePoints = new();
 70
 71    /// <summary>
 72    /// Gets an enumeration of the pressure points
 73    /// </summary>
 74    public static IEnumerable<IPressurePoint> List => _PressurePoints.Select(p => p.Value);
 75
 76    /// <summary>
 77    /// Registers the specified <see cref="IPressurePoint"/> for inclusion in <see cref="PressureMonitor.ExternalPressur
 78    /// </summary>
 79    /// <param name="pp">The <see cref="IPressurePoint"/> to register.</param>
 80    /// <returns>true if the pressure point was successfully registered, false if there was already a pressure point wit
 81    public static bool Register(IPressurePoint pp)
 82    {
 83        if (pp == null) throw new ArgumentNullException(nameof(pp));
 84        return _PressurePoints.TryAdd(pp.Name, pp);
 85    }
 86}
 87/// <summary>
 88/// A disposable class that monitors and reports on system pressure so that background processing can be adjusted accord
 89/// </summary>
 90/// <remarks>
 91/// <pitch>One cheap number answering "how loaded is this system right now?" so background work can throttle itself befo
 92/// <pledge>
 93/// <see cref="InternalPressure"/>, <see cref="ExternalPressure"/>, and <see cref="OverallPressure"/> each report the hi
 94/// Reads are thread-safe from any context; disposal stops refreshing.
 95/// </pledge>
 96/// <plan>
 97/// An <see cref="AmbientCallbackTimer"/> (default one second) polls every <see cref="IPressurePoint"/> in <see cref="In
 98/// Max-of-pressures (rather than sum or average) is the deliberate trade-off: one saturated resource is enough to warra
 99/// </plan>
 100/// <priority>
 101/// 1. Free reads over current readings: pressure is refreshed on a timer rather than computed on read, so a read is a f
 102/// 2. Max-of-pressures over sum or average: one saturated resource is reason enough to throttle, and taking the maximum
 103/// </priority>
 104/// </remarks>
 105public class PressureMonitor : IDisposable
 106{
 2107    private static readonly AmbientService<IAmbientStatistics> AmbientStatistics = Ambient.GetService<IAmbientStatistics
 108    private const int PressureRecalculateFrequencyMilliseconds = 1_000;
 109    private const double FixedFloatingPointAdjustment = 100_000_000;
 110    private const long MinRawValue = 0;
 111    private const long MaxRawValue = (long)(1.00f * FixedFloatingPointAdjustment);
 112    private const long NeutralRawValue = (long)(0.89f * FixedFloatingPointAdjustment);
 113
 114    /// <summary>
 115    /// Gets the default system pressure monitor.
 116    /// </summary>
 2117    public static PressureMonitor Default { get; } = new();
 118
 119    private readonly AmbientCallbackTimer _timer;
 2120    private readonly IAmbientStatistic? _internalPressureStat = AmbientStatistics.Local?.GetOrAddStatistic(AmbientStatis
 2121    private readonly IAmbientStatistic? _externalPressureStat = AmbientStatistics.Local?.GetOrAddStatistic(AmbientStatis
 2122    private readonly IAmbientStatistic? _overallPressureStat = AmbientStatistics.Local?.GetOrAddStatistic(AmbientStatist
 123
 124    private float _internalPressure;
 125    private float _externalPressure;
 126    private float _overallPressure;
 127    private bool _disposed;
 128
 129    /// <summary>
 130    /// Gets the (overall) internal system pressure (the highest of the individual internal pressures).
 131    /// </summary>
 2132    public float InternalPressure => _internalPressure;
 133    /// <summary>
 134    /// Gets the (overall) external system pressure (the highest of the individual external pressures).
 135    /// </summary>
 2136    public float ExternalPressure => _externalPressure;
 137    /// <summary>
 138    /// Gets the overall system pressure (the highest of the individual pressures, including external pressures like dat
 139    /// </summary>
 2140    public float OverallPressure => _overallPressure;
 141
 142    /// <summary>
 143    /// Constructs a new pressure monitor with the specified frequency.
 144    /// </summary>
 145    /// <param name="frequency">The frequency to recompute pressure.</param>
 2146    public PressureMonitor(TimeSpan frequency)
 147    {
 2148        _timer = new(OnTimerCallback, null, frequency, frequency);
 2149    }
 150    /// <summary>
 151    /// Constructs a new pressure monitor with the specified frequency.
 152    /// </summary>
 153    /// <param name="frequencyMilliseconds">The frequency to recompute pressure, in milliseconds.</param>
 2154    public PressureMonitor(int? frequencyMilliseconds = null) : this(TimeSpan.FromMilliseconds(frequencyMilliseconds ?? 
 155    {
 2156    }
 157
 158    private void OnTimerCallback(object? state)
 159    {
 2160        float internalPressure = 0;
 2161        foreach (IPressurePoint pp in InternalPressurePoints.List)
 162        {
 2163            float pressure = pp.Pressure;
 2164            internalPressure = Max(internalPressure, pressure);
 165        }
 2166        Interlocked.Exchange(ref _internalPressure, internalPressure);
 2167        _internalPressureStat?.SetValue(internalPressure);
 168
 2169        float externalPressure = 0;
 2170        foreach (IPressurePoint pp in ExternalPressurePoints.List)
 171        {
 2172            float pressure = pp.Pressure;
 2173            externalPressure = Max(externalPressure, pressure);
 174        }
 2175        Interlocked.Exchange(ref _externalPressure, externalPressure);
 2176        _externalPressureStat?.SetValue(externalPressure);
 177
 2178        float overallPressure = Math.Min(1.0f, Max(0.0f, internalPressure, externalPressure));
 2179        Interlocked.Exchange(ref _overallPressure, overallPressure);
 2180        _overallPressureStat?.SetValue(overallPressure);
 2181    }
 182    /// <summary>
 183    /// Gets the maximum of all the specified values.
 184    /// </summary>
 185    /// <param name="items">A variable-length array of floating point numbers.</param>
 186    /// <returns>The highest of all the specified floating point numbers.</returns>
 187    public static float Max(params float[] items)
 188    {
 2189        return Max((IEnumerable<float>)items);
 190    }
 191    /// <summary>
 192    /// Gets the maximum of all the specified values.
 193    /// </summary>
 194    /// <param name="items">An enumeration of floating point numbers.</param>
 195    /// <returns>The highest of all the specified floating point numbers.</returns>
 196    public static float Max(IEnumerable<float> items)
 197    {
 2198        if (items == null) throw new ArgumentNullException(nameof(items));
 2199        float max = float.MinValue;
 2200        foreach (float f in items)
 201        {
 2202            if (f > max) max = f;
 203        }
 2204        return max;
 205    }
 206    /// <summary>
 207    /// Disposes or finalizes the instance.
 208    /// </summary>
 209    /// <param name="disposing">Whether or not we are disposing.</param>
 210    protected virtual void Dispose(bool disposing)
 211    {
 2212        if (!_disposed)
 213        {
 2214            if (disposing)
 215            {
 2216                _timer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
 2217                _timer.Dispose();
 2218                _internalPressureStat?.Dispose();
 2219                _externalPressureStat?.Dispose();
 2220                _overallPressureStat?.Dispose();
 221            }
 222            // TODO: free unmanaged resources (unmanaged objects) and override finalizer
 223            // TODO: set large fields to null
 2224            _disposed = true;
 225        }
 2226    }
 227
 228    // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
 229    // ~PressureMonitor()
 230    // {
 231    //     // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
 232    //     Dispose(disposing: false);
 233    // }
 234
 235    /// <summary>
 236    /// Disposes of the instance.
 237    /// </summary>
 238    public void Dispose()
 239    {
 240        // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
 2241        Dispose(disposing: true);
 2242        GC.SuppressFinalize(this);
 2243    }
 244}