refactor performance counter code
This commit performs some general refactoring, including... - avoid manually duplicating the event list; - rework the 'is important' event flag; - remove the new packages (Cyotek.Collections can be replaced with built-in types, and System.ValueTuple won't work in the Mono version used on Linux/Mac); - improve performance; - minor cleanup.
This commit is contained in:
parent
1b905205a3
commit
22a0a32b6d
|
@ -32,7 +32,6 @@
|
|||
<Copy SourceFiles="$(TargetDir)\SMAPI.metadata.json" DestinationFiles="$(GamePath)\smapi-internal\metadata.json" />
|
||||
<Copy SourceFiles="$(TargetDir)\0Harmony.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\Newtonsoft.Json.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\Cyotek.Collections.Generic.CircularBuffer.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="$(TargetDir)\Mono.Cecil.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||
<Copy SourceFiles="@(TranslationFiles)" DestinationFolder="$(GamePath)\smapi-internal\i18n" />
|
||||
</Target>
|
||||
|
|
|
@ -41,7 +41,6 @@
|
|||
<Copy SourceFiles="$(CompiledSmapiPath)\0Harmony.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
<Copy SourceFiles="$(CompiledSmapiPath)\Mono.Cecil.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
<Copy SourceFiles="$(CompiledSmapiPath)\Newtonsoft.Json.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
<Copy SourceFiles="$(CompiledSmapiPath)\Cyotek.Collections.Generic.CircularBuffer.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
<Copy SourceFiles="$(CompiledSmapiPath)\SMAPI.config.json" DestinationFiles="$(PackagePath)\bundle\smapi-internal\config.json" />
|
||||
<Copy SourceFiles="$(CompiledSmapiPath)\SMAPI.metadata.json" DestinationFiles="$(PackagePath)\bundle\smapi-internal\metadata.json" />
|
||||
<Copy SourceFiles="$(CompiledToolkitPath)\SMAPI.Toolkit.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
@ -32,13 +32,6 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands
|
|||
/// <param name="index">The zero-based index of the element to get.</param>
|
||||
public string this[int index] => this.Args[index];
|
||||
|
||||
/// <summary>A method which parses a string argument into the given value.</summary>
|
||||
/// <typeparam name="T">The expected argument type.</typeparam>
|
||||
/// <param name="input">The argument to parse.</param>
|
||||
/// <param name="output">The parsed value.</param>
|
||||
/// <returns>Returns whether the argument was successfully parsed.</returns>
|
||||
public delegate bool ParseDelegate<T>(string input, out T output);
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
|
@ -114,19 +107,6 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool IsDecimal(int index)
|
||||
{
|
||||
if (!this.TryGet(index, "", out string raw, false))
|
||||
return false;
|
||||
|
||||
if (!decimal.TryParse(raw, NumberStyles.Number, CultureInfo.InvariantCulture, out decimal value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Try to read a decimal argument.</summary>
|
||||
/// <param name="index">The argument index.</param>
|
||||
/// <param name="name">The argument name for error messages.</param>
|
||||
|
|
|
@ -7,24 +7,13 @@ using StardewModdingAPI.Framework.PerformanceCounter;
|
|||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
||||
{
|
||||
// ReSharper disable once UnusedType.Global
|
||||
/// <summary>A set of commands which displays or configures performance monitoring.</summary>
|
||||
internal class PerformanceCounterCommand : TrainerCommand
|
||||
{
|
||||
/// <summary>The command names and aliases</summary>
|
||||
private readonly Dictionary<SubCommand, string[]> SubCommandNames = new Dictionary<SubCommand, string[]>()
|
||||
{
|
||||
{SubCommand.Summary, new[] {"summary", "sum", "s"}},
|
||||
{SubCommand.Detail, new[] {"detail", "d"}},
|
||||
{SubCommand.Reset, new[] {"reset", "r"}},
|
||||
{SubCommand.Trigger, new[] {"trigger"}},
|
||||
{SubCommand.Enable, new[] {"enable"}},
|
||||
{SubCommand.Disable, new[] {"disable"}},
|
||||
{SubCommand.Examples, new[] {"examples"}},
|
||||
{SubCommand.Concepts, new[] {"concepts"}},
|
||||
{SubCommand.Help, new[] {"help"}},
|
||||
};
|
||||
|
||||
/// <summary>The available commands enum</summary>
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The available commands.</summary>
|
||||
private enum SubCommand
|
||||
{
|
||||
Summary,
|
||||
|
@ -33,16 +22,16 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
Trigger,
|
||||
Enable,
|
||||
Disable,
|
||||
Examples,
|
||||
Help,
|
||||
Concepts,
|
||||
None
|
||||
Help
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public PerformanceCounterCommand() : base("pc", PerformanceCounterCommand.GetDescription())
|
||||
{
|
||||
}
|
||||
public PerformanceCounterCommand()
|
||||
: base("performance", PerformanceCounterCommand.GetDescription()) { }
|
||||
|
||||
/// <summary>Handle the command.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
|
@ -50,92 +39,94 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
/// <param name="args">The command arguments.</param>
|
||||
public override void Handle(IMonitor monitor, string command, ArgumentParser args)
|
||||
{
|
||||
if (args.TryGet(0, "command", out string subCommandString, false))
|
||||
// parse args
|
||||
SubCommand subcommand = SubCommand.Summary;
|
||||
{
|
||||
SubCommand subSubCommand = this.ParseCommandString(subCommandString);
|
||||
|
||||
switch (subSubCommand)
|
||||
if (args.TryGet(0, "command", out string subcommandStr, false) && !Enum.TryParse(subcommandStr, ignoreCase: true, out subcommand))
|
||||
{
|
||||
case SubCommand.Summary:
|
||||
this.HandleSummarySubCommand(monitor, args);
|
||||
break;
|
||||
case SubCommand.Detail:
|
||||
this.HandleDetailSubCommand(monitor, args);
|
||||
break;
|
||||
case SubCommand.Reset:
|
||||
this.HandleResetSubCommand(monitor, args);
|
||||
break;
|
||||
case SubCommand.Trigger:
|
||||
this.HandleTriggerSubCommand(monitor, args);
|
||||
break;
|
||||
case SubCommand.Examples:
|
||||
break;
|
||||
case SubCommand.Concepts:
|
||||
this.OutputHelp(monitor, SubCommand.Concepts);
|
||||
break;
|
||||
case SubCommand.Enable:
|
||||
SCore.PerformanceCounterManager.EnableTracking = true;
|
||||
monitor.Log("Performance counter tracking is now enabled", LogLevel.Info);
|
||||
break;
|
||||
case SubCommand.Disable:
|
||||
SCore.PerformanceCounterManager.EnableTracking = false;
|
||||
monitor.Log("Performance counter tracking is now disabled", LogLevel.Info);
|
||||
break;
|
||||
case SubCommand.Help:
|
||||
if (args.TryGet(1, "command", out string commandString))
|
||||
this.OutputHelp(monitor, this.ParseCommandString(commandString));
|
||||
break;
|
||||
default:
|
||||
this.LogUsageError(monitor, $"Unknown command {subCommandString}");
|
||||
break;
|
||||
this.LogUsageError(monitor, $"Unknown command {subcommandStr}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
this.HandleSummarySubCommand(monitor, args);
|
||||
|
||||
// handle
|
||||
switch (subcommand)
|
||||
{
|
||||
case SubCommand.Summary:
|
||||
this.HandleSummarySubCommand(monitor, args);
|
||||
break;
|
||||
|
||||
case SubCommand.Detail:
|
||||
this.HandleDetailSubCommand(monitor, args);
|
||||
break;
|
||||
|
||||
case SubCommand.Reset:
|
||||
this.HandleResetSubCommand(monitor, args);
|
||||
break;
|
||||
|
||||
case SubCommand.Trigger:
|
||||
this.HandleTriggerSubCommand(monitor, args);
|
||||
break;
|
||||
|
||||
case SubCommand.Enable:
|
||||
SCore.PerformanceMonitor.EnableTracking = true;
|
||||
monitor.Log("Performance counter tracking is now enabled", LogLevel.Info);
|
||||
break;
|
||||
|
||||
case SubCommand.Disable:
|
||||
SCore.PerformanceMonitor.EnableTracking = false;
|
||||
monitor.Log("Performance counter tracking is now disabled", LogLevel.Info);
|
||||
break;
|
||||
|
||||
case SubCommand.Help:
|
||||
this.OutputHelp(monitor, args.TryGet(1, "command", out _) ? subcommand : null as SubCommand?);
|
||||
break;
|
||||
|
||||
default:
|
||||
this.LogUsageError(monitor, $"Unknown command {subcommand}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Handles the summary sub command.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <param name="args">The command arguments.</param>
|
||||
private void HandleSummarySubCommand(IMonitor monitor, ArgumentParser args)
|
||||
{
|
||||
IEnumerable<PerformanceCounterCollection> data;
|
||||
|
||||
if (!args.TryGet(1, "mode", out string mode, false))
|
||||
{
|
||||
mode = "important";
|
||||
}
|
||||
|
||||
IEnumerable<PerformanceCounterCollection> data = SCore.PerformanceMonitor.GetCollections();
|
||||
switch (mode)
|
||||
{
|
||||
case null:
|
||||
case "important":
|
||||
data = SCore.PerformanceCounterManager.PerformanceCounterCollections.Where(p => p.IsImportant);
|
||||
data = data.Where(p => p.IsPerformanceCritical);
|
||||
break;
|
||||
|
||||
case "all":
|
||||
data = SCore.PerformanceCounterManager.PerformanceCounterCollections;
|
||||
break;
|
||||
|
||||
default:
|
||||
data = SCore.PerformanceCounterManager.PerformanceCounterCollections.Where(p =>
|
||||
p.Name.ToLowerInvariant().Contains(mode.ToLowerInvariant()));
|
||||
data = data.Where(p => p.Name.ToLowerInvariant().Contains(mode.ToLowerInvariant()));
|
||||
break;
|
||||
}
|
||||
|
||||
double? threshold = null;
|
||||
|
||||
if (args.TryGetDecimal(2, "threshold", out decimal t, false))
|
||||
{
|
||||
threshold = (double?) t;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
threshold = (double?)t;
|
||||
|
||||
TimeSpan interval = TimeSpan.FromSeconds(60);
|
||||
|
||||
sb.AppendLine($"Summary over the last {interval.TotalSeconds} seconds:");
|
||||
sb.AppendLine(this.GetTableString(
|
||||
StringBuilder report = new StringBuilder();
|
||||
report.AppendLine($"Summary over the last {interval.TotalSeconds} seconds:");
|
||||
report.AppendLine(this.GetTableString(
|
||||
data: data,
|
||||
header: new[] {"Collection", "Avg Calls/s", "Avg Exec Time (Game)", "Avg Exec Time (Mods)", "Avg Exec Time (Game+Mods)", "Peak Exec Time"},
|
||||
header: new[] { "Collection", "Avg Calls/s", "Avg Exec Time (Game)", "Avg Exec Time (Mods)", "Avg Exec Time (Game+Mods)", "Peak Exec Time" },
|
||||
getRow: item => new[]
|
||||
{
|
||||
item.Name,
|
||||
|
@ -148,7 +139,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
true
|
||||
));
|
||||
|
||||
monitor.Log(sb.ToString(), LogLevel.Info);
|
||||
monitor.Log(report.ToString(), LogLevel.Info);
|
||||
}
|
||||
|
||||
/// <summary>Handles the detail sub command.</summary>
|
||||
|
@ -163,26 +154,16 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
|
||||
if (args.TryGet(1, "collection", out string collectionName))
|
||||
{
|
||||
collections.AddRange(SCore.PerformanceCounterManager.PerformanceCounterCollections.Where(
|
||||
collection => collection.Name.ToLowerInvariant().Contains(collectionName.ToLowerInvariant())));
|
||||
collections.AddRange(SCore.PerformanceMonitor.GetCollections().Where(collection => collection.Name.ToLowerInvariant().Contains(collectionName.ToLowerInvariant())));
|
||||
|
||||
if (args.IsDecimal(2) && args.TryGetDecimal(2, "threshold", out decimal value, false))
|
||||
{
|
||||
thresholdMilliseconds = (double?) value;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (args.TryGet(2, "source", out string sourceName, false))
|
||||
{
|
||||
sourceFilter = sourceName;
|
||||
}
|
||||
}
|
||||
if (args.Count >= 2 && decimal.TryParse(args[2], out _) && args.TryGetDecimal(2, "threshold", out decimal value, false))
|
||||
thresholdMilliseconds = (double?)value;
|
||||
else if (args.TryGet(2, "source", out string sourceName, false))
|
||||
sourceFilter = sourceName;
|
||||
}
|
||||
|
||||
foreach (PerformanceCounterCollection c in collections)
|
||||
{
|
||||
this.OutputPerformanceCollectionDetail(monitor, c, averageInterval, thresholdMilliseconds, sourceFilter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Handles the trigger sub command.</summary>
|
||||
|
@ -197,46 +178,44 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
case "list":
|
||||
this.OutputAlertTriggers(monitor);
|
||||
break;
|
||||
|
||||
case "collection":
|
||||
if (args.TryGet(2, "name", out string collectionName))
|
||||
{
|
||||
if (args.TryGetDecimal(3, "threshold", out decimal threshold))
|
||||
{
|
||||
if (args.TryGet(4, "source", out string source, false))
|
||||
{
|
||||
this.ConfigureAlertTrigger(monitor, collectionName, source, threshold);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ConfigureAlertTrigger(monitor, collectionName, null, threshold);
|
||||
}
|
||||
if (!args.TryGet(4, "source", out string source, required: false))
|
||||
source = null;
|
||||
this.ConfigureAlertTrigger(monitor, collectionName, source, threshold);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "pause":
|
||||
SCore.PerformanceCounterManager.PauseAlerts = true;
|
||||
monitor.Log($"Alerts are now paused.", LogLevel.Info);
|
||||
SCore.PerformanceMonitor.PauseAlerts = true;
|
||||
monitor.Log("Alerts are now paused.", LogLevel.Info);
|
||||
break;
|
||||
|
||||
case "resume":
|
||||
SCore.PerformanceCounterManager.PauseAlerts = false;
|
||||
monitor.Log($"Alerts are now resumed.", LogLevel.Info);
|
||||
SCore.PerformanceMonitor.PauseAlerts = false;
|
||||
monitor.Log("Alerts are now resumed.", LogLevel.Info);
|
||||
break;
|
||||
|
||||
case "dump":
|
||||
this.OutputAlertTriggers(monitor, true);
|
||||
break;
|
||||
|
||||
case "clear":
|
||||
this.ClearAlertTriggers(monitor);
|
||||
break;
|
||||
|
||||
default:
|
||||
this.LogUsageError(monitor, $"Unknown mode {mode}. See 'pc help trigger' for usage.");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
this.OutputAlertTriggers(monitor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Sets up an an alert trigger.</summary>
|
||||
|
@ -246,7 +225,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
/// <param name="threshold">The trigger threshold, or 0 to remove.</param>
|
||||
private void ConfigureAlertTrigger(IMonitor monitor, string collectionName, string sourceName, decimal threshold)
|
||||
{
|
||||
foreach (PerformanceCounterCollection collection in SCore.PerformanceCounterManager.PerformanceCounterCollections)
|
||||
foreach (PerformanceCounterCollection collection in SCore.PerformanceMonitor.GetCollections())
|
||||
{
|
||||
if (collection.Name.ToLowerInvariant().Equals(collectionName.ToLowerInvariant()))
|
||||
{
|
||||
|
@ -255,8 +234,8 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
if (threshold != 0)
|
||||
{
|
||||
collection.EnableAlerts = true;
|
||||
collection.AlertThresholdMilliseconds = (double) threshold;
|
||||
monitor.Log($"Set up alert triggering for '{collectionName}' with '{this.FormatMilliseconds((double?) threshold)}'", LogLevel.Info);
|
||||
collection.AlertThresholdMilliseconds = (double)threshold;
|
||||
monitor.Log($"Set up alert triggering for '{collectionName}' with '{this.FormatMilliseconds((double?)threshold)}'", LogLevel.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -275,14 +254,11 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
if (threshold != 0)
|
||||
{
|
||||
performanceCounter.Value.EnableAlerts = true;
|
||||
performanceCounter.Value.AlertThresholdMilliseconds = (double) threshold;
|
||||
monitor.Log($"Set up alert triggering for '{sourceName}' in collection '{collectionName}' with '{this.FormatMilliseconds((double?) threshold)}", LogLevel.Info);
|
||||
performanceCounter.Value.AlertThresholdMilliseconds = (double)threshold;
|
||||
monitor.Log($"Set up alert triggering for '{sourceName}' in collection '{collectionName}' with '{this.FormatMilliseconds((double?)threshold)}", LogLevel.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
performanceCounter.Value.EnableAlerts = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -302,7 +278,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
private void ClearAlertTriggers(IMonitor monitor)
|
||||
{
|
||||
int clearedTriggers = 0;
|
||||
foreach (PerformanceCounterCollection collection in SCore.PerformanceCounterManager.PerformanceCounterCollections)
|
||||
foreach (PerformanceCounterCollection collection in SCore.PerformanceMonitor.GetCollections())
|
||||
{
|
||||
if (collection.EnableAlerts)
|
||||
{
|
||||
|
@ -329,92 +305,75 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
/// <param name="asDump">True to dump the triggers as commands.</param>
|
||||
private void OutputAlertTriggers(IMonitor monitor, bool asDump = false)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("Configured triggers:");
|
||||
sb.AppendLine();
|
||||
var collectionTriggers = new List<(string collectionName, double threshold)>();
|
||||
var sourceTriggers = new List<(string collectionName, string sourceName, double threshold)>();
|
||||
StringBuilder report = new StringBuilder();
|
||||
report.AppendLine("Configured triggers:");
|
||||
report.AppendLine();
|
||||
var collectionTriggers = new List<CollectionTrigger>();
|
||||
var sourceTriggers = new List<SourceTrigger>();
|
||||
|
||||
foreach (PerformanceCounterCollection collection in SCore.PerformanceCounterManager.PerformanceCounterCollections)
|
||||
foreach (PerformanceCounterCollection collection in SCore.PerformanceMonitor.GetCollections())
|
||||
{
|
||||
if (collection.EnableAlerts)
|
||||
{
|
||||
collectionTriggers.Add((collection.Name, collection.AlertThresholdMilliseconds));
|
||||
}
|
||||
collectionTriggers.Add(new CollectionTrigger(collection.Name, collection.AlertThresholdMilliseconds));
|
||||
|
||||
sourceTriggers.AddRange(from performanceCounter in
|
||||
collection.PerformanceCounters where performanceCounter.Value.EnableAlerts
|
||||
select (collection.Name, performanceCounter.Value.Source, performanceCounter.Value.AlertThresholdMilliseconds));
|
||||
sourceTriggers.AddRange(
|
||||
from counter in collection.PerformanceCounters
|
||||
where counter.Value.EnableAlerts
|
||||
select new SourceTrigger(collection.Name, counter.Value.Source, counter.Value.AlertThresholdMilliseconds)
|
||||
);
|
||||
}
|
||||
|
||||
if (collectionTriggers.Count > 0)
|
||||
{
|
||||
sb.AppendLine("Collection Triggers:");
|
||||
sb.AppendLine();
|
||||
report.AppendLine("Collection Triggers:");
|
||||
report.AppendLine();
|
||||
|
||||
if (asDump)
|
||||
{
|
||||
foreach (var item in collectionTriggers)
|
||||
{
|
||||
sb.AppendLine($"pc trigger {item.collectionName} {item.threshold}");
|
||||
}
|
||||
report.AppendLine($"pc trigger {item.CollectionName} {item.Threshold}");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine(this.GetTableString(
|
||||
report.AppendLine(this.GetTableString(
|
||||
data: collectionTriggers,
|
||||
header: new[] {"Collection", "Threshold"},
|
||||
getRow: item => new[]
|
||||
{
|
||||
item.collectionName,
|
||||
this.FormatMilliseconds(item.threshold)
|
||||
},
|
||||
header: new[] { "Collection", "Threshold" },
|
||||
getRow: item => new[] { item.CollectionName, this.FormatMilliseconds(item.Threshold) },
|
||||
true
|
||||
));
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
report.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine("No collection triggers.");
|
||||
}
|
||||
report.AppendLine("No collection triggers.");
|
||||
|
||||
if (sourceTriggers.Count > 0)
|
||||
{
|
||||
sb.AppendLine("Source Triggers:");
|
||||
sb.AppendLine();
|
||||
report.AppendLine("Source Triggers:");
|
||||
report.AppendLine();
|
||||
|
||||
if (asDump)
|
||||
{
|
||||
foreach (var item in sourceTriggers)
|
||||
{
|
||||
sb.AppendLine($"pc trigger {item.collectionName} {item.threshold} {item.sourceName}");
|
||||
}
|
||||
foreach (SourceTrigger item in sourceTriggers)
|
||||
report.AppendLine($"pc trigger {item.CollectionName} {item.Threshold} {item.SourceName}");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine(this.GetTableString(
|
||||
report.AppendLine(this.GetTableString(
|
||||
data: sourceTriggers,
|
||||
header: new[] {"Collection", "Source", "Threshold"},
|
||||
getRow: item => new[]
|
||||
{
|
||||
item.collectionName,
|
||||
item.sourceName,
|
||||
this.FormatMilliseconds(item.threshold)
|
||||
},
|
||||
header: new[] { "Collection", "Source", "Threshold" },
|
||||
getRow: item => new[] { item.CollectionName, item.SourceName, this.FormatMilliseconds(item.Threshold) },
|
||||
true
|
||||
));
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
report.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine("No source triggers.");
|
||||
}
|
||||
report.AppendLine("No source triggers.");
|
||||
|
||||
monitor.Log(sb.ToString(), LogLevel.Info);
|
||||
monitor.Log(report.ToString(), LogLevel.Info);
|
||||
}
|
||||
|
||||
/// <summary>Handles the reset sub command.</summary>
|
||||
|
@ -422,25 +381,25 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
/// <param name="args">The command arguments.</param>
|
||||
private void HandleResetSubCommand(IMonitor monitor, ArgumentParser args)
|
||||
{
|
||||
if (args.TryGet(1, "type", out string type, false, new []{"category", "source"}))
|
||||
if (args.TryGet(1, "type", out string type, false, new[] { "category", "source" }))
|
||||
{
|
||||
args.TryGet(2, "name", out string name);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case "category":
|
||||
SCore.PerformanceCounterManager.ResetCollection(name);
|
||||
SCore.PerformanceMonitor.ResetCollection(name);
|
||||
monitor.Log($"All performance counters for category {name} are now cleared.", LogLevel.Info);
|
||||
break;
|
||||
case "source":
|
||||
SCore.PerformanceCounterManager.ResetSource(name);
|
||||
SCore.PerformanceMonitor.ResetSource(name);
|
||||
monitor.Log($"All performance counters for source {name} are now cleared.", LogLevel.Info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SCore.PerformanceCounterManager.Reset();
|
||||
SCore.PerformanceMonitor.Reset();
|
||||
monitor.Log("All performance counters are now cleared.", LogLevel.Info);
|
||||
}
|
||||
}
|
||||
|
@ -455,7 +414,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
private void OutputPerformanceCollectionDetail(IMonitor monitor, PerformanceCounterCollection collection,
|
||||
TimeSpan averageInterval, double? thresholdMilliseconds, string sourceFilter = null)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder($"Performance Counter for {collection.Name}:\n\n");
|
||||
StringBuilder report = new StringBuilder($"Performance Counter for {collection.Name}:\n\n");
|
||||
|
||||
List<KeyValuePair<string, PerformanceCounter>> data = collection.PerformanceCounters.ToList();
|
||||
|
||||
|
@ -466,15 +425,13 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
}
|
||||
|
||||
if (thresholdMilliseconds != null)
|
||||
{
|
||||
data = data.Where(p => p.Value.GetAverage(averageInterval) >= thresholdMilliseconds).ToList();
|
||||
}
|
||||
|
||||
if (data.Any())
|
||||
{
|
||||
sb.AppendLine(this.GetTableString(
|
||||
report.AppendLine(this.GetTableString(
|
||||
data: data,
|
||||
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)"},
|
||||
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,
|
||||
|
@ -488,28 +445,13 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
}
|
||||
else
|
||||
{
|
||||
sb.Clear();
|
||||
sb.AppendLine($"Performance Counter for {collection.Name}: none.");
|
||||
report.Clear();
|
||||
report.AppendLine($"Performance Counter for {collection.Name}: none.");
|
||||
}
|
||||
|
||||
monitor.Log(sb.ToString(), LogLevel.Info);
|
||||
monitor.Log(report.ToString(), LogLevel.Info);
|
||||
}
|
||||
|
||||
/// <summary>Parses a command string and returns the associated command.</summary>
|
||||
/// <param name="commandString">The command string</param>
|
||||
/// <returns>The parsed command.</returns>
|
||||
private SubCommand ParseCommandString(string commandString)
|
||||
{
|
||||
foreach (var i in this.SubCommandNames.Where(i =>
|
||||
i.Value.Any(str => str.Equals(commandString, StringComparison.InvariantCultureIgnoreCase))))
|
||||
{
|
||||
return i.Key;
|
||||
}
|
||||
|
||||
return SubCommand.None;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Formats the given milliseconds value into a string format. Optionally
|
||||
/// allows a threshold to return "-" if the value is less than the threshold.</summary>
|
||||
/// <param name="milliseconds">The milliseconds to format. Returns "-" if null</param>
|
||||
|
@ -518,175 +460,212 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
private string FormatMilliseconds(double? milliseconds, double? thresholdMilliseconds = null)
|
||||
{
|
||||
if (milliseconds == null || (thresholdMilliseconds != null && milliseconds < thresholdMilliseconds))
|
||||
{
|
||||
return "-";
|
||||
}
|
||||
|
||||
return ((double) milliseconds).ToString("F2");
|
||||
return ((double)milliseconds).ToString("F2");
|
||||
}
|
||||
|
||||
/// <summary>Shows detailed help for a specific sub command.</summary>
|
||||
/// <param name="monitor">The output monitor</param>
|
||||
/// <param name="subCommand">The sub command</param>
|
||||
private void OutputHelp(IMonitor monitor, SubCommand subCommand)
|
||||
/// <param name="monitor">The output monitor.</param>
|
||||
/// <param name="subcommand">The subcommand.</param>
|
||||
private void OutputHelp(IMonitor monitor, SubCommand? subcommand)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine();
|
||||
StringBuilder report = new StringBuilder();
|
||||
report.AppendLine();
|
||||
|
||||
switch (subCommand)
|
||||
switch (subcommand)
|
||||
{
|
||||
case SubCommand.Concepts:
|
||||
sb.AppendLine("A performance counter is a metric which measures execution time. Each performance");
|
||||
sb.AppendLine("counter consists of:");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" - A source, which typically is a mod or the game itself.");
|
||||
sb.AppendLine(" - A ring buffer which stores the data points (execution time and time when it was executed)");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("A set of performance counters is organized in a collection to group various areas.");
|
||||
sb.AppendLine("Per default, collections for all game events [1] are created.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Example:");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("The performance counter collection named 'Display.Rendered' contains one performance");
|
||||
sb.AppendLine("counters when the game executes the 'Display.Rendered' event, and one additional");
|
||||
sb.AppendLine("performance counter for each mod which handles the 'Display.Rendered' event.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("[1] https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events");
|
||||
break;
|
||||
case SubCommand.Detail:
|
||||
sb.AppendLine("Usage: pc detail <collection> <source>");
|
||||
sb.AppendLine(" pc detail <collection> <threshold>");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Displays details for a specific collection.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Arguments:");
|
||||
sb.AppendLine(" <collection> Required. The full or partial name of the collection to display.");
|
||||
sb.AppendLine(" <source> Optional. The full or partial name of the source.");
|
||||
sb.AppendLine(" <threshold> Optional. The threshold in milliseconds. Any average execution time below that");
|
||||
sb.AppendLine(" threshold is not reported.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Examples:");
|
||||
sb.AppendLine("pc detail Display.Rendering Displays all performance counters for the 'Display.Rendering' collection");
|
||||
sb.AppendLine("pc detail Display.Rendering Pathoschild.ChestsAnywhere Displays the 'Display.Rendering' performance counter for 'Pathoschild.ChestsAnywhere'");
|
||||
sb.AppendLine("pc detail Display.Rendering 5 Displays the 'Display.Rendering' performance counters exceeding an average of 5ms");
|
||||
report.AppendLine("Usage: pc detail <collection> <source>");
|
||||
report.AppendLine(" pc detail <collection> <threshold>");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Displays details for a specific collection.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Arguments:");
|
||||
report.AppendLine(" <collection> Required. The full or partial name of the collection to display.");
|
||||
report.AppendLine(" <source> Optional. The full or partial name of the source.");
|
||||
report.AppendLine(" <threshold> Optional. The threshold in milliseconds. Any average execution time below that");
|
||||
report.AppendLine(" threshold is not reported.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Examples:");
|
||||
report.AppendLine("pc detail Display.Rendering Displays all performance counters for the 'Display.Rendering' collection");
|
||||
report.AppendLine("pc detail Display.Rendering Pathoschild.ChestsAnywhere Displays the 'Display.Rendering' performance counter for 'Pathoschild.ChestsAnywhere'");
|
||||
report.AppendLine("pc detail Display.Rendering 5 Displays the 'Display.Rendering' performance counters exceeding an average of 5ms");
|
||||
break;
|
||||
|
||||
case SubCommand.Summary:
|
||||
sb.AppendLine("Usage: pc summary <mode|name> <threshold>");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Displays the performance counter summary.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Arguments:");
|
||||
sb.AppendLine(" <mode> Optional. Defaults to 'important' if omitted. Specifies one of these modes:");
|
||||
sb.AppendLine(" - all Displays performance counters from all collections");
|
||||
sb.AppendLine(" - important Displays only important performance counter collections");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" <name> Optional. Only shows performance counter collections matching the given name");
|
||||
sb.AppendLine(" <threshold> Optional. Hides the actual execution time if it is below this threshold");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Examples:");
|
||||
sb.AppendLine("pc summary all Shows all events");
|
||||
sb.AppendLine("pc summary all 5 Shows all events");
|
||||
sb.AppendLine("pc summary Display.Rendering Shows only the 'Display.Rendering' collection");
|
||||
report.AppendLine("Usage: pc summary <mode|name> <threshold>");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Displays the performance counter summary.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Arguments:");
|
||||
report.AppendLine(" <mode> Optional. Defaults to 'important' if omitted. Specifies one of these modes:");
|
||||
report.AppendLine(" - all Displays performance counters from all collections");
|
||||
report.AppendLine(" - important Displays only important performance counter collections");
|
||||
report.AppendLine();
|
||||
report.AppendLine(" <name> Optional. Only shows performance counter collections matching the given name");
|
||||
report.AppendLine(" <threshold> Optional. Hides the actual execution time if it is below this threshold");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Examples:");
|
||||
report.AppendLine("pc summary all Shows all events");
|
||||
report.AppendLine("pc summary all 5 Shows all events");
|
||||
report.AppendLine("pc summary Display.Rendering Shows only the 'Display.Rendering' collection");
|
||||
break;
|
||||
|
||||
case SubCommand.Trigger:
|
||||
sb.AppendLine("Usage: pc trigger <mode>");
|
||||
sb.AppendLine("Usage: pc trigger collection <collectionName> <threshold>");
|
||||
sb.AppendLine("Usage: pc trigger collection <collectionName> <threshold> <sourceName>");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Manages alert triggers.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Arguments:");
|
||||
sb.AppendLine(" <mode> Optional. Specifies if a specific source or a specific collection should be triggered.");
|
||||
sb.AppendLine(" - list Lists current triggers");
|
||||
sb.AppendLine(" - collection Sets up a trigger for a collection");
|
||||
sb.AppendLine(" - clear Clears all trigger entries");
|
||||
sb.AppendLine(" - pause Pauses triggering of alerts");
|
||||
sb.AppendLine(" - resume Resumes triggering of alerts");
|
||||
sb.AppendLine(" - dump Dumps all triggers as commands for copy and paste");
|
||||
sb.AppendLine(" Defaults to 'list' if not specified.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" <collectionName> Required if the mode 'collection' is specified.");
|
||||
sb.AppendLine(" Specifies the name of the collection to be triggered. Must be an exact match.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" <sourceName> Optional. Specifies the name of a specific source. Must be an exact match.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" <threshold> Required if the mode 'collection' is specified.");
|
||||
sb.AppendLine(" Specifies the threshold in milliseconds (fractions allowed).");
|
||||
sb.AppendLine(" Specify '0' to remove the threshold.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Examples:");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("pc trigger collection Display.Rendering 10");
|
||||
sb.AppendLine(" Sets up an alert trigger which writes on the console if the execution time of all performance counters in");
|
||||
sb.AppendLine(" the 'Display.Rendering' collection exceed 10 milliseconds.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("pc trigger collection Display.Rendering 5 Pathoschild.ChestsAnywhere");
|
||||
sb.AppendLine(" Sets up an alert trigger to write on the console if the execution time of Pathoschild.ChestsAnywhere in");
|
||||
sb.AppendLine(" the 'Display.Rendering' collection exceed 5 milliseconds.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("pc trigger collection Display.Rendering 0");
|
||||
sb.AppendLine(" Removes the threshold previously defined from the collection. Note that source-specific thresholds are left intact.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("pc trigger clear");
|
||||
sb.AppendLine(" Clears all previously setup alert triggers.");
|
||||
report.AppendLine("Usage: pc trigger <mode>");
|
||||
report.AppendLine("Usage: pc trigger collection <collectionName> <threshold>");
|
||||
report.AppendLine("Usage: pc trigger collection <collectionName> <threshold> <sourceName>");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Manages alert triggers.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Arguments:");
|
||||
report.AppendLine(" <mode> Optional. Specifies if a specific source or a specific collection should be triggered.");
|
||||
report.AppendLine(" - list Lists current triggers");
|
||||
report.AppendLine(" - collection Sets up a trigger for a collection");
|
||||
report.AppendLine(" - clear Clears all trigger entries");
|
||||
report.AppendLine(" - pause Pauses triggering of alerts");
|
||||
report.AppendLine(" - resume Resumes triggering of alerts");
|
||||
report.AppendLine(" - dump Dumps all triggers as commands for copy and paste");
|
||||
report.AppendLine(" Defaults to 'list' if not specified.");
|
||||
report.AppendLine();
|
||||
report.AppendLine(" <collectionName> Required if the mode 'collection' is specified.");
|
||||
report.AppendLine(" Specifies the name of the collection to be triggered. Must be an exact match.");
|
||||
report.AppendLine();
|
||||
report.AppendLine(" <sourceName> Optional. Specifies the name of a specific source. Must be an exact match.");
|
||||
report.AppendLine();
|
||||
report.AppendLine(" <threshold> Required if the mode 'collection' is specified.");
|
||||
report.AppendLine(" Specifies the threshold in milliseconds (fractions allowed).");
|
||||
report.AppendLine(" Specify '0' to remove the threshold.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Examples:");
|
||||
report.AppendLine();
|
||||
report.AppendLine("pc trigger collection Display.Rendering 10");
|
||||
report.AppendLine(" Sets up an alert trigger which writes on the console if the execution time of all performance counters in");
|
||||
report.AppendLine(" the 'Display.Rendering' collection exceed 10 milliseconds.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("pc trigger collection Display.Rendering 5 Pathoschild.ChestsAnywhere");
|
||||
report.AppendLine(" Sets up an alert trigger to write on the console if the execution time of Pathoschild.ChestsAnywhere in");
|
||||
report.AppendLine(" the 'Display.Rendering' collection exceed 5 milliseconds.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("pc trigger collection Display.Rendering 0");
|
||||
report.AppendLine(" Removes the threshold previously defined from the collection. Note that source-specific thresholds are left intact.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("pc trigger clear");
|
||||
report.AppendLine(" Clears all previously setup alert triggers.");
|
||||
break;
|
||||
|
||||
case SubCommand.Reset:
|
||||
sb.AppendLine("Usage: pc reset <type> <name>");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Resets performance counters.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Arguments:");
|
||||
sb.AppendLine(" <type> Optional. Specifies if a collection or source should be reset.");
|
||||
sb.AppendLine(" If omitted, all performance counters are reset.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" - source Clears performance counters for a specific source");
|
||||
sb.AppendLine(" - collection Clears performance counters for a specific collection");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" <name> Required if a <type> is given. Specifies the name of either the collection");
|
||||
sb.AppendLine(" or the source. The name must be an exact match.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Examples:");
|
||||
sb.AppendLine("pc reset Resets all performance counters");
|
||||
sb.AppendLine("pc reset source Pathoschild.ChestsAnywhere Resets all performance for the source named Pathoschild.ChestsAnywhere");
|
||||
sb.AppendLine("pc reset collection Display.Rendering Resets all performance for the collection named Display.Rendering");
|
||||
report.AppendLine("Usage: pc reset <type> <name>");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Resets performance counters.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Arguments:");
|
||||
report.AppendLine(" <type> Optional. Specifies if a collection or source should be reset.");
|
||||
report.AppendLine(" If omitted, all performance counters are reset.");
|
||||
report.AppendLine();
|
||||
report.AppendLine(" - source Clears performance counters for a specific source");
|
||||
report.AppendLine(" - collection Clears performance counters for a specific collection");
|
||||
report.AppendLine();
|
||||
report.AppendLine(" <name> Required if a <type> is given. Specifies the name of either the collection");
|
||||
report.AppendLine(" or the source. The name must be an exact match.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Examples:");
|
||||
report.AppendLine("pc reset Resets all performance counters");
|
||||
report.AppendLine("pc reset source Pathoschild.ChestsAnywhere Resets all performance for the source named Pathoschild.ChestsAnywhere");
|
||||
report.AppendLine("pc reset collection Display.Rendering Resets all performance for the collection named Display.Rendering");
|
||||
break;
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
monitor.Log(sb.ToString(), LogLevel.Info);
|
||||
report.AppendLine();
|
||||
monitor.Log(report.ToString(), LogLevel.Info);
|
||||
}
|
||||
|
||||
/// <summary>Get the command description.</summary>
|
||||
private static string GetDescription()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
StringBuilder report = new StringBuilder();
|
||||
|
||||
sb.AppendLine("Displays and configures performance counters.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("A performance counter records the invocation time of in-game events being");
|
||||
sb.AppendLine("processed by mods or the game itself. See 'concepts' for a detailed explanation.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Usage: pc <command> <action>");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Commands:");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" summary|sum|s Displays a summary of important or all collections");
|
||||
sb.AppendLine(" detail|d Shows performance counter information for a given collection");
|
||||
sb.AppendLine(" reset|r Resets the performance counters");
|
||||
sb.AppendLine(" trigger Configures alert triggers");
|
||||
sb.AppendLine(" enable Enables performance counter recording");
|
||||
sb.AppendLine(" disable Disables performance counter recording");
|
||||
sb.AppendLine(" examples Displays various examples");
|
||||
sb.AppendLine(" concepts Displays an explanation of the performance counter concepts");
|
||||
sb.AppendLine(" help Displays verbose help for the available commands");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("To get help for a specific command, use 'pc help <command>', for example:");
|
||||
sb.AppendLine("pc help summary");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Defaults to summary if no command is given.");
|
||||
sb.AppendLine();
|
||||
report.AppendLine("Displays or configures performance monitoring for diagnose issues.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("A 'performance counter' is a metric which measures execution time across a range of time for a source (e.g. a mod).");
|
||||
report.AppendLine("A set of performance counters is organized in a collection to group various areas.");
|
||||
report.AppendLine("For example, the performance counter collection named 'Display.Rendered' contains one performance");
|
||||
report.AppendLine("counter when the game executes the 'Display.Rendered' event, and another counter for each mod which handles it.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Usage: pc <command> <action>");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Commands:");
|
||||
report.AppendLine();
|
||||
report.AppendLine(" summary Show a summary of collections.");
|
||||
report.AppendLine(" detail Show a summary for a given collection.");
|
||||
report.AppendLine(" reset Reset all performance counters.");
|
||||
report.AppendLine(" trigger Configure alert triggers.");
|
||||
report.AppendLine(" enable Enable performance counter recording.");
|
||||
report.AppendLine(" disable Disable performance counter recording.");
|
||||
report.AppendLine(" help Show verbose help for the available commands.");
|
||||
report.AppendLine();
|
||||
report.AppendLine("To get help for a specific command, use 'pc help <command>', for example:");
|
||||
report.AppendLine("pc help summary");
|
||||
report.AppendLine();
|
||||
report.AppendLine("Defaults to summary if no command is given.");
|
||||
report.AppendLine();
|
||||
|
||||
return sb.ToString();
|
||||
return report.ToString();
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private models
|
||||
*********/
|
||||
/// <summary>An alert trigger for a collection.</summary>
|
||||
private class CollectionTrigger
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The collection name.</summary>
|
||||
public string CollectionName { get; }
|
||||
|
||||
/// <summary>The trigger threshold.</summary>
|
||||
public double Threshold { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="collectionName">The collection name.</param>
|
||||
/// <param name="threshold">The trigger threshold.</param>
|
||||
public CollectionTrigger(string collectionName, double threshold)
|
||||
{
|
||||
this.CollectionName = collectionName;
|
||||
this.Threshold = threshold;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>An alert triggered for a source.</summary>
|
||||
private class SourceTrigger : CollectionTrigger
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The source name.</summary>
|
||||
public string SourceName { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="collectionName">The collection name.</param>
|
||||
/// <param name="sourceName">The source name.</param>
|
||||
/// <param name="threshold">The trigger threshold.</param>
|
||||
public SourceTrigger(string collectionName, string sourceName, double threshold)
|
||||
: base(collectionName, threshold)
|
||||
{
|
||||
this.SourceName = sourceName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
|
@ -66,7 +66,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands
|
|||
/// <param name="data">The data to display.</param>
|
||||
/// <param name="header">The table header.</param>
|
||||
/// <param name="getRow">Returns a set of fields for a data value.</param>
|
||||
/// <param name="rightAlign">True to right-align the data, false for left-align. Default false.</param>
|
||||
/// <param name="rightAlign">Whether to right-align the data.</param>
|
||||
protected string GetTableString<T>(IEnumerable<T> data, string[] header, Func<T, string[]> getRow, bool rightAlign = false)
|
||||
{
|
||||
// get table data
|
||||
|
@ -93,19 +93,11 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands
|
|||
};
|
||||
lines.AddRange(rows);
|
||||
|
||||
if (rightAlign)
|
||||
{
|
||||
return string.Join(
|
||||
Environment.NewLine,
|
||||
lines.Select(line => string.Join(" | ", line.Select((field, i) => field.PadLeft(widths[i], ' ')).ToArray())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return string.Join(
|
||||
Environment.NewLine,
|
||||
lines.Select(line => string.Join(" | ", line.Select((field, i) => field.PadRight(widths[i], ' ')).ToArray())
|
||||
)
|
||||
lines.Select(line => string.Join(" | ",
|
||||
line.Select((field, i) => rightAlign ? field.PadRight(widths[i], ' ') : field.PadLeft(widths[i], ' '))
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,10 +67,6 @@
|
|||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" />
|
||||
<Import Project="..\..\build\common.targets" />
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ namespace StardewModdingAPI
|
|||
/// <summary>The URL of the SMAPI home page.</summary>
|
||||
internal const string HomePageUrl = "https://smapi.io";
|
||||
|
||||
/// <summary>The URL of the SMAPI home page.</summary>
|
||||
/// <summary>The default performance counter name for unknown event handlers.</summary>
|
||||
internal const string GamePerformanceCounterName = "<StardewValley>";
|
||||
|
||||
/// <summary>The absolute path to the folder containing SMAPI's internal files.</summary>
|
||||
|
@ -103,6 +103,7 @@ namespace StardewModdingAPI
|
|||
/// <summary>The language code for non-translated mod assets.</summary>
|
||||
internal static LocalizedContentManager.LanguageCode DefaultLanguage { get; } = LocalizedContentManager.LanguageCode.en;
|
||||
|
||||
|
||||
/*********
|
||||
** Internal methods
|
||||
*********/
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using StardewModdingAPI.Events;
|
||||
using StardewModdingAPI.Framework.PerformanceCounter;
|
||||
|
||||
|
@ -174,29 +176,32 @@ namespace StardewModdingAPI.Framework.Events
|
|||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="monitor">Writes messages to the log.</param>
|
||||
/// <param name="modRegistry">The mod registry with which to identify mods.</param>
|
||||
/// <param name="performanceCounterManager">The performance counter manager.</param>
|
||||
public EventManager(IMonitor monitor, ModRegistry modRegistry, PerformanceCounterManager performanceCounterManager)
|
||||
/// <param name="performanceMonitor">Tracks performance metrics.</param>
|
||||
public EventManager(IMonitor monitor, ModRegistry modRegistry, PerformanceMonitor performanceMonitor)
|
||||
{
|
||||
// create shortcut initializers
|
||||
ManagedEvent<TEventArgs> ManageEventOf<TEventArgs>(string typeName, string eventName) => new ManagedEvent<TEventArgs>($"{typeName}.{eventName}", monitor, modRegistry, performanceCounterManager);
|
||||
ManagedEvent<TEventArgs> ManageEventOf<TEventArgs>(string typeName, string eventName, bool isPerformanceCritical = false)
|
||||
{
|
||||
return new ManagedEvent<TEventArgs>($"{typeName}.{eventName}", monitor, modRegistry, performanceMonitor, isPerformanceCritical);
|
||||
}
|
||||
|
||||
// init events (new)
|
||||
this.MenuChanged = ManageEventOf<MenuChangedEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.MenuChanged));
|
||||
this.Rendering = ManageEventOf<RenderingEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendering));
|
||||
this.Rendered = ManageEventOf<RenderedEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendered));
|
||||
this.RenderingWorld = ManageEventOf<RenderingWorldEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingWorld));
|
||||
this.RenderedWorld = ManageEventOf<RenderedWorldEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedWorld));
|
||||
this.RenderingActiveMenu = ManageEventOf<RenderingActiveMenuEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingActiveMenu));
|
||||
this.RenderedActiveMenu = ManageEventOf<RenderedActiveMenuEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedActiveMenu));
|
||||
this.RenderingHud = ManageEventOf<RenderingHudEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingHud));
|
||||
this.RenderedHud = ManageEventOf<RenderedHudEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedHud));
|
||||
this.Rendering = ManageEventOf<RenderingEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendering), isPerformanceCritical: true);
|
||||
this.Rendered = ManageEventOf<RenderedEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendered), isPerformanceCritical: true);
|
||||
this.RenderingWorld = ManageEventOf<RenderingWorldEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingWorld), isPerformanceCritical: true);
|
||||
this.RenderedWorld = ManageEventOf<RenderedWorldEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedWorld), isPerformanceCritical: true);
|
||||
this.RenderingActiveMenu = ManageEventOf<RenderingActiveMenuEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingActiveMenu), isPerformanceCritical: true);
|
||||
this.RenderedActiveMenu = ManageEventOf<RenderedActiveMenuEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedActiveMenu), isPerformanceCritical: true);
|
||||
this.RenderingHud = ManageEventOf<RenderingHudEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingHud), isPerformanceCritical: true);
|
||||
this.RenderedHud = ManageEventOf<RenderedHudEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedHud), isPerformanceCritical: true);
|
||||
this.WindowResized = ManageEventOf<WindowResizedEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.WindowResized));
|
||||
|
||||
this.GameLaunched = ManageEventOf<GameLaunchedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.GameLaunched));
|
||||
this.UpdateTicking = ManageEventOf<UpdateTickingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicking));
|
||||
this.UpdateTicked = ManageEventOf<UpdateTickedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicked));
|
||||
this.OneSecondUpdateTicking = ManageEventOf<OneSecondUpdateTickingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicking));
|
||||
this.OneSecondUpdateTicked = ManageEventOf<OneSecondUpdateTickedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicked));
|
||||
this.UpdateTicking = ManageEventOf<UpdateTickingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicking), isPerformanceCritical: true);
|
||||
this.UpdateTicked = ManageEventOf<UpdateTickedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicked), isPerformanceCritical: true);
|
||||
this.OneSecondUpdateTicking = ManageEventOf<OneSecondUpdateTickingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicking), isPerformanceCritical: true);
|
||||
this.OneSecondUpdateTicked = ManageEventOf<OneSecondUpdateTickedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicked), isPerformanceCritical: true);
|
||||
this.SaveCreating = ManageEventOf<SaveCreatingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.SaveCreating));
|
||||
this.SaveCreated = ManageEventOf<SaveCreatedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.SaveCreated));
|
||||
this.Saving = ManageEventOf<SavingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.Saving));
|
||||
|
@ -209,7 +214,7 @@ namespace StardewModdingAPI.Framework.Events
|
|||
|
||||
this.ButtonPressed = ManageEventOf<ButtonPressedEventArgs>(nameof(IModEvents.Input), nameof(IInputEvents.ButtonPressed));
|
||||
this.ButtonReleased = ManageEventOf<ButtonReleasedEventArgs>(nameof(IModEvents.Input), nameof(IInputEvents.ButtonReleased));
|
||||
this.CursorMoved = ManageEventOf<CursorMovedEventArgs>(nameof(IModEvents.Input), nameof(IInputEvents.CursorMoved));
|
||||
this.CursorMoved = ManageEventOf<CursorMovedEventArgs>(nameof(IModEvents.Input), nameof(IInputEvents.CursorMoved), isPerformanceCritical: true);
|
||||
this.MouseWheelScrolled = ManageEventOf<MouseWheelScrolledEventArgs>(nameof(IModEvents.Input), nameof(IInputEvents.MouseWheelScrolled));
|
||||
|
||||
this.PeerContextReceived = ManageEventOf<PeerContextReceivedEventArgs>(nameof(IModEvents.Multiplayer), nameof(IMultiplayerEvents.PeerContextReceived));
|
||||
|
@ -230,8 +235,15 @@ namespace StardewModdingAPI.Framework.Events
|
|||
this.TerrainFeatureListChanged = ManageEventOf<TerrainFeatureListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.TerrainFeatureListChanged));
|
||||
|
||||
this.LoadStageChanged = ManageEventOf<LoadStageChangedEventArgs>(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.LoadStageChanged));
|
||||
this.UnvalidatedUpdateTicking = ManageEventOf<UnvalidatedUpdateTickingEventArgs>(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.UnvalidatedUpdateTicking));
|
||||
this.UnvalidatedUpdateTicked = ManageEventOf<UnvalidatedUpdateTickedEventArgs>(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.UnvalidatedUpdateTicked));
|
||||
this.UnvalidatedUpdateTicking = ManageEventOf<UnvalidatedUpdateTickingEventArgs>(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.UnvalidatedUpdateTicking), isPerformanceCritical: true);
|
||||
this.UnvalidatedUpdateTicked = ManageEventOf<UnvalidatedUpdateTickedEventArgs>(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.UnvalidatedUpdateTicked), isPerformanceCritical: true);
|
||||
}
|
||||
|
||||
/// <summary>Get all managed events.</summary>
|
||||
public IEnumerable<IManagedEvent> GetAllEvents()
|
||||
{
|
||||
foreach (FieldInfo field in this.GetType().GetFields())
|
||||
yield return (IManagedEvent)field.GetValue(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,15 @@
|
|||
namespace StardewModdingAPI.Framework.Events
|
||||
{
|
||||
/// <summary>Metadata for an event raised by SMAPI.</summary>
|
||||
internal interface IManagedEvent
|
||||
{
|
||||
string GetName();
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>A human-readable name for the event.</summary>
|
||||
string EventName { get; }
|
||||
|
||||
/// <summary>Whether the event is typically called at least once per second.</summary>
|
||||
bool IsPerformanceCritical { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using PerformanceCounterManager = StardewModdingAPI.Framework.PerformanceCounter.PerformanceCounterManager;
|
||||
using StardewModdingAPI.Framework.PerformanceCounter;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Events
|
||||
{
|
||||
/// <summary>An event wrapper which intercepts and logs errors in handler code.</summary>
|
||||
/// <typeparam name="TEventArgs">The event arguments type.</typeparam>
|
||||
internal class ManagedEvent<TEventArgs>: IManagedEvent
|
||||
internal class ManagedEvent<TEventArgs> : IManagedEvent
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
|
@ -15,9 +15,6 @@ namespace StardewModdingAPI.Framework.Events
|
|||
/// <summary>The underlying event.</summary>
|
||||
private event EventHandler<TEventArgs> Event;
|
||||
|
||||
/// <summary>A human-readable name for the event.</summary>
|
||||
private readonly string EventName;
|
||||
|
||||
/// <summary>Writes messages to the log.</summary>
|
||||
private readonly IMonitor Monitor;
|
||||
|
||||
|
@ -30,8 +27,19 @@ namespace StardewModdingAPI.Framework.Events
|
|||
/// <summary>The cached invocation list.</summary>
|
||||
private EventHandler<TEventArgs>[] CachedInvocationList;
|
||||
|
||||
/// <summary>The performance counter manager.</summary>
|
||||
private readonly PerformanceCounterManager PerformanceCounterManager;
|
||||
/// <summary>Tracks performance metrics.</summary>
|
||||
private readonly PerformanceMonitor PerformanceMonitor;
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>A human-readable name for the event.</summary>
|
||||
public string EventName { get; }
|
||||
|
||||
/// <summary>Whether the event is typically called at least once per second.</summary>
|
||||
public bool IsPerformanceCritical { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
|
@ -40,19 +48,15 @@ namespace StardewModdingAPI.Framework.Events
|
|||
/// <param name="eventName">A human-readable name for the event.</param>
|
||||
/// <param name="monitor">Writes messages to the log.</param>
|
||||
/// <param name="modRegistry">The mod registry with which to identify mods.</param>
|
||||
/// <param name="performanceCounterManager">The performance counter manager</param>
|
||||
public ManagedEvent(string eventName, IMonitor monitor, ModRegistry modRegistry, PerformanceCounterManager performanceCounterManager)
|
||||
/// <param name="performanceMonitor">Tracks performance metrics.</param>
|
||||
/// <param name="isPerformanceCritical">Whether the event is typically called at least once per second.</param>
|
||||
public ManagedEvent(string eventName, IMonitor monitor, ModRegistry modRegistry, PerformanceMonitor performanceMonitor, bool isPerformanceCritical = false)
|
||||
{
|
||||
this.EventName = eventName;
|
||||
this.Monitor = monitor;
|
||||
this.ModRegistry = modRegistry;
|
||||
this.PerformanceCounterManager = performanceCounterManager;
|
||||
}
|
||||
|
||||
/// <summary>Gets the event name.</summary>
|
||||
public string GetName()
|
||||
{
|
||||
return this.EventName;
|
||||
this.PerformanceMonitor = performanceMonitor;
|
||||
this.IsPerformanceCritical = isPerformanceCritical;
|
||||
}
|
||||
|
||||
/// <summary>Get whether anything is listening to the event.</summary>
|
||||
|
@ -93,22 +97,20 @@ namespace StardewModdingAPI.Framework.Events
|
|||
return;
|
||||
|
||||
|
||||
this.PerformanceCounterManager.BeginTrackInvocation(this.EventName);
|
||||
|
||||
foreach (EventHandler<TEventArgs> handler in this.CachedInvocationList)
|
||||
this.PerformanceMonitor.Track(this.EventName, () =>
|
||||
{
|
||||
try
|
||||
foreach (EventHandler<TEventArgs> handler in this.CachedInvocationList)
|
||||
{
|
||||
this.PerformanceCounterManager.Track(this.EventName, this.GetModNameForPerformanceCounters(handler),
|
||||
() => handler.Invoke(null, args));
|
||||
try
|
||||
{
|
||||
this.PerformanceMonitor.Track(this.EventName, this.GetModNameForPerformanceCounters(handler), () => handler.Invoke(null, args));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError(handler, ex);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError(handler, ex);
|
||||
}
|
||||
}
|
||||
|
||||
this.PerformanceCounterManager.EndTrackInvocation(this.EventName);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Raise the event and notify all handlers.</summary>
|
||||
|
@ -139,18 +141,17 @@ namespace StardewModdingAPI.Framework.Events
|
|||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
|
||||
/// <summary>Get the mod name for a given event handler to display in performance monitoring reports.</summary>
|
||||
/// <param name="handler">The event handler.</param>
|
||||
private string GetModNameForPerformanceCounters(EventHandler<TEventArgs> handler)
|
||||
{
|
||||
IModMetadata mod = this.GetSourceMod(handler);
|
||||
|
||||
if (mod == null)
|
||||
{
|
||||
return Constants.GamePerformanceCounterName;
|
||||
}
|
||||
|
||||
return mod.HasManifest() ? mod.Manifest.UniqueID : mod.DisplayName;
|
||||
|
||||
return mod.HasManifest()
|
||||
? mod.Manifest.UniqueID
|
||||
: mod.DisplayName;
|
||||
}
|
||||
|
||||
/// <summary>Track an event handler.</summary>
|
||||
|
|
|
@ -3,13 +3,20 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
|
|||
/// <summary>The context for an alert.</summary>
|
||||
internal struct AlertContext
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The source which triggered the alert.</summary>
|
||||
public readonly string Source;
|
||||
public string Source { get; }
|
||||
|
||||
/// <summary>The elapsed milliseconds.</summary>
|
||||
public readonly double Elapsed;
|
||||
public double Elapsed { get; }
|
||||
|
||||
/// <summary>Creates a new alert context.</summary>
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="source">The source which triggered the alert.</param>
|
||||
/// <param name="elapsed">The elapsed milliseconds.</param>
|
||||
public AlertContext(string source, double elapsed)
|
||||
|
@ -18,6 +25,7 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
|
|||
this.Elapsed = elapsed;
|
||||
}
|
||||
|
||||
/// <summary>Get a human-readable text form of this instance.</summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Source}: {this.Elapsed:F2}ms";
|
||||
|
|
|
@ -1,28 +1,33 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace StardewModdingAPI.Framework.PerformanceCounter
|
||||
{
|
||||
/// <summary>A single alert entry.</summary>
|
||||
internal struct AlertEntry
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The collection in which the alert occurred.</summary>
|
||||
public readonly PerformanceCounterCollection Collection;
|
||||
public PerformanceCounterCollection Collection { get; }
|
||||
|
||||
/// <summary>The actual execution time in milliseconds.</summary>
|
||||
public readonly double ExecutionTimeMilliseconds;
|
||||
public double ExecutionTimeMilliseconds { get; }
|
||||
|
||||
/// <summary>The configured alert threshold. </summary>
|
||||
public readonly double ThresholdMilliseconds;
|
||||
/// <summary>The configured alert threshold in milliseconds.</summary>
|
||||
public double ThresholdMilliseconds { get; }
|
||||
|
||||
/// <summary>The context list, which records all sources involved in exceeding the threshold.</summary>
|
||||
public readonly List<AlertContext> Context;
|
||||
/// <summary>The sources involved in exceeding the threshold.</summary>
|
||||
public AlertContext[] Context { get; }
|
||||
|
||||
/// <summary>Creates a new alert entry.</summary>
|
||||
/// <param name="collection">The source collection in which the alert occurred.</param>
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="collection">The collection in which the alert occurred.</param>
|
||||
/// <param name="executionTimeMilliseconds">The actual execution time in milliseconds.</param>
|
||||
/// <param name="thresholdMilliseconds">The configured threshold in milliseconds.</param>
|
||||
/// <param name="context">A list of AlertContext to record which sources were involved</param>
|
||||
public AlertEntry(PerformanceCounterCollection collection, double executionTimeMilliseconds, double thresholdMilliseconds, List<AlertContext> context)
|
||||
/// <param name="thresholdMilliseconds">The configured alert threshold in milliseconds.</param>
|
||||
/// <param name="context">The sources involved in exceeding the threshold.</param>
|
||||
public AlertEntry(PerformanceCounterCollection collection, double executionTimeMilliseconds, double thresholdMilliseconds, AlertContext[] context)
|
||||
{
|
||||
this.Collection = collection;
|
||||
this.ExecutionTimeMilliseconds = executionTimeMilliseconds;
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
using StardewModdingAPI.Framework.Events;
|
||||
|
||||
namespace StardewModdingAPI.Framework.PerformanceCounter
|
||||
{
|
||||
/// <summary>Represents a performance counter collection specific to game events.</summary>
|
||||
internal class EventPerformanceCounterCollection: PerformanceCounterCollection
|
||||
{
|
||||
/// <summary>Creates a new event performance counter collection.</summary>
|
||||
/// <param name="manager">The performance counter manager.</param>
|
||||
/// <param name="event">The ManagedEvent.</param>
|
||||
/// <param name="isImportant">If the event is flagged as important.</param>
|
||||
public EventPerformanceCounterCollection(PerformanceCounterManager manager, IManagedEvent @event, bool isImportant) : base(manager, @event.GetName(), isImportant)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +1,31 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StardewModdingAPI.Framework.PerformanceCounter
|
||||
{
|
||||
/// <summary>A peak invocation time.</summary>
|
||||
internal struct PeakEntry
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The actual execution time in milliseconds.</summary>
|
||||
public readonly double ExecutionTimeMilliseconds;
|
||||
public double ExecutionTimeMilliseconds { get; }
|
||||
|
||||
/// <summary>The DateTime when the entry occured.</summary>
|
||||
public DateTime EventTime;
|
||||
/// <summary>When the entry occurred.</summary>
|
||||
public DateTime EventTime { get; }
|
||||
|
||||
/// <summary>The context list, which records all sources involved in exceeding the threshold.</summary>
|
||||
public readonly List<AlertContext> Context;
|
||||
/// <summary>The sources involved in exceeding the threshold.</summary>
|
||||
public AlertContext[] Context { get; }
|
||||
|
||||
public PeakEntry(double executionTimeMilliseconds, DateTime eventTime, List<AlertContext> context)
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="executionTimeMilliseconds">The actual execution time in milliseconds.</param>
|
||||
/// <param name="eventTime">When the entry occurred.</param>
|
||||
/// <param name="context">The sources involved in exceeding the threshold.</param>
|
||||
public PeakEntry(double executionTimeMilliseconds, DateTime eventTime, AlertContext[] context)
|
||||
{
|
||||
this.ExecutionTimeMilliseconds = executionTimeMilliseconds;
|
||||
this.EventTime = eventTime;
|
||||
|
|
|
@ -1,23 +1,32 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Cyotek.Collections.Generic;
|
||||
using Harmony;
|
||||
|
||||
namespace StardewModdingAPI.Framework.PerformanceCounter
|
||||
{
|
||||
/// <summary>Tracks metadata about a particular code event.</summary>
|
||||
internal class PerformanceCounter
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The size of the ring buffer.</summary>
|
||||
private const int MAX_ENTRIES = 16384;
|
||||
private readonly int MaxEntries = 16384;
|
||||
|
||||
/// <summary>The collection to which this performance counter belongs.</summary>
|
||||
private readonly PerformanceCounterCollection ParentCollection;
|
||||
|
||||
/// <summary>The circular buffer which stores all performance counter entries</summary>
|
||||
private readonly CircularBuffer<PerformanceCounterEntry> _counter;
|
||||
/// <summary>The performance counter entries.</summary>
|
||||
private readonly Stack<PerformanceCounterEntry> Entries;
|
||||
|
||||
/// <summary>The peak execution time</summary>
|
||||
/// <summary>The entry with the highest execution time.</summary>
|
||||
private PerformanceCounterEntry? PeakPerformanceCounterEntry;
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The name of the source.</summary>
|
||||
public string Source { get; }
|
||||
|
||||
|
@ -27,118 +36,90 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
|
|||
/// <summary>If alerting is enabled or not</summary>
|
||||
public bool EnableAlerts { get; set; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="parentCollection">The collection to which this performance counter belongs.</param>
|
||||
/// <param name="source">The name of the source.</param>
|
||||
public PerformanceCounter(PerformanceCounterCollection parentCollection, string source)
|
||||
{
|
||||
this.ParentCollection = parentCollection;
|
||||
this.Source = source;
|
||||
this._counter = new CircularBuffer<PerformanceCounterEntry>(PerformanceCounter.MAX_ENTRIES);
|
||||
this.Entries = new Stack<PerformanceCounterEntry>(this.MaxEntries);
|
||||
}
|
||||
|
||||
/// <summary>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.</summary>
|
||||
/// <summary>Add a performance counter entry to the list, update monitoring, and raise alerts if needed.</summary>
|
||||
/// <param name="entry">The entry to add.</param>
|
||||
public void Add(PerformanceCounterEntry entry)
|
||||
{
|
||||
this._counter.Put(entry);
|
||||
// add entry
|
||||
if (this.Entries.Count > this.MaxEntries)
|
||||
this.Entries.Pop();
|
||||
this.Entries.Add(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)
|
||||
// update metrics
|
||||
if (this.PeakPerformanceCounterEntry == null || entry.ElapsedMilliseconds > this.PeakPerformanceCounterEntry.Value.ElapsedMilliseconds)
|
||||
this.PeakPerformanceCounterEntry = entry;
|
||||
else
|
||||
{
|
||||
if (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));
|
||||
}
|
||||
|
||||
/// <summary>Clears all performance counter entries and resets the peak entry.</summary>
|
||||
/// <summary>Clear all performance counter entries and monitoring.</summary>
|
||||
public void Reset()
|
||||
{
|
||||
this._counter.Clear();
|
||||
this.Entries.Clear();
|
||||
this.PeakPerformanceCounterEntry = null;
|
||||
}
|
||||
|
||||
/// <summary>Returns the peak entry.</summary>
|
||||
/// <returns>The peak entry.</returns>
|
||||
/// <summary>Get the peak entry.</summary>
|
||||
public PerformanceCounterEntry? GetPeak()
|
||||
{
|
||||
return this.PeakPerformanceCounterEntry;
|
||||
}
|
||||
|
||||
/// <summary>Returns the peak entry.</summary>
|
||||
/// <returns>The peak entry.</returns>
|
||||
public PerformanceCounterEntry? GetPeak(TimeSpan range, DateTime? relativeTo = null)
|
||||
/// <summary>Get the entry with the highest execution time.</summary>
|
||||
/// <param name="range">The time range to search.</param>
|
||||
/// <param name="endTime">The end time for the <paramref name="range"/>, or null for the current time.</param>
|
||||
public PerformanceCounterEntry? GetPeak(TimeSpan range, DateTime? endTime = null)
|
||||
{
|
||||
if (this._counter.IsEmpty)
|
||||
return null;
|
||||
endTime ??= DateTime.UtcNow;
|
||||
DateTime startTime = endTime.Value.Subtract(range);
|
||||
|
||||
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();
|
||||
return this.Entries
|
||||
.Where(entry => entry.EventTime >= startTime && entry.EventTime <= endTime)
|
||||
.OrderByDescending(x => x.ElapsedMilliseconds)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>Resets the peak entry.</summary>
|
||||
public void ResetPeak()
|
||||
{
|
||||
this.PeakPerformanceCounterEntry = null;
|
||||
}
|
||||
|
||||
/// <summary>Returns the last entry added to the list.</summary>
|
||||
/// <returns>The last entry</returns>
|
||||
/// <summary>Get the last entry added to the list.</summary>
|
||||
public PerformanceCounterEntry? GetLastEntry()
|
||||
{
|
||||
if (this._counter.IsEmpty)
|
||||
if (this.Entries.Count == 0)
|
||||
return null;
|
||||
|
||||
return this._counter.PeekLast();
|
||||
return this.Entries.Peek();
|
||||
}
|
||||
|
||||
/// <summary>Returns the average execution time of all entries.</summary>
|
||||
/// <returns>The average execution time in milliseconds.</returns>
|
||||
public double GetAverage()
|
||||
/// <summary>Get the average over a given time span.</summary>
|
||||
/// <param name="range">The time range to search.</param>
|
||||
/// <param name="endTime">The end time for the <paramref name="range"/>, or null for the current time.</param>
|
||||
public double GetAverage(TimeSpan range, DateTime? endTime = null)
|
||||
{
|
||||
if (this._counter.IsEmpty)
|
||||
return 0;
|
||||
endTime ??= DateTime.UtcNow;
|
||||
DateTime startTime = endTime.Value.Subtract(range);
|
||||
|
||||
return this._counter.Average(p => p.ElapsedMilliseconds);
|
||||
}
|
||||
double[] entries = this.Entries
|
||||
.Where(entry => entry.EventTime >= startTime && entry.EventTime <= endTime)
|
||||
.Select(p => p.ElapsedMilliseconds)
|
||||
.ToArray();
|
||||
|
||||
/// <summary>Returns the average over a given time span.</summary>
|
||||
/// <param name="range">The time range to retrieve.</param>
|
||||
/// <param name="relativeTo">The DateTime from which to start the average. Defaults to DateTime.UtcNow if null</param>
|
||||
/// <returns>The average execution time in milliseconds.</returns>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
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)).ToList();
|
||||
|
||||
if (!entries.Any())
|
||||
return 0;
|
||||
|
||||
return entries.Average(x => x.ElapsedMilliseconds);
|
||||
return entries.Length > 0
|
||||
? entries.Average()
|
||||
: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,153 +2,129 @@ 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;
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The number of peak invocations to keep.</summary>
|
||||
private readonly int MaxEntries = 16384;
|
||||
|
||||
/// <summary>The list of triggered performance counters.</summary>
|
||||
/// <summary>The sources involved in exceeding alert thresholds.</summary>
|
||||
private readonly List<AlertContext> TriggeredPerformanceCounters = new List<AlertContext>();
|
||||
|
||||
/// <summary>The stopwatch used to track the invocation time.</summary>
|
||||
private readonly Stopwatch InvocationStopwatch = new Stopwatch();
|
||||
|
||||
/// <summary>The performance counter manager.</summary>
|
||||
private readonly PerformanceCounterManager PerformanceCounterManager;
|
||||
private readonly PerformanceMonitor PerformanceMonitor;
|
||||
|
||||
/// <summary>Holds the time to calculate the average calls per second.</summary>
|
||||
/// <summary>The time to calculate average calls per second.</summary>
|
||||
private DateTime CallsPerSecondStart = DateTime.UtcNow;
|
||||
|
||||
/// <summary>The number of invocations of this collection.</summary>
|
||||
/// <summary>The number of invocations.</summary>
|
||||
private long CallCount;
|
||||
|
||||
/// <summary>The circular buffer which stores all peak invocations</summary>
|
||||
private readonly CircularBuffer<PeakEntry> PeakInvocations;
|
||||
/// <summary>The peak invocations.</summary>
|
||||
private readonly Stack<PeakEntry> PeakInvocations;
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The associated performance counters.</summary>
|
||||
public IDictionary<string, PerformanceCounter> PerformanceCounters { get; } = new Dictionary<string, PerformanceCounter>();
|
||||
|
||||
/// <summary>The name of this collection.</summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>Flag if this collection is important (used for the console summary command).</summary>
|
||||
public bool IsImportant { get; }
|
||||
/// <summary>Whether the source is typically invoked at least once per second.</summary>
|
||||
public bool IsPerformanceCritical { get; }
|
||||
|
||||
/// <summary>The alert threshold in milliseconds.</summary>
|
||||
public double AlertThresholdMilliseconds { get; set; }
|
||||
|
||||
/// <summary>If alerting is enabled or not</summary>
|
||||
/// <summary>Whether alerts are enabled.</summary>
|
||||
public bool EnableAlerts { get; set; }
|
||||
|
||||
|
||||
public PerformanceCounterCollection(PerformanceCounterManager performanceCounterManager, string name, bool isImportant)
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="performanceMonitor">The performance counter manager.</param>
|
||||
/// <param name="name">The name of this collection.</param>
|
||||
/// <param name="isPerformanceCritical">Whether the source is typically invoked at least once per second.</param>
|
||||
public PerformanceCounterCollection(PerformanceMonitor performanceMonitor, string name, bool isPerformanceCritical = false)
|
||||
{
|
||||
this.PeakInvocations = new CircularBuffer<PeakEntry>(PerformanceCounterCollection.MAX_ENTRIES);
|
||||
this.PeakInvocations = new Stack<PeakEntry>(this.MaxEntries);
|
||||
this.Name = name;
|
||||
this.PerformanceCounterManager = performanceCounterManager;
|
||||
this.IsImportant = isImportant;
|
||||
this.PerformanceMonitor = performanceMonitor;
|
||||
this.IsPerformanceCritical = isPerformanceCritical;
|
||||
}
|
||||
|
||||
public PerformanceCounterCollection(PerformanceCounterManager performanceCounterManager, string name)
|
||||
{
|
||||
this.PeakInvocations = new CircularBuffer<PeakEntry>(PerformanceCounterCollection.MAX_ENTRIES);
|
||||
this.PerformanceCounterManager = performanceCounterManager;
|
||||
this.Name = name;
|
||||
}
|
||||
|
||||
/// <summary>Tracks a single invocation for a named source.</summary>
|
||||
/// <summary>Track a single invocation for a named source.</summary>
|
||||
/// <param name="source">The name of the source.</param>
|
||||
/// <param name="entry">The entry.</param>
|
||||
public void Track(string source, PerformanceCounterEntry entry)
|
||||
{
|
||||
// add entry
|
||||
if (!this.PerformanceCounters.ContainsKey(source))
|
||||
this.PerformanceCounters.Add(source, new PerformanceCounter(this, source));
|
||||
|
||||
this.PerformanceCounters[source].Add(entry);
|
||||
|
||||
// raise alert
|
||||
if (this.EnableAlerts)
|
||||
this.TriggeredPerformanceCounters.Add(new AlertContext(source, entry.ElapsedMilliseconds));
|
||||
}
|
||||
|
||||
/// <summary>Returns the average execution time for all non-game internal sources.</summary>
|
||||
/// <returns>The average execution time in milliseconds</returns>
|
||||
public double GetModsAverageExecutionTime()
|
||||
{
|
||||
return this.PerformanceCounters.Where(p =>
|
||||
p.Key != Constants.GamePerformanceCounterName).Sum(p => p.Value.GetAverage());
|
||||
}
|
||||
|
||||
/// <summary>Returns the average execution time for all non-game internal sources.</summary>
|
||||
/// <summary>Get the average execution time for all non-game internal sources in milliseconds.</summary>
|
||||
/// <param name="interval">The interval for which to get the average, relative to now</param>
|
||||
/// <returns>The average execution time in milliseconds</returns>
|
||||
public double GetModsAverageExecutionTime(TimeSpan interval)
|
||||
{
|
||||
return this.PerformanceCounters.Where(p =>
|
||||
p.Key != Constants.GamePerformanceCounterName).Sum(p => p.Value.GetAverage(interval));
|
||||
return this.PerformanceCounters
|
||||
.Where(entry => entry.Key != Constants.GamePerformanceCounterName)
|
||||
.Sum(entry => entry.Value.GetAverage(interval));
|
||||
}
|
||||
|
||||
/// <summary>Returns the overall average execution time.</summary>
|
||||
/// <returns>The average execution time in milliseconds</returns>
|
||||
public double GetAverageExecutionTime()
|
||||
{
|
||||
return this.PerformanceCounters.Sum(p => p.Value.GetAverage());
|
||||
}
|
||||
|
||||
/// <summary>Returns the overall average execution time.</summary>
|
||||
/// <summary>Get the overall average execution time in milliseconds.</summary>
|
||||
/// <param name="interval">The interval for which to get the average, relative to now</param>
|
||||
/// <returns>The average execution time in milliseconds</returns>
|
||||
public double GetAverageExecutionTime(TimeSpan interval)
|
||||
{
|
||||
return this.PerformanceCounters.Sum(p => p.Value.GetAverage(interval));
|
||||
return this.PerformanceCounters
|
||||
.Sum(entry => entry.Value.GetAverage(interval));
|
||||
}
|
||||
|
||||
/// <summary>Returns the average execution time for game-internal sources.</summary>
|
||||
/// <returns>The average execution time in milliseconds</returns>
|
||||
public double GetGameAverageExecutionTime()
|
||||
{
|
||||
if (this.PerformanceCounters.TryGetValue(Constants.GamePerformanceCounterName, out PerformanceCounter gameExecTime))
|
||||
return gameExecTime.GetAverage();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>Returns the average execution time for game-internal sources.</summary>
|
||||
/// <returns>The average execution time in milliseconds</returns>
|
||||
/// <summary>Get the average execution time for game-internal sources in milliseconds.</summary>
|
||||
public double GetGameAverageExecutionTime(TimeSpan interval)
|
||||
{
|
||||
if (this.PerformanceCounters.TryGetValue(Constants.GamePerformanceCounterName, out PerformanceCounter gameExecTime))
|
||||
return gameExecTime.GetAverage(interval);
|
||||
|
||||
return 0;
|
||||
return this.PerformanceCounters.TryGetValue(Constants.GamePerformanceCounterName, out PerformanceCounter gameExecTime)
|
||||
? gameExecTime.GetAverage(interval)
|
||||
: 0;
|
||||
}
|
||||
|
||||
/// <summary>Returns the peak execution time</summary>
|
||||
/// <param name="interval">The interval for which to get the peak, relative to <paramref name="relativeTo"/></param>
|
||||
/// <param name="relativeTo">The DateTime which the <paramref name="interval"/> is relative to, or DateTime.Now if not given</param>
|
||||
/// <returns>The peak execution time</returns>
|
||||
public double GetPeakExecutionTime(TimeSpan range, DateTime? relativeTo = null)
|
||||
/// <summary>Get the peak execution time in milliseconds.</summary>
|
||||
/// <param name="range">The time range to search.</param>
|
||||
/// <param name="endTime">The end time for the <paramref name="range"/>, or null for the current time.</param>
|
||||
public double GetPeakExecutionTime(TimeSpan range, DateTime? endTime = null)
|
||||
{
|
||||
if (this.PeakInvocations.IsEmpty)
|
||||
if (this.PeakInvocations.Count == 0)
|
||||
return 0;
|
||||
|
||||
if (relativeTo == null)
|
||||
relativeTo = DateTime.UtcNow;
|
||||
endTime ??= DateTime.UtcNow;
|
||||
DateTime startTime = endTime.Value.Subtract(range);
|
||||
|
||||
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;
|
||||
return this.PeakInvocations
|
||||
.Where(entry => entry.EventTime >= startTime && entry.EventTime <= endTime)
|
||||
.OrderByDescending(x => x.ExecutionTimeMilliseconds)
|
||||
.Select(p => p.ExecutionTimeMilliseconds)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>Begins tracking the invocation of this collection.</summary>
|
||||
/// <summary>Start tracking the invocation of this collection.</summary>
|
||||
public void BeginTrackInvocation()
|
||||
{
|
||||
this.TriggeredPerformanceCounters.Clear();
|
||||
|
@ -158,60 +134,58 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
|
|||
this.CallCount++;
|
||||
}
|
||||
|
||||
/// <summary>Ends tracking the invocation of this collection. Also records an alert if alerting is enabled
|
||||
/// and the invocation time exceeds the threshold.</summary>
|
||||
/// <summary>End tracking the invocation of this collection, and raise an alert if needed.</summary>
|
||||
public void EndTrackInvocation()
|
||||
{
|
||||
this.InvocationStopwatch.Stop();
|
||||
|
||||
this.PeakInvocations.Put(
|
||||
new PeakEntry(this.InvocationStopwatch.Elapsed.TotalMilliseconds,
|
||||
DateTime.UtcNow,
|
||||
this.TriggeredPerformanceCounters));
|
||||
// add invocation
|
||||
if (this.PeakInvocations.Count >= this.MaxEntries)
|
||||
this.PeakInvocations.Pop();
|
||||
this.PeakInvocations.Push(new PeakEntry(this.InvocationStopwatch.Elapsed.TotalMilliseconds, DateTime.UtcNow, this.TriggeredPerformanceCounters.ToArray()));
|
||||
|
||||
if (!this.EnableAlerts) return;
|
||||
|
||||
if (this.InvocationStopwatch.Elapsed.TotalMilliseconds >= this.AlertThresholdMilliseconds)
|
||||
this.AddAlert(this.InvocationStopwatch.Elapsed.TotalMilliseconds,
|
||||
this.AlertThresholdMilliseconds, this.TriggeredPerformanceCounters);
|
||||
// raise alert
|
||||
if (this.EnableAlerts && this.InvocationStopwatch.Elapsed.TotalMilliseconds >= this.AlertThresholdMilliseconds)
|
||||
this.AddAlert(this.InvocationStopwatch.Elapsed.TotalMilliseconds, this.AlertThresholdMilliseconds, this.TriggeredPerformanceCounters.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>Adds an alert.</summary>
|
||||
/// <summary>Add an alert.</summary>
|
||||
/// <param name="executionTimeMilliseconds">The execution time in milliseconds.</param>
|
||||
/// <param name="thresholdMilliseconds">The configured threshold.</param>
|
||||
/// <param name="alerts">The list of alert contexts.</param>
|
||||
public void AddAlert(double executionTimeMilliseconds, double thresholdMilliseconds, List<AlertContext> alerts)
|
||||
/// <param name="alerts">The sources involved in exceeding the threshold.</param>
|
||||
public void AddAlert(double executionTimeMilliseconds, double thresholdMilliseconds, AlertContext[] alerts)
|
||||
{
|
||||
this.PerformanceCounterManager.AddAlert(new AlertEntry(this, executionTimeMilliseconds,
|
||||
thresholdMilliseconds, alerts));
|
||||
this.PerformanceMonitor.AddAlert(
|
||||
new AlertEntry(this, executionTimeMilliseconds, thresholdMilliseconds, alerts)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Adds an alert for a single AlertContext</summary>
|
||||
/// <summary>Add an alert.</summary>
|
||||
/// <param name="executionTimeMilliseconds">The execution time in milliseconds.</param>
|
||||
/// <param name="thresholdMilliseconds">The configured threshold.</param>
|
||||
/// <param name="alert">The context</param>
|
||||
/// <param name="alert">The source involved in exceeding the threshold.</param>
|
||||
public void AddAlert(double executionTimeMilliseconds, double thresholdMilliseconds, AlertContext alert)
|
||||
{
|
||||
this.AddAlert(executionTimeMilliseconds, thresholdMilliseconds, new List<AlertContext>() {alert});
|
||||
this.AddAlert(executionTimeMilliseconds, thresholdMilliseconds, new[] { alert });
|
||||
}
|
||||
|
||||
/// <summary>Resets the calls per second counter.</summary>
|
||||
/// <summary>Reset the calls per second counter.</summary>
|
||||
public void ResetCallsPerSecond()
|
||||
{
|
||||
this.CallCount = 0;
|
||||
this.CallsPerSecondStart = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>Resets all performance counters in this collection.</summary>
|
||||
/// <summary>Reset all performance counters in this collection.</summary>
|
||||
public void Reset()
|
||||
{
|
||||
this.PeakInvocations.Clear();
|
||||
foreach (var i in this.PerformanceCounters)
|
||||
i.Value.Reset();
|
||||
foreach (var counter in this.PerformanceCounters)
|
||||
counter.Value.Reset();
|
||||
}
|
||||
|
||||
/// <summary>Resets the performance counter for a specific source.</summary>
|
||||
/// <param name="source">The source name</param>
|
||||
/// <summary>Reset the performance counter for a specific source.</summary>
|
||||
/// <param name="source">The source name.</param>
|
||||
public void ResetSource(string source)
|
||||
{
|
||||
foreach (var i in this.PerformanceCounters)
|
||||
|
@ -219,15 +193,13 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
|
|||
i.Value.Reset();
|
||||
}
|
||||
|
||||
/// <summary>Returns the average calls per second.</summary>
|
||||
/// <returns>The average calls per second.</returns>
|
||||
/// <summary>Get the average calls per second.</summary>
|
||||
public long GetAverageCallsPerSecond()
|
||||
{
|
||||
long runtimeInSeconds = (long) DateTime.UtcNow.Subtract(this.CallsPerSecondStart).TotalSeconds;
|
||||
|
||||
if (runtimeInSeconds == 0) return 0;
|
||||
|
||||
return this.CallCount / runtimeInSeconds;
|
||||
long runtimeInSeconds = (long)DateTime.UtcNow.Subtract(this.CallsPerSecondStart).TotalSeconds;
|
||||
return runtimeInSeconds > 0
|
||||
? this.CallCount / runtimeInSeconds
|
||||
: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,29 @@ using System;
|
|||
|
||||
namespace StardewModdingAPI.Framework.PerformanceCounter
|
||||
{
|
||||
/// <summary>A single performance counter entry. Records the DateTime of the event and the elapsed millisecond.</summary>
|
||||
/// <summary>A single performance counter entry.</summary>
|
||||
internal struct PerformanceCounterEntry
|
||||
{
|
||||
/// <summary>The DateTime when the entry occured.</summary>
|
||||
public DateTime EventTime;
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>When the entry occurred.</summary>
|
||||
public DateTime EventTime { get; }
|
||||
|
||||
/// <summary>The elapsed milliseconds</summary>
|
||||
public double ElapsedMilliseconds;
|
||||
/// <summary>The elapsed milliseconds.</summary>
|
||||
public double ElapsedMilliseconds { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="eventTime">When the entry occurred.</param>
|
||||
/// <param name="elapsedMilliseconds">The elapsed milliseconds.</param>
|
||||
public PerformanceCounterEntry(DateTime eventTime, double elapsedMilliseconds)
|
||||
{
|
||||
this.EventTime = eventTime;
|
||||
this.ElapsedMilliseconds = elapsedMilliseconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,12 +7,14 @@ using StardewModdingAPI.Framework.Events;
|
|||
|
||||
namespace StardewModdingAPI.Framework.PerformanceCounter
|
||||
{
|
||||
internal class PerformanceCounterManager
|
||||
/// <summary>Tracks performance metrics.</summary>
|
||||
internal class PerformanceMonitor
|
||||
{
|
||||
public HashSet<PerformanceCounterCollection> PerformanceCounterCollections = new HashSet<PerformanceCounterCollection>();
|
||||
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The recorded alerts.</summary>
|
||||
private readonly List<AlertEntry> Alerts = new List<AlertEntry>();
|
||||
private readonly IList<AlertEntry> Alerts = new List<AlertEntry>();
|
||||
|
||||
/// <summary>The monitor for output logging.</summary>
|
||||
private readonly IMonitor Monitor;
|
||||
|
@ -20,62 +22,64 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
|
|||
/// <summary>The invocation stopwatch.</summary>
|
||||
private readonly Stopwatch InvocationStopwatch = new Stopwatch();
|
||||
|
||||
/// <summary>Specifies if alerts should be paused.</summary>
|
||||
/// <summary>The underlying performance counter collections.</summary>
|
||||
private readonly IDictionary<string, PerformanceCounterCollection> Collections = new Dictionary<string, PerformanceCounterCollection>(StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>Whether alerts are paused.</summary>
|
||||
public bool PauseAlerts { get; set; }
|
||||
|
||||
/// <summary>Specifies if performance counter tracking should be enabled.</summary>
|
||||
/// <summary>Whether performance counter tracking is enabled.</summary>
|
||||
public bool EnableTracking { get; set; }
|
||||
|
||||
/// <summary>Constructs a performance counter manager.</summary>
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="monitor">The monitor for output logging.</param>
|
||||
public PerformanceCounterManager(IMonitor monitor)
|
||||
public PerformanceMonitor(IMonitor monitor)
|
||||
{
|
||||
this.Monitor = monitor;
|
||||
}
|
||||
|
||||
/// <summary>Resets all performance counters in all collections.</summary>
|
||||
/// <summary>Reset all performance counters in all collections.</summary>
|
||||
public void Reset()
|
||||
{
|
||||
foreach (PerformanceCounterCollection collection in this.PerformanceCounterCollections)
|
||||
{
|
||||
foreach (PerformanceCounterCollection collection in this.Collections.Values)
|
||||
collection.Reset();
|
||||
}
|
||||
|
||||
foreach (var eventPerformanceCounter in
|
||||
this.PerformanceCounterCollections.SelectMany(performanceCounter => performanceCounter.PerformanceCounters))
|
||||
{
|
||||
eventPerformanceCounter.Value.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Begins tracking the invocation for a collection.</summary>
|
||||
/// <param name="collectionName">The collection name</param>
|
||||
public void BeginTrackInvocation(string collectionName)
|
||||
/// <summary>Track the invocation time for a collection.</summary>
|
||||
/// <param name="collectionName">The name of the collection.</param>
|
||||
/// <param name="action">The action to execute and track.</param>
|
||||
public void Track(string collectionName, Action action)
|
||||
{
|
||||
if (!this.EnableTracking)
|
||||
{
|
||||
action();
|
||||
return;
|
||||
}
|
||||
|
||||
this.GetOrCreateCollectionByName(collectionName).BeginTrackInvocation();
|
||||
}
|
||||
|
||||
/// <summary>Ends tracking the invocation for a collection.</summary>
|
||||
/// <param name="collectionName"></param>
|
||||
public void EndTrackInvocation(string collectionName)
|
||||
{
|
||||
if (!this.EnableTracking)
|
||||
PerformanceCounterCollection collection = this.GetOrCreateCollectionByName(collectionName);
|
||||
collection.BeginTrackInvocation();
|
||||
try
|
||||
{
|
||||
return;
|
||||
action();
|
||||
}
|
||||
finally
|
||||
{
|
||||
collection.EndTrackInvocation();
|
||||
}
|
||||
|
||||
this.GetOrCreateCollectionByName(collectionName).EndTrackInvocation();
|
||||
}
|
||||
|
||||
/// <summary>Tracks a single performance counter invocation in a specific collection.</summary>
|
||||
/// <summary>Track a single performance counter invocation in a specific collection.</summary>
|
||||
/// <param name="collectionName">The name of the collection.</param>
|
||||
/// <param name="sourceName">The name of the source.</param>
|
||||
/// <param name="action">The action to execute and track invocation time for.</param>
|
||||
/// <param name="action">The action to execute and track.</param>
|
||||
public void Track(string collectionName, string sourceName, Action action)
|
||||
{
|
||||
if (!this.EnableTracking)
|
||||
|
@ -84,6 +88,7 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
|
|||
return;
|
||||
}
|
||||
|
||||
PerformanceCounterCollection collection = this.GetOrCreateCollectionByName(collectionName);
|
||||
DateTime eventTime = DateTime.UtcNow;
|
||||
this.InvocationStopwatch.Reset();
|
||||
this.InvocationStopwatch.Start();
|
||||
|
@ -95,79 +100,50 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
|
|||
finally
|
||||
{
|
||||
this.InvocationStopwatch.Stop();
|
||||
|
||||
this.GetOrCreateCollectionByName(collectionName).Track(sourceName, new PerformanceCounterEntry
|
||||
{
|
||||
EventTime = eventTime,
|
||||
ElapsedMilliseconds = this.InvocationStopwatch.Elapsed.TotalMilliseconds
|
||||
});
|
||||
collection.Track(sourceName, new PerformanceCounterEntry(eventTime, this.InvocationStopwatch.Elapsed.TotalMilliseconds));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets a collection by name.</summary>
|
||||
/// <param name="name">The name of the collection.</param>
|
||||
/// <returns>The collection or null if none was found.</returns>
|
||||
private PerformanceCounterCollection GetCollectionByName(string name)
|
||||
{
|
||||
return this.PerformanceCounterCollections.FirstOrDefault(collection => collection.Name == name);
|
||||
}
|
||||
|
||||
/// <summary>Gets a collection by name and creates it if it doesn't exist.</summary>
|
||||
/// <param name="name">The name of the collection.</param>
|
||||
/// <returns>The collection.</returns>
|
||||
private PerformanceCounterCollection GetOrCreateCollectionByName(string name)
|
||||
{
|
||||
PerformanceCounterCollection collection = this.GetCollectionByName(name);
|
||||
|
||||
if (collection != null) return collection;
|
||||
|
||||
collection = new PerformanceCounterCollection(this, name);
|
||||
this.PerformanceCounterCollections.Add(collection);
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
/// <summary>Resets the performance counters for a specific collection.</summary>
|
||||
/// <summary>Reset the performance counters for a specific collection.</summary>
|
||||
/// <param name="name">The collection name.</param>
|
||||
public void ResetCollection(string name)
|
||||
{
|
||||
foreach (PerformanceCounterCollection performanceCounterCollection in
|
||||
this.PerformanceCounterCollections.Where(performanceCounterCollection =>
|
||||
performanceCounterCollection.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)))
|
||||
if (this.Collections.TryGetValue(name, out PerformanceCounterCollection collection))
|
||||
{
|
||||
performanceCounterCollection.ResetCallsPerSecond();
|
||||
performanceCounterCollection.Reset();
|
||||
collection.ResetCallsPerSecond();
|
||||
collection.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Resets performance counters for a specific source.</summary>
|
||||
/// <summary>Reset performance counters for a specific source.</summary>
|
||||
/// <param name="name">The name of the source.</param>
|
||||
public void ResetSource(string name)
|
||||
{
|
||||
foreach (PerformanceCounterCollection performanceCounterCollection in this.PerformanceCounterCollections)
|
||||
foreach (PerformanceCounterCollection performanceCounterCollection in this.Collections.Values)
|
||||
performanceCounterCollection.ResetSource(name);
|
||||
}
|
||||
|
||||
/// <summary>Print any queued alerts.</summary>
|
||||
public void PrintQueuedAlerts()
|
||||
{
|
||||
if (this.Alerts.Count == 0) return;
|
||||
if (this.Alerts.Count == 0)
|
||||
return;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
StringBuilder report = new StringBuilder();
|
||||
|
||||
foreach (AlertEntry alert in this.Alerts)
|
||||
{
|
||||
sb.AppendLine($"{alert.Collection.Name} took {alert.ExecutionTimeMilliseconds:F2}ms (exceeded threshold of {alert.ThresholdMilliseconds:F2}ms)");
|
||||
report.AppendLine($"{alert.Collection.Name} took {alert.ExecutionTimeMilliseconds:F2}ms (exceeded threshold of {alert.ThresholdMilliseconds:F2}ms)");
|
||||
|
||||
foreach (AlertContext context in alert.Context.OrderByDescending(p => p.Elapsed))
|
||||
sb.AppendLine(context.ToString());
|
||||
report.AppendLine(context.ToString());
|
||||
}
|
||||
|
||||
this.Alerts.Clear();
|
||||
this.Monitor.Log(sb.ToString(), LogLevel.Error);
|
||||
this.Monitor.Log(report.ToString(), LogLevel.Error);
|
||||
}
|
||||
|
||||
/// <summary>Adds an alert to the queue.</summary>
|
||||
/// <summary>Add an alert to the queue.</summary>
|
||||
/// <param name="entry">The alert to add.</param>
|
||||
public void AddAlert(AlertEntry entry)
|
||||
{
|
||||
|
@ -175,68 +151,34 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
|
|||
this.Alerts.Add(entry);
|
||||
}
|
||||
|
||||
/// <summary>Initialized the default performance counter collections.</summary>
|
||||
/// <summary>Initialize the default performance counter collections.</summary>
|
||||
/// <param name="eventManager">The event manager.</param>
|
||||
public void InitializePerformanceCounterCollections(EventManager eventManager)
|
||||
{
|
||||
this.PerformanceCounterCollections = new HashSet<PerformanceCounterCollection>()
|
||||
foreach (IManagedEvent @event in eventManager.GetAllEvents())
|
||||
this.Collections[@event.EventName] = new PerformanceCounterCollection(this, @event.EventName, @event.IsPerformanceCritical);
|
||||
}
|
||||
|
||||
/// <summary>Get the underlying performance counters.</summary>
|
||||
public IEnumerable<PerformanceCounterCollection> GetCollections()
|
||||
{
|
||||
return this.Collections.Values;
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Get a collection by name and creates it if it doesn't exist.</summary>
|
||||
/// <param name="name">The name of the collection.</param>
|
||||
private PerformanceCounterCollection GetOrCreateCollectionByName(string name)
|
||||
{
|
||||
if (!this.Collections.TryGetValue(name, out PerformanceCounterCollection collection))
|
||||
{
|
||||
new EventPerformanceCounterCollection(this, eventManager.MenuChanged, false),
|
||||
|
||||
// Rendering Events
|
||||
new EventPerformanceCounterCollection(this, eventManager.Rendering, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.Rendered, true),
|
||||
new EventPerformanceCounterCollection(this, eventManager.RenderingWorld, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.RenderedWorld, true),
|
||||
new EventPerformanceCounterCollection(this, eventManager.RenderingActiveMenu, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.RenderedActiveMenu, true),
|
||||
new EventPerformanceCounterCollection(this, eventManager.RenderingHud, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.RenderedHud, true),
|
||||
|
||||
new EventPerformanceCounterCollection(this, eventManager.WindowResized, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.GameLaunched, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.UpdateTicking, true),
|
||||
new EventPerformanceCounterCollection(this, eventManager.UpdateTicked, true),
|
||||
new EventPerformanceCounterCollection(this, eventManager.OneSecondUpdateTicking, true),
|
||||
new EventPerformanceCounterCollection(this, eventManager.OneSecondUpdateTicked, true),
|
||||
|
||||
new EventPerformanceCounterCollection(this, eventManager.SaveCreating, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.SaveCreated, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.Saving, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.Saved, false),
|
||||
|
||||
new EventPerformanceCounterCollection(this, eventManager.DayStarted, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.DayEnding, false),
|
||||
|
||||
new EventPerformanceCounterCollection(this, eventManager.TimeChanged, true),
|
||||
|
||||
new EventPerformanceCounterCollection(this, eventManager.ReturnedToTitle, false),
|
||||
|
||||
new EventPerformanceCounterCollection(this, eventManager.ButtonPressed, true),
|
||||
new EventPerformanceCounterCollection(this, eventManager.ButtonReleased, true),
|
||||
new EventPerformanceCounterCollection(this, eventManager.CursorMoved, true),
|
||||
new EventPerformanceCounterCollection(this, eventManager.MouseWheelScrolled, true),
|
||||
|
||||
new EventPerformanceCounterCollection(this, eventManager.PeerContextReceived, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.ModMessageReceived, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.PeerDisconnected, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.InventoryChanged, true),
|
||||
new EventPerformanceCounterCollection(this, eventManager.LevelChanged, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.Warped, false),
|
||||
|
||||
new EventPerformanceCounterCollection(this, eventManager.LocationListChanged, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.BuildingListChanged, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.LocationListChanged, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.DebrisListChanged, true),
|
||||
new EventPerformanceCounterCollection(this, eventManager.LargeTerrainFeatureListChanged, true),
|
||||
new EventPerformanceCounterCollection(this, eventManager.NpcListChanged, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.ObjectListChanged, true),
|
||||
new EventPerformanceCounterCollection(this, eventManager.ChestInventoryChanged, true),
|
||||
new EventPerformanceCounterCollection(this, eventManager.TerrainFeatureListChanged, true),
|
||||
new EventPerformanceCounterCollection(this, eventManager.LoadStageChanged, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.UnvalidatedUpdateTicking, false),
|
||||
new EventPerformanceCounterCollection(this, eventManager.UnvalidatedUpdateTicked, false),
|
||||
};
|
||||
collection = new PerformanceCounterCollection(this, name);
|
||||
this.Collections[name] = collection;
|
||||
}
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ using StardewModdingAPI.Framework.Models;
|
|||
using StardewModdingAPI.Framework.ModHelpers;
|
||||
using StardewModdingAPI.Framework.ModLoading;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewModdingAPI.Framework.PerformanceCounter;
|
||||
using StardewModdingAPI.Framework.Reflection;
|
||||
using StardewModdingAPI.Framework.Serialization;
|
||||
using StardewModdingAPI.Patches;
|
||||
|
@ -33,7 +34,6 @@ using StardewModdingAPI.Toolkit.Serialization;
|
|||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
using StardewValley;
|
||||
using Object = StardewValley.Object;
|
||||
using PerformanceCounterManager = StardewModdingAPI.Framework.PerformanceCounter.PerformanceCounterManager;
|
||||
using ThreadState = System.Threading.ThreadState;
|
||||
|
||||
namespace StardewModdingAPI.Framework
|
||||
|
@ -135,8 +135,8 @@ namespace StardewModdingAPI.Framework
|
|||
internal static DeprecationManager DeprecationManager { get; private set; }
|
||||
|
||||
/// <summary>Manages performance counters.</summary>
|
||||
/// <remarks>This is initialized after the game starts. This is accessed directly because it's not part of the normal class model.</remarks>
|
||||
internal static PerformanceCounterManager PerformanceCounterManager { get; private set; }
|
||||
/// <remarks>This is initialized after the game starts. This is non-private for use by Console Commands.</remarks>
|
||||
internal static PerformanceMonitor PerformanceMonitor { get; private set; }
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -167,9 +167,9 @@ namespace StardewModdingAPI.Framework
|
|||
};
|
||||
this.MonitorForGame = this.GetSecondaryMonitor("game");
|
||||
|
||||
SCore.PerformanceCounterManager = new PerformanceCounterManager(this.Monitor);
|
||||
this.EventManager = new EventManager(this.Monitor, this.ModRegistry, SCore.PerformanceCounterManager);
|
||||
SCore.PerformanceCounterManager.InitializePerformanceCounterCollections(this.EventManager);
|
||||
SCore.PerformanceMonitor = new PerformanceMonitor(this.Monitor);
|
||||
this.EventManager = new EventManager(this.Monitor, this.ModRegistry, SCore.PerformanceMonitor);
|
||||
SCore.PerformanceMonitor.InitializePerformanceCounterCollections(this.EventManager);
|
||||
|
||||
SCore.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry);
|
||||
|
||||
|
@ -248,7 +248,7 @@ namespace StardewModdingAPI.Framework
|
|||
jsonHelper: this.Toolkit.JsonHelper,
|
||||
modRegistry: this.ModRegistry,
|
||||
deprecationManager: SCore.DeprecationManager,
|
||||
performanceCounterManager: SCore.PerformanceCounterManager,
|
||||
performanceMonitor: SCore.PerformanceMonitor,
|
||||
onGameInitialized: this.InitializeAfterGameStart,
|
||||
onGameExiting: this.Dispose,
|
||||
cancellationToken: this.CancellationToken,
|
||||
|
@ -1307,6 +1307,7 @@ namespace StardewModdingAPI.Framework
|
|||
this.ReloadTranslations(this.ModRegistry.GetAll(contentPacks: false));
|
||||
this.Monitor.Log("Reloaded translation files for all mods. This only affects new translations the mods fetch; if they cached some text, it may not be updated.", LogLevel.Info);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException($"Unrecognized core SMAPI command '{name}'.");
|
||||
}
|
||||
|
|
|
@ -59,7 +59,8 @@ namespace StardewModdingAPI.Framework
|
|||
/// <summary>Manages deprecation warnings.</summary>
|
||||
private readonly DeprecationManager DeprecationManager;
|
||||
|
||||
private readonly PerformanceCounterManager PerformanceCounterManager;
|
||||
/// <summary>Tracks performance metrics.</summary>
|
||||
private readonly PerformanceMonitor PerformanceMonitor;
|
||||
|
||||
/// <summary>The maximum number of consecutive attempts SMAPI should make to recover from a draw error.</summary>
|
||||
private readonly Countdown DrawCrashTimer = new Countdown(60); // 60 ticks = roughly one second
|
||||
|
@ -155,12 +156,12 @@ namespace StardewModdingAPI.Framework
|
|||
/// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
|
||||
/// <param name="modRegistry">Tracks the installed mods.</param>
|
||||
/// <param name="deprecationManager">Manages deprecation warnings.</param>
|
||||
/// <param name="performanceCounterManager">Manages performance monitoring.</param>
|
||||
/// <param name="performanceMonitor">Tracks performance metrics.</param>
|
||||
/// <param name="onGameInitialized">A callback to invoke after the game finishes initializing.</param>
|
||||
/// <param name="onGameExiting">A callback to invoke when the game exits.</param>
|
||||
/// <param name="cancellationToken">Propagates notification that SMAPI should exit.</param>
|
||||
/// <param name="logNetworkTraffic">Whether to log network traffic.</param>
|
||||
internal SGame(Monitor monitor, IMonitor monitorForGame, Reflector reflection, Translator translator, EventManager eventManager, JsonHelper jsonHelper, ModRegistry modRegistry, DeprecationManager deprecationManager, PerformanceCounterManager performanceCounterManager, Action onGameInitialized, Action onGameExiting, CancellationTokenSource cancellationToken, bool logNetworkTraffic)
|
||||
internal SGame(Monitor monitor, IMonitor monitorForGame, Reflector reflection, Translator translator, EventManager eventManager, JsonHelper jsonHelper, ModRegistry modRegistry, DeprecationManager deprecationManager, PerformanceMonitor performanceMonitor, Action onGameInitialized, Action onGameExiting, CancellationTokenSource cancellationToken, bool logNetworkTraffic)
|
||||
{
|
||||
this.OnLoadingFirstAsset = SGame.ConstructorHack.OnLoadingFirstAsset;
|
||||
SGame.ConstructorHack = null;
|
||||
|
@ -180,7 +181,7 @@ namespace StardewModdingAPI.Framework
|
|||
this.Reflection = reflection;
|
||||
this.Translator = translator;
|
||||
this.DeprecationManager = deprecationManager;
|
||||
this.PerformanceCounterManager = performanceCounterManager;
|
||||
this.PerformanceMonitor = performanceMonitor;
|
||||
this.OnGameInitialized = onGameInitialized;
|
||||
this.OnGameExiting = onGameExiting;
|
||||
Game1.input = new SInputState();
|
||||
|
@ -312,7 +313,7 @@ namespace StardewModdingAPI.Framework
|
|||
try
|
||||
{
|
||||
this.DeprecationManager.PrintQueued();
|
||||
this.PerformanceCounterManager.PrintQueuedAlerts();
|
||||
this.PerformanceMonitor.PrintQueuedAlerts();
|
||||
|
||||
/*********
|
||||
** First-tick initialization
|
||||
|
|
|
@ -11,7 +11,7 @@ using StardewModdingAPI.Framework;
|
|||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
|
||||
[assembly: InternalsVisibleTo("SMAPI.Tests")]
|
||||
[assembly: InternalsVisibleTo("ConsoleCommands")]
|
||||
[assembly: InternalsVisibleTo("ConsoleCommands")] // for performance monitoring commands
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // Moq for unit testing
|
||||
namespace StardewModdingAPI
|
||||
{
|
||||
|
|
|
@ -15,12 +15,7 @@
|
|||
<ApplicationIcon>icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DefineConstants>SMAPI_FOR_WINDOWS</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Cyotek.CircularBuffer" Version="1.0.2" />
|
||||
<PackageReference Include="LargeAddressAware" Version="1.0.3" />
|
||||
<PackageReference Include="Lib.Harmony" Version="1.2.0.1" />
|
||||
<PackageReference Include="Mono.Cecil" Version="0.11.1" />
|
||||
|
|
Loading…
Reference in New Issue