using System; using System.Collections.Generic; using System.Linq; using Harmony; namespace StardewModdingAPI.Framework.PerformanceMonitoring { /// Tracks metadata about a particular code event. internal class PerformanceCounter { /********* ** Fields *********/ /// The size of the ring buffer. private readonly int MaxEntries = 16384; /// The collection to which this performance counter belongs. private readonly PerformanceCounterCollection ParentCollection; /// The performance counter entries. private readonly Stack Entries; /// The entry with the highest execution time. private PerformanceCounterEntry? PeakPerformanceCounterEntry; /********* ** Accessors *********/ /// The name of the source. public string Source { get; } /// The alert threshold in milliseconds public double AlertThresholdMilliseconds { get; set; } /// 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.Entries = new Stack(this.MaxEntries); } /// Add a performance counter entry to the list, update monitoring, and raise alerts if needed. /// The entry to add. public void Add(PerformanceCounterEntry entry) { // add entry if (this.Entries.Count > this.MaxEntries) this.Entries.Pop(); this.Entries.Add(entry); // update metrics if (this.PeakPerformanceCounterEntry == null || 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)); } /// Clear all performance counter entries and monitoring. public void Reset() { this.Entries.Clear(); this.PeakPerformanceCounterEntry = null; } /// Get the peak entry. public PerformanceCounterEntry? GetPeak() { return this.PeakPerformanceCounterEntry; } /// 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) { endTime ??= DateTime.UtcNow; DateTime startTime = endTime.Value.Subtract(range); return this.Entries .Where(entry => entry.EventTime >= startTime && entry.EventTime <= endTime) .OrderByDescending(x => x.ElapsedMilliseconds) .FirstOrDefault(); } /// Get the last entry added to the list. public PerformanceCounterEntry? GetLastEntry() { if (this.Entries.Count == 0) return null; return this.Entries.Peek(); } /// 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) { endTime ??= DateTime.UtcNow; DateTime startTime = endTime.Value.Subtract(range); double[] entries = this.Entries .Where(entry => entry.EventTime >= startTime && entry.EventTime <= endTime) .Select(p => p.ElapsedMilliseconds) .ToArray(); return entries.Length > 0 ? entries.Average() : 0; } } }