Skip to content

Commit

Permalink
Copy endpoint info, runtime info, commmand parsing, and tests from do…
Browse files Browse the repository at this point in the history
…tnet/diagnostics (#451)
  • Loading branch information
jander-msft authored Jun 18, 2021
1 parent 3fc13d4 commit deb715e
Show file tree
Hide file tree
Showing 38 changed files with 1,033 additions and 104 deletions.
1 change: 1 addition & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
<!-- Third-party references -->
<NJsonSchemaVersion>10.3.11</NJsonSchemaVersion>
<SwashbuckleAspNetCoreSwaggerGenVersion>5.6.3</SwashbuckleAspNetCoreSwaggerGenVersion>
<XunitAssertVersion>2.4.1</XunitAssertVersion>
</PropertyGroup>
<PropertyGroup Label="Dev Workflow">
<!-- These versions are not used directly. For Dev workflows, nuget requires these to properly follow
Expand Down
69 changes: 69 additions & 0 deletions src/Microsoft.Diagnostics.Monitoring.WebApi/CommandLineHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Text;

namespace Microsoft.Diagnostics.Monitoring.WebApi
{
internal class CommandLineHelper
{
public static string ExtractExecutablePath(string commandLine, bool isWindows)
{
if (string.IsNullOrEmpty(commandLine))
{
return commandLine;
}

int commandLineLength = commandLine.Length;
bool isQuoted = false;
bool isEscaped = false;
int i = 0;
char c = commandLine[0];

// Search for the first whitespace character that is not quoted.
// Store character literals as it iterates the command line. Escaped
// characters within double quotes are unescaped for non-Windows systems.
// Algorithm based on INIT_FormatCommandLine behavior from
// https://github.com/dotnet/runtime/blob/main/src/coreclr/pal/src/init/pal.cpp
StringBuilder builder = new StringBuilder(commandLineLength);
do
{
if (isEscaped)
{
builder.Append(c);
isEscaped = false;
}
else if (c == '"')
{
isQuoted = !isQuoted;
}
else if (c == '\\' && !isWindows)
{
if (isQuoted)
{
isEscaped = true;
}
else
{
builder.Append(c);
}
}
else
{
builder.Append(c);
}

if (commandLineLength == ++i)
{
break;
}

c = commandLine[i];
}
while (isQuoted || !char.IsWhiteSpace(c));

return builder.ToString();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.Diagnostics.NETCore.Client;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Diagnostics.Monitoring.WebApi
{
internal sealed class ClientEndpointInfoSource : IEndpointInfoSourceInternal
{
public async Task<IEnumerable<IEndpointInfo>> GetEndpointInfoAsync(CancellationToken token)
{
var endpointInfoTasks = new List<Task<EndpointInfo>>();
// Run the EndpointInfo creation parallel. The call to FromProcessId sends
// a GetProcessInfo command to the runtime instance to get additional information.
foreach (int pid in DiagnosticsClient.GetPublishedProcesses())
{
endpointInfoTasks.Add(Task.Run(() =>
{
try
{
return EndpointInfo.FromProcessId(pid);
}
// Catch when runtime instance shuts down while attepting to use the established diagnostic port connection.
catch (EndOfStreamException)
{
return null;
}
//Catch when the application is running a more privilaged socket than dotnet-monitor. For example, running a web app as administrator
//while running dotnet-monitor without elevation.
catch (UnauthorizedAccessException)
{
return null;
}
//Most errors from IpcTransport, such as a stale socket.
catch (ServerNotAvailableException)
{
return null;
}
}, token));
}

await Task.WhenAll(endpointInfoTasks);

return endpointInfoTasks.Where(t => t.Result != null).Select(t => t.Result);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.Diagnostics.NETCore.Client;
using System;
using System.Diagnostics;

namespace Microsoft.Diagnostics.Monitoring.WebApi
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
internal class EndpointInfo : IEndpointInfo
{
public static EndpointInfo FromProcessId(int processId)
{
var client = new DiagnosticsClient(processId);

ProcessInfo processInfo = null;
try
{
// Primary motivation is to get the runtime instance cookie in order to
// keep parity with the FromIpcEndpointInfo implementation; store the
// remainder of the information since it already has access to it.
processInfo = client.GetProcessInfo();

Debug.Assert(processId == unchecked((int)processInfo.ProcessId));
}
catch (ServerErrorException)
{
// The runtime likely doesn't understand the GetProcessInfo command.
}
catch (TimeoutException)
{
// Runtime didn't respond within client timeout.
}

// CONSIDER: Generate a runtime instance identifier based on the pipe name
// for .NET Core 3.1 e.g. pid + disambiguator in GUID form.
return new EndpointInfo()
{
Endpoint = new PidIpcEndpoint(processId),
ProcessId = processId,
RuntimeInstanceCookie = processInfo?.RuntimeInstanceCookie ?? Guid.Empty,
CommandLine = processInfo?.CommandLine,
OperatingSystem = processInfo?.OperatingSystem,
ProcessArchitecture = processInfo?.ProcessArchitecture
};
}

public static EndpointInfo FromIpcEndpointInfo(IpcEndpointInfo info)
{
var client = new DiagnosticsClient(info.Endpoint);

ProcessInfo processInfo = null;
try
{
// Primary motivation is to keep parity with the FromProcessId implementation,
// which provides the additional process information because it already has
// access to it.
processInfo = client.GetProcessInfo();

Debug.Assert(info.ProcessId == unchecked((int)processInfo.ProcessId));
Debug.Assert(info.RuntimeInstanceCookie == processInfo.RuntimeInstanceCookie);
}
catch (ServerErrorException)
{
// The runtime likely doesn't understand the GetProcessInfo command.
}
catch (TimeoutException)
{
// Runtime didn't respond within client timeout.
}

return new EndpointInfo()
{
Endpoint = info.Endpoint,
ProcessId = info.ProcessId,
RuntimeInstanceCookie = info.RuntimeInstanceCookie,
CommandLine = processInfo?.CommandLine,
OperatingSystem = processInfo?.OperatingSystem,
ProcessArchitecture = processInfo?.ProcessArchitecture
};
}

public IpcEndpoint Endpoint { get; private set; }

public int ProcessId { get; private set; }

public Guid RuntimeInstanceCookie { get; private set; }

public string CommandLine { get; private set; }

public string OperatingSystem { get; private set; }

public string ProcessArchitecture { get; private set; }

internal string DebuggerDisplay => FormattableString.Invariant($"PID={ProcessId}, Cookie={RuntimeInstanceCookie}");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.Diagnostics.NETCore.Client;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Diagnostics.Monitoring.WebApi
{
internal interface IEndpointInfo
{
IpcEndpoint Endpoint { get; }

int ProcessId { get; }

Guid RuntimeInstanceCookie { get; }

string CommandLine { get; }

string OperatingSystem { get; }

string ProcessArchitecture { get; }
}

public interface IEndpointInfoSource
{
}

internal interface IEndpointInfoSourceInternal : IEndpointInfoSource
{
Task<IEnumerable<IEndpointInfo>> GetEndpointInfoAsync(CancellationToken token);
}
}
Loading

0 comments on commit deb715e

Please sign in to comment.