< Summary

Information
Class: AmbientServices.StatusResults
Assembly: AmbientServices
File(s): /home/runner/work/AmbientServices/AmbientServices/AmbientServices/Status/StatusResults.cs
Tag: 332_35464845198
Line coverage
100%
Covered lines: 102
Uncovered lines: 0
Coverable lines: 102
Total lines: 411
Line coverage: 100%
Branch coverage
100%
Covered branches: 48
Total branches: 48
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%
GetPendingResults(...)100%11100%
.ctor(...)100%22100%
.ctor(...)100%11100%
.ctor(...)100%11100%
get_SourceSystemDisplayName()100%11100%
get_TargetSystemDisplayName()100%11100%
get_Properties()100%11100%
get_Children()100%11100%
SourceDisplayName(...)100%22100%
TargetDisplayName(...)100%44100%
GetSummaryAlerts(...)100%2020100%
RenderTargetedResults(...)100%66100%
Aggregate(...)100%88100%
ToString()100%66100%

File(s)

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

#LineLine coverage
 1using AmbientServices.Utilities;
 2using System;
 3using System.Collections.Generic;
 4using System.Collections.Immutable;
 5using System.Text;
 6
 7namespace AmbientServices;
 8
 9/// <summary>
 10/// An enumeration of possible indicators as to the nature of a system and its children.
 11/// </summary>
 12public enum StatusNatureOfSystem
 13{
 14    /// <summary>
 15    /// Indicates that this system doesn't have any children.
 16    /// Used for nodes with properties that may trigger threshold status ratings.
 17    /// </summary>
 18    Leaf,
 19    /// <summary>
 20    /// Indicates that this system is irrelevant when computing redundancy and overall status.
 21    /// Used for nodes that are purely informational and which cannot trigger threshold status ratings.
 22    /// </summary>
 23    /// <remarks>
 24    /// Nodes of this nature will lack a status report and one should not be computed for them.
 25    /// </remarks>
 26    ChildrenIrrelevant,
 27    /// <summary>
 28    /// Indicates that a node is a system of children of varying functionality or nature, each of which must be working 
 29    /// These nodes may also contain properties, but will primarily rely on the rating determined by descendants.
 30    /// </summary>
 31    /// <remarks>
 32    /// Nodes of this nature will always return the worst (least) status rating gathered or computed from all of their c
 33    /// </remarks>
 34    ChildrenHeterogeneous,
 35    /// <summary>
 36    /// Indicates that a node is the parent node of children which are all identical, only one of which has to be workin
 37    /// These nodes may also contain properties, but will primarily rely on the rating determined by descendants.
 38    /// </summary>
 39    /// <remarks>
 40    /// When all the children have status ratings in the same range, nodes of this nature will use the average rating fr
 41    /// When one or more children have status ratings in ranges different from the other children, the overall rating wi
 42    /// </remarks>
 43    ChildrenHomogeneous,
 44}
 45/// <summary>
 46/// An immutable class that contains properties describing the nature or state of a status node.
 47/// </summary>
 48public sealed class StatusProperty
 49{
 50
 51    /// <summary>
 52    /// The name of the status node property.  If the name begins with an underscore, the value contains sensitive infor
 53    /// </summary>
 54    public string Name { get; }
 55    /// <summary>
 56    /// The value of the status node property.
 57    /// </summary>
 58    public string Value { get; }
 59
 60    /// <summary>
 61    /// Constructs a <see cref="StatusProperty"/> with the specified name and value.
 62    /// </summary>
 63    /// <param name="name">The name of the property.</param>
 64    /// <param name="value">The value of the property.</param>
 65    public StatusProperty(string name, string value) { Name = name; Value = value; }
 66    /// <summary>
 67    /// Creates a <see cref="StatusProperty"/> with the specified name and value.
 68    /// </summary>
 69    /// <param name="name">The name of the property.</param>
 70    /// <param name="value">The value of the property.</param>
 71    public static StatusProperty Create<T>(string name, T value) where T : notnull { return new StatusProperty(name, val
 72    /// <summary>
 73    /// Gets a string representation of this object.
 74    /// </summary>
 75    /// <returns>A string representation of this object.</returns>
 76    public override string ToString()
 77    {
 78        return Name + "=" + Value;
 79    }
 80}
 81/// <summary>
 82/// An immutable class that holds a snapshot of a status results tree (whether from an audit or not), with all the infor
 83/// </summary>
 84/// <remarks>
 85/// <pitch>
 86/// The interchange format of the status system: an immutable tree of source/target-labeled nodes carrying properties, c
 87/// </pitch>
 88/// <pledge>
 89/// A node carries either children or a <see cref="Report"/>, never both: reported (leaf) nodes are the rated facts, and
 90/// Naming rules make cross-server merging work: a <see cref="TargetSystem"/> without a leading slash names a subsystem 
 91/// <see cref="GetSummaryAlerts"/> computes a summary alert over the tree — worst-first, aggregating equivalent alerts, 
 92/// </pledge>
 93/// <plan>
 94/// Properties and children are stored in <see cref="ImmutableArray{T}"/>s captured at construction.  <see cref="GetSumm
 95/// </plan>
 96/// <pin>
 97/// The cross-server interchange conventions of the status system.  They are frozen because results are forwarded betwee
 98/// A leading slash on <see cref="TargetSystem"/> marks a shared system that summarization re-roots as top-level; its ab
 99/// A property name beginning with an underscore declares its value sensitive and not publicly shareable (user or machin
 100/// </pin>
 101/// <priority>
 102/// 1. Being mergeable across servers and across time over being compact: every node carries its own source, target, pro
 103/// 2. Immutability over cheap construction: instances and the whole tree are immutable, so results can be shared, summa
 104/// </priority>
 105/// </remarks>
 106public sealed class StatusResults
 107{
 108#if RAWRATINGS
 109    private const string DebugRatingFloatFormat = "0.00";
 110#endif
 111    private readonly ImmutableArray<StatusProperty> _properties;
 112    private readonly ImmutableArray<StatusResults> _children;
 113
 2114    private StatusResults(string? sourceSystem, string targetSystem)
 115    {
 2116        SourceSystem = sourceSystem;
 2117        TargetSystem = targetSystem;
 2118        Time = AmbientClock.UtcNow;
 2119        RelativeDetailLevel = 0;
 2120        NatureOfSystem = StatusNatureOfSystem.Leaf;
 2121        _properties = ImmutableArray<StatusProperty>.Empty;
 2122        _children = ImmutableArray<StatusResults>.Empty;
 2123        Report = StatusAuditReport.Pending;
 2124    }
 125    /// <summary>
 126    /// Gets a <see cref="StatusResults"/> that indicates that the results for the specified source and target are pendi
 127    /// </summary>
 128    /// <param name="source">The source system (usually null, indicating the local system).</param>
 129    /// <param name="target">The target system, with a leading slash if it is a shared system.</param>
 130    /// <returns></returns>
 131    public static StatusResults GetPendingResults(string? source, string target)
 132    {
 2133        return new StatusResults(source, target);
 134    }
 135#if LATER
 136    /// <summary>
 137    /// Constructs a <see cref="StatusResults"/> from the specified property data (for serialization).
 138    /// </summary>
 139    /// <param name="sourceSystem">The name of the source system (if known).</param>
 140    /// <param name="targetSystem">The name of the target system (if any).</param>
 141    /// <param name="time">The <see cref="DateTime"/> when the properties were gathered.</param>
 142    /// <param name="relativeDetailLevel">The relative level of detail provided by the properties at this level.</param>
 143    /// <param name="properties">An enumeration of <see cref="StatusProperty"/> indicating various properties of the sys
 144    /// <param name="natureOfSystem">A <see cref="StatusNatureOfSystem"/> indicating if or how audit results from childr
 145    /// <param name="children">An enumeration of <see cref="StatusResults"/> for child nodes in the status tree.  Ignore
 146    /// <param name="report">An optional <see cref="StatusAuditReport"/> containing the results of the most recent audit
 147    private StatusResults(string sourceSystem, string targetSystem, DateTime time, int relativeDetailLevel, IEnumerable<
 148    {
 149        _sourceSystem = sourceSystem;
 150        _targetSystem = targetSystem;
 151        _time = time;
 152        _relativeDetailLevel = relativeDetailLevel;
 153        _properties = ImmutableArrayExtensions.FromEnumerable(properties);
 154        if (report != null)
 155        {
 156            _report = report;
 157            _natureOfSystem = StatusNatureOfSystem.Leaf;
 158            _children = ImmutableArray<StatusResults>.Empty;
 159        }
 160        else
 161        {
 162            _report = null;
 163            _natureOfSystem = natureOfSystem;
 164            _children = ImmutableArrayExtensions.FromEnumerable(children);
 165        }
 166    }
 167#endif
 168    /// <summary>
 169    /// Constructs a <see cref="StatusResults"/> from the specified property data.
 170    /// </summary>
 171    /// <param name="sourceSystem">The name of the source system (if known).</param>
 172    /// <param name="targetSystem">The name of the target system, or null or an empty string if these results are not as
 173    /// <param name="time">The <see cref="DateTime"/> when the properties were gathered.</param>
 174    /// <param name="relativeDetailLevel">The relative level of detail provided by the properties at this level.</param>
 175    /// <param name="properties">An enumeration of <see cref="StatusProperty"/> indicating various properties of the sys
 176    /// <param name="natureOfSystem">A <see cref="StatusNatureOfSystem"/> indicating if or how audit results from childr
 177    /// <param name="children">An enumeration of <see cref="StatusResults"/> for child nodes in the status tree.</param>
 2178    public StatusResults(string? sourceSystem, string? targetSystem, DateTime time, int relativeDetailLevel, IEnumerable
 179    {
 2180        SourceSystem = sourceSystem;
 2181        TargetSystem = targetSystem ?? "";
 2182        Time = time;
 2183        RelativeDetailLevel = relativeDetailLevel;
 2184        _properties = ImmutableArrayUtilities.FromEnumerable(properties);
 2185        Report = null;
 2186        NatureOfSystem = natureOfSystem;
 2187        _children = ImmutableArrayUtilities.FromEnumerable(children);
 2188    }
 189    /// <summary>
 190    /// Constructs a <see cref="StatusResults"/> from the specified property data.
 191    /// </summary>
 192    /// <param name="sourceSystem">The name of the source system (if known).</param>
 193    /// <param name="targetSystem">The name of the target system, or an empty string if these results are not associated
 194    /// <param name="time">The <see cref="DateTime"/> when the properties were gathered.</param>
 195    /// <param name="relativeDetailLevel">The relative level of detail provided by the properties at this level.</param>
 196    /// <param name="properties">An enumeration of <see cref="StatusProperty"/> indicating various properties of the sys
 197    /// <param name="report">An optional <see cref="StatusAuditReport"/> containing the results of the most recent audit
 2198    public StatusResults(string? sourceSystem, string targetSystem, DateTime time, int relativeDetailLevel, IEnumerable<
 199    {
 2200        SourceSystem = sourceSystem;
 2201        TargetSystem = targetSystem;
 2202        Time = time;
 2203        RelativeDetailLevel = relativeDetailLevel;
 2204        _properties = ImmutableArrayUtilities.FromEnumerable(properties);
 2205        Report = report;
 2206        NatureOfSystem = StatusNatureOfSystem.Leaf;
 2207        _children = ImmutableArray<StatusResults>.Empty;
 2208    }
 209    /// <summary>
 210    /// Constructs a <see cref="StatusResults"/> including a summary report from the specified children.
 211    /// </summary>
 212    /// <param name="sourceSystem">The name of the source system (if known).</param>
 213    /// <param name="targetSystem">The name of the target system, or an empty string if these results are not associated
 214    /// <param name="children">An enumeration of <see cref="StatusResults"/> for child nodes in the status tree.</param>
 2215    public StatusResults(string? sourceSystem, string targetSystem, IEnumerable<StatusResults> children)
 216    {
 2217        SourceSystem = sourceSystem;
 2218        TargetSystem = targetSystem;
 2219        Time = AmbientClock.UtcNow;
 2220        RelativeDetailLevel = 0;
 2221        _properties = ImmutableArray<StatusProperty>.Empty;
 2222        NatureOfSystem = StatusNatureOfSystem.ChildrenHeterogeneous;
 2223        _children = ImmutableArrayUtilities.FromEnumerable(children);
 2224        Report = null;
 2225    }
 226
 227    /// <summary>
 228    /// A string indicating which system performed the audit.
 229    /// Null except for results gathered from other systems.
 230    /// When specified, all parent and ancestor source system identifiers are overridden and ignored.
 231    /// For summarization purposes, source system names for the same system should match exactly no matter what path the
 232    /// </summary>
 233    public string? SourceSystem { get; }
 234    /// <summary>
 235    /// A string indicating which system performed the audit.
 236    /// "Localhost" when <see cref="SourceSystem"/> is null.
 237    /// </summary>
 2238    public string SourceSystemDisplayName => SourceDisplayName(SourceSystem);
 239    /// <summary>
 240    /// A string indicating which backend system, subsystem, or feature the results were gathered about.
 241    /// If the string does not begin with a slash, the target system is a non-shared subsystem of the target system that
 242    /// If the string begins with a slash, the target system is not a subsystem of the parent node, but rather an indepe
 243    /// May be empty string if this node represents a feature of the parent system that does not require unique identifi
 244    /// For summarization purposes, target system names for the same backend service should match exactly no matter whic
 245    /// </summary>
 246    /// <remarks>
 247    /// For example, the local disk system should be identified with something like "LocalDisk" (no leading slash) becau
 248    /// A shared database system should be identified with something like "/Database" (with a leading slash) because it'
 249    /// </remarks>
 250    public string TargetSystem { get; }
 251    /// <summary>
 252    /// A string indicating which backend system, subsystem, or feature the results were gathered about.
 253    /// If <see cref="TargetSystem"/> is "/", this will be "Overall".
 254    /// If <see cref="TargetSystem"/> is null or empty, this will be "Unknown Target".
 255    /// </summary>
 2256    public string TargetSystemDisplayName => TargetDisplayName(TargetSystem);
 257    /// <summary>
 258    /// A <see cref="DateTime"/> indicating when the information was gathered (which may or may not be periodically audi
 259    /// </summary>
 260    public DateTime Time { get; }
 261    /// <summary>
 262    /// The relative detail level of the properties of this node.
 263    /// Usually zero, indicating that these properties are at the same level of detail as the parent node's properties;
 264    /// or one, indicating that the properties here provide just slightly more detail than the parent node's properties.
 265    /// May be used to filter properties at deeper level status nodes, but may be overridden by status ratings (any node
 266    /// </summary>
 267    public int RelativeDetailLevel { get; }
 268    /// <summary>
 269    /// An enumeration of <see cref="StatusProperty"/> key-value pairs containing detailed information about the system.
 270    /// </summary>
 2271    public IEnumerable<StatusProperty> Properties => _properties;
 272    /// <summary>
 273    /// A <see cref="StatusNatureOfSystem"/> indicating the nature of the system represented by these results for the pu
 274    /// </summary>
 275    public StatusNatureOfSystem NatureOfSystem { get; }
 276    /// <summary>
 277    /// An enumeration of <see cref="StatusResults"/> from child nodes which may or may not be aggregatable, depending o
 278    /// StatusResults may have either children or a report, but never both.
 279    /// </summary>
 2280    public IEnumerable<StatusResults> Children => _children;
 281    /// <summary>
 282    /// An optional <see cref="StatusAuditReport"/> that might contain results of an audit.
 283    /// Overrides any information that might be in child nodes.
 284    /// Null if audits don't apply to this node, or the report must be computed based on <see cref="Properties"/>, <see 
 285    /// Set to the special <see cref="StatusAuditReport.Pending"/> value if the first audit is still pending,
 286    /// StatusResults may have either children or a report, but never both.
 287    /// </summary>
 288    public StatusAuditReport? Report { get; }
 289
 2290    private static string SourceDisplayName(string? source) { return source ?? "Localhost"; }
 2291    private static string TargetDisplayName(string? target) { return (target == "/") ? "Overall" : (string.IsNullOrEmpty
 292    /// <summary>
 293    /// Computes a summary status report based on <see cref="Properties"/>, <see cref="NatureOfSystem"/>, and <see cref=
 294    /// </summary>
 295    /// <param name="includeHtmlTag">Whether or not to include the html and body tags.</param>
 296    /// <param name="ignoreRatingsBetterThan">A value indicating which reports to completely ignore.</param>
 297    /// <param name="ignorePendingRatings">Whether or not to ignore pending ratings.</param>
 298    /// <param name="notificationTimeZone">An optional <see cref="TimeZoneInfo"/> that will be used to convert the notif
 299    /// <returns>A <see cref="StatusAuditAlert"/> summarizing the overall state of the system.</returns>
 300    public StatusAuditAlert GetSummaryAlerts(bool includeHtmlTag, float ignoreRatingsBetterThan, bool ignorePendingRatin
 301    {
 2302        DateTime start = AmbientClock.UtcNow;
 2303        StatusResultsOrganizer organized = new(this);
 304
 2305        organized.ComputeOverallRatingAndSort();
 306
 2307        DateTime notificationTime = TimeZoneInfo.ConvertTimeFromUtc(organized.MostRecentTime, notificationTimeZone ?? Ti
 2308        StatusNotificationWriter writer = new(notificationTime);
 309
 310        // build HTML style and header for the indicated rating and rating range
 2311        float overallRating = organized.SortRating;
 2312        if (includeHtmlTag) writer.EnterHtmlAndBody(overallRating);
 2313        writer.EnterStatusRange(overallRating);
 2314        StatusRatingRange ratingRange = StatusRating.FindRange(overallRating);
 315
 316        // filter irrelevant top-level reports
 2317        AggregatedAlert? aggregatedAlert = null;
 2318        foreach (StatusResultsOrganizer child in organized.Children)
 319        {
 320            // use the specified child rating, or okay if one is not specified
 2321            float childRating = child.SortRating;
 322            // is this one better than the cutoff?  stop now because all the subsequent reports are better than this one
 2323            if (childRating > ignoreRatingsBetterThan || (ignorePendingRatings && float.IsNaN(childRating))) break;
 2324            StatusRatingRange childRatingRange = StatusRating.FindRange(childRating);
 2325            if (childRatingRange != ratingRange)
 326            {
 2327                if (aggregatedAlert != null)
 328                {
 2329                    writer.WriteAggregatedAlert(aggregatedAlert);
 2330                    aggregatedAlert = null;
 331                }
 2332                writer.LeaveStatusRange();
 2333                writer.EnterStatusRange(childRating);
 2334                ratingRange = childRatingRange;
 335            }
 2336            Aggregate(ref aggregatedAlert, writer, start, child, ignoreRatingsBetterThan);
 337        }
 2338        if (aggregatedAlert != null)
 339        {
 2340            writer.WriteAggregatedAlert(aggregatedAlert);
 2341            aggregatedAlert = null;
 342        }
 2343        writer.LeaveStatusRange();
 2344        if (includeHtmlTag) writer.LeaveBodyAndHtml();
 2345        StatusAuditAlert alert = new(overallRating, string.Empty, writer.Terse, writer.Details);
 2346        return alert;
 347    }
 348
 349    private void RenderTargetedResults(StatusNotificationWriter writer, DateTime start, StatusResultsOrganizer results, 
 350    {
 2351        float rating = results.SortRating;
 2352        writer.EnterTarget(results.Target, rating);
 2353        AggregatedAlert? aggregatedAlert = null;
 2354        foreach (StatusResultsOrganizer child in results.Children)
 355        {
 356            // is this one better than the cutoff?  stop now because all the subsequent reports are better than this one
 2357            if (child.OverallRating > ignoreRatingsBetterThan) break;
 2358            Aggregate(ref aggregatedAlert, writer, start, child, ignoreRatingsBetterThan);
 359        }
 2360        if (aggregatedAlert != null) writer.WriteAggregatedAlert(aggregatedAlert);
 2361        writer.LeaveTarget();
 2362    }
 363
 364    private void Aggregate(ref AggregatedAlert? aggregatedAlert, StatusNotificationWriter writer, DateTime start, Status
 365    {
 366        // does this child have children?
 2367        if (child.ChildrenCount > 0)
 368        {
 369            System.Diagnostics.Debug.Assert(child.NatureOfSystem == StatusNatureOfSystem.ChildrenIrrelevant || child.Chi
 370            // recurse for this level too
 2371            RenderTargetedResults(writer, start, child, ignoreRatingsBetterThan);
 372        }
 373        else
 374        {
 375            // can we aggregate this one?
 2376            if (aggregatedAlert != null && aggregatedAlert.CanBeAggregated(child.Target, child.OverallReport))
 377            {
 2378                aggregatedAlert.Aggregate(child.Source, child.Target, child.MostRecentTime, child.OverallReport);
 379            }
 380            else // this one can't be aggregated with previous ones, so we need to flush the previously-aggregated alert
 381            {
 2382                if (aggregatedAlert != null) writer.WriteAggregatedAlert(aggregatedAlert);
 2383                aggregatedAlert = new AggregatedAlert(child.Source, child.Target, child.MostRecentTime, child.OverallRep
 384            }
 385        }
 2386    }
 387
 388    /// <summary>
 389    /// Gets a string representing the instance.
 390    /// </summary>
 391    /// <returns>A string representing the instance.</returns>
 392    public override string ToString()
 393    {
 2394        StringBuilder output = new();
 2395        if (SourceSystem != null)
 396        {
 2397            output.Append(SourceDisplayName(SourceSystem));
 2398            output.Append("->");
 399        }
 2400        if (!string.IsNullOrEmpty(TargetSystem))
 401        {
 2402            output.Append(TargetDisplayName(TargetSystem));
 2403            output.Append(':');
 404        }
 2405        if (Report != null)
 406        {
 2407            output.Append(Report.ToString());
 408        }
 2409        return output.ToString();
 410    }
 411}