diff options
Diffstat (limited to 'src/SMAPI')
| -rw-r--r-- | src/SMAPI/Constants.cs | 3 | ||||
| -rw-r--r-- | src/SMAPI/Framework/Events/EventManager.cs | 48 | ||||
| -rw-r--r-- | src/SMAPI/Framework/Events/IManagedEvent.cs | 10 | ||||
| -rw-r--r-- | src/SMAPI/Framework/Events/ManagedEvent.cs | 71 | ||||
| -rw-r--r-- | src/SMAPI/Framework/PerformanceCounter/AlertContext.cs | 14 | ||||
| -rw-r--r-- | src/SMAPI/Framework/PerformanceCounter/AlertEntry.cs | 31 | ||||
| -rw-r--r-- | src/SMAPI/Framework/PerformanceCounter/EventPerformanceCounterCollection.cs | 16 | ||||
| -rw-r--r-- | src/SMAPI/Framework/PerformanceCounter/PeakEntry.cs | 25 | ||||
| -rw-r--r-- | src/SMAPI/Framework/PerformanceCounter/PerformanceCounter.cs | 143 | ||||
| -rw-r--r-- | src/SMAPI/Framework/PerformanceCounter/PerformanceCounterCollection.cs | 196 | ||||
| -rw-r--r-- | src/SMAPI/Framework/PerformanceCounter/PerformanceCounterEntry.cs | 26 | ||||
| -rw-r--r-- | src/SMAPI/Framework/PerformanceCounter/PerformanceCounterManager.cs | 212 | ||||
| -rw-r--r-- | src/SMAPI/Framework/SCore.cs | 15 | ||||
| -rw-r--r-- | src/SMAPI/Framework/SGame.cs | 11 | ||||
| -rw-r--r-- | src/SMAPI/Program.cs | 2 | ||||
| -rw-r--r-- | src/SMAPI/SMAPI.csproj | 5 |
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; 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> pri |
