diff options
author | Drachenkaetzchen <felicia@drachenkatze.org> | 2020-01-15 16:01:35 +0100 |
---|---|---|
committer | Drachenkaetzchen <felicia@drachenkatze.org> | 2020-01-15 16:01:35 +0100 |
commit | 694cca4b21878850ba6131105a0c560fdfbc5f10 (patch) | |
tree | 016aadbd247f72e352fc341f3ce944980dc70c90 /src | |
parent | 280dc911839f8996cddd9804f3f545cc38d20243 (diff) | |
download | SMAPI-694cca4b21878850ba6131105a0c560fdfbc5f10.tar.gz SMAPI-694cca4b21878850ba6131105a0c560fdfbc5f10.tar.bz2 SMAPI-694cca4b21878850ba6131105a0c560fdfbc5f10.zip |
Added documentation for all performance counter methods and members. Refactored the naming of several members and methods to reflect their actual intention.
Diffstat (limited to 'src')
11 files changed, 276 insertions, 210 deletions
diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/PerformanceCounterCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/PerformanceCounterCommand.cs index 84b9504e..750e3792 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/PerformanceCounterCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/PerformanceCounterCommand.cs @@ -134,8 +134,8 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other { if (sourceName == null) { - collection.Monitor = true; - collection.MonitorThresholdMilliseconds = threshold; + collection.EnableAlerts = true; + collection.AlertThresholdMilliseconds = threshold; monitor.Log($"Set up monitor for '{collectionName}' with '{this.FormatMilliseconds(threshold)}'", LogLevel.Info); return; } @@ -145,8 +145,8 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other { if (performanceCounter.Value.Source.ToLowerInvariant().Equals(sourceName.ToLowerInvariant())) { - performanceCounter.Value.Monitor = true; - performanceCounter.Value.MonitorThresholdMilliseconds = threshold; + performanceCounter.Value.EnableAlerts = true; + performanceCounter.Value.AlertThresholdMilliseconds = threshold; monitor.Log($"Set up monitor for '{sourceName}' in collection '{collectionName}' with '{this.FormatMilliseconds(threshold)}", LogLevel.Info); return; } @@ -167,17 +167,17 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other int clearedCounters = 0; foreach (PerformanceCounterCollection collection in SCore.PerformanceCounterManager.PerformanceCounterCollections) { - if (collection.Monitor) + if (collection.EnableAlerts) { - collection.Monitor = false; + collection.EnableAlerts = false; clearedCounters++; } foreach (var performanceCounter in collection.PerformanceCounters) { - if (performanceCounter.Value.Monitor) + if (performanceCounter.Value.EnableAlerts) { - performanceCounter.Value.Monitor = false; + performanceCounter.Value.EnableAlerts = false; clearedCounters++; } } @@ -197,14 +197,14 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other foreach (PerformanceCounterCollection collection in SCore.PerformanceCounterManager.PerformanceCounterCollections) { - if (collection.Monitor) + if (collection.EnableAlerts) { - collectionMonitors.Add((collection.Name, collection.MonitorThresholdMilliseconds)); + collectionMonitors.Add((collection.Name, collection.AlertThresholdMilliseconds)); } sourceMonitors.AddRange(from performanceCounter in - collection.PerformanceCounters where performanceCounter.Value.Monitor - select (collection.Name, performanceCounter.Value.Source, performanceCounter.Value.MonitorThresholdMilliseconds)); + collection.PerformanceCounters where performanceCounter.Value.EnableAlerts + select (collection.Name, performanceCounter.Value.Source, MonitorThresholdMilliseconds: performanceCounter.Value.AlertThresholdMilliseconds)); } if (collectionMonitors.Count > 0) @@ -377,7 +377,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other switch (type) { case "category": - SCore.PerformanceCounterManager.ResetCategory(name); + SCore.PerformanceCounterManager.ResetCollection(name); monitor.Log($"All performance counters for category {name} are now cleared.", LogLevel.Info); break; case "mod": @@ -491,8 +491,8 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other { item.Key, this.FormatMilliseconds(item.Value.GetAverage(averageInterval), thresholdMilliseconds), - this.FormatMilliseconds(item.Value.GetLastEntry()?.Elapsed.TotalMilliseconds), - this.FormatMilliseconds(item.Value.GetPeak()?.Elapsed.TotalMilliseconds) + this.FormatMilliseconds(item.Value.GetLastEntry()?.ElapsedMilliseconds), + this.FormatMilliseconds(item.Value.GetPeak()?.ElapsedMilliseconds) } )); diff --git a/src/SMAPI/Framework/PerformanceCounter/AlertContext.cs b/src/SMAPI/Framework/PerformanceCounter/AlertContext.cs index c4a57a49..63f0a5ed 100644 --- a/src/SMAPI/Framework/PerformanceCounter/AlertContext.cs +++ b/src/SMAPI/Framework/PerformanceCounter/AlertContext.cs @@ -1,14 +1,26 @@ namespace StardewModdingAPI.Framework.PerformanceCounter { - public struct AlertContext + /// <summary>The context for an alert.</summary> + internal struct AlertContext { - public string Source; - public double Elapsed; + /// <summary>The source which triggered the alert.</summary> + public readonly string Source; + /// <summary>The elapsed milliseconds.</summary> + public readonly double Elapsed; + + /// <summary>Creates a new alert context.</summary> + /// <param name="source">The source which triggered the alert.</param> + /// <param name="elapsed">The elapsed milliseconds.</param> public AlertContext(string source, double elapsed) { this.Source = source; this.Elapsed = elapsed; } + + 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 284af1ce..b87d8642 100644 --- a/src/SMAPI/Framework/PerformanceCounter/AlertEntry.cs +++ b/src/SMAPI/Framework/PerformanceCounter/AlertEntry.cs @@ -2,18 +2,31 @@ using System.Collections.Generic; namespace StardewModdingAPI.Framework.PerformanceCounter { + /// <summary>A single alert entry.</summary> internal struct AlertEntry { - public PerformanceCounterCollection Collection; - public double ExecutionTimeMilliseconds; - public double Threshold; - public List<AlertContext> Context; + /// <summary>The collection in which the alert occurred.</summary> + public readonly PerformanceCounterCollection Collection; - public AlertEntry(PerformanceCounterCollection collection, double executionTimeMilliseconds, double threshold, List<AlertContext> context) + /// <summary>The actual execution time in milliseconds.</summary> + public readonly double ExecutionTimeMilliseconds; + + /// <summary>The configured alert threshold. </summary> + public readonly double ThresholdMilliseconds; + + /// <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> + /// <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) { this.Collection = collection; this.ExecutionTimeMilliseconds = executionTimeMilliseconds; - this.Threshold = threshold; + this.ThresholdMilliseconds = thresholdMilliseconds; this.Context = context; } } diff --git a/src/SMAPI/Framework/PerformanceCounter/EventPerformanceCounterCollection.cs b/src/SMAPI/Framework/PerformanceCounter/EventPerformanceCounterCollection.cs index 1aec28f3..4690c512 100644 --- a/src/SMAPI/Framework/PerformanceCounter/EventPerformanceCounterCollection.cs +++ b/src/SMAPI/Framework/PerformanceCounter/EventPerformanceCounterCollection.cs @@ -2,8 +2,13 @@ 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/IPerformanceCounterEvent.cs b/src/SMAPI/Framework/PerformanceCounter/IPerformanceCounterEvent.cs deleted file mode 100644 index 1bcf4fa0..00000000 --- a/src/SMAPI/Framework/PerformanceCounter/IPerformanceCounterEvent.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace StardewModdingAPI.Framework.Utilities -{ - public interface IPerformanceCounterEvent - { - string GetEventName(); - long GetAverageCallsPerSecond(); - - double GetGameAverageExecutionTime(); - double GetModsAverageExecutionTime(); - double GetAverageExecutionTime(); - } -} diff --git a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounter.cs b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounter.cs index 3dbc693a..b2ec4c90 100644 --- a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounter.cs +++ b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounter.cs @@ -1,26 +1,32 @@ using System; -using System.Diagnostics; using System.Linq; using Cyotek.Collections.Generic; -using StardewModdingAPI.Framework.Utilities; namespace StardewModdingAPI.Framework.PerformanceCounter { internal class PerformanceCounter { + /// <summary>The size of the ring buffer.</summary> private const int MAX_ENTRIES = 16384; - public string Source { get; } - public static Stopwatch Stopwatch = new Stopwatch(); - public static long TotalNumEventsLogged; - public double MonitorThresholdMilliseconds { get; set; } - public bool Monitor { get; set; } + /// <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 peak execution time</summary> private PerformanceCounterEntry? PeakPerformanceCounterEntry; + /// <summary>The name of the source.</summary> + public string Source { get; } + + /// <summary>The alert threshold in milliseconds</summary> + public double AlertThresholdMilliseconds { get; set; } + + /// <summary>If alerting is enabled or not</summary> + public bool EnableAlerts { get; set; } + public PerformanceCounter(PerformanceCounterCollection parentCollection, string source) { this.ParentCollection = parentCollection; @@ -28,90 +34,87 @@ namespace StardewModdingAPI.Framework.PerformanceCounter this._counter = new CircularBuffer<PerformanceCounterEntry>(PerformanceCounter.MAX_ENTRIES); } - public void Reset() - { - this._counter.Clear(); - this.PeakPerformanceCounterEntry = null; - } - - public int GetAverageCallsPerSecond() - { - var x = this._counter.GroupBy( - p => - (int) p.EventTime.Subtract( - new DateTime(1970, 1, 1) - ).TotalSeconds); - - return x.Last().Count(); - } - + /// <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> + /// <param name="entry">The entry to add.</param> public void Add(PerformanceCounterEntry entry) { - PerformanceCounter.Stopwatch.Start(); this._counter.Put(entry); - if (this.Monitor && entry.Elapsed.TotalMilliseconds > this.MonitorThresholdMilliseconds) - { - this.ParentCollection.AddAlert(entry.Elapsed.TotalMilliseconds, this.MonitorThresholdMilliseconds, new AlertContext(this.Source, entry.Elapsed.TotalMilliseconds)); - } + if (this.EnableAlerts && entry.ElapsedMilliseconds > this.AlertThresholdMilliseconds) + this.ParentCollection.AddAlert(entry.ElapsedMilliseconds, this.AlertThresholdMilliseconds, + new AlertContext(this.Source, entry.ElapsedMilliseconds)); if (this.PeakPerformanceCounterEntry == null) - { this.PeakPerformanceCounterEntry = entry; - } else { - if (entry.Elapsed.TotalMilliseconds > this.PeakPerformanceCounterEntry.Value.Elapsed.TotalMilliseconds) - { + if (entry.ElapsedMilliseconds > this.PeakPerformanceCounterEntry.Value.ElapsedMilliseconds) this.PeakPerformanceCounterEntry = entry; - } } + } - PerformanceCounter.Stopwatch.Stop(); - PerformanceCounter.TotalNumEventsLogged++; + /// <summary>Clears all performance counter entries and resets the peak entry.</summary> + public void Reset() + { + this._counter.Clear(); + this.PeakPerformanceCounterEntry = null; } + /// <summary>Returns the peak entry.</summary> + /// <returns>The peak entry.</returns> public PerformanceCounterEntry? GetPeak() { return this.PeakPerformanceCounterEntry; } + /// <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> public PerformanceCounterEntry? GetLastEntry() { if (this._counter.IsEmpty) - { return null; - } + return this._counter.PeekLast(); } + /// <summary>Returns the average execution time of all entries.</summary> + /// <returns>The average execution time in milliseconds.</returns> public double GetAverage() { if (this._counter.IsEmpty) - { return 0; - } - return this._counter.Average(p => p.Elapsed.TotalMilliseconds); + return this._counter.Average(p => p.ElapsedMilliseconds); } - public double GetAverage(TimeSpan range) + /// <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; - } - var lastTime = this._counter.Max(x => x.EventTime); - var start = lastTime.Subtract(range); + if (relativeTo == null) + relativeTo = DateTime.UtcNow; + + DateTime start = relativeTo.Value.Subtract(range); - var entries = this._counter.Where(x => (x.EventTime >= start) && (x.EventTime <= lastTime)); - return entries.Average(x => x.Elapsed.TotalMilliseconds); + var entries = this._counter.Where(x => (x.EventTime >= start) && (x.EventTime <= relativeTo)); + return entries.Average(x => x.ElapsedMilliseconds); } } } diff --git a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterCollection.cs b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterCollection.cs index 343fddf6..b48efd67 100644 --- a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterCollection.cs +++ b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterCollection.cs @@ -2,23 +2,41 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using StardewModdingAPI.Framework.Utilities; namespace StardewModdingAPI.Framework.PerformanceCounter { internal class PerformanceCounterCollection { - public IDictionary<string, PerformanceCounter> PerformanceCounters { get; } = new Dictionary<string, PerformanceCounter>(); - private DateTime StartDateTime = DateTime.Now; - private long CallCount; - public string Name { get; private set; } - public bool IsImportant { get; set; } - private readonly Stopwatch Stopwatch = new Stopwatch(); - private readonly PerformanceCounterManager PerformanceCounterManager; - public double MonitorThresholdMilliseconds { get; set; } - public bool Monitor { get; set; } + /// <summary>The list of triggered performance counters.</summary> private readonly List<AlertContext> TriggeredPerformanceCounters = new List<AlertContext>(); + /// <summary>The stopwatch used to track the invocation time.</summary> + private readonly Stopwatch InvocationStopwatch = new Stopwatch(); + + /// <summary>The performance counter manager.</summary> + private readonly PerformanceCounterManager PerformanceCounterManager; + + /// <summary>Holds the time to calculate the average calls per second.</summary> + private DateTime CallsPerSecondStart = DateTime.UtcNow; + + /// <summary>The number of invocations of this collection.</summary> + private long CallCount; + + public IDictionary<string, PerformanceCounter> PerformanceCounters { get; } = new Dictionary<string, PerformanceCounter>(); + + /// <summary>The name of this collection.</summary> + public string Name { get; } + + /// <summary>Flag if this collection is important (used for the console summary command).</summary> + public bool IsImportant { get; } + + /// <summary>The alert threshold in milliseconds.</summary> + public double AlertThresholdMilliseconds { get; set; } + + /// <summary>If alerting is enabled or not</summary> + public bool EnableAlerts { get; set; } + + public PerformanceCounterCollection(PerformanceCounterManager performanceCounterManager, string name, bool isImportant) { this.Name = name; @@ -32,111 +50,120 @@ namespace StardewModdingAPI.Framework.PerformanceCounter this.Name = name; } + /// <summary>Tracks a single invocation for a named source.</summary> + /// <param name="source">The name of the source.</param> + /// <param name="entry">The entry.</param> public void Track(string source, PerformanceCounterEntry entry) { if (!this.PerformanceCounters.ContainsKey(source)) - { this.PerformanceCounters.Add(source, new PerformanceCounter(this, source)); - } + this.PerformanceCounters[source].Add(entry); - if (this.Monitor) - { - this.TriggeredPerformanceCounters.Add(new AlertContext(source, entry.Elapsed.TotalMilliseconds)); - } + if (this.EnableAlerts) + this.TriggeredPerformanceCounters.Add(new AlertContext(source, entry.ElapsedMilliseconds)); } + /// <summary>Returns the average execution time for all non-game internal sources.</summary> + /// <returns>The average execution time in milliseconds</returns> public double GetModsAverageExecutionTime() { - return this.PerformanceCounters.Where(p => p.Key != Constants.GamePerformanceCounterName).Sum(p => p.Value.GetAverage()); + return this.PerformanceCounters.Where(p => + p.Key != Constants.GamePerformanceCounterName).Sum(p => p.Value.GetAverage()); } + /// <summary>Returns the overall average execution time.</summary> + /// <returns>The average execution time in milliseconds</returns> public double GetAverageExecutionTime() { return this.PerformanceCounters.Sum(p => p.Value.GetAverage()); } + /// <summary>Returns the average execution time for game-internal sources.</summary> + /// <returns>The average execution time in milliseconds</returns> public double GetGameAverageExecutionTime() { if (this.PerformanceCounters.TryGetValue(Constants.GamePerformanceCounterName, out PerformanceCounter gameExecTime)) - { return gameExecTime.GetAverage(); - } return 0; } + /// <summary>Begins tracking the invocation of this collection.</summary> public void BeginTrackInvocation() { - if (this.Monitor) + if (this.EnableAlerts) { this.TriggeredPerformanceCounters.Clear(); - this.Stopwatch.Reset(); - this.Stopwatch.Start(); + this.InvocationStopwatch.Reset(); + this.InvocationStopwatch.Start(); } this.CallCount++; - } + /// <summary>Ends tracking the invocation of this collection. Also records an alert if alerting is enabled + /// and the invocation time exceeds the threshold.</summary> public void EndTrackInvocation() { - if (!this.Monitor) return; + if (!this.EnableAlerts) return; - this.Stopwatch.Stop(); - if (this.Stopwatch.Elapsed.TotalMilliseconds >= this.MonitorThresholdMilliseconds) - { - this.AddAlert(this.Stopwatch.Elapsed.TotalMilliseconds, - this.MonitorThresholdMilliseconds, this.TriggeredPerformanceCounters); - } + this.InvocationStopwatch.Stop(); + + if (this.InvocationStopwatch.Elapsed.TotalMilliseconds >= this.AlertThresholdMilliseconds) + this.AddAlert(this.InvocationStopwatch.Elapsed.TotalMilliseconds, + this.AlertThresholdMilliseconds, this.TriggeredPerformanceCounters); } - public void AddAlert(double executionTimeMilliseconds, double threshold, List<AlertContext> alerts) + /// <summary>Adds an alert.</summary> + /// <param name="executionTimeMilliseconds">The execution time in milliseconds.</param> + /// <param name="thresholdMilliseconds">The configured threshold.</param> + /// <param name="alerts">The list of alert contexts.</param> + public void AddAlert(double executionTimeMilliseconds, double thresholdMilliseconds, List<AlertContext> alerts) { this.PerformanceCounterManager.AddAlert(new AlertEntry(this, executionTimeMilliseconds, - threshold, alerts)); + thresholdMilliseconds, alerts)); } - public void AddAlert(double executionTimeMilliseconds, double threshold, AlertContext alert) + /// <summary>Adds an alert for a single AlertContext</summary> + /// <param name="executionTimeMilliseconds">The execution time in milliseconds.</param> + /// <param name="thresholdMilliseconds">The configured threshold.</param> + /// <param name="alert">The context</param> + public void AddAlert(double executionTimeMilliseconds, double thresholdMilliseconds, AlertContext alert) { - this.AddAlert(executionTimeMilliseconds, threshold, new List<AlertContext>() {alert}); + this.AddAlert(executionTimeMilliseconds, thresholdMilliseconds, new List<AlertContext>() {alert}); } + /// <summary>Resets the calls per second counter.</summary> public void ResetCallsPerSecond() { this.CallCount = 0; - this.StartDateTime = DateTime.Now; + this.CallsPerSecondStart = DateTime.UtcNow; } + /// <summary>Resets all performance counters in this collection.</summary> public void Reset() { foreach (var i in this.PerformanceCounters) - { i.Value.Reset(); - i.Value.ResetPeak(); - } } + /// <summary>Resets the performance counter for a specific source.</summary> + /// <param name="source">The source name</param> public void ResetSource(string source) { foreach (var i in this.PerformanceCounters) - { if (i.Value.Source.Equals(source, StringComparison.InvariantCultureIgnoreCase)) - { i.Value.Reset(); - i.Value.ResetPeak(); - } - } } + /// <summary>Returns the average calls per second.</summary> + /// <returns>The average calls per second.</returns> public long GetAverageCallsPerSecond() { - long runtimeInSeconds = (long) DateTime.Now.Subtract(this.StartDateTime).TotalSeconds; + long runtimeInSeconds = (long) DateTime.UtcNow.Subtract(this.CallsPerSecondStart).TotalSeconds; - if (runtimeInSeconds == 0) - { - return 0; - } + if (runtimeInSeconds == 0) return 0; return this.CallCount / runtimeInSeconds; } diff --git a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterEntry.cs b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterEntry.cs index 8e156a32..a50fce7d 100644 --- a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterEntry.cs +++ b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterEntry.cs @@ -1,10 +1,14 @@ using System; -namespace StardewModdingAPI.Framework.Utilities +namespace StardewModdingAPI.Framework.PerformanceCounter { - public struct PerformanceCounterEntry + /// <summary>A single performance counter entry. Records the DateTime of the event and the elapsed millisecond.</summary> + internal struct PerformanceCounterEntry { + /// <summary>The DateTime when the entry occured.</summary> public DateTime EventTime; - public TimeSpan Elapsed; + + /// <summary>The elapsed milliseconds</summary> + public double ElapsedMilliseconds; } } diff --git a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterManager.cs b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterManager.cs index 9e77e2fa..d8f1f172 100644 --- a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterManager.cs +++ b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterManager.cs @@ -4,72 +4,62 @@ using System.Diagnostics; using System.Linq; using System.Text; using StardewModdingAPI.Framework.Events; -using StardewModdingAPI.Framework.Utilities; namespace StardewModdingAPI.Framework.PerformanceCounter { internal class PerformanceCounterManager { public HashSet<PerformanceCounterCollection> PerformanceCounterCollections = new HashSet<PerformanceCounterCollection>(); - public List<AlertEntry> Alerts = new List<AlertEntry>(); + + /// <summary>The recorded alerts.</summary> + private readonly List<AlertEntry> Alerts = new List<AlertEntry>(); + + /// <summary>The monitor for output logging.</summary> private readonly IMonitor Monitor; - private readonly Stopwatch Stopwatch = new Stopwatch(); + /// <summary>The invocation stopwatch.</summary> + private readonly Stopwatch InvocationStopwatch = new Stopwatch(); + + /// <summary>Constructs a performance counter manager.</summary> + /// <param name="monitor">The monitor for output logging.</param> public PerformanceCounterManager(IMonitor monitor) { this.Monitor = monitor; } + /// <summary>Resets all performance counters in all collections.</summary> public void Reset() { - foreach (var performanceCounter in this.PerformanceCounterCollections) - { - foreach (var eventPerformanceCounter in performanceCounter.PerformanceCounters) - { - eventPerformanceCounter.Value.Reset(); - } - } - } - - /// <summary>Print any queued messages.</summary> - public void PrintQueued() - { - if (this.Alerts.Count == 0) + foreach (var eventPerformanceCounter in + this.PerformanceCounterCollections.SelectMany(performanceCounter => performanceCounter.PerformanceCounters)) { - return; + eventPerformanceCounter.Value.Reset(); } - StringBuilder sb = new StringBuilder(); - - foreach (var alert in this.Alerts) - { - sb.AppendLine($"{alert.Collection.Name} took {alert.ExecutionTimeMilliseconds:F2}ms (exceeded threshold of {alert.Threshold:F2}ms)"); - - foreach (var context in alert.Context) - { - sb.AppendLine($"{context.Source}: {context.Elapsed:F2}ms"); - } - } - - this.Alerts.Clear(); - - this.Monitor.Log(sb.ToString(), LogLevel.Error); } + /// <summary>Begins tracking the invocation for a collection.</summary> + /// <param name="collectionName">The collection name</param> public void BeginTrackInvocation(string collectionName) { this.GetOrCreateCollectionByName(collectionName).BeginTrackInvocation(); } + /// <summary>Ends tracking the invocation for a collection.</summary> + /// <param name="collectionName"></param> public void EndTrackInvocation(string collectionName) { this.GetOrCreateCollectionByName(collectionName).EndTrackInvocation(); } - public void Track(string collectionName, string modName, Action action) + /// <summary>Tracks a single performance counter invocation in a specific collection.</summary> + /// <param name="collectionName">The name of the collection.</param> + /// <param name="sourceName">The name of the source.</param> + /// <param name="action">The action to execute and track invocation time for.</param> + public void Track(string collectionName, string sourceName, Action action) { DateTime eventTime = DateTime.UtcNow; - this.Stopwatch.Reset(); - this.Stopwatch.Start(); + this.InvocationStopwatch.Reset(); + this.InvocationStopwatch.Start(); try { @@ -77,75 +67,102 @@ namespace StardewModdingAPI.Framework.PerformanceCounter } finally { - this.Stopwatch.Stop(); + this.InvocationStopwatch.Stop(); - this.GetOrCreateCollectionByName(collectionName).Track(modName, new PerformanceCounterEntry + this.GetOrCreateCollectionByName(collectionName).Track(sourceName, new PerformanceCounterEntry { EventTime = eventTime, - Elapsed = this.Stopwatch.Elapsed + ElapsedMilliseconds = this.InvocationStopwatch.Elapsed.TotalMilliseconds }); } } - public PerformanceCounterCollection GetCollectionByName(string name) + /// <summary>Gets a collection by name.</summary> + /// <param name="name">The name of the collection.</param> + /// <returns>The collection or null if none was found.</returns> + private PerformanceCounterCollection GetCollectionByName(string name) { return this.PerformanceCounterCollections.FirstOrDefault(collection => collection.Name == name); } - public PerformanceCounterCollection GetOrCreateCollectionByName(string name) + /// <summary>Gets a collection by name and creates it if it doesn't exist.</summary> + /// <param name="name">The name of the collection.</param> + /// <returns>The collection.</returns> + private PerformanceCounterCollection GetOrCreateCollectionByName(string name) { PerformanceCounterCollection collection = this.GetCollectionByName(name); - if (collection == null) - { - collection = new PerformanceCounterCollection(this, name); - this.PerformanceCounterCollections.Add(collection); - } + if (collection != null) return collection; + + collection = new PerformanceCounterCollection(this, name); + this.PerformanceCounterCollections.Add(collection); return collection; } - public void ResetCategory(string name) + /// <summary>Resets the performance counters for a specific collection.</summary> + /// <param name="name">The collection name.</param> + public void ResetCollection(string name) { - foreach (var performanceCounterCollection in this.PerformanceCounterCollections) + foreach (PerformanceCounterCollection performanceCounterCollection in + this.PerformanceCounterCollections.Where(performanceCounterCollection => + performanceCounterCollection.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))) { - if (performanceCounterCollection.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)) - { - performanceCounterCollection.ResetCallsPerSecond(); - performanceCounterCollection.Reset(); - } + performanceCounterCollection.ResetCallsPerSecond(); + performanceCounterCollection.Reset(); } } + /// <summary>Resets performance counters for a specific source.</summary> + /// <param name="name">The name of the source.</param> public void ResetSource(string name) { - foreach (var performanceCounterCollection in this.PerformanceCounterCollections) - { + foreach (PerformanceCounterCollection performanceCounterCollection in this.PerformanceCounterCollections) performanceCounterCollection.ResetSource(name); - } } + /// <summary>Print any queued alerts.</summary> + public void PrintQueuedAlerts() + { + if (this.Alerts.Count == 0) return; + + StringBuilder sb = 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)"); + foreach (AlertContext context in alert.Context.OrderByDescending(p => p.Elapsed)) + sb.AppendLine(context.ToString()); + } + + this.Alerts.Clear(); + this.Monitor.Log(sb.ToString(), LogLevel.Error); + } + + /// <summary>Adds an alert to the queue.</summary> + /// <param name="entry">The alert to add.</param> public void AddAlert(AlertEntry entry) { this.Alerts.Add(entry); } - public void InitializePerformanceCounterEvents(EventManager eventManager) + /// <summary>Initialized the default performance counter collections.</summary> + /// <param name="eventManager">The event manager.</param> + public void InitializePerformanceCounterCollections(EventManager eventManager) { this.PerformanceCounterCollections = new HashSet<PerformanceCounterCollection>() { new EventPerformanceCounterCollection(this, eventManager.MenuChanged, false), - // Rendering Events - new EventPerformanceCounterCollection(this, eventManager.Rendering, true), + new EventPerformanceCounterCollection(this, eventManager.Rendering, false), new EventPerformanceCounterCollection(this, eventManager.Rendered, true), - new EventPerformanceCounterCollection(this, eventManager.RenderingWorld, true), + new EventPerformanceCounterCollection(this, eventManager.RenderingWorld, false), new EventPerformanceCounterCollection(this, eventManager.RenderedWorld, true), - new EventPerformanceCounterCollection(this, eventManager.RenderingActiveMenu, true), + new EventPerformanceCounterCollection(this, eventManager.RenderingActiveMenu, false), new EventPerformanceCounterCollection(this, eventManager.RenderedActiveMenu, true), - new EventPerformanceCounterCollection(this, eventManager.RenderingHud, true), + new EventPerformanceCounterCollection(this, eventManager.RenderingHud, false), new EventPerformanceCounterCollection(this, eventManager.RenderedHud, true), new EventPerformanceCounterCollection(this, eventManager.WindowResized, false), @@ -172,19 +189,19 @@ namespace StardewModdingAPI.Framework.PerformanceCounter new EventPerformanceCounterCollection(this, eventManager.CursorMoved, true), new EventPerformanceCounterCollection(this, eventManager.MouseWheelScrolled, true), - new EventPerformanceCounterCollection(this, eventManager.PeerContextReceived, true), - new EventPerformanceCounterCollection(this, eventManager.ModMessageReceived, true), - new EventPerformanceCounterCollection(this, eventManager.PeerDisconnected, 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, true), - new EventPerformanceCounterCollection(this, eventManager.Warped, true), + new EventPerformanceCounterCollection(this, eventManager.LevelChanged, false), + new EventPerformanceCounterCollection(this, eventManager.Warped, false), - new EventPerformanceCounterCollection(this, eventManager.LocationListChanged, true), - new EventPerformanceCounterCollection(this, eventManager.BuildingListChanged, true), - new EventPerformanceCounterCollection(this, eventManager.LocationListChanged, true), + 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, 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), diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 5b0c6691..af7513e3 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -169,7 +169,7 @@ namespace StardewModdingAPI.Framework SCore.PerformanceCounterManager = new PerformanceCounterManager(this.Monitor); this.EventManager = new EventManager(this.Monitor, this.ModRegistry, SCore.PerformanceCounterManager); - SCore.PerformanceCounterManager.InitializePerformanceCounterEvents(this.EventManager); + SCore.PerformanceCounterManager.InitializePerformanceCounterCollections(this.EventManager); SCore.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry); diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 8aba9b57..266e2e6f 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -312,7 +312,7 @@ namespace StardewModdingAPI.Framework try { this.DeprecationManager.PrintQueued(); - this.PerformanceCounterManager.PrintQueued(); + this.PerformanceCounterManager.PrintQueuedAlerts(); /********* ** First-tick initialization |