summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterCollection.cs
blob: b48efd672beb6dfd26071d5c10ea85b988da7918 (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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace StardewModdingAPI.Framework.PerformanceCounter
{
    internal class PerformanceCounterCollection
    {
        /// <summary>The list of triggered performance counters.</summary>
        private readonly List<AlertContext> TriggeredPerformanceCounters = new List<AlertContext>();

        /// <summary>The stopwatch used to track the invocation time.</summary>
        private readonly Stopwatch InvocationStopwatch = new Stopwatch();

        /// <summary>The performance counter manager.</summary>
        private readonly PerformanceCounterManager PerformanceCounterManager;

        /// <summary>Holds the time to calculate the average calls per second.</summary>
        private DateTime CallsPerSecondStart = DateTime.UtcNow;

        /// <summary>The number of invocations of this collection.</summary>
        private long CallCount;

        public IDictionary<string, PerformanceCounter> PerformanceCounters { get; } = new Dictionary<string, PerformanceCounter>();

        /// <summary>The name of this collection.</summary>
        public string Name { get; }

        /// <summary>Flag if this collection is important (used for the console summary command).</summary>
        public bool IsImportant { 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 PerformanceCounterCollection(PerformanceCounterManager performanceCounterManager, string name, bool isImportant)
        {
            this.Name = name;
            this.PerformanceCounterManager = performanceCounterManager;
            this.IsImportant = isImportant;
        }

        public PerformanceCounterCollection(PerformanceCounterManager performanceCounterManager, string name)
        {
            this.PerformanceCounterManager = performanceCounterManager;
            this.Name = name;
        }

        /// <summary>Tracks a single invocation for a named source.</summary>
        /// <param name="source">The name of the source.</param>
        /// <param name="entry">The entry.</param>
        public void Track(string source, PerformanceCounterEntry entry)
        {
            if (!this.PerformanceCounters.ContainsKey(source))
                this.PerformanceCounters.Add(source, new PerformanceCounter(this, source));

            this.PerformanceCounters[source].Add(entry);

            if (this.EnableAlerts)
                this.TriggeredPerformanceCounters.Add(new AlertContext(source, entry.ElapsedMilliseconds));
        }

        /// <summary>Returns the average execution time for all non-game internal sources.</summary>
        /// <returns>The average execution time in milliseconds</returns>
        public double GetModsAverageExecutionTime()
        {
            return this.PerformanceCounters.Where(p =>
                p.Key != Constants.GamePerformanceCounterName).Sum(p => p.Value.GetAverage());
        }

        /// <summary>Returns the overall average execution time.</summary>
        /// <returns>The average execution time in milliseconds</returns>
        public double GetAverageExecutionTime()
        {
            return this.PerformanceCounters.Sum(p => p.Value.GetAverage());
        }

        /// <summary>Returns the average execution time for game-internal sources.</summary>
        /// <returns>The average execution time in milliseconds</returns>
        public double GetGameAverageExecutionTime()
        {
            if (this.PerformanceCounters.TryGetValue(Constants.GamePerformanceCounterName, out PerformanceCounter gameExecTime))
                return gameExecTime.GetAverage();

            return 0;
        }

        /// <summary>Begins tracking the invocation of this collection.</summary>
        public void BeginTrackInvocation()
        {
            if (this.EnableAlerts)
            {
                this.TriggeredPerformanceCounters.Clear();
                this.InvocationStopwatch.Reset();
                this.InvocationStopwatch.Start();
            }

            this.CallCount++;
        }

        /// <summary>Ends tracking the invocation of this collection. Also records an alert if alerting is enabled
        /// and the invocation time exceeds the threshold.</summary>
        public void EndTrackInvocation()
        {
            if (!this.EnableAlerts) return;

            this.InvocationStopwatch.Stop();

            if (this.InvocationStopwatch.Elapsed.TotalMilliseconds >= this.AlertThresholdMilliseconds)
                this.AddAlert(this.InvocationStopwatch.Elapsed.TotalMilliseconds,
                    this.AlertThresholdMilliseconds, this.TriggeredPerformanceCounters);
        }

        /// <summary>Adds an alert.</summary>
        /// <param name="executionTimeMilliseconds">The execution time in milliseconds.</param>
        /// <param name="thresholdMilliseconds">The configured threshold.</param>
        /// <param name="alerts">The list of alert contexts.</param>
        public void AddAlert(double executionTimeMilliseconds, double thresholdMilliseconds, List<AlertContext> alerts)
        {
            this.PerformanceCounterManager.AddAlert(new AlertEntry(this, executionTimeMilliseconds,
                thresholdMilliseconds, alerts));
        }

        /// <summary>Adds an alert for a single AlertContext</summary>
        /// <param name="executionTimeMilliseconds">The execution time in milliseconds.</param>
        /// <param name="thresholdMilliseconds">The configured threshold.</param>
        /// <param name="alert">The context</param>
        public void AddAlert(double executionTimeMilliseconds, double thresholdMilliseconds, AlertContext alert)
        {
            this.AddAlert(executionTimeMilliseconds, thresholdMilliseconds, new List<AlertContext>() {alert});
        }

        /// <summary>Resets the calls per second counter.</summary>
        public void ResetCallsPerSecond()
        {
            this.CallCount = 0;
            this.CallsPerSecondStart = DateTime.UtcNow;
        }

        /// <summary>Resets all performance counters in this collection.</summary>
        public void Reset()
        {
            foreach (var i in this.PerformanceCounters)
                i.Value.Reset();
        }

        /// <summary>Resets the performance counter for a specific source.</summary>
        /// <param name="source">The source name</param>
        public void ResetSource(string source)
        {
            foreach (var i in this.PerformanceCounters)
                if (i.Value.Source.Equals(source, StringComparison.InvariantCultureIgnoreCase))
                    i.Value.Reset();
        }

        /// <summary>Returns the average calls per second.</summary>
        /// <returns>The average calls per second.</returns>
        public long GetAverageCallsPerSecond()
        {
            long runtimeInSeconds = (long) DateTime.UtcNow.Subtract(this.CallsPerSecondStart).TotalSeconds;

            if (runtimeInSeconds == 0) return 0;

            return this.CallCount / runtimeInSeconds;
        }
    }
}