From 22a0a32b6d959946bfd80bf0ca9796378f36e0cd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 26 Jan 2020 19:49:17 -0500 Subject: 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. --- .../Framework/Commands/ArgumentParser.cs | 22 +- .../Commands/Other/PerformanceCounterCommand.cs | 655 ++++++++++----------- .../Framework/Commands/TrainerCommand.cs | 18 +- 3 files changed, 323 insertions(+), 372 deletions(-) (limited to 'src/SMAPI.Mods.ConsoleCommands/Framework') diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ArgumentParser.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ArgumentParser.cs index 40691a3e..9c7082c9 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ArgumentParser.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ArgumentParser.cs @@ -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 /// The zero-based index of the element to get. public string this[int index] => this.Args[index]; - /// A method which parses a string argument into the given value. - /// The expected argument type. - /// The argument to parse. - /// The parsed value. - /// Returns whether the argument was successfully parsed. - public delegate bool ParseDelegate(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; - } - /// Try to read a decimal argument. /// The argument index. /// The argument name for error messages. diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/PerformanceCounterCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/PerformanceCounterCommand.cs index 2260296b..820f1939 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/PerformanceCounterCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/PerformanceCounterCommand.cs @@ -7,24 +7,13 @@ using StardewModdingAPI.Framework.PerformanceCounter; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other { - // ReSharper disable once UnusedType.Global + /// A set of commands which displays or configures performance monitoring. internal class PerformanceCounterCommand : TrainerCommand { - /// The command names and aliases - private readonly Dictionary SubCommandNames = new Dictionary() - { - {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"}}, - }; - - /// The available commands enum + /********* + ** Fields + *********/ + /// The available commands. 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 + *********/ /// Construct an instance. - public PerformanceCounterCommand() : base("pc", PerformanceCounterCommand.GetDescription()) - { - } + public PerformanceCounterCommand() + : base("performance", PerformanceCounterCommand.GetDescription()) { } /// Handle the command. /// Writes messages to the console and log file. @@ -50,92 +39,94 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other /// The command arguments. 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 + *********/ /// Handles the summary sub command. /// Writes messages to the console and log file. /// The command arguments. private void HandleSummarySubCommand(IMonitor monitor, ArgumentParser args) { - IEnumerable data; - if (!args.TryGet(1, "mode", out string mode, false)) - { mode = "important"; - } + IEnumerable 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); } /// Handles the detail sub command. @@ -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); - } } /// Handles the trigger sub command. @@ -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); - } } /// Sets up an an alert trigger. @@ -246,7 +225,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other /// The trigger threshold, or 0 to remove. 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 /// True to dump the triggers as commands. 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(); + var sourceTriggers = new List(); - 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); } /// Handles the reset sub command. @@ -422,25 +381,25 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other /// The command arguments. 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> 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."); - } - - monitor.Log(sb.ToString(), LogLevel.Info); - } - - /// Parses a command string and returns the associated command. - /// The command string - /// The parsed command. - 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; + report.Clear(); + report.AppendLine($"Performance Counter for {collection.Name}: none."); } - return SubCommand.None; + monitor.Log(report.ToString(), LogLevel.Info); } - /// Formats the given milliseconds value into a string format. Optionally /// allows a threshold to return "-" if the value is less than the threshold. /// The milliseconds to format. Returns "-" if null @@ -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"); } /// Shows detailed help for a specific sub command. - /// The output monitor - /// The sub command - private void OutputHelp(IMonitor monitor, SubCommand subCommand) + /// The output monitor. + /// The subcommand. + 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 "); - sb.AppendLine(" pc detail "); - sb.AppendLine(); - sb.AppendLine("Displays details for a specific collection."); - sb.AppendLine(); - sb.AppendLine("Arguments:"); - sb.AppendLine(" Required. The full or partial name of the collection to display."); - sb.AppendLine(" Optional. The full or partial name of the source."); - sb.AppendLine(" 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 "); + report.AppendLine(" pc detail "); + report.AppendLine(); + report.AppendLine("Displays details for a specific collection."); + report.AppendLine(); + report.AppendLine("Arguments:"); + report.AppendLine(" Required. The full or partial name of the collection to display."); + report.AppendLine(" Optional. The full or partial name of the source."); + report.AppendLine(" 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 "); - sb.AppendLine(); - sb.AppendLine("Displays the performance counter summary."); - sb.AppendLine(); - sb.AppendLine("Arguments:"); - sb.AppendLine(" 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(" Optional. Only shows performance counter collections matching the given name"); - sb.AppendLine(" 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 "); + report.AppendLine(); + report.AppendLine("Displays the performance counter summary."); + report.AppendLine(); + report.AppendLine("Arguments:"); + report.AppendLine(" 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(" Optional. Only shows performance counter collections matching the given name"); + report.AppendLine(" 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 "); - sb.AppendLine("Usage: pc trigger collection "); - sb.AppendLine("Usage: pc trigger collection "); - sb.AppendLine(); - sb.AppendLine("Manages alert triggers."); - sb.AppendLine(); - sb.AppendLine("Arguments:"); - sb.AppendLine(" 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(" 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(" Optional. Specifies the name of a specific source. Must be an exact match."); - sb.AppendLine(); - sb.AppendLine(" 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 "); + report.AppendLine("Usage: pc trigger collection "); + report.AppendLine("Usage: pc trigger collection "); + report.AppendLine(); + report.AppendLine("Manages alert triggers."); + report.AppendLine(); + report.AppendLine("Arguments:"); + report.AppendLine(" 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(" 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(" Optional. Specifies the name of a specific source. Must be an exact match."); + report.AppendLine(); + report.AppendLine(" 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 "); - sb.AppendLine(); - sb.AppendLine("Resets performance counters."); - sb.AppendLine(); - sb.AppendLine("Arguments:"); - sb.AppendLine(" 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(" Required if a 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 "); + report.AppendLine(); + report.AppendLine("Resets performance counters."); + report.AppendLine(); + report.AppendLine("Arguments:"); + report.AppendLine(" 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(" Required if a 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); } /// Get the command description. private static string GetDescription() { - StringBuilder sb = 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 "); - 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 ', for example:"); - sb.AppendLine("pc help summary"); - sb.AppendLine(); - sb.AppendLine("Defaults to summary if no command is given."); - sb.AppendLine(); - - return sb.ToString(); + StringBuilder report = new StringBuilder(); + + 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 "); + 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 ', for example:"); + report.AppendLine("pc help summary"); + report.AppendLine(); + report.AppendLine("Defaults to summary if no command is given."); + report.AppendLine(); + + return report.ToString(); + } + + + /********* + ** Private models + *********/ + /// An alert trigger for a collection. + private class CollectionTrigger + { + /********* + ** Accessors + *********/ + /// The collection name. + public string CollectionName { get; } + + /// The trigger threshold. + public double Threshold { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The collection name. + /// The trigger threshold. + public CollectionTrigger(string collectionName, double threshold) + { + this.CollectionName = collectionName; + this.Threshold = threshold; + } + } + + /// An alert triggered for a source. + private class SourceTrigger : CollectionTrigger + { + /********* + ** Accessors + *********/ + /// The source name. + public string SourceName { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The collection name. + /// The source name. + /// The trigger threshold. + public SourceTrigger(string collectionName, string sourceName, double threshold) + : base(collectionName, threshold) + { + this.SourceName = sourceName; + } } } } diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/TrainerCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/TrainerCommand.cs index 8f0d89ba..2b562a08 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/TrainerCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/TrainerCommand.cs @@ -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 /// The data to display. /// The table header. /// Returns a set of fields for a data value. - /// True to right-align the data, false for left-align. Default false. + /// Whether to right-align the data. protected string GetTableString(IEnumerable data, string[] header, Func 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], ' ')) + )) ); } } -- cgit