From 62f66e0c05d91438b67c5bd5fa679314793fcf54 Mon Sep 17 00:00:00 2001 From: lateralusX Date: Tue, 18 Jul 2023 17:15:16 +0200 Subject: [PATCH 1/5] Add convert (nettrace->gcdump) command to dotnet-gcdump. --- .../CommandLine/ConvertCommandHandler.cs | 92 +++++++++++++++++++ .../EventPipeDotNetHeapDumper.cs | 83 +++++++++++++++++ src/Tools/dotnet-gcdump/Program.cs | 1 + 3 files changed, 176 insertions(+) create mode 100644 src/Tools/dotnet-gcdump/CommandLine/ConvertCommandHandler.cs diff --git a/src/Tools/dotnet-gcdump/CommandLine/ConvertCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/ConvertCommandHandler.cs new file mode 100644 index 0000000000..60de9b0e9e --- /dev/null +++ b/src/Tools/dotnet-gcdump/CommandLine/ConvertCommandHandler.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.CommandLine; +using System.IO; +using Graphs; +using Microsoft.Tools.Common; + +namespace Microsoft.Diagnostics.Tools.GCDump +{ + internal static class ConvertCommandHandler + { + public static int ConvertFile(FileInfo input, string output, bool verbose) + { + if (!input.Exists) + { + Console.Error.WriteLine($"File '{input.FullName}' does not exist."); + return -1; + } + + output = string.IsNullOrEmpty(output) + ? Path.ChangeExtension(input.FullName, "gcdump") + : output; + + FileInfo outputFileInfo = new(output); + + if (outputFileInfo.Exists) + { + outputFileInfo.Delete(); + } + + if (string.IsNullOrEmpty(outputFileInfo.Extension) || outputFileInfo.Extension != ".gcdump") + { + outputFileInfo = new FileInfo(outputFileInfo.FullName + ".gcdump"); + } + + Console.Out.WriteLine($"Writing gcdump to '{outputFileInfo.FullName}'..."); + + DotNetHeapInfo heapInfo = new(); + TextWriter log = verbose ? Console.Out : TextWriter.Null; + + MemoryGraph memoryGraph = new(50_000); + + if (!EventPipeDotNetHeapDumper.DumpFromEventPipeFile(input.FullName, memoryGraph, log, heapInfo)) + { + return -1; + } + + memoryGraph.AllowReading(); + GCHeapDump.WriteMemoryGraph(memoryGraph, outputFileInfo.FullName, "dotnet-gcdump"); + + return 0; + } + + public static System.CommandLine.Command ConvertCommand() => + new( + name: "convert", + description: "Converts nettrace file into .gcdump file handled by analysis tools. Can only convert from the nettrace format.") + { + // Handler + System.CommandLine.Invocation.CommandHandler.Create(ConvertFile), + // Arguments and Options + InputPathArgument(), + OutputPathOption(), + VerboseOption() + }; + + private static Argument InputPathArgument() => + new Argument("input") + { + Description = "Input trace file to be converted.", + Arity = new ArgumentArity(0, 1) + }.ExistingOnly(); + + private static Option OutputPathOption() => + new( + aliases: new[] { "-o", "--output" }, + description: $@"The path where converted gcdump should be written. Defaults to '.gcdump'") + { + Argument = new Argument(name: "output", getDefaultValue: () => string.Empty) + }; + + private static Option VerboseOption() => + new( + aliases: new[] { "-v", "--verbose" }, + description: "Output the log while converting the gcdump.") + { + Argument = new Argument(name: "verbose", getDefaultValue: () => false) + }; + } +} diff --git a/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs b/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs index fb97bc3555..fb624bcf5b 100644 --- a/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs +++ b/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs @@ -20,6 +20,89 @@ public static class EventPipeDotNetHeapDumper internal static volatile bool eventPipeDataPresent; internal static volatile bool dumpComplete; + /// + /// Given a nettrace file from a EventPipe session with the appropriate provider and keywords turned on, + /// generate a GCHeapDump using the resulting events. + /// + /// + /// + /// + /// + /// + public static bool DumpFromEventPipeFile(string path, MemoryGraph memoryGraph, TextWriter log, DotNetHeapInfo dotNetInfo = null) + { + DateTime start = DateTime.Now; + Func getElapsed = () => DateTime.Now - start; + + DotNetHeapDumpGraphReader dumper = new(log) + { + DotNetHeapInfo = dotNetInfo + }; + + try + { + TimeSpan lastEventPipeUpdate = getElapsed(); + + int gcNum = -1; + + EventPipeEventSource source = new(path); + + source.Clr.GCStart += delegate (GCStartTraceData data) + { + eventPipeDataPresent = true; + + if (gcNum < 0 && data.Depth == 2 && data.Type != GCType.BackgroundGC) + { + gcNum = data.Count; + log.WriteLine("{0,5:n1}s: .NET Dump Started...", getElapsed().TotalSeconds); + } + }; + + source.Clr.GCStop += delegate (GCEndTraceData data) + { + if (data.Count == gcNum) + { + log.WriteLine("{0,5:n1}s: .NET GC Complete.", getElapsed().TotalSeconds); + dumpComplete = true; + } + }; + + source.Clr.GCBulkNode += delegate (GCBulkNodeTraceData data) + { + eventPipeDataPresent = true; + + if ((getElapsed() - lastEventPipeUpdate).TotalMilliseconds > 500) + { + log.WriteLine("{0,5:n1}s: Making GC Heap Progress...", getElapsed().TotalSeconds); + } + + lastEventPipeUpdate = getElapsed(); + }; + + if (memoryGraph != null) + { + dumper.SetupCallbacks(memoryGraph, source); + } + + log.WriteLine("{0,5:n1}s: Starting to process events", getElapsed().TotalSeconds); + source.Process(); + log.WriteLine("{0,5:n1}s: Finished processing events", getElapsed().TotalSeconds); + + if (eventPipeDataPresent) + { + dumper.ConvertHeapDataToGraph(); + } + } + catch (Exception e) + { + log.WriteLine($"{getElapsed().TotalSeconds,5:n1}s: [Error] Exception processing events: {e}"); + } + + log.WriteLine("[{0,5:n1}s: Done Dumping .NET heap success={1}]", getElapsed().TotalSeconds, dumpComplete); + + return dumpComplete; + } + /// /// Given a factory for creating an EventPipe session with the appropriate provider and keywords turned on, /// generate a GCHeapDump using the resulting events. The correct keywords and provider name diff --git a/src/Tools/dotnet-gcdump/Program.cs b/src/Tools/dotnet-gcdump/Program.cs index 8830c743d5..5de61d81ad 100644 --- a/src/Tools/dotnet-gcdump/Program.cs +++ b/src/Tools/dotnet-gcdump/Program.cs @@ -16,6 +16,7 @@ public static Task Main(string[] args) .AddCommand(CollectCommandHandler.CollectCommand()) .AddCommand(ProcessStatusCommandHandler.ProcessStatusCommand("Lists the dotnet processes that gcdumps can be collected from.")) .AddCommand(ReportCommandHandler.ReportCommand()) + .AddCommand(ConvertCommandHandler.ConvertCommand()) .UseDefaults() .Build(); From daea0f9a833af27351b41d9a741e21173f61d0c4 Mon Sep 17 00:00:00 2001 From: lateralusX Date: Tue, 18 Jul 2023 18:30:52 +0200 Subject: [PATCH 2/5] Add diagnostic-port connect mode to dotnet-gcdump. --- ...icrosoft.Diagnostics.NETCore.Client.csproj | 3 +- .../CommandLine/CollectCommandHandler.cs | 46 +++++++++++++++---- .../CommandLine/ReportCommandHandler.cs | 2 +- .../EventPipeDotNetHeapDumper.cs | 36 ++++++++++++--- 4 files changed, 71 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj b/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj index 094b309355..c4fe96c3e8 100644 --- a/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj +++ b/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj @@ -1,4 +1,4 @@ - + Library netstandard2.0;net6.0 @@ -31,6 +31,7 @@ + diff --git a/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs index ee8723815e..dccff06d7d 100644 --- a/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs +++ b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Graphs; +using Microsoft.Diagnostics.NETCore.Client; using Microsoft.Internal.Common.Utils; using Microsoft.Tools.Common; @@ -15,7 +16,7 @@ namespace Microsoft.Diagnostics.Tools.GCDump { internal static class CollectCommandHandler { - private delegate Task CollectDelegate(CancellationToken ct, IConsole console, int processId, string output, int timeout, bool verbose, string name); + private delegate Task CollectDelegate(CancellationToken ct, IConsole console, int processId, string output, int timeout, bool verbose, string name, string diagnosticPort); /// /// Collects a gcdump from a currently running process. @@ -25,7 +26,7 @@ internal static class CollectCommandHandler /// The process to collect the gcdump from. /// The output path for the collected gcdump. /// - private static async Task Collect(CancellationToken ct, IConsole console, int processId, string output, int timeout, bool verbose, string name) + private static async Task Collect(CancellationToken ct, IConsole console, int processId, string output, int timeout, bool verbose, string name, string diagnosticPort) { if (name != null) { @@ -41,6 +42,12 @@ private static async Task Collect(CancellationToken ct, IConsole console, i } } + if (processId != 0 && !string.IsNullOrEmpty(diagnosticPort)) + { + Console.WriteLine("Can only specify either --name, --process-id or --diagnostic-port option."); + return -1; + } + try { if (processId < 0) @@ -49,12 +56,22 @@ private static async Task Collect(CancellationToken ct, IConsole console, i return -1; } - if (processId == 0) + if (processId == 0 && string.IsNullOrEmpty(diagnosticPort)) { - Console.Out.WriteLine("-p|--process-id is required"); + Console.Out.WriteLine("-p|--process-id or --diagnostic-port is required"); return -1; } + if (!string.IsNullOrEmpty(diagnosticPort)) + { + IpcEndpointConfig config = IpcEndpointConfig.Parse(diagnosticPort); + if (!config.IsConnectConfig) + { + Console.WriteLine("--diagnostic-port is only supporting connect mode."); + return -1; + } + } + output = string.IsNullOrEmpty(output) ? $"{DateTime.Now:yyyyMMdd\\_HHmmss}_{processId}.gcdump" : output; @@ -74,7 +91,7 @@ private static async Task Collect(CancellationToken ct, IConsole console, i Console.Out.WriteLine($"Writing gcdump to '{outputFileInfo.FullName}'..."); Task dumpTask = Task.Run(() => { - if (TryCollectMemoryGraph(ct, processId, timeout, verbose, out MemoryGraph memoryGraph)) + if (TryCollectMemoryGraph(ct, processId, diagnosticPort, timeout, verbose, out MemoryGraph memoryGraph)) { GCHeapDump.WriteMemoryGraph(memoryGraph, outputFileInfo.FullName, "dotnet-gcdump"); return true; @@ -109,7 +126,7 @@ private static async Task Collect(CancellationToken ct, IConsole console, i } } - internal static bool TryCollectMemoryGraph(CancellationToken ct, int processId, int timeout, bool verbose, + internal static bool TryCollectMemoryGraph(CancellationToken ct, int processId, string diagnosticPort, int timeout, bool verbose, out MemoryGraph memoryGraph) { DotNetHeapInfo heapInfo = new(); @@ -117,7 +134,7 @@ internal static bool TryCollectMemoryGraph(CancellationToken ct, int processId, memoryGraph = new MemoryGraph(50_000); - if (!EventPipeDotNetHeapDumper.DumpFromEventPipe(ct, processId, memoryGraph, log, timeout, heapInfo)) + if (!EventPipeDotNetHeapDumper.DumpFromEventPipe(ct, processId, diagnosticPort, memoryGraph, log, timeout, heapInfo)) { return false; } @@ -134,7 +151,12 @@ public static Command CollectCommand() => // Handler HandlerDescriptor.FromDelegate((CollectDelegate) Collect).GetCommandHandler(), // Options - ProcessIdOption(), OutputPathOption(), VerboseOption(), TimeoutOption(), NameOption() + ProcessIdOption(), + OutputPathOption(), + VerboseOption(), + TimeoutOption(), + NameOption(), + DiagnosticPortOption() }; private static Option ProcessIdOption() => @@ -177,5 +199,13 @@ private static Option TimeoutOption() => { Argument = new Argument(name: "timeout", getDefaultValue: () => DefaultTimeout) }; + + private static Option DiagnosticPortOption() => + new( + alias: "--diagnostic-port", + description: @"The path to a diagnostic port to be used.") + { + Argument = new Argument(name: "diagnosticPort", getDefaultValue: () => string.Empty) + }; } } diff --git a/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs index 2f754f7406..cf155ad9a5 100644 --- a/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs +++ b/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs @@ -75,7 +75,7 @@ private static Task HandleUnknownParam() private static Task ReportFromProcess(int processId, CancellationToken ct) { if (!CollectCommandHandler - .TryCollectMemoryGraph(ct, processId, CollectCommandHandler.DefaultTimeout, false, out Graphs.MemoryGraph mg)) + .TryCollectMemoryGraph(ct, processId, string.Empty, CollectCommandHandler.DefaultTimeout, false, out Graphs.MemoryGraph mg)) { Console.Error.WriteLine("An error occured while collecting gcdump."); return Task.FromResult(-1); diff --git a/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs b/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs index fb624bcf5b..57084c1930 100644 --- a/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs +++ b/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs @@ -109,12 +109,12 @@ public static bool DumpFromEventPipeFile(string path, MemoryGraph memoryGraph, T /// are given as input to the Func eventPipeEventSourceFactory. /// /// - /// A delegate for creating and stopping EventPipe sessions + /// /// /// /// /// - public static bool DumpFromEventPipe(CancellationToken ct, int processID, MemoryGraph memoryGraph, TextWriter log, int timeout, DotNetHeapInfo dotNetInfo = null) + public static bool DumpFromEventPipe(CancellationToken ct, int processID, string diagnosticPort, MemoryGraph memoryGraph, TextWriter log, int timeout, DotNetHeapInfo dotNetInfo = null) { DateTime start = DateTime.Now; Func getElapsed = () => DateTime.Now - start; @@ -130,7 +130,7 @@ public static bool DumpFromEventPipe(CancellationToken ct, int processID, Memory bool fDone = false; log.WriteLine("{0,5:n1}s: Creating type table flushing task", getElapsed().TotalSeconds); - using (EventPipeSessionController typeFlushSession = new(processID, new List { + using (EventPipeSessionController typeFlushSession = new(processID, diagnosticPort, new List { new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational) }, false)) { @@ -155,7 +155,7 @@ public static bool DumpFromEventPipe(CancellationToken ct, int processID, Memory // Start the providers and trigger the GCs. log.WriteLine("{0,5:n1}s: Requesting a .NET Heap Dump", getElapsed().TotalSeconds); - using EventPipeSessionController gcDumpSession = new(processID, new List { + using EventPipeSessionController gcDumpSession = new(processID, diagnosticPort, new List { new EventPipeProvider("Microsoft-Windows-DotNETRuntime", EventLevel.Verbose, (long)(ClrTraceEventParser.Keywords.GCHeapSnapshot)) }); log.WriteLine("{0,5:n1}s: gcdump EventPipe Session started", getElapsed().TotalSeconds); @@ -164,6 +164,11 @@ public static bool DumpFromEventPipe(CancellationToken ct, int processID, Memory gcDumpSession.Source.Clr.GCStart += delegate (GCStartTraceData data) { + if (processID == 0) + { + processID = data.ProcessID; + log.WriteLine("Process wildcard selects process id {0}", processID); + } if (data.ProcessID != processID) { return; @@ -312,15 +317,34 @@ internal sealed class EventPipeSessionController : IDisposable private EventPipeSession _session; private EventPipeEventSource _source; private int _pid; + private IpcEndpointConfig _diagnosticPort; public IReadOnlyList Providers => _providers.AsReadOnly(); public EventPipeEventSource Source => _source; - public EventPipeSessionController(int pid, List providers, bool requestRundown = true) + public EventPipeSessionController(int pid, string diagnosticPort, List providers, bool requestRundown = true) { + if (!string.IsNullOrEmpty(diagnosticPort)) + { + _diagnosticPort = IpcEndpointConfig.Parse(diagnosticPort); + if (!_diagnosticPort.IsConnectConfig) + { + throw new ArgumentException("DiagnosticPort is only supporting connect mode."); + } + } + _pid = pid; _providers = providers; - _client = new DiagnosticsClient(pid); + + if (_diagnosticPort != null) + { + _client = new DiagnosticsClient(_diagnosticPort); + } + else + { + _client = new DiagnosticsClient(pid); + } + _session = _client.StartEventPipeSession(providers, requestRundown, 1024); _source = new EventPipeEventSource(_session.EventStream); } From f4a552826e4c32e00b2bafbd265484fb79d1a742 Mon Sep 17 00:00:00 2001 From: lateralusX Date: Wed, 19 Jul 2023 12:07:54 +0200 Subject: [PATCH 3/5] Add ability to infrom diagnostic tools that -p is a dsrouter process. --- .../DiagnosticsIpc/IpcTransport.cs | 15 ++- .../DiagnosticsServerRouterRunner.cs | 2 + src/Tools/Common/Commands/Utils.cs | 19 ++-- src/Tools/dotnet-counters/CounterMonitor.cs | 19 +++- src/Tools/dotnet-counters/Program.cs | 22 ++++- .../DiagnosticsServerRouterCommands.cs | 45 ++++----- .../USBMuxTcpClientRouterFactory.cs | 2 + .../CommandLine/CollectCommandHandler.cs | 89 ++++++++--------- .../CommandLine/ReportCommandHandler.cs | 95 ++++++++++++++++--- .../EventPipeDotNetHeapDumper.cs | 23 ++--- .../CommandLine/Commands/CollectCommand.cs | 24 ++++- 11 files changed, 239 insertions(+), 116 deletions(-) diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcTransport.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcTransport.cs index d49b24d007..7df8f333fc 100644 --- a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcTransport.cs +++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcTransport.cs @@ -275,7 +275,7 @@ private string GetDefaultAddress() throw new ServerNotAvailableException($"Process {_pid} seems to be elevated."); } - if (!TryGetDefaultAddress(_pid, out string transportName)) + if (!TryGetDefaultAddress(_pid, false, out string transportName)) { throw new ServerNotAvailableException($"Process {_pid} not running compatible .NET runtime."); } @@ -283,19 +283,21 @@ private string GetDefaultAddress() return transportName; } - private static bool TryGetDefaultAddress(int pid, out string defaultAddress) + private static bool TryGetDefaultAddress(int pid, bool dsRouter, out string defaultAddress) { defaultAddress = null; + string addressPrefix = !dsRouter ? "dotnet-diagnostic" : "dotnet-dsrouter"; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - defaultAddress = $"dotnet-diagnostic-{pid}"; + defaultAddress = $"{addressPrefix}-{pid}"; } else { try { - defaultAddress = Directory.GetFiles(IpcRootPath, $"dotnet-diagnostic-{pid}-*-socket") // Try best match. + defaultAddress = Directory.GetFiles(IpcRootPath, $"{addressPrefix}-{pid}-*-socket") // Try best match. .OrderByDescending(f => new FileInfo(f).LastWriteTime) .FirstOrDefault(); } @@ -307,6 +309,11 @@ private static bool TryGetDefaultAddress(int pid, out string defaultAddress) return !string.IsNullOrEmpty(defaultAddress); } + public static string GetDefaultAddressForProcessId(int pid, bool dsRouter) + { + return TryGetDefaultAddress(pid, dsRouter, out string defaultAddress) ? defaultAddress : string.Empty; + } + public override bool Equals(object obj) { return Equals(obj as PidIpcEndpoint); diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsServerRouter/DiagnosticsServerRouterRunner.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsServerRouter/DiagnosticsServerRouterRunner.cs index 19cd302023..80abfd44c3 100644 --- a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsServerRouter/DiagnosticsServerRouterRunner.cs +++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsServerRouter/DiagnosticsServerRouterRunner.cs @@ -137,6 +137,8 @@ private static async Task runRouter(CancellationToken token, DiagnosticsSer routerFactory.Logger?.LogInformation("Starting automatic shutdown."); throw; } + + routerFactory.Logger?.LogTrace($"runRouter continues after exception: {ex.Message}"); } } } diff --git a/src/Tools/Common/Commands/Utils.cs b/src/Tools/Common/Commands/Utils.cs index 2d4951e9b5..823bedaf24 100644 --- a/src/Tools/Common/Commands/Utils.cs +++ b/src/Tools/Common/Commands/Utils.cs @@ -2,9 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.Diagnostics; - +using System.Collections.Generic; using Microsoft.Diagnostics.NETCore.Client; namespace Microsoft.Internal.Common.Utils @@ -72,7 +71,7 @@ public static bool ValidateArgumentsForChildProcess(int processId, string name, public static bool ValidateArgumentsForAttach(int processId, string name, string port, out int resolvedProcessId) { resolvedProcessId = -1; - if (processId == 0 && name == null && string.IsNullOrEmpty(port)) + if (processId == 0 && string.IsNullOrEmpty(name) && string.IsNullOrEmpty(port)) { Console.WriteLine("Must specify either --process-id, --name, or --diagnostic-port."); return false; @@ -82,24 +81,24 @@ public static bool ValidateArgumentsForAttach(int processId, string name, string Console.WriteLine($"{processId} is not a valid process ID"); return false; } - else if (processId != 0 && name != null && !string.IsNullOrEmpty(port)) + else if (processId != 0 && !string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(port)) { Console.WriteLine("Only one of the --name, --process-id, or --diagnostic-port options may be specified."); return false; } - else if (processId != 0 && name != null) + else if (processId != 0 && !string.IsNullOrEmpty(name)) { - Console.WriteLine("Can only one of specify --name or --process-id."); + Console.WriteLine("Only one of the --name or --process-id options may be specified."); return false; } else if (processId != 0 && !string.IsNullOrEmpty(port)) { - Console.WriteLine("Can only one of specify --process-id or --diagnostic-port."); + Console.WriteLine("Only one of the --process-id or --diagnostic-port options may be specified."); return false; } - else if (name != null && !string.IsNullOrEmpty(port)) + else if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(port)) { - Console.WriteLine("Can only one of specify --name or --diagnostic-port."); + Console.WriteLine("Only one of the --name or --diagnostic-port options may be specified."); return false; } // If we got this far it means only one of --name/--diagnostic-port/--process-id was specified @@ -108,7 +107,7 @@ public static bool ValidateArgumentsForAttach(int processId, string name, string return true; } // Resolve name option - else if (name != null) + else if (!string.IsNullOrEmpty(name)) { processId = CommandUtils.FindProcessIdWithName(name); if (processId < 0) diff --git a/src/Tools/dotnet-counters/CounterMonitor.cs b/src/Tools/dotnet-counters/CounterMonitor.cs index a017074386..afae9fc40d 100644 --- a/src/Tools/dotnet-counters/CounterMonitor.cs +++ b/src/Tools/dotnet-counters/CounterMonitor.cs @@ -503,7 +503,8 @@ public async Task Monitor( bool resumeRuntime, int maxHistograms, int maxTimeSeries, - TimeSpan duration) + TimeSpan duration, + bool dsRouter) { try { @@ -519,6 +520,12 @@ public async Task Monitor( } ct.Register(() => _shouldExit.TrySetResult((int)ReturnCode.Ok)); + if (_processId > 0 && dsRouter) + { + diagnosticPort = PidIpcEndpoint.GetDefaultAddressForProcessId(_processId, dsRouter) + ",connect"; + _processId = -1; + } + DiagnosticsClientBuilder builder = new("dotnet-counters", 10); using (DiagnosticsClientHolder holder = await builder.Build(ct, _processId, diagnosticPort, showChildIO: false, printLaunchCommand: false).ConfigureAwait(false)) using (VirtualTerminalMode vTerm = VirtualTerminalMode.TryEnable()) @@ -579,7 +586,8 @@ public async Task Collect( bool resumeRuntime, int maxHistograms, int maxTimeSeries, - TimeSpan duration) + TimeSpan duration, + bool dsRouter) { try { @@ -593,9 +601,14 @@ public async Task Collect( { return (int)ReturnCode.ArgumentError; } - ct.Register(() => _shouldExit.TrySetResult((int)ReturnCode.Ok)); + if (_processId > 0 && dsRouter) + { + diagnosticPort = PidIpcEndpoint.GetDefaultAddressForProcessId(_processId, dsRouter) + ",connect"; + _processId = -1; + } + DiagnosticsClientBuilder builder = new("dotnet-counters", 10); using (DiagnosticsClientHolder holder = await builder.Build(ct, _processId, diagnosticPort, showChildIO: false, printLaunchCommand: false).ConfigureAwait(false)) { diff --git a/src/Tools/dotnet-counters/Program.cs b/src/Tools/dotnet-counters/Program.cs index 826b8373b6..790157c36d 100644 --- a/src/Tools/dotnet-counters/Program.cs +++ b/src/Tools/dotnet-counters/Program.cs @@ -35,7 +35,8 @@ private delegate Task CollectDelegate( bool resumeRuntime, int maxHistograms, int maxTimeSeries, - TimeSpan duration); + TimeSpan duration, + bool dsRouter); private delegate Task MonitorDelegate( CancellationToken ct, @@ -49,7 +50,8 @@ private delegate Task MonitorDelegate( bool resumeRuntime, int maxHistograms, int maxTimeSeries, - TimeSpan duration); + TimeSpan duration, + bool dsRouter); private static Command MonitorCommand() => new( @@ -68,7 +70,8 @@ private static Command MonitorCommand() => ResumeRuntimeOption(), MaxHistogramOption(), MaxTimeSeriesOption(), - DurationOption() + DurationOption(), + DSRouterOption() }; private static Command CollectCommand() => @@ -90,7 +93,8 @@ private static Command CollectCommand() => ResumeRuntimeOption(), MaxHistogramOption(), MaxTimeSeriesOption(), - DurationOption() + DurationOption(), + DSRouterOption() }; private static Option NameOption() => @@ -167,7 +171,7 @@ private static Option RuntimeVersionOption() => private static Option DiagnosticPortOption() => new( - alias: "--diagnostic-port", + aliases: new[] { "--dport", "--diagnostic-port" }, description: "The path to diagnostic port to be used.") { Argument = new Argument(name: "diagnosticPort", getDefaultValue: () => "") @@ -207,6 +211,14 @@ private static Option DurationOption() => Argument = new Argument(name: "duration-timespan", getDefaultValue: () => default) }; + private static Option DSRouterOption() => + new( + aliases: new[] { "--dsrouter" }, + description: "Process identified by -p|-n|--process-id|--name is a dotnet-dsrouter process.") + { + Argument = new Argument(name: "dsrouter", getDefaultValue: () => false) + }; + private static readonly string[] s_SupportedRuntimeVersions = KnownData.s_AllVersions; public static int List(IConsole console, string runtimeVersion) diff --git a/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs b/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs index f7c37f1177..1896f0f12d 100644 --- a/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs +++ b/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs @@ -105,6 +105,8 @@ public async Task CommonRunLoop(Func routerTask = createRouterTask(logger, Launcher, linkedCancelToken); while (!linkedCancelToken.IsCancellationRequested) @@ -127,7 +129,19 @@ await Task.WhenAny(routerTask, Task.Delay( } } } - return routerTask.Result; + + if (!routerTask.IsCompleted) + { + cancelRouterTask.Cancel(); + } + + await Task.WhenAny(routerTask, Task.Delay(1000, CancellationToken.None)).ConfigureAwait(false); + if (routerTask.IsCompleted) + { + return routerTask.Result; + } + + return 0; } } @@ -335,19 +349,12 @@ public async Task RunIpcClientWebSocketServerRouter(CancellationToken token private static string GetDefaultIpcServerPath(ILogger logger) { + string path = string.Empty; int processId = Process.GetCurrentProcess().Id; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - string path = Path.Combine(PidIpcEndpoint.IpcRootPath, $"dotnet-diagnostic-{processId}"); - if (File.Exists(path)) - { - logger?.LogWarning($"Default IPC server path, {path}, already in use. To disable default diagnostics for dotnet-dsrouter, set DOTNET_EnableDiagnostics=0 and re-run."); - - path = Path.Combine(PidIpcEndpoint.IpcRootPath, $"dotnet-dsrouter-{processId}"); - logger?.LogWarning($"Fallback using none default IPC server path, {path}."); - } - - return path.Substring(PidIpcEndpoint.IpcRootPath.Length); + path = $"dotnet-dsrouter-{processId}"; } else { @@ -358,19 +365,13 @@ private static string GetDefaultIpcServerPath(ILogger logger) unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); #endif TimeSpan diff = Process.GetCurrentProcess().StartTime.ToUniversalTime() - unixEpoch; - - string path = Path.Combine(PidIpcEndpoint.IpcRootPath, $"dotnet-diagnostic-{processId}-{(long)diff.TotalSeconds}-socket"); - if (Directory.GetFiles(PidIpcEndpoint.IpcRootPath, $"dotnet-diagnostic-{processId}-*-socket").Length != 0) - { - logger?.LogWarning($"Default IPC server path, {Path.Combine(PidIpcEndpoint.IpcRootPath, $"dotnet-diagnostic-{processId}-*-socket")}, already in use. To disable default diagnostics for dotnet-dsrouter, set DOTNET_EnableDiagnostics=0 and re-run."); - - path = Path.Combine(PidIpcEndpoint.IpcRootPath, $"dotnet-dsrouter-{processId}-{(long)diff.TotalSeconds}-socket"); - logger?.LogWarning($"Fallback using none default IPC server path, {path}."); - } - - return path; + path = Path.Combine(PidIpcEndpoint.IpcRootPath, $"dotnet-dsrouter-{processId}-{(long)diff.TotalSeconds}-socket"); } + logger?.LogDebug($"Using default IPC server path, {path}."); + logger?.LogDebug($"Attach to default IPC server using -p {processId} and --dsrouter diagnostic tooling arguments."); + + return path; } private static TcpClientRouterFactory.CreateInstanceDelegate ChooseTcpClientRouterFactory(string forwardPort, ILogger logger) diff --git a/src/Tools/dotnet-dsrouter/USBMuxTcpClientRouterFactory.cs b/src/Tools/dotnet-dsrouter/USBMuxTcpClientRouterFactory.cs index 95cce47a61..1bea8efc78 100644 --- a/src/Tools/dotnet-dsrouter/USBMuxTcpClientRouterFactory.cs +++ b/src/Tools/dotnet-dsrouter/USBMuxTcpClientRouterFactory.cs @@ -362,6 +362,7 @@ private int ConnectTcpClientOverUSBMux() { if (_deviceConnectionID == 0) { + _logger.LogError($"Failed to connect device over USB, no device currently connected."); throw new Exception($"Failed to connect device over USB, no device currently connected."); } @@ -370,6 +371,7 @@ private int ConnectTcpClientOverUSBMux() if (result != 0) { + _logger?.LogError($"Failed USBMuxConnectByPort: device = {_deviceConnectionID}, port = {_port}, result = {result}."); throw new Exception($"Failed to connect device over USB using connection {_deviceConnectionID} and port {_port}."); } diff --git a/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs index dccff06d7d..bb7cd6f212 100644 --- a/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs +++ b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs @@ -16,7 +16,7 @@ namespace Microsoft.Diagnostics.Tools.GCDump { internal static class CollectCommandHandler { - private delegate Task CollectDelegate(CancellationToken ct, IConsole console, int processId, string output, int timeout, bool verbose, string name, string diagnosticPort); + private delegate Task CollectDelegate(CancellationToken ct, IConsole console, int processId, string output, int timeout, bool verbose, string name, string diagnosticPort, bool dsRouter); /// /// Collects a gcdump from a currently running process. @@ -25,53 +25,48 @@ internal static class CollectCommandHandler /// /// The process to collect the gcdump from. /// The output path for the collected gcdump. + /// The timeout for the collected gcdump. + /// Enable verbose logging. + /// The process name to collect the gcdump from. + /// The diagnostic IPC channel to collect the gcdump from. + /// Process identified by processId is a dotnet-dsrouter process. /// - private static async Task Collect(CancellationToken ct, IConsole console, int processId, string output, int timeout, bool verbose, string name, string diagnosticPort) + private static async Task Collect(CancellationToken ct, IConsole console, int processId, string output, int timeout, bool verbose, string name, string diagnosticPort, bool dsRouter) { - if (name != null) + if (!CommandUtils.ValidateArgumentsForAttach (processId, name, diagnosticPort, out int resolvedProcessId)) { - if (processId != 0) - { - Console.WriteLine("Can only specify either --name or --process-id option."); - return -1; - } - processId = CommandUtils.FindProcessIdWithName(name); - if (processId < 0) - { - return -1; - } + return -1; } - if (processId != 0 && !string.IsNullOrEmpty(diagnosticPort)) + processId = resolvedProcessId; + + if (processId > 0 && dsRouter) { - Console.WriteLine("Can only specify either --name, --process-id or --diagnostic-port option."); - return -1; + diagnosticPort = PidIpcEndpoint.GetDefaultAddressForProcessId(processId, dsRouter) + ",connect"; } - try + if (!string.IsNullOrEmpty(diagnosticPort)) { - if (processId < 0) - { - Console.Out.WriteLine($"The PID cannot be negative: {processId}"); - return -1; - } - - if (processId == 0 && string.IsNullOrEmpty(diagnosticPort)) - { - Console.Out.WriteLine("-p|--process-id or --diagnostic-port is required"); - return -1; - } - - if (!string.IsNullOrEmpty(diagnosticPort)) + try { IpcEndpointConfig config = IpcEndpointConfig.Parse(diagnosticPort); if (!config.IsConnectConfig) { - Console.WriteLine("--diagnostic-port is only supporting connect mode."); + Console.Error.WriteLine("--diagnostic-port is only supporting connect mode."); return -1; } } + catch (Exception ex) + { + Console.Error.WriteLine($"--diagnostic-port argument error: {ex.Message}"); + return -1; + } + + processId = 0; + } + try + { output = string.IsNullOrEmpty(output) ? $"{DateTime.Now:yyyyMMdd\\_HHmmss}_{processId}.gcdump" : output; @@ -126,8 +121,7 @@ private static async Task Collect(CancellationToken ct, IConsole console, i } } - internal static bool TryCollectMemoryGraph(CancellationToken ct, int processId, string diagnosticPort, int timeout, bool verbose, - out MemoryGraph memoryGraph) + internal static bool TryCollectMemoryGraph(CancellationToken ct, int processId, string diagnosticPort, int timeout, bool verbose, out MemoryGraph memoryGraph) { DotNetHeapInfo heapInfo = new(); TextWriter log = verbose ? Console.Out : TextWriter.Null; @@ -156,10 +150,11 @@ public static Command CollectCommand() => VerboseOption(), TimeoutOption(), NameOption(), - DiagnosticPortOption() + DiagnosticPortOption(), + DSRouterOption() }; - private static Option ProcessIdOption() => + private static Option ProcessIdOption() => new( aliases: new[] { "-p", "--process-id" }, description: "The process id to collect the gcdump from.") @@ -167,7 +162,7 @@ private static Option ProcessIdOption() => Argument = new Argument(name: "pid"), }; - private static Option NameOption() => + private static Option NameOption() => new( aliases: new[] { "-n", "--name" }, description: "The name of the process to collect the gcdump from.") @@ -175,7 +170,7 @@ private static Option NameOption() => Argument = new Argument(name: "name") }; - private static Option OutputPathOption() => + private static Option OutputPathOption() => new( aliases: new[] { "-o", "--output" }, description: $@"The path where collected gcdumps should be written. Defaults to '.\YYYYMMDD_HHMMSS_.gcdump' where YYYYMMDD is Year/Month/Day and HHMMSS is Hour/Minute/Second. Otherwise, it is the full path and file name of the dump.") @@ -183,7 +178,7 @@ private static Option OutputPathOption() => Argument = new Argument(name: "gcdump-file-path", getDefaultValue: () => string.Empty) }; - private static Option VerboseOption() => + private static Option VerboseOption() => new( aliases: new[] { "-v", "--verbose" }, description: "Output the log while collecting the gcdump.") @@ -192,7 +187,7 @@ private static Option VerboseOption() => }; public static int DefaultTimeout = 30; - private static Option TimeoutOption() => + private static Option TimeoutOption() => new( aliases: new[] { "-t", "--timeout" }, description: $"Give up on collecting the gcdump if it takes longer than this many seconds. The default value is {DefaultTimeout}s.") @@ -200,12 +195,20 @@ private static Option TimeoutOption() => Argument = new Argument(name: "timeout", getDefaultValue: () => DefaultTimeout) }; - private static Option DiagnosticPortOption() => + private static Option DiagnosticPortOption() => + new( + aliases: new[] { "--dport", "--diagnostic-port" }, + description: "The path to a diagnostic port to collect the dump from.") + { + Argument = new Argument(name: "diagnostic-port", getDefaultValue: () => string.Empty) + }; + + private static Option DSRouterOption() => new( - alias: "--diagnostic-port", - description: @"The path to a diagnostic port to be used.") + aliases: new[] { "--dsrouter" }, + description: "Process identified by -p|-n|--process-id|--name is a dotnet-dsrouter process.") { - Argument = new Argument(name: "diagnosticPort", getDefaultValue: () => string.Empty) + Argument = new Argument(name: "dsrouter", getDefaultValue: () => false) }; } } diff --git a/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs index cf155ad9a5..3f1239d728 100644 --- a/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs +++ b/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs @@ -9,12 +9,14 @@ using System.Threading.Tasks; using Microsoft.Diagnostics.Tools.GCDump.CommandLine; using Microsoft.Tools.Common; +using Microsoft.Internal.Common.Utils; +using Microsoft.Diagnostics.NETCore.Client; namespace Microsoft.Diagnostics.Tools.GCDump { internal static class ReportCommandHandler { - private delegate Task ReportDelegate(CancellationToken ct, IConsole console, FileInfo gcdump_filename, int? processId = null, ReportType reportType = ReportType.HeapStat); + private delegate Task ReportDelegate(CancellationToken ct, IConsole console, FileInfo gcdump_filename, int? processId = null, ReportType reportType = ReportType.HeapStat, string diagnosticPort = null, bool dsRouter = false); public static Command ReportCommand() => new( @@ -24,23 +26,33 @@ public static Command ReportCommand() => // Handler HandlerDescriptor.FromDelegate((ReportDelegate) Report).GetCommandHandler(), // Options - FileNameArgument(), ProcessIdOption(), ReportTypeOption() + FileNameArgument(), + ProcessIdOption(), + ReportTypeOption(), + DiagnosticPortOption(), + DSRouterOption() }; - private static Task Report(CancellationToken ct, IConsole console, FileInfo gcdump_filename, int? processId = null, ReportType type = ReportType.HeapStat) + private static Task Report(CancellationToken ct, IConsole console, FileInfo gcdump_filename, int? processId = null, ReportType type = ReportType.HeapStat, string diagnosticPort = null, bool dsRouter = false) { // // Validation // - if (gcdump_filename == null && !processId.HasValue) + if (gcdump_filename == null && !processId.HasValue && string.IsNullOrEmpty(diagnosticPort)) { - Console.Error.WriteLine(" or -p|--process-id is required"); + Console.Error.WriteLine(" or -p|--process-id or --dport|--diagnostic-port is required"); return Task.FromResult(-1); } - if (gcdump_filename != null && processId.HasValue) + if (gcdump_filename != null && (processId.HasValue || !string.IsNullOrEmpty(diagnosticPort))) { - Console.Error.WriteLine("Specify only one of -f|--file or -p|--process-id."); + Console.Error.WriteLine("Specify only one of -f|--file or -p|--process-id or --dport|--diagnostic-port."); + return Task.FromResult(-1); + } + + if (processId.HasValue && !string.IsNullOrEmpty(diagnosticPort)) + { + Console.Error.WriteLine("Specify only one of -p|--process-id or -dport|--diagnostic-port."); return Task.FromResult(-1); } @@ -53,14 +65,14 @@ private static Task Report(CancellationToken ct, IConsole console, FileInfo { source = ReportSource.DumpFile; } - else if (processId.HasValue) + else if (processId.HasValue || !string.IsNullOrEmpty(diagnosticPort)) { source = ReportSource.Process; } return (source, type) switch { - (ReportSource.Process, ReportType.HeapStat) => ReportFromProcess(processId.Value, ct), + (ReportSource.Process, ReportType.HeapStat) => ReportFromProcess(processId ?? 0, diagnosticPort, dsRouter, ct), (ReportSource.DumpFile, ReportType.HeapStat) => ReportFromFile(gcdump_filename), _ => HandleUnknownParam() }; @@ -72,10 +84,42 @@ private static Task HandleUnknownParam() return Task.FromResult(-1); } - private static Task ReportFromProcess(int processId, CancellationToken ct) + private static Task ReportFromProcess(int processId, string diagnosticPort, bool dsRouter, CancellationToken ct) { + if (!CommandUtils.ValidateArgumentsForAttach(processId, string.Empty, diagnosticPort, out int resolvedProcessId)) + { + return Task.FromResult(-1); + } + + processId = resolvedProcessId; + + if (processId > 0 && dsRouter) + { + diagnosticPort = PidIpcEndpoint.GetDefaultAddressForProcessId(processId, dsRouter) + ",connect"; + } + + if (!string.IsNullOrEmpty(diagnosticPort)) + { + try + { + IpcEndpointConfig config = IpcEndpointConfig.Parse(diagnosticPort); + if (!config.IsConnectConfig) + { + Console.Error.WriteLine("--diagnostic-port is only supporting connect mode."); + return Task.FromResult(-1); + } + } + catch (Exception ex) + { + Console.Error.WriteLine($"--diagnostic-port argument error: {ex.Message}"); + return Task.FromResult(-1); + } + + processId = 0; + } + if (!CollectCommandHandler - .TryCollectMemoryGraph(ct, processId, string.Empty, CollectCommandHandler.DefaultTimeout, false, out Graphs.MemoryGraph mg)) + .TryCollectMemoryGraph(ct, processId, diagnosticPort, CollectCommandHandler.DefaultTimeout, false, out Graphs.MemoryGraph mg)) { Console.Error.WriteLine("An error occured while collecting gcdump."); return Task.FromResult(-1); @@ -115,12 +159,35 @@ private static Argument FileNameArgument() => }.ExistingOnly(); private static Option ProcessIdOption() => - new(new[] { "-p", "--process-id" }, "The process id to collect the gcdump from."); + new( + aliases: new[] { "-p", "--process-id" }, + description: "The process id to collect the gcdump from.") + { + Argument = new Argument(name: "pid"), + }; private static Option ReportTypeOption() => - new(new[] { "-t", "--report-type" }, "The type of report to generate. Available options: heapstat (default)") + new( + aliases: new[] { "-t", "--report-type" }, + description: "The type of report to generate. Available options: heapstat (default)") + { + Argument = new Argument(name: "report-type", () => ReportType.HeapStat) + }; + + private static Option DiagnosticPortOption() => + new( + aliases: new[] { "--dport", "--diagnostic-port" }, + description: "The path to a diagnostic port to collect the dump from.") + { + Argument = new Argument(name: "diagnostic-port", getDefaultValue: () => string.Empty) + }; + + private static Option DSRouterOption() => + new( + aliases: new[] { "--dsrouter" }, + description: "Process identified by -p|--process-id is a dotnet-dsrouter process.") { - Argument = new Argument(() => ReportType.HeapStat) + Argument = new Argument(name: "dsrouter", getDefaultValue: () => false) }; private enum ReportSource diff --git a/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs b/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs index 57084c1930..073af36553 100644 --- a/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs +++ b/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs @@ -108,13 +108,14 @@ public static bool DumpFromEventPipeFile(string path, MemoryGraph memoryGraph, T /// generate a GCHeapDump using the resulting events. The correct keywords and provider name /// are given as input to the Func eventPipeEventSourceFactory. /// - /// + /// /// /// /// + /// /// /// - public static bool DumpFromEventPipe(CancellationToken ct, int processID, string diagnosticPort, MemoryGraph memoryGraph, TextWriter log, int timeout, DotNetHeapInfo dotNetInfo = null) + public static bool DumpFromEventPipe(CancellationToken ct, int processId, string diagnosticPort, MemoryGraph memoryGraph, TextWriter log, int timeout, DotNetHeapInfo dotNetInfo) { DateTime start = DateTime.Now; Func getElapsed = () => DateTime.Now - start; @@ -130,7 +131,7 @@ public static bool DumpFromEventPipe(CancellationToken ct, int processID, string bool fDone = false; log.WriteLine("{0,5:n1}s: Creating type table flushing task", getElapsed().TotalSeconds); - using (EventPipeSessionController typeFlushSession = new(processID, diagnosticPort, new List { + using (EventPipeSessionController typeFlushSession = new(processId, diagnosticPort, new List { new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational) }, false)) { @@ -155,7 +156,7 @@ public static bool DumpFromEventPipe(CancellationToken ct, int processID, string // Start the providers and trigger the GCs. log.WriteLine("{0,5:n1}s: Requesting a .NET Heap Dump", getElapsed().TotalSeconds); - using EventPipeSessionController gcDumpSession = new(processID, diagnosticPort, new List { + using EventPipeSessionController gcDumpSession = new(processId, diagnosticPort, new List { new EventPipeProvider("Microsoft-Windows-DotNETRuntime", EventLevel.Verbose, (long)(ClrTraceEventParser.Keywords.GCHeapSnapshot)) }); log.WriteLine("{0,5:n1}s: gcdump EventPipe Session started", getElapsed().TotalSeconds); @@ -164,12 +165,12 @@ public static bool DumpFromEventPipe(CancellationToken ct, int processID, string gcDumpSession.Source.Clr.GCStart += delegate (GCStartTraceData data) { - if (processID == 0) + if (processId == 0) { - processID = data.ProcessID; - log.WriteLine("Process wildcard selects process id {0}", processID); + processId = data.ProcessID; + log.WriteLine("Process wildcard selects process id {0}", processId); } - if (data.ProcessID != processID) + if (data.ProcessID != processId) { return; } @@ -185,7 +186,7 @@ public static bool DumpFromEventPipe(CancellationToken ct, int processID, string gcDumpSession.Source.Clr.GCStop += delegate (GCEndTraceData data) { - if (data.ProcessID != processID) + if (data.ProcessID != processId) { return; } @@ -199,7 +200,7 @@ public static bool DumpFromEventPipe(CancellationToken ct, int processID, string gcDumpSession.Source.Clr.GCBulkNode += delegate (GCBulkNodeTraceData data) { - if (data.ProcessID != processID) + if (data.ProcessID != processId) { return; } @@ -216,7 +217,7 @@ public static bool DumpFromEventPipe(CancellationToken ct, int processID, string if (memoryGraph != null) { - dumper.SetupCallbacks(memoryGraph, gcDumpSession.Source, processID.ToString()); + dumper.SetupCallbacks(memoryGraph, gcDumpSession.Source, processId.ToString()); } // Set up a separate thread that will listen for EventPipe events coming back telling us we succeeded. diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs index fbf3fdac41..49b717c253 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs @@ -31,7 +31,7 @@ private static void ConsoleWriteLine(string str) } } - private delegate Task CollectDelegate(CancellationToken ct, IConsole console, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration, string clrevents, string clreventlevel, string name, string port, bool showchildio, bool resumeRuntime); + private delegate Task CollectDelegate(CancellationToken ct, IConsole console, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration, string clrevents, string clreventlevel, string name, string port, bool showchildio, bool resumeRuntime, bool dsRouter); /// /// Collects a diagnostic trace from a currently running process or launch a child process and trace it. @@ -52,8 +52,9 @@ private static void ConsoleWriteLine(string str) /// Path to the diagnostic port to be used. /// Should IO from a child process be hidden. /// Resume runtime once session has been initialized. + /// Process identified by processId is a dotnet-dsrouter process. /// - private static async Task Collect(CancellationToken ct, IConsole console, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration, string clrevents, string clreventlevel, string name, string diagnosticPort, bool showchildio, bool resumeRuntime) + private static async Task Collect(CancellationToken ct, IConsole console, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration, string clrevents, string clreventlevel, string name, string diagnosticPort, bool showchildio, bool resumeRuntime, bool dsRouter) { bool collectionStopped = false; bool cancelOnEnter = true; @@ -107,6 +108,12 @@ private static async Task Collect(CancellationToken ct, IConsole console, i return (int)ReturnCode.ArgumentError; } + if (processId > 0 && dsRouter) + { + diagnosticPort = PidIpcEndpoint.GetDefaultAddressForProcessId(processId, dsRouter) + ",connect"; + processId = -1; + } + if (profile.Length == 0 && providers.Length == 0 && clrevents.Length == 0) { ConsoleWriteLine("No profile or providers specified, defaulting to trace profile 'cpu-sampling'"); @@ -455,7 +462,8 @@ public static Command CollectCommand() => CommonOptions.NameOption(), DiagnosticPortOption(), ShowChildIOOption(), - ResumeRuntimeOption() + ResumeRuntimeOption(), + DSRouterOption() }; private static uint DefaultCircularBufferSizeInMB() => 256; @@ -526,7 +534,7 @@ private static Option CLREventLevelOption() => }; private static Option DiagnosticPortOption() => new( - alias: "--diagnostic-port", + aliases: new[] { "--dport", "--diagnostic-port" }, description: @"The path to a diagnostic port to be used.") { Argument = new Argument(name: "diagnosticPort", getDefaultValue: () => string.Empty) @@ -546,5 +554,13 @@ private static Option ResumeRuntimeOption() => { Argument = new Argument(name: "resumeRuntime", getDefaultValue: () => true) }; + + private static Option DSRouterOption() => + new( + aliases: new[] { "--dsrouter" }, + description: "Process identified by -p|-n|--process-id|--name is a dotnet-dsrouter process.") + { + Argument = new Argument(name: "dsrouter", getDefaultValue: () => false) + }; } } From d0f078958cac7f9a3c033eea6023cdf16c97466a Mon Sep 17 00:00:00 2001 From: lateralusX Date: Thu, 20 Jul 2023 16:59:59 +0200 Subject: [PATCH 4/5] Drop --dsrouter argument, adjust default IPC channel probing to detect dsrouter. --- .../DiagnosticsIpc/IpcTransport.cs | 44 +++++++++++++------ src/Tools/dotnet-counters/CounterMonitor.cs | 18 +------- src/Tools/dotnet-counters/Program.cs | 20 ++------- .../DiagnosticsServerRouterCommands.cs | 6 +-- .../CommandLine/CollectCommandHandler.cs | 21 ++------- .../CommandLine/ReportCommandHandler.cs | 22 ++-------- .../EventPipeDotNetHeapDumper.cs | 2 +- .../CommandLine/Commands/CollectCommand.cs | 22 ++-------- 8 files changed, 50 insertions(+), 105 deletions(-) diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcTransport.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcTransport.cs index 7df8f333fc..951f8be5a0 100644 --- a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcTransport.cs +++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcTransport.cs @@ -275,7 +275,7 @@ private string GetDefaultAddress() throw new ServerNotAvailableException($"Process {_pid} seems to be elevated."); } - if (!TryGetDefaultAddress(_pid, false, out string transportName)) + if (!TryGetDefaultAddress(_pid, out string transportName)) { throw new ServerNotAvailableException($"Process {_pid} not running compatible .NET runtime."); } @@ -283,37 +283,53 @@ private string GetDefaultAddress() return transportName; } - private static bool TryGetDefaultAddress(int pid, bool dsRouter, out string defaultAddress) + private static bool TryGetDefaultAddress(int pid, out string defaultAddress) { defaultAddress = null; - string addressPrefix = !dsRouter ? "dotnet-diagnostic" : "dotnet-dsrouter"; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - defaultAddress = $"{addressPrefix}-{pid}"; + defaultAddress = $"dotnet-diagnostic-{pid}"; + + try + { + string dsrouterAddress = Directory.GetFiles(IpcRootPath, $"dotnet-diagnostic-dsrouter-{pid}").FirstOrDefault(); + if (!string.IsNullOrEmpty(dsrouterAddress)) + { + defaultAddress = dsrouterAddress; + } + } + catch { } } else { try { - defaultAddress = Directory.GetFiles(IpcRootPath, $"{addressPrefix}-{pid}-*-socket") // Try best match. + defaultAddress = Directory.GetFiles(IpcRootPath, $"dotnet-diagnostic-{pid}-*-socket") // Try best match. .OrderByDescending(f => new FileInfo(f).LastWriteTime) .FirstOrDefault(); + + string dsrouterAddress = Directory.GetFiles(IpcRootPath, $"dotnet-diagnostic-dsrouter-{pid}-*-socket") // Try best match. + .OrderByDescending(f => new FileInfo(f).LastWriteTime) + .FirstOrDefault(); + + if (!string.IsNullOrEmpty(dsrouterAddress) && !string.IsNullOrEmpty(defaultAddress)) + { + FileInfo defaultFile = new(defaultAddress); + FileInfo dsrouterFile = new(dsrouterAddress); + + if (dsrouterFile.LastWriteTime >= defaultFile.LastWriteTime) + { + defaultAddress = dsrouterAddress; + } + } } - catch (InvalidOperationException) - { - } + catch { } } return !string.IsNullOrEmpty(defaultAddress); } - public static string GetDefaultAddressForProcessId(int pid, bool dsRouter) - { - return TryGetDefaultAddress(pid, dsRouter, out string defaultAddress) ? defaultAddress : string.Empty; - } - public override bool Equals(object obj) { return Equals(obj as PidIpcEndpoint); diff --git a/src/Tools/dotnet-counters/CounterMonitor.cs b/src/Tools/dotnet-counters/CounterMonitor.cs index afae9fc40d..ea6b2e5f59 100644 --- a/src/Tools/dotnet-counters/CounterMonitor.cs +++ b/src/Tools/dotnet-counters/CounterMonitor.cs @@ -503,8 +503,7 @@ public async Task Monitor( bool resumeRuntime, int maxHistograms, int maxTimeSeries, - TimeSpan duration, - bool dsRouter) + TimeSpan duration) { try { @@ -520,12 +519,6 @@ public async Task Monitor( } ct.Register(() => _shouldExit.TrySetResult((int)ReturnCode.Ok)); - if (_processId > 0 && dsRouter) - { - diagnosticPort = PidIpcEndpoint.GetDefaultAddressForProcessId(_processId, dsRouter) + ",connect"; - _processId = -1; - } - DiagnosticsClientBuilder builder = new("dotnet-counters", 10); using (DiagnosticsClientHolder holder = await builder.Build(ct, _processId, diagnosticPort, showChildIO: false, printLaunchCommand: false).ConfigureAwait(false)) using (VirtualTerminalMode vTerm = VirtualTerminalMode.TryEnable()) @@ -586,8 +579,7 @@ public async Task Collect( bool resumeRuntime, int maxHistograms, int maxTimeSeries, - TimeSpan duration, - bool dsRouter) + TimeSpan duration) { try { @@ -603,12 +595,6 @@ public async Task Collect( } ct.Register(() => _shouldExit.TrySetResult((int)ReturnCode.Ok)); - if (_processId > 0 && dsRouter) - { - diagnosticPort = PidIpcEndpoint.GetDefaultAddressForProcessId(_processId, dsRouter) + ",connect"; - _processId = -1; - } - DiagnosticsClientBuilder builder = new("dotnet-counters", 10); using (DiagnosticsClientHolder holder = await builder.Build(ct, _processId, diagnosticPort, showChildIO: false, printLaunchCommand: false).ConfigureAwait(false)) { diff --git a/src/Tools/dotnet-counters/Program.cs b/src/Tools/dotnet-counters/Program.cs index 790157c36d..ec79a2e6ef 100644 --- a/src/Tools/dotnet-counters/Program.cs +++ b/src/Tools/dotnet-counters/Program.cs @@ -35,8 +35,7 @@ private delegate Task CollectDelegate( bool resumeRuntime, int maxHistograms, int maxTimeSeries, - TimeSpan duration, - bool dsRouter); + TimeSpan duration); private delegate Task MonitorDelegate( CancellationToken ct, @@ -50,8 +49,7 @@ private delegate Task MonitorDelegate( bool resumeRuntime, int maxHistograms, int maxTimeSeries, - TimeSpan duration, - bool dsRouter); + TimeSpan duration); private static Command MonitorCommand() => new( @@ -70,8 +68,7 @@ private static Command MonitorCommand() => ResumeRuntimeOption(), MaxHistogramOption(), MaxTimeSeriesOption(), - DurationOption(), - DSRouterOption() + DurationOption() }; private static Command CollectCommand() => @@ -93,8 +90,7 @@ private static Command CollectCommand() => ResumeRuntimeOption(), MaxHistogramOption(), MaxTimeSeriesOption(), - DurationOption(), - DSRouterOption() + DurationOption() }; private static Option NameOption() => @@ -211,14 +207,6 @@ private static Option DurationOption() => Argument = new Argument(name: "duration-timespan", getDefaultValue: () => default) }; - private static Option DSRouterOption() => - new( - aliases: new[] { "--dsrouter" }, - description: "Process identified by -p|-n|--process-id|--name is a dotnet-dsrouter process.") - { - Argument = new Argument(name: "dsrouter", getDefaultValue: () => false) - }; - private static readonly string[] s_SupportedRuntimeVersions = KnownData.s_AllVersions; public static int List(IConsole console, string runtimeVersion) diff --git a/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs b/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs index 1896f0f12d..f1b11d711a 100644 --- a/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs +++ b/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs @@ -354,7 +354,7 @@ private static string GetDefaultIpcServerPath(ILogger logger) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - path = $"dotnet-dsrouter-{processId}"; + path = $"dotnet-diagnostic-dsrouter-{processId}"; } else { @@ -365,11 +365,11 @@ private static string GetDefaultIpcServerPath(ILogger logger) unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); #endif TimeSpan diff = Process.GetCurrentProcess().StartTime.ToUniversalTime() - unixEpoch; - path = Path.Combine(PidIpcEndpoint.IpcRootPath, $"dotnet-dsrouter-{processId}-{(long)diff.TotalSeconds}-socket"); + path = Path.Combine(PidIpcEndpoint.IpcRootPath, $"dotnet-diagnostic-dsrouter-{processId}-{(long)diff.TotalSeconds}-socket"); } logger?.LogDebug($"Using default IPC server path, {path}."); - logger?.LogDebug($"Attach to default IPC server using -p {processId} and --dsrouter diagnostic tooling arguments."); + logger?.LogDebug($"Attach to default dotnet-dsrouter IPC server using -p {processId} diagnostic tooling argument."); return path; } diff --git a/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs index bb7cd6f212..054644328f 100644 --- a/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs +++ b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs @@ -16,7 +16,7 @@ namespace Microsoft.Diagnostics.Tools.GCDump { internal static class CollectCommandHandler { - private delegate Task CollectDelegate(CancellationToken ct, IConsole console, int processId, string output, int timeout, bool verbose, string name, string diagnosticPort, bool dsRouter); + private delegate Task CollectDelegate(CancellationToken ct, IConsole console, int processId, string output, int timeout, bool verbose, string name, string diagnosticPort); /// /// Collects a gcdump from a currently running process. @@ -29,9 +29,8 @@ internal static class CollectCommandHandler /// Enable verbose logging. /// The process name to collect the gcdump from. /// The diagnostic IPC channel to collect the gcdump from. - /// Process identified by processId is a dotnet-dsrouter process. /// - private static async Task Collect(CancellationToken ct, IConsole console, int processId, string output, int timeout, bool verbose, string name, string diagnosticPort, bool dsRouter) + private static async Task Collect(CancellationToken ct, IConsole console, int processId, string output, int timeout, bool verbose, string name, string diagnosticPort) { if (!CommandUtils.ValidateArgumentsForAttach (processId, name, diagnosticPort, out int resolvedProcessId)) { @@ -40,11 +39,6 @@ private static async Task Collect(CancellationToken ct, IConsole console, i processId = resolvedProcessId; - if (processId > 0 && dsRouter) - { - diagnosticPort = PidIpcEndpoint.GetDefaultAddressForProcessId(processId, dsRouter) + ",connect"; - } - if (!string.IsNullOrEmpty(diagnosticPort)) { try @@ -150,8 +144,7 @@ public static Command CollectCommand() => VerboseOption(), TimeoutOption(), NameOption(), - DiagnosticPortOption(), - DSRouterOption() + DiagnosticPortOption() }; private static Option ProcessIdOption() => @@ -202,13 +195,5 @@ private static Option DiagnosticPortOption() => { Argument = new Argument(name: "diagnostic-port", getDefaultValue: () => string.Empty) }; - - private static Option DSRouterOption() => - new( - aliases: new[] { "--dsrouter" }, - description: "Process identified by -p|-n|--process-id|--name is a dotnet-dsrouter process.") - { - Argument = new Argument(name: "dsrouter", getDefaultValue: () => false) - }; } } diff --git a/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs index 3f1239d728..27dd2400ee 100644 --- a/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs +++ b/src/Tools/dotnet-gcdump/CommandLine/ReportCommandHandler.cs @@ -16,7 +16,7 @@ namespace Microsoft.Diagnostics.Tools.GCDump { internal static class ReportCommandHandler { - private delegate Task ReportDelegate(CancellationToken ct, IConsole console, FileInfo gcdump_filename, int? processId = null, ReportType reportType = ReportType.HeapStat, string diagnosticPort = null, bool dsRouter = false); + private delegate Task ReportDelegate(CancellationToken ct, IConsole console, FileInfo gcdump_filename, int? processId = null, ReportType reportType = ReportType.HeapStat, string diagnosticPort = null); public static Command ReportCommand() => new( @@ -30,10 +30,9 @@ public static Command ReportCommand() => ProcessIdOption(), ReportTypeOption(), DiagnosticPortOption(), - DSRouterOption() }; - private static Task Report(CancellationToken ct, IConsole console, FileInfo gcdump_filename, int? processId = null, ReportType type = ReportType.HeapStat, string diagnosticPort = null, bool dsRouter = false) + private static Task Report(CancellationToken ct, IConsole console, FileInfo gcdump_filename, int? processId = null, ReportType type = ReportType.HeapStat, string diagnosticPort = null) { // // Validation @@ -72,7 +71,7 @@ private static Task Report(CancellationToken ct, IConsole console, FileInfo return (source, type) switch { - (ReportSource.Process, ReportType.HeapStat) => ReportFromProcess(processId ?? 0, diagnosticPort, dsRouter, ct), + (ReportSource.Process, ReportType.HeapStat) => ReportFromProcess(processId ?? 0, diagnosticPort, ct), (ReportSource.DumpFile, ReportType.HeapStat) => ReportFromFile(gcdump_filename), _ => HandleUnknownParam() }; @@ -84,7 +83,7 @@ private static Task HandleUnknownParam() return Task.FromResult(-1); } - private static Task ReportFromProcess(int processId, string diagnosticPort, bool dsRouter, CancellationToken ct) + private static Task ReportFromProcess(int processId, string diagnosticPort, CancellationToken ct) { if (!CommandUtils.ValidateArgumentsForAttach(processId, string.Empty, diagnosticPort, out int resolvedProcessId)) { @@ -93,11 +92,6 @@ private static Task ReportFromProcess(int processId, string diagnosticPort, processId = resolvedProcessId; - if (processId > 0 && dsRouter) - { - diagnosticPort = PidIpcEndpoint.GetDefaultAddressForProcessId(processId, dsRouter) + ",connect"; - } - if (!string.IsNullOrEmpty(diagnosticPort)) { try @@ -182,14 +176,6 @@ private static Option DiagnosticPortOption() => Argument = new Argument(name: "diagnostic-port", getDefaultValue: () => string.Empty) }; - private static Option DSRouterOption() => - new( - aliases: new[] { "--dsrouter" }, - description: "Process identified by -p|--process-id is a dotnet-dsrouter process.") - { - Argument = new Argument(name: "dsrouter", getDefaultValue: () => false) - }; - private enum ReportSource { Unknown, diff --git a/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs b/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs index 073af36553..563bc3afa3 100644 --- a/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs +++ b/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs @@ -29,7 +29,7 @@ public static class EventPipeDotNetHeapDumper /// /// /// - public static bool DumpFromEventPipeFile(string path, MemoryGraph memoryGraph, TextWriter log, DotNetHeapInfo dotNetInfo = null) + public static bool DumpFromEventPipeFile(string path, MemoryGraph memoryGraph, TextWriter log, DotNetHeapInfo dotNetInfo) { DateTime start = DateTime.Now; Func getElapsed = () => DateTime.Now - start; diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs index 49b717c253..15eab9c799 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs @@ -31,7 +31,7 @@ private static void ConsoleWriteLine(string str) } } - private delegate Task CollectDelegate(CancellationToken ct, IConsole console, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration, string clrevents, string clreventlevel, string name, string port, bool showchildio, bool resumeRuntime, bool dsRouter); + private delegate Task CollectDelegate(CancellationToken ct, IConsole console, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration, string clrevents, string clreventlevel, string name, string port, bool showchildio, bool resumeRuntime); /// /// Collects a diagnostic trace from a currently running process or launch a child process and trace it. @@ -52,9 +52,8 @@ private static void ConsoleWriteLine(string str) /// Path to the diagnostic port to be used. /// Should IO from a child process be hidden. /// Resume runtime once session has been initialized. - /// Process identified by processId is a dotnet-dsrouter process. /// - private static async Task Collect(CancellationToken ct, IConsole console, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration, string clrevents, string clreventlevel, string name, string diagnosticPort, bool showchildio, bool resumeRuntime, bool dsRouter) + private static async Task Collect(CancellationToken ct, IConsole console, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration, string clrevents, string clreventlevel, string name, string diagnosticPort, bool showchildio, bool resumeRuntime) { bool collectionStopped = false; bool cancelOnEnter = true; @@ -108,12 +107,6 @@ private static async Task Collect(CancellationToken ct, IConsole console, i return (int)ReturnCode.ArgumentError; } - if (processId > 0 && dsRouter) - { - diagnosticPort = PidIpcEndpoint.GetDefaultAddressForProcessId(processId, dsRouter) + ",connect"; - processId = -1; - } - if (profile.Length == 0 && providers.Length == 0 && clrevents.Length == 0) { ConsoleWriteLine("No profile or providers specified, defaulting to trace profile 'cpu-sampling'"); @@ -462,8 +455,7 @@ public static Command CollectCommand() => CommonOptions.NameOption(), DiagnosticPortOption(), ShowChildIOOption(), - ResumeRuntimeOption(), - DSRouterOption() + ResumeRuntimeOption() }; private static uint DefaultCircularBufferSizeInMB() => 256; @@ -554,13 +546,5 @@ private static Option ResumeRuntimeOption() => { Argument = new Argument(name: "resumeRuntime", getDefaultValue: () => true) }; - - private static Option DSRouterOption() => - new( - aliases: new[] { "--dsrouter" }, - description: "Process identified by -p|-n|--process-id|--name is a dotnet-dsrouter process.") - { - Argument = new Argument(name: "dsrouter", getDefaultValue: () => false) - }; } } From fd5c7e9acffc165de318da44d72ad7e02e41d26c Mon Sep 17 00:00:00 2001 From: lateralusX Date: Wed, 26 Jul 2023 10:00:34 +0200 Subject: [PATCH 5/5] Fix dotnet-gcdump to use wildcard process id for dotnet-dsrouter. --- .../DiagnosticsIpc/IpcTransport.cs | 54 ++++++++++++------- .../DiagnosticsServerRouterCommands.cs | 2 +- .../CommandLine/CollectCommandHandler.cs | 2 +- .../EventPipeDotNetHeapDumper.cs | 20 +++++-- 4 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcTransport.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcTransport.cs index 951f8be5a0..6789b0aedf 100644 --- a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcTransport.cs +++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcTransport.cs @@ -262,25 +262,7 @@ public override async Task WaitForConnectionAsync(CancellationToken token) private string GetDefaultAddress() { - try - { - Process process = Process.GetProcessById(_pid); - } - catch (ArgumentException) - { - throw new ServerNotAvailableException($"Process {_pid} is not running."); - } - catch (InvalidOperationException) - { - throw new ServerNotAvailableException($"Process {_pid} seems to be elevated."); - } - - if (!TryGetDefaultAddress(_pid, out string transportName)) - { - throw new ServerNotAvailableException($"Process {_pid} not running compatible .NET runtime."); - } - - return transportName; + return GetDefaultAddress(_pid); } private static bool TryGetDefaultAddress(int pid, out string defaultAddress) @@ -330,6 +312,40 @@ private static bool TryGetDefaultAddress(int pid, out string defaultAddress) return !string.IsNullOrEmpty(defaultAddress); } + public static string GetDefaultAddress(int pid) + { + try + { + Process process = Process.GetProcessById(pid); + } + catch (ArgumentException) + { + throw new ServerNotAvailableException($"Process {pid} is not running."); + } + catch (InvalidOperationException) + { + throw new ServerNotAvailableException($"Process {pid} seems to be elevated."); + } + + if (!TryGetDefaultAddress(pid, out string defaultAddress)) + { + throw new ServerNotAvailableException($"Process {pid} not running compatible .NET runtime."); + } + + return defaultAddress; + } + + public static bool IsDefaultAddressDSRouter(int pid, string address) + { + if (address.StartsWith(IpcRootPath, StringComparison.OrdinalIgnoreCase)) + { + address = address.Substring(IpcRootPath.Length); + } + + string dsrouterAddress = $"dotnet-diagnostic-dsrouter-{pid}"; + return address.StartsWith(dsrouterAddress, StringComparison.OrdinalIgnoreCase); + } + public override bool Equals(object obj) { return Equals(obj as PidIpcEndpoint); diff --git a/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs b/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs index f1b11d711a..59692a9e10 100644 --- a/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs +++ b/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs @@ -369,7 +369,7 @@ private static string GetDefaultIpcServerPath(ILogger logger) } logger?.LogDebug($"Using default IPC server path, {path}."); - logger?.LogDebug($"Attach to default dotnet-dsrouter IPC server using -p {processId} diagnostic tooling argument."); + logger?.LogDebug($"Attach to default dotnet-dsrouter IPC server using --process-id {processId} diagnostic tooling argument."); return path; } diff --git a/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs index 054644328f..63f8912ecc 100644 --- a/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs +++ b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs @@ -32,7 +32,7 @@ internal static class CollectCommandHandler /// private static async Task Collect(CancellationToken ct, IConsole console, int processId, string output, int timeout, bool verbose, string name, string diagnosticPort) { - if (!CommandUtils.ValidateArgumentsForAttach (processId, name, diagnosticPort, out int resolvedProcessId)) + if (!CommandUtils.ValidateArgumentsForAttach(processId, name, diagnosticPort, out int resolvedProcessId)) { return -1; } diff --git a/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs b/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs index 563bc3afa3..4d8a40c6bb 100644 --- a/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs +++ b/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs @@ -165,10 +165,9 @@ public static bool DumpFromEventPipe(CancellationToken ct, int processId, string gcDumpSession.Source.Clr.GCStart += delegate (GCStartTraceData data) { - if (processId == 0) + if (gcDumpSession.UseWildcardProcessId) { processId = data.ProcessID; - log.WriteLine("Process wildcard selects process id {0}", processId); } if (data.ProcessID != processId) { @@ -217,7 +216,7 @@ public static bool DumpFromEventPipe(CancellationToken ct, int processId, string if (memoryGraph != null) { - dumper.SetupCallbacks(memoryGraph, gcDumpSession.Source, processId.ToString()); + dumper.SetupCallbacks(memoryGraph, gcDumpSession.Source, gcDumpSession.UseWildcardProcessId ? null : processId.ToString()); } // Set up a separate thread that will listen for EventPipe events coming back telling us we succeeded. @@ -323,8 +322,23 @@ internal sealed class EventPipeSessionController : IDisposable public IReadOnlyList Providers => _providers.AsReadOnly(); public EventPipeEventSource Source => _source; + public bool UseWildcardProcessId => _diagnosticPort != null; + public EventPipeSessionController(int pid, string diagnosticPort, List providers, bool requestRundown = true) { + if (string.IsNullOrEmpty(diagnosticPort)) + { + try + { + string defaultAddress = PidIpcEndpoint.GetDefaultAddress(pid); + if (!string.IsNullOrEmpty(defaultAddress) && PidIpcEndpoint.IsDefaultAddressDSRouter(pid, defaultAddress)) + { + diagnosticPort = defaultAddress + ",connect"; + } + } + catch { } + } + if (!string.IsNullOrEmpty(diagnosticPort)) { _diagnosticPort = IpcEndpointConfig.Parse(diagnosticPort);