summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/PerformanceCounter
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework/PerformanceCounter')
-rw-r--r--src/SMAPI/Framework/PerformanceCounter/AlertContext.cs14
-rw-r--r--src/SMAPI/Framework/PerformanceCounter/AlertEntry.cs31
-rw-r--r--src/SMAPI/Framework/PerformanceCounter/EventPerformanceCounterCollection.cs16
-rw-r--r--src/SMAPI/Framework/PerformanceCounter/PeakEntry.cs25
-rw-r--r--src/SMAPI/Framework/PerformanceCounter/PerformanceCounter.cs143
-rw-r--r--src/SMAPI/Framework/PerformanceCounter/PerformanceCounterCollection.cs196
-rw-r--r--src/SMAPI/Framework/PerformanceCounter/PerformanceCounterEntry.cs26
-rw-r--r--src/SMAPI/Framework/PerformanceCounter/PerformanceCounterManager.cs212
8 files changed, 291 insertions, 372 deletions
diff --git a/src/SMAPI/Framework/PerformanceCounter/AlertContext.cs b/src/SMAPI/Framework/PerformanceCounter/AlertContext.cs
index 63f0a5ed..76c472e1 100644
--- a/src/SMAPI/Framework/PerformanceCounter/AlertContext.cs
+++ b/src/SMAPI/Framework/PerformanceCounter/AlertContext.cs
@@ -3,13 +3,20 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
/// <summary>The context for an alert.</summary>
internal struct AlertContext
{
+ /*********
+ ** Accessors
+ *********/
/// <summary>The source which triggered the alert.</summary>
- public readonly string Source;
+ public string Source { get; }
/// <summary>The elapsed milliseconds.</summary>
- public readonly double Elapsed;
+ public double Elapsed { get; }
- /// <summary>Creates a new alert context.</summary>
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
/// <param name="source">The source which triggered the alert.</param>
/// <param name="elapsed">The elapsed milliseconds.</param>
public AlertContext(string source, double elapsed)
@@ -18,6 +25,7 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
this.Elapsed = elapsed;
}
+ /// <summary>Get a human-readable text form of this instance.</summary>
public override string ToString()
{
return $"{this.Source}: {this.Elapsed:F2}ms";
diff --git a/src/SMAPI/Framework/PerformanceCounter/AlertEntry.cs b/src/SMAPI/Framework/PerformanceCounter/AlertEntry.cs
index b87d8642..494f34a9 100644
--- a/src/SMAPI/Framework/PerformanceCounter/AlertEntry.cs
+++ b/src/SMAPI/Framework/PerformanceCounter/AlertEntry.cs
@@ -1,28 +1,33 @@
-using System.Collections.Generic;
-
namespace StardewModdingAPI.Framework.PerformanceCounter
{
/// <summary>A single alert entry.</summary>
internal struct AlertEntry
{
+ /*********
+ ** Accessors
+ *********/
/// <summary>The collection in which the alert occurred.</summary>
- public readonly PerformanceCounterCollection Collection;
+ public PerformanceCounterCollection Collection { get; }
/// <summary>The actual execution time in milliseconds.</summary>
- public readonly double ExecutionTimeMilliseconds;
+ public double ExecutionTimeMilliseconds { get; }
+
+ /// <summary>The configured alert threshold in milliseconds.</summary>
+ public double ThresholdMilliseconds { get; }
- /// <summary>The configured alert threshold. </summary>
- public readonly double ThresholdMilliseconds;
+ /// <summary>The sources involved in exceeding the threshold.</summary>
+ public AlertContext[] Context { get; }
- /// <summary>The context list, which records all sources involved in exceeding the threshold.</summary>
- public readonly List<AlertContext> Context;
- /// <summary>Creates a new alert entry.</summary>
- /// <param name="collection">The source collection in which the alert occurred.</param>
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="collection">The collection in which the alert occurred.</param>
/// <param name="executionTimeMilliseconds">The actual execution time in milliseconds.</param>
- /// <param name="thresholdMilliseconds">The configured threshold in milliseconds.</param>
- /// <param name="context">A list of AlertContext to record which sources were involved</param>
- public AlertEntry(PerformanceCounterCollection collection, double executionTimeMilliseconds, double thresholdMilliseconds, List<AlertContext> context)
+ /// <param name="thresholdMilliseconds">The configured alert threshold in milliseconds.</param>
+ /// <param name="context">The sources involved in exceeding the threshold.</param>
+ public AlertEntry(PerformanceCounterCollection collection, double executionTimeMilliseconds, double thresholdMilliseconds, AlertContext[] context)
{
this.Collection = collection;
this.ExecutionTimeMilliseconds = executionTimeMilliseconds;
diff --git a/src/SMAPI/Framework/PerformanceCounter/EventPerformanceCounterCollection.cs b/src/SMAPI/Framework/PerformanceCounter/EventPerformanceCounterCollection.cs
deleted file mode 100644
index 4690c512..00000000
--- a/src/SMAPI/Framework/PerformanceCounter/EventPerformanceCounterCollection.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using StardewModdingAPI.Framework.Events;
-
-namespace StardewModdingAPI.Framework.PerformanceCounter
-{
- /// <summary>Represents a performance counter collection specific to game events.</summary>
- internal class EventPerformanceCounterCollection: PerformanceCounterCollection
- {
- /// <summary>Creates a new event performance counter collection.</summary>
- /// <param name="manager">The performance counter manager.</param>
- /// <param name="event">The ManagedEvent.</param>
- /// <param name="isImportant">If the event is flagged as important.</param>
- public EventPerformanceCounterCollection(PerformanceCounterManager manager, IManagedEvent @event, bool isImportant) : base(manager, @event.GetName(), isImportant)
- {
- }
- }
-}
diff --git a/src/SMAPI/Framework/PerformanceCounter/PeakEntry.cs b/src/SMAPI/Framework/PerformanceCounter/PeakEntry.cs
index 95dc11f4..abb29541 100644
--- a/src/SMAPI/Framework/PerformanceCounter/PeakEntry.cs
+++ b/src/SMAPI/Framework/PerformanceCounter/PeakEntry.cs
@@ -1,20 +1,31 @@
using System;
-using System.Collections.Generic;
namespace StardewModdingAPI.Framework.PerformanceCounter
{
+ /// <summary>A peak invocation time.</summary>
internal struct PeakEntry
{
+ /*********
+ ** Accessors
+ *********/
/// <summary>The actual execution time in milliseconds.</summary>
- public readonly double ExecutionTimeMilliseconds;
+ public double ExecutionTimeMilliseconds { get; }
- /// <summary>The DateTime when the entry occured.</summary>
- public DateTime EventTime;
+ /// <summary>When the entry occurred.</summary>
+ public DateTime EventTime { get; }
- /// <summary>The context list, which records all sources involved in exceeding the threshold.</summary>
- public readonly List<AlertContext> Context;
+ /// <summary>The sources involved in exceeding the threshold.</summary>
+ public AlertContext[] Context { get; }
- public PeakEntry(double executionTimeMilliseconds, DateTime eventTime, List<AlertContext> context)
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="executionTimeMilliseconds">The actual execution time in milliseconds.</param>
+ /// <param name="eventTime">When the entry occurred.</param>
+ /// <param name="context">The sources involved in exceeding the threshold.</param>
+ public PeakEntry(double executionTimeMilliseconds, DateTime eventTime, AlertContext[] context)
{
this.ExecutionTimeMilliseconds = executionTimeMilliseconds;
this.EventTime = eventTime;
diff --git a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounter.cs b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounter.cs
index e9dfcb14..0fb6482d 100644
--- a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounter.cs
+++ b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounter.cs
@@ -1,23 +1,32 @@
using System;
+using System.Collections.Generic;
using System.Linq;
-using Cyotek.Collections.Generic;
+using Harmony;
namespace StardewModdingAPI.Framework.PerformanceCounter
{
+ /// <summary>Tracks metadata about a particular code event.</summary>
internal class PerformanceCounter
{
+ /*********
+ ** Fields
+ *********/
/// <summary>The size of the ring buffer.</summary>
- private const int MAX_ENTRIES = 16384;
+ private readonly int MaxEntries = 16384;
/// <summary>The collection to which this performance counter belongs.</summary>
private readonly PerformanceCounterCollection ParentCollection;
- /// <summary>The circular buffer which stores all performance counter entries</summary>
- private readonly CircularBuffer<PerformanceCounterEntry> _counter;
+ /// <summary>The performance counter entries.</summary>
+ private readonly Stack<PerformanceCounterEntry> Entries;
- /// <summary>The peak execution time</summary>
+ /// <summary>The entry with the highest execution time.</summary>
private PerformanceCounterEntry? PeakPerformanceCounterEntry;
+
+ /*********
+ ** Accessors
+ *********/
/// <summary>The name of the source.</summary>
public string Source { get; }
@@ -27,118 +36,90 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
/// <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._counter = new CircularBuffer<PerformanceCounterEntry>(PerformanceCounter.MAX_ENTRIES);
+ this.Entries = new Stack<PerformanceCounterEntry>(this.MaxEntries);
}
- /// <summary>Adds a new performance counter entry to the list. Updates the peak entry and adds an alert if
- /// monitoring is enabled and the execution time exceeds the threshold.</summary>
+ /// <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)
{
- this._counter.Put(entry);
-
- if (this.EnableAlerts && entry.ElapsedMilliseconds > this.AlertThresholdMilliseconds)
- this.ParentCollection.AddAlert(entry.ElapsedMilliseconds, this.AlertThresholdMilliseconds,
- new AlertContext(this.Source, entry.ElapsedMilliseconds));
+ // add entry
+ if (this.Entries.Count > this.MaxEntries)
+ this.Entries.Pop();
+ this.Entries.Add(entry);
- if (this.PeakPerformanceCounterEntry == null)
+ // update metrics
+ if (this.PeakPerformanceCounterEntry == null || entry.ElapsedMilliseconds > this.PeakPerformanceCounterEntry.Value.ElapsedMilliseconds)
this.PeakPerformanceCounterEntry = entry;
- else
- {
- if (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>Clears all performance counter entries and resets the peak entry.</summary>
+ /// <summary>Clear all performance counter entries and monitoring.</summary>
public void Reset()
{
- this._counter.Clear();
+ this.Entries.Clear();
this.PeakPerformanceCounterEntry = null;
}
- /// <summary>Returns the peak entry.</summary>
- /// <returns>The peak entry.</returns>
+ /// <summary>Get the peak entry.</summary>
public PerformanceCounterEntry? GetPeak()
{
return this.PeakPerformanceCounterEntry;
}
- /// <summary>Returns the peak entry.</summary>
- /// <returns>The peak entry.</returns>
- public PerformanceCounterEntry? GetPeak(TimeSpan range, DateTime? relativeTo = null)
+ /// <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)
{
- if (this._counter.IsEmpty)
- return null;
-
- if (relativeTo == null)
- relativeTo = DateTime.UtcNow;
-
- DateTime start = relativeTo.Value.Subtract(range);
-
- var entries = this._counter.Where(x => (x.EventTime >= start) && (x.EventTime <= relativeTo)).ToList();
-
- if (!entries.Any())
- return null;
+ endTime ??= DateTime.UtcNow;
+ DateTime startTime = endTime.Value.Subtract(range);
- return entries.OrderByDescending(x => x.ElapsedMilliseconds).First();
+ return this.Entries
+ .Where(entry => entry.EventTime >= startTime && entry.EventTime <= endTime)
+ .OrderByDescending(x => x.ElapsedMilliseconds)
+ .FirstOrDefault();
}
- /// <summary>Resets the peak entry.</summary>
- public void ResetPeak()
- {
- this.PeakPerformanceCounterEntry = null;
- }
-
- /// <summary>Returns the last entry added to the list.</summary>
- /// <returns>The last entry</returns>
+ /// <summary>Get the last entry added to the list.</summary>
public PerformanceCounterEntry? GetLastEntry()
{
- if (this._counter.IsEmpty)
+ if (this.Entries.Count == 0)
return null;
- return this._counter.PeekLast();
+ return this.Entries.Peek();
}
- /// <summary>Returns the average execution time of all entries.</summary>
- /// <returns>The average execution time in milliseconds.</returns>
- public double GetAverage()
+ /// <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)
{
- if (this._counter.IsEmpty)
- return 0;
-
- return this._counter.Average(p => p.ElapsedMilliseconds);
- }
-
- /// <summary>Returns the average over a given time span.</summary>
- /// <param name="range">The time range to retrieve.</param>
- /// <param name="relativeTo">The DateTime from which to start the average. Defaults to DateTime.UtcNow if null</param>
- /// <returns>The average execution time in milliseconds.</returns>
- /// <remarks>
- /// The relativeTo parameter specifies from which point in time the range is subtracted. Example:
- /// If DateTime is set to 60 seconds ago, and the range is set to 60 seconds, the method would return
- /// the average between all entries between 120s ago and 60s ago.
- /// </remarks>
- public double GetAverage(TimeSpan range, DateTime? relativeTo = null)
- {
- if (this._counter.IsEmpty)
- return 0;
-
- if (relativeTo == null)
- relativeTo = DateTime.UtcNow;
-
- DateTime start = relativeTo.Value.Subtract(range);
-
- var entries = this._counter.Where(x => (x.EventTime >= start) && (x.EventTime <= relativeTo)).ToList();
+ endTime ??= DateTime.UtcNow;
+ DateTime startTime = endTime.Value.Subtract(range);
- if (!entries.Any())
- return 0;
+ double[] entries = this.Entries
+ .Where(entry => entry.EventTime >= startTime && entry.EventTime <= endTime)
+ .Select(p => p.ElapsedMilliseconds)
+ .ToArray();
- return entries.Average(x => x.ElapsedMilliseconds);
+ return entries.Length > 0
+ ? entries.Average()
+ : 0;
}
}
}
diff --git a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterCollection.cs b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterCollection.cs
index f469eceb..bd13a36e 100644
--- a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterCollection.cs
+++ b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterCollection.cs
@@ -2,153 +2,129 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-using Cyotek.Collections.Generic;
namespace StardewModdingAPI.Framework.PerformanceCounter
{
internal class PerformanceCounterCollection
{
- /// <summary>The size of the ring buffer.</summary>
- private const int MAX_ENTRIES = 16384;
+ /*********
+ ** Fields
+ *********/
+ /// <summary>The number of peak invocations to keep.</summary>
+ private readonly int MaxEntries = 16384;
- /// <summary>The list of triggered performance counters.</summary>
+ /// <summary>The sources involved in exceeding alert thresholds.</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;
+ private readonly PerformanceMonitor PerformanceMonitor;
- /// <summary>Holds the time to calculate the average calls per second.</summary>
+ /// <summary>The time to calculate average calls per second.</summary>
private DateTime CallsPerSecondStart = DateTime.UtcNow;
- /// <summary>The number of invocations of this collection.</summary>
+ /// <summary>The number of invocations.</summary>
private long CallCount;
- /// <summary>The circular buffer which stores all peak invocations</summary>
- private readonly CircularBuffer<PeakEntry> PeakInvocations;
+ /// <summary>The peak invocations.</summary>
+ private readonly Stack<PeakEntry> PeakInvocations;
+
+ /*********
+ ** Accessors
+ *********/
/// <summary>The associated performance counters.</summary>
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>Whether the source is typically invoked at least once per second.</summary>
+ public bool IsPerformanceCritical { get; }
/// <summary>The alert threshold in milliseconds.</summary>
public double AlertThresholdMilliseconds { get; set; }
- /// <summary>If alerting is enabled or not</summary>
+ /// <summary>Whether alerts are enabled.</summary>
public bool EnableAlerts { get; set; }
- public PerformanceCounterCollection(PerformanceCounterManager performanceCounterManager, string name, bool isImportant)
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="performanceMonitor">The performance counter manager.</param>
+ /// <param name="name">The name of this collection.</param>
+ /// <param name="isPerformanceCritical">Whether the source is typically invoked at least once per second.</param>
+ public PerformanceCounterCollection(PerformanceMonitor performanceMonitor, string name, bool isPerformanceCritical = false)
{
- this.PeakInvocations = new CircularBuffer<PeakEntry>(PerformanceCounterCollection.MAX_ENTRIES);
+ this.PeakInvocations = new Stack<PeakEntry>(this.MaxEntries);
this.Name = name;
- this.PerformanceCounterManager = performanceCounterManager;
- this.IsImportant = isImportant;
+ this.PerformanceMonitor = performanceMonitor;
+ this.IsPerformanceCritical = isPerformanceCritical;
}
- public PerformanceCounterCollection(PerformanceCounterManager performanceCounterManager, string name)
- {
- this.PeakInvocations = new CircularBuffer<PeakEntry>(PerformanceCounterCollection.MAX_ENTRIES);
- this.PerformanceCounterManager = performanceCounterManager;
- this.Name = name;
- }
-
- /// <summary>Tracks a single invocation for a named source.</summary>
+ /// <summary>Track 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)
{
+ // add entry
if (!this.PerformanceCounters.ContainsKey(source))
this.PerformanceCounters.Add(source, new PerformanceCounter(this, source));
-
this.PerformanceCounters[source].Add(entry);
+ // raise alert
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 average execution time for all non-game internal sources.</summary>
+ /// <summary>Get the average execution time for all non-game internal sources in milliseconds.</summary>
/// <param name="interval">The interval for which to get the average, relative to now</param>
- /// <returns>The average execution time in milliseconds</returns>
public double GetModsAverageExecutionTime(TimeSpan interval)
{
- return this.PerformanceCounters.Where(p =>
- p.Key != Constants.GamePerformanceCounterName).Sum(p => p.Value.GetAverage(interval));
- }
-
- /// <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());
+ return this.PerformanceCounters
+ .Where(entry => entry.Key != Constants.GamePerformanceCounterName)
+ .Sum(entry => entry.Value.GetAverage(interval));
}
- /// <summary>Returns the overall average execution time.</summary>
+ /// <summary>Get the overall average execution time in milliseconds.</summary>
/// <param name="interval">The interval for which to get the average, relative to now</param>
- /// <returns>The average execution time in milliseconds</returns>
public double GetAverageExecutionTime(TimeSpan interval)
{
- return this.PerformanceCounters.Sum(p => p.Value.GetAverage(interval));
+ return this.PerformanceCounters
+ .Sum(entry => entry.Value.GetAverage(interval));
}
- /// <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>Returns the average execution time for game-internal sources.</summary>
- /// <returns>The average execution time in milliseconds</returns>
+ /// <summary>Get the average execution time for game-internal sources in milliseconds.</summary>
public double GetGameAverageExecutionTime(TimeSpan interval)
{
- if (this.PerformanceCounters.TryGetValue(Constants.GamePerformanceCounterName, out PerformanceCounter gameExecTime))
- return gameExecTime.GetAverage(interval);
-
- return 0;
+ return this.PerformanceCounters.TryGetValue(Constants.GamePerformanceCounterName, out PerformanceCounter gameExecTime)
+ ? gameExecTime.GetAverage(interval)
+ : 0;
}
- /// <summary>Returns the peak execution time</summary>
- /// <param name="interval">The interval for which to get the peak, relative to <paramref name="relativeTo"/></param>
- /// <param name="relativeTo">The DateTime which the <paramref name="interval"/> is relative to, or DateTime.Now if not given</param>
- /// <returns>The peak execution time</returns>
- public double GetPeakExecutionTime(TimeSpan range, DateTime? relativeTo = null)
+ /// <summary>Get the peak execution time in milliseconds.</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 GetPeakExecutionTime(TimeSpan range, DateTime? endTime = null)
{
- if (this.PeakInvocations.IsEmpty)
+ if (this.PeakInvocations.Count == 0)
return 0;
- if (relativeTo == null)
- relativeTo = DateTime.UtcNow;
-
- DateTime start = relativeTo.Value.Subtract(range);
-
- var entries = this.PeakInvocations.Where(x => (x.EventTime >= start) && (x.EventTime <= relativeTo)).ToList();
+ endTime ??= DateTime.UtcNow;
+ DateTime startTime = endTime.Value.Subtract(range);
- if (!entries.Any())
- return 0;
-
- return entries.OrderByDescending(x => x.ExecutionTimeMilliseconds).First().ExecutionTimeMilliseconds;
+ return this.PeakInvocations
+ .Where(entry => entry.EventTime >= startTime && entry.EventTime <= endTime)
+ .OrderByDescending(x => x.ExecutionTimeMilliseconds)
+ .Select(p => p.ExecutionTimeMilliseconds)
+ .FirstOrDefault();
}
- /// <summary>Begins tracking the invocation of this collection.</summary>
+ /// <summary>Start tracking the invocation of this collection.</summary>
public void BeginTrackInvocation()
{
this.TriggeredPerformanceCounters.Clear();
@@ -158,60 +134,58 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
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>
+ /// <summary>End tracking the invocation of this collection, and raise an alert if needed.</summary>
public void EndTrackInvocation()
{
this.InvocationStopwatch.Stop();
- this.PeakInvocations.Put(
- new PeakEntry(this.InvocationStopwatch.Elapsed.TotalMilliseconds,
- DateTime.UtcNow,
- this.TriggeredPerformanceCounters));
-
- if (!this.EnableAlerts) return;
+ // add invocation
+ if (this.PeakInvocations.Count >= this.MaxEntries)
+ this.PeakInvocations.Pop();
+ this.PeakInvocations.Push(new PeakEntry(this.InvocationStopwatch.Elapsed.TotalMilliseconds, DateTime.UtcNow, this.TriggeredPerformanceCounters.ToArray()));
- if (this.InvocationStopwatch.Elapsed.TotalMilliseconds >= this.AlertThresholdMilliseconds)
- this.AddAlert(this.InvocationStopwatch.Elapsed.TotalMilliseconds,
- this.AlertThresholdMilliseconds, this.TriggeredPerformanceCounters);
+ // raise alert
+ if (this.EnableAlerts && this.InvocationStopwatch.Elapsed.TotalMilliseconds >= this.AlertThresholdMilliseconds)
+ this.AddAlert(this.InvocationStopwatch.Elapsed.TotalMilliseconds, this.AlertThresholdMilliseconds, this.TriggeredPerformanceCounters.ToArray());
}
- /// <summary>Adds an alert.</summary>
+ /// <summary>Add 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)
+ /// <param name="alerts">The sources involved in exceeding the threshold.</param>
+ public void AddAlert(double executionTimeMilliseconds, double thresholdMilliseconds, AlertContext[] alerts)
{
- this.PerformanceCounterManager.AddAlert(new AlertEntry(this, executionTimeMilliseconds,
- thresholdMilliseconds, alerts));
+ this.PerformanceMonitor.AddAlert(
+ new AlertEntry(this, executionTimeMilliseconds, thresholdMilliseconds, alerts)
+ );
}
- /// <summary>Adds an alert for a single AlertContext</summary>
+ /// <summary>Add an alert.</summary>
/// <param name="executionTimeMilliseconds">The execution time in milliseconds.</param>
/// <param name="thresholdMilliseconds">The configured threshold.</param>
- /// <param name="alert">The context</param>
+ /// <param name="alert">The source involved in exceeding the threshold.</param>
public void AddAlert(double executionTimeMilliseconds, double thresholdMilliseconds, AlertContext alert)
{
- this.AddAlert(executionTimeMilliseconds, thresholdMilliseconds, new List<AlertContext>() {alert});
+ this.AddAlert(executionTimeMilliseconds, thresholdMilliseconds, new[] { alert });
}
- /// <summary>Resets the calls per second counter.</summary>
+ /// <summary>Reset 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>
+ /// <summary>Reset all performance counters in this collection.</summary>
public void Reset()
{
this.PeakInvocations.Clear();
- foreach (var i in this.PerformanceCounters)
- i.Value.Reset();
+ foreach (var counter in this.PerformanceCounters)
+ counter.Value.Reset();
}
- /// <summary>Resets the performance counter for a specific source.</summary>
- /// <param name="source">The source name</param>
+ /// <summary>Reset 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)
@@ -219,15 +193,13 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
i.Value.Reset();
}
- /// <summary>Returns the average calls per second.</summary>
- /// <returns>The average calls per second.</returns>
+ /// <summary>Get the average calls per second.</summary>
public long GetAverageCallsPerSecond()
{
- long runtimeInSeconds = (long) DateTime.UtcNow.Subtract(this.CallsPerSecondStart).TotalSeconds;
-
- if (runtimeInSeconds == 0) return 0;
-
- return this.CallCount / runtimeInSeconds;
+ long runtimeInSeconds = (long)DateTime.UtcNow.Subtract(this.CallsPerSecondStart).TotalSeconds;
+ return runtimeInSeconds > 0
+ ? this.CallCount / runtimeInSeconds
+ : 0;
}
}
}
diff --git a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterEntry.cs b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterEntry.cs
index a50fce7d..a1d78fc8 100644
--- a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterEntry.cs
+++ b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterEntry.cs
@@ -2,13 +2,29 @@ using System;
namespace StardewModdingAPI.Framework.PerformanceCounter
{
- /// <summary>A single performance counter entry. Records the DateTime of the event and the elapsed millisecond.</summary>
+ /// <summary>A single performance counter entry.</summary>
internal struct PerformanceCounterEntry
{
- /// <summary>The DateTime when the entry occured.</summary>
- public DateTime EventTime;
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>When the entry occurred.</summary>
+ public DateTime EventTime { get; }
- /// <summary>The elapsed milliseconds</summary>
- public double ElapsedMilliseconds;
+ /// <summary>The elapsed milliseconds.</summary>
+ public double ElapsedMilliseconds { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="eventTime">When the entry occurred.</param>
+ /// <param name="elapsedMilliseconds">The elapsed milliseconds.</param>
+ public PerformanceCounterEntry(DateTime eventTime, double elapsedMilliseconds)
+ {
+ this.EventTime = eventTime;
+ this.ElapsedMilliseconds = elapsedMilliseconds;
+ }
}
}
diff --git a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterManager.cs b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterManager.cs
index bd964442..81e4e468 100644
--- a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterManager.cs
+++ b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterManager.cs
@@ -7,12 +7,14 @@ using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Framework.PerformanceCounter
{
- internal class PerformanceCounterManager
+ /// <summary>Tracks performance metrics.</summary>
+ internal class PerformanceMonitor
{
- public HashSet<PerformanceCounterCollection> PerformanceCounterCollections = new HashSet<PerformanceCounterCollection>();
-
+ /*********
+ ** Fields
+ *********/
/// <summary>The recorded alerts.</summary>
- private readonly List<AlertEntry> Alerts = new List<AlertEntry>();
+ private readonly IList<AlertEntry> Alerts = new List<AlertEntry>();
/// <summary>The monitor for output logging.</summary>
private readonly IMonitor Monitor;
@@ -20,62 +22,64 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
/// <summary>The invocation stopwatch.</summary>
private readonly Stopwatch InvocationStopwatch = new Stopwatch();
- /// <summary>Specifies if alerts should be paused.</summary>
+ /// <summary>The underlying performance counter collections.</summary>
+ private readonly IDictionary<string, PerformanceCounterCollection> Collections = new Dictionary<string, PerformanceCounterCollection>(StringComparer.InvariantCultureIgnoreCase);
+
+
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>Whether alerts are paused.</summary>
public bool PauseAlerts { get; set; }
- /// <summary>Specifies if performance counter tracking should be enabled.</summary>
+ /// <summary>Whether performance counter tracking is enabled.</summary>
public bool EnableTracking { get; set; }
- /// <summary>Constructs a performance counter manager.</summary>
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
/// <param name="monitor">The monitor for output logging.</param>
- public PerformanceCounterManager(IMonitor monitor)
+ public PerformanceMonitor(IMonitor monitor)
{
this.Monitor = monitor;
}
- /// <summary>Resets all performance counters in all collections.</summary>
+ /// <summary>Reset all performance counters in all collections.</summary>
public void Reset()
{
- foreach (PerformanceCounterCollection collection in this.PerformanceCounterCollections)
- {
+ foreach (PerformanceCounterCollection collection in this.Collections.Values)
collection.Reset();
- }
-
- foreach (var eventPerformanceCounter in
- this.PerformanceCounterCollections.SelectMany(performanceCounter => performanceCounter.PerformanceCounters))
- {
- eventPerformanceCounter.Value.Reset();
- }
}
- /// <summary>Begins tracking the invocation for a collection.</summary>
- /// <param name="collectionName">The collection name</param>
- public void BeginTrackInvocation(string collectionName)
+ /// <summary>Track the invocation time for a collection.</summary>
+ /// <param name="collectionName">The name of the collection.</param>
+ /// <param name="action">The action to execute and track.</param>
+ public void Track(string collectionName, Action action)
{
if (!this.EnableTracking)
{
+ action();
return;
}
- this.GetOrCreateCollectionByName(collectionName).BeginTrackInvocation();
- }
-
- /// <summary>Ends tracking the invocation for a collection.</summary>
- /// <param name="collectionName"></param>
- public void EndTrackInvocation(string collectionName)
- {
- if (!this.EnableTracking)
+ PerformanceCounterCollection collection = this.GetOrCreateCollectionByName(collectionName);
+ collection.BeginTrackInvocation();
+ try
{
- return;
+ action();
+ }
+ finally
+ {
+ collection.EndTrackInvocation();
}
-
- this.GetOrCreateCollectionByName(collectionName).EndTrackInvocation();
}
- /// <summary>Tracks a single performance counter invocation in a specific collection.</summary>
+ /// <summary>Track a single performance counter invocation in a specific collection.</summary>
/// <param name="collectionName">The name of the collection.</param>
/// <param name="sourceName">The name of the source.</param>
- /// <param name="action">The action to execute and track invocation time for.</param>
+ /// <param name="action">The action to execute and track.</param>
public void Track(string collectionName, string sourceName, Action action)
{
if (!this.EnableTracking)
@@ -84,6 +88,7 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
return;
}
+ PerformanceCounterCollection collection = this.GetOrCreateCollectionByName(collectionName);
DateTime eventTime = DateTime.UtcNow;
this.InvocationStopwatch.Reset();
this.InvocationStopwatch.Start();
@@ -95,79 +100,50 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
finally
{
this.InvocationStopwatch.Stop();
-
- this.GetOrCreateCollectionByName(collectionName).Track(sourceName, new PerformanceCounterEntry
- {
- EventTime = eventTime,
- ElapsedMilliseconds = this.InvocationStopwatch.Elapsed.TotalMilliseconds
- });
+ collection.Track(sourceName, new PerformanceCounterEntry(eventTime, this.InvocationStopwatch.Elapsed.TotalMilliseconds));
}
}
- /// <summary>Gets a collection by name.</summary>
- /// <param name="name">The name of the collection.</param>
- /// <returns>The collection or null if none was found.</returns>
- private PerformanceCounterCollection GetCollectionByName(string name)
- {
- return this.PerformanceCounterCollections.FirstOrDefault(collection => collection.Name == name);
- }
-
- /// <summary>Gets a collection by name and creates it if it doesn't exist.</summary>
- /// <param name="name">The name of the collection.</param>
- /// <returns>The collection.</returns>
- private PerformanceCounterCollection GetOrCreateCollectionByName(string name)
- {
- PerformanceCounterCollection collection = this.GetCollectionByName(name);
-
- if (collection != null) return collection;
-
- collection = new PerformanceCounterCollection(this, name);
- this.PerformanceCounterCollections.Add(collection);
-
- return collection;
- }
-
- /// <summary>Resets the performance counters for a specific collection.</summary>
+ /// <summary>Reset the performance counters for a specific collection.</summary>
/// <param name="name">The collection name.</param>
public void ResetCollection(string name)
{
- foreach (PerformanceCounterCollection performanceCounterCollection in
- this.PerformanceCounterCollections.Where(performanceCounterCollection =>
- performanceCounterCollection.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)))
+ if (this.Collections.TryGetValue(name, out PerformanceCounterCollection collection))
{
- performanceCounterCollection.ResetCallsPerSecond();
- performanceCounterCollection.Reset();
+ collection.ResetCallsPerSecond();
+ collection.Reset();
}
}
- /// <summary>Resets performance counters for a specific source.</summary>
+ /// <summary>Reset performance counters for a specific source.</summary>
/// <param name="name">The name of the source.</param>
public void ResetSource(string name)
{
- foreach (PerformanceCounterCollection performanceCounterCollection in this.PerformanceCounterCollections)
+ foreach (PerformanceCounterCollection performanceCounterCollection in this.Collections.Values)
performanceCounterCollection.ResetSource(name);
}
/// <summary>Print any queued alerts.</summary>
public void PrintQueuedAlerts()
{
- if (this.Alerts.Count == 0) return;
+ if (this.Alerts.Count == 0)
+ return;
- StringBuilder sb = new StringBuilder();
+ StringBuilder report = new StringBuilder();
foreach (AlertEntry alert in this.Alerts)
{
- sb.AppendLine($"{alert.Collection.Name} took {alert.ExecutionTimeMilliseconds:F2}ms (exceeded threshold of {alert.ThresholdMilliseconds:F2}ms)");
+ report.AppendLine($"{alert.Collection.Name} took {alert.ExecutionTimeMilliseconds:F2}ms (exceeded threshold of {alert.ThresholdMilliseconds:F2}ms)");
foreach (AlertContext context in alert.Context.OrderByDescending(p => p.Elapsed))
- sb.AppendLine(context.ToString());
+ report.AppendLine(context.ToString());
}
this.Alerts.Clear();
- this.Monitor.Log(sb.ToString(), LogLevel.Error);
+ this.Monitor.Log(report.ToString(), LogLevel.Error);
}
- /// <summary>Adds an alert to the queue.</summary>
+ /// <summary>Add an alert to the queue.</summary>
/// <param name="entry">The alert to add.</param>
public void AddAlert(AlertEntry entry)
{
@@ -175,68 +151,34 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
this.Alerts.Add(entry);
}
- /// <summary>Initialized the default performance counter collections.</summary>
+ /// <summary>Initialize the default performance counter collections.</summary>
/// <param name="eventManager">The event manager.</param>
public void InitializePerformanceCounterCollections(EventManager eventManager)
{
- this.PerformanceCounterCollections = new HashSet<PerformanceCounterCollection>()
+ foreach (IManagedEvent @event in eventManager.GetAllEvents())
+ this.Collections[@event.EventName] = new PerformanceCounterCollection(this, @event.EventName, @event.IsPerformanceCritical);
+ }
+
+ /// <summary>Get the underlying performance counters.</summary>
+ public IEnumerable<PerformanceCounterCollection> GetCollections()
+ {
+ return this.Collections.Values;
+ }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Get a collection by name and creates it if it doesn't exist.</summary>
+ /// <param name="name">The name of the collection.</param>
+ private PerformanceCounterCollection GetOrCreateCollectionByName(string name)
+ {
+ if (!this.Collections.TryGetValue(name, out PerformanceCounterCollection collection))
{
- new EventPerformanceCounterCollection(this, eventManager.MenuChanged, false),
-
- // Rendering Events
- new EventPerformanceCounterCollection(this, eventManager.Rendering, false),
- new EventPerformanceCounterCollection(this, eventManager.Rendered, true),
- new EventPerformanceCounterCollection(this, eventManager.RenderingWorld, false),
- new EventPerformanceCounterCollection(this, eventManager.RenderedWorld, true),
- new EventPerformanceCounterCollection(this, eventManager.RenderingActiveMenu, false),
- new EventPerformanceCounterCollection(this, eventManager.RenderedActiveMenu, true),
- new EventPerformanceCounterCollection(this, eventManager.RenderingHud, false),
- new EventPerformanceCounterCollection(this, eventManager.RenderedHud, true),
-
- new EventPerformanceCounterCollection(this, eventManager.WindowResized, false),
- new EventPerformanceCounterCollection(this, eventManager.GameLaunched, false),
- new EventPerformanceCounterCollection(this, eventManager.UpdateTicking, true),
- new EventPerformanceCounterCollection(this, eventManager.UpdateTicked, true),
- new EventPerformanceCounterCollection(this, eventManager.OneSecondUpdateTicking, true),
- new EventPerformanceCounterCollection(this, eventManager.OneSecondUpdateTicked, true),
-
- new EventPerformanceCounterCollection(this, eventManager.SaveCreating, false),
- new EventPerformanceCounterCollection(this, eventManager.SaveCreated, false),
- new EventPerformanceCounterCollection(this, eventManager.Saving, false),
- new EventPerformanceCounterCollection(this, eventManager.Saved, false),
-
- new EventPerformanceCounterCollection(this, eventManager.DayStarted, false),
- new EventPerformanceCounterCollection(this, eventManager.DayEnding, false),
-
- new EventPerformanceCounterCollection(this, eventManager.TimeChanged, true),
-
- new EventPerformanceCounterCollection(this, eventManager.ReturnedToTitle, false),
-
- new EventPerformanceCounterCollection(this, eventManager.ButtonPressed, true),
- new EventPerformanceCounterCollection(this, eventManager.ButtonReleased, true),
- new EventPerformanceCounterCollection(this, eventManager.CursorMoved, true),
- new EventPerformanceCounterCollection(this, eventManager.MouseWheelScrolled, true),
-
- new EventPerformanceCounterCollection(this, eventManager.PeerContextReceived, false),
- new EventPerformanceCounterCollection(this, eventManager.ModMessageReceived, false),
- new EventPerformanceCounterCollection(this, eventManager.PeerDisconnected, false),
- new EventPerformanceCounterCollection(this, eventManager.InventoryChanged, true),
- new EventPerformanceCounterCollection(this, eventManager.LevelChanged, false),
- new EventPerformanceCounterCollection(this, eventManager.Warped, false),
-
- new EventPerformanceCounterCollection(this, eventManager.LocationListChanged, false),
- new EventPerformanceCounterCollection(this, eventManager.BuildingListChanged, false),
- new EventPerformanceCounterCollection(this, eventManager.LocationListChanged, false),
- new EventPerformanceCounterCollection(this, eventManager.DebrisListChanged, true),
- new EventPerformanceCounterCollection(this, eventManager.LargeTerrainFeatureListChanged, true),
- new EventPerformanceCounterCollection(this, eventManager.NpcListChanged, false),
- new EventPerformanceCounterCollection(this, eventManager.ObjectListChanged, true),
- new EventPerformanceCounterCollection(this, eventManager.ChestInventoryChanged, true),
- new EventPerformanceCounterCollection(this, eventManager.TerrainFeatureListChanged, true),
- new EventPerformanceCounterCollection(this, eventManager.LoadStageChanged, false),
- new EventPerformanceCounterCollection(this, eventManager.UnvalidatedUpdateTicking, false),
- new EventPerformanceCounterCollection(this, eventManager.UnvalidatedUpdateTicked, false),
- };
+ collection = new PerformanceCounterCollection(this, name);
+ this.Collections[name] = collection;
+ }
+ return collection;
}
}
}