// Creates a ProcessInfo object from the IEndpointInfo. Attempts to get the command line using event pipe // if the endpoint information doesn't provide it. The cancelation token can be used to timebox this fallback // mechansim. public static async Task <ProcessInfo> FromEndpointInfoAsync(IEndpointInfo endpointInfo, CancellationToken extendedInfoCancellationToken) { if (null == endpointInfo) { throw new ArgumentNullException(nameof(endpointInfo)); } var client = new DiagnosticsClient(endpointInfo.Endpoint); string commandLine = endpointInfo.CommandLine; if (string.IsNullOrEmpty(commandLine)) { try { var infoSettings = new EventProcessInfoPipelineSettings { Duration = Timeout.InfiniteTimeSpan, }; await using var pipeline = new EventProcessInfoPipeline(client, infoSettings, (cmdLine, token) => { commandLine = cmdLine; return(Task.CompletedTask); }); await pipeline.RunAsync(extendedInfoCancellationToken); } catch { } } string processName = null; if (!string.IsNullOrEmpty(commandLine)) { // Get the process name from the command line bool isWindowsProcess = false; if (string.IsNullOrEmpty(endpointInfo.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 // must be running on the same type of operating system as the target process (e.g. dotnet-monitor must be running on Windows // if the target process is running on Windows), then checking the local operating system should be a sufficient heuristic // to determine the operating system of the target process. isWindowsProcess = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); } else { isWindowsProcess = ProcessOperatingSystemWindowsValue.Equals(endpointInfo.OperatingSystem, StringComparison.OrdinalIgnoreCase); } string processPath = CommandLineHelper.ExtractExecutablePath(commandLine, isWindowsProcess); if (!string.IsNullOrEmpty(processPath)) { processName = Path.GetFileName(processPath); if (isWindowsProcess) { // Remove the extension on Windows to match the behavior of Process.ProcessName processName = Path.GetFileNameWithoutExtension(processName); } } } return(new ProcessInfo( endpointInfo, commandLine, processName)); }
// Creates an IProcessInfo object from the IEndpointInfo. Attempts to get the command line using event pipe // if the endpoint information doesn't provide it. The cancelation token can be used to timebox this fallback // mechanism. public static async Task <IProcessInfo> FromEndpointInfoAsync(IEndpointInfo endpointInfo, CancellationToken extendedInfoCancellationToken) { if (null == endpointInfo) { throw new ArgumentNullException(nameof(endpointInfo)); } DiagnosticsClient client = new(endpointInfo.Endpoint); string commandLine = endpointInfo.CommandLine; if (string.IsNullOrEmpty(commandLine)) { // The EventProcessInfoPipeline will frequently hang during disposal of its // EventPipeStreamProvider, which is trying to send a SessionStop command to // stop the event pipe session. When this happens, it waits for the 30 timeout // before giving up. Because this is happening during a disposal call, it is // not cancellable and hangs the entire operation for at least 30 seconds. To // mitigate, start the pipeline, get the command line, and they start the disposal // on a separate Task that is not awaited. EventProcessInfoPipeline pipeline = null; try { TaskCompletionSource <string> commandLineSource = new(TaskCreationOptions.RunContinuationsAsynchronously); using IDisposable registration = extendedInfoCancellationToken.Register( () => commandLineSource.TrySetResult(null)); EventProcessInfoPipelineSettings settings = new() { Duration = Timeout.InfiniteTimeSpan }; pipeline = new EventProcessInfoPipeline(client, settings, (cmdLine, token) => { commandLineSource.TrySetResult(cmdLine); return(Task.CompletedTask); }); await pipeline.StartAsync(extendedInfoCancellationToken); commandLine = await commandLineSource.Task; } catch { } finally { if (null != pipeline) { _ = Task.Run(() => pipeline.DisposeAsync()); } } } string processName = null; if (!string.IsNullOrEmpty(commandLine)) { // Get the process name from the command line bool isWindowsProcess = false; if (string.IsNullOrEmpty(endpointInfo.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 // must be running on the same type of operating system as the target process (e.g. dotnet-monitor must be running on Windows // if the target process is running on Windows), then checking the local operating system should be a sufficient heuristic // to determine the operating system of the target process. isWindowsProcess = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); } else { isWindowsProcess = ProcessOperatingSystemWindowsValue.Equals(endpointInfo.OperatingSystem, StringComparison.OrdinalIgnoreCase); } string processPath = CommandLineHelper.ExtractExecutablePath(commandLine, isWindowsProcess); if (!string.IsNullOrEmpty(processPath)) { processName = Path.GetFileName(processPath); if (isWindowsProcess) { // Remove the extension on Windows to match the behavior of Process.ProcessName processName = Path.GetFileNameWithoutExtension(processName); } } } return(new ProcessInfoImpl( endpointInfo, commandLine, processName)); }