Added documentation for all commands. Renamed the "monitor" command to "trigger". Method name refactoring to be more consistent.
This commit is contained in:
parent
1d58a525fa
commit
fce5814bcb
|
@ -7,65 +7,71 @@ using StardewModdingAPI.Framework.PerformanceCounter;
|
|||
|
||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
||||
{
|
||||
// ReSharper disable once UnusedType.Global
|
||||
internal class PerformanceCounterCommand : TrainerCommand
|
||||
{
|
||||
private readonly Dictionary<Command, string[]> CommandNames = new Dictionary<Command, string[]>()
|
||||
/// <summary>The command names and aliases</summary>
|
||||
private readonly Dictionary<SubCommand, string[]> SubCommandNames = new Dictionary<SubCommand, string[]>()
|
||||
{
|
||||
{Command.Summary, new[] {"summary", "sum", "s"}},
|
||||
{Command.Detail, new[] {"detail", "d"}},
|
||||
{Command.Reset, new[] {"reset", "r"}},
|
||||
{Command.Monitor, new[] {"monitor"}},
|
||||
{Command.Examples, new[] {"examples"}},
|
||||
{Command.Concepts, new[] {"concepts"}},
|
||||
{Command.Help, new[] {"help"}},
|
||||
{SubCommand.Summary, new[] {"summary", "sum", "s"}},
|
||||
{SubCommand.Detail, new[] {"detail", "d"}},
|
||||
{SubCommand.Reset, new[] {"reset", "r"}},
|
||||
{SubCommand.Trigger, new[] {"trigger"}},
|
||||
{SubCommand.Examples, new[] {"examples"}},
|
||||
{SubCommand.Concepts, new[] {"concepts"}},
|
||||
{SubCommand.Help, new[] {"help"}},
|
||||
};
|
||||
|
||||
private enum Command
|
||||
/// <summary>The available commands enum</summary>
|
||||
private enum SubCommand
|
||||
{
|
||||
Summary,
|
||||
Detail,
|
||||
Reset,
|
||||
Monitor,
|
||||
Trigger,
|
||||
Examples,
|
||||
Help,
|
||||
Concepts,
|
||||
None
|
||||
}
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public PerformanceCounterCommand() : base("pc", PerformanceCounterCommand.GetDescription())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Handle the command.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <param name="command">The command name.</param>
|
||||
/// <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))
|
||||
{
|
||||
Command subCommand = this.ParseCommandString(subCommandString);
|
||||
SubCommand subSubCommand = this.ParseCommandString(subCommandString);
|
||||
|
||||
switch (subCommand)
|
||||
switch (subSubCommand)
|
||||
{
|
||||
case Command.Summary:
|
||||
this.DisplayPerformanceCounterSummary(monitor, args);
|
||||
case SubCommand.Summary:
|
||||
this.HandleSummarySubCommand(monitor, args);
|
||||
break;
|
||||
case Command.Detail:
|
||||
this.DisplayPerformanceCounterDetail(monitor, args);
|
||||
case SubCommand.Detail:
|
||||
this.HandleDetailSubCommand(monitor, args);
|
||||
break;
|
||||
case Command.Reset:
|
||||
this.ResetCounter(monitor, args);
|
||||
case SubCommand.Reset:
|
||||
this.HandleResetSubCommand(monitor, args);
|
||||
break;
|
||||
case Command.Monitor:
|
||||
this.HandleMonitor(monitor, args);
|
||||
case SubCommand.Trigger:
|
||||
this.HandleTriggerSubCommand(monitor, args);
|
||||
break;
|
||||
case Command.Examples:
|
||||
case SubCommand.Examples:
|
||||
break;
|
||||
case Command.Concepts:
|
||||
this.ShowHelp(monitor, Command.Concepts);
|
||||
case SubCommand.Concepts:
|
||||
this.OutputHelp(monitor, SubCommand.Concepts);
|
||||
break;
|
||||
case Command.Help:
|
||||
args.TryGet(1, "command", out string commandString, true);
|
||||
|
||||
var helpCommand = this.ParseCommandString(commandString);
|
||||
this.ShowHelp(monitor, helpCommand);
|
||||
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}");
|
||||
|
@ -73,60 +79,154 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.DisplayPerformanceCounterSummary(monitor, args);
|
||||
}
|
||||
this.HandleSummarySubCommand(monitor, args);
|
||||
}
|
||||
|
||||
private Command ParseCommandString(string command)
|
||||
/// <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)
|
||||
{
|
||||
foreach (var i in this.CommandNames.Where(i => i.Value.Any(str => str.Equals(command, StringComparison.InvariantCultureIgnoreCase))))
|
||||
IEnumerable<PerformanceCounterCollection> data;
|
||||
|
||||
if (!args.TryGet(1, "mode", out string mode, false))
|
||||
{
|
||||
return i.Key;
|
||||
mode = "important";
|
||||
}
|
||||
|
||||
return Command.None;
|
||||
switch (mode)
|
||||
{
|
||||
case null:
|
||||
case "important":
|
||||
data = SCore.PerformanceCounterManager.PerformanceCounterCollections.Where(p => p.IsImportant);
|
||||
break;
|
||||
case "all":
|
||||
data = SCore.PerformanceCounterManager.PerformanceCounterCollections;
|
||||
break;
|
||||
default:
|
||||
data = SCore.PerformanceCounterManager.PerformanceCounterCollections.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();
|
||||
|
||||
sb.AppendLine("Summary:");
|
||||
sb.AppendLine(this.GetTableString(
|
||||
data: data,
|
||||
header: new[] {"Collection", "Avg Calls/s", "Avg Execution Time (Game)", "Avg Execution Time (Mods)", "Avg Execution Time (Game+Mods)"},
|
||||
getRow: item => new[]
|
||||
{
|
||||
item.Name,
|
||||
item.GetAverageCallsPerSecond().ToString(),
|
||||
this.FormatMilliseconds(item.GetGameAverageExecutionTime(), threshold),
|
||||
this.FormatMilliseconds(item.GetModsAverageExecutionTime(), threshold),
|
||||
this.FormatMilliseconds(item.GetAverageExecutionTime(), threshold)
|
||||
},
|
||||
true
|
||||
));
|
||||
|
||||
monitor.Log(sb.ToString(), LogLevel.Info);
|
||||
}
|
||||
|
||||
private void HandleMonitor(IMonitor monitor, ArgumentParser args)
|
||||
/// <summary>Handles the detail sub command.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <param name="args">The command arguments.</param>
|
||||
private void HandleDetailSubCommand(IMonitor monitor, ArgumentParser args)
|
||||
{
|
||||
var collections = new List<PerformanceCounterCollection>();
|
||||
TimeSpan averageInterval = TimeSpan.FromSeconds(60);
|
||||
double? thresholdMilliseconds = null;
|
||||
string sourceFilter = null;
|
||||
|
||||
if (args.TryGet(1, "collection", out string collectionName))
|
||||
{
|
||||
collections.AddRange(SCore.PerformanceCounterManager.PerformanceCounterCollections.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (PerformanceCounterCollection c in collections)
|
||||
{
|
||||
this.OutputPerformanceCollectionDetail(monitor, c, averageInterval, thresholdMilliseconds, sourceFilter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Handles the trigger sub command.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <param name="args">The command arguments.</param>
|
||||
private void HandleTriggerSubCommand(IMonitor monitor, ArgumentParser args)
|
||||
{
|
||||
if (args.TryGet(1, "mode", out string mode, false))
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case "list":
|
||||
this.ListMonitors(monitor);
|
||||
this.OutputAlertTriggers(monitor);
|
||||
break;
|
||||
case "collection":
|
||||
args.TryGet(2, "name", out string collectionName);
|
||||
decimal threshold = 0;
|
||||
if (args.IsDecimal(3) && args.TryGetDecimal(3, "threshold", out threshold, false))
|
||||
if (args.TryGet(2, "name", out string collectionName))
|
||||
{
|
||||
this.SetCollectionMonitor(monitor, collectionName, null, (double)threshold);
|
||||
} else if (args.TryGet(3, "source", out string source))
|
||||
{
|
||||
if (args.TryGetDecimal(4, "threshold", out threshold))
|
||||
if (args.TryGetDecimal(3, "threshold", out decimal threshold))
|
||||
{
|
||||
this.SetCollectionMonitor(monitor, collectionName, source, (double) threshold);
|
||||
if (args.TryGet(4, "source", out string source, false))
|
||||
{
|
||||
this.ConfigureAlertTrigger(monitor, collectionName, source, threshold);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ConfigureAlertTrigger(monitor, collectionName, null, threshold);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "pause":
|
||||
SCore.PerformanceCounterManager.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);
|
||||
break;
|
||||
case "clear":
|
||||
this.ClearMonitors(monitor);
|
||||
this.ClearAlertTriggers(monitor);
|
||||
break;
|
||||
default:
|
||||
monitor.Log($"Unknown mode {mode}. See 'pc help monitor' for usage.");
|
||||
this.LogUsageError(monitor, $"Unknown mode {mode}. See 'pc help trigger' for usage.");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ListMonitors(monitor);
|
||||
this.OutputAlertTriggers(monitor);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetCollectionMonitor(IMonitor monitor, string collectionName, string sourceName, double threshold)
|
||||
/// <summary>Sets up an an alert trigger.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <param name="collectionName">The name of the collection.</param>
|
||||
/// <param name="sourceName">The name of the source, or null for all sources.</param>
|
||||
/// <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)
|
||||
{
|
||||
|
@ -134,9 +234,18 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
{
|
||||
if (sourceName == null)
|
||||
{
|
||||
collection.EnableAlerts = true;
|
||||
collection.AlertThresholdMilliseconds = threshold;
|
||||
monitor.Log($"Set up monitor for '{collectionName}' with '{this.FormatMilliseconds(threshold)}'", LogLevel.Info);
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
collection.EnableAlerts = false;
|
||||
monitor.Log($"Cleared alert triggering for '{collection}'.");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
@ -145,9 +254,17 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
{
|
||||
if (performanceCounter.Value.Source.ToLowerInvariant().Equals(sourceName.ToLowerInvariant()))
|
||||
{
|
||||
performanceCounter.Value.EnableAlerts = true;
|
||||
performanceCounter.Value.AlertThresholdMilliseconds = threshold;
|
||||
monitor.Log($"Set up monitor for '{sourceName}' in collection '{collectionName}' with '{this.FormatMilliseconds(threshold)}", LogLevel.Info);
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
performanceCounter.Value.EnableAlerts = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -162,15 +279,17 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
}
|
||||
|
||||
|
||||
private void ClearMonitors(IMonitor monitor)
|
||||
/// <summary>Clears alert triggering for all collections.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
private void ClearAlertTriggers(IMonitor monitor)
|
||||
{
|
||||
int clearedCounters = 0;
|
||||
int clearedTriggers = 0;
|
||||
foreach (PerformanceCounterCollection collection in SCore.PerformanceCounterManager.PerformanceCounterCollections)
|
||||
{
|
||||
if (collection.EnableAlerts)
|
||||
{
|
||||
collection.EnableAlerts = false;
|
||||
clearedCounters++;
|
||||
clearedTriggers++;
|
||||
}
|
||||
|
||||
foreach (var performanceCounter in collection.PerformanceCounters)
|
||||
|
@ -178,82 +297,203 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
if (performanceCounter.Value.EnableAlerts)
|
||||
{
|
||||
performanceCounter.Value.EnableAlerts = false;
|
||||
clearedCounters++;
|
||||
clearedTriggers++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
monitor.Log($"Cleared {clearedCounters} counters.", LogLevel.Info);
|
||||
monitor.Log($"Cleared {clearedTriggers} alert triggers.", LogLevel.Info);
|
||||
}
|
||||
|
||||
private void ListMonitors(IMonitor monitor)
|
||||
/// <summary>Lists all configured alert triggers.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
private void OutputAlertTriggers(IMonitor monitor)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("Configured triggers:");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine();
|
||||
var collectionMonitors = new List<(string collectionName, double threshold)>();
|
||||
var sourceMonitors = new List<(string collectionName, string sourceName, double threshold)>();
|
||||
var collectionTriggers = new List<(string collectionName, double threshold)>();
|
||||
var sourceTriggers = new List<(string collectionName, string sourceName, double threshold)>();
|
||||
|
||||
foreach (PerformanceCounterCollection collection in SCore.PerformanceCounterManager.PerformanceCounterCollections)
|
||||
{
|
||||
if (collection.EnableAlerts)
|
||||
{
|
||||
collectionMonitors.Add((collection.Name, collection.AlertThresholdMilliseconds));
|
||||
collectionTriggers.Add((collection.Name, collection.AlertThresholdMilliseconds));
|
||||
}
|
||||
|
||||
sourceMonitors.AddRange(from performanceCounter in
|
||||
sourceTriggers.AddRange(from performanceCounter in
|
||||
collection.PerformanceCounters where performanceCounter.Value.EnableAlerts
|
||||
select (collection.Name, performanceCounter.Value.Source, MonitorThresholdMilliseconds: performanceCounter.Value.AlertThresholdMilliseconds));
|
||||
select (collection.Name, performanceCounter.Value.Source, performanceCounter.Value.AlertThresholdMilliseconds));
|
||||
}
|
||||
|
||||
if (collectionMonitors.Count > 0)
|
||||
if (collectionTriggers.Count > 0)
|
||||
{
|
||||
sb.AppendLine("Collection Monitors:");
|
||||
sb.AppendLine("Collection Triggers:");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(this.GetTableString(
|
||||
data: collectionMonitors,
|
||||
data: collectionTriggers,
|
||||
header: new[] {"Collection", "Threshold"},
|
||||
getRow: item => new[]
|
||||
{
|
||||
item.collectionName,
|
||||
this.FormatMilliseconds(item.threshold)
|
||||
}
|
||||
},
|
||||
true
|
||||
));
|
||||
|
||||
sb.AppendLine();
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine("No collection triggers.");
|
||||
}
|
||||
|
||||
if (sourceMonitors.Count > 0)
|
||||
if (sourceTriggers.Count > 0)
|
||||
{
|
||||
sb.AppendLine("Source Monitors:");
|
||||
sb.AppendLine("Source Triggers:");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(this.GetTableString(
|
||||
data: sourceMonitors,
|
||||
data: sourceTriggers,
|
||||
header: new[] {"Collection", "Source", "Threshold"},
|
||||
getRow: item => new[]
|
||||
{
|
||||
item.collectionName,
|
||||
item.sourceName,
|
||||
this.FormatMilliseconds(item.threshold)
|
||||
}
|
||||
},
|
||||
true
|
||||
));
|
||||
|
||||
sb.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine("No source triggers.");
|
||||
}
|
||||
|
||||
monitor.Log(sb.ToString(), LogLevel.Info);
|
||||
}
|
||||
|
||||
private void ShowHelp(IMonitor monitor, Command command)
|
||||
/// <summary>Handles the reset sub command.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <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"}))
|
||||
{
|
||||
args.TryGet(2, "name", out string name);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case "category":
|
||||
SCore.PerformanceCounterManager.ResetCollection(name);
|
||||
monitor.Log($"All performance counters for category {name} are now cleared.", LogLevel.Info);
|
||||
break;
|
||||
case "source":
|
||||
SCore.PerformanceCounterManager.ResetSource(name);
|
||||
monitor.Log($"All performance counters for source {name} are now cleared.", LogLevel.Info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SCore.PerformanceCounterManager.Reset();
|
||||
monitor.Log("All performance counters are now cleared.", LogLevel.Info);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Outputs the details for a collection.</summary>
|
||||
/// <param name="monitor">Writes messages to the console and log file.</param>
|
||||
/// <param name="collection">The collection.</param>
|
||||
/// <param name="averageInterval">The interval over which to calculate the averages.</param>
|
||||
/// <param name="thresholdMilliseconds">The threshold.</param>
|
||||
/// <param name="sourceFilter">The source filter.</param>
|
||||
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");
|
||||
|
||||
List<KeyValuePair<string, PerformanceCounter>> data = collection.PerformanceCounters.ToList();
|
||||
|
||||
if (sourceFilter != null)
|
||||
{
|
||||
data = collection.PerformanceCounters.Where(p =>
|
||||
p.Value.Source.ToLowerInvariant().Contains(sourceFilter.ToLowerInvariant())).ToList();
|
||||
}
|
||||
|
||||
if (thresholdMilliseconds != null)
|
||||
{
|
||||
data = data.Where(p => p.Value.GetAverage(averageInterval) >= thresholdMilliseconds).ToList();
|
||||
}
|
||||
|
||||
if (data.Any())
|
||||
{
|
||||
sb.AppendLine(this.GetTableString(
|
||||
data: data,
|
||||
header: new[] {"Mod", $"Avg Execution Time (last {(int) averageInterval.TotalSeconds}s)", "Last Execution Time", "Peak Execution Time"},
|
||||
getRow: item => new[]
|
||||
{
|
||||
item.Key,
|
||||
this.FormatMilliseconds(item.Value.GetAverage(averageInterval), thresholdMilliseconds),
|
||||
this.FormatMilliseconds(item.Value.GetLastEntry()?.ElapsedMilliseconds),
|
||||
this.FormatMilliseconds(item.Value.GetPeak()?.ElapsedMilliseconds)
|
||||
},
|
||||
true
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Clear();
|
||||
sb.AppendLine($"Performance Counter for {collection.Name}: none.");
|
||||
}
|
||||
|
||||
monitor.Log(sb.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>
|
||||
/// <param name="thresholdMilliseconds">The threshold. Any value below this is returned as "-".</param>
|
||||
/// <returns>The formatted milliseconds.</returns>
|
||||
private string FormatMilliseconds(double? milliseconds, double? thresholdMilliseconds = null)
|
||||
{
|
||||
if (milliseconds == null || (thresholdMilliseconds != null && milliseconds < thresholdMilliseconds))
|
||||
{
|
||||
return "-";
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine();
|
||||
switch (command)
|
||||
|
||||
switch (subCommand)
|
||||
{
|
||||
case Command.Concepts:
|
||||
case SubCommand.Concepts:
|
||||
sb.AppendLine("A performance counter is a metric which measures execution time. Each performance");
|
||||
sb.AppendLine("counter consists of:");
|
||||
sb.AppendLine();
|
||||
|
@ -271,7 +511,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
sb.AppendLine();
|
||||
sb.AppendLine("[1] https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events");
|
||||
break;
|
||||
case Command.Detail:
|
||||
case SubCommand.Detail:
|
||||
sb.AppendLine("Usage: pc detail <collection> <source>");
|
||||
sb.AppendLine(" pc detail <collection> <threshold>");
|
||||
sb.AppendLine();
|
||||
|
@ -288,61 +528,66 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
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");
|
||||
break;
|
||||
case Command.Summary:
|
||||
sb.AppendLine("Usage: pc summary <mode|name>");
|
||||
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(" <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(" <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");
|
||||
break;
|
||||
case Command.Monitor:
|
||||
sb.AppendLine("Usage: pc monitor <mode> <collectionName> <threshold>");
|
||||
sb.AppendLine("Usage: pc monitor <mode> <collectionName> <sourceName> <threshold>");
|
||||
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 monitoring settings.");
|
||||
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 monitored.");
|
||||
sb.AppendLine(" - list Lists current monitoring settings");
|
||||
sb.AppendLine(" - collection Sets up a monitor for a collection");
|
||||
sb.AppendLine(" - clear Clears all monitoring entries");
|
||||
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(" 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 monitored. Must be an exact match.");
|
||||
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(" Can also be 'remove' to remove the threshold.");
|
||||
sb.AppendLine(" Specify '0' to remove the threshold.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Examples:");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("pc monitor collection Display.Rendering 10");
|
||||
sb.AppendLine(" Sets up monitoring to write an alert on the console if the execution time of all performance counters in");
|
||||
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 monitor collection Display.Rendering Pathoschild.ChestsAnywhere 5");
|
||||
sb.AppendLine(" Sets up monitoring to write an alert on the console if the execution time of Pathoschild.ChestsAnywhere in");
|
||||
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 monitor collection Display.Rendering remove");
|
||||
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 monitor clear");
|
||||
sb.AppendLine(" Clears all previously setup monitors.");
|
||||
sb.AppendLine("pc trigger clear");
|
||||
sb.AppendLine(" Clears all previously setup alert triggers.");
|
||||
break;
|
||||
case Command.Reset:
|
||||
case SubCommand.Reset:
|
||||
sb.AppendLine("Usage: pc reset <type> <name>");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Resets performance counters.");
|
||||
|
@ -368,153 +613,12 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
monitor.Log(sb.ToString(), LogLevel.Info);
|
||||
}
|
||||
|
||||
private void ResetCounter(IMonitor monitor, ArgumentParser args)
|
||||
{
|
||||
if (args.TryGet(1, "type", out string type, false))
|
||||
{
|
||||
args.TryGet(2, "name", out string name);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case "category":
|
||||
SCore.PerformanceCounterManager.ResetCollection(name);
|
||||
monitor.Log($"All performance counters for category {name} are now cleared.", LogLevel.Info);
|
||||
break;
|
||||
case "mod":
|
||||
SCore.PerformanceCounterManager.ResetSource(name);
|
||||
monitor.Log($"All performance counters for mod {name} are now cleared.", LogLevel.Info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SCore.PerformanceCounterManager.Reset();
|
||||
monitor.Log("All performance counters are now cleared.", LogLevel.Info);
|
||||
}
|
||||
}
|
||||
|
||||
private void DisplayPerformanceCounterSummary(IMonitor monitor, ArgumentParser args)
|
||||
{
|
||||
IEnumerable<PerformanceCounterCollection> data;
|
||||
|
||||
if (!args.TryGet(1, "mode", out string mode, false))
|
||||
{
|
||||
mode = "important";
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case null:
|
||||
case "important":
|
||||
data = SCore.PerformanceCounterManager.PerformanceCounterCollections.Where(p => p.IsImportant);
|
||||
break;
|
||||
case "all":
|
||||
data = SCore.PerformanceCounterManager.PerformanceCounterCollections;
|
||||
break;
|
||||
default:
|
||||
data = SCore.PerformanceCounterManager.PerformanceCounterCollections.Where(p =>
|
||||
p.Name.ToLowerInvariant().Contains(mode.ToLowerInvariant()));
|
||||
break;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("Summary:");
|
||||
sb.AppendLine(this.GetTableString(
|
||||
data: data,
|
||||
header: new[] {"Collection", "Avg Calls/s", "Avg Execution Time (Game)", "Avg Execution Time (Mods)", "Avg Execution Time (Game+Mods)"},
|
||||
getRow: item => new[]
|
||||
{
|
||||
item.Name,
|
||||
item.GetAverageCallsPerSecond().ToString(),
|
||||
this.FormatMilliseconds(item.GetGameAverageExecutionTime()),
|
||||
this.FormatMilliseconds(item.GetModsAverageExecutionTime()),
|
||||
this.FormatMilliseconds(item.GetAverageExecutionTime())
|
||||
}
|
||||
));
|
||||
|
||||
monitor.Log(sb.ToString(), LogLevel.Info);
|
||||
}
|
||||
|
||||
private void DisplayPerformanceCounterDetail(IMonitor monitor, ArgumentParser args)
|
||||
{
|
||||
List<PerformanceCounterCollection> collections = new List<PerformanceCounterCollection>();
|
||||
TimeSpan averageInterval = TimeSpan.FromSeconds(60);
|
||||
double? thresholdMilliseconds = null;
|
||||
string sourceFilter = null;
|
||||
|
||||
if (args.TryGet(1, "collection", out string collectionName))
|
||||
{
|
||||
collections.AddRange(SCore.PerformanceCounterManager.PerformanceCounterCollections.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var c in collections)
|
||||
{
|
||||
this.DisplayPerformanceCollectionDetail(monitor, c, averageInterval, thresholdMilliseconds, sourceFilter);
|
||||
}
|
||||
}
|
||||
|
||||
private void DisplayPerformanceCollectionDetail(IMonitor monitor, PerformanceCounterCollection collection,
|
||||
TimeSpan averageInterval, double? thresholdMilliseconds, string sourceFilter = null)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder($"Performance Counter for {collection.Name}:\n\n");
|
||||
|
||||
IEnumerable<KeyValuePair<string, PerformanceCounter>> data = collection.PerformanceCounters;
|
||||
|
||||
if (sourceFilter != null)
|
||||
{
|
||||
data = collection.PerformanceCounters.Where(p =>
|
||||
p.Value.Source.ToLowerInvariant().Contains(sourceFilter.ToLowerInvariant()));
|
||||
}
|
||||
|
||||
if (thresholdMilliseconds != null)
|
||||
{
|
||||
data = data.Where(p => p.Value.GetAverage(averageInterval) >= thresholdMilliseconds);
|
||||
}
|
||||
|
||||
sb.AppendLine(this.GetTableString(
|
||||
data: data,
|
||||
header: new[] {"Mod", $"Avg Execution Time (last {(int) averageInterval.TotalSeconds}s)", "Last Execution Time", "Peak Execution Time"},
|
||||
getRow: item => new[]
|
||||
{
|
||||
item.Key,
|
||||
this.FormatMilliseconds(item.Value.GetAverage(averageInterval), thresholdMilliseconds),
|
||||
this.FormatMilliseconds(item.Value.GetLastEntry()?.ElapsedMilliseconds),
|
||||
this.FormatMilliseconds(item.Value.GetPeak()?.ElapsedMilliseconds)
|
||||
}
|
||||
));
|
||||
|
||||
monitor.Log(sb.ToString(), LogLevel.Info);
|
||||
}
|
||||
|
||||
private string FormatMilliseconds(double? milliseconds, double? thresholdMilliseconds = null)
|
||||
{
|
||||
if (milliseconds == null || (thresholdMilliseconds != null && milliseconds < thresholdMilliseconds))
|
||||
{
|
||||
return "-";
|
||||
}
|
||||
|
||||
return ((double) milliseconds).ToString("F2");
|
||||
}
|
||||
|
||||
/// <summary>Get the command description.</summary>
|
||||
private static string GetDescription()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("Displays and configured performance counters.");
|
||||
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.");
|
||||
|
@ -526,7 +630,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
|
|||
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(" monitor Configures monitoring settings");
|
||||
sb.AppendLine(" trigger Configures alert triggers");
|
||||
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");
|
||||
|
|
|
@ -174,6 +174,7 @@ 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)
|
||||
{
|
||||
// create shortcut initializers
|
||||
|
|
|
@ -40,6 +40,7 @@ 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)
|
||||
{
|
||||
this.EventName = eventName;
|
||||
|
|
|
@ -114,6 +114,10 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
|
|||
DateTime start = relativeTo.Value.Subtract(range);
|
||||
|
||||
var entries = this._counter.Where(x => (x.EventTime >= start) && (x.EventTime <= relativeTo));
|
||||
|
||||
if (!entries.Any())
|
||||
return 0;
|
||||
|
||||
return entries.Average(x => x.ElapsedMilliseconds);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,9 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
|
|||
/// <summary>The invocation stopwatch.</summary>
|
||||
private readonly Stopwatch InvocationStopwatch = new Stopwatch();
|
||||
|
||||
/// <summary>Specifies if alerts should be paused.</summary>
|
||||
public bool PauseAlerts { get; set; }
|
||||
|
||||
/// <summary>Constructs a performance counter manager.</summary>
|
||||
/// <param name="monitor">The monitor for output logging.</param>
|
||||
public PerformanceCounterManager(IMonitor monitor)
|
||||
|
@ -144,7 +147,8 @@ namespace StardewModdingAPI.Framework.PerformanceCounter
|
|||
/// <param name="entry">The alert to add.</param>
|
||||
public void AddAlert(AlertEntry entry)
|
||||
{
|
||||
this.Alerts.Add(entry);
|
||||
if (!this.PauseAlerts)
|
||||
this.Alerts.Add(entry);
|
||||
}
|
||||
|
||||
/// <summary>Initialized the default performance counter collections.</summary>
|
||||
|
|
Loading…
Reference in New Issue