diff options
author | Drachenkaetzchen <felicia@drachenkatze.org> | 2020-01-11 15:45:45 +0100 |
---|---|---|
committer | Drachenkaetzchen <felicia@drachenkatze.org> | 2020-01-11 15:45:45 +0100 |
commit | 280dc911839f8996cddd9804f3f545cc38d20243 (patch) | |
tree | 20b351281ae16e1d43b761fd2eab3710a6fb8689 /src/SMAPI/Framework/PerformanceCounter | |
parent | 8a77373b18dbda77f268e8e7f772e950da60829f (diff) | |
download | SMAPI-280dc911839f8996cddd9804f3f545cc38d20243.tar.gz SMAPI-280dc911839f8996cddd9804f3f545cc38d20243.tar.bz2 SMAPI-280dc911839f8996cddd9804f3f545cc38d20243.zip |
Reworked the console implementation, added monitoring. Some internal refactoring.
Diffstat (limited to 'src/SMAPI/Framework/PerformanceCounter')
8 files changed, 368 insertions, 89 deletions
diff --git a/src/SMAPI/Framework/PerformanceCounter/AlertContext.cs b/src/SMAPI/Framework/PerformanceCounter/AlertContext.cs new file mode 100644 index 00000000..c4a57a49 --- /dev/null +++ b/src/SMAPI/Framework/PerformanceCounter/AlertContext.cs @@ -0,0 +1,14 @@ +namespace StardewModdingAPI.Framework.PerformanceCounter +{ + public struct AlertContext + { + public string Source; + public double Elapsed; + + public AlertContext(string source, double elapsed) + { + this.Source = source; + this.Elapsed = elapsed; + } + } +} diff --git a/src/SMAPI/Framework/PerformanceCounter/AlertEntry.cs b/src/SMAPI/Framework/PerformanceCounter/AlertEntry.cs new file mode 100644 index 00000000..284af1ce --- /dev/null +++ b/src/SMAPI/Framework/PerformanceCounter/AlertEntry.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace StardewModdingAPI.Framework.PerformanceCounter +{ + internal struct AlertEntry + { + public PerformanceCounterCollection Collection; + public double ExecutionTimeMilliseconds; + public double Threshold; + public List<AlertContext> Context; + + public AlertEntry(PerformanceCounterCollection collection, double executionTimeMilliseconds, double threshold, List<AlertContext> context) + { + this.Collection = collection; + this.ExecutionTimeMilliseconds = executionTimeMilliseconds; + this.Threshold = threshold; + this.Context = context; + } + } +} diff --git a/src/SMAPI/Framework/PerformanceCounter/EventPerformanceCounterCategory.cs b/src/SMAPI/Framework/PerformanceCounter/EventPerformanceCounterCategory.cs deleted file mode 100644 index 14f74317..00000000 --- a/src/SMAPI/Framework/PerformanceCounter/EventPerformanceCounterCategory.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace StardewModdingAPI.Framework.Utilities -{ - public class EventPerformanceCounterCategory - { - public IPerformanceCounterEvent Event { get; } - public double MonitorThreshold { get; } - public bool IsImportant { get; } - public bool Monitor { get; } - - public EventPerformanceCounterCategory(IPerformanceCounterEvent @event, bool isImportant) - { - this.Event = @event; - this.IsImportant = isImportant; - } - } -} diff --git a/src/SMAPI/Framework/PerformanceCounter/EventPerformanceCounterCollection.cs b/src/SMAPI/Framework/PerformanceCounter/EventPerformanceCounterCollection.cs new file mode 100644 index 00000000..1aec28f3 --- /dev/null +++ b/src/SMAPI/Framework/PerformanceCounter/EventPerformanceCounterCollection.cs @@ -0,0 +1,11 @@ +using StardewModdingAPI.Framework.Events; + +namespace StardewModdingAPI.Framework.PerformanceCounter +{ + internal class EventPerformanceCounterCollection: PerformanceCounterCollection + { + 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 index 6b83586d..1bcf4fa0 100644 --- a/src/SMAPI/Framework/PerformanceCounter/IPerformanceCounterEvent.cs +++ b/src/SMAPI/Framework/PerformanceCounter/IPerformanceCounterEvent.cs @@ -7,7 +7,6 @@ namespace StardewModdingAPI.Framework.Utilities { string GetEventName(); long GetAverageCallsPerSecond(); - IDictionary<string, PerformanceCounter.PerformanceCounter> PerformanceCounters { get; } double GetGameAverageExecutionTime(); double GetModsAverageExecutionTime(); diff --git a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounter.cs b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounter.cs index 0b0275b7..3dbc693a 100644 --- a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounter.cs +++ b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounter.cs @@ -6,22 +6,25 @@ using StardewModdingAPI.Framework.Utilities; namespace StardewModdingAPI.Framework.PerformanceCounter { - public class PerformanceCounter + internal class PerformanceCounter { private const int MAX_ENTRIES = 16384; - public string Name { get; } + public string Source { get; } public static Stopwatch Stopwatch = new Stopwatch(); public static long TotalNumEventsLogged; - + public double MonitorThresholdMilliseconds { get; set; } + public bool Monitor { get; set; } + private readonly PerformanceCounterCollection ParentCollection; private readonly CircularBuffer<PerformanceCounterEntry> _counter; private PerformanceCounterEntry? PeakPerformanceCounterEntry; - public PerformanceCounter(string name) + public PerformanceCounter(PerformanceCounterCollection parentCollection, string source) { - this.Name = name; + this.ParentCollection = parentCollection; + this.Source = source; this._counter = new CircularBuffer<PerformanceCounterEntry>(PerformanceCounter.MAX_ENTRIES); } @@ -47,6 +50,11 @@ namespace StardewModdingAPI.Framework.PerformanceCounter 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.PeakPerformanceCounterEntry == null) { this.PeakPerformanceCounterEntry = entry; diff --git a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterCollection.cs b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterCollection.cs new file mode 100644 index 00000000..343fddf6 --- /dev/null +++ b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterCollection.cs @@ -0,0 +1,144 @@ +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; } + private readonly List<AlertContext> TriggeredPerformanceCounters = new List<AlertContext>(); + + public PerformanceCounterCollection(PerformanceCounterManager performanceCounterManager, string name, bool isImportant) + { + this.Name = name; + this.PerformanceCounterManager = performanceCounterManager; + this.IsImportant = isImportant; + } + + public PerformanceCounterCollection(PerformanceCounterManager performanceCounterManager, string name) + { + this.PerformanceCounterManager = performanceCounterManager; + this.Name = name; + } + + 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)); + } + } + + public double GetModsAverageExecutionTime() + { + return this.PerformanceCounters.Where(p => p.Key != Constants.GamePerformanceCounterName).Sum(p => p.Value.GetAverage()); + } + + public double GetAverageExecutionTime() + { + return this.PerformanceCounters.Sum(p => p.Value.GetAverage()); + } + + public double GetGameAverageExecutionTime() + { + if (this.PerformanceCounters.TryGetValue(Constants.GamePerformanceCounterName, out PerformanceCounter gameExecTime)) + { + return gameExecTime.GetAverage(); + } + + return 0; + } + + public void BeginTrackInvocation() + { + if (this.Monitor) + { + this.TriggeredPerformanceCounters.Clear(); + this.Stopwatch.Reset(); + this.Stopwatch.Start(); + } + + this.CallCount++; + + } + + public void EndTrackInvocation() + { + if (!this.Monitor) return; + + this.Stopwatch.Stop(); + if (this.Stopwatch.Elapsed.TotalMilliseconds >= this.MonitorThresholdMilliseconds) + { + this.AddAlert(this.Stopwatch.Elapsed.TotalMilliseconds, + this.MonitorThresholdMilliseconds, this.TriggeredPerformanceCounters); + } + } + + public void AddAlert(double executionTimeMilliseconds, double threshold, List<AlertContext> alerts) + { + this.PerformanceCounterManager.AddAlert(new AlertEntry(this, executionTimeMilliseconds, + threshold, alerts)); + } + + public void AddAlert(double executionTimeMilliseconds, double threshold, AlertContext alert) + { + this.AddAlert(executionTimeMilliseconds, threshold, new List<AlertContext>() {alert}); + } + + public void ResetCallsPerSecond() + { + this.CallCount = 0; + this.StartDateTime = DateTime.Now; + } + + public void Reset() + { + foreach (var i in this.PerformanceCounters) + { + i.Value.Reset(); + i.Value.ResetPeak(); + } + } + + 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(); + } + } + } + + public long GetAverageCallsPerSecond() + { + long runtimeInSeconds = (long) DateTime.Now.Subtract(this.StartDateTime).TotalSeconds; + + if (runtimeInSeconds == 0) + { + return 0; + } + + return this.CallCount / runtimeInSeconds; + } + } +} diff --git a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterManager.cs b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterManager.cs index ae7258e2..9e77e2fa 100644 --- a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterManager.cs +++ b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterManager.cs @@ -1,4 +1,8 @@ +using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; using StardewModdingAPI.Framework.Events; using StardewModdingAPI.Framework.Utilities; @@ -6,92 +10,187 @@ namespace StardewModdingAPI.Framework.PerformanceCounter { internal class PerformanceCounterManager { - public HashSet<EventPerformanceCounterCategory> PerformanceCounterEvents = new HashSet<EventPerformanceCounterCategory>(); + public HashSet<PerformanceCounterCollection> PerformanceCounterCollections = new HashSet<PerformanceCounterCollection>(); + public List<AlertEntry> Alerts = new List<AlertEntry>(); + private readonly IMonitor Monitor; + private readonly Stopwatch Stopwatch = new Stopwatch(); - private readonly EventManager EventManager; - - public PerformanceCounterManager(EventManager eventManager) + public PerformanceCounterManager(IMonitor monitor) { - this.EventManager = eventManager; - this.InitializePerformanceCounterEvents(); + this.Monitor = monitor; } public void Reset() { - foreach (var performanceCounter in this.PerformanceCounterEvents) + foreach (var performanceCounter in this.PerformanceCounterCollections) { - this.ResetCategory(performanceCounter); + foreach (var eventPerformanceCounter in performanceCounter.PerformanceCounters) + { + eventPerformanceCounter.Value.Reset(); + } } } - public void ResetCategory(EventPerformanceCounterCategory category) + /// <summary>Print any queued messages.</summary> + public void PrintQueued() { - foreach (var eventPerformanceCounter in category.Event.PerformanceCounters) + if (this.Alerts.Count == 0) + { + return; + } + StringBuilder sb = new StringBuilder(); + + foreach (var alert in this.Alerts) { - eventPerformanceCounter.Value.Reset(); + 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); + } + + public void BeginTrackInvocation(string collectionName) + { + this.GetOrCreateCollectionByName(collectionName).BeginTrackInvocation(); + } + + public void EndTrackInvocation(string collectionName) + { + this.GetOrCreateCollectionByName(collectionName).EndTrackInvocation(); } - private void InitializePerformanceCounterEvents() + public void Track(string collectionName, string modName, Action action) { - this.PerformanceCounterEvents = new HashSet<EventPerformanceCounterCategory>() + DateTime eventTime = DateTime.UtcNow; + this.Stopwatch.Reset(); + this.Stopwatch.Start(); + + try + { + action(); + } + finally { - new EventPerformanceCounterCategory(this.EventManager.MenuChanged, false), + this.Stopwatch.Stop(); - // Rendering Events - new EventPerformanceCounterCategory(this.EventManager.Rendering, true), - new EventPerformanceCounterCategory(this.EventManager.Rendered, true), - new EventPerformanceCounterCategory(this.EventManager.RenderingWorld, true), - new EventPerformanceCounterCategory(this.EventManager.RenderedWorld, true), - new EventPerformanceCounterCategory(this.EventManager.RenderingActiveMenu, true), - new EventPerformanceCounterCategory(this.EventManager.RenderedActiveMenu, true), - new EventPerformanceCounterCategory(this.EventManager.RenderingHud, true), - new EventPerformanceCounterCategory(this.EventManager.RenderedHud, true), - - new EventPerformanceCounterCategory(this.EventManager.WindowResized, false), - new EventPerformanceCounterCategory(this.EventManager.GameLaunched, false), - new EventPerformanceCounterCategory(this.EventManager.UpdateTicking, true), - new EventPerformanceCounterCategory(this.EventManager.UpdateTicked, true), - new EventPerformanceCounterCategory(this.EventManager.OneSecondUpdateTicking, true), - new EventPerformanceCounterCategory(this.EventManager.OneSecondUpdateTicked, true), - - new EventPerformanceCounterCategory(this.EventManager.SaveCreating, false), - new EventPerformanceCounterCategory(this.EventManager.SaveCreated, false), - new EventPerformanceCounterCategory(this.EventManager.Saving, false), - new EventPerformanceCounterCategory(this.EventManager.Saved, false), - - new EventPerformanceCounterCategory(this.EventManager.DayStarted, false), - new EventPerformanceCounterCategory(this.EventManager.DayEnding, false), - - new EventPerformanceCounterCategory(this.EventManager.TimeChanged, true), - - new EventPerformanceCounterCategory(this.EventManager.ReturnedToTitle, false), - - new EventPerformanceCounterCategory(this.EventManager.ButtonPressed, true), - new EventPerformanceCounterCategory(this.EventManager.ButtonReleased, true), - new EventPerformanceCounterCategory(this.EventManager.CursorMoved, true), - new EventPerformanceCounterCategory(this.EventManager.MouseWheelScrolled, true), - - new EventPerformanceCounterCategory(this.EventManager.PeerContextReceived, true), - new EventPerformanceCounterCategory(this.EventManager.ModMessageReceived, true), - new EventPerformanceCounterCategory(this.EventManager.PeerDisconnected, true), - new EventPerformanceCounterCategory(this.EventManager.InventoryChanged, true), - new EventPerformanceCounterCategory(this.EventManager.LevelChanged, true), - new EventPerformanceCounterCategory(this.EventManager.Warped, true), - - new EventPerformanceCounterCategory(this.EventManager.LocationListChanged, true), - new EventPerformanceCounterCategory(this.EventManager.BuildingListChanged, true), - new EventPerformanceCounterCategory(this.EventManager.LocationListChanged, true), - new EventPerformanceCounterCategory(this.EventManager.DebrisListChanged, true), - new EventPerformanceCounterCategory(this.EventManager.LargeTerrainFeatureListChanged, true), - new EventPerformanceCounterCategory(this.EventManager.NpcListChanged, true), - new EventPerformanceCounterCategory(this.EventManager.ObjectListChanged, true), - new EventPerformanceCounterCategory(this.EventManager.ChestInventoryChanged, true), - new EventPerformanceCounterCategory(this.EventManager.TerrainFeatureListChanged, true), - new EventPerformanceCounterCategory(this.EventManager.LoadStageChanged, false), - new EventPerformanceCounterCategory(this.EventManager.UnvalidatedUpdateTicking, true), - new EventPerformanceCounterCategory(this.EventManager.UnvalidatedUpdateTicked, true), + this.GetOrCreateCollectionByName(collectionName).Track(modName, new PerformanceCounterEntry + { + EventTime = eventTime, + Elapsed = this.Stopwatch.Elapsed + }); + } + } + + public PerformanceCounterCollection GetCollectionByName(string name) + { + return this.PerformanceCounterCollections.FirstOrDefault(collection => collection.Name == name); + } + + public PerformanceCounterCollection GetOrCreateCollectionByName(string name) + { + PerformanceCounterCollection collection = this.GetCollectionByName(name); + if (collection == null) + { + collection = new PerformanceCounterCollection(this, name); + this.PerformanceCounterCollections.Add(collection); + } + + return collection; + } + + public void ResetCategory(string name) + { + foreach (var performanceCounterCollection in this.PerformanceCounterCollections) + { + if (performanceCounterCollection.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)) + { + performanceCounterCollection.ResetCallsPerSecond(); + performanceCounterCollection.Reset(); + } + } + } + + public void ResetSource(string name) + { + foreach (var performanceCounterCollection in this.PerformanceCounterCollections) + { + performanceCounterCollection.ResetSource(name); + } + } + + + public void AddAlert(AlertEntry entry) + { + this.Alerts.Add(entry); + } + + public void InitializePerformanceCounterEvents(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.Rendered, true), + new EventPerformanceCounterCollection(this, eventManager.RenderingWorld, true), + new EventPerformanceCounterCollection(this, eventManager.RenderedWorld, true), + new EventPerformanceCounterCollection(this, eventManager.RenderingActiveMenu, true), + new EventPerformanceCounterCollection(this, eventManager.RenderedActiveMenu, true), + new EventPerformanceCounterCollection(this, eventManager.RenderingHud, true), + 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, true), + new EventPerformanceCounterCollection(this, eventManager.ModMessageReceived, true), + new EventPerformanceCounterCollection(this, eventManager.PeerDisconnected, true), + new EventPerformanceCounterCollection(this, eventManager.InventoryChanged, true), + new EventPerformanceCounterCollection(this, eventManager.LevelChanged, true), + new EventPerformanceCounterCollection(this, eventManager.Warped, true), + + new EventPerformanceCounterCollection(this, eventManager.LocationListChanged, true), + new EventPerformanceCounterCollection(this, eventManager.BuildingListChanged, true), + new EventPerformanceCounterCollection(this, eventManager.LocationListChanged, true), + new EventPerformanceCounterCollection(this, eventManager.DebrisListChanged, true), + new EventPerformanceCounterCollection(this, eventManager.LargeTerrainFeatureListChanged, true), + new EventPerformanceCounterCollection(this, eventManager.NpcListChanged, true), + 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), }; } } |