Skip to content

Commit

Permalink
Add reversed diagnostics server, endpoints, and endpoint sources. (#1303
Browse files Browse the repository at this point in the history
)

Add reversed diagnostics server and endpoint information.
Add diagnostics endpoint source for unifying server and client connections into same contract.
Consume endpoint source in dotnet-monitor.
Add unit tests for reversed server, endpoints, and endpoint source concepts.
  • Loading branch information
jander-msft authored Aug 6, 2020
1 parent 732a607 commit 455c891
Show file tree
Hide file tree
Showing 42 changed files with 2,620 additions and 283 deletions.
1 change: 1 addition & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
<MicrosoftExtensionsConfigurationJsonVersion>2.1.1</MicrosoftExtensionsConfigurationJsonVersion>
<MicrosoftExtensionsConfigurationKeyPerFileVersion>2.1.1</MicrosoftExtensionsConfigurationKeyPerFileVersion>
<MicrosoftExtensionsDependencyInjectionVersion>2.1.1</MicrosoftExtensionsDependencyInjectionVersion>
<MicrosoftExtensionsHostingAbstractionsVersion>2.1.1</MicrosoftExtensionsHostingAbstractionsVersion>
<MicrosoftExtensionsLoggingVersion>2.1.1</MicrosoftExtensionsLoggingVersion>
<MicrosoftExtensionsLoggingConsoleVersion>2.1.1</MicrosoftExtensionsLoggingConsoleVersion>
<!-- We use a newer version of LoggingEventSource due to a bug in an older version-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ public DiagController(ILogger<DiagController> logger, IDiagnosticServices diagno
}

