< Summary

Information
Class: AmbientServices.RatioStatistic
Assembly: AmbientServices
File(s): /home/runner/work/AmbientServices/AmbientServices/AmbientServices/DefaultImplementation/BasicAmbientStatistics.cs
Tag: 332_35464845198
Line coverage
100%
Covered lines: 18
Uncovered lines: 0
Coverable lines: 18
Total lines: 330
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_NumeratorStatisticId()100%11100%
Dispose()100%11100%

File(s)

/home/runner/work/AmbientServices/AmbientServices/AmbientServices/DefaultImplementation/BasicAmbientStatistics.cs

#LineLine coverage
 1using AmbientServices.Utilities;
 2using System;
 3using System.Collections.Concurrent;
 4using System.Collections.Generic;
 5using System.Diagnostics;
 6
 7namespace AmbientServices;
 8
 9/// <summary>
 10/// A basic default implementation of <see cref="IAmbientStatistics"/> that keeps all statistics in memory.
 11/// </summary>
 12/// <remarks>
 13/// <pitch>The zero-configuration, in-process statistics registry used unless overridden.  Registry operations are lock-
 14/// <pledge><see cref="IAmbientStatistics"/></pledge>
 15/// <plan>
 16/// Two <see cref="ConcurrentDictionary{TKey,TValue}"/>s hold statistics and ratio-statistic descriptors keyed by ID; ge
 17/// The built-in execution-time statistic is a read-only, computed statistic (elapsed <see cref="AmbientClock.Ticks"/> s
 18/// </plan>
 19/// </remarks>
 20[DefaultAmbientService]
 21internal class BasicAmbientStatistics : IAmbientStatistics
 22{
 23    /// <summary>
 24    /// Gets the ID of the built-in execution time statistic, especially for ratio statistics.
 25    /// </summary>
 26    public const string ExecutionTimeStatisticId = "ExecutionTime";
 27
 28    private readonly ProcessExecutionTimeStatistic _executionTime;
 29    private readonly ConcurrentDictionary<string, IAmbientStatisticReader> _statistics = new();
 30    private readonly ConcurrentDictionary<string, IAmbientRatioStatistic> _ratioStatistics = new();
 31
 32    public BasicAmbientStatistics()
 33    {
 34        _executionTime = new ProcessExecutionTimeStatistic(this);
 35        _statistics.GetOrAdd(_executionTime.Id, _executionTime);
 36    }
 37
 38    public IAmbientStatisticReader ExecutionTime => _executionTime;
 39
 40    public IDictionary<string, IAmbientStatisticReader> Statistics => _statistics;
 41
 42    public IDictionary<string, IAmbientRatioStatistic> RatioStatistics => _ratioStatistics;
 43
 44    public IAmbientStatisticReader? ReadStatistic(string id)
 45    {
 46        if (_statistics.TryGetValue(id, out IAmbientStatisticReader? statistic)) return statistic;
 47        return null;
 48    }
 49
 50    public IAmbientStatistic GetOrAddStatistic(AmbientStatisticType type, string id, string name, string description, bo
 51        , long initialRawValue = 0, long? minimumExpectedRawValue = null, long? maximumExpectedRawValue = null
 52        , string? units = null, double fixedFloatingPointAdjustment = 1.0
 53        , AggregationTypes temporalAggregationTypes = AggregationTypes.None
 54        , AggregationTypes spatialAggregationTypes = AggregationTypes.None
 55        , AggregationTypes preferredTemporalAggregationType = AggregationTypes.None
 56        , AggregationTypes preferredSpatialAggregationType = AggregationTypes.None
 57        , MissingSampleHandling missingSampleHandling = MissingSampleHandling.LinearEstimation
 58        )
 59    {
 60        IAmbientStatistic? statistic = new Statistic(this, () => _statistics.TryRemove(id, out _), type, id, name, descr
 61        if (replaceIfAlreadyExists)
 62        {
 63            _statistics.AddOrUpdate(id, statistic, (k, v) => statistic);
 64        }
 65        else
 66        {
 67            statistic = _statistics.GetOrAdd(id, statistic) as IAmbientStatistic;   // this *could* return something tha
 68            if (statistic == null) throw new InvalidOperationException("The specified statistic identifier is already in
 69        }
 70        return statistic;
 71    }
 72    public IAmbientStatistic GetOrAddTimeBasedStatistic(AmbientStatisticType type, string id, string name, string descri
 73        , long initialRawValue = 0, long? minimumExpectedRawValue = null, long? maximumExpectedRawValue = null
 74        , AggregationTypes temporalAggregationTypes = AggregationTypes.None
 75        , AggregationTypes spatialAggregationTypes = AggregationTypes.None
 76        , AggregationTypes preferredTemporalAggregationType = AggregationTypes.None
 77        , AggregationTypes preferredSpatialAggregationType = AggregationTypes.None
 78        , MissingSampleHandling missingSampleHandling = MissingSampleHandling.LinearEstimation
 79        )
 80    {
 81        return GetOrAddStatistic(type, id, name, description, replaceIfAlreadyExists, initialRawValue, minimumExpectedRa
 82            "seconds", Stopwatch.Frequency,
 83            temporalAggregationTypes, spatialAggregationTypes, preferredTemporalAggregationType, preferredSpatialAggrega
 84    }
 85    public bool RemoveStatistic(string id)
 86    {
 87        if (id == _executionTime.Id) return false;
 88        return _statistics.TryRemove(id, out _);
 89    }
 90    public IAmbientRatioStatistic GetOrAddRatioStatistic(string id, string name, string description, bool replaceIfAlrea
 91        , string? numeratorStatistic = null, bool numeratorDelta = true
 92        , string? denominatorStatistic = null, bool denominatorDelta = true
 93        )
 94    {
 95        // no units specified?
 96        if (units == null)
 97        {
 98            // is there no denominator?
 99            if ((denominatorStatistic == null) && numeratorStatistic != null && _statistics.TryGetValue(numeratorStatist
 100            {
 101                // just use the numerator units
 102                units = numeratorStat.AdjustedUnits;
 103            }
 104            // is there both a numerator and a denominator?
 105            else if (denominatorStatistic != null && numeratorStatistic != null && _statistics.TryGetValue(numeratorStat
 106            {
 107                // use "numerator units / denominator units"
 108                units = $"{numeratorStat.AdjustedUnits}/{denominatorStat.AdjustedUnits}";
 109            }
 110        }
 111        IAmbientRatioStatistic? statistic = new RatioStatistic(this, () => _ratioStatistics.TryRemove(id, out _), id, na
 112        if (replaceIfAlreadyExists)
 113        {
 114            _ratioStatistics.AddOrUpdate(id, statistic, (k, v) => statistic);
 115        }
 116        else
 117        {
 118            statistic = _ratioStatistics.GetOrAdd(id, statistic);
 119        }
 120        return statistic;
 121    }
 122    public bool RemoveRatioStatistic(string id)
 123    {
 124        return _ratioStatistics.TryRemove(id, out _);
 125    }
 126}
 127
 128/// <summary>
 129/// The standard read-write statistic realization used by <see cref="BasicAmbientStatistics"/>.
 130/// </summary>
 131/// <remarks>
 132/// <pitch>A single interlocked 64-bit sample plus immutable metadata — the cheapest possible statistic.</pitch>
 133/// <pledge><see cref="IAmbientStatistic"/></pledge>
 134/// <plan>The sample is one <see cref="long"/> field updated with <see cref="System.Threading.Interlocked"/> operations;
 135/// </remarks>
 136internal class Statistic : IAmbientStatistic
 137{
 138    private readonly Action _removeRegistration;
 139    private long _currentValue;    // interlocked
 140
 141    public Statistic(IAmbientStatistics statisticsSet, Action removeRegistration, AmbientStatisticType type, string id, 
 142        , long initialRawValue = 0, long? expectedMinRawValue = null, long? expectedMaxRawValue = null
 143        , string? units = null, double fixedFloatingPointAdjustment = 1.0
 144        , AggregationTypes temporalAggregationTypes = AggregationTypes.None
 145        , AggregationTypes spatialAggregationTypes = AggregationTypes.None
 146        , AggregationTypes preferredTemporalAggregationType = AggregationTypes.None
 147        , AggregationTypes preferredSpatialAggregationType = AggregationTypes.None
 148        , MissingSampleHandling missingSampleHandling = MissingSampleHandling.LinearEstimation
 149        )
 150    {
 151        StatisticsSet = statisticsSet;
 152        _removeRegistration = removeRegistration;
 153        StatisticType = type;
 154        Id = id;
 155        Name = name;
 156        Description = description;
 157        AdjustedUnits = units;
 158        _currentValue = initialRawValue;
 159        ExpectedMinimumRawValue = expectedMinRawValue;
 160        ExpectedMaximumRawValue = expectedMaxRawValue;
 161        FixedFloatingPointAdjustment = (fixedFloatingPointAdjustment == 0) ? 1.0 : fixedFloatingPointAdjustment;    // d
 162        TemporalAggregationTypes = temporalAggregationTypes == AggregationTypes.None ? IAmbientStatisticsExtensions.Defa
 163        SpatialAggregationTypes = spatialAggregationTypes == AggregationTypes.None ? IAmbientStatisticsExtensions.Defaul
 164        PreferredTemporalAggregationType = preferredTemporalAggregationType == AggregationTypes.None ? IAmbientStatistic
 165        PreferredSpatialAggregationType = preferredSpatialAggregationType == AggregationTypes.None ? IAmbientStatisticsE
 166        MissingSampleHandling = missingSampleHandling;
 167    }
 168
 169    public IAmbientStatistics StatisticsSet { get; }
 170
 171    public AmbientStatisticType StatisticType { get; }
 172
 173    public string Id { get; }
 174
 175    public string Name { get; }
 176
 177    public string Description { get; }
 178
 179    public string? AdjustedUnits { get; }
 180
 181    public long CurrentRawValue => _currentValue;
 182
 183    public long? ExpectedMinimumRawValue { get; private set; }
 184
 185    public long? ExpectedMaximumRawValue { get; private set; }
 186
 187    public double FixedFloatingPointAdjustment { get; private set;}
 188
 189    public AggregationTypes TemporalAggregationTypes { get; private set; }
 190
 191    public AggregationTypes SpatialAggregationTypes { get; private set; }
 192
 193    public AggregationTypes PreferredTemporalAggregationType { get; private set; }
 194
 195    public AggregationTypes PreferredSpatialAggregationType { get; private set; }
 196
 197    public MissingSampleHandling MissingSampleHandling { get; private set; }
 198
 199    public long IncrementRaw()
 200    {
 201        return System.Threading.Interlocked.Increment(ref _currentValue);
 202    }
 203    public long DecrementRaw()
 204    {
 205        return System.Threading.Interlocked.Decrement(ref _currentValue);
 206    }
 207    public long AddRaw(long addend)
 208    {
 209        return System.Threading.Interlocked.Add(ref _currentValue, addend);
 210    }
 211    public void SetRawValue(long newValue)
 212    {
 213        System.Threading.Interlocked.Exchange(ref _currentValue, newValue);
 214    }
 215    public long SetRawMin(long newPossibleMinValue)
 216    {
 217        return InterlockedUtilities.TryOptimisticMin(ref _currentValue, newPossibleMinValue);
 218    }
 219    public long SetRawMax(long newPossibleMaxValue)
 220    {
 221        return InterlockedUtilities.TryOptimisticMax(ref _currentValue, newPossibleMaxValue);
 222    }
 223    public void Dispose()
 224    {
 225        _removeRegistration();
 226    }
 227}
 228
 229/// <summary>
 230/// The built-in read-only execution-time statistic.
 231/// </summary>
 232/// <remarks>
 233/// <pitch>The always-available "how long has this been running" statistic, and the standard denominator for per-second 
 234/// <pledge><see cref="IAmbientStatisticReader"/></pledge>
 235/// <plan>Stores only the <see cref="AmbientClock.Ticks"/> value at construction and computes the current raw value as e
 236/// </remarks>
 237internal class ProcessExecutionTimeStatistic : IAmbientStatisticReader
 238{
 239    private readonly long _startTime;
 240
 241    public ProcessExecutionTimeStatistic(IAmbientStatistics statisticsSet)
 242    {
 243        StatisticsSet = statisticsSet;
 244        _startTime = AmbientClock.Ticks;
 245    }
 246
 247    public IAmbientStatistics StatisticsSet { get; }
 248
 249    public AmbientStatisticType StatisticType => AmbientStatisticType.Cumulative;
 250
 251    public string Id => "ExecutionTime";
 252
 253    public string Name => "Execution Time";
 254
 255    public string Description => "The number of seconds elapsed since the statistics system started.";
 256
 257    public string? AdjustedUnits => "seconds";
 258
 259    public long CurrentRawValue => AmbientClock.Ticks - _startTime;
 260
 261    public long? ExpectedMinimumRawValue => 0;
 262
 263    public long? ExpectedMaximumRawValue => null;
 264
 265    public double FixedFloatingPointAdjustment => Stopwatch.Frequency;
 266
 267    public AggregationTypes TemporalAggregationTypes => AggregationTypes.MostRecent | AggregationTypes.Min | Aggregation
 268
 269    public AggregationTypes SpatialAggregationTypes => AggregationTypes.Average | AggregationTypes.Min | AggregationType
 270
 271    public AggregationTypes PreferredTemporalAggregationType => AggregationTypes.MostRecent;
 272
 273    public AggregationTypes PreferredSpatialAggregationType => AggregationTypes.Average;
 274
 275    public MissingSampleHandling MissingSampleHandling => MissingSampleHandling.LinearEstimation;
 276}
 277
 278/// <summary>
 279/// The standard ratio-statistic descriptor used by <see cref="BasicAmbientStatistics"/>.
 280/// </summary>
 281/// <remarks>
 282/// <pitch>A pure, immutable ratio descriptor — it holds the numerator/denominator IDs and delta flags and nothing else.
 283/// <pledge><see cref="IAmbientRatioStatistic"/></pledge>
 284/// <plan>Immutable properties set at construction; disposal just invokes the removal callback captured from the registr
 285/// </remarks>
 286internal sealed class RatioStatistic : IAmbientRatioStatistic
 287{
 288    private readonly Action _removeRegistration;
 289    private readonly string? _numeratorStatistic;
 290
 2291    public RatioStatistic(IAmbientStatistics statisticsSet, Action removeRegistration, string id, string name, string de
 2292        , string? numeratorStatistic = null, bool numeratorDelta = true
 2293        , string? denominatorStatistic = null, bool denominatorDelta = true
 2294        )
 295    {
 2296        StatisticsSet = statisticsSet;
 2297        _removeRegistration = removeRegistration;
 2298        Id = id;
 2299        Name = name;
 2300        Description = description;
 2301        AdjustedUnits = units;
 2302        _numeratorStatistic = numeratorStatistic;
 2303        NumeratorDelta = numeratorDelta;
 2304        DenominatorStatisticId = denominatorStatistic;
 2305        DenominatorDelta = denominatorDelta;
 2306    }
 307
 308    public IAmbientStatistics StatisticsSet { get; }
 309
 310    public string Id { get; }
 311
 312    public string Name { get; }
 313
 314    public string Description { get; }
 315
 316    public string? AdjustedUnits { get; }
 317
 2318    public string? NumeratorStatisticId => _numeratorStatistic;
 319
 320    public bool NumeratorDelta { get; }
 321
 322    public string? DenominatorStatisticId { get; }
 323
 324    public bool DenominatorDelta { get; }
 325
 326    public void Dispose()
 327    {
 2328        _removeRegistration();
 2329    }
 330}