[dotnet-trace] Minor updates to match recent CoreClr changes, error handling, and...
authorJosé Rivero <jorive@microsoft.com>
Tue, 9 Apr 2019 16:46:01 +0000 (09:46 -0700)
committerGitHub <noreply@github.com>
Tue, 9 Apr 2019 16:46:01 +0000 (09:46 -0700)
- Move common csproj properties to a common props file
- Add more tracing file extensions to .gitignore
- Update runtime client to enable streaming on non-Windows platforms.
- Adding helper bash scripts
- Update RunTimeClient
  - Updated `DiagnosticMessageType` private -> public
  - Ignore `sessionId == 0` on the "Disable" command
  - Added a `SessionLoggingType` to be added to the command protocol
  - Removed `multi file sec` related functionality
  - Ignore buffer size of zero
  - Added some initial tests that need to be moved to CoreClr.
- Update dotnet-counters with changes in RuntimeClient library
  - dotnet-counters.csproj : error NU1605: Detected package downgrade: Microsoft.Diagnostics.Tracing.TraceEvent from 2.0.40 to 2.0.38.
  - Use new helper interface.

23 files changed:
.gitignore
src/Common.props [new file with mode: 0644]
src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/DiagnosticMessageType.cs
src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/EventPipeClient.cs
src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/EventPipeSessionType.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/Provider.cs
src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/SessionConfiguration.cs
src/Microsoft.Diagnostics.Tools.RuntimeClient/Extensions.cs
src/Microsoft.Diagnostics.Tools.RuntimeClient/Microsoft.Diagnostics.Tools.RuntimeClient.csproj
src/Tools/dotnet-counters/CounterMonitor.cs
src/Tools/dotnet-counters/dotnet-counters.csproj
src/Tools/dotnet-trace/CommandLine/Commands/PortsCommandHandler.cs
src/Tools/dotnet-trace/CommandLine/Commands/ProvidersCommandHandler.cs
src/Tools/dotnet-trace/CommandLine/Commands/StartCommandHandler.cs
src/Tools/dotnet-trace/CommandLine/Commands/StopCommandHandler.cs
src/Tools/dotnet-trace/CommandLine/Commands/StreamCommand.cs
src/Tools/dotnet-trace/build.sh [new file with mode: 0755]
src/Tools/dotnet-trace/dotnet-trace.csproj
src/Tools/dotnet-trace/run.sh [new file with mode: 0755]
src/tests/Microsoft.Diagnostics.Tools.RuntimeClient/Assert.cs [new file with mode: 0644]
src/tests/Microsoft.Diagnostics.Tools.RuntimeClient/Program.cs [new file with mode: 0644]
src/tests/Microsoft.Diagnostics.Tools.RuntimeClient/Workload.cs [new file with mode: 0644]
src/tests/Microsoft.Diagnostics.Tools.RuntimeClient/eventpipetests.csproj [new file with mode: 0644]

index 073132444f78861b2dba3e3da980657c60d09759..ddbbfd871d61e426c1603d5a3c8f1b3537d03eaa 100644 (file)
@@ -114,5 +114,7 @@ $RECYCLE.BIN/
 # SOS stress log
 StressLog.txt
 
-# EventPipe files
+# Tracing files
+*.etl
+*.etlx
 *.netperf
diff --git a/src/Common.props b/src/Common.props
new file mode 100644 (file)
index 0000000..f3894f1
--- /dev/null
@@ -0,0 +1,31 @@
+<Project>
+
+  <PropertyGroup Label="Repository information">
+    <RepositoryUrl>https://github.com/dotnet/diagnostics.git</RepositoryUrl>
+    <RepositoryType>git</RepositoryType>
+  </PropertyGroup>
+
+  <PropertyGroup Label="Common Build properties">
+    <UseSharedCompilation>False</UseSharedCompilation>
+    <LangVersion>Latest</LangVersion>
+    <WarningLevel>4</WarningLevel>
+    <TreatWarningsAsErrors>True</TreatWarningsAsErrors>
+  </PropertyGroup>
+
+  <PropertyGroup Label="NuGet package information">
+    <PackageIconUrl>http://go.microsoft.com/fwlink/?LinkID=288859</PackageIconUrl>
+    <PackageProjectUrl>https://github.com/dotnet/diagnostics.git</PackageProjectUrl>
+    <PackageLicenseUrl>https://raw.githubusercontent.com/dotnet/diagnostics/master/LICENSE.TXT</PackageLicenseUrl>
+  </PropertyGroup>
+
+  <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>
index 3668f9482e41c0fb2c59a177dea70e79423a0052..7dc940e3c51f8ba4b26385794d929afe7ceaa77e 100644 (file)
@@ -4,12 +4,10 @@
 
 namespace Microsoft.Diagnostics.Tools.RuntimeClient
 {
-    enum DiagnosticMessageType : uint
+    public enum DiagnosticMessageType : uint
     {
         StartSession = 1024,
         StopSession,
         Stream,
-        Attach,
-        Detach,
     }
 }
