From: José Rivero Date: Wed, 27 Mar 2019 23:19:20 +0000 (-0700) Subject: [dotnet-trace] Updated dotnet-trace to use IPC to communicate with the runtime (... X-Git-Tag: submit/tizen/20190813.035844~44 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=10450a03f5a24068c6e73abdc63916db6ed7c4a1;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git [dotnet-trace] Updated dotnet-trace to use IPC to communicate with the runtime (#143) * Microsoft.Diagnostics.Tools.Collect -> Microsoft.Diagnostics.Tools.Trace * Updated dotnet-trace with the following options: start - Starts tracing stop - Stops tracing * Ignore netperf files. * Naive stream. * Helper scripts. * Update the `diagnostics.sln` file. * Small changes in API --- diff --git a/.gitignore b/.gitignore index 5f458400c..073132444 100644 --- a/.gitignore +++ b/.gitignore @@ -114,3 +114,5 @@ $RECYCLE.BIN/ # SOS stress log StressLog.txt +# EventPipe files +*.netperf diff --git a/diagnostics.sln b/diagnostics.sln index d5da8fd2a..73cf4d207 100644 --- a/diagnostics.sln +++ b/diagnostics.sln @@ -45,6 +45,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostic.Repl", 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 @@ -343,6 +345,46 @@ Global {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 @@ -679,46 +721,46 @@ Global {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 @@ -733,6 +775,7 @@ Global {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} @@ -742,7 +785,8 @@ Global {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} diff --git a/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/DiagnosticMessageType.cs b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/DiagnosticMessageType.cs new file mode 100644 index 000000000..aae781108 --- /dev/null +++ b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/DiagnosticMessageType.cs @@ -0,0 +1,15 @@ +// 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, + } +} diff --git a/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/EventPipeClient.cs b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/EventPipeClient.cs new file mode 100644 index 000000000..636259fc4 --- /dev/null +++ b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/EventPipeClient.cs @@ -0,0 +1,175 @@ +// 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 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; + } + + } + } +} diff --git a/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/MessageHeader.cs b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/MessageHeader.cs new file mode 100644 index 000000000..14ab6e630 --- /dev/null +++ b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/MessageHeader.cs @@ -0,0 +1,16 @@ +// 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; + } +} diff --git a/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/Provider.cs b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/Provider.cs new file mode 100644 index 000000000..8995a8c32 --- /dev/null +++ b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/Provider.cs @@ -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 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; } + } +} diff --git a/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/SessionConfiguration.cs b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/SessionConfiguration.cs new file mode 100644 index 000000000..ad7c3295e --- /dev/null +++ b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/SessionConfiguration.cs @@ -0,0 +1,38 @@ +// 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 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(providers); + } + + public uint CircularBufferSizeInMB { get; } + + public ulong MultiFileTraceLengthInSeconds { get; } + + public string OutputPath => _outputPath.FullName; + + public IEnumerable Providers => _providers; + + private readonly FileInfo _outputPath; + private readonly List _providers; + } +} diff --git a/src/Microsoft.Diagnostics.Tools.RuntimeClient/Extensions.cs b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Extensions.cs new file mode 100644 index 000000000..4f581ec35 --- /dev/null +++ b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Extensions.cs @@ -0,0 +1,55 @@ +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 + } +} diff --git a/src/Microsoft.Diagnostics.Tools.RuntimeClient/Microsoft.Diagnostics.Tools.RuntimeClient.csproj b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Microsoft.Diagnostics.Tools.RuntimeClient.csproj new file mode 100644 index 000000000..0aa447482 --- /dev/null +++ b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Microsoft.Diagnostics.Tools.RuntimeClient.csproj @@ -0,0 +1,32 @@ + + + + Library + netcoreapp2.1 + Microsoft.Diagnostics.Tools.RuntimeClient + False + + + + False + Latest + 4 + True + True + + + + + + + + + True + + + diff --git a/src/Tools/dotnet-trace/CollectionConfiguration.cs b/src/Tools/dotnet-trace/CollectionConfiguration.cs deleted file mode 100644 index e7af348f8..000000000 --- a/src/Tools/dotnet-trace/CollectionConfiguration.cs +++ /dev/null @@ -1,85 +0,0 @@ -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 Providers { get; set; } = new List(); - public IList Loggers { get; set; } = new List(); - - 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 providers) => string.Join(",", providers.Select(s => s.ToConfigString())); - - private IEnumerable GenerateLoggerSpec(IList 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() { - { "FilterSpecs", filterSpec.ToString() } - }); - } - } - } -} diff --git a/src/Tools/dotnet-trace/CollectionProfile.cs b/src/Tools/dotnet-trace/CollectionProfile.cs deleted file mode 100644 index 0e3f03ed7..000000000 --- a/src/Tools/dotnet-trace/CollectionProfile.cs +++ /dev/null @@ -1,23 +0,0 @@ -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 EventSpecs { get; } - public IReadOnlyList LoggerSpecs { get; } - - public CollectionProfile(string name, string description, IEnumerable eventSpecs, IEnumerable loggerSpecs) - { - Name = name; - Description = description; - EventSpecs = eventSpecs.ToList(); - LoggerSpecs = loggerSpecs.ToList(); - } - } -} diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/PortsCommandHandler.cs b/src/Tools/dotnet-trace/CommandLine/Commands/PortsCommandHandler.cs new file mode 100644 index 000000000..b8d5bd072 --- /dev/null +++ b/src/Tools/dotnet-trace/CommandLine/Commands/PortsCommandHandler.cs @@ -0,0 +1,39 @@ +// 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 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(GetActivePorts), + isHidden: true); + } +} diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/ProvidersCommandHandler.cs b/src/Tools/dotnet-trace/CommandLine/Commands/ProvidersCommandHandler.cs new file mode 100644 index 000000000..c48d3c08d --- /dev/null +++ b/src/Tools/dotnet-trace/CommandLine/Commands/ProvidersCommandHandler.cs @@ -0,0 +1,52 @@ +// 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 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(KnownProviders), + isHidden: true); + + private static IEnumerable 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"), + }; + } +} diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/StartCommandHandler.cs b/src/Tools/dotnet-trace/CommandLine/Commands/StartCommandHandler.cs new file mode 100644 index 000000000..22ae309b2 --- /dev/null +++ b/src/Tools/dotnet-trace/CommandLine/Commands/StartCommandHandler.cs @@ -0,0 +1,85 @@ +// 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 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(Start)); + + private static Option OutputPathOption() => + new Option( + new[] { "-o", "--output" }, + @"The file name to log events to.", + new Argument { Name = "filename" }); + + private static Option CircularBufferOption() => + new Option( + new[] { "--buffersize" }, + @"Sets the size of the in-memory circular buffer in megabytes.", + new Argument { 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 { 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 { Name = "MultiFileSec" }); + + private static IEnumerable ToProviders(string providers) + { + if (string.IsNullOrWhiteSpace(providers)) + throw new ArgumentNullException(nameof(providers)); + return providers.Split(',') + .Select(Provider.ToProvider); + } + } +} diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/StopCommandHandler.cs b/src/Tools/dotnet-trace/CommandLine/Commands/StopCommandHandler.cs new file mode 100644 index 000000000..230e9fced --- /dev/null +++ b/src/Tools/dotnet-trace/CommandLine/Commands/StopCommandHandler.cs @@ -0,0 +1,47 @@ +// 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 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(Stop)); + + private static Option SessionIdOption() => + new Option( + new[] { "--session-id" }, + @"Session Id being recorded.", + new Argument { Name = "SessionId" }); + } +} diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/StreamCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/StreamCommand.cs new file mode 100644 index 000000000..d673195c9 --- /dev/null +++ b/src/Tools/dotnet-trace/CommandLine/Commands/StreamCommand.cs @@ -0,0 +1,84 @@ +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 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(Stream)); + + private static Option CircularBufferOption() => + new Option( + new[] { "--buffersize" }, + @"Sets the size of the in-memory circular buffer in megabytes.", + new Argument { 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 { Name = "Providers" }); // TODO: Can we specify an actual type? + + private static IEnumerable ToProviders(string providers) + { + if (string.IsNullOrWhiteSpace(providers)) + throw new ArgumentNullException(nameof(providers)); + return providers.Split(',') + .Select(Provider.ToProvider); + } + } +} diff --git a/src/Tools/dotnet-trace/CommandLine/Options/CommonOptions.cs b/src/Tools/dotnet-trace/CommandLine/Options/CommonOptions.cs new file mode 100644 index 000000000..e0c5475e5 --- /dev/null +++ b/src/Tools/dotnet-trace/CommandLine/Options/CommonOptions.cs @@ -0,0 +1,17 @@ +// 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 { Name = "ProcessId" }); + } +} diff --git a/src/Tools/dotnet-trace/ConfigPathDetector.cs b/src/Tools/dotnet-trace/ConfigPathDetector.cs deleted file mode 100644 index 050f54602..000000000 --- a/src/Tools/dotnet-trace/ConfigPathDetector.cs +++ /dev/null @@ -1,212 +0,0 @@ -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 _managedExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) { ".exe", ".dll" }; - - // Known .NET Platform Assemblies - private static readonly HashSet _platformAssemblies = new HashSet(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; - - /// - /// Gets the full path to the executable file identified by the specified PID - /// - /// The PID of the running process - /// A pointer to an allocated block of memory that will be filled with the process path - /// The size of the buffer, should be PROC_PIDPATHINFO_MAXSIZE - /// Returns the length of the path returned on success - [DllImport("libproc.dylib", SetLastError = true)] - private static extern unsafe int proc_pidpath( - int pid, - byte* buffer, - uint bufferSize); - - /// - /// Gets the full path to the executable file identified by the specified PID - /// - /// The PID of the running process - /// Returns the full path to the process executable - 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 _knownNativeLibraries = new HashSet(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()) - { - // 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; - } - } - } -} diff --git a/src/Tools/dotnet-trace/EtwCollector.cs b/src/Tools/dotnet-trace/EtwCollector.cs deleted file mode 100644 index d896b583e..000000000 --- a/src/Tools/dotnet-trace/EtwCollector.cs +++ /dev/null @@ -1,74 +0,0 @@ -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() { 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; - } - } -} diff --git a/src/Tools/dotnet-trace/EventCollector.cs b/src/Tools/dotnet-trace/EventCollector.cs deleted file mode 100644 index 640330611..000000000 --- a/src/Tools/dotnet-trace/EventCollector.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Threading.Tasks; - -namespace Microsoft.Diagnostics.Tools.Collect -{ - public abstract class EventCollector - { - public abstract Task StartCollectingAsync(); - public abstract Task StopCollectingAsync(); - } -} diff --git a/src/Tools/dotnet-trace/EventPipeCollector.cs b/src/Tools/dotnet-trace/EventPipeCollector.cs deleted file mode 100644 index 47c05ea86..000000000 --- a/src/Tools/dotnet-trace/EventPipeCollector.cs +++ /dev/null @@ -1,29 +0,0 @@ -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; - } - } -} diff --git a/src/Tools/dotnet-trace/EventSpec.cs b/src/Tools/dotnet-trace/EventSpec.cs deleted file mode 100644 index e384e7090..000000000 --- a/src/Tools/dotnet-trace/EventSpec.cs +++ /dev/null @@ -1,169 +0,0 @@ -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 Parameters { get; } - - public EventSpec(string provider, ulong keywords, EventLevel level) - : this(provider, keywords, level, new Dictionary()) - { - } - - public EventSpec(string provider, ulong keywords, EventLevel level, IDictionary 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(); - - 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 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 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; - } - } -} diff --git a/src/Tools/dotnet-trace/KnownData.cs b/src/Tools/dotnet-trace/KnownData.cs deleted file mode 100644 index 1ac0a40be..000000000 --- a/src/Tools/dotnet-trace/KnownData.cs +++ /dev/null @@ -1,98 +0,0 @@ -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 _knownProviders = - CreateKnownProviders().ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase); - - private static readonly IReadOnlyDictionary _knownProfiles = - CreateProfiles().ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase); - - private static IEnumerable CreateKnownProviders() - { - yield return CreateClrProvider(); - } - - private static IEnumerable 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()); - - 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()); - - 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()); - } - - private static KnownProvider CreateClrProvider() - { - return new KnownProvider( - ClrTraceEventParser.ProviderName, - ClrTraceEventParser.ProviderGuid, - ScanKeywordType(typeof(ClrTraceEventParser.Keywords))); - } - - public static IReadOnlyList GetAllProfiles() => _knownProfiles.Values.ToList(); - public static IReadOnlyList 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 ScanKeywordType(Type keywordType) - { - var values = Enum.GetValues(keywordType).Cast().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 Keywords { get; } - - public KnownProvider(string name, Guid guid, IEnumerable 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; - } - } -} diff --git a/src/Tools/dotnet-trace/LoggerSpec.cs b/src/Tools/dotnet-trace/LoggerSpec.cs deleted file mode 100644 index 4e1d1e8fc..000000000 --- a/src/Tools/dotnet-trace/LoggerSpec.cs +++ /dev/null @@ -1,47 +0,0 @@ -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 _levelMap = new Dictionary(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; - } - } -} diff --git a/src/Tools/dotnet-trace/Program.cs b/src/Tools/dotnet-trace/Program.cs index 076e05722..9bd16f1a3 100644 --- a/src/Tools/dotnet-trace/Program.cs +++ b/src/Tools/dotnet-trace/Program.cs @@ -1,278 +1,29 @@ -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 ", 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 ", Description = "Filter to only the process with the specified process ID.")] - public int ProcessId { get; set; } - - [Option("-o|--output ", Description = "The directory to write the trace to. Defaults to the current working directory.")] - public string OutputDir { get; set; } - - [Option("--buffer ", Description = "The size of the in-memory circular buffer in megabytes.")] - public int? CircularMB { get; set; } - - [Option("--provider ", Description = "An EventPipe provider to enable. A string in the form ':::'. Can be specified multiple times to enable multiple providers.")] - public IList Providers { get; set; } - - [Option("--profile ", 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 Profiles { get; set; } - - [Option("--logger ", Description = "A Microsoft.Extensions.Logging logger to enable. A string in the form ':'. Can be specified multiple times to enable multiple loggers.")] - public IList Loggers { get; set; } - - [Option("--keywords-for ", 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 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(); - 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 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(" *: - Enable messages at the specified '' or higher from all loggers."); - writer.WriteLine(" - Enable all messages at all levels from all loggers starting with ''."); - writer.WriteLine(" : - Enable messages at the specified '' or higher from all loggers starting with ''."); - writer.WriteLine(); - writer.WriteLine(" '' is the prefix for a logger to enable. For example 'Microsoft.AspNetCore' to enable all ASP.NET Core loggers."); - writer.WriteLine(" '' 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(" - Enable all events at all levels for the provider."); - writer.WriteLine(" : - Enable events matching the specified keywords for the specified provider."); - writer.WriteLine(" :: - Enable events matching the specified keywords, at the specified level for the specified provider."); - writer.WriteLine(" ::: - Enable events matching the specified keywords, at the specified level for the specified provider and provide key-value parameters."); - writer.WriteLine(); - writer.WriteLine(" '' must be the name of the EventSource."); - writer.WriteLine(" '' can be one of: Critical (1), Error (2), Warning (3), Informational (4), Verbose (5). Either the name or number can be specified."); - writer.WriteLine(" '' 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(" '' 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: '=;=;...'. 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); } } } diff --git a/src/Tools/dotnet-trace/StringParser.cs b/src/Tools/dotnet-trace/StringParser.cs deleted file mode 100644 index 5c3c643cc..000000000 --- a/src/Tools/dotnet-trace/StringParser.cs +++ /dev/null @@ -1,334 +0,0 @@ -// 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 -{ - /// - /// Provides a string parser that may be used instead of String.Split - /// to avoid unnecessary string and array allocations. - /// - internal struct StringParser - { - /// The string being parsed. - private readonly string _buffer; - - /// The separator character used to separate subcomponents of the larger string. - private readonly char _separator; - - /// true if empty subcomponents should be skipped; false to treat them as valid entries. - private readonly bool _skipEmpty; - - /// The starting index from which to parse the current entry. - private int _startIndex; - - /// The ending index that represents the next index after the last character that's part of the current entry. - private int _endIndex; - - /// Initialize the StringParser. - /// The string to parse. - /// The separator character used to separate subcomponents of . - /// true if empty subcomponents should be skipped; false to treat them as valid entries. Defaults to false. - 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; - } - - /// Moves to the next component of the string. - /// true if there is a next component to be parsed; otherwise, false. - 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; - } - } - } - - /// - /// Moves to the next component of the string. If there isn't one, it throws an exception. - /// - public void MoveNextOrFail() - { - if (!MoveNext()) - { - ThrowForInvalidData(); - } - } - - /// - /// Moves to the next component of the string and returns it as a string. - /// - /// - public string MoveAndExtractNext() - { - MoveNextOrFail(); - return _buffer.Substring(_startIndex, _endIndex - _startIndex); - } - - /// - /// 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. - /// - /// - 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; - } - - /// - /// Gets the current subcomponent of the string as a string. - /// - public string ExtractCurrent() - { - if (_buffer == null || _startIndex == -1) - { - throw new InvalidOperationException(); - } - return _buffer.Substring(_startIndex, _endIndex - _startIndex); - } - - /// Moves to the next component and parses it as an Int32. - 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; - } - - /// Moves to the next component and parses it as an Int64. - 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; - } - - /// Moves to the next component and parses it as a UInt32. - 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; - } - - /// Moves to the next component and parses it as a UInt64. - 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; - } - - /// Moves to the next component and parses it as a Char. - 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(string buffer, ref int startIndex, ref int endIndex); - - /// - /// 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. - /// - internal T ParseRaw(ParseRawFunc selector) - { - MoveNextOrFail(); - return selector(_buffer, ref _startIndex, ref _endIndex); - } - - /// - /// Gets the current subcomponent and all remaining components of the string as a string. - /// - public string ExtractCurrentToEnd() - { - if (_buffer == null || _startIndex == -1) - { - throw new InvalidOperationException(); - } - return _buffer.Substring(_startIndex); - } - - /// Throws unconditionally for invalid data. - private static void ThrowForInvalidData() - { - throw new InvalidDataException(); - } - } -} diff --git a/src/Tools/dotnet-trace/build.cmd b/src/Tools/dotnet-trace/build.cmd new file mode 100644 index 000000000..ee7a8b071 --- /dev/null +++ b/src/Tools/dotnet-trace/build.cmd @@ -0,0 +1,14 @@ +@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 + ) +) diff --git a/src/Tools/dotnet-trace/dotnet-trace.csproj b/src/Tools/dotnet-trace/dotnet-trace.csproj index 092fb25d5..f315774c0 100644 --- a/src/Tools/dotnet-trace/dotnet-trace.csproj +++ b/src/Tools/dotnet-trace/dotnet-trace.csproj @@ -2,28 +2,36 @@ Exe - - netcoreapp2.1 + Microsoft.Diagnostics.Tools.Trace + False + False + - Microsoft.Diagnostics.Tools.Collect - true - - + + False + Latest + 4 + True + False - - - + - - + + + + True + + \ No newline at end of file diff --git a/src/Tools/dotnet-trace/run.cmd b/src/Tools/dotnet-trace/run.cmd new file mode 100644 index 000000000..dffa8cf06 --- /dev/null +++ b/src/Tools/dotnet-trace/run.cmd @@ -0,0 +1,11 @@ +@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%