[HttpGet("processes")]
public ActionResult<IEnumerable<ProcessModel>> GetProcesses()
public Task<ActionResult<IEnumerable<ProcessModel>>> GetProcesses()
{
return this.InvokeService(() =>
return this.InvokeService(async () =>
{
IList<ProcessModel> processes = new List<ProcessModel>();
foreach (int pid in _diagnosticServices.GetProcesses())
foreach (IProcessInfo p in await _diagnosticServices.GetProcessesAsync(HttpContext.RequestAborted))
{
processes.Add(new ProcessModel() { Pid = pid });
processes.Add(ProcessModel.FromProcessInfo(p));
}
return new ActionResult<IEnumerable<ProcessModel>>(processes);
});
Expand All @@ -62,8 +62,8 @@ public Task<ActionResult> GetDump(int? pid, [FromQuery] DumpType type = DumpType
{
return this.InvokeService(async () =>
{
int pidValue = _diagnosticServices.ResolveProcess(pid);
Stream result = await _diagnosticServices.GetDump(pidValue, type);
int pidValue = await _diagnosticServices.ResolveProcessAsync(pid, HttpContext.RequestAborted);
Stream result = await _diagnosticServices.GetDump(pidValue, type, HttpContext.RequestAborted);

string dumpFileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
FormattableString.Invariant($"dump_{GetFileNameTimeStampUtcNow()}.dmp") :
Expand All @@ -80,7 +80,7 @@ public Task<ActionResult> GetGcDump(int? pid)
{
return this.InvokeService(async () =>
{
int pidValue = _diagnosticServices.ResolveProcess(pid);
int pidValue = await _diagnosticServices.ResolveProcessAsync(pid, HttpContext.RequestAborted);
Stream result = await _diagnosticServices.GetGcDump(pidValue, this.HttpContext.RequestAborted);
return File(result, "application/octet-stream", FormattableString.Invariant($"{GetFileNameTimeStampUtcNow()}_{pidValue}.gcdump"));
});
Expand Down Expand Up @@ -159,12 +159,12 @@ public Task<ActionResult> TraceCustomConfiguration(

[HttpGet("logs/{pid?}")]
[Produces(ContentTypeEventStream, ContentTypeNdJson, ContentTypeJson)]
public ActionResult Logs(int? pid, [FromQuery][Range(-1, int.MaxValue)] int durationSeconds = 30, [FromQuery] LogLevel level = LogLevel.Debug)
public Task<ActionResult> Logs(int? pid, [FromQuery][Range(-1, int.MaxValue)] int durationSeconds = 30, [FromQuery] LogLevel level = LogLevel.Debug)
{
TimeSpan duration = ConvertSecondsToTimeSpan(durationSeconds);
return this.InvokeService(() =>
return this.InvokeService(async () =>
{
int pidValue = _diagnosticServices.ResolveProcess(pid);
int pidValue = await _diagnosticServices.ResolveProcessAsync(pid, HttpContext.RequestAborted);

LogFormat format = ComputeLogFormat(Request.GetTypedHeaders().Accept);
if (format == LogFormat.None)
Expand All @@ -184,7 +184,7 @@ public ActionResult Logs(int? pid, [FromQuery][Range(-1, int.MaxValue)] int dura

private async Task<StreamWithCleanupResult> StartTrace(int? pid, MonitoringSourceConfiguration configuration, TimeSpan duration)
{
int pidValue = _diagnosticServices.ResolveProcess(pid);
int pidValue = await _diagnosticServices.ResolveProcessAsync(pid, HttpContext.RequestAborted);
IStreamWithCleanup result = await _diagnosticServices.StartTrace(pidValue, configuration, duration, this.HttpContext.RequestAborted);
return new StreamWithCleanupResult(result, "application/octet-stream", FormattableString.Invariant($"{GetFileNameTimeStampUtcNow()}_{pidValue}.nettrace"));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@
// 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.Tracing.Analysis;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;

namespace Microsoft.Diagnostics.Monitoring.RestServer
{
Expand Down Expand Up @@ -46,8 +43,9 @@ protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
//TODO In multi-process scenarios, how do we decide which process to choose?
//One possibility is to enable metrics after a request to begin polling for metrics
int pid = _services.ResolveProcess(pid: null);
await _pipeProcessor.Process(pid, Timeout.InfiniteTimeSpan, stoppingToken);
int pid = await _services.ResolveProcessAsync(pid: null, stoppingToken);
var client = new DiagnosticsClient(pid);
await _pipeProcessor.Process(client, pid, Timeout.InfiniteTimeSpan, stoppingToken);
}
catch(Exception e) when (!(e is OperationCanceledException))
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Runtime.Serialization;
using System;
using System.Runtime.Serialization;

namespace Microsoft.Diagnostics.Monitoring.RestServer.Models
{
Expand All @@ -7,5 +8,13 @@ public class ProcessModel
{
[DataMember(Name = "pid")]
public int Pid { get; set; }

[DataMember(Name = "uid")]
public Guid Uid { get; set; }

public static ProcessModel FromProcessInfo(IProcessInfo processInfo)
{
return new ProcessModel() { Pid = processInfo.Pid, Uid = processInfo.Uid };
}
}
}
44 changes: 44 additions & 0 deletions src/Microsoft.Diagnostics.Monitoring/ClientEndpointInfoSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 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;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Diagnostics.NETCore.Client;

namespace Microsoft.Diagnostics.Monitoring
{
internal sealed class ClientEndpointInfoSource : IEndpointInfoSourceInternal
{
public Task<IEnumerable<IEndpointInfo>> GetEndpointInfoAsync(CancellationToken token)
{
List<IEndpointInfo> endpointInfos = new List<IEndpointInfo>();
foreach (int pid in DiagnosticsClient.GetPublishedProcesses())
{
// CONSIDER: Generate a "runtime instance identifier" based on the pipe name
// e.g. pid + disambiguator in GUID form.
endpointInfos.Add(new EndpointInfo(pid));
}

return Task.FromResult(endpointInfos.AsEnumerable());
}

private class EndpointInfo : IEndpointInfo
{
public EndpointInfo(int processId)
{
Endpoint = new PidIpcEndpoint(processId);
ProcessId = processId;
}

public IpcEndpoint Endpoint { get; }

public int ProcessId { get; }

public Guid RuntimeInstanceCookie => Guid.Empty;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public sealed class CpuProfileConfiguration : MonitoringSourceConfiguration
public override IList<EventPipeProvider> GetProviders() =>
new EventPipeProvider[]
{
new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", System.Diagnostics.Tracing.EventLevel.Informational),
new EventPipeProvider(SampleProfilerProviderName, System.Diagnostics.Tracing.EventLevel.Informational),
new EventPipeProvider("Microsoft-Windows-DotNETRuntime", System.Diagnostics.Tracing.EventLevel.Informational, (long) Tracing.Parsers.ClrTraceEventParser.Keywords.Default)
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public abstract class MonitoringSourceConfiguration
public const string GrpcAspNetCoreServer = "Grpc.AspNetCore.Server";
public const string DiagnosticSourceEventSource = "Microsoft-Diagnostics-DiagnosticSource";
public const string TplEventSource = "System.Threading.Tasks.TplEventSource";
public const string SampleProfilerProviderName = "Microsoft-DotNETCore-SampleProfiler";
public const string EventPipeProviderName = "Microsoft-DotNETCore-EventPipe";

public abstract IList<EventPipeProvider> GetProviders();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// 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.Collections.Generic;
using System.Diagnostics.Tracing;

namespace Microsoft.Diagnostics.Monitoring
{
public sealed class SampleProfilerConfiguration : MonitoringSourceConfiguration
{
public override IList<EventPipeProvider> GetProviders() =>
new EventPipeProvider[]
{
new EventPipeProvider(SampleProfilerProviderName, EventLevel.Informational)
};

public override int BufferSizeInMB => 1;

public override bool RequestRundown => false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ namespace Microsoft.Diagnostics.Monitoring
/// </summary>
public interface IDiagnosticServices : IDisposable
{
IEnumerable<int> GetProcesses();
Task<IEnumerable<IProcessInfo>> GetProcessesAsync(CancellationToken token);

int ResolveProcess(int? pid);
Task<int> ResolveProcessAsync(int? pid, CancellationToken token);

Task<Stream> GetDump(int pid, DumpType mode);
Task<Stream> GetDump(int pid, DumpType mode, CancellationToken token);

Task<Stream> GetGcDump(int pid, CancellationToken token);

Expand All @@ -35,6 +35,13 @@ public interface IStreamWithCleanup : IAsyncDisposable
Stream Stream { get; }
}

public interface IProcessInfo
{
int Pid { get; }

Guid Uid { get; }
}

public enum DumpType
{
Full = 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Diagnostics.NETCore.Client;

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

int ProcessId { get; }

Guid RuntimeInstanceCookie { get; }
}

public interface IEndpointInfoSource
{
}

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

0 comments on commit 455c891

Please sign in to comment.