summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounter.cs
blob: 3cf668ee45852d370769f116fa94144f1c0b4826 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
using System;
using System.Collections.Generic;
using System.Linq;
using Harmony;

namespace StardewModdingAPI.Framework.PerformanceMonitoring
{
    /// <summary>Tracks metadata about a particular code event.</summary>
    internal class PerformanceCounter
    {
        /*********
        ** Fields
        *********/
        /// <summary>The size of the ring buffer.</summary>
        private readonly int MaxEntries = 16384;

        /// <summary>The collection to which this performance counter belongs.</summary>
        private readonly PerformanceCounterCollection ParentCollection;

        /// <summary>The performance counter entries.</summary>
        private readonly Stack<PerformanceCounterEntry> Entries;

        /// <summary>The entry with the highest execution time.</summary>
        private PerformanceCounterEntry? PeakPerformanceCounterEntry;


        /*********
        ** Accessors
        *********/
        /// <summary>The name of the source.</summary>
        public string Source { get; }

        /// <summary>The alert threshold in milliseconds</summary>
        public double AlertThresholdMilliseconds { get; set; }

        /// <summary>If alerting is enabled or not</summary>
        public bool EnableAlerts { get; set; }


        /*********
        ** Public methods
        *********/
        /// <summary>Construct an instance.</summary>
        /// <param name="parentCollection">The collection to which this performance counter belongs.</param>
        /// <param name="source">The name of the source.</param>
        public PerformanceCounter(PerformanceCounterCollection parentCollection, string source)
        {
            this.ParentCollection = parentCollection;
            this.Source = source;
            this.Entries = new Stack<PerformanceCounterEntry>(this.MaxEntries);
        }

        /// <summary>Add a performance counter entry to the list, update monitoring, and raise alerts if needed.</summary>
        /// <param name="entry">The entry to add.</param>
        public void Add(PerformanceCounterEntry entry)
        {
            // add entry
            if (this.Entries.Count > this.MaxEntries)
                this.Entries.Pop();
            this.Entries.Add(entry);

            // update metrics
            if (this.PeakPerformanceCounterEntry == null || entry.ElapsedMilliseconds > this.PeakPerformanceCounterEntry.Value.ElapsedMilliseconds)
                this.PeakPerformanceCounterEntry = entry;

            // raise alert
            if (this.EnableAlerts && entry.ElapsedMilliseconds > this.AlertThresholdMilliseconds)
                this.ParentCollection.AddAlert(entry.ElapsedMilliseconds, this.AlertThresholdMilliseconds, new AlertContext(this.Source, entry.ElapsedMilliseconds));
        }

        /// <summary>Clear all performance counter entries and monitoring.</summary>
        public void Reset()
        {
            this.Entries.Clear();
            this.PeakPerformanceCounterEntry = null;
        }

        /// <summary>Get the peak entry.</summary>
        public PerformanceCounterEntry? GetPeak()
        {
            return this.PeakPerformanceCounterEntry;
        }

        /// <summary>Get the entry with the highest execution time.</summary>
        /// <param name="range">The time range to search.</param>
        /// <param name="endTime">The end time for the <paramref name="range"/>, or null for the current time.</param>
        public PerformanceCounterEntry? GetPeak(TimeSpan range, DateTime? endTime = null)
        {
            endTime ??= DateTime.UtcNow;
            DateTime startTime = endTime.Value.Subtract(range);

            return this.Entries
                .Where(entry => entry.EventTime >= startTime && entry.EventTime <= endTime)
                .OrderByDescending(x => x.ElapsedMilliseconds)
                .FirstOrDefault();
        }

        /// <summary>Get the last entry added to the list.</summary>
        public PerformanceCounterEntry? GetLastEntry()
        {
            if (this.Entries.Count == 0)
                return null;

            return this.Entries.Peek();
        }

        /// <summary>Get the average over a given time span.</summary>
        /// <param name="range">The time range to search.</param>
        /// <param name="endTime">The end time for the <paramref name="range"/>, or null for the current time.</param>
        public double GetAverage(TimeSpan range, DateTime? endTime = null)
        {
            endTime ??= DateTime.UtcNow;
            DateTime startTime = endTime.Value.Subtract(range);

            double[] entries = this.Entries
                .Where(entry => entry.EventTime >= startTime && entry.EventTime <= endTime)
                .Select(p => p.ElapsedMilliseconds)
                .ToArray();

            return entries.Length > 0
                ? entries.Average()
                : 0;
        }
    }
}