< Summary

Information
Class: AmbientServices.StatusPropertyThresholds
Assembly: AmbientServices
File(s): /home/runner/work/AmbientServices/AmbientServices/AmbientServices/Status/StatusPropertyThresholds.cs
Tag: 332_35464845198
Line coverage
100%
Covered lines: 87
Uncovered lines: 0
Coverable lines: 87
Total lines: 351
Line coverage: 100%
Branch coverage
100%
Covered branches: 80
Total branches: 80
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%
get_DefaultPropertyThresholds()100%11100%
InitializeThresholds()100%44100%
CurrentDomain_AssemblyLoad(...)100%11100%
RegisterAssemblyThresholds(...)100%88100%
GetThresholds()100%1414100%
.ctor(...)100%1414100%
Rate(...)100%11100%
Rate(...)100%3636100%
LowIsGoodImportance(...)100%44100%
HighIsGoodImportance(...)100%11100%

File(s)

/home/runner/work/AmbientServices/AmbientServices/AmbientServices/Status/StatusPropertyThresholds.cs

#LineLine coverage
 1using AmbientServices.Extensions;
 2using AmbientServices.Utilities;
 3using System;
 4using System.Collections.Concurrent;
 5using System.Collections.Generic;
 6using System.Linq;
 7
 8namespace AmbientServices;
 9
 10/// <summary>
 11/// An enum identifying the nature of the status threshold.
 12/// </summary>
 13public enum StatusThresholdNature
 14{
 15    /// <summary>
 16    /// Low values for this threshold are good.
 17    /// </summary>
 18    LowIsGood,
 19    /// <summary>
 20    /// High values for this threshold are good.
 21    /// </summary>
 22    HighIsGood,
 23}
 24/// <summary>
 25/// An immutable class that holds status information about property threshold values at which status ratings should tran
 26/// Thresholds only apply to <see cref="StatusProperty"/>s whose values are numeric, and those property values must be c
 27/// The static <see cref="DefaultPropertyThresholds"/> property provides access to the thresholds for all currently-load
 28/// </summary>
 29/// <remarks>
 30/// <pitch>Turns a measured number (free disk space, queue depth, latency) into a <see cref="StatusRating"/>: three opti
 31/// <pledge>
 32/// The three thresholds must be monotonic; their ordering determines whether low or high values are good (the explicit 
 33/// <see cref="Rate(string,float,float)"/> is pure: it returns a <see cref="StatusAuditAlert"/> whose rating is position
 34/// </pledge>
 35/// <plan>
 36/// Rating is a threshold ladder walked in the good-to-bad direction indicated by the nature, with linear interpolation 
 37/// </plan>
 38/// </remarks>
 39public class StatusPropertyThresholds
 40{
 241    private static ConcurrentDictionary<string, StatusPropertyThresholds> _thresholds = InitializeThresholds();
 242    private static readonly DefaultStatusThresholds _thresholdsAccessor = new(_thresholds);
 43    /// <summary>
 44    /// Gets a <see cref="IStatusThresholdsRegistry"/> containing the default status thresholds (those assigned via <see
 45    /// </summary>
 246    public static IStatusThresholdsRegistry DefaultPropertyThresholds => _thresholdsAccessor;
 47
 48    private static ConcurrentDictionary<string, StatusPropertyThresholds> InitializeThresholds()
 49    {
 250        _thresholds = new ConcurrentDictionary<string, StatusPropertyThresholds>();
 51        // hook into all subsequent assembly loads so we can register their thresholds
 252        AppDomain.CurrentDomain.AssemblyLoad += CurrentDomain_AssemblyLoad;
 53        // register the thresholds from all currently-loaded assemblies
 254        foreach (System.Reflection.Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
 55        {
 256            RegisterAssemblyThresholds(assembly);
 57        }
 258        return _thresholds;
 59    }
 60    private static void CurrentDomain_AssemblyLoad(object? sender, AssemblyLoadEventArgs args)
 61    {
 262        RegisterAssemblyThresholds(args.LoadedAssembly);
 263    }
 64    private static void RegisterAssemblyThresholds(System.Reflection.Assembly assembly)
 65    {
 66        // does the loaded assembly refer to this one (if it doesn't, it can't have the stuff we're looking for)
 267        if (assembly.DoesAssemblyReferDirectlyToAssembly(System.Reflection.Assembly.GetExecutingAssembly()))
 68        {
 69            // loop through all the types looking for types that are not abstract, inherit from StatusNode (directly or 
 270            foreach (Type type in assembly.GetLoadableTypes())
 71            {
 272                if (Status.IsTestableStatusCheckerClass(type))
 73                {
 274                    foreach (KeyValuePair<string, StatusPropertyThresholds> kvp in GetThresholds(type))
 75                    {
 276                        _thresholds.TryAdd(kvp.Key.ToUpperInvariant(), kvp.Value);
 77                    }
 78                }
 79            }
 80        }
 281    }
 82    private static IEnumerable<KeyValuePair<string, StatusPropertyThresholds>> GetThresholds(Type type)
 83    {
 284        object[] attributes = type.GetCustomAttributes(typeof(DefaultPropertyThresholdsAttribute), true);
 285        if (attributes != null && attributes.Length > 0)
 86        {
 287            foreach (DefaultPropertyThresholdsAttribute checkerThresholds in attributes.Where(o => o.GetType() == typeof
 88            {
 289                if (checkerThresholds.DeferToType != null)
 90                {
 291                    foreach (KeyValuePair<string, StatusPropertyThresholds> kvp in GetThresholds(checkerThresholds.Defer
 92                    {
 293                        yield return new KeyValuePair<string, StatusPropertyThresholds>(checkerThresholds.PropertyPath +
 94                    }
 95                }
 296                else if (checkerThresholds.Thresholds != null)
 97                {
 298                    yield return new KeyValuePair<string, StatusPropertyThresholds>(checkerThresholds.PropertyPath, chec
 99                }
 2100            }
 101        }
 2102    }
 103
 104    /// <summary>
 105    /// Constructs a <see cref="StatusPropertyThresholds"/> instance with the specified thresholds.
 106    /// </summary>
 107    /// <param name="nature">A <see cref="StatusThresholdNature"/> indicating whether or not low values are good.  Only 
 108    /// <param name="failVsAlertThreshold">The threshold which divides failures from alerts (at this value it counts as 
 109    /// <param name="alertVsOkayThreshold">The threshold which divides alerts from okays (at this value it counts as an 
 110    /// <param name="okayVsSuperlativeThreshold">The threshold which divides okays from superlatives (at this value it c
 2111    public StatusPropertyThresholds(float? failVsAlertThreshold, float? alertVsOkayThreshold, float? okayVsSuperlativeTh
 112    {
 2113        StatusThresholdNature? computedNature = null;
 2114        if (failVsAlertThreshold > alertVsOkayThreshold || alertVsOkayThreshold > okayVsSuperlativeThreshold || failVsAl
 2115        if (failVsAlertThreshold < alertVsOkayThreshold || alertVsOkayThreshold < okayVsSuperlativeThreshold || failVsAl
 116        {
 2117            if (computedNature != null) throw new ArgumentException("The threshold values must be listed in either ascen
 2118            computedNature = StatusThresholdNature.HighIsGood;
 119        }
 2120        Nature = computedNature ?? nature;
 2121        FailVsAlertThreshold = failVsAlertThreshold;
 2122        AlertVsOkayThreshold = alertVsOkayThreshold;
 2123        OkayVsSuperlativeThreshold = okayVsSuperlativeThreshold;
 2124    }
 125
 126    /// <summary>
 127    /// Gets a <see cref="StatusThresholdNature"/> indicating whether low values are good or not.
 128    /// </summary>
 129    public StatusThresholdNature Nature { get; }
 130    /// <summary>
 131    /// Gets the threshold value which divides failures from alerts.  When the measured value is exactly equal to this v
 132    /// </summary>
 133    public float? FailVsAlertThreshold { get; }
 134    /// <summary>
 135    /// Gets the threshold value which divides alerts from okay.  When the measured value is exactly equal to this value
 136    /// </summary>
 137    public float? AlertVsOkayThreshold { get; }
 138    /// <summary>
 139    /// Gets the threshold value which divides okays from superlatives.  When the measured value is exactly equal to thi
 140    /// </summary>
 141    public float? OkayVsSuperlativeThreshold { get; }
 142
 143    /// <summary>
 144    /// Rates the value based on the thresholds and gets a <see cref="StatusAuditAlert"/> indicating the status of the v
 145    /// </summary>
 146    /// <param name="propertyName">The name of the property being rated.</param>
 147    /// <param name="value">The value to be rated.</param>
 148    /// <returns>A <see cref="StatusAuditAlert"/> indicating the status relative to the thresholds.</returns>
 149    public StatusAuditAlert Rate(string propertyName, float value)
 150    {
 2151        return Rate(propertyName, value, value);
 152    }
 153    /// <summary>
 154    /// Rates the value based on the thresholds and gets a <see cref="StatusAuditAlert"/> indicating the status of the v
 155    /// </summary>
 156    /// <param name="propertyName">The name of the property being rated.</param>
 157    /// <param name="lowValue">The low value of the range to be rated.</param>
 158    /// <param name="highValue">The high value of the range to be rated.</param>
 159    /// <returns>A <see cref="StatusAuditAlert"/> indicating the status relative to the thresholds.</returns>
 160    public StatusAuditAlert Rate(string propertyName, float lowValue, float highValue)
 161    {
 162//            if (lowValue < 0) throw new ArgumentOutOfRangeException(nameof(lowValue), "Status rating values must not b
 163//            if (highValue < 0) throw new ArgumentOutOfRangeException(nameof(highValue), "Status rating values must not
 2164        string code = propertyName + ".Threshold";
 165        string tersePrefix;
 2166        string detailedPrefix = (lowValue == highValue)
 2167            ? (propertyName + " is " + lowValue.ToSi(4) + " which is ")
 2168            : (propertyName + " is between " + lowValue.ToSi(4) + " and " + highValue.ToSi(4) + " which is ");
 2169        if (Nature == StatusThresholdNature.LowIsGood)
 170        {
 2171            float value = highValue;
 2172            tersePrefix = propertyName + ":" + value.ToSi(1);
 2173            if (value < OkayVsSuperlativeThreshold)
 174            {
 2175                float seriousness = LowIsGoodImportance(0.0f, OkayVsSuperlativeThreshold.Value, value);
 2176                float rating = StatusRating.Superlative - seriousness;
 2177                return new StatusAuditAlert(rating, code, tersePrefix + "<" + OkayVsSuperlativeThreshold.Value.ToSi(1), 
 178            }
 2179            else if (value < AlertVsOkayThreshold)
 180            {
 2181                float lowThreshold = OkayVsSuperlativeThreshold ?? 0.0f;
 2182                float seriousness = LowIsGoodImportance(lowThreshold, AlertVsOkayThreshold.Value, value);
 2183                float rating = StatusRating.Okay - seriousness;
 2184                return new StatusAuditAlert(rating, code, tersePrefix + ">=" + lowThreshold.ToSi(1), detailedPrefix + "i
 185            }
 2186            else if (value < FailVsAlertThreshold)
 187            {
 2188                float lowThreshold = AlertVsOkayThreshold ?? OkayVsSuperlativeThreshold ?? 0.0f;
 2189                float seriousness = LowIsGoodImportance(lowThreshold, FailVsAlertThreshold.Value, value);
 2190                float rating = StatusRating.Alert - seriousness;
 2191                return new StatusAuditAlert(rating, code, tersePrefix + ">=" + lowThreshold.ToSi(1), detailedPrefix + "i
 192            }
 193            else
 194            {
 2195                float lowThreshold = FailVsAlertThreshold ?? AlertVsOkayThreshold ?? OkayVsSuperlativeThreshold ?? 0.0f;
 2196                float seriousness = LowIsGoodImportance(lowThreshold, float.MaxValue, value);
 2197                float rating = StatusRating.Fail - seriousness;
 2198                return new StatusAuditAlert(rating, code, tersePrefix + ">=" + lowThreshold.ToSi(1), detailedPrefix + "a
 199            }
 200        }
 201        else
 202        {
 2203            float value = lowValue;
 2204            tersePrefix = propertyName + ":" + value.ToSi(1);
 2205            if (value > OkayVsSuperlativeThreshold)
 206            {
 2207                float seriousness = HighIsGoodImportance(OkayVsSuperlativeThreshold.Value, float.MaxValue, value);
 2208                float rating = StatusRating.Superlative - seriousness;
 2209                return new StatusAuditAlert(rating, code, tersePrefix + ">" + OkayVsSuperlativeThreshold.Value.ToSi(1), 
 210            }
 2211            if (value > AlertVsOkayThreshold)
 212            {
 2213                float highThreshold = OkayVsSuperlativeThreshold ?? float.MaxValue;
 2214                float seriousness = HighIsGoodImportance(AlertVsOkayThreshold.Value, highThreshold, value);
 2215                float rating = StatusRating.Okay - seriousness;
 2216                return new StatusAuditAlert(rating, code, tersePrefix + "<=" + highThreshold.ToSi(1), detailedPrefix + "
 217            }
 2218            else if (value > FailVsAlertThreshold)
 219            {
 2220                float highThreshold = AlertVsOkayThreshold ?? OkayVsSuperlativeThreshold ?? float.MaxValue;
 2221                float seriousness = HighIsGoodImportance(FailVsAlertThreshold.Value, highThreshold, value);
 2222                float rating = StatusRating.Alert - seriousness;
 2223                return new StatusAuditAlert(rating, code, tersePrefix + "<=" + highThreshold.ToSi(1), detailedPrefix + "
 224            }
 225            else
 226            {
 2227                float highThreshold = FailVsAlertThreshold ?? AlertVsOkayThreshold ?? OkayVsSuperlativeThreshold ?? floa
 2228                float seriousness = HighIsGoodImportance(0.0f, highThreshold, value);
 2229                float rating = StatusRating.Fail - seriousness;
 2230                return new StatusAuditAlert(rating, code, tersePrefix + "<=" + highThreshold.ToSi(1), detailedPrefix + "
 231            }
 232        }
 233    }
 234    private static float LowIsGoodImportance(float low, float high, float value)
 235    {
 2236        if (value <= low) return 0.0f;
 2237        if (value >= high) return 1.0f;
 2238        return (float)((value - low) / (high - low));
 239    }
 240    private static float HighIsGoodImportance(float low, float high, float value)
 241    {
 2242        return 1.0f - LowIsGoodImportance(low, high, value);
 243    }
 244}
 245
 246internal class DefaultStatusThresholds : IStatusThresholdsRegistry
 247{
 248    private readonly ConcurrentDictionary<string, StatusPropertyThresholds> _thresholds;
 249
 250    public DefaultStatusThresholds(ConcurrentDictionary<string, StatusPropertyThresholds> thresholds)
 251    {
 252        _thresholds = thresholds;
 253    }
 254
 255    public StatusPropertyThresholds? GetThresholds(string path)
 256    {
 257        StatusPropertyThresholds? value;
 258        if (!_thresholds.TryGetValue(path.ToUpperInvariant(), out value)) return null;
 259        return value;
 260    }
 261}
 262/// <summary>
 263/// An attribute class that identifies the default property thresholds for a status test.
 264/// </summary>
 265/// <remarks>
 266/// <pitch>The declarative way for a checker or auditor to ship sensible default thresholds with its code: decorate the 
 267/// <pledge>
 268/// Attributes on testable checker classes are gathered into <see cref="StatusPropertyThresholds.DefaultPropertyThreshol
 269/// The <see cref="DeferToType"/> form composes: it imports the thresholds declared on another type, prefixing each with
 270/// </pledge>
 271/// <priority>
 272/// 1. Working thresholds shipped with the code over correct ones supplied at deployment: a checker declares its own def
 273/// 2. Living beside the code it rates over living in one central table: thresholds are declared on the checker class an
 274/// </priority>
 275/// </remarks>
 276[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
 277public sealed class DefaultPropertyThresholdsAttribute : Attribute
 278{
 279
 280    /// <summary>
 281    /// Constructs a default property thresholds instance by looking at a different type whose threshold attribute will 
 282    /// </summary>
 283    /// <param name="propertyPath">The path to the indicated node.</param>
 284    /// <param name="deferToType">The <see cref="Type"/> that will be added at the specified node path.</param>
 285    public DefaultPropertyThresholdsAttribute(string propertyPath, Type deferToType)
 286    {
 287        PropertyPath = propertyPath;
 288        Thresholds = null;
 289        DeferToType = deferToType;
 290    }
 291    /// <summary>
 292    /// Constructs a default property thresholds attribute instance using the specified parameters.
 293    /// Note that attribute parameters cannot take nullable values, so we use <see cref="float.NaN"/> instead to indicat
 294    /// </summary>
 295    /// <param name="propertyPath">The path to the property with a default threshold.</param>
 296    /// <param name="failVsAlertThreshold">The first value that is a failure instead of an alert.  <see cref="float.NaN"
 297    /// <param name="alertVsOkayThreshold">The first value that is an alert instead of okay.  <see cref="float.NaN"/> if
 298    /// <param name="okayVsSuperlativeThreshold">The first value that is okay instead of superlative.  <see cref="float.
 299    /// <param name="thresholdNature">A <see cref="StatusThresholdNature"/> indicating whether low values are good or ba
 300    public DefaultPropertyThresholdsAttribute(string propertyPath, float failVsAlertThreshold = float.NaN, float alertVs
 301    {
 302        PropertyPath = propertyPath;
 303        Thresholds = new StatusPropertyThresholds(float.IsNaN(failVsAlertThreshold) ? null : failVsAlertThreshold, float
 304        DeferToType = null;
 305    }
 306    /// <summary>
 307    /// Gets the name of the property the thresholds apply to.
 308    /// </summary>
 309    public string PropertyPath { get; }
 310    /// <summary>
 311    /// Gets the <see cref="StatusPropertyThresholds"/> for the corresponding property.
 312    /// May be null if deferred to another type but that type has no attribute thresholds.
 313    /// </summary>
 314    public StatusPropertyThresholds? Thresholds { get; }
 315    /// <summary>
 316    /// A type to defer to for default property thresholds.  Thresholds attached to that type will be added with the pro
 317    /// </summary>
 318    public Type? DeferToType { get; }
 319    /// <summary>
 320    /// Gets the status rating threshold that distinguishes failures from alerts.
 321    /// </summary>
 322    public float FailVsAlertThreshold => Thresholds?.FailVsAlertThreshold ?? float.NaN;
 323    /// <summary>
 324    /// Gets the status rating threshold that distinguishes alerts from okay.
 325    /// </summary>
 326    public float AlertVsOkayThreshold => Thresholds?.AlertVsOkayThreshold ?? float.NaN;
 327    /// <summary>
 328    /// Gets the status rating threshold that distinguishes okay from superlative.
 329    /// </summary>
 330    public float OkayVsSuperlativeThreshold => Thresholds?.OkayVsSuperlativeThreshold ?? float.NaN;
 331    /// <summary>
 332    /// Gets the status rating threshold that distinguishes okay from superlative.
 333    /// </summary>
 334    public StatusThresholdNature ThresholdNature => Thresholds?.Nature ?? StatusThresholdNature.HighIsGood;
 335}
 336/// <summary>
 337/// An interface that abstracts the querying of thresholds used to rate system statuses.
 338/// </summary>
 339/// <remarks>
 340/// <pitch>The seam for overriding how property values are rated: supply your own registry to summarization to replace t
 341/// <pledge>A pure lookup from a dotted target-system property path to its <see cref="StatusPropertyThresholds"/>; retur
 342/// </remarks>
 343public interface IStatusThresholdsRegistry
 344{
 345    /// <summary>
 346    /// Gets the <see cref="StatusPropertyThresholds"/> instance for the specified path, or null if no overriding thresh
 347    /// </summary>
 348    /// <param name="path">The target system path whose thresholds are desired.</param>
 349    /// <returns>A <see cref="StatusPropertyThresholds"/> instance containing the status rating thresholds, or null if t
 350    StatusPropertyThresholds? GetThresholds(string path);
 351}