< Summary

Information
Class: AmbientServices.DefaultPropertyThresholdsAttribute
Assembly: AmbientServices
File(s): /home/runner/work/AmbientServices/AmbientServices/AmbientServices/Status/StatusPropertyThresholds.cs
Tag: 332_35464845198
Line coverage
100%
Covered lines: 14
Uncovered lines: 0
Coverable lines: 14
Total lines: 351
Line coverage: 100%
Branch coverage
100%
Covered branches: 14
Total branches: 14
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.ctor(...)100%66100%
get_FailVsAlertThreshold()100%22100%
get_AlertVsOkayThreshold()100%22100%
get_OkayVsSuperlativeThreshold()100%22100%
get_ThresholdNature()100%22100%

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{
 41    private static ConcurrentDictionary<string, StatusPropertyThresholds> _thresholds = InitializeThresholds();
 42    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>
 46    public static IStatusThresholdsRegistry DefaultPropertyThresholds => _thresholdsAccessor;
 47
 48    private static ConcurrentDictionary<string, StatusPropertyThresholds> InitializeThresholds()
 49    {
 50        _thresholds = new ConcurrentDictionary<string, StatusPropertyThresholds>();
 51        // hook into all subsequent assembly loads so we can register their thresholds
 52        AppDomain.CurrentDomain.AssemblyLoad += CurrentDomain_AssemblyLoad;
 53        // register the thresholds from all currently-loaded assemblies
 54        foreach (System.Reflection.Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
 55        {
 56            RegisterAssemblyThresholds(assembly);
 57        }
 58        return _thresholds;
 59    }
 60    private static void CurrentDomain_AssemblyLoad(object? sender, AssemblyLoadEventArgs args)
 61    {
 62        RegisterAssemblyThresholds(args.LoadedAssembly);
 63    }
 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)
 67        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 
 70            foreach (Type type in assembly.GetLoadableTypes())
 71            {
 72                if (Status.IsTestableStatusCheckerClass(type))
 73                {
 74                    foreach (KeyValuePair<string, StatusPropertyThresholds> kvp in GetThresholds(type))
 75                    {
 76                        _thresholds.TryAdd(kvp.Key.ToUpperInvariant(), kvp.Value);
 77                    }
 78                }
 79            }
 80        }
 81    }
 82    private static IEnumerable<KeyValuePair<string, StatusPropertyThresholds>> GetThresholds(Type type)
 83    {
 84        object[] attributes = type.GetCustomAttributes(typeof(DefaultPropertyThresholdsAttribute), true);
 85        if (attributes != null && attributes.Length > 0)
 86        {
 87            foreach (DefaultPropertyThresholdsAttribute checkerThresholds in attributes.Where(o => o.GetType() == typeof
 88            {
 89                if (checkerThresholds.DeferToType != null)
 90                {
 91                    foreach (KeyValuePair<string, StatusPropertyThresholds> kvp in GetThresholds(checkerThresholds.Defer
 92                    {
 93                        yield return new KeyValuePair<string, StatusPropertyThresholds>(checkerThresholds.PropertyPath +
 94                    }
 95                }
 96                else if (checkerThresholds.Thresholds != null)
 97                {
 98                    yield return new KeyValuePair<string, StatusPropertyThresholds>(checkerThresholds.PropertyPath, chec
 99                }
 100            }
 101        }
 102    }
 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
 111    public StatusPropertyThresholds(float? failVsAlertThreshold, float? alertVsOkayThreshold, float? okayVsSuperlativeTh
 112    {
 113        StatusThresholdNature? computedNature = null;
 114        if (failVsAlertThreshold > alertVsOkayThreshold || alertVsOkayThreshold > okayVsSuperlativeThreshold || failVsAl
 115        if (failVsAlertThreshold < alertVsOkayThreshold || alertVsOkayThreshold < okayVsSuperlativeThreshold || failVsAl
 116        {
 117            if (computedNature != null) throw new ArgumentException("The threshold values must be listed in either ascen
 118            computedNature = StatusThresholdNature.HighIsGood;
 119        }
 120        Nature = computedNature ?? nature;
 121        FailVsAlertThreshold = failVsAlertThreshold;
 122        AlertVsOkayThreshold = alertVsOkayThreshold;
 123        OkayVsSuperlativeThreshold = okayVsSuperlativeThreshold;
 124    }
 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    {
 151        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
 164        string code = propertyName + ".Threshold";
 165        string tersePrefix;
 166        string detailedPrefix = (lowValue == highValue)
 167            ? (propertyName + " is " + lowValue.ToSi(4) + " which is ")
 168            : (propertyName + " is between " + lowValue.ToSi(4) + " and " + highValue.ToSi(4) + " which is ");
 169        if (Nature == StatusThresholdNature.LowIsGood)
 170        {
 171            float value = highValue;
 172            tersePrefix = propertyName + ":" + value.ToSi(1);
 173            if (value < OkayVsSuperlativeThreshold)
 174            {
 175                float seriousness = LowIsGoodImportance(0.0f, OkayVsSuperlativeThreshold.Value, value);
 176                float rating = StatusRating.Superlative - seriousness;
 177                return new StatusAuditAlert(rating, code, tersePrefix + "<" + OkayVsSuperlativeThreshold.Value.ToSi(1), 
 178            }
 179            else if (value < AlertVsOkayThreshold)
 180            {
 181                float lowThreshold = OkayVsSuperlativeThreshold ?? 0.0f;
 182                float seriousness = LowIsGoodImportance(lowThreshold, AlertVsOkayThreshold.Value, value);
 183                float rating = StatusRating.Okay - seriousness;
 184                return new StatusAuditAlert(rating, code, tersePrefix + ">=" + lowThreshold.ToSi(1), detailedPrefix + "i
 185            }
 186            else if (value < FailVsAlertThreshold)
 187            {
 188                float lowThreshold = AlertVsOkayThreshold ?? OkayVsSuperlativeThreshold ?? 0.0f;
 189                float seriousness = LowIsGoodImportance(lowThreshold, FailVsAlertThreshold.Value, value);
 190                float rating = StatusRating.Alert - seriousness;
 191                return new StatusAuditAlert(rating, code, tersePrefix + ">=" + lowThreshold.ToSi(1), detailedPrefix + "i
 192            }
 193            else
 194            {
 195                float lowThreshold = FailVsAlertThreshold ?? AlertVsOkayThreshold ?? OkayVsSuperlativeThreshold ?? 0.0f;
 196                float seriousness = LowIsGoodImportance(lowThreshold, float.MaxValue, value);
 197                float rating = StatusRating.Fail - seriousness;
 198                return new StatusAuditAlert(rating, code, tersePrefix + ">=" + lowThreshold.ToSi(1), detailedPrefix + "a
 199            }
 200        }
 201        else
 202        {
 203            float value = lowValue;
 204            tersePrefix = propertyName + ":" + value.ToSi(1);
 205            if (value > OkayVsSuperlativeThreshold)
 206            {
 207                float seriousness = HighIsGoodImportance(OkayVsSuperlativeThreshold.Value, float.MaxValue, value);
 208                float rating = StatusRating.Superlative - seriousness;
 209                return new StatusAuditAlert(rating, code, tersePrefix + ">" + OkayVsSuperlativeThreshold.Value.ToSi(1), 
 210            }
 211            if (value > AlertVsOkayThreshold)
 212            {
 213                float highThreshold = OkayVsSuperlativeThreshold ?? float.MaxValue;
 214                float seriousness = HighIsGoodImportance(AlertVsOkayThreshold.Value, highThreshold, value);
 215                float rating = StatusRating.Okay - seriousness;
 216                return new StatusAuditAlert(rating, code, tersePrefix + "<=" + highThreshold.ToSi(1), detailedPrefix + "
 217            }
 218            else if (value > FailVsAlertThreshold)
 219            {
 220                float highThreshold = AlertVsOkayThreshold ?? OkayVsSuperlativeThreshold ?? float.MaxValue;
 221                float seriousness = HighIsGoodImportance(FailVsAlertThreshold.Value, highThreshold, value);
 222                float rating = StatusRating.Alert - seriousness;
 223                return new StatusAuditAlert(rating, code, tersePrefix + "<=" + highThreshold.ToSi(1), detailedPrefix + "
 224            }
 225            else
 226            {
 227                float highThreshold = FailVsAlertThreshold ?? AlertVsOkayThreshold ?? OkayVsSuperlativeThreshold ?? floa
 228                float seriousness = HighIsGoodImportance(0.0f, highThreshold, value);
 229                float rating = StatusRating.Fail - seriousness;
 230                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    {
 236        if (value <= low) return 0.0f;
 237        if (value >= high) return 1.0f;
 238        return (float)((value - low) / (high - low));
 239    }
 240    private static float HighIsGoodImportance(float low, float high, float value)
 241    {
 242        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>
 2285    public DefaultPropertyThresholdsAttribute(string propertyPath, Type deferToType)
 286    {
 2287        PropertyPath = propertyPath;
 2288        Thresholds = null;
 2289        DeferToType = deferToType;
 2290    }
 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
 2300    public DefaultPropertyThresholdsAttribute(string propertyPath, float failVsAlertThreshold = float.NaN, float alertVs
 301    {
 2302        PropertyPath = propertyPath;
 2303        Thresholds = new StatusPropertyThresholds(float.IsNaN(failVsAlertThreshold) ? null : failVsAlertThreshold, float
 2304        DeferToType = null;
 2305    }
 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>
 2322    public float FailVsAlertThreshold => Thresholds?.FailVsAlertThreshold ?? float.NaN;
 323    /// <summary>
 324    /// Gets the status rating threshold that distinguishes alerts from okay.
 325    /// </summary>
 2326    public float AlertVsOkayThreshold => Thresholds?.AlertVsOkayThreshold ?? float.NaN;
 327    /// <summary>
 328    /// Gets the status rating threshold that distinguishes okay from superlative.
 329    /// </summary>
 2330    public float OkayVsSuperlativeThreshold => Thresholds?.OkayVsSuperlativeThreshold ?? float.NaN;
 331    /// <summary>
 332    /// Gets the status rating threshold that distinguishes okay from superlative.
 333    /// </summary>
 2334    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}