Added peak execution time over the last 60 seconds

This commit is contained in:
Drachenkaetzchen 2020-01-15 19:08:50 +01:00
parent 238b5db4f7
commit 84973ce572
5 changed files with 96 additions and 12 deletions

View File

@ -118,17 +118,20 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
StringBuilder sb = new StringBuilder();
TimeSpan peakSpan = TimeSpan.FromSeconds(60);
sb.AppendLine("Summary:");
sb.AppendLine(this.GetTableString(
data: data,
header: new[] {"Collection", "Avg Calls/s", "Avg Execution Time (Game)", "Avg Execution Time (Mods)", "Avg Execution Time (Game+Mods)"},
header: new[] {"Collection", "Avg Calls/s", "Avg Exec Time (Game)", "Avg Exec Time (Mods)", "Avg Exec Time (Game+Mods)", "Peak Exec Time (60s)"},
getRow: item => new[]
{
item.Name,
item.GetAverageCallsPerSecond().ToString(),
this.FormatMilliseconds(item.GetGameAverageExecutionTime(), threshold),
this.FormatMilliseconds(item.GetModsAverageExecutionTime(), threshold),
this.FormatMilliseconds(item.GetAverageExecutionTime(), threshold)
this.FormatMilliseconds(item.GetAverageExecutionTime(), threshold),
this.FormatMilliseconds(item.GetPeakExecutionTime(peakSpan), threshold)
},
true
));
@ -459,13 +462,14 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
{
sb.AppendLine(this.GetTableString(
data: data,
header: new[] {"Mod", $"Avg Execution Time (last {(int) averageInterval.TotalSeconds}s)", "Last Execution Time", "Peak Execution Time"},
header: new[] {"Mod", $"Avg Exec Time (last {(int) averageInterval.TotalSeconds}s)", "Last Exec Time", "Peak Exec Time", $"Peak Exec Time (last {(int) averageInterval.TotalSeconds}s)"},
getRow: item => new[]
{
item.Key,
this.FormatMilliseconds(item.Value.GetAverage(averageInterval), thresholdMilliseconds),
this.FormatMilliseconds(item.Value.GetLastEntry()?.ElapsedMilliseconds),
this.FormatMilliseconds(item.Value.GetPeak()?.ElapsedMilliseconds)
this.FormatMilliseconds(item.Value.GetPeak()?.ElapsedMilliseconds),
this.FormatMilliseconds(item.Value.GetPeak(averageInterval)?.ElapsedMilliseconds)
},
true
));

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
namespace StardewModdingAPI.Framework.PerformanceCounter
{
internal struct PeakEntry
{
/// <summary>The actual execution time in milliseconds.</summary>
public readonly double ExecutionTimeMilliseconds;
/// <summary>The DateTime when the entry occured.</summary>
public DateTime EventTime;
/// <summary>The context list, which records all sources involved in exceeding the threshold.</summary>
public readonly List<AlertContext> Context;
public PeakEntry(double executionTimeMilliseconds, DateTime eventTime, List<AlertContext> context)
{
this.ExecutionTimeMilliseconds = executionTimeMilliseconds;
this.EventTime = eventTime;
this.Context = context;
}
}
}

View File

@ -68,6 +68,26 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
return this.PeakPerformanceCounterEntry;
}
/// <summary>Returns the peak entry.</summary>
/// <returns>The peak entry.</returns>
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();
}
/// <summary>Resets the peak entry.</summary>
public void ResetPeak()
{

View File

@ -2,11 +2,15 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Cyotek.Collections.Generic;
namespace StardewModdingAPI.Framework.PerformanceCounter
{
internal class PerformanceCounterCollection
{
/// <summary>The size of the ring buffer.</summary>
private const int MAX_ENTRIES = 16384;
/// <summary>The list of triggered performance counters.</summary>
private readonly List<AlertContext> TriggeredPerformanceCounters = new List<AlertContext>();
@ -22,6 +26,10 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
/// <summary>The number of invocations of this collection.</summary>
private long CallCount;
/// <summary>The circular buffer which stores all peak invocations</summary>
private readonly CircularBuffer<PeakEntry> PeakInvocations;
/// <summary>The associated performance counters.</summary>
public IDictionary<string, PerformanceCounter> PerformanceCounters { get; } = new Dictionary<string, PerformanceCounter>();
/// <summary>The name of this collection.</summary>
@ -39,6 +47,7 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
public PerformanceCounterCollection(PerformanceCounterManager performanceCounterManager, string name, bool isImportant)
{
this.PeakInvocations = new CircularBuffer<PeakEntry>(PerformanceCounterCollection.MAX_ENTRIES);
this.Name = name;
this.PerformanceCounterManager = performanceCounterManager;
this.IsImportant = isImportant;
@ -46,6 +55,7 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
public PerformanceCounterCollection(PerformanceCounterManager performanceCounterManager, string name)
{
this.PeakInvocations = new CircularBuffer<PeakEntry>(PerformanceCounterCollection.MAX_ENTRIES);
this.PerformanceCounterManager = performanceCounterManager;
this.Name = name;
}
@ -89,15 +99,30 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
return 0;
}
public double GetPeakExecutionTime(TimeSpan range, DateTime? relativeTo = null)
{
if (this.PeakInvocations.IsEmpty)
return 0;
if (relativeTo == null)
relativeTo = DateTime.UtcNow;
DateTime start = relativeTo.Value.Subtract(range);
var entries = this.PeakInvocations.Where(x => (x.EventTime >= start) && (x.EventTime <= relativeTo)).ToList();
if (!entries.Any())
return 0;
return entries.OrderByDescending(x => x.ExecutionTimeMilliseconds).First().ExecutionTimeMilliseconds;
}
/// <summary>Begins tracking the invocation of this collection.</summary>
public void BeginTrackInvocation()
{
if (this.EnableAlerts)
{
this.TriggeredPerformanceCounters.Clear();
this.InvocationStopwatch.Reset();
this.InvocationStopwatch.Start();
}
this.CallCount++;
}
@ -106,10 +131,15 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
/// and the invocation time exceeds the threshold.</summary>
public void EndTrackInvocation()
{
if (!this.EnableAlerts) return;
this.InvocationStopwatch.Stop();
this.PeakInvocations.Put(
new PeakEntry(this.InvocationStopwatch.Elapsed.TotalMilliseconds,
DateTime.UtcNow,
this.TriggeredPerformanceCounters));
if (!this.EnableAlerts) return;
if (this.InvocationStopwatch.Elapsed.TotalMilliseconds >= this.AlertThresholdMilliseconds)
this.AddAlert(this.InvocationStopwatch.Elapsed.TotalMilliseconds,
this.AlertThresholdMilliseconds, this.TriggeredPerformanceCounters);
@ -144,6 +174,7 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
/// <summary>Resets all performance counters in this collection.</summary>
public void Reset()
{
this.PeakInvocations.Clear();
foreach (var i in this.PerformanceCounters)
i.Value.Reset();
}

View File

@ -33,6 +33,11 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
/// <summary>Resets all performance counters in all collections.</summary>
public void Reset()
{
foreach (PerformanceCounterCollection collection in this.PerformanceCounterCollections)
{
collection.Reset();
}
foreach (var eventPerformanceCounter in
this.PerformanceCounterCollections.SelectMany(performanceCounter => performanceCounter.PerformanceCounters))
{