diff options
Diffstat (limited to 'src')
5 files changed, 361 insertions, 247 deletions
diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/PerformanceCounterCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/PerformanceCounterCommand.cs index 750e3792..82b44562 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/PerformanceCounterCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/PerformanceCounterCommand.cs @@ -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.HandleSummarySubCommand(monitor, args); + } + + /// <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"; + } + + 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)) { - this.DisplayPerformanceCounterSummary(monitor, args); + 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 Command ParseCommandString(string command) + /// <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) { - foreach (var i in this.CommandNames.Where(i => i.Value.Any(str => str.Equals(command, StringComparison.InvariantCultureIgnoreCase)))) + var collections = new List<PerformanceCounterCollection>(); + TimeSpan averageInterval = TimeSpan.FromSeconds(60); + double? thresholdMilliseconds = null; + string sourceFilter = null; + + if (args.TryGet(1, "collection", out string collectionName)) { - return i.Key; + 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; + } + } } - return Command.None; + foreach (PerformanceCounterCollection c in collections) + { + this.OutputPerformanceCollectionDetail(monitor, c, averageInterval, thresholdMilliseconds, sourceFilter); + } } - private void HandleMonitor(IMonitor monitor, ArgumentParser args) + /// <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"); diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs index 9c65a6cc..19a4dff8 100644 --- a/src/SMAPI/Framework/Events/EventManager.cs +++ b/src/SMAPI/Framework/Events/EventManager.cs @@ -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 diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index bba94c35..dfdd7449 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -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; diff --git a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounter.cs b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounter.cs index b2ec4c90..33ddde2f 100644 --- a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounter.cs +++ b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounter.cs @@ -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); } } diff --git a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterManager.cs b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterManager.cs index d8f1f172..49720431 100644 --- a/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterManager.cs +++ b/src/SMAPI/Framework/PerformanceCounter/PerformanceCounterManager.cs @@ -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> |