Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy endpoint info, runtime info, commmand parsing, and tests from dotnet/diagnostics #451

Merged
merged 6 commits into from
Jun 18, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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