From 22a0a32b6d959946bfd80bf0ca9796378f36e0cd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 26 Jan 2020 19:49:17 -0500 Subject: 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. --- src/SMAPI/Constants.cs | 3 +- src/SMAPI/Framework/Events/EventManager.cs | 48 +++-- src/SMAPI/Framework/Events/IManagedEvent.cs | 10 +- src/SMAPI/Framework/Events/ManagedEvent.cs | 71 +++---- .../Framework/PerformanceCounter/AlertContext.cs | 14 +- .../Framework/PerformanceCounter/AlertEntry.cs | 31 +-- .../EventPerformanceCounterCollection.cs | 16 -- .../Framework/PerformanceCounter/PeakEntry.cs | 25 ++- .../PerformanceCounter/PerformanceCounter.cs | 143 ++++++-------- .../PerformanceCounterCollection.cs | 196 ++++++++----------- .../PerformanceCounter/PerformanceCounterEntry.cs | 26 ++- .../PerformanceCounterManager.cs | 212 ++++++++------------- src/SMAPI/Framework/SCore.cs | 15 +- src/SMAPI/Framework/SGame.cs | 11 +- src/SMAPI/Program.cs | 2 +- src/SMAPI/SMAPI.csproj | 5 - 16 files changed, 383 insertions(+), 445 deletions(-) delete mode 100644 src/SMAPI/Framework/PerformanceCounter/EventPerformanceCounterCollection.cs (limited to 'src/SMAPI') 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 /// The URL of the SMAPI home page. internal const string HomePageUrl = "https://smapi.io"; - /// The URL of the SMAPI home page. + /// The default performance counter name for unknown event handlers. internal const string GamePerformanceCounterName = ""; /// The absolute path to the folder containing SMAPI's internal files. @@ -103,6 +103,7 @@ namespace StardewModdingAPI /// The language code for non-translated mod assets. 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 /// Construct an instance. /// Writes messages to the log. /// The mod registry with which to identify mods. - /// The performance counter manager. - public EventManager(IMonitor monitor, ModRegistry modRegistry, PerformanceCounterManager performanceCounterManager) + /// Tracks performance metrics. + public EventManager(IMonitor monitor, ModRegistry modRegistry, PerformanceMonitor performanceMonitor) { // create shortcut initializers - ManagedEvent ManageEventOf(string typeName, string eventName) => new ManagedEvent($"{typeName}.{eventName}", monitor, modRegistry, performanceCounterManager); + ManagedEvent ManageEventOf(string typeName, string eventName, bool isPerformanceCritical = false) + { + return new ManagedEvent($"{typeName}.{eventName}", monitor, modRegistry, performanceMonitor, isPerformanceCritical); + } // init events (new) this.MenuChanged = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.MenuChanged)); - this.Rendering = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendering)); - this.Rendered = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendered)); - this.RenderingWorld = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingWorld)); - this.RenderedWorld = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedWorld)); - this.RenderingActiveMenu = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingActiveMenu)); - this.RenderedActiveMenu = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedActiveMenu)); - this.RenderingHud = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingHud)); - this.RenderedHud = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedHud)); + this.Rendering = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendering), isPerformanceCritical: true); + this.Rendered = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendered), isPerformanceCritical: true); + this.RenderingWorld = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingWorld), isPerformanceCritical: true); + this.RenderedWorld = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedWorld), isPerformanceCritical: true); + this.RenderingActiveMenu = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingActiveMenu), isPerformanceCritical: true); + this.RenderedActiveMenu = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedActiveMenu), isPerformanceCritical: true); + this.RenderingHud = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingHud), isPerformanceCritical: true); + this.RenderedHud = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedHud), isPerformanceCritical: true); this.WindowResized = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.WindowResized)); this.GameLaunched = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.GameLaunched)); - this.UpdateTicking = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicking)); - this.UpdateTicked = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicked)); - this.OneSecondUpdateTicking = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicking)); - this.OneSecondUpdateTicked = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicked)); + this.UpdateTicking = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicking), isPerformanceCritical: true); + this.UpdateTicked = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicked), isPerformanceCritical: true); + this.OneSecondUpdateTicking = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicking), isPerformanceCritical: true); + this.OneSecondUpdateTicked = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicked), isPerformanceCritical: true); this.SaveCreating = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.SaveCreating)); this.SaveCreated = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.SaveCreated)); this.Saving = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.Saving)); @@ -209,7 +214,7 @@ namespace StardewModdingAPI.Framework.Events this.ButtonPressed = ManageEventOf(nameof(IModEvents.Input), nameof(IInputEvents.ButtonPressed)); this.ButtonReleased = ManageEventOf(nameof(IModEvents.Input), nameof(IInputEvents.ButtonReleased)); - this.CursorMoved = ManageEventOf(nameof(IModEvents.Input), nameof(IInputEvents.CursorMoved)); + this.CursorMoved = ManageEventOf(nameof(IModEvents.Input), nameof(IInputEvents.CursorMoved), isPerformanceCritical: true); this.MouseWheelScrolled = ManageEventOf(nameof(IModEvents.Input), nameof(IInputEvents.MouseWheelScrolled)); this.PeerContextReceived = ManageEventOf(nameof(IModEvents.Multiplayer), nameof(IMultiplayerEvents.PeerContextReceived)); @@ -230,8 +235,15 @@ namespace StardewModdingAPI.Framework.Events this.TerrainFeatureListChanged = ManageEventOf(nameof(IModEvents.World), nameof(IWorldEvents.TerrainFeatureListChanged)); this.LoadStageChanged = ManageEventOf(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.LoadStageChanged)); - this.UnvalidatedUpdateTicking = ManageEventOf(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.UnvalidatedUpdateTicking)); - this.UnvalidatedUpdateTicked = ManageEventOf(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.UnvalidatedUpdateTicked)); + this.UnvalidatedUpdateTicking = ManageEventOf(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.UnvalidatedUpdateTicking), isPerformanceCritical: true); + this.UnvalidatedUpdateTicked = ManageEventOf(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.UnvalidatedUpdateTicked), isPerformanceCritical: true); + } + + /// Get all managed events. + public IEnumerable 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 { + /// Metadata for an event raised by SMAPI. internal interface IManagedEvent { - string GetName(); + /********* + ** Accessors + *********/ + /// A human-readable name for the event. + string EventName { get; } + + /// Whether the event is typically called at least once per second. + 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 { /// An event wrapper which intercepts and logs errors in handler code. /// The event arguments type. - internal class ManagedEvent: IManagedEvent + internal class ManagedEvent : IManagedEvent { /********* ** Fields @@ -15,9 +15,6 @@ namespace StardewModdingAPI.Framework.Events /// The underlying event. private event EventHandler Event; - /// A human-readable name for the event. - private readonly string EventName; - /// Writes messages to the log. private readonly IMonitor Monitor; @@ -30,8 +27,19 @@ namespace StardewModdingAPI.Framework.Events /// The cached invocation list. private EventHandler[] CachedInvocationList; - /// The performance counter manager. - private readonly PerformanceCounterManager PerformanceCounterManager; + /// Tracks performance metrics. + private readonly PerformanceMonitor PerformanceMonitor; + + + /********* + ** Accessors + *********/ + /// A human-readable name for the event. + public string EventName { get; } + + /// Whether the event is typically called at least once per second. + public bool IsPerformanceCritical { get; } + /********* ** Public methods @@ -40,19 +48,15 @@ namespace StardewModdingAPI.Framework.Events /// A human-readable name for the event. /// Writes messages to the log. /// The mod registry with which to identify mods. - /// The performance counter manager - public ManagedEvent(string eventName, IMonitor monitor, ModRegistry modRegistry, PerformanceCounterManager performanceCounterManager) + /// Tracks performance metrics. + /// Whether the event is typically called at least once per second. + 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; - } - - /// Gets the event name. - public string GetName() - { - return this.EventName; + this.PerformanceMonitor = performanceMonitor; + this.IsPerformanceCritical = isPerformanceCritical; } /// Get whether anything is listening to the event. @@ -93,22 +97,20 @@ namespace StardewModdingAPI.Framework.Events return; - this.PerformanceCounterManager.BeginTrackInvocation(this.EventName); - - foreach (EventHandler 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 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); + }); } /// Raise the event and notify all handlers. @@ -139,18 +141,17 @@ namespace StardewModdingAPI.Framework.Events /********* ** Private methods *********/ - + /// Get the mod name for a given event handler to display in performance monitoring reports. + /// The event handler. private string GetModNameForPerformanceCounters(EventHandler 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; } /// Track an event handler. 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 /// The context for an alert. internal struct AlertContext { + /********* + ** Accessors + *********/ /// The source which triggered the alert. - public readonly string Source; + public string Source { get; } /// The elapsed milliseconds. - public readonly double Elapsed; + public double Elapsed { get; } - /// Creates a new alert context. + + /********* + ** Public methods + *********/ + /// Construct an instance. /// The source which triggered the alert. /// The elapsed milliseconds. public AlertContext(string source, double elapsed) @@ -18,6 +25,7 @@ namespace StardewModdingAPI.Framework.PerformanceCounter this.Elapsed = elapsed; } + /// Get a human-readable text form of this instance. 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 { /// A single alert entry. internal struct AlertEntry { + /********* + ** Accessors + *********/ /// The collection in which the alert occurred. - public readonly PerformanceCounterCollection Collection; + public PerformanceCounterCollection Collection { get; } /// The actual execution time in milliseconds. - public readonly double ExecutionTimeMilliseconds; + public double ExecutionTimeMilliseconds { get; } + + /// The configured alert threshold in milliseconds. + public double ThresholdMilliseconds { get; } - /// The configured alert threshold. - public readonly double ThresholdMilliseconds; + /// The sources involved in exceeding the threshold. + public AlertContext[] Context { get; } - /// The context list, which records all sources involved in exceeding the threshold. - public readonly List Context; - /// Creates a new alert entry. - /// The source collection in which the alert occurred. + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The collection in which the alert occurred. /// The actual execution time in milliseconds. - /// The configured threshold in milliseconds. - /// A list of AlertContext to record which sources were involved - public AlertEntry(PerformanceCounterCollection collection, double executionTimeMilliseconds, double thresholdMilliseconds, List context) + /// The configured alert threshold in milliseconds. + /// The sources involved in exceeding the threshold. + 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 -{ - /// Represents a performance counter collection specific to game events. - internal class EventPerformanceCounterCollection: PerformanceCounterCollection - { - /// Creates a new event performance counter collection. - /// The performance counter manager. - /// The ManagedEvent. - /// If the event is flagged as important. - 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 { + /// A peak invocation time. internal struct PeakEntry { + /********* + ** Accessors + *********/ /// The actual execution time in milliseconds. - public readonly double ExecutionTimeMilliseconds; + public double ExecutionTimeMilliseconds { get; } - /// The DateTime when the entry occured. - public DateTime EventTime; + /// When the entry occurred. + public DateTime EventTime { get; } - /// The context list, which records all sources involved in exceeding the threshold. - public readonly List Context; + /// The sources involved in exceeding the threshold. + public AlertContext[] Context { get; } - public PeakEntry(double executionTimeMilliseconds, DateTime eventTime, List context) + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The actual execution time in milliseconds. + /// When the entry occurred. + /// The sources involved in exceeding the threshold. + 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 { + /// Tracks metadata about a particular code event. internal class PerformanceCounter { + /********* + ** Fields + *********/ /// The size of the ring buffer. - private const int MAX_ENTRIES = 16384; + private readonly int MaxEntries = 16384; /// The collection to which this performance counter belongs. private readonly PerformanceCounterCollection ParentCollection; - /// The circular buffer which stores all performance counter entries - private readonly CircularBuffer _counter; + /// The performance counter entries. + private readonly Stack Entries; - /// The peak execution time + /// The entry with the highest execution time. private PerformanceCounterEntry? PeakPerformanceCounterEntry; + + /********* + ** Accessors + *********/ /// The name of the source. public string Source { get; } @@ -27,118 +36,90 @@ namespace StardewModdingAPI.Framework.PerformanceCounter /// If alerting is enabled or not public bool EnableAlerts { get; set; } + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The collection to which this performance counter belongs. + /// The name of the source. public PerformanceCounter(PerformanceCounterCollection parentCollection, string source) { this.ParentCollection = parentCollection; this.Source = source; - this._counter = new CircularBuffer(PerformanceCounter.MAX_ENTRIES); + this.Entries = new Stack(this.MaxEntries); } - /// 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. + /// Add a performance counter entry to the list, update monitoring, and raise alerts if needed. /// The entry to add. 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)); } - /// Clears all performance counter entries and resets the peak entry. + /// Clear all performance counter entries and monitoring. public void Reset() { - this._counter.Clear(); + this.Entries.Clear(); this.PeakPerformanceCounterEntry = null; } - /// Returns the peak entry. - /// The peak entry. + /// Get the peak entry. public PerformanceCounterEntry? GetPeak() { return this.PeakPerformanceCounterEntry; } - /// Returns the peak entry. - /// The peak entry. - public PerformanceCounterEntry? GetPeak(TimeSpan range, DateTime? relativeTo = null) + /// Get the entry with the highest execution time. + /// The time range to search. + /// The end time for the , or null for the current time. + 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(); } - /// Resets the peak entry. - public void ResetPeak() - { - this.PeakPerformanceCounterEntry = null; - } - - /// Returns the last entry added to the list. - /// The last entry + /// Get the last entry added to the list. public PerformanceCounterEntry? GetLastEntry() { - if (this._counter.IsEmpty) + if (this.Entries.Count == 0) return null; - return this._counter.PeekLast(); + return this.Entries.Peek(); } - /// Returns the average execution time of all entries. - /// The average execution time in milliseconds. - public double GetAverage() + /// Get the average over a given time span. + /// The time range to search. + /// The end time for the , or null for the current time. + public double GetAverage(TimeSpan range, DateTime? endTime = null) { - if (this._counter.IsEmpty) - return 0; - - return this._counter.Average(p => p.ElapsedMilliseconds); - } - - /// Returns the average over a given time span. - /// The time range to retrieve. - /// The DateTime from which to start the average. Defaults to DateTime.UtcNow if null - /// The average execution time in milliseconds. - /// - /// 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. - /// - 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 { - /// The size of the ring buffer. - private const int MAX_ENTRIES = 16384; + /********* + ** Fields + *********/ + /// The number of peak invocations to keep. + private readonly int MaxEntries = 16384; - /// The list of triggered performance counters. + /// The sources involved in exceeding alert thresholds. private readonly List TriggeredPerformanceCounters = new List(); /// The stopwatch used to track the invocation time. private readonly Stopwatch InvocationStopwatch = new Stopwatch(); /// The performance counter manager. - private readonly PerformanceCounterManager PerformanceCounterManager; + private readonly PerformanceMonitor PerformanceMonitor; - /// Holds the time to calculate the average calls per second. + /// The time to calculate average calls per second. private DateTime CallsPerSecondStart = DateTime.UtcNow; - /// The number of invocations of this collection. + /// The number of invocations. private long CallCount; - /// The circular buffer which stores all peak invocations - private readonly CircularBuffer PeakInvocations; + /// The peak invocations. + private readonly Stack PeakInvocations; + + /********* + ** Accessors + *********/ /// The associated performance counters. public IDictionary PerformanceCounters { get; } = new Dictionary(); /// The name of this collection. public string Name { get; } - /// Flag if this collection is important (used for the console summary command). - public bool IsImportant { get; } + /// Whether the source is typically invoked at least once per second. + public bool IsPerformanceCritical { get; } /// The alert threshold in milliseconds. public double AlertThresholdMilliseconds { get; set; } - /// If alerting is enabled or not + /// Whether alerts are enabled. public bool EnableAlerts { get; set; } - public PerformanceCounterCollection(PerformanceCounterManager performanceCounterManager, string name, bool isImportant) + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The performance counter manager. + /// The name of this collection. + /// Whether the source is typically invoked at least once per second. + public PerformanceCounterCollection(PerformanceMonitor performanceMonitor, string name, bool isPerformanceCritical = false) { - this.PeakInvocations = new CircularBuffer(PerformanceCounterCollection.MAX_ENTRIES); + this.PeakInvocations = new Stack(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(PerformanceCounterCollection.MAX_ENTRIES); - this.PerformanceCounterManager = performanceCounterManager; - this.Name = name; - } - - /// Tracks a single invocation for a named source. + /// Track a single invocation for a named source. /// The name of the source. /// The entry. 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)); } - /// Returns the average execution time for all non-game internal sources. - /// The average execution time in milliseconds - public double GetModsAverageExecutionTime() - { - return this.PerformanceCounters.Where(p => - p.Key != Constants.GamePerformanceCounterName).Sum(p => p.Value.GetAverage()); - } - - /// Returns the average execution time for all non-game internal sources. + /// Get the average execution time for all non-game internal sources in milliseconds. /// The interval for which to get the average, relative to now - /// The average execution time in milliseconds public double GetModsAverageExecutionTime(TimeSpan interval) { - return this.PerformanceCounters.Where(p => - p.Key != Constants.GamePerformanceCounterName).Sum(p => p.Value.GetAverage(interval)); - } - - /// Returns the overall average execution time. - /// The average execution time in milliseconds - 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)); } - /// Returns the overall average execution time. + /// Get the overall average execution time in milliseconds. /// The interval for which to get the average, relative to now - /// The average execution time in milliseconds public double GetAverageExecutionTime(TimeSpan interval) { - return this.PerformanceCounters.Sum(p => p.Value.GetAverage(interval)); + return this.PerformanceCounters + .Sum(entry => entry.Value.GetAverage(interval)); } - /// Returns the average execution time for game-internal sources. - /// The average execution time in milliseconds - public double GetGameAverageExecutionTime() - { - if (this.PerformanceCounters.TryGetValue(Constants.GamePerformanceCounterName, out PerformanceCounter gameExecTime)) - return gameExecTime.GetAverage(); - - return 0; - } - - /// Returns the average execution time for game-internal sources. - /// The average execution time in milliseconds + /// Get the average execution time for game-internal sources in milliseconds. 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; } - /// Returns the peak execution time - /// The interval for which to get the peak, relative to - /// The DateTime which the is relative to, or DateTime.Now if not given - /// The peak execution time - public double GetPeakExecutionTime(TimeSpan range, DateTime? relativeTo = null) + /// Get the peak execution time in milliseconds. + /// The time range to search. + /// The end time for the , or null for the current time. + 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(); } - /// Begins tracking the invocation of this collection. + /// Start tracking the invocation of this collection. public void BeginTrackInvocation() { this.TriggeredPerformanceCounters.Clear(); @@ -158,60 +134,58 @@ namespace StardewModdingAPI.Framework.PerformanceCounter this.CallCount++; } - /// Ends tracking the invocation of this collection. Also records an alert if alerting is enabled - /// and the invocation time exceeds the threshold. + /// End tracking the invocation of this collection, and raise an alert if needed. 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()); } - /// Adds an alert. + /// Add an alert. /// The execution time in milliseconds. /// The configured threshold. - /// The list of alert contexts. - public void AddAlert(double executionTimeMilliseconds, double thresholdMilliseconds, List alerts) + /// The sources involved in exceeding the threshold. + 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) + ); } - /// Adds an alert for a single AlertContext + /// Add an alert. /// The execution time in milliseconds. /// The configured threshold. - /// The context + /// The source involved in exceeding the threshold. public void AddAlert(double executionTimeMilliseconds, double thresholdMilliseconds, AlertContext alert) { - this.AddAlert(executionTimeMilliseconds, thresholdMilliseconds, new List() {alert}); + this.AddAlert(executionTimeMilliseconds, thresholdMilliseconds, new[] { alert }); } - /// Resets the calls per second counter. + /// Reset the calls per second counter. public void ResetCallsPerSecond() { this.CallCount = 0; this.CallsPerSecondStart = DateTime.UtcNow; } - /// Resets all performance counters in this collection. + /// Reset all performance counters in this collection. public void Reset() { this.PeakInvocations.Clear(); - foreach (var i in this.PerformanceCounters) - i.Value.Reset(); + foreach (var counter in this.PerformanceCounters) + counter.Value.Reset(); } - /// Resets the performance counter for a specific source. - /// The source name + /// Reset the performance counter for a specific source. + /// The source name. public void ResetSource(string source) { foreach (var i in this.PerformanceCounters) @@ -219,15 +193,13 @@ namespace StardewModdingAPI.Framework.PerformanceCounter i.Value.Reset(); } - /// Returns the average calls per second. - /// The average calls per second. + /// Get the average calls per second. 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 { - /// A single performance counter entry. Records the DateTime of the event and the elapsed millisecond. + /// A single performance counter entry. internal struct PerformanceCounterEntry { - /// The DateTime when the entry occured. - public DateTime EventTime; + /********* + ** Accessors + *********/ + /// When the entry occurred. + public DateTime EventTime { get; } - /// The elapsed milliseconds - public double ElapsedMilliseconds; + /// The elapsed milliseconds. + public double ElapsedMilliseconds { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// When the entry occurred. + /// The elapsed milliseconds. + 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 + /// Tracks performance metrics. + internal class PerformanceMonitor { - public HashSet PerformanceCounterCollections = new HashSet(); - + /********* + ** Fields + *********/ /// The recorded alerts. - private readonly List Alerts = new List(); + private readonly IList Alerts = new List(); /// The monitor for output logging. private readonly IMonitor Monitor; @@ -20,62 +22,64 @@ namespace StardewModdingAPI.Framework.PerformanceCounter /// The invocation stopwatch. private readonly Stopwatch InvocationStopwatch = new Stopwatch(); - /// Specifies if alerts should be paused. + /// The underlying performance counter collections. + private readonly IDictionary Collections = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + + /********* + ** Accessors + *********/ + /// Whether alerts are paused. public bool PauseAlerts { get; set; } - /// Specifies if performance counter tracking should be enabled. + /// Whether performance counter tracking is enabled. public bool EnableTracking { get; set; } - /// Constructs a performance counter manager. + + /********* + ** Public methods + *********/ + /// Construct an instance. /// The monitor for output logging. - public PerformanceCounterManager(IMonitor monitor) + public PerformanceMonitor(IMonitor monitor) { this.Monitor = monitor; } - /// Resets all performance counters in all collections. + /// Reset all performance counters in all collections. 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(); - } } - /// Begins tracking the invocation for a collection. - /// The collection name - public void BeginTrackInvocation(string collectionName) + /// Track the invocation time for a collection. + /// The name of the collection. + /// The action to execute and track. + public void Track(string collectionName, Action action) { if (!this.EnableTracking) { + action(); return; } - this.GetOrCreateCollectionByName(collectionName).BeginTrackInvocation(); - } - - /// Ends tracking the invocation for a collection. - /// - 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(); } - /// Tracks a single performance counter invocation in a specific collection. + /// Track a single performance counter invocation in a specific collection. /// The name of the collection. /// The name of the source. - /// The action to execute and track invocation time for. + /// The action to execute and track. 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)); } } - /// Gets a collection by name. - /// The name of the collection. - /// The collection or null if none was found. - private PerformanceCounterCollection GetCollectionByName(string name) - { - return this.PerformanceCounterCollections.FirstOrDefault(collection => collection.Name == name); - } - - /// Gets a collection by name and creates it if it doesn't exist. - /// The name of the collection. - /// The collection. - 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; - } - - /// Resets the performance counters for a specific collection. + /// Reset the performance counters for a specific collection. /// The collection name. 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(); } } - /// Resets performance counters for a specific source. + /// Reset performance counters for a specific source. /// The name of the source. public void ResetSource(string name) { - foreach (PerformanceCounterCollection performanceCounterCollection in this.PerformanceCounterCollections) + foreach (PerformanceCounterCollection performanceCounterCollection in this.Collections.Values) performanceCounterCollection.ResetSource(name); } /// Print any queued alerts. 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); } - /// Adds an alert to the queue. + /// Add an alert to the queue. /// The alert to add. public void AddAlert(AlertEntry entry) { @@ -175,68 +151,34 @@ namespace StardewModdingAPI.Framework.PerformanceCounter this.Alerts.Add(entry); } - /// Initialized the default performance counter collections. + /// Initialize the default performance counter collections. /// The event manager. public void InitializePerformanceCounterCollections(EventManager eventManager) { - this.PerformanceCounterCollections = new HashSet() + foreach (IManagedEvent @event in eventManager.GetAllEvents()) + this.Collections[@event.EventName] = new PerformanceCounterCollection(this, @event.EventName, @event.IsPerformanceCritical); + } + + /// Get the underlying performance counters. + public IEnumerable GetCollections() + { + return this.Collections.Values; + } + + + /********* + ** Public methods + *********/ + /// Get a collection by name and creates it if it doesn't exist. + /// The name of the collection. + 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; } } } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index af7513e3..ac89587e 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -23,6 +23,7 @@ using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Framework.ModHelpers; using StardewModdingAPI.Framework.ModLoading; using StardewModdingAPI.Framework.Patching; +using StardewModdingAPI.Framework.PerformanceCounter; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Serialization; using StardewModdingAPI.Patches; @@ -33,7 +34,6 @@ using StardewModdingAPI.Toolkit.Serialization; using StardewModdingAPI.Toolkit.Utilities; using StardewValley; using Object = StardewValley.Object; -using PerformanceCounterManager = StardewModdingAPI.Framework.PerformanceCounter.PerformanceCounterManager; using ThreadState = System.Threading.ThreadState; namespace StardewModdingAPI.Framework @@ -135,8 +135,8 @@ namespace StardewModdingAPI.Framework internal static DeprecationManager DeprecationManager { get; private set; } /// Manages performance counters. - /// This is initialized after the game starts. This is accessed directly because it's not part of the normal class model. - internal static PerformanceCounterManager PerformanceCounterManager { get; private set; } + /// This is initialized after the game starts. This is non-private for use by Console Commands. + internal static PerformanceMonitor PerformanceMonitor { get; private set; } /********* @@ -167,9 +167,9 @@ namespace StardewModdingAPI.Framework }; this.MonitorForGame = this.GetSecondaryMonitor("game"); - SCore.PerformanceCounterManager = new PerformanceCounterManager(this.Monitor); - this.EventManager = new EventManager(this.Monitor, this.ModRegistry, SCore.PerformanceCounterManager); - SCore.PerformanceCounterManager.InitializePerformanceCounterCollections(this.EventManager); + SCore.PerformanceMonitor = new PerformanceMonitor(this.Monitor); + this.EventManager = new EventManager(this.Monitor, this.ModRegistry, SCore.PerformanceMonitor); + SCore.PerformanceMonitor.InitializePerformanceCounterCollections(this.EventManager); SCore.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry); @@ -248,7 +248,7 @@ namespace StardewModdingAPI.Framework jsonHelper: this.Toolkit.JsonHelper, modRegistry: this.ModRegistry, deprecationManager: SCore.DeprecationManager, - performanceCounterManager: SCore.PerformanceCounterManager, + performanceMonitor: SCore.PerformanceMonitor, onGameInitialized: this.InitializeAfterGameStart, onGameExiting: this.Dispose, cancellationToken: this.CancellationToken, @@ -1307,6 +1307,7 @@ namespace StardewModdingAPI.Framework this.ReloadTranslations(this.ModRegistry.GetAll(contentPacks: false)); this.Monitor.Log("Reloaded translation files for all mods. This only affects new translations the mods fetch; if they cached some text, it may not be updated.", LogLevel.Info); break; + default: throw new NotSupportedException($"Unrecognized core SMAPI command '{name}'."); } diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 266e2e6f..352859ec 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -59,7 +59,8 @@ namespace StardewModdingAPI.Framework /// Manages deprecation warnings. private readonly DeprecationManager DeprecationManager; - private readonly PerformanceCounterManager PerformanceCounterManager; + /// Tracks performance metrics. + private readonly PerformanceMonitor PerformanceMonitor; /// The maximum number of consecutive attempts SMAPI should make to recover from a draw error. private readonly Countdown DrawCrashTimer = new Countdown(60); // 60 ticks = roughly one second @@ -155,12 +156,12 @@ namespace StardewModdingAPI.Framework /// Encapsulates SMAPI's JSON file parsing. /// Tracks the installed mods. /// Manages deprecation warnings. - /// Manages performance monitoring. + /// Tracks performance metrics. /// A callback to invoke after the game finishes initializing. /// A callback to invoke when the game exits. /// Propagates notification that SMAPI should exit. /// Whether to log network traffic. - internal SGame(Monitor monitor, IMonitor monitorForGame, Reflector reflection, Translator translator, EventManager eventManager, JsonHelper jsonHelper, ModRegistry modRegistry, DeprecationManager deprecationManager, PerformanceCounterManager performanceCounterManager, Action onGameInitialized, Action onGameExiting, CancellationTokenSource cancellationToken, bool logNetworkTraffic) + internal SGame(Monitor monitor, IMonitor monitorForGame, Reflector reflection, Translator translator, EventManager eventManager, JsonHelper jsonHelper, ModRegistry modRegistry, DeprecationManager deprecationManager, PerformanceMonitor performanceMonitor, Action onGameInitialized, Action onGameExiting, CancellationTokenSource cancellationToken, bool logNetworkTraffic) { this.OnLoadingFirstAsset = SGame.ConstructorHack.OnLoadingFirstAsset; SGame.ConstructorHack = null; @@ -180,7 +181,7 @@ namespace StardewModdingAPI.Framework this.Reflection = reflection; this.Translator = translator; this.DeprecationManager = deprecationManager; - this.PerformanceCounterManager = performanceCounterManager; + this.PerformanceMonitor = performanceMonitor; this.OnGameInitialized = onGameInitialized; this.OnGameExiting = onGameExiting; Game1.input = new SInputState(); @@ -312,7 +313,7 @@ namespace StardewModdingAPI.Framework try { this.DeprecationManager.PrintQueued(); - this.PerformanceCounterManager.PrintQueuedAlerts(); + this.PerformanceMonitor.PrintQueuedAlerts(); /********* ** First-tick initialization diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 933590d3..c26ae29a 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -11,7 +11,7 @@ using StardewModdingAPI.Framework; using StardewModdingAPI.Toolkit.Utilities; [assembly: InternalsVisibleTo("SMAPI.Tests")] -[assembly: InternalsVisibleTo("ConsoleCommands")] +[assembly: InternalsVisibleTo("ConsoleCommands")] // for performance monitoring commands [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // Moq for unit testing namespace StardewModdingAPI { diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 0bc290ac..3bb73295 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -15,12 +15,7 @@ icon.ico - - SMAPI_FOR_WINDOWS - - - -- cgit