index ce7a2e0dda2464afe8411e0731883f62f2fa909c..fe4ded22000ad8d1054ae3600a73ee6f387221f1 100644 (file)
@@ -31,17 +31,13 @@ namespace Microsoft.Diagnostics.Tools.RuntimeClient
                     ".", pipeName, PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.Impersonation))
                 {
                     namedPipe.Connect((int)TimeSpan.FromSeconds(20).TotalMilliseconds);
+                    namedPipe.Write(buffer, 0, buffer.Length);
 
-                    var sw = new BinaryWriter(namedPipe);
-                    sw.Write(buffer);
-
-                    var br = new BinaryReader(namedPipe);
-                    return br.ReadUInt64();
+                    return new BinaryReader(namedPipe).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$"));
@@ -54,12 +50,19 @@ namespace Microsoft.Diagnostics.Tools.RuntimeClient
                     socket.Send(buffer);
 
                     var content = new byte[sizeof(ulong)];
-                    int nReceivedBytes = socket.Receive(content);
+                    var nReceivedBytes = socket.Receive(content);
                     return (nReceivedBytes == sizeof(ulong)) ? BitConverter.ToUInt64(content, 0) : 0;
                 }
             }
         }
 
+        /// <summary>
+        /// Get the files associated with the opened IPC Ports for DotNet Core applications.
+        /// </summary>
+        /// <returns>
+        /// A collection of process identifiers associated with the list of opened files (IPC ports).
+        /// These process Ids migth have expired and not properly cleaned up.
+        /// </returns>
         public static IEnumerable<int> ListAvailablePorts()
         {
             return Directory.GetFiles(IpcRootPath)
@@ -68,7 +71,7 @@ namespace Microsoft.Diagnostics.Tools.RuntimeClient
                 .Select(input => int.Parse(Regex.Match(input, DiagnosticPortPattern).Groups[1].Value, NumberStyles.Integer));
         }
 
-        public static BinaryReader StreamTracingToFile(int processId, SessionConfiguration configuration, out ulong sessionId)
+        public static Stream StreamTracingToFile(int processId, SessionConfiguration configuration, out ulong sessionId)
         {
             sessionId = 0;
 
@@ -88,16 +91,32 @@ namespace Microsoft.Diagnostics.Tools.RuntimeClient
                     ".", pipeName, PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.Impersonation);
                 namedPipe.Connect((int)TimeSpan.FromSeconds(20).TotalMilliseconds);
 
-                var sw = new BinaryWriter(namedPipe);
-                sw.Write(serializedConfiguration);
+                // Request start-streaming
+                namedPipe.Write(serializedConfiguration, 0, serializedConfiguration.Length);
 
-                var br = new BinaryReader(namedPipe);
-                sessionId = br.ReadUInt64();
-                return br;
+                sessionId = new BinaryReader(namedPipe).ReadUInt64();
+                return namedPipe;
             }
             else
             {
-                throw new PlatformNotSupportedException("TODO: Get the ApplicationGroupId to form the string: 'dotnetcore-diagnostic-{processId}-{ApplicationGroupId}-socket'");
+                // TODO: Determine ApplicationGroupId
+                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);
+
+                var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
+                socket.Connect(remoteEP);
+
+                // Request start-streaming
+                socket.Send(serializedConfiguration);
+
+                var content = new byte[sizeof(ulong)];
+                int nReceivedBytes = socket.Receive(content);
+                sessionId = (nReceivedBytes == sizeof(ulong)) ? BitConverter.ToUInt64(content, 0) : 0;
+
+                return new NetworkStream(socket, FileAccess.Read, true);
             }
         }
 
