# SOS stress log
StressLog.txt
+# EventPipe files
+*.netperf
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-counters", "src\Tools\dotnet-counters\dotnet-counters.csproj", "{2A9B5988-982F-4E26-9E44-D38AC5978C30}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Tools.RuntimeClient", "src\Microsoft.Diagnostics.Tools.RuntimeClient\Microsoft.Diagnostics.Tools.RuntimeClient.csproj", "{54C240C5-7932-4421-A5FB-75205DE0B824}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Checked|Any CPU = Checked|Any CPU
{1576314E-F823-4A24-BC90-22282AB33353}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{1576314E-F823-4A24-BC90-22282AB33353}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
{1576314E-F823-4A24-BC90-22282AB33353}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|Any CPU.Build.0 = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|ARM.ActiveCfg = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|ARM.Build.0 = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|ARM64.ActiveCfg = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|ARM64.Build.0 = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|x64.ActiveCfg = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|x64.Build.0 = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|x86.ActiveCfg = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|x86.Build.0 = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|ARM.Build.0 = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|x64.Build.0 = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|x86.Build.0 = Debug|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|Any CPU.Build.0 = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|ARM.ActiveCfg = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|ARM.Build.0 = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|ARM64.Build.0 = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|x64.ActiveCfg = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|x64.Build.0 = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|x86.ActiveCfg = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|x86.Build.0 = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
{43D41DE9-7CCC-4DCB-A68A-B9099E538125}.Checked|Any CPU.ActiveCfg = Release|Any CPU
{43D41DE9-7CCC-4DCB-A68A-B9099E538125}.Checked|Any CPU.Build.0 = Release|Any CPU
{43D41DE9-7CCC-4DCB-A68A-B9099E538125}.Checked|ARM.ActiveCfg = Release|Any CPU
{2A9B5988-982F-4E26-9E44-D38AC5978C30}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{2A9B5988-982F-4E26-9E44-D38AC5978C30}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
{2A9B5988-982F-4E26-9E44-D38AC5978C30}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|Any CPU.Build.0 = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|ARM.ActiveCfg = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|ARM.Build.0 = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|ARM64.ActiveCfg = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|ARM64.Build.0 = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|x64.ActiveCfg = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|x64.Build.0 = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|x86.ActiveCfg = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|x86.Build.0 = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|ARM.ActiveCfg = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|ARM.Build.0 = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|ARM64.ActiveCfg = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|ARM64.Build.0 = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|x64.ActiveCfg = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|x64.Build.0 = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|x86.ActiveCfg = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|x86.Build.0 = Debug|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|Any CPU.Build.0 = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|ARM.ActiveCfg = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|ARM.Build.0 = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|ARM64.ActiveCfg = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|ARM64.Build.0 = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|x64.ActiveCfg = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|x64.Build.0 = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|x86.ActiveCfg = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|x86.Build.0 = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Checked|Any CPU.Build.0 = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Checked|ARM.ActiveCfg = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Checked|ARM.Build.0 = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Checked|ARM64.ActiveCfg = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Checked|ARM64.Build.0 = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Checked|x64.ActiveCfg = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Checked|x64.Build.0 = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Checked|x86.ActiveCfg = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Checked|x86.Build.0 = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Debug|ARM.Build.0 = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Debug|x64.Build.0 = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Debug|x86.Build.0 = Debug|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Release|Any CPU.Build.0 = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Release|ARM.ActiveCfg = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Release|ARM.Build.0 = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Release|ARM64.Build.0 = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Release|x64.ActiveCfg = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Release|x64.Build.0 = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Release|x86.ActiveCfg = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.Release|x86.Build.0 = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
+ {54C240C5-7932-4421-A5FB-75205DE0B824}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
{D52C65C4-2C7D-45E6-9F5C-6F3A96796018} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
{B62728C8-1267-4043-B46F-5537BBAEC692} = {19FAB78C-3351-4911-8F0C-8C6056401740}
{1576314E-F823-4A24-BC90-22282AB33353} = {B62728C8-1267-4043-B46F-5537BBAEC692}
+ {718350FA-2DD9-4950-BA41-D7A7F66DAC91} = {B62728C8-1267-4043-B46F-5537BBAEC692}
{43D41DE9-7CCC-4DCB-A68A-B9099E538125} = {B62728C8-1267-4043-B46F-5537BBAEC692}
{41F59D85-FC36-3015-861B-F177863252BC} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
{A9A7C879-C320-3327-BB84-16E1322E17AE} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
{41351955-16D5-48D7-AF4C-AF25F5FB2E78} = {B62728C8-1267-4043-B46F-5537BBAEC692}
{ED27F39F-DF5C-4E22-87E0-EC5B5873B503} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
{90CF2633-58F0-44EE-943B-D70207455F20} = {19FAB78C-3351-4911-8F0C-8C6056401740}
- {718350FA-2DD9-4950-BA41-D7A7F66DAC91} = {B62728C8-1267-4043-B46F-5537BBAEC692}
+ {2A9B5988-982F-4E26-9E44-D38AC5978C30} = {B62728C8-1267-4043-B46F-5537BBAEC692}
+ {54C240C5-7932-4421-A5FB-75205DE0B824} = {19FAB78C-3351-4911-8F0C-8C6056401740}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0}
--- /dev/null
+// 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.
+
+namespace Microsoft.Diagnostics.Tools.RuntimeClient.Eventing
+{
+ enum DiagnosticMessageType : uint
+ {
+ StartSession = 1024,
+ StopSession,
+ Stream,
+ Attach,
+ Detach,
+ }
+}
--- /dev/null
+// 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.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.IO.Pipes;
+using System.Linq;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+using System.Security.Principal;
+using System.Text.RegularExpressions;
+
+namespace Microsoft.Diagnostics.Tools.RuntimeClient.Eventing
+{
+ public static class EventPipeClient
+ {
+ private static string DiagnosticPortPattern { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"^dotnetcore-diagnostic-(\d+)$" : @"^dotnetcore-diagnostic-(\d+)-(\d+)-socket$";
+
+ private static string IpcRootPath { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"\\.\pipe\" : Path.GetTempPath();
+
+ public static ulong SendCommand(int processId, byte[] buffer)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ var pipeName = $"dotnetcore-diagnostic-{processId}";
+ using (var namedPipe = new NamedPipeClientStream(
+ ".", pipeName, PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.Impersonation))
+ {
+ namedPipe.Connect((int)TimeSpan.FromSeconds(20).TotalMilliseconds);
+
+ var sw = new BinaryWriter(namedPipe);
+ sw.Write(buffer);
+
+ var br = new BinaryReader(namedPipe);
+ return br.ReadUInt64();
+ }
+ }
+ else
+ {
+ //throw new PlatformNotSupportedException("TODO: Get the ApplicationGroupId to form the string: 'dotnetcore-diagnostic-{processId}-{ApplicationGroupId}-socket'");
+ var ipcPort = Directory.GetFiles(IpcRootPath) // Try best match.
+ .Select(namedPipe => (new FileInfo(namedPipe)).Name)
+ .Single(input => Regex.IsMatch(input, $"^dotnetcore-diagnostic-{processId}-(\\d+)-socket$"));
+ var path = Path.Combine(Path.GetTempPath(), ipcPort);
+ var remoteEP = new UnixDomainSocketEndPoint(path);
+
+ using (var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified))
+ {
+ socket.Bind(remoteEP);
+ socket.Connect(remoteEP);
+ socket.Send(buffer);
+
+ var content = new byte[sizeof(ulong)];
+ int nReceivedBytes = socket.Receive(content);
+ return (nReceivedBytes == sizeof(ulong)) ? BitConverter.ToUInt64(content, 0) : 0;
+ }
+ }
+ }
+
+ public static IEnumerable<int> ListAvailablePorts()
+ {
+ return Directory.GetFiles(IpcRootPath)
+ .Select(namedPipe => (new FileInfo(namedPipe)).Name)
+ .Where(input => Regex.IsMatch(input, DiagnosticPortPattern))
+ .Select(input => int.Parse(Regex.Match(input, DiagnosticPortPattern).Groups[1].Value, NumberStyles.Integer));
+ }
+
+ public static BinaryReader StreamTracingToFile(int processId, SessionConfiguration configuration, out ulong sessionId)
+ {
+ sessionId = 0;
+
+ var header = new MessageHeader {
+ RequestType = DiagnosticMessageType.Stream,
+ Pid = (uint)Process.GetCurrentProcess().Id,
+ };
+
+ byte[] serializedConfiguration;
+ using (var stream = new MemoryStream())
+ serializedConfiguration = Serialize(header, configuration, stream);
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ var pipeName = $"dotnetcore-diagnostic-{processId}";
+ var namedPipe = new NamedPipeClientStream(
+ ".", pipeName, PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.Impersonation);
+ namedPipe.Connect((int)TimeSpan.FromSeconds(20).TotalMilliseconds);
+
+ var sw = new BinaryWriter(namedPipe);
+ sw.Write(serializedConfiguration);
+
+ var br = new BinaryReader(namedPipe);
+ sessionId = br.ReadUInt64();
+ return br;
+ }
+ else
+ {
+ throw new PlatformNotSupportedException("TODO: Get the ApplicationGroupId to form the string: 'dotnetcore-diagnostic-{processId}-{ApplicationGroupId}-socket'");
+ }
+ }
+
+ public static ulong EnableTracingToFile(int processId, SessionConfiguration configuration)
+ {
+ var header = new MessageHeader {
+ RequestType = DiagnosticMessageType.StartSession,
+ Pid = (uint)Process.GetCurrentProcess().Id,
+ };
+
+ byte[] serializedConfiguration;
+ using (var stream = new MemoryStream())
+ serializedConfiguration = Serialize(header, configuration, stream);
+
+ return SendCommand(processId, serializedConfiguration);
+ }
+
+ public static ulong DisableTracingToFile(int processId, ulong sessionId)
+ {
+ var header = new MessageHeader {
+ RequestType = DiagnosticMessageType.StopSession,
+ Pid = (uint)Process.GetCurrentProcess().Id,
+ };
+
+ byte[] sessionIdInBytes;
+ using (var stream = new MemoryStream())
+ {
+ using (var sw = new BinaryWriter(stream))
+ {
+ sw.Write((uint)header.RequestType);
+ sw.Write(header.Pid);
+
+ sw.Write(sessionId);
+ sw.Flush();
+ sessionIdInBytes = stream.ToArray();
+ }
+ }
+
+ return SendCommand(processId, sessionIdInBytes);
+ }
+
+ private static byte[] Serialize(MessageHeader header, SessionConfiguration configuration, Stream stream)
+ {
+ using (var bw = new BinaryWriter(stream))
+ {
+ bw.Write((uint)header.RequestType);
+ bw.Write(header.Pid);
+
+ bw.Write(configuration.CircularBufferSizeInMB);
+ bw.Write(configuration.MultiFileTraceLengthInSeconds);
+
+ bw.WriteString(configuration.OutputPath);
+
+ bw.Write(configuration.Providers.Count());
+ foreach (var provider in configuration.Providers)
+ {
+ bw.Write(provider.Keywords);
+ bw.Write((uint)provider.EventLevel);
+
+ bw.WriteString(provider.Name);
+ bw.WriteString(provider.FilterData);
+ }
+
+ bw.Flush();
+ stream.Position = 0;
+
+ var bytes = new byte[stream.Length];
+ stream.Read(bytes, 0, bytes.Length);
+ return bytes;
+ }
+
+ }
+ }
+}
--- /dev/null
+// 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.Runtime.InteropServices;
+
+namespace Microsoft.Diagnostics.Tools.RuntimeClient.Eventing
+{
+ [StructLayout(LayoutKind.Sequential)]
+ struct MessageHeader
+ {
+ public DiagnosticMessageType RequestType;
+
+ public uint Pid;
+ }
+}
--- /dev/null
+// 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.Diagnostics.Tracing;
+
+namespace Microsoft.Diagnostics.Tools.RuntimeClient.Eventing
+{
+ public struct Provider
+ {
+ public Provider(
+ string name,
+ ulong keywords = ulong.MaxValue,
+ EventLevel eventLevel = EventLevel.Verbose,
+ string filterData = null)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ throw new ArgumentNullException(nameof(name));
+
+ Name = name;
+ Keywords = keywords;
+ EventLevel = eventLevel;
+ FilterData = string.IsNullOrWhiteSpace(filterData) ? null : filterData;
+ }
+
+ public static Provider ToProvider(string provider)
+ {
+ if (string.IsNullOrWhiteSpace(provider))
+ throw new ArgumentNullException(nameof(provider));
+
+ var tokens = provider.Split(new[] { ':' }, 4, StringSplitOptions.None); // Keep empty tokens;
+
+ string providerName = tokens.Length > 0 ? tokens[0] : null;
+ if (string.IsNullOrWhiteSpace(providerName))
+ throw new ArgumentException("Provider name was not specified.");
+
+ ulong keywords = tokens.Length > 1 ? Convert.ToUInt64(tokens[1], 16) : ulong.MaxValue;
+ EventLevel eventLevel = tokens.Length > 2 && uint.TryParse(tokens[2], out var level) ?
+ (EventLevel)level : EventLevel.Verbose;
+ string filterData = tokens.Length > 3 ? tokens[3] : null;
+
+ return new Provider(providerName, keywords, eventLevel, filterData);
+ }
+
+ public ulong Keywords { get; }
+
+ public EventLevel EventLevel { get; }
+
+ public string Name { get; }
+
+ public string FilterData { get; }
+ }
+}
--- /dev/null
+// 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.IO;
+using System.Linq;
+
+namespace Microsoft.Diagnostics.Tools.RuntimeClient.Eventing
+{
+ public struct SessionConfiguration
+ {
+ public SessionConfiguration(uint circularBufferSizeMB, ulong multiFileSec, string outputPath, IEnumerable<Provider> providers)
+ {
+ if (providers == null)
+ throw new ArgumentNullException(nameof(providers));
+ if (providers.Count() <= 0)
+ throw new ArgumentException($"Specified providers collection is empty.");
+
+ CircularBufferSizeInMB = circularBufferSizeMB;
+ MultiFileTraceLengthInSeconds = multiFileSec;
+ _outputPath = new FileInfo(fileName: outputPath ?? $"eventpipe-{DateTime.Now:yyyyMMdd_HHmmss}.netperf");
+ _providers = new List<Provider>(providers);
+ }
+
+ public uint CircularBufferSizeInMB { get; }
+
+ public ulong MultiFileTraceLengthInSeconds { get; }
+
+ public string OutputPath => _outputPath.FullName;
+
+ public IEnumerable<Provider> Providers => _providers;
+
+ private readonly FileInfo _outputPath;
+ private readonly List<Provider> _providers;
+ }
+}
--- /dev/null
+using Microsoft.Diagnostics.Tools.RuntimeClient.Eventing;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Microsoft.Diagnostics.Tools.RuntimeClient
+{
+ internal static class Extensions
+ {
+ public static void WriteString(this BinaryWriter @this, string value)
+ {
+ if (@this == null)
+ throw new ArgumentNullException(nameof(@this));
+
+ @this.Write(value != null ? (value.Length + 1) : 0);
+ if (value != null)
+ @this.Write(Encoding.Unicode.GetBytes(value + '\0'));
+ }
+
+
+#if DEBUG
+ private static int GetByteCount(this string @this)
+ {
+ if (@this == null)
+ throw new ArgumentNullException(nameof(@this));
+
+ var strLength = @this == null ? 0 : Encoding.Unicode.GetByteCount(@this + '\0');
+ return Marshal.SizeOf(typeof(int)) + strLength;
+ }
+
+ public static int GetByteCount(this SessionConfiguration @this)
+ {
+ int size = 0;
+
+ size += Marshal.SizeOf(@this.CircularBufferSizeInMB.GetType());
+ size += Marshal.SizeOf(@this.MultiFileTraceLengthInSeconds.GetType());
+
+ size += @this.OutputPath.GetByteCount();
+
+ size += Marshal.SizeOf(typeof(int));
+ foreach (var provider in @this.Providers)
+ {
+ size += Marshal.SizeOf(provider.Keywords.GetType());
+ size += Marshal.SizeOf(typeof(uint)); // provider.EventLevel.GetType()
+ size += provider.Name.GetByteCount();
+ size += provider.FilterData.GetByteCount();
+ }
+
+ return size;
+ }
+#endif // DEBUG
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Library</OutputType>
+ <TargetFramework>netcoreapp2.1</TargetFramework>
+ <RootNamespace>Microsoft.Diagnostics.Tools.RuntimeClient</RootNamespace>
+ <IsPackable>False</IsPackable>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <UseSharedCompilation>False</UseSharedCompilation>
+ <LangVersion>Latest</LangVersion>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>True</TreatWarningsAsErrors>
+ <GenerateDocumentationFile>True</GenerateDocumentationFile>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="2.0.38" />
+ </ItemGroup>
+
+ <PropertyGroup>
+ <!--
+ Suppress warnings for using preview SDK:
+ You are working with a preview version of the .NET Core SDK.
+ You can define the SDK version via a global.json file in the current project.
+ More at https://go.microsoft.com/fwlink/?linkid=869452
+ -->
+ <SuppressNETCoreSdkPreviewMessage>True</SuppressNETCoreSdkPreviewMessage>
+ </PropertyGroup>
+
+</Project>
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.Tracing;
-using System.Linq;
-using System.Text;
-
-namespace Microsoft.Diagnostics.Tools.Collect
-{
- public class CollectionConfiguration
- {
- public int? ProcessId { get; set; }
- public string OutputPath { get; set; }
- public int? CircularMB { get; set; }
- public IList<EventSpec> Providers { get; set; } = new List<EventSpec>();
- public IList<LoggerSpec> Loggers { get; set; } = new List<LoggerSpec>();
-
- internal string ToConfigString()
- {
- var builder = new StringBuilder();
- if (ProcessId != null)
- {
- builder.AppendLine($"ProcessId={ProcessId.Value}");
- }
- if (!string.IsNullOrEmpty(OutputPath))
- {
- builder.AppendLine($"OutputPath={OutputPath}");
- }
- if (CircularMB != null)
- {
- builder.AppendLine($"CircularMB={CircularMB}");
- }
- if (Providers != null && Providers.Count > 0)
- {
- builder.AppendLine($"Providers={SerializeProviders(Enumerable.Concat(Providers, GenerateLoggerSpec(Loggers)))}");
- }
- return builder.ToString();
- }
-
- public void AddProfile(CollectionProfile profile)
- {
- foreach (var provider in profile.EventSpecs)
- {
- Providers.Add(provider);
- }
-
- foreach (var logger in profile.LoggerSpecs)
- {
- Loggers.Add(logger);
- }
- }
-
- private string SerializeProviders(IEnumerable<EventSpec> providers) => string.Join(",", providers.Select(s => s.ToConfigString()));
-
- private IEnumerable<EventSpec> GenerateLoggerSpec(IList<LoggerSpec> loggers)
- {
- if (loggers.Count > 0)
- {
- var filterSpec = new StringBuilder();
- foreach (var logger in loggers)
- {
- if (string.IsNullOrEmpty(logger.Level))
- {
- filterSpec.Append($"{logger.Prefix}");
- }
- else
- {
- filterSpec.Append($"{logger.Prefix}:{logger.Level}");
- }
- filterSpec.Append(";");
- }
-
- // Remove trailing ';'
- filterSpec.Length -= 1;
-
- yield return new EventSpec(
- provider: "Microsoft-Extensions-Logging",
- keywords: 0x04 | 0x08, // FormattedMessage | JsonMessage (source: https://github.com/aspnet/Extensions/blob/aa7fa91cfc8f6ff078b020a428bcad71ae7a32ab/src/Logging/Logging.EventSource/src/LoggingEventSource.cs#L95)
- level: EventLevel.LogAlways,
- parameters: new Dictionary<string, string>() {
- { "FilterSpecs", filterSpec.ToString() }
- });
- }
- }
- }
-}
+++ /dev/null
-using System.Collections.Generic;
-using System.Linq;
-
-namespace Microsoft.Diagnostics.Tools.Collect
-{
- public class CollectionProfile
- {
- public static readonly string DefaultProfileName = "Default";
-
- public string Name { get; }
- public string Description { get; }
- public IReadOnlyList<EventSpec> EventSpecs { get; }
- public IReadOnlyList<LoggerSpec> LoggerSpecs { get; }
-
- public CollectionProfile(string name, string description, IEnumerable<EventSpec> eventSpecs, IEnumerable<LoggerSpec> loggerSpecs)
- {
- Name = name;
- Description = description;
- EventSpecs = eventSpecs.ToList();
- LoggerSpecs = loggerSpecs.ToList();
- }
- }
-}
--- /dev/null
+// 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.Tools.RuntimeClient.Eventing;
+using System;
+using System.CommandLine;
+using System.CommandLine.Invocation;
+using System.Threading.Tasks;
+
+namespace Microsoft.Diagnostics.Tools.Trace
+{
+ internal static class PortsCommandHandler
+ {
+ public static async Task<int> GetActivePorts(IConsole console)
+ {
+ try
+ {
+ foreach (var pid in EventPipeClient.ListAvailablePorts())
+ Console.Out.WriteLine($"{System.Diagnostics.Process.GetProcessById(pid).ProcessName}({pid})");
+
+ await Task.FromResult(0);
+ return 0;
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine($"[ERROR]: {ex.ToString()}");
+ return 1;
+ }
+ }
+
+ public static Command ActivePortsCommand() =>
+ new Command(
+ name: "ports",
+ description: "List all active DotNet Core Diagnostic ports.",
+ handler: CommandHandler.Create<IConsole>(GetActivePorts),
+ isHidden: true);
+ }
+}
--- /dev/null
+// 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.Tools.RuntimeClient.Eventing;
+using System;
+using System.Collections.Generic;
+using System.CommandLine;
+using System.CommandLine.Invocation;
+using System.Diagnostics.Tracing;
+using System.Threading.Tasks;
+
+namespace Microsoft.Diagnostics.Tools.Trace
+{
+ internal static class ProvidersCommandHandler
+ {
+ public static async Task<int> KnownProviders(IConsole console)
+ {
+ try
+ {
+ foreach (var provider in CommonProviders)
+ {
+ var filterData = provider.FilterData == null ? "" : $":{provider.FilterData}";
+ Console.Out.WriteLine($"Provider={provider.Name}:0x{provider.Keywords:X16}:{(uint)provider.EventLevel}{filterData}");
+ }
+
+ await Task.FromResult(0);
+ return 0;
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine($"[ERROR]: {ex.ToString()}");
+ return 1;
+ }
+ }
+
+ public static Command KnownProvidersCommand() =>
+ new Command(
+ name: "knownproviders",
+ description: "List known tracing flags.",
+ handler: CommandHandler.Create<IConsole>(KnownProviders),
+ isHidden: true);
+
+ private static IEnumerable<Provider> CommonProviders { get; } = new[] {
+ new Provider("Microsoft-Windows-DotNETRuntime", 0x0000000000000001, EventLevel.Informational), // ClrGC
+ new Provider("Microsoft-Windows-DotNETRuntime", 0x0000000000000002, EventLevel.Informational), // ClrThreadPool
+ new Provider("Microsoft-Windows-DotNETRuntime", 0x00000000FFFFFFFF, EventLevel.Verbose), // ClrAll
+ new Provider("Microsoft-Windows-DotNETRuntime", 0x00000004C14FCCBD, EventLevel.Informational), //
+ new Provider("Microsoft-Windows-DotNETRuntimeRundown"),
+ };
+ }
+}
--- /dev/null
+// 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.Tools.RuntimeClient.Eventing;
+using System;
+using System.Collections.Generic;
+using System.CommandLine;
+using System.CommandLine.Invocation;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Microsoft.Diagnostics.Tools.Trace
+{
+ internal static class StartCommandHandler
+ {
+ public static async Task<int> Start(IConsole console, int pid, string output, uint buffersize, string providers, ulong multiFileSec)
+ {
+ try
+ {
+ var configuration = new SessionConfiguration(
+ circularBufferSizeMB: buffersize,
+ multiFileSec: multiFileSec,
+ outputPath: output,
+ ToProviders(providers));
+ var sessionId = EventPipeClient.EnableTracingToFile(pid, configuration);
+ Console.Out.WriteLine($"OutputPath={configuration.OutputPath}");
+ Console.Out.WriteLine($"SessionId=0x{sessionId:X16}");
+
+ await Task.FromResult(0);
+ return sessionId != 0 ? 0 : 1;
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine($"[ERROR]: {ex.ToString()}");
+ return 1;
+ }
+ }
+
+ public static Command StartCommand() =>
+ new Command(
+ name: "start",
+ description: "Starts an EventPipe session.",
+ symbols: new Option[] {
+ CommonOptions.ProcessIdOption(),
+ OutputPathOption(),
+ CircularBufferOption(),
+ ProvidersOption(),
+ MultiFileSecOption(),
+ },
+ handler: CommandHandler.Create<IConsole, int, string, uint, string, ulong>(Start));
+
+ private static Option OutputPathOption() =>
+ new Option(
+ new[] { "-o", "--output" },
+ @"The file name to log events to.",
+ new Argument<string> { Name = "filename" });
+
+ private static Option CircularBufferOption() =>
+ new Option(
+ new[] { "--buffersize" },
+ @"Sets the size of the in-memory circular buffer in megabytes.",
+ new Argument<uint> { Name = "Size" }); // TODO: 1024 ? Default ?
+
+ private static Option ProvidersOption() =>
+ new Option(
+ aliases: new[] { "--providers" },
+ description: @"A list EventPipe provider to be enabled in the form 'Provider[,Provider]', where Provider is in the form: '(GUID|KnownProviderName)[:Flags[:Level][:KeyValueArgs]]', and KeyValueArgs is in the form: '[key1=value1][;key2=value2]'",
+ argument: new Argument<string> { Name = "Providers" }); // TODO: Can we specify an actual type?
+
+ private static Option MultiFileSecOption() =>
+ new Option(
+ aliases: new[] { "--multifilesec" },
+ description: @"Enable a file switch timer every 'n' seconds (Default is 0)",
+ argument: new Argument<ulong> { Name = "MultiFileSec" });
+
+ private static IEnumerable<Provider> ToProviders(string providers)
+ {
+ if (string.IsNullOrWhiteSpace(providers))
+ throw new ArgumentNullException(nameof(providers));
+ return providers.Split(',')
+ .Select(Provider.ToProvider);
+ }
+ }
+}
--- /dev/null
+// 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.Tools.RuntimeClient.Eventing;
+using System;
+using System.CommandLine;
+using System.CommandLine.Invocation;
+using System.Threading.Tasks;
+
+namespace Microsoft.Diagnostics.Tools.Trace
+{
+ internal static class StopCommandHandler
+ {
+ public static async Task<int> Stop(IConsole console, int pid, ulong sessionId)
+ {
+ try
+ {
+ EventPipeClient.DisableTracingToFile(pid, sessionId);
+
+ await Task.FromResult(0);
+ return sessionId != 0 ? 0 : 1;
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine($"[ERROR]: {ex.ToString()}");
+ return 1;
+ }
+ }
+
+ public static Command StopCommand() =>
+ new Command(
+ name: "stop",
+ description: "Stops an EventPipe session.",
+ symbols: new Option[] {
+ CommonOptions.ProcessIdOption(),
+ SessionIdOption(),
+ },
+ handler: CommandHandler.Create<IConsole, int, ulong>(Stop));
+
+ private static Option SessionIdOption() =>
+ new Option(
+ new[] { "--session-id" },
+ @"Session Id being recorded.",
+ new Argument<ulong> { Name = "SessionId" });
+ }
+}
--- /dev/null
+using Microsoft.Diagnostics.Tools.RuntimeClient.Eventing;
+using System;
+using System.Collections.Generic;
+using System.CommandLine;
+using System.CommandLine.Invocation;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Microsoft.Diagnostics.Tools.Trace
+{
+ internal static class StreamCommandHandler
+ {
+ public static async Task<int> Stream(IConsole console, int pid, string output, uint buffersize, string providers)
+ {
+ try
+ {
+ var configuration = new SessionConfiguration(
+ circularBufferSizeMB: buffersize,
+ multiFileSec: 0,
+ outputPath: output,
+ ToProviders(providers));
+ var binaryReader = EventPipeClient.StreamTracingToFile(pid, configuration, out var sessionId);
+ Console.Out.WriteLine($"SessionId=0x{sessionId:X16}");
+
+ if (sessionId != 0)
+ {
+ var filePath = $"dotnetcore-eventpipe-{pid}-0x{sessionId:X16}.netperf";
+ filePath = Path.Combine(@"S:\github.com\jorive\diagnostics\src\Tools\dotnet-trace", filePath);
+ using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
+ {
+ while (true)
+ {
+ var buffer = new byte[1024];
+ int nBytesRead = binaryReader.Read(buffer, 0, buffer.Length);
+ if (nBytesRead <= 0)
+ break;
+ fs.Write(buffer, 0, nBytesRead);
+ }
+ }
+ }
+
+ await Task.FromResult(0);
+ return sessionId != 0 ? 0 : 1;
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine($"[ERROR]: {ex.ToString()}");
+ return 1;
+ }
+ }
+
+ public static Command StartCommand() =>
+ new Command(
+ name: "stream",
+ description: "Starts an EventPipe session.",
+ symbols: new Option[] {
+ CommonOptions.ProcessIdOption(),
+ CircularBufferOption(),
+ ProvidersOption(),
+ },
+ handler: CommandHandler.Create<IConsole, int, string, uint, string>(Stream));
+
+ private static Option CircularBufferOption() =>
+ new Option(
+ new[] { "--buffersize" },
+ @"Sets the size of the in-memory circular buffer in megabytes.",
+ new Argument<uint> { Name = "Size" }); // TODO: 1024 ? Default ?
+
+ private static Option ProvidersOption() =>
+ new Option(
+ aliases: new[] { "--providers" },
+ description: @"A list EventPipe provider to be enabled in the form 'Provider[,Provider]', where Provider is in the form: '(GUID|KnownProviderName)[:Flags[:Level][:KeyValueArgs]]', and KeyValueArgs is in the form: '[key1=value1][;key2=value2]'",
+ argument: new Argument<string> { Name = "Providers" }); // TODO: Can we specify an actual type?
+
+ private static IEnumerable<Provider> ToProviders(string providers)
+ {
+ if (string.IsNullOrWhiteSpace(providers))
+ throw new ArgumentNullException(nameof(providers));
+ return providers.Split(',')
+ .Select(Provider.ToProvider);
+ }
+ }
+}
--- /dev/null
+// 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.CommandLine;
+
+namespace Microsoft.Diagnostics.Tools.Trace
+{
+ internal static class CommonOptions
+ {
+ public static Option ProcessIdOption() =>
+ new Option(
+ new[] { "-pid" },
+ "The unique identifier of the associated process to connect to.",
+ new Argument<int> { Name = "ProcessId" });
+ }
+}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices;
-
-namespace Microsoft.Diagnostics.Tools.Collect
-{
- internal static class ConfigPathDetector
- {
- private static readonly HashSet<string> _managedExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".exe", ".dll" };
-
- // Known .NET Platform Assemblies
- private static readonly HashSet<string> _platformAssemblies = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
- {
- "System.Private.CoreLib.dll",
- "clrjit.dll",
- };
-
- internal static string TryDetectConfigPath(int processId)
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- return Windows.TryDetectConfigPath(processId);
- }
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- {
- return Linux.TryDetectConfigPath(processId);
- }
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- return OSX.TryDetectConfigPath(processId);
- }
- return null;
- }
-
- private static class OSX
- {
- // This is defined in proc_info.h (https://opensource.apple.com/source/xnu/xnu-1228/bsd/sys/proc_info.h)
- private const int PROC_PIDPATHINFO_MAXSIZE = 1024 * 4;
-
- /// <summary>
- /// Gets the full path to the executable file identified by the specified PID
- /// </summary>
- /// <param name="pid">The PID of the running process</param>
- /// <param name="buffer">A pointer to an allocated block of memory that will be filled with the process path</param>
- /// <param name="bufferSize">The size of the buffer, should be PROC_PIDPATHINFO_MAXSIZE</param>
- /// <returns>Returns the length of the path returned on success</returns>
- [DllImport("libproc.dylib", SetLastError = true)]
- private static extern unsafe int proc_pidpath(
- int pid,
- byte* buffer,
- uint bufferSize);
-
- /// <summary>
- /// Gets the full path to the executable file identified by the specified PID
- /// </summary>
- /// <param name="pid">The PID of the running process</param>
- /// <returns>Returns the full path to the process executable</returns>
- internal static unsafe string proc_pidpath(int pid)
- {
- // The path is a fixed buffer size, so use that and trim it after
- int result = 0;
- byte* pBuffer = stackalloc byte[PROC_PIDPATHINFO_MAXSIZE];
-
- // WARNING - Despite its name, don't try to pass in a smaller size than specified by PROC_PIDPATHINFO_MAXSIZE.
- // For some reason libproc returns -1 if you specify something that's NOT EQUAL to PROC_PIDPATHINFO_MAXSIZE
- // even if you declare your buffer to be smaller/larger than this size.
- result = proc_pidpath(pid, pBuffer, (uint)(PROC_PIDPATHINFO_MAXSIZE * sizeof(byte)));
- if (result <= 0)
- {
- throw new InvalidOperationException("Could not find procpath using libproc.");
- }
-
- // OS X uses UTF-8. The conversion may not strip off all trailing \0s so remove them here
- return System.Text.Encoding.UTF8.GetString(pBuffer, result);
- }
-
- public static string TryDetectConfigPath(int processId)
- {
- try
- {
- var path = proc_pidpath(processId);
- var candidateDir = Path.GetDirectoryName(path);
- var candidateName = Path.GetFileNameWithoutExtension(path);
- return Path.Combine(candidateDir, $"{candidateName}.eventpipeconfig");
- }
- catch (InvalidOperationException)
- {
- return null; // The pinvoke above may fail - return null in that case to handle error gracefully.
- }
- }
- }
-
- private static class Linux
- {
- public static string TryDetectConfigPath(int processId)
- {
- // Read procfs maps list
- var lines = File.ReadAllLines($"/proc/{processId}/maps");
-
- foreach (var line in lines)
- {
- try
- {
- var parser = new StringParser(line, separator: ' ', skipEmpty: true);
-
- // Skip the address range
- parser.MoveNext();
-
- var permissions = parser.MoveAndExtractNext();
-
- // The managed entry point is Read-Only, Non-Execute and Shared.
- if (!string.Equals(permissions, "r--s", StringComparison.Ordinal))
- {
- continue;
- }
-
- // Skip offset, dev, and inode
- parser.MoveNext();
- parser.MoveNext();
- parser.MoveNext();
-
- // Parse the path
- if (!parser.MoveNext())
- {
- continue;
- }
-
- var path = parser.ExtractCurrentToEnd();
- var candidateDir = Path.GetDirectoryName(path);
- var candidateName = Path.GetFileNameWithoutExtension(path);
- if (File.Exists(Path.Combine(candidateDir, $"{candidateName}.deps.json")))
- {
- return Path.Combine(candidateDir, $"{candidateName}.eventpipeconfig");
- }
- }
- catch (ArgumentNullException) { return null; }
- catch (InvalidDataException) { return null; }
- catch (InvalidOperationException) { return null; }
- }
- return null;
- }
- }
-
- private static class Windows
- {
- private static readonly HashSet<string> _knownNativeLibraries = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
- {
- // .NET Core Host
- "dotnet.exe",
- "hostfxr.dll",
- "hostpolicy.dll",
- "coreclr.dll",
-
- // Windows Native Libraries
- "ntdll.dll",
- "kernel32.dll",
- "kernelbase.dll",
- "apphelp.dll",
- "ucrtbase.dll",
- "advapi32.dll",
- "msvcrt.dll",
- "sechost.dll",
- "rpcrt4.dll",
- "ole32.dll",
- "combase.dll",
- "bcryptPrimitives.dll",
- "gdi32.dll",
- "gdi32full.dll",
- "msvcp_win.dll",
- "user32.dll",
- "win32u.dll",
- "oleaut32.dll",
- "shlwapi.dll",
- "version.dll",
- "bcrypt.dll",
- "imm32.dll",
- "kernel.appcore.dll",
- };
-
- public static string TryDetectConfigPath(int processId)
- {
- var process = Process.GetProcessById(processId);
-
- // Iterate over modules
- foreach (var module in process.Modules.Cast<ProcessModule>())
- {
- // Filter out things that aren't exes and dlls (useful on Unix/macOS to skip native libraries)
- var extension = Path.GetExtension(module.FileName);
- var name = Path.GetFileName(module.FileName);
- if (_managedExtensions.Contains(extension) && !_knownNativeLibraries.Contains(name) && !_platformAssemblies.Contains(name))
- {
- var candidateDir = Path.GetDirectoryName(module.FileName);
- var appName = Path.GetFileNameWithoutExtension(module.FileName);
-
- // Check for the deps.json file
- // TODO: Self-contained apps?
- if (File.Exists(Path.Combine(candidateDir, $"{appName}.deps.json")))
- {
- // This is an app!
- return Path.Combine(candidateDir, $"{appName}.eventpipeconfig");
- }
- }
- }
-
- return null;
- }
- }
- }
-}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.Tracing;
-using System.IO;
-using System.Threading.Tasks;
-using Microsoft.Diagnostics.Tracing;
-using Microsoft.Diagnostics.Tracing.Session;
-
-namespace Microsoft.Diagnostics.Tools.Collect
-{
- public class EtwCollector : EventCollector
- {
- private readonly CollectionConfiguration _config;
- private TraceEventSession _session;
-
- public EtwCollector(CollectionConfiguration config)
- {
- _config = config;
- }
-
- public override Task StartCollectingAsync()
- {
- // TODO: Allow a file name to be provided
- var outputFile = _config.ProcessId == null ?
- Path.Combine(_config.OutputPath, "dotnet-trace.etl") :
- Path.Combine(_config.OutputPath, $"dotnet-trace.{_config.ProcessId.Value}.etl");
- if (File.Exists(outputFile))
- {
- throw new InvalidOperationException($"Target file already exists: {outputFile}");
- }
- _session = new TraceEventSession("dotnet-trace", outputFile);
-
- if (_config.CircularMB is int circularMb)
- {
- _session.CircularBufferMB = circularMb;
- }
-
- var options = new TraceEventProviderOptions();
- if (_config.ProcessId is int pid)
- {
- options.ProcessIDFilter = new List<int>() { pid };
- }
-
- // Enable the providers requested
- foreach (var provider in _config.Providers)
- {
- _session.EnableProvider(provider.Provider, ConvertLevel(provider.Level), provider.Keywords, options);
- }
-
- return Task.CompletedTask;
- }
-
- private TraceEventLevel ConvertLevel(EventLevel level)
- {
- switch (level)
- {
- case EventLevel.Critical: return TraceEventLevel.Critical;
- case EventLevel.Error: return TraceEventLevel.Error;
- case EventLevel.Informational: return TraceEventLevel.Informational;
- case EventLevel.LogAlways: return TraceEventLevel.Always;
- case EventLevel.Verbose: return TraceEventLevel.Verbose;
- case EventLevel.Warning: return TraceEventLevel.Warning;
- default:
- throw new InvalidOperationException($"Unknown EventLevel: {level}");
- }
- }
-
- public override Task StopCollectingAsync()
- {
- _session.Dispose();
- return Task.CompletedTask;
- }
- }
-}
+++ /dev/null
-using System.Threading.Tasks;
-
-namespace Microsoft.Diagnostics.Tools.Collect
-{
- public abstract class EventCollector
- {
- public abstract Task StartCollectingAsync();
- public abstract Task StopCollectingAsync();
- }
-}
+++ /dev/null
-using System.IO;
-using System.Threading.Tasks;
-
-namespace Microsoft.Diagnostics.Tools.Collect
-{
- public class EventPipeCollector : EventCollector
- {
- private readonly CollectionConfiguration _config;
- private readonly string _configPath;
-
- public EventPipeCollector(CollectionConfiguration config, string configPath)
- {
- _config = config;
- _configPath = configPath;
- }
-
- public override Task StartCollectingAsync()
- {
- var configContent = _config.ToConfigString();
- return File.WriteAllTextAsync(_configPath, configContent);
- }
-
- public override Task StopCollectingAsync()
- {
- File.Delete(_configPath);
- return Task.CompletedTask;
- }
- }
-}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.Tracing;
-using System.Globalization;
-using System.Text;
-using Microsoft.Internal.Utilities;
-
-namespace Microsoft.Diagnostics.Tools.Collect
-{
- public class EventSpec
- {
- public string Provider { get; }
- public ulong Keywords { get; }
- public EventLevel Level { get; }
- public IDictionary<string, string> Parameters { get; }
-
- public EventSpec(string provider, ulong keywords, EventLevel level)
- : this(provider, keywords, level, new Dictionary<string, string>())
- {
- }
-
- public EventSpec(string provider, ulong keywords, EventLevel level, IDictionary<string, string> parameters)
- {
- Provider = provider;
- Keywords = keywords;
- Level = level;
- Parameters = parameters;
- }
-
- public static bool TryParse(string input, out EventSpec spec)
- {
- spec = null;
- var splat = input.Split(':');
-
- if (splat.Length == 0)
- {
- return false;
- }
-
- var provider = splat[0];
- var keywords = ulong.MaxValue;
- var level = EventLevel.Verbose;
- var parameters = new Dictionary<string, string>();
-
- if (splat.Length > 1)
- {
- if (!TryParseKeywords(splat[1], provider, out keywords))
- {
- return false;
- }
- }
-
- if (splat.Length > 2)
- {
- if (!TryParseLevel(splat[2], out level))
- {
- return false;
- }
- }
-
- if (splat.Length > 3)
- {
- if (!TryParseParameters(splat[3], parameters))
- {
- return false;
- }
- }
-
- spec = new EventSpec(provider, keywords, level, parameters);
- return true;
- }
-
- public string ToConfigString()
- {
- var config = $"{Provider}:0x{Keywords:X}:{(int)Level}";
- if(Parameters.Count > 0)
- {
- config += $":{FormatParameters(Parameters)}";
- }
- return config;
- }
-
- private static string FormatParameters(IDictionary<string, string> parameters)
- {
- var builder = new StringBuilder();
- foreach(var (key, value) in parameters)
- {
- builder.Append($"{key}={value};");
- }
-
- // Remove the trailing ';'
- builder.Length -= 1;
-
- return builder.ToString();
- }
-
- private static bool TryParseParameters(string input, IDictionary<string, string> parameters)
- {
- var splat = input.Split(';');
- foreach(var item in splat)
- {
- var splot = item.Split('=');
- if(splot.Length != 2)
- {
- return false;
- }
-
- parameters[splot[0]] = splot[1];
- }
-
- return true;
- }
-
- private static bool TryParseLevel(string input, out EventLevel level)
- {
- level = EventLevel.Verbose;
- if (int.TryParse(input, out var intLevel))
- {
- if (intLevel >= (int)EventLevel.LogAlways && intLevel <= (int)EventLevel.Verbose)
- {
- level = (EventLevel)intLevel;
- return true;
- }
- }
- else if (Enum.TryParse(input, ignoreCase: true, out level))
- {
- return true;
- }
- return false;
- }
-
- private static bool TryParseKeywords(string input, string provider, out ulong keywords)
- {
- if (string.Equals("*", input, StringComparison.Ordinal))
- {
- keywords = ulong.MaxValue;
- return true;
- }
- else if (input.StartsWith("0x"))
- {
- // Keywords
- if (ulong.TryParse(input, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out keywords))
- {
- return true;
- }
- }
- else if(KnownData.TryGetProvider(provider, out var knownProvider))
- {
- var splat = input.Split(',');
- keywords = 0;
- foreach(var item in splat)
- {
- if(knownProvider.Keywords.TryGetValue(item, out var knownKeyword))
- {
- keywords |= knownKeyword.Value;
- }
- else
- {
- throw new CommandLineException($"Keyword '{item}' is not a well-known keyword for '{provider}'");
- }
- }
- return true;
- }
-
- keywords = ulong.MaxValue;
- return false;
- }
- }
-}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.Tracing;
-using System.Linq;
-using Microsoft.Diagnostics.Tracing.Parsers;
-
-namespace Microsoft.Diagnostics.Tools.Collect
-{
- internal static class KnownData
- {
- private static readonly IReadOnlyDictionary<string, KnownProvider> _knownProviders =
- CreateKnownProviders().ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase);
-
- private static readonly IReadOnlyDictionary<string, CollectionProfile> _knownProfiles =
- CreateProfiles().ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase);
-
- private static IEnumerable<KnownProvider> CreateKnownProviders()
- {
- yield return CreateClrProvider();
- }
-
- private static IEnumerable<CollectionProfile> CreateProfiles()
- {
- yield return new CollectionProfile(
- CollectionProfile.DefaultProfileName,
- "A default set of event providers useful for diagosing problems in any .NET application.",
- new[] {
- new EventSpec(ClrTraceEventParser.ProviderName, (ulong)ClrTraceEventParser.Keywords.Default, EventLevel.Informational)
- },
- Array.Empty<LoggerSpec>());
-
- yield return new CollectionProfile(
- "AspNetCore",
- "A set of event providers useful for diagnosing problems in ASP.NET Core applications.",
- new[]
- {
- new EventSpec("Microsoft-AspNetCore-Hosting", ulong.MaxValue, EventLevel.Informational),
- },
- Array.Empty<LoggerSpec>());
-
- yield return new CollectionProfile(
- "Kestrel",
- "Detailed events for ASP.NET Core Kestrel",
- new[]
- {
- new EventSpec("Microsoft-AspNetCore-Server-Kestrel", ulong.MaxValue, EventLevel.Verbose),
- },
- Array.Empty<LoggerSpec>());
- }
-
- private static KnownProvider CreateClrProvider()
- {
- return new KnownProvider(
- ClrTraceEventParser.ProviderName,
- ClrTraceEventParser.ProviderGuid,
- ScanKeywordType(typeof(ClrTraceEventParser.Keywords)));
- }
-
- public static IReadOnlyList<CollectionProfile> GetAllProfiles() => _knownProfiles.Values.ToList();
- public static IReadOnlyList<KnownProvider> GetAllProviders() => _knownProviders.Values.ToList();
-
- public static bool TryGetProvider(string providerName, out KnownProvider provider) => _knownProviders.TryGetValue(providerName, out provider);
- public static bool TryGetProfile(string profileName, out CollectionProfile profile) => _knownProfiles.TryGetValue(profileName, out profile);
-
- private static IEnumerable<KnownKeyword> ScanKeywordType(Type keywordType)
- {
- var values = Enum.GetValues(keywordType).Cast<long>().ToList();
- var keywords = values.Distinct().Select(v => new KnownKeyword(Enum.GetName(keywordType, v), (ulong)v)).ToList();
- return keywords;
- }
- }
-
- internal class KnownProvider
- {
- public string Name { get; }
- public Guid Guid { get; }
- public IReadOnlyDictionary<string, KnownKeyword> Keywords { get; }
-
- public KnownProvider(string name, Guid guid, IEnumerable<KnownKeyword> keywords)
- {
- Name = name;
- Guid = guid;
- Keywords = keywords.ToDictionary(k => k.Name, StringComparer.OrdinalIgnoreCase);
- }
- }
-
- internal class KnownKeyword
- {
- public string Name { get; }
- public ulong Value { get; }
-
- public KnownKeyword(string name, ulong value)
- {
- Name = name;
- Value = value;
- }
- }
-}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-
-namespace Microsoft.Diagnostics.Tools.Collect
-{
- public class LoggerSpec
- {
- // Handles case normalization because key lookup is case-insensitive.
- private static readonly Dictionary<string, string> _levelMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
- {
- { "Trace", "Trace" },
- { "Debug", "Debug" },
- { "Information", "Information" },
- { "Warning", "Warning" },
- { "Error", "Error" },
- { "Critical", "Critical" },
- };
-
- public string Prefix { get; }
- public string Level { get; }
-
- public LoggerSpec(string prefix, string level)
- {
- Prefix = prefix;
- Level = level;
- }
-
- public static bool TryParse(string input, out LoggerSpec spec)
- {
- var splat = input.Split(':');
-
- var prefix = splat[0];
- string level = null;
- if (splat.Length > 1)
- {
- if (!_levelMap.TryGetValue(splat[1], out level))
- {
- spec = null;
- return false;
- }
- }
-
- spec = new LoggerSpec(prefix, level);
- return true;
- }
- }
-}
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices;
+// 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.CommandLine.Builder;
+using System.CommandLine.Invocation;
using System.Threading.Tasks;
-using McMaster.Extensions.CommandLineUtils;
-using Microsoft.Internal.Utilities;
-namespace Microsoft.Diagnostics.Tools.Collect
+namespace Microsoft.Diagnostics.Tools.Trace
{
- [Command(Name = "dotnet-trace", Description = "Collects Event Traces from .NET processes")]
- internal class Program
+ class Program
{
-
- [Option("-c|--config-path <CONFIG_PATH>", Description = "The path of the EventPipe config file to write, must be named [AppName].eventpipeconfig and be in the base directory for a managed app.")]
- public string ConfigPath { get; set; }
-
- [Option("--etw", Description = "Specify this flag to use ETW to collect events rather than using EventPipe (Windows only).")]
- public bool Etw { get; set; }
-
- [Required]
- [Option("-p|--process-id <PROCESS_ID>", Description = "Filter to only the process with the specified process ID.")]
- public int ProcessId { get; set; }
-
- [Option("-o|--output <OUTPUT_DIRECTORY>", Description = "The directory to write the trace to. Defaults to the current working directory.")]
- public string OutputDir { get; set; }
-
- [Option("--buffer <BUFFER_SIZE_IN_MB>", Description = "The size of the in-memory circular buffer in megabytes.")]
- public int? CircularMB { get; set; }
-
- [Option("--provider <PROVIDER_SPEC>", Description = "An EventPipe provider to enable. A string in the form '<provider name>:<keywords>:<level>:<parameters>'. Can be specified multiple times to enable multiple providers.")]
- public IList<string> Providers { get; set; }
-
- [Option("--profile <PROFILE_NAME>", Description = "A collection profile to enable. Use '--list-profiles' to get a list of all available profiles. Can be mixed with '--provider' and specified multiple times.")]
- public IList<string> Profiles { get; set; }
-
- [Option("--logger <LOGGER_NAME>", Description = "A Microsoft.Extensions.Logging logger to enable. A string in the form '<logger prefix>:<level>'. Can be specified multiple times to enable multiple loggers.")]
- public IList<string> Loggers { get; set; }
-
- [Option("--keywords-for <PROVIDER_NAME>", Description = "Gets a list of known keywords (if any) for the specified provider.")]
- public string KeywordsForProvider { get; set; }
-
- [Option("--list-profiles", Description = "Gets a list of predefined collection profiles.")]
- public bool ListProfiles { get; set; }
-
- [Option("--no-default", Description = "Don't enable the default profile.")]
- public bool NoDefault { get; set; }
-
- public async Task<int> OnExecuteAsync(IConsole console, CommandLineApplication app)
- {
- if (ListProfiles)
- {
- WriteProfileList(console.Out);
- return 0;
- }
-
- if (!string.IsNullOrEmpty(KeywordsForProvider))
- {
- return ExecuteKeywordsForAsync(console);
- }
-
- if(string.IsNullOrEmpty(ConfigPath))
- {
- ConfigPath = ConfigPathDetector.TryDetectConfigPath(ProcessId);
- if(string.IsNullOrEmpty(ConfigPath))
- {
- console.Error.WriteLine("Couldn't determine the path for the eventpipeconfig file from the process ID. Specify the '--config-path' option to provide it manually.");
- return 1;
- }
- console.WriteLine($"Detected config file path: {ConfigPath}");
- }
-
- var config = new CollectionConfiguration()
- {
- ProcessId = ProcessId,
- CircularMB = CircularMB,
- OutputPath = string.IsNullOrEmpty(OutputDir) ? Directory.GetCurrentDirectory() : OutputDir
- };
-
- if (Profiles != null && Profiles.Count > 0)
- {
- foreach (var profile in Profiles)
- {
- if (!KnownData.TryGetProfile(profile, out var collectionProfile))
- {
- console.Error.WriteLine($"Unknown profile name: '{profile}'. See 'dotnet-trace --list-profiles' to get a list of profiles.");
- return 1;
- }
- config.AddProfile(collectionProfile);
- }
- }
-
- if (Providers != null && Providers.Count > 0)
- {
- foreach (var provider in Providers)
- {
- if (!EventSpec.TryParse(provider, out var providerSpec))
- {
- console.Error.WriteLine($"Invalid provider specification: '{provider}'. See 'dotnet-trace --help' for more information.");
- return 1;
- }
- config.Providers.Add(providerSpec);
- }
- }
-
- if (Loggers != null && Loggers.Count > 0)
- {
- foreach (var logger in Loggers)
- {
- if (!LoggerSpec.TryParse(logger, out var loggerSpec))
- {
- console.Error.WriteLine($"Invalid logger specification: '{logger}'. See 'dotnet-trace --help' for more information.");
- return 1;
- }
- config.Loggers.Add(loggerSpec);
- }
- }
-
- if (!NoDefault)
- {
- // Enable the default profile if nothing is specified
- if (!KnownData.TryGetProfile(CollectionProfile.DefaultProfileName, out var defaultProfile))
- {
- console.Error.WriteLine("No providers or profiles were specified and there is no default profile available.");
- return 1;
- }
- config.AddProfile(defaultProfile);
- }
-
- if (!TryCreateCollector(console, config, out var collector))
- {
- return 1;
- }
-
- // Write the config file contents
- await collector.StartCollectingAsync();
- console.WriteLine("Tracing has started. Press Ctrl-C to stop.");
-
- await console.WaitForCtrlCAsync();
-
- await collector.StopCollectingAsync();
- console.WriteLine($"Tracing stopped. Trace files written to {config.OutputPath}");
-
- return 0;
- }
-
- private static void WriteProfileList(TextWriter console)
- {
- var profiles = KnownData.GetAllProfiles();
- var maxNameLength = profiles.Max(p => p.Name.Length);
- console.WriteLine("Available profiles:");
- foreach (var profile in profiles)
- {
- console.WriteLine($"* {profile.Name.PadRight(maxNameLength)} {profile.Description}");
- }
- }
-
- private int ExecuteKeywordsForAsync(IConsole console)
- {
- if (KnownData.TryGetProvider(KeywordsForProvider, out var provider))
- {
- console.WriteLine($"Known keywords for {provider.Name} ({provider.Guid}):");
- foreach (var keyword in provider.Keywords.Values)
- {
- console.WriteLine($"* 0x{keyword.Value:x16} {keyword.Name}");
- }
- return 0;
- }
- else
- {
- console.WriteLine($"There are no known keywords for {KeywordsForProvider}.");
- return 1;
- }
- }
-
- private bool TryCreateCollector(IConsole console, CollectionConfiguration config, out EventCollector collector)
- {
- collector = null;
-
- if (Etw)
- {
- if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- console.Error.WriteLine("Error: ETW-based collection is only supported on Windows.");
- return false;
- }
-
- if (!string.IsNullOrEmpty(ConfigPath))
- {
- console.Error.WriteLine("WARNING: The '-c' option is ignored when using ETW-based collection.");
- }
- collector = new EtwCollector(config);
- return true;
- }
- else
- {
- if (File.Exists(ConfigPath))
- {
- console.Error.WriteLine("Config file already exists, tracing is already underway by a different consumer.");
- return false;
- }
-
- collector = new EventPipeCollector(config, ConfigPath);
- return true;
- }
- }
-
- private static int Main(string[] args)
- {
- DebugUtil.WaitForDebuggerIfRequested(ref args);
-
- try
- {
- var app = new CommandLineApplication<Program>();
- app.Conventions.UseDefaultConventions();
- app.ExtendedHelpText = GetExtendedHelp();
- return app.Execute(args);
- }
- catch (PlatformNotSupportedException ex)
- {
- Console.Error.WriteLine(ex.Message);
- return 1;
- }
- catch (CommandLineException clex)
- {
- Console.Error.WriteLine(clex.Message);
- return 1;
- }
- catch (OperationCanceledException)
- {
- return 0;
- }
- }
-
- private static string GetExtendedHelp()
+ public static Task<int> Main(string[] args)
{
- using (var writer = new StringWriter())
- {
- writer.WriteLine();
- writer.WriteLine("Profiles");
- writer.WriteLine(" Profiles are sets of well-defined provider configurations that provide useful information.");
- writer.WriteLine();
- WriteProfileList(writer);
- writer.WriteLine();
- writer.WriteLine("Specifying Loggers:");
- writer.WriteLine(" Use one of the following formats to specify a logger in '--logger'");
- writer.WriteLine(" * - Enable all messages at all levels from all loggers.");
- writer.WriteLine(" *:<level> - Enable messages at the specified '<level>' or higher from all loggers.");
- writer.WriteLine(" <loggerPrefix> - Enable all messages at all levels from all loggers starting with '<loggerPrefix>'.");
- writer.WriteLine(" <loggerPrefix>:<level> - Enable messages at the specified '<level>' or higher from all loggers starting with '<loggerPrefix>'.");
- writer.WriteLine();
- writer.WriteLine(" '<loggerPrefix>' is the prefix for a logger to enable. For example 'Microsoft.AspNetCore' to enable all ASP.NET Core loggers.");
- writer.WriteLine(" '<level>' can be one of: Critical, Error, Warning, Informational, Debug, or Trace.");
- writer.WriteLine();
- writer.WriteLine("Specifying Providers:");
- writer.WriteLine(" Use one of the following formats to specify a provider in '--provider'");
- writer.WriteLine(" <providerName> - Enable all events at all levels for the provider.");
- writer.WriteLine(" <providerName>:<keywords> - Enable events matching the specified keywords for the specified provider.");
- writer.WriteLine(" <providerName>:<keywords>:<level> - Enable events matching the specified keywords, at the specified level for the specified provider.");
- writer.WriteLine(" <providerName>:<keywords>:<level>:<parameters> - Enable events matching the specified keywords, at the specified level for the specified provider and provide key-value parameters.");
- writer.WriteLine();
- writer.WriteLine(" '<provider>' must be the name of the EventSource.");
- writer.WriteLine(" '<level>' can be one of: Critical (1), Error (2), Warning (3), Informational (4), Verbose (5). Either the name or number can be specified.");
- writer.WriteLine(" '<keywords>' is one of the following:");
- writer.WriteLine(" A '*' character, indicating ALL keywords should be enabled (this can be very costly for some providers!)");
- writer.WriteLine(" A comma-separated list of known keywords for a provider (use 'dotnet trace collect --keywords-for [providerName]' to get a list of known keywords for a provider)");
- writer.WriteLine(" A 64-bit hexadecimal number, starting with '0x' indicating the keywords to enable");
- writer.WriteLine(" '<parameters>' is an optional list of key-value parameters to provide to the EventPipe provider. The expected values depend on the provider you are enabling.");
- writer.WriteLine(" This should be a list of key-value pairs, in the form: '<key1>=<value1>;<key2>=<value2>;...'. Note that some shells, such as PowerShell, require that you");
- writer.WriteLine(" quote or escape the ';' character.");
- return writer.GetStringBuilder().ToString();
- }
+ var parser = new CommandLineBuilder()
+#if DEBUG
+ .AddCommand(PortsCommandHandler.ActivePortsCommand())
+ .AddCommand(ProvidersCommandHandler.KnownProvidersCommand())
+#endif
+ .AddCommand(StreamCommandHandler.StartCommand())
+ .AddCommand(StartCommandHandler.StartCommand())
+ .AddCommand(StopCommandHandler.StopCommand())
+ .UseDefaults()
+ .Build();
+
+ return parser.InvokeAsync(args);
}
}
}
+++ /dev/null
-// Borrowed from https://github.com/dotnet/corefx/blob/b2f960abe1d8690be9d68dd9b56ea7636fb4a38b/src/Common/src/System/IO/StringParser.cs
-
-// 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.Diagnostics;
-
-namespace System.IO
-{
- /// <summary>
- /// Provides a string parser that may be used instead of String.Split
- /// to avoid unnecessary string and array allocations.
- /// </summary>
- internal struct StringParser
- {
- /// <summary>The string being parsed.</summary>
- private readonly string _buffer;
-
- /// <summary>The separator character used to separate subcomponents of the larger string.</summary>
- private readonly char _separator;
-
- /// <summary>true if empty subcomponents should be skipped; false to treat them as valid entries.</summary>
- private readonly bool _skipEmpty;
-
- /// <summary>The starting index from which to parse the current entry.</summary>
- private int _startIndex;
-
- /// <summary>The ending index that represents the next index after the last character that's part of the current entry.</summary>
- private int _endIndex;
-
- /// <summary>Initialize the StringParser.</summary>
- /// <param name="buffer">The string to parse.</param>
- /// <param name="separator">The separator character used to separate subcomponents of <paramref name="buffer"/>.</param>
- /// <param name="skipEmpty">true if empty subcomponents should be skipped; false to treat them as valid entries. Defaults to false.</param>
- public StringParser(string buffer, char separator, bool skipEmpty = false)
- {
- if (buffer == null)
- {
- throw new ArgumentNullException(nameof(buffer));
- }
- _buffer = buffer;
- _separator = separator;
- _skipEmpty = skipEmpty;
- _startIndex = -1;
- _endIndex = -1;
- }
-
- /// <summary>Moves to the next component of the string.</summary>
- /// <returns>true if there is a next component to be parsed; otherwise, false.</returns>
- public bool MoveNext()
- {
- if (_buffer == null)
- {
- throw new InvalidOperationException();
- }
-
- while (true)
- {
- if (_endIndex >= _buffer.Length)
- {
- _startIndex = _endIndex;
- return false;
- }
-
- int nextSeparator = _buffer.IndexOf(_separator, _endIndex + 1);
- _startIndex = _endIndex + 1;
- _endIndex = nextSeparator >= 0 ? nextSeparator : _buffer.Length;
-
- if (!_skipEmpty || _endIndex >= _startIndex + 1)
- {
- return true;
- }
- }
- }
-
- /// <summary>
- /// Moves to the next component of the string. If there isn't one, it throws an exception.
- /// </summary>
- public void MoveNextOrFail()
- {
- if (!MoveNext())
- {
- ThrowForInvalidData();
- }
- }
-
- /// <summary>
- /// Moves to the next component of the string and returns it as a string.
- /// </summary>
- /// <returns></returns>
- public string MoveAndExtractNext()
- {
- MoveNextOrFail();
- return _buffer.Substring(_startIndex, _endIndex - _startIndex);
- }
-
- /// <summary>
- /// Moves to the next component of the string, which must be enclosed in the only set of top-level parentheses
- /// in the string. The extracted value will be everything between (not including) those parentheses.
- /// </summary>
- /// <returns></returns>
- public string MoveAndExtractNextInOuterParens()
- {
- // Move to the next position
- MoveNextOrFail();
-
- // After doing so, we should be sitting at a the opening paren.
- if (_buffer[_startIndex] != '(')
- {
- ThrowForInvalidData();
- }
-
- // Since we only allow for one top-level set of parentheses, find the last
- // parenthesis in the string; it's paired with the opening one we just found.
- int lastParen = _buffer.LastIndexOf(')');
- if (lastParen == -1 || lastParen < _startIndex)
- {
- ThrowForInvalidData();
- }
-
- // Extract the contents of the parens, then move our ending position to be after the paren
- string result = _buffer.Substring(_startIndex + 1, lastParen - _startIndex - 1);
- _endIndex = lastParen + 1;
-
- return result;
- }
-
- /// <summary>
- /// Gets the current subcomponent of the string as a string.
- /// </summary>
- public string ExtractCurrent()
- {
- if (_buffer == null || _startIndex == -1)
- {
- throw new InvalidOperationException();
- }
- return _buffer.Substring(_startIndex, _endIndex - _startIndex);
- }
-
- /// <summary>Moves to the next component and parses it as an Int32.</summary>
- public unsafe int ParseNextInt32()
- {
- MoveNextOrFail();
-
- bool negative = false;
- int result = 0;
-
- fixed (char* bufferPtr = _buffer)
- {
- char* p = bufferPtr + _startIndex;
- char* end = bufferPtr + _endIndex;
-
- if (p == end)
- {
- ThrowForInvalidData();
- }
-
- if (*p == '-')
- {
- negative = true;
- p++;
- if (p == end)
- {
- ThrowForInvalidData();
- }
- }
-
- while (p != end)
- {
- int d = *p - '0';
- if (d < 0 || d > 9)
- {
- ThrowForInvalidData();
- }
- result = negative ? checked((result * 10) - d) : checked((result * 10) + d);
-
- p++;
- }
- }
-
- Debug.Assert(result == int.Parse(ExtractCurrent()), "Expected manually parsed result to match Parse result");
- return result;
- }
-
- /// <summary>Moves to the next component and parses it as an Int64.</summary>
- public unsafe long ParseNextInt64()
- {
- MoveNextOrFail();
-
- bool negative = false;
- long result = 0;
-
- fixed (char* bufferPtr = _buffer)
- {
- char* p = bufferPtr + _startIndex;
- char* end = bufferPtr + _endIndex;
-
- if (p == end)
- {
- ThrowForInvalidData();
- }
-
- if (*p == '-')
- {
- negative = true;
- p++;
- if (p == end)
- {
- ThrowForInvalidData();
- }
- }
-
- while (p != end)
- {
- int d = *p - '0';
- if (d < 0 || d > 9)
- {
- ThrowForInvalidData();
- }
- result = negative ? checked((result * 10) - d) : checked((result * 10) + d);
-
- p++;
- }
- }
-
- Debug.Assert(result == long.Parse(ExtractCurrent()), "Expected manually parsed result to match Parse result");
- return result;
- }
-
- /// <summary>Moves to the next component and parses it as a UInt32.</summary>
- public unsafe uint ParseNextUInt32()
- {
- MoveNextOrFail();
- if (_startIndex == _endIndex)
- {
- ThrowForInvalidData();
- }
-
- uint result = 0;
- fixed (char* bufferPtr = _buffer)
- {
- char* p = bufferPtr + _startIndex;
- char* end = bufferPtr + _endIndex;
- while (p != end)
- {
- int d = *p - '0';
- if (d < 0 || d > 9)
- {
- ThrowForInvalidData();
- }
- result = (uint)checked((result * 10) + d);
-
- p++;
- }
- }
-
- Debug.Assert(result == uint.Parse(ExtractCurrent()), "Expected manually parsed result to match Parse result");
- return result;
- }
-
- /// <summary>Moves to the next component and parses it as a UInt64.</summary>
- public unsafe ulong ParseNextUInt64()
- {
- MoveNextOrFail();
-
- ulong result = 0;
- fixed (char* bufferPtr = _buffer)
- {
- char* p = bufferPtr + _startIndex;
- char* end = bufferPtr + _endIndex;
- while (p != end)
- {
- int d = *p - '0';
- if (d < 0 || d > 9)
- {
- ThrowForInvalidData();
- }
- result = checked((result * 10ul) + (ulong)d);
-
- p++;
- }
- }
-
- Debug.Assert(result == ulong.Parse(ExtractCurrent()), "Expected manually parsed result to match Parse result");
- return result;
- }
-
- /// <summary>Moves to the next component and parses it as a Char.</summary>
- public char ParseNextChar()
- {
- MoveNextOrFail();
-
- if (_endIndex - _startIndex != 1)
- {
- ThrowForInvalidData();
- }
- char result = _buffer[_startIndex];
-
- Debug.Assert(result == char.Parse(ExtractCurrent()), "Expected manually parsed result to match Parse result");
- return result;
- }
-
- internal delegate T ParseRawFunc<T>(string buffer, ref int startIndex, ref int endIndex);
-
- /// <summary>
- /// Moves to the next component and hands the raw buffer and indexing data to a selector function
- /// that can validate and return the appropriate data from the component.
- /// </summary>
- internal T ParseRaw<T>(ParseRawFunc<T> selector)
- {
- MoveNextOrFail();
- return selector(_buffer, ref _startIndex, ref _endIndex);
- }
-
- /// <summary>
- /// Gets the current subcomponent and all remaining components of the string as a string.
- /// </summary>
- public string ExtractCurrentToEnd()
- {
- if (_buffer == null || _startIndex == -1)
- {
- throw new InvalidOperationException();
- }
- return _buffer.Substring(_startIndex);
- }
-
- /// <summary>Throws unconditionally for invalid data.</summary>
- private static void ThrowForInvalidData()
- {
- throw new InvalidDataException();
- }
- }
-}
--- /dev/null
+@if not defined _echo echo off
+cls
+
+dotnet.exe restore "%~dp0dotnet-trace.csproj" --packages "%~dp0..\..\..\artifacts\packages" || (
+ echo [ERROR] Failed to restore.
+ exit /b 1
+)
+
+for %%c in (Debug Release) do (
+ dotnet.exe build "%~dp0dotnet-trace.csproj" -c %%c --no-restore || (
+ echo [ERROR] Failed to build %%c.
+ exit /b 1
+ )
+)
<PropertyGroup>
<OutputType>Exe</OutputType>
-
- <!-- Target .NET Core 2.1 so it will run on LTS -->
<TargetFramework>netcoreapp2.1</TargetFramework>
+ <RootNamespace>Microsoft.Diagnostics.Tools.Trace</RootNamespace>
+ <IsPackable>False</IsPackable>
+ <PackAsTool>False</PackAsTool>
+ </PropertyGroup>
- <RootNamespace>Microsoft.Diagnostics.Tools.Collect</RootNamespace>
- <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-
- <!-- Don't pack until ship engineering is done. Currently causing the official job to fail.
- <IsPackable>true</IsPackable>
- <PackAsTool>true</PackAsTool>
- -->
+ <PropertyGroup>
+ <UseSharedCompilation>False</UseSharedCompilation>
+ <LangVersion>Latest</LangVersion>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>True</TreatWarningsAsErrors>
+ <GenerateDocumentationFile>False</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
- <Compile Include="..\Common\CommandLineException.cs" Link="CommandLineException.cs" />
- <Compile Include="..\Common\ConsoleCancellation.cs" Link="ConsoleCancellation.cs" />
- <Compile Include="..\Common\DebugUtil.cs" Link="DebugUtil.cs" />
+ <PackageReference Include="System.CommandLine.Experimental" Version="0.2.0-alpha.19167.2" />
</ItemGroup>
<ItemGroup>
- <PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.2.5" />
- <PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="2.0.30" />
+ <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.Tools.RuntimeClient\Microsoft.Diagnostics.Tools.RuntimeClient.csproj" />
</ItemGroup>
+ <PropertyGroup>
+ <!--
+ Suppress warnings for using preview SDK:
+ You are working with a preview version of the .NET Core SDK.
+ You can define the SDK version via a global.json file in the current project.
+ More at https://go.microsoft.com/fwlink/?linkid=869452
+ -->
+ <SuppressNETCoreSdkPreviewMessage>True</SuppressNETCoreSdkPreviewMessage>
+ </PropertyGroup>
+
</Project>
\ No newline at end of file
--- /dev/null
+@if not defined _echo echo off
+
+call :run_command dotnet.exe run -c Debug --no-restore --no-build -- %*
+exit /b %ERRORLEVEL%
+
+:run_command
+ echo/%USERNAME%@%COMPUTERNAME% "%CD%"
+ echo/[%DATE% %TIME%] $ %*
+ echo/
+ call %*
+ exit /b %ERRORLEVEL%