}                                                                                                                                                               // string.IsNullOrEmpty ensures response is not null

        /// <summary>
        /// Computes a summary status report based on <see cref="Properties"/>, <see cref="NatureOfSystem"/>, and <see cref="Children"/>, when a node-level report is not available.
        /// </summary>
        /// <param name="includeHtmlTag">Whether or not to include the html and body tags.</param>
        /// <param name="ignoreRatingsBetterThan">A value indicating which reports to completely ignore.</param>
        /// <param name="ignorePendingRatings">Whether or not to ignore pending ratings.</param>
        /// <param name="notificationTimeZone">An optional <see cref="TimeZoneInfo"/> that will be used to convert the notification time.  If not specified, UTC will be used.</param>
        /// <returns>A <see cref="StatusAuditAlert"/> summarizing the overall state of the system.</returns>
        public StatusAuditAlert GetSummaryAlerts(bool includeHtmlTag, float ignoreRatingsBetterThan, bool ignorePendingRatings, TimeZoneInfo?notificationTimeZone = null)
        {
            DateTime start = AmbientClock.UtcNow;
            StatusResultsOrganizer organized = new StatusResultsOrganizer(this);

            organized.ComputeOverallRatingAndSort();

            DateTime notificationTime       = TimeZoneInfo.ConvertTimeFromUtc(organized.MostRecentTime, notificationTimeZone ?? TimeZoneInfo.Utc);
            StatusNotificationWriter writer = new StatusNotificationWriter(notificationTime);

            // build HTML style and header for the indicated rating and rating range
            float overallRating = organized.SortRating;

            if (includeHtmlTag)
            {
                writer.EnterHtmlAndBody(overallRating);
            }
            writer.EnterStatusRange(overallRating);
            StatusRatingRange ratingRange = StatusRating.FindRange(overallRating);

            // filter irrelevant top-level reports
            AggregatedAlert?aggregatedAlert = null;

            foreach (StatusResultsOrganizer child in organized.Children)
            {
                // use the specified child rating, or okay if one is not specified
                float childRating = child.SortRating;
                // is this one better than the cutoff?  stop now because all the subsequent reports are better than this one! (because they're sorted by rating)
                if (childRating > ignoreRatingsBetterThan || (ignorePendingRatings && float.IsNaN(childRating)))
                {
                    break;
                }
                StatusRatingRange childRatingRange = StatusRating.FindRange(childRating);
                if (childRatingRange != ratingRange)
                {
                    if (aggregatedAlert != null)
                    {
                        writer.WriteAggregatedAlert(aggregatedAlert);
                        aggregatedAlert = null;
                    }
                    writer.LeaveStatusRange();
                    writer.EnterStatusRange(childRating);
                    ratingRange = childRatingRange;
                }
                Aggregate(ref aggregatedAlert, writer, start, child, ignoreRatingsBetterThan);
            }
            if (aggregatedAlert != null)
            {
                writer.WriteAggregatedAlert(aggregatedAlert);
                aggregatedAlert = null;
            }
            writer.LeaveStatusRange();
            if (includeHtmlTag)
            {
                writer.LeaveBodyAndHtml();
            }
            StatusAuditAlert alert = new StatusAuditAlert(overallRating, string.Empty, writer.Terse, writer.Details);

            return(alert);
        }
 private void Aggregate(ref AggregatedAlert?aggregatedAlert, StatusNotificationWriter writer, DateTime start, StatusResultsOrganizer child, float ignoreRatingsBetterThan)
 {
     // does this child have children?
     if (child.ChildrenCount > 0)
     {
         System.Diagnostics.Debug.Assert(child.NatureOfSystem == StatusNatureOfSystem.ChildrenIrrelevant || child.ChildrenCount > 1);
         // recurse for this level too
         RenderTargetedResults(writer, start, child, ignoreRatingsBetterThan);
     }
     else
     {
         // can we aggregate this one?
         if (aggregatedAlert != null && aggregatedAlert.CanBeAggregated(child.Target, child.OverallReport))
         {
             aggregatedAlert.Aggregate(child.Source, child.Target, child.MostRecentTime, child.OverallReport);
         }
         else // this one can't be aggregated with previous ones, so we need to flush the previously-aggregated alerts and start a new aggregation
         {
             if (aggregatedAlert != null)
             {
                 writer.WriteAggregatedAlert(aggregatedAlert);
             }
             aggregatedAlert = new AggregatedAlert(child.Source, child.Target, child.MostRecentTime, child.OverallReport);
         }
     }
 }
        private void RenderTargetedResults(StatusNotificationWriter writer, DateTime start, StatusResultsOrganizer results, float ignoreRatingsBetterThan)
        {
            float rating = results.SortRating;

            writer.EnterTarget(results.Target, rating);
            AggregatedAlert?aggregatedAlert = null;

            foreach (StatusResultsOrganizer child in results.Children)
            {
                // is this one better than the cutoff?  stop now because all the subsequent reports are better than this one!
                if (child.OverallRating > ignoreRatingsBetterThan)
                {
                    break;
                }
                Aggregate(ref aggregatedAlert, writer, start, child, ignoreRatingsBetterThan);
            }
            if (aggregatedAlert != null)
            {
                writer.WriteAggregatedAlert(aggregatedAlert);
            }
            writer.LeaveTarget();
        }