@@ -117,6 +136,9 @@ namespace Microsoft.Diagnostics.Tools.RuntimeClient
 
         public static ulong DisableTracingToFile(int processId, ulong sessionId)
         {
+            if (sessionId == 0)
+                return sessionId; // TODO: Throw here instead?
+
             var header = new MessageHeader {
                 RequestType = DiagnosticMessageType.StopSession,
                 Pid = (uint)Process.GetCurrentProcess().Id,
@@ -147,7 +169,6 @@ namespace Microsoft.Diagnostics.Tools.RuntimeClient
                 bw.Write(header.Pid);
 
                 bw.Write(configuration.CircularBufferSizeInMB);
-                bw.Write(configuration.MultiFileTraceLengthInSeconds);
 
                 bw.WriteString(configuration.OutputPath);
 
diff --git a/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/EventPipeSessionType.cs b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/EventPipeSessionType.cs
new file mode 100644 (file)
index 0000000..fb8deba
--- /dev/null
@@ -0,0 +1,27 @@
+// 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
+{
+    /// <summary>
+    /// Defines constants for EventPipe logging sessions.
+    /// </summary>
+    public enum SessionLoggingType : uint
+    {
+        /// <summary>
+        /// The events will be written to file at the end of the session.
+        /// </summary>
+        TraceToFile,
+
+        /// <summary>
+        /// Events will be passed to the EventListener.
+        /// </summary>
+        CallbackListener,
+
+        /// <summary>
+        /// Events will be sent out-of-proc by writing them to the underlying IPC stream implementation.
+        /// </summary>
+        TraceToStream
+    }
+}
index 2738735e508381a0aaf84f6d9c249ba89367ad0c..35c0328e18365cc7cf3d91de56a6d8f6c4034142 100644 (file)
@@ -3,7 +3,9 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
+using System.Collections.Generic;
 using System.Diagnostics.Tracing;
+using System.Linq;
 
 namespace Microsoft.Diagnostics.Tools.RuntimeClient
 {
@@ -24,20 +26,35 @@ namespace Microsoft.Diagnostics.Tools.RuntimeClient
             FilterData = string.IsNullOrWhiteSpace(filterData) ? null : filterData;
         }
 
-        public static Provider ToProvider(string provider)
+        public static IReadOnlyCollection<Provider> ToProviders(string providers)
+        {
+            if (string.IsNullOrWhiteSpace(providers))
+                throw new ArgumentNullException(nameof(providers));
+            return providers.Split(',')
+                .Select(ToProvider)
+                .ToArray();
+        }
+
+        private 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;
 
+            // Provider name
             string providerName = tokens.Length > 0 ? tokens[0] : null;
             if (string.IsNullOrWhiteSpace(providerName))
                 throw new ArgumentException("Provider name was not specified.");
 
+            // Keywords
             ulong keywords = tokens.Length > 1 ? Convert.ToUInt64(tokens[1], 16) : ulong.MaxValue;
+
+            // Level
             EventLevel eventLevel = tokens.Length > 2 && uint.TryParse(tokens[2], out var level) ?
                 (EventLevel)level : EventLevel.Verbose;
+
+            // Event counters
             string filterData = tokens.Length > 3 ? tokens[3] : null;
 
             return new Provider(providerName, keywords, eventLevel, filterData);
index 3950f1ba6c1cc0c240867533a26f74d061c04424..4006afaa4181dbc1fa057b42d018945b0db2734d 100644 (file)
@@ -11,26 +11,30 @@ namespace Microsoft.Diagnostics.Tools.RuntimeClient
 {
     public struct SessionConfiguration
     {
-        public SessionConfiguration(uint circularBufferSizeMB, ulong multiFileSec, string outputPath, IEnumerable<Provider> providers)
+        public SessionConfiguration(uint circularBufferSizeMB, string outputPath, IReadOnlyCollection<Provider> providers)
         {
             if (providers == null)
                 throw new ArgumentNullException(nameof(providers));
             if (providers.Count() <= 0)
                 throw new ArgumentException($"Specified providers collection is empty.");
+            if (circularBufferSizeMB == 0)
+                throw new ArgumentException($"Buffer size cannot be zero.");
+            if (Directory.Exists(outputPath))
+                throw new ArgumentException($"Specified output file name: {outputPath}, refers to a directory.");
 
             CircularBufferSizeInMB = circularBufferSizeMB;
-            MultiFileTraceLengthInSeconds = multiFileSec;
-            _outputPath = new FileInfo(fileName: outputPath ?? $"eventpipe-{DateTime.Now:yyyyMMdd_HHmmss}.netperf");
+
+            outputPath = outputPath ?? $"eventpipe-{DateTime.Now:yyyyMMdd_HHmmss}.netperf";
+            outputPath = !outputPath.EndsWith(".netperf") ? $"{outputPath}.netperf" : outputPath;
+            _outputPath = new FileInfo(fileName: outputPath);
             _providers = new List<Provider>(providers);
         }
 
         public uint CircularBufferSizeInMB { get; }
 
-        public ulong MultiFileTraceLengthInSeconds { get; }
-
         public string OutputPath => _outputPath.FullName;
 
-        public IEnumerable<Provider> Providers => _providers;
+        public IReadOnlyCollection<Provider> Providers => _providers.AsReadOnly();
 
         private readonly FileInfo _outputPath;
         private readonly List<Provider> _providers;
index 266e717dc1e338ac951fcd2e72514e324530669e..7c8db451957805db061a4638dfb9c433d3169546 100644 (file)
@@ -21,7 +21,6 @@ namespace Microsoft.Diagnostics.Tools.RuntimeClient
                 @this.Write(Encoding.Unicode.GetBytes(value + '\0'));
         }
 
-
 #if DEBUG
         private static int GetByteCount(this string @this)
         {
@@ -37,11 +36,9 @@ namespace Microsoft.Diagnostics.Tools.RuntimeClient
             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());
index 0aa4474823e8019226169c3b73b4e889672f1fb1..5d0536311a4fab6868b0417c56cd3fea4d0b315a 100644 (file)
@@ -1,32 +1,23 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <Import Project="$(MSBuildThisFileDirectory)\..\Common.props" />
+
   <PropertyGroup>
     <OutputType>Library</OutputType>
     <TargetFramework>netcoreapp2.1</TargetFramework>
     <RootNamespace>Microsoft.Diagnostics.Tools.RuntimeClient</RootNamespace>
+  </PropertyGroup>
+
+  <PropertyGroup>
     <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" />
+    <PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="2.0.40" />
   </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>
index 66cd4c030cd2b94ee0dbaa6cdf7503169c1c99fc..f557fa1a5ab98bf7b0094782d362259bd99d591f 100644 (file)
@@ -1,18 +1,13 @@
 // Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
+using Microsoft.Diagnostics.Tools.RuntimeClient;
 using System;
 using System.CommandLine;
-using System.Collections.Generic;
 using System.IO;
-using System.Linq;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
-using System.CommandLine.Builder;
-using System.CommandLine.Invocation;
-
-using Microsoft.Diagnostics.Tools.RuntimeClient;
 
 namespace Microsoft.Diagnostics.Tools.Counters
 {
@@ -58,14 +53,6 @@ namespace Microsoft.Diagnostics.Tools.Counters
             }
         }
 
-        private static IEnumerable<Provider> ToProviders(string providers)
-        {
-            if (string.IsNullOrWhiteSpace(providers))
-                throw new ArgumentNullException(nameof(providers));
-            return providers.Split(',')
-                .Select(Provider.ToProvider);
-        }
-
         private async Task<int> StartMonitor()
         {
             if (_processId == 0) {
@@ -117,10 +104,9 @@ namespace Microsoft.Diagnostics.Tools.Counters
             }
 
             var configuration = new SessionConfiguration(
-                1000,
-                0,
-                outputPath,
-                ToProviders(providerString));
+                circularBufferSizeMB: 1000,
+                outputPath: outputPath,
+                providers: Provider.ToProviders(providerString));
 
             sessionId = EventPipeClient.EnableTracingToFile(_processId, configuration);
 
index d1b91a1c0e4d8aae6ff43bd753188c62405f76e8..a1a3d740966c0da88b1ac899d59f5ad52b679d6f 100644 (file)
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="2.0.38" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <ProjectReference Include="..\..\Microsoft.Diagnostic.Repl\Microsoft.Diagnostic.Repl.csproj" />
+    <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostic.Repl\Microsoft.Diagnostic.Repl.csproj" />
     <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.Tools.RuntimeClient\Microsoft.Diagnostics.Tools.RuntimeClient.csproj" />
   </ItemGroup>
 
index 49b89e0d4eee5565a6ed1cdbecf932e04edff5d8..9b77784bcdd729a9b27339e1134290135601462e 100644 (file)
@@ -5,7 +5,8 @@
 using Microsoft.Diagnostics.Tools.RuntimeClient;
 using System;
 using System.CommandLine;
-using System.CommandLine.Invocation;
+using System.Diagnostics;
+using System.Linq;
 using System.Threading.Tasks;
 
 namespace Microsoft.Diagnostics.Tools.Trace
@@ -16,24 +17,41 @@ namespace Microsoft.Diagnostics.Tools.Trace
         {
             try
             {
-                foreach (var pid in EventPipeClient.ListAvailablePorts())
-                    Console.Out.WriteLine($"{System.Diagnostics.Process.GetProcessById(pid).ProcessName}({pid})");
+                var processes = EventPipeClient.ListAvailablePorts()
+                    .Select(GetProcessById)
+                    .Where(process => process != null)
+                    .OrderBy(process => process.ProcessName);
+
+                foreach (var process in processes)
+                    Console.Out.WriteLine($"{process.Id, 10} {process.ProcessName, -10} - {process.MainModule.FileName}");
 
                 await Task.FromResult(0);
                 return 0;
             }
             catch (Exception ex)
             {
-                Console.Error.WriteLine($"[ERROR]: {ex.ToString()}");
+                Console.Error.WriteLine($"[ERROR] {ex.ToString()}");
                 return 1;
             }
         }
 
+        private static Process GetProcessById(int processId)
+        {
+            try
+            {
+                return Process.GetProcessById(processId);
+            }
+            catch (ArgumentException)
+            {
+                return null;
+            }
+        }
+
         public static Command ActivePortsCommand() =>
             new Command(
                 name: "ports",
                 description: "List all active DotNet Core Diagnostic ports.",
-                handler: CommandHandler.Create<IConsole>(GetActivePorts),
+                handler: System.CommandLine.Invocation.CommandHandler.Create<IConsole>(GetActivePorts),
                 isHidden: true);
     }
 }
