using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using StardewModdingAPI.Framework.Events; namespace StardewModdingAPI.Framework.PerformanceCounter { internal class PerformanceCounterManager { public HashSet PerformanceCounterCollections = new HashSet(); /// The recorded alerts. private readonly List Alerts = new List(); /// The monitor for output logging. private readonly IMonitor Monitor; /// The invocation stopwatch. private readonly Stopwatch InvocationStopwatch = new Stopwatch(); /// Specifies if alerts should be paused. public bool PauseAlerts { get; set; } /// Constructs a performance counter manager. /// The monitor for output logging. public PerformanceCounterManager(IMonitor monitor) { this.Monitor = monitor; } /// Resets all performance counters in all collections. public void 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) { this.GetOrCreateCollectionByName(collectionName).BeginTrackInvocation(); } /// Ends tracking the invocation for a collection. /// public void EndTrackInvocation(string collectionName) { this.GetOrCreateCollectionByName(collectionName).EndTrackInvocation(); } /// Tracks 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. public void Track(string collectionName, string sourceName, Action action) { DateTime eventTime = DateTime.UtcNow; this.InvocationStopwatch.Reset(); this.InvocationStopwatch.Start(); try { action(); } finally { this.InvocationStopwatch.Stop(); this.GetOrCreateCollectionByName(collectionName).Track(sourceName, new PerformanceCounterEntry { EventTime = eventTime, ElapsedMilliseconds = 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. /// The collection name. public void ResetCollection(string name) { foreach (PerformanceCounterCollection performanceCounterCollection in this.PerformanceCounterCollections.Where(performanceCounterCollection => performanceCounterCollection.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))) { performanceCounterCollection.ResetCallsPerSecond(); performanceCounterCollection.Reset(); } } /// Resets performance counters for a specific source. /// The name of the source. public void ResetSource(string name) { foreach (PerformanceCounterCollection performanceCounterCollection in this.PerformanceCounterCollections) performanceCounterCollection.ResetSource(name); } /// Print any queued alerts. 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); } /// Adds an alert to the queue. /// The alert to add. public void AddAlert(AlertEntry entry) { if (!this.PauseAlerts) this.Alerts.Add(entry); } /// Initialized the default performance counter collections. /// The event manager. public void InitializePerformanceCounterCollections(EventManager eventManager) { this.PerformanceCounterCollections = new HashSet() { 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), }; } } }