Exemple #4
0
        public void ComputeOverallRatingAndSort(string target = "")
        {
            float?            worstRating      = null;
            StatusRatingRange worstRatingRange = StatusRatingRange.Superlative + 1;
            bool childPending = false;

            // keep track of the worst property rating
            StatusAuditAlert?   worstAlert = null;
            StatusPropertyRange?worstAlertPropertyRange = null;

            foreach (StatusPropertyRange propertyRange in _propertyRanges)
            {
                string propertyPath = ComputeTarget(target, propertyRange.Name).TrimStart('/');
                // is there a thresholds to use to rate a property here or are there defaults?
                StatusPropertyThresholds?thresholds = (_thresholds ?? StatusPropertyThresholds.DefaultPropertyThresholds).GetThresholds(propertyPath);
                // is there a numeric value for which thresholds can be applied?
                float?minValue = null;
                if (!string.IsNullOrEmpty(propertyRange.MinValue))
                {
                    float f;
                    if (float.TryParse(propertyRange.MinValue, out f))
                    {
                        minValue = f;
                    }
                }
                float?maxValue = null;
                if (!string.IsNullOrEmpty(propertyRange.MaxValue))
                {
                    float f;
                    if (float.TryParse(propertyRange.MaxValue, out f))
                    {
                        maxValue = f;
                    }
                }
                // are there thresholds AND range values?
                if (thresholds != null && minValue != null && maxValue != null)
                {
                    // rate based on the value and the thresholds--is this now the worst rating?
                    StatusAuditAlert alert = thresholds.Rate(propertyRange.Name, minValue.Value, maxValue.Value);
                    if (Object.ReferenceEquals(worstAlert, null) || alert.Rating < worstAlert.Rating)
                    {
                        worstAlert = alert;
                        worstAlertPropertyRange = propertyRange;
                    }
                }
            }
            WorstPropertyAlert = worstAlert;
            WorstPropertyRange = worstAlertPropertyRange;

            StatusAuditAlert?assignedAlert        = OverallReport?.Alert;
            float?           assignedRating       = assignedAlert?.Rating;
            float?           worstThresholdRating = WorstPropertyAlert?.Rating;

            // the overall rating will depend on the type of system we're rating
            switch (NatureOfSystem)
            {
            case StatusNatureOfSystem.ChildrenIrrelevant:
                // there shouldn't be a report here--we don't care!
                System.Diagnostics.Debug.Assert(OverallReport == null);
                // let's rate the children anyway so we can add it to the report even if it doesn't affect the rating here
                foreach (StatusResultsOrganizer child in _children)
                {
                    // child not rated yet?
                    if (child.OverallRating == null)
                    {
                        child.ComputeOverallRatingAndSort(ComputeTarget(target, child.Target));
                    }
                    // pending?
                    if (child.SomeRatingsPending)
                    {
                        childPending = true;
                    }
                }
                OverallRating = StatusRating.Okay;
                break;

            case StatusNatureOfSystem.Leaf:
                // is there neither an explicitly-assigned rating nor a rating based on property thresholds?  bail out now without setting a new overall report
                if (assignedRating == null && worstThresholdRating == null)
                {
                    return;
                }
                OverallRating = assignedRating;
                break;

            default:
            case StatusNatureOfSystem.ChildrenHeterogenous:
                // find the worst child rating
                foreach (StatusResultsOrganizer child in _children)
                {
                    // child not rated yet?
                    if (child.OverallRating == null)
                    {
                        child.ComputeOverallRatingAndSort(ComputeTarget(target, child.Target));
                    }
                    if (child.OverallRating != null)
                    {
                        // aggregate results for each child
                        float childRating = child.OverallRating.Value;
                        if (worstRating == null || childRating < worstRating)
                        {
                            worstRating = childRating;
                        }
                        StatusRatingRange childRatingRange = StatusRating.FindRange(childRating);
                        if (childRatingRange < worstRatingRange)
                        {
                            worstRatingRange = childRatingRange;
                        }
                    }
                    // pending?
                    if (child.SomeRatingsPending)
                    {
                        childPending = true;
                    }
                }
                OverallRating = assignedRating = worstRating ?? StatusRating.Okay;
                break;

            case StatusNatureOfSystem.ChildrenHomogenous:
                // compute both ways because we don't know up front what the distribution of status rating ranges is
                float ratingSum        = 0.0f;
                float clampedRatingSum = 0.0f;
                int   ratedChildCount  = 0;
                // first count how many reports are in each clamped rating class
                int[] childrenWithRating = new int[StatusRating.Ranges];
                // make sure that if the number of clamped rating ranges is exactly three (we have to change the code here if this changes)
                System.Diagnostics.Debug.Assert(ClampedRating(StatusRating.Catastrophic) - ClampedRating(StatusRating.Okay) <= 2);
                // check all the child groups
                foreach (StatusResultsOrganizer child in Children)
                {
                    // child not rated yet?
                    if (child.OverallRating == null)
                    {
                        child.ComputeOverallRatingAndSort(ComputeTarget(target, child.Target));
                    }
                    if (child.OverallRating != null)
                    {
                        float             childRating   = child.OverallRating.Value;
                        float             clampedRating = ClampedRating(childRating);
                        StatusRatingRange range         = StatusRating.FindRange(clampedRating);
                        clampedRatingSum += clampedRating;
                        ratingSum        += childRating;
                        ++ratedChildCount;
                        System.Diagnostics.Debug.Assert(range >= StatusRatingRange.Fail && range <= StatusRatingRange.Okay);
                        ++childrenWithRating[(int)range];
                    }
                    // pending?
                    if (child.SomeRatingsPending)
                    {
                        childPending = true;
                    }
                }
                float rating;
#pragma warning disable CA1508  // this check is explicitly to make sure that the subsequent condition is changed if the number of ranges changes
                System.Diagnostics.Debug.Assert(StatusRating.Ranges == 5);
#pragma warning restore CA1508
                // are all of the ratings in the same range?
                if (childrenWithRating[(int)StatusRatingRange.Okay] == ratedChildCount || childrenWithRating[(int)StatusRatingRange.Alert] == ratedChildCount || childrenWithRating[(int)StatusRatingRange.Fail] == ratedChildCount)
                {
                    // the rating is the average of all the children
                    rating = ratingSum / ratedChildCount;
                }
                else     // we have ratings in more than one range, so the overall rating will be in the StatusRating.Alert range
                {
                    // the average clamped rating cannot be out of range because it's clamped, and it cannot be on a boundary because one of the children was not in the same range with the others!
                    System.Diagnostics.Debug.Assert(clampedRatingSum / ratedChildCount > -1.0f && clampedRatingSum / ratedChildCount < 3.0f);
                    rating = StatusRating.Fail + ((clampedRatingSum / ratedChildCount) + 1.0f) / 4.0f;
                }
                OverallRating = assignedRating = rating;
                break;
            }
            // is there a child that is pending (or is this node pending)?
            if (childPending || float.IsNaN(OverallReport?.Alert?.Rating ?? 0.0f))
            {
                SomeRatingsPending = true;
            }

            // only one child and it counts?
            if (_children.Count == 1 && NatureOfSystem != StatusNatureOfSystem.ChildrenIrrelevant)
            {
                // move everything from that child up into us
                StatusResultsOrganizer child = _children[0];
                _propertyRanges.Clear();
                _propertyRanges.AddRange(child.PropertyRanges);
                _children.Clear();
                _children.AddRange(child.Children);
                NatureOfSystem = child.NatureOfSystem;
                OverallRating  = child.OverallRating;
                OverallReport  = child.OverallReport;
                Source         = child.Source ?? Source;
                Target         = ComputeTarget(Target, child.Target);
                // note that the child's children should already be sorted
            }
            else if (_children.Count > 1) // sort the children (if any)
            {
                _children.Sort((a, b) => (a.OverallRating ?? StatusRating.Okay).CompareTo(b.OverallRating ?? StatusRating.Okay));
            }
            // is the threshold rating worse than the assigned rating?
            if (worstThresholdRating < OverallRating)
            {
                System.Diagnostics.Debug.Assert(worstThresholdRating == WorstPropertyAlert?.Rating);
                OverallRating = worstThresholdRating;
                // was there no report before
                if (OverallReport == null)
                {
                    // create a new report with the worst property value
                    OverallReport = new StatusAuditReport(AmbientClock.UtcNow, TimeSpan.Zero, null, WorstPropertyAlert);
                }
                else // there was an existing report
                {
                    // replace that report with a new one with the alert from the worst property rating
                    OverallReport = new StatusAuditReport(OverallReport.AuditStartTime, OverallReport.AuditDuration, OverallReport.NextAuditTime, WorstPropertyAlert);
                }
            }
            // still no rating?  that's okay (literally)
            else if (OverallRating == null)
            {
                OverallRating = StatusRating.Okay;
            }
        }