index 2a5ce5e6eeea562cf88b8a7e7bf44f82578a94b1..4900efc98a67935978adc514d2d8a382e6ec72a2 100644 (file)
@@ -29,7 +29,7 @@ namespace Microsoft.Diagnostics.Tools.Trace
             }
             catch (Exception ex)
             {
-                Console.Error.WriteLine($"[ERROR]: {ex.ToString()}");
+                Console.Error.WriteLine($"[ERROR] {ex.ToString()}");
                 return 1;
             }
         }
index 627c5179b5e5726eb2c9737438a0135192659521..9fbeb08bc012cff10f1c7e4c9e951b4b69d79b7e 100644 (file)
@@ -4,25 +4,22 @@
 
 using Microsoft.Diagnostics.Tools.RuntimeClient;
 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)
+        public static async Task<int> Start(IConsole console, int pid, string output, uint buffersize, string providers)
         {
             try
             {
                 var configuration = new SessionConfiguration(
                     circularBufferSizeMB: buffersize,
-                    multiFileSec: multiFileSec,
                     outputPath: output,
-                    ToProviders(providers));
+                    Provider.ToProviders(providers));
                 var sessionId = EventPipeClient.EnableTracingToFile(pid, configuration);
                 Console.Out.WriteLine($"OutputPath={configuration.OutputPath}");
                 Console.Out.WriteLine($"SessionId=0x{sessionId:X16}");
@@ -32,7 +29,7 @@ namespace Microsoft.Diagnostics.Tools.Trace
             }
             catch (Exception ex)
             {
-                Console.Error.WriteLine($"[ERROR]: {ex.ToString()}");
+                Console.Error.WriteLine($"[ERROR] {ex.ToString()}");
                 return 1;
             }
         }
