using System;
using System.Collections.Generic;
using System.Linq;
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.Push(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;
}
}
}