Exemple #5
0
        /// <summary>
        /// Adds the specified <see cref="StatusResults"/> as a descendant of the specified root.
        /// The position in the tree where the results will be put is determined by the <see cref="StatusResults.TargetDisplayName"/> property,
        /// with nodes that have the same target ending up as siblings to a common parent.
        /// </summary>
        /// <param name="root">The root <see cref="StatusResultsOrganizer"/> being added to.</param>
        /// <param name="results">The <see cref="StatusResults"/> being added.</param>
        /// <param name="source">A source, if one needs to be assigned.</param>
        /// <param name="target">A parent target for the specified node.</param>
        public void Add(StatusResultsOrganizer root, StatusResults results, string?source = null, string target = "")
        {
            // a leaf node?
            if (results.NatureOfSystem == StatusNatureOfSystem.Leaf)
            {
                // rate this leaf node if we can (we need the ratings here in order to collate and sort results in ComputeOverallRatingAndSort)
                string localTarget = results.TargetSystem;
                StatusResultsOrganizer organizedResults = new StatusResultsOrganizer(results, source, localTarget);
                StatusResultsOrganizer parent           = this;
                string?localtarget = results.TargetSystem;
                // a child of the root?
                if (localtarget?.StartsWith("/", StringComparison.Ordinal) ?? false)
                {
                    parent      = root;
                    localtarget = localtarget.Substring(1);
                }
                parent._children.Add(organizedResults);
                // merge in the properties
                organizedResults.MergeProperties(results);
            }
            else // the results instance we're adding is an inner node
            {
                StatusResultsOrganizer?match;
                // do the results specify a target?
                if (!string.IsNullOrEmpty(results.TargetSystem))
                {
                    if (results.TargetSystem == "/")
                    {
                        match = root;
                    }
                    else
                    {
                        StatusResultsOrganizer parent = this;
                        string?localTarget            = results.TargetSystem;
                        // a child of the root?
                        if (localTarget.StartsWith("/", StringComparison.Ordinal))
                        {
                            parent      = root;
                            localTarget = localTarget.Substring(1);
                        }
                        // is there NOT a matching node that's a child of the parent?
                        match = parent._children.Where(c => c.Target == localTarget && c.Source == source && c.NatureOfSystem == results.NatureOfSystem).FirstOrDefault(); // note that the matching node cannot be a leaf in this case because the NatureOfSystem had to match the results and we already checked results for that
                        if (match == null)
                        {                                                                                                                                                  // we don't have a node to merge into, so build a new node now with the properties of these results
                            match = new StatusResultsOrganizer(results, source, localTarget);                                                                              // note that this new matching node cannot be a leaf in this case because the NatureOfSystem had to match the results and we already checked results for that
                            parent._children.Add(match);
                        }
                        System.Diagnostics.Debug.Assert(match.NatureOfSystem != StatusNatureOfSystem.Leaf);
                    }
                }
                else // no target is specified, so we should merge these results directly into this node
                {
                    match = this;
                }

                // does the matching node have children?
                if (match.NatureOfSystem != StatusNatureOfSystem.Leaf)
                {
                    DateTime?time = null;
                    // merge the children of the specified results
                    foreach (StatusResults child in results.Children)
                    {
                        match.Add(root, child, results.SourceSystem ?? source, ComputeTarget(target, results.TargetSystem));
                        if (time == null || child.Time > time)
                        {
                            time = child.Time;
                        }
                    }
                    MostRecentTime = (time > results.Time) ? time.Value : results.Time;
                }
                else // the matching node is a leaf node (this is strange because the node we're adding is NOT a leaf node)
                {
                    match.MostRecentTime = results.Time;
                    // the node we're adding is NOT a leaf node, so it CANNOT have a report (StatusResults cannot have both children and a report, though we can here once we rate parent nodes based on their children)
                    System.Diagnostics.Debug.Assert(results.Report == null);
                }
                // merge in the properties
                match.MergeProperties(results);
            }
        }