@@ -46,9 +43,8 @@ namespace Microsoft.Diagnostics.Tools.Trace
                     OutputPathOption(),
                     CircularBufferOption(),
                     ProvidersOption(),
-                    MultiFileSecOption(),
                 },
-                handler: CommandHandler.Create<IConsole, int, string, uint, string, ulong>(Start));
+                handler: CommandHandler.Create<IConsole, int, string, uint, string>(Start));
 
         private static Option OutputPathOption() =>
             new Option(
@@ -60,26 +56,14 @@ namespace Microsoft.Diagnostics.Tools.Trace
             new Option(
                 new[] { "--buffersize" },
                 @"Sets the size of the in-memory circular buffer in megabytes.",
-                new Argument<uint> { Name = "Size" }); // TODO: 1024 ? Default ?
+                new Argument<uint>(defaultValue: 1024) {
+                    Name = "Size",
+                }); // TODO: Seems excesive, but this has been the value.
 
         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);
-        }
     }
 }
index e7bde9b3568eb1a95a187f99398d2835c6532c0d..e36a1f2540b863d917d44feda6d0d7437d806605 100644 (file)
@@ -23,7 +23,7 @@ namespace Microsoft.Diagnostics.Tools.Trace
             }
             catch (Exception ex)
             {
-                Console.Error.WriteLine($"[ERROR]: {ex.ToString()}");
+                Console.Error.WriteLine($"[ERROR] {ex.ToString()}");
                 return 1;
             }
         }
index 5f10ea65dc39e108d02ecdb251aa7e9696eaa8fe..52f1e4371c9175b58d2b8edd1c608f9d0a44b8f4 100644 (file)
@@ -3,12 +3,13 @@
 // See the LICENSE file in the project root for more information.
 
 using Microsoft.Diagnostics.Tools.RuntimeClient;
+using Microsoft.Diagnostics.Tracing;
+using Microsoft.Diagnostics.Tracing.Etlx;
 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
@@ -21,34 +22,64 @@ namespace Microsoft.Diagnostics.Tools.Trace
             {
                 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}");
+                    Provider.ToProviders(providers));
+                string filePath = null;
+                ulong sessionId = 0;
 
