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);
}
}
}