summaryrefslogtreecommitdiff
path: root/src/SMAPI
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2020-01-26 19:49:17 -0500
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2020-01-26 19:49:17 -0500
commit22a0a32b6d959946bfd80bf0ca9796378f36e0cd (patch)
tree4dc91461e2a06148c975d12d46986a6a9648e1ff /src/SMAPI
parent1b905205a3073c56e29c46b5e57c4a9cb2ca5832 (diff)
downloadSMAPI-22a0a32b6d959946bfd80bf0ca9796378f36e0cd.tar.gz
SMAPI-22a0a32b6d959946bfd80bf0ca9796378f36e0cd.tar.bz2
SMAPI-22a0a32b6d959946bfd80bf0ca9796378f36e0cd.zip
refactor performance counter code
This commit performs some general refactoring, including... - avoid manually duplicating the event list; - rework the 'is important' event flag; - remove the new packages (Cyotek.Collections can be replaced with built-in types, and System.ValueTuple won't work in the Mono version used on Linux/Mac); - improve performance; - minor cleanup.
Diffstat (limited to 'src/SMAPI')
-rw-r--r--src/SMAPI/Constants.cs3
-rw-r--r--src/SMAPI/Framework/Events/EventManager.cs48
-rw-r--r--src/SMAPI/Framework/Events/IManagedEvent.cs10
-rw-r--r--src/SMAPI/Framework/Events/ManagedEvent.cs71
-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
-rw-r--r--src/SMAPI/Framework/SCore.cs15
-rw-r--r--src/SMAPI/Framework/SGame.cs11
-rw-r--r--src/SMAPI/Program.cs2
-rw-r--r--src/SMAPI/SMAPI.csproj5
16 files changed, 383 insertions, 445 deletions
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs
index 76cb6f89..67c7b576 100644
--- a/src/SMAPI/Constants.cs
+++ b/src/SMAPI/Constants.cs
@@ -55,7 +55,7 @@ namespace StardewModdingAPI
/// <summary>The URL of the SMAPI home page.</summary>
internal const string HomePageUrl = "https://smapi.io";
- /// <summary>The URL of the SMAPI home page.</summary>
+ /// <summary>The default performance counter name for unknown event handlers.</summary>
internal const string GamePerformanceCounterName = "<StardewValley>";
/// <summary>The absolute path to the folder containing SMAPI's internal files.</summary>
@@ -103,6 +103,7 @@ namespace StardewModdingAPI
/// <summary>The language code for non-translated mod assets.</summary>
internal static LocalizedContentManager.LanguageCode DefaultLanguage { get; } = LocalizedContentManager.LanguageCode.en;
+
/*********
** Internal methods
*********/
diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs
index 19a4dff8..50dcc9ef 100644
--- a/src/SMAPI/Framework/Events/EventManager.cs
+++ b/src/SMAPI/Framework/Events/EventManager.cs
@@ -1,4 +1,6 @@
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework.PerformanceCounter;
@@ -174,29 +176,32 @@ namespace StardewModdingAPI.Framework.Events
/// <summary>Construct an instance.</summary>
/// <param name="monitor">Writes messages to the log.</param>
/// <param name="modRegistry">The mod registry with which to identify mods.</param>
- /// <param name="performanceCounterManager">The performance counter manager.</param>
- public EventManager(IMonitor monitor, ModRegistry modRegistry, PerformanceCounterManager performanceCounterManager)
+ /// <param name="performanceMonitor">Tracks performance metrics.</param>
+ public EventManager(IMonitor monitor, ModRegistry modRegistry, PerformanceMonitor performanceMonitor)
{
// create shortcut initializers
- ManagedEvent<TEventArgs> ManageEventOf<TEventArgs>(string typeName, string eventName) => new ManagedEvent<TEventArgs>($"{typeName}.{eventName}", monitor, modRegistry, performanceCounterManager);
+ ManagedEvent<TEventArgs> ManageEventOf<TEventArgs>(string typeName, string eventName, bool isPerformanceCritical = false)
+ {
+ return new ManagedEvent<TEventArgs>($"{typeName}.{eventName}", monitor, modRegistry, performanceMonitor, isPerformanceCritical);
+ }
// init events (new)
this.MenuChanged = ManageEventOf<MenuChangedEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.MenuChanged));
- this.Rendering = ManageEventOf<RenderingEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendering));
- this.Rendered = ManageEventOf<RenderedEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendered));
- this.RenderingWorld = ManageEventOf<RenderingWorldEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingWorld));
- this.RenderedWorld = ManageEventOf<RenderedWorldEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedWorld));
- this.RenderingActiveMenu = ManageEventOf<RenderingActiveMenuEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingActiveMenu));
- this.RenderedActiveMenu = ManageEventOf<RenderedActiveMenuEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedActiveMenu));
- this.RenderingHud = ManageEventOf<RenderingHudEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingHud));
- this.RenderedHud = ManageEventOf<RenderedHudEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedHud));
+ this.Rendering = ManageEventOf<RenderingEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendering), isPerformanceCritical: true);
+ this.Rendered = ManageEventOf<RenderedEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendered), isPerformanceCritical: true);
+ this.RenderingWorld = ManageEventOf<RenderingWorldEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingWorld), isPerformanceCritical: true);
+ this.RenderedWorld = ManageEventOf<RenderedWorldEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedWorld), isPerformanceCritical: true);
+ this.RenderingActiveMenu = ManageEventOf<RenderingActiveMenuEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingActiveMenu), isPerformanceCritical: true);
+ this.RenderedActiveMenu = ManageEventOf<RenderedActiveMenuEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedActiveMenu), isPerformanceCritical: true);
+ this.RenderingHud = ManageEventOf<RenderingHudEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingHud), isPerformanceCritical: true);
+ this.RenderedHud = ManageEventOf<RenderedHudEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedHud), isPerformanceCritical: true);
this.WindowResized = ManageEventOf<WindowResizedEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.WindowResized));
this.GameLaunched = ManageEventOf<GameLaunchedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.GameLaunched));
- this.UpdateTicking = ManageEventOf<UpdateTickingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicking));
- this.UpdateTicked = ManageEventOf<UpdateTickedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicked));
- this.OneSecondUpdateTicking = ManageEventOf<OneSecondUpdateTickingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicking));
- this.OneSecondUpdateTicked = ManageEventOf<OneSecondUpdateTickedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicked));
+ this.UpdateTicking = ManageEventOf<UpdateTickingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicking), isPerformanceCritical: true);
+ this.UpdateTicked = ManageEventOf<UpdateTickedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicked), isPerformanceCritical: true);
+ this.OneSecondUpdateTicking = ManageEventOf<OneSecondUpdateTickingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicking), isPerformanceCritical: true);
+ this.OneSecondUpdateTicked = ManageEventOf<OneSecondUpdateTickedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicked), isPerformanceCritical: true);
this.SaveCreating = ManageEventOf<SaveCreatingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.SaveCreating));
this.SaveCreated = ManageEventOf<SaveCreatedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.SaveCreated));
this.Saving = ManageEventOf<SavingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.Saving));
@@ -209,7 +214,7 @@ namespace StardewModdingAPI.Framework.Events
this.ButtonPressed = ManageEventOf<ButtonPressedEventArgs>(nameof(IModEvents.Input), nameof(IInputEvents.ButtonPressed));
this.ButtonReleased = ManageEventOf<ButtonReleasedEventArgs>(nameof(IModEvents.Input), nameof(IInputEvents.ButtonReleased));
- this.CursorMoved = ManageEventOf<CursorMovedEventArgs>(nameof(IModEvents.Input), nameof(IInputEvents.CursorMoved));
+ this.CursorMoved = ManageEventOf<CursorMovedEventArgs>(nameof(IModEvents.Input), nameof(IInputEvents.CursorMoved), isPerformanceCritical: true);
this.MouseWheelScrolled = ManageEventOf<MouseWheelScrolledEventArgs>(nameof(IModEvents.Input), nameof(IInputEvents.MouseWheelScrolled));
this.PeerContextReceived = ManageEventOf<PeerContextReceivedEventArgs>(nameof(IModEvents.Multiplayer), nameof(IMultiplayerEvents.PeerContextReceived));
@@ -230,8 +235,15 @@ namespace StardewModdingAPI.Framework.Events
this.TerrainFeatureListChanged = ManageEventOf<TerrainFeatureListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.TerrainFeatureListChanged));
this.LoadStageChanged = ManageEventOf<LoadStageChangedEventArgs>(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.LoadStageChanged));
- this.UnvalidatedUpdateTicking = ManageEventOf<UnvalidatedUpdateTickingEventArgs>(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.UnvalidatedUpdateTicking));
- this.UnvalidatedUpdateTicked = ManageEventOf<UnvalidatedUpdateTickedEventArgs>(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.UnvalidatedUpdateTicked));
+ this.UnvalidatedUpdateTicking = ManageEventOf<UnvalidatedUpdateTickingEventArgs>(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.UnvalidatedUpdateTicking), isPerformanceCritical: true);
+ this.UnvalidatedUpdateTicked = ManageEventOf<UnvalidatedUpdateTickedEventArgs>(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.UnvalidatedUpdateTicked), isPerformanceCritical: true);
+ }
+
+ /// <summary>Get all managed events.</summary>
+ public IEnumerable<IManagedEvent> GetAllEvents()
+ {
+ foreach (FieldInfo field in this.GetType().GetFields())
+ yield return (IManagedEvent)field.GetValue(this);
}
}
}
diff --git a/src/SMAPI/Framework/Events/IManagedEvent.cs b/src/SMAPI/Framework/Events/IManagedEvent.cs
index 04476866..e4e3ca08 100644
--- a/src/SMAPI/Framework/Events/IManagedEvent.cs
+++ b/src/SMAPI/Framework/Events/IManagedEvent.cs
@@ -1,7 +1,15 @@
namespace StardewModdingAPI.Framework.Events
{
+ /// <summary>Metadata for an event raised by SMAPI.</summary>
internal interface IManagedEvent
{
- string GetName();
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>A human-readable name for the event.</summary>
+ string EventName { get; }
+
+ /// <summary>Whether the event is typically called at least once per second.</summary>
+ bool IsPerformanceCritical { get; }
}
}
diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs
index dfdd7449..60e5c599 100644
--- a/src/SMAPI/Framework/Events/ManagedEvent.cs
+++ b/src/SMAPI/Framework/Events/ManagedEvent.cs
@@ -1,13 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using PerformanceCounterManager = StardewModdingAPI.Framework.PerformanceCounter.PerformanceCounterManager;
+using StardewModdingAPI.Framework.PerformanceCounter;
namespace StardewModdingAPI.Framework.Events
{
/// <summary>An event wrapper which intercepts and logs errors in handler code.</summary>
/// <typeparam name="TEventArgs">The event arguments type.</typeparam>
- internal class ManagedEvent<TEventArgs>: IManagedEvent
+ internal class ManagedEvent<TEventArgs> : IManagedEvent
{
/*********
** Fields
@@ -15,9 +15,6 @@ namespace StardewModdingAPI.Framework.Events
/// <summary>The underlying event.</summary>
private event EventHandler<TEventArgs> Event;
- /// <summary>A human-readable name for the event.</summary>
- private readonly string EventName;
-
/// <summary>Writes messages to the log.</summary>
private readonly IMonitor Monitor;
@@ -30,8 +27,19 @@ namespace StardewModdingAPI.Framework.Events
/// <summary>The cached invocation list.</summary>
private EventHandler<TEventArgs>[] CachedInvocationList;
- /// <summary>The performance counter manager.</summary>
- private readonly PerformanceCounterManager PerformanceCounterManager;
+ /// <summary>Tracks performance metrics.</summary>
+ private readonly PerformanceMonitor PerformanceMonitor;
+
+
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>A human-readable name for the event.</summary>
+ public string EventName { get; }
+
+ /// <summary>Whether the event is typically called at least once per second.</summary>
+ public bool IsPerformanceCritical { get; }
+
/*********
** Public methods
@@ -40,19 +48,15 @@ namespace StardewModdingAPI.Framework.Events
/// <param name="eventName">A human-readable name for the event.</param>
/// <param name="monitor">Writes messages to the log.</param>
/// <param name="modRegistry">The mod registry with which to identify mods.</param>
- /// <param name="performanceCounterManager">The performance counter manager</param>
- public ManagedEvent(string eventName, IMonitor monitor, ModRegistry modRegistry, PerformanceCounterManager performanceCounterManager)
+ /// <param name="performanceMonitor">Tracks performance metrics.</param>
+ /// <param name="isPerformanceCritical">Whether the event is typically called at least once per second.</param>
+ public ManagedEvent(string eventName, IMonitor monitor, ModRegistry modRegistry, PerformanceMonitor performanceMonitor, bool isPerformanceCritical = false)
{
this.EventName = eventName;
this.Monitor = monitor;
this.ModRegistry = modRegistry;
- this.PerformanceCounterManager = performanceCounterManager;
- }
-
- /// <summary>Gets the event name.</summary>
- public string GetName()
- {
- return this.EventName;
+ this.PerformanceMonitor = performanceMonitor;
+ this.IsPerformanceCritical = isPerformanceCritical;
}
/// <summary>Get whether anything is listening to the event.</summary>
@@ -93,22 +97,20 @@ namespace StardewModdingAPI.Framework.Events
return;
- this.PerformanceCounterManager.BeginTrackInvocation(this.EventName);
-
- foreach (EventHandler<TEventArgs> handler in this.CachedInvocationList)
+ this.PerformanceMonitor.Track(this.EventName, () =>
{
- try
- {
- this.PerformanceCounterManager.Track(this.EventName, this.GetModNameForPerformanceCounters(handler),
- () => handler.Invoke(null, args));
- }
- catch (Exception ex)
+ foreach (EventHandler<TEventArgs> handler in this.CachedInvocationList)
{
- this.LogError(handler, ex);
+ try
+ {
+ this.PerformanceMonitor.Track(this.EventName, this.GetModNameForPerformanceCounters(handler), () => handler.Invoke(null, args));
+ }
+ catch (Exception ex)
+ {
+ this.LogError(handler, ex);
+ }
}
- }
-
- this.PerformanceCounterManager.EndTrackInvocation(this.EventName);
+ });
}
/// <summary>Raise the event and notify all handlers.</summary>
@@ -139,18 +141,17 @@ namespace StardewModdingAPI.Framework.Events
/*********
** Private methods
*********/
-
+ /// <summary>Get the mod name for a given event handler to display in performance monitoring reports.</summary>
+ /// <param name="handler">The event handler.</param>
private string GetModNameForPerformanceCounters(EventHandler<TEventArgs> handler)
{
IModMetadata mod = this.GetSourceMod(handler);
-
if (mod == null)
- {
return Constants.GamePerformanceCounterName;
- }
-
- return mod.HasManifest() ? mod.Manifest.UniqueID : mod.DisplayName;
+ return mod.HasManifest()
+ ? mod.Manifest.UniqueID
+ : mod.DisplayName;
}
/// <summary>Track an event handler.</summary>
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;