-                if (sessionId != 0)
+                using (Stream stream = EventPipeClient.StreamTracingToFile(pid, configuration, out sessionId))
                 {
-                    var filePath = $"dotnetcore-eventpipe-{pid}-0x{sessionId:X16}.netperf";
+                    if (sessionId == 0)
+                    {
+                        Console.Error.WriteLine("Unable to create streaming session.");
+                        return -1;
+                    }
+
+                    filePath = $"dotnetcore-eventpipe-{pid}-0x{sessionId:X16}.netperf";
                     using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
                     {
+                        Console.Out.WriteLine($"OutputPath={fs.Name}");
+                        Console.Out.WriteLine($"SessionId=0x{sessionId:X16}");
+
                         while (true)
                         {
                             var buffer = new byte[1024];
-                            int nBytesRead = binaryReader.Read(buffer, 0, buffer.Length);
+                            int nBytesRead = stream.Read(buffer, 0, buffer.Length);
                             if (nBytesRead <= 0)
                                 break;
+                            Console.WriteLine($"PACKET: {Convert.ToBase64String(buffer, 0, nBytesRead)}");
                             fs.Write(buffer, 0, nBytesRead);
                         }
                     }
                 }
 
+                if (sessionId != 0 && filePath != null)
+                {
+                    var eventPipeResults = new List<TraceEvent>();
+                    using (var trace = new TraceLog(TraceLog.CreateFromEventPipeDataFile(filePath)).Events.GetSource())
+                    {
+                        trace.Dynamic.All += (TraceEvent data) => {
+                            eventPipeResults.Add(data);
+                        };
+
+                        trace.Process();
+                    }
+
+                    eventPipeResults.ForEach(e => {
+                        if (!string.IsNullOrWhiteSpace(e.ProviderName) && !string.IsNullOrWhiteSpace(e.EventName))
+                        {
+                            Console.Out.WriteLine($"Event Provider: {e.ProviderName}");
+                            Console.Out.WriteLine($"    Event Name: {e.EventName}");
+                        }
+                    });
+                }
+
                 await Task.FromResult(0);
                 return sessionId != 0 ? 0 : 1;
             }
             catch (Exception ex)
             {
-                Console.Error.WriteLine($"[ERROR]: {ex.ToString()}");
+                Console.Error.WriteLine($"[ERROR] {ex.ToString()}");
                 return 1;
             }
         }
@@ -75,13 +106,5 @@ namespace Microsoft.Diagnostics.Tools.Trace
                 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);
-        }
     }
 }
diff --git a/src/Tools/dotnet-trace/build.sh b/src/Tools/dotnet-trace/build.sh
new file mode 100755 (executable)
index 0000000..69b93af
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+dotnet restore dotnet-trace.csproj
+dotnet build dotnet-trace.csproj -c Debug --no-restore
+
index f315774c013d4f6e32cddea404e7ab54396c6069..14c9d64179178503f0045efcd75eb9cf0048d788 100644 (file)
@@ -1,37 +1,25 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
+  <Import Project="$(MSBuildThisFileDirectory)\..\..\Common.props" />
+
   <PropertyGroup>
     <OutputType>Exe</OutputType>
     <TargetFramework>netcoreapp2.1</TargetFramework>
     <RootNamespace>Microsoft.Diagnostics.Tools.Trace</RootNamespace>
-    <IsPackable>False</IsPackable>
-    <PackAsTool>False</PackAsTool>
+    <GenerateDocumentationFile>False</GenerateDocumentationFile>
   </PropertyGroup>
 
   <PropertyGroup>
-    <UseSharedCompilation>False</UseSharedCompilation>
-    <LangVersion>Latest</LangVersion>
-    <WarningLevel>4</WarningLevel>
-    <TreatWarningsAsErrors>True</TreatWarningsAsErrors>
-    <GenerateDocumentationFile>False</GenerateDocumentationFile>
+    <IsPackable>False</IsPackable>
+    <PackAsTool>False</PackAsTool>
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="System.CommandLine.Experimental" Version="0.2.0-alpha.19167.2" />
+    <PackageReference Include="System.CommandLine.Experimental" Version="0.2.0-alpha.19179.1" />
   </ItemGroup>
 
   <ItemGroup>
     <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
