From 0dae9f085fb0cc4ef720a44233d37a7651cafb4c Mon Sep 17 00:00:00 2001 From: wiktork Date: Wed, 22 Feb 2023 12:56:17 -0800 Subject: [PATCH] Add substitution tokens for other process info --- .../ProcessInfoImpl.cs | 19 ++++++--- .../ActionDependencyAnalyzerTests.cs | 40 +++++++++++++++++++ .../ActionOptionsDependencyAnalyzer.cs | 6 ++- .../ConfigurationTokenParser.cs | 22 +++++++++- 4 files changed, 78 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/ProcessInfoImpl.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/ProcessInfoImpl.cs index 07f8e1e3418..2197b3428db 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/ProcessInfoImpl.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/ProcessInfoImpl.cs @@ -100,12 +100,22 @@ public static async Task FromEndpointInfoAsync(IEndpointInfo endpo } } + string processName = GetProcessName(commandLine, endpointInfo.OperatingSystem); + + return new ProcessInfoImpl( + endpointInfo, + commandLine, + processName); + } + + internal static string GetProcessName(string commandLine, string operatingSystem) + { string processName = null; if (!string.IsNullOrEmpty(commandLine)) { // Get the process name from the command line bool isWindowsProcess = false; - if (string.IsNullOrEmpty(endpointInfo.OperatingSystem)) + if (string.IsNullOrEmpty(operatingSystem)) { // If operating system is null, the process is likely .NET Core 3.1 (which doesn't have the GetProcessInfo command). // Since the underlying diagnostic communication channel used by the .NET runtime requires that the diagnostic process @@ -116,7 +126,7 @@ public static async Task FromEndpointInfoAsync(IEndpointInfo endpo } else { - isWindowsProcess = ProcessOperatingSystemWindowsValue.Equals(endpointInfo.OperatingSystem, StringComparison.OrdinalIgnoreCase); + isWindowsProcess = ProcessOperatingSystemWindowsValue.Equals(operatingSystem, StringComparison.OrdinalIgnoreCase); } string processPath = CommandLineHelper.ExtractExecutablePath(commandLine, isWindowsProcess); @@ -131,10 +141,7 @@ public static async Task FromEndpointInfoAsync(IEndpointInfo endpo } } - return new ProcessInfoImpl( - endpointInfo, - commandLine, - processName); + return processName; } public IEndpointInfo EndpointInfo { get; } diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/ActionDependencyAnalyzerTests.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/ActionDependencyAnalyzerTests.cs index a251c64f88b..cd130239e80 100644 --- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/ActionDependencyAnalyzerTests.cs +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/ActionDependencyAnalyzerTests.cs @@ -13,6 +13,7 @@ using Microsoft.Extensions.Options; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -115,6 +116,45 @@ await TestHostHelper.CreateCollectionRulesHost(_outputHelper, rootOptions => }); } + [Fact] + public async Task ProcessInfoTest() + { + PassThroughOptions settings = null; + await TestHostHelper.CreateCollectionRulesHost(_outputHelper, rootOptions => + { + CollectionRuleOptions options = rootOptions.CreateCollectionRule(DefaultRuleName) + .AddPassThroughAction("a1", ConfigurationTokenParser.ProcessNameReference, ConfigurationTokenParser.ProcessIdReference, ConfigurationTokenParser.CommandLineReference) + .SetStartupTrigger(); + + settings = (PassThroughOptions)options.Actions.Last().Settings; + }, host => + { + using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TimeoutMs); + + CollectionRuleOptions ruleOptions = host.Services.GetRequiredService>().Get(DefaultRuleName); + ILogger logger = host.Services.GetRequiredService>(); + ISystemClock clock = host.Services.GetRequiredService(); + + const string processName = "actionProcess"; + const int processId = 123; + string commandLine = FormattableString.Invariant($"{processName} arg1"); + + Guid instanceId = Guid.NewGuid(); + CollectionRuleContext context = new(DefaultRuleName, ruleOptions, new TestEndpointInfo(instanceId, processId: processId, commandLine: commandLine), logger, clock); + + ActionOptionsDependencyAnalyzer analyzer = ActionOptionsDependencyAnalyzer.Create(context); + PassThroughOptions newSettings = (PassThroughOptions)analyzer.SubstituteOptionValues(new Dictionary(), 1, settings); + + Assert.Equal(processName, newSettings.Input1); + Assert.Equal(processId.ToString(CultureInfo.InvariantCulture), newSettings.Input2); + Assert.Equal(commandLine, newSettings.Input3); + + }, serviceCollection => + { + serviceCollection.RegisterCollectionRuleAction(nameof(PassThroughAction)); + }); + } + [Fact] public async Task InvalidTokenReferenceTest() { diff --git a/src/Tools/dotnet-monitor/CollectionRules/ActionOptionsDependencyAnalyzer.cs b/src/Tools/dotnet-monitor/CollectionRules/ActionOptionsDependencyAnalyzer.cs index 26e81886e14..854802eaa73 100644 --- a/src/Tools/dotnet-monitor/CollectionRules/ActionOptionsDependencyAnalyzer.cs +++ b/src/Tools/dotnet-monitor/CollectionRules/ActionOptionsDependencyAnalyzer.cs @@ -151,11 +151,15 @@ public object SubstituteOptionValues(IDictionary EnvironmentBlock { get; set; } = new Dictionary(); } @@ -31,7 +36,14 @@ internal sealed class ConfigurationTokenParser private const string ProcessInfoReference = "Process"; private const string RuntimeId = "RuntimeId"; - public static readonly string RuntimeIdReference = FormattableString.Invariant($"{SubstitutionPrefix}{ProcessInfoReference}{Separator}{RuntimeId}{SubstitutionSuffix}"); + private const string ProcessId = "ProcessId"; + private const string ProcessName = "Name"; + private const string CommandLine = "CommandLine"; + + public static readonly string RuntimeIdReference = CreateTokenReference(ProcessInfoReference, RuntimeId); + public static readonly string ProcessIdReference = CreateTokenReference(ProcessInfoReference, ProcessId); + public static readonly string ProcessNameReference = CreateTokenReference(ProcessInfoReference, ProcessName); + public static readonly string CommandLineReference = CreateTokenReference(ProcessInfoReference, CommandLine); public ConfigurationTokenParser(ILogger logger) { @@ -51,6 +63,9 @@ public object SubstituteOptionValues(object originalSettings, TokenContext conte } string replacement = originalPropertyValue.Replace(RuntimeIdReference, context.RuntimeId.ToString("D"), StringComparison.Ordinal); + replacement = replacement.Replace(ProcessIdReference, context.ProcessId.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal); + replacement = replacement.Replace(ProcessNameReference, context.ProcessName, StringComparison.Ordinal); + replacement = replacement.Replace(CommandLineReference, context.CommandLine, StringComparison.Ordinal); if (!ReferenceEquals(replacement, originalPropertyValue)) { @@ -95,5 +110,8 @@ public static IEnumerable GetPropertiesFromSettings(object setting .GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.PropertyType == typeof(string) && (predicate?.Invoke(p) ?? true)) ?? Enumerable.Empty(); + + private static string CreateTokenReference(string category, string token) => + FormattableString.Invariant($"{SubstitutionPrefix}{category}{Separator}{token}{SubstitutionSuffix}"); } }