using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; namespace StardewModdingAPI.Framework.PerformanceCounter { internal class PerformanceCounterCollection { /// The list of triggered performance counters. 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; /// Holds the time to calculate the average calls per second. private DateTime CallsPerSecondStart = DateTime.UtcNow; /// The number of invocations of this collection. private long CallCount; 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; } /// The alert threshold in milliseconds. public double AlertThresholdMilliseconds { get; set; } /// If alerting is enabled or not public bool EnableAlerts { get; set; } 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; } /// Tracks a single invocation for a named source. /// The name of the source. /// The entry. 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.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 overall average execution time. /// The average execution time in milliseconds public double GetAverageExecutionTime() { return this.PerformanceCounters.Sum(p => p.Value.GetAverage()); } /// 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; } /// Begins tracking the invocation of this collection. public void BeginTrackInvocation() { if (this.EnableAlerts) { this.TriggeredPerformanceCounters.Clear(); this.InvocationStopwatch.Reset(); this.InvocationStopwatch.Start(); } this.CallCount++; } /// Ends tracking the invocation of this collection. Also records an alert if alerting is enabled /// and the invocation time exceeds the threshold. public void EndTrackInvocation() { if (!this.EnableAlerts) return; this.InvocationStopwatch.Stop(); if (this.InvocationStopwatch.Elapsed.TotalMilliseconds >= this.AlertThresholdMilliseconds) this.AddAlert(this.InvocationStopwatch.Elapsed.TotalMilliseconds, this.AlertThresholdMilliseconds, this.TriggeredPerformanceCounters); } /// Adds an alert. /// The execution time in milliseconds. /// The configured threshold. /// The list of alert contexts. public void AddAlert(double executionTimeMilliseconds, double thresholdMilliseconds, List alerts) { this.PerformanceCounterManager.AddAlert(new AlertEntry(this, executionTimeMilliseconds, thresholdMilliseconds, alerts)); } /// Adds an alert for a single AlertContext /// The execution time in milliseconds. /// The configured threshold. /// The context public void AddAlert(double executionTimeMilliseconds, double thresholdMilliseconds, AlertContext alert) { this.AddAlert(executionTimeMilliseconds, thresholdMilliseconds, new List() {alert}); } /// Resets the calls per second counter. public void ResetCallsPerSecond() { this.CallCount = 0; this.CallsPerSecondStart = DateTime.UtcNow; } /// Resets all performance counters in this collection. public void Reset() { foreach (var i in this.PerformanceCounters) i.Value.Reset(); } /// Resets the performance counter for a specific source. /// The source name public void ResetSource(string source) { foreach (var i in this.PerformanceCounters) if (i.Value.Source.Equals(source, StringComparison.InvariantCultureIgnoreCase)) i.Value.Reset(); } /// Returns the average calls per second. /// 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; } } }