+</Project>
diff --git a/src/Tools/dotnet-trace/run.sh b/src/Tools/dotnet-trace/run.sh
new file mode 100755 (executable)
index 0000000..34bdf93
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+echo $USER@`hostname` "$PWD"
+echo [`date`] $ dotnet run -p dotnet-trace.csproj -c Debug --no-restore --no-build -- "$@"
+dotnet run -p dotnet-trace.csproj -c Debug --no-restore --no-build -- "$@"
+
diff --git a/src/tests/Microsoft.Diagnostics.Tools.RuntimeClient/Assert.cs b/src/tests/Microsoft.Diagnostics.Tools.RuntimeClient/Assert.cs
new file mode 100644 (file)
index 0000000..2bd5a2c
--- /dev/null
@@ -0,0 +1,53 @@
+// 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;
+
+namespace Tracing.Tests.Common
+{
+    public static class Assert
+    {
+        public static void True(string name, bool condition)
+        {
+            if (!condition)
+            {
+                throw new Exception(
+                    string.Format("Condition '{0}' is not true", name));
+            }
+        }
+
+        public static void Equal<T>(string name, T left, T right) where T : IEquatable<T>
+        {
+            if (left == null && right != null)
+            {
+                throw new Exception(
+                    string.Format("Values for '{0}' are not equal!  Left=NULL Right='{1}'", name, right));
+            }
+            else if (left != null && right == null)
+            {
+                throw new Exception(
+                    string.Format("Values for '{0}' are not equal!  Left='{1}' Right=NULL", name, left));
+            }
+            else if (!left.Equals(right))
+            {
+                throw new Exception(
+                    string.Format("Values for '{0}' are not equal! Left='{1}' Right='{2}'", name, left, right));
+            }
+        }
+
+        public static void NotEqual<T>(string name, T left, T right) where T : IEquatable<T>
+        {
+            if (left == null && right == null)
+            {
+                throw new Exception(
+                    string.Format("Values for '{0}' are equal! Left=NULL Right=NULL", name));
+            }
+            else if (left != null && left.Equals(right))
+            {
+                throw new Exception(
+                    string.Format("Values for '{0}' are equal! Left='{1}' Right='{2}'", name, left, right));
+            }
+        }
+    }
+}
diff --git a/src/tests/Microsoft.Diagnostics.Tools.RuntimeClient/Program.cs b/src/tests/Microsoft.Diagnostics.Tools.RuntimeClient/Program.cs
new file mode 100644 (file)
index 0000000..81f5f3c
--- /dev/null
@@ -0,0 +1,168 @@
+// 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.Tracing;
+using Microsoft.Diagnostics.Tracing.Etlx;
+using System;
+using System.Diagnostics;
+using System.IO;
+using Tracing.Tests.Common;
+
+namespace Microsoft.Diagnostics.Tools.RuntimeClient.Tests
+{
+    class Program
+    {
+        static int Main(string[] args)
+        {
+            SendSmallerHeaderCommand();
+            SendInvalidDiagnosticMessageTypeCommand();
+
+            StartNewTracingToFileSession();
+            StartNewTracingToStreamSession();
+            return 100;
+        }
+
+        private static Process ThisProcess { get; } = Process.GetCurrentProcess();
+
+        private static void SendSmallerHeaderCommand()
+        {
+            Console.WriteLine("Send a small payload as header.");
+
+            ulong sessionId = 0;
+
+            try
+            {
+                byte[] bytes;
+                using (var stream = new MemoryStream())
+                {
+                    using (var bw = new BinaryWriter(stream))
+                    {
+                        bw.Write((uint)DiagnosticMessageType.StartSession);
+                        bw.Flush();
+                        stream.Position = 0;
+
+                        bytes = new byte[stream.Length];
+                        stream.Read(bytes, 0, bytes.Length);
+                    }
+                }
+
+                sessionId = EventPipeClient.SendCommand(ThisProcess.Id, bytes);
+            }
+            catch (EndOfStreamException)
+            {
+                Assert.Equal("EventPipe Session Id", sessionId, (ulong)0);
+            }
+            catch
+            {
+                Assert.True("Send command threw unexpected exception", false);
+            }
+        }
+
+        private static void SendInvalidDiagnosticMessageTypeCommand()
+        {
+            Console.WriteLine("Send a wrong message type as the diagnostic header header.");
+            ulong sessionId = 0;
+
+            try
+            {
+                byte[] bytes;
+                using (var stream = new MemoryStream())
+                {
+                    using (var bw = new BinaryWriter(stream))
+                    {
+                        bw.Write(uint.MaxValue);
+                        bw.Write(ThisProcess.Id);
+                        bw.Flush();
+                        stream.Position = 0;
+
+                        bytes = new byte[stream.Length];
+                        stream.Read(bytes, 0, bytes.Length);
+                    }
+                }
+
+                sessionId = EventPipeClient.SendCommand(ThisProcess.Id, bytes);
+            }
+            catch (EndOfStreamException)
+            {
+                Assert.Equal("EventPipe Session Id", sessionId, (ulong)0);
+            }
+            catch
+            {
+                Assert.True("Send command threw unexpected exception", false);
+            }
+        }
+
+        private static void StartNewTracingToFileSession()
+        {
+            Console.WriteLine("Start collection.");
+
+            ulong sessionId = 0;
+
+            try
+            {
+                uint circularBufferSizeMB = 64;
+                var filePath = Path.Combine(
+                    Directory.GetCurrentDirectory(),
+                    $"dotnetcore-eventpipe-{ThisProcess.Id}.netperf");
+                var providers = new[] {
+                    new Provider(name: "Microsoft-Windows-DotNETRuntime"),
+                };
+                // "Microsoft-Windows-DotNETRuntime:0x00000004C14FCCBD:4"
+
+                var configuration = new SessionConfiguration(circularBufferSizeMB, filePath, providers);
+
+                // Start session.
+                sessionId = EventPipeClient.EnableTracingToFile(
+                    processId: ThisProcess.Id,
+                    configuration: configuration);
+
+                // Check that a session was created.
+                Assert.NotEqual("EventPipe Session Id", sessionId, (ulong)0);
+
+                // Check that file is created
+                // NOTE: This might change in the future, and file could be created only "OnDisable".
+                Assert.Equal("EventPipe output file", File.Exists(filePath), true);
+
+                {
+                    // Attempt to create another session, and verify that is not possible.
+                    var sessionId2 = EventPipeClient.EnableTracingToFile(
+                        processId: ThisProcess.Id,
+                        configuration: configuration);
+
+                    // Check that a new session was not created.
+                    Assert.Equal("EventPipe Session Id", sessionId2, (ulong)0);
+                }
+
+                Workload.DoWork(10);
+
+                var ret = EventPipeClient.DisableTracingToFile(ThisProcess.Id, sessionId);
+                Assert.Equal("Expect return value to be the disabled session Id", sessionId, ret);
+                sessionId = 0; // Reset session Id, we do not need to disable it later.
+
+                // Check file is valid.
+                var nEventPipeResults = 0;
+                using (var trace = new TraceLog(TraceLog.CreateFromEventPipeDataFile(filePath)).Events.GetSource())
+                {
+                    trace.Dynamic.All += (TraceEvent data) => {
+                        ++nEventPipeResults;
+                    };
+                    trace.Process();
+                }
+
+                // Assert there were events in the file.
+                Assert.NotEqual("Found events in trace file", nEventPipeResults, 0);
+            }
+            finally
+            {
+                if (sessionId != 0)
+                    EventPipeClient.DisableTracingToFile(ThisProcess.Id, sessionId);
+            }
+        }
+
+        private static void StartNewTracingToStreamSession()
+        {
+            Console.WriteLine("Start streaming.");
+        }
+    }
+}
diff --git a/src/tests/Microsoft.Diagnostics.Tools.RuntimeClient/Workload.cs b/src/tests/Microsoft.Diagnostics.Tools.RuntimeClient/Workload.cs
new file mode 100644 (file)
index 0000000..a44095c
--- /dev/null
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Microsoft.Diagnostics.Tools.RuntimeClient.Tests
+{
+    internal static class Workload
+    {
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public static void DoWork(int nIterations)
+        {
+            for (int i = 0; i < nIterations; ++i)
+            {
+                MemoryAccessPerformance();
+                BranchPredictionPerformance(seed: i);
+            }
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private static double MemoryAccessPerformance()
+        {
+            var doubles = new double[8 * 1024 * 1024];
+            for (int i = 0; i < doubles.Length; i += 100)
+                doubles[i] = 2.0;
+            for (int i = 0; i < doubles.Length; i += 200)
+                doubles[i] *= 3.0;
+            for (int i = 0; i < doubles.Length; i += 400)
+                doubles[i] *= 5.0;
+            for (int i = 0; i < doubles.Length; i += 800)
+                doubles[i] *= 7.0;
+            for (int i = 0; i < doubles.Length; i += 1600)
+                doubles[i] *= 11.0;
+            return doubles.Average();
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private static IEnumerable<int> BranchPredictionPerformance(int seed)
+        {
+            const int nCards = 52;
+            var deck = new List<int>(Enumerable.Range(0, nCards));
+            var rnd = new Random((int)DateTime.Now.Ticks + seed);
+
+            for (int i = 0; i < deck.Count(); ++i)
+            {
+                var pos = rnd.Next(nCards);
+                if (pos % 3 != 0)
+                    pos = rnd.Next(nCards);
+                var temp = deck[i];
+                deck[i] = deck[pos];
+                deck[pos] = temp;
+            }
+
+            return deck;
+        }
+    }
+}
diff --git a/src/tests/Microsoft.Diagnostics.Tools.RuntimeClient/eventpipetests.csproj b/src/tests/Microsoft.Diagnostics.Tools.RuntimeClient/eventpipetests.csproj
new file mode 100644 (file)
index 0000000..c2e84c0
--- /dev/null
@@ -0,0 +1,21 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <Import Project="$(MSBuildThisFileDirectory)\..\..\Common.props" />
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+    <RootNamespace>Microsoft.Diagnostics.Tools.RuntimeClient.Tests</RootNamespace>
+    <GenerateDocumentationFile>False</GenerateDocumentationFile>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <IsPackable>False</IsPackable>
+    <PackAsTool>False</PackAsTool>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.Tools.RuntimeClient\Microsoft.Diagnostics.Tools.RuntimeClient.csproj" />
+  </ItemGroup>
+
+</Project>