using System; using System.Linq; using Cyotek.Collections.Generic; namespace StardewModdingAPI.Framework.PerformanceCounter { internal class PerformanceCounter { /// The size of the ring buffer. private const int MAX_ENTRIES = 16384; /// The collection to which this performance counter belongs. private readonly PerformanceCounterCollection ParentCollection; /// The circular buffer which stores all performance counter entries private readonly CircularBuffer _counter; /// The peak execution time private PerformanceCounterEntry? PeakPerformanceCounterEntry; /// 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 PerformanceCounter(PerformanceCounterCollection parentCollection, string source) { this.ParentCollection = parentCollection; this.Source = source; this._counter = new CircularBuffer(PerformanceCounter.MAX_ENTRIES); } /// 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. /// The entry to add. public void Add(PerformanceCounterEntry entry) { this._counter.Put(entry); 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.ElapsedMilliseconds > this.PeakPerformanceCounterEntry.Value.ElapsedMilliseconds) this.PeakPerformanceCounterEntry = entry; } } /// Clears all performance counter entries and resets the peak entry. public void Reset() { this._counter.Clear(); this.PeakPerformanceCounterEntry = null; } /// Returns the peak entry. /// The peak entry. public PerformanceCounterEntry? GetPeak() { return this.PeakPerformanceCounterEntry; } /// Returns the peak entry. /// The peak entry. public PerformanceCounterEntry? GetPeak(TimeSpan range, DateTime? relativeTo = null) { if (this._counter.IsEmpty) return null; if (relativeTo == null) relativeTo = DateTime.UtcNow; DateTime start = relativeTo.Value.Subtract(range); var entries = this._counter.Where(x => (x.EventTime >= start) && (x.EventTime <= relativeTo)).ToList(); if (!entries.Any()) return null; return entries.OrderByDescending(x => x.ElapsedMilliseconds).First(); } /// Resets the peak entry. public void ResetPeak() { this.PeakPerformanceCounterEntry = null; } /// Returns the last entry added to the list. /// The last entry public PerformanceCounterEntry? GetLastEntry() { if (this._counter.IsEmpty) return null; return this._counter.PeekLast(); } /// Returns the average execution time of all entries. /// The average execution time in milliseconds. public double GetAverage() { if (this._counter.IsEmpty) return 0; return this._counter.Average(p => p.ElapsedMilliseconds); } /// Returns the average over a given time span. /// The time range to retrieve. /// The DateTime from which to start the average. Defaults to DateTime.UtcNow if null /// The average execution time in milliseconds. /// /// 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. /// public double GetAverage(TimeSpan range, DateTime? relativeTo = null) { if (this._counter.IsEmpty) return 0; if (relativeTo == null) relativeTo = DateTime.UtcNow; DateTime start = relativeTo.Value.Subtract(range); var entries = this._counter.Where(x => (x.EventTime >= start) && (x.EventTime <= relativeTo)); if (!entries.Any()) return 0; return entries.Average(x => x.ElapsedMilliseconds); } } }