diff --git a/diagnostics.sln b/diagnostics.sln
index 6796cda207..45df2c9e38 100644
--- a/diagnostics.sln
+++ b/diagnostics.sln
@@ -55,6 +55,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-gcdump", "src\Tools\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotnetCounters.UnitTests", "src\tests\dotnet-counters\DotnetCounters.UnitTests.csproj", "{E5A7DC6C-BF8D-418A-BCBD-094EB748FA82}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.NETCore.Client", "src\Microsoft.Diagnostics.NETCore.Client\Microsoft.Diagnostics.NETCore.Client.csproj", "{D8BE9C81-194E-43E5-82CF-2080892FBDE7}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.NETCore.Client.UnitTests", "src\tests\Microsoft.Diagnostics.NETCore.Client\Microsoft.Diagnostics.NETCore.Client.UnitTests.csproj", "{6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tracee", "src\tests\Tracee\Tracee.csproj", "{C79D6069-2C18-48CB-846E-71F7168C2F7D}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventPipe.UnitTests", "src\tests\eventpipe\EventPipe.UnitTests.csproj", "{CED9ABBA-861E-4C0A-9359-22351208EF27}"
EndProject
Global
@@ -890,6 +896,126 @@ Global
{E5A7DC6C-BF8D-418A-BCBD-094EB748FA82}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{E5A7DC6C-BF8D-418A-BCBD-094EB748FA82}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
{E5A7DC6C-BF8D-418A-BCBD-094EB748FA82}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Checked|Any CPU.Build.0 = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Checked|ARM.ActiveCfg = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Checked|ARM.Build.0 = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Checked|ARM64.ActiveCfg = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Checked|ARM64.Build.0 = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Checked|x64.ActiveCfg = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Checked|x64.Build.0 = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Checked|x86.ActiveCfg = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Checked|x86.Build.0 = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Debug|ARM.Build.0 = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Debug|x64.Build.0 = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Debug|x86.Build.0 = Debug|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Release|ARM.ActiveCfg = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Release|ARM.Build.0 = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Release|ARM64.Build.0 = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Release|x64.ActiveCfg = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Release|x64.Build.0 = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Release|x86.ActiveCfg = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.Release|x86.Build.0 = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Checked|Any CPU.Build.0 = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Checked|ARM.ActiveCfg = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Checked|ARM.Build.0 = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Checked|ARM64.ActiveCfg = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Checked|ARM64.Build.0 = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Checked|x64.ActiveCfg = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Checked|x64.Build.0 = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Checked|x86.ActiveCfg = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Checked|x86.Build.0 = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Debug|ARM.Build.0 = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Debug|x64.Build.0 = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Debug|x86.Build.0 = Debug|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Release|ARM.ActiveCfg = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Release|ARM.Build.0 = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Release|ARM64.Build.0 = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Release|x64.ActiveCfg = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Release|x64.Build.0 = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Release|x86.ActiveCfg = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.Release|x86.Build.0 = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Checked|Any CPU.Build.0 = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Checked|ARM.ActiveCfg = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Checked|ARM.Build.0 = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Checked|ARM64.ActiveCfg = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Checked|ARM64.Build.0 = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Checked|x64.ActiveCfg = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Checked|x64.Build.0 = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Checked|x86.ActiveCfg = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Checked|x86.Build.0 = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Debug|ARM.Build.0 = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Debug|x64.Build.0 = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Debug|x86.Build.0 = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Release|ARM.ActiveCfg = Release|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Release|ARM.Build.0 = Release|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Release|ARM64.Build.0 = Release|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Release|x64.ActiveCfg = Release|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Release|x64.Build.0 = Release|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Release|x86.ActiveCfg = Release|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.Release|x86.Build.0 = Release|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.RelWithDebInfo|Any CPU.ActiveCfg = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.RelWithDebInfo|Any CPU.Build.0 = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.RelWithDebInfo|ARM.ActiveCfg = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.RelWithDebInfo|ARM.Build.0 = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.RelWithDebInfo|ARM64.ActiveCfg = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.RelWithDebInfo|ARM64.Build.0 = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.RelWithDebInfo|x86.ActiveCfg = Debug|Any CPU
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D}.RelWithDebInfo|x86.Build.0 = Debug|Any CPU
{CED9ABBA-861E-4C0A-9359-22351208EF27}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
{CED9ABBA-861E-4C0A-9359-22351208EF27}.Checked|Any CPU.Build.0 = Debug|Any CPU
{CED9ABBA-861E-4C0A-9359-22351208EF27}.Checked|ARM.ActiveCfg = Debug|Any CPU
@@ -960,6 +1086,9 @@ Global
{AEDCCF5B-5AD0-4D64-BF73-5CF468E07D22} = {03479E19-3F18-49A6-910A-F5041E27E7C0}
{936678B3-3392-4F4F-943C-B6A4BFCBAADC} = {B62728C8-1267-4043-B46F-5537BBAEC692}
{E5A7DC6C-BF8D-418A-BCBD-094EB748FA82} = {03479E19-3F18-49A6-910A-F5041E27E7C0}
+ {D8BE9C81-194E-43E5-82CF-2080892FBDE7} = {19FAB78C-3351-4911-8F0C-8C6056401740}
+ {6F4DD2F8-1C7B-4A87-B7E5-1BEE9F5AC128} = {03479E19-3F18-49A6-910A-F5041E27E7C0}
+ {C79D6069-2C18-48CB-846E-71F7168C2F7D} = {03479E19-3F18-49A6-910A-F5041E27E7C0}
{CED9ABBA-861E-4C0A-9359-22351208EF27} = {03479E19-3F18-49A6-910A-F5041E27E7C0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
diff --git a/src/Microsoft.Diagnostics.NETCore.Client/BinaryWriterExtensions.cs b/src/Microsoft.Diagnostics.NETCore.Client/BinaryWriterExtensions.cs
new file mode 100644
index 0000000000..c95b24d5e3
--- /dev/null
+++ b/src/Microsoft.Diagnostics.NETCore.Client/BinaryWriterExtensions.cs
@@ -0,0 +1,25 @@
+// 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.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Microsoft.Diagnostics.NETCore.Client
+{
+ internal static class BinaryWriterExtensions
+ {
+ 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'));
+ }
+
+ }
+}
diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs
new file mode 100644
index 0000000000..5d558c4ff9
--- /dev/null
+++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs
@@ -0,0 +1,163 @@
+// 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.IO;
+using System.Globalization;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text.RegularExpressions;
+
+namespace Microsoft.Diagnostics.NETCore.Client
+{
+ ///
+ /// This is a top-level class that contains methods to send various diagnostics command to the runtime.
+ ///
+ public sealed class DiagnosticsClient
+ {
+ private int _processId;
+
+ public DiagnosticsClient(int processId)
+ {
+ _processId = processId;
+ }
+
+ ///
+ /// Start tracing the application and return an EventPipeSession object
+ ///
+ /// An IEnumerable containing the list of Providers to turn on.
+ /// If true, request rundown events from the runtime
+ /// The size of the runtime's buffer for collecting events in MB
+ ///
+ /// An EventPipeSession object representing the EventPipe session that just started.
+ ///
+ public EventPipeSession StartEventPipeSession(IEnumerable providers, bool requestRundown=true, int circularBufferMB=256)
+ {
+ return new EventPipeSession(_processId, providers, requestRundown, circularBufferMB);
+ }
+
+ ///
+ /// Trigger a core dump generation.
+ ///
+ /// Type of the dump to be generated
+ /// Full path to the dump to be generated. By default it is /tmp/coredump.{pid}
+ /// When set to true, display the dump generation debug log to the console.
+ public void WriteDump(DumpType dumpType, string dumpPath, bool logDumpGeneration=false)
+ {
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ throw new PlatformNotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}");
+
+ if (string.IsNullOrEmpty(dumpPath))
+ throw new ArgumentNullException($"{nameof(dumpPath)} required");
+
+ var payload = SerializeCoreDump(dumpPath, dumpType, logDumpGeneration);
+ var message = new IpcMessage(DiagnosticsServerCommandSet.Dump, (byte)DumpCommandId.GenerateCoreDump, payload);
+ var response = IpcClient.SendMessage(_processId, message);
+ var hr = 0;
+ switch ((DiagnosticsServerCommandId)response.Header.CommandId)
+ {
+ case DiagnosticsServerCommandId.Error:
+ hr = BitConverter.ToInt32(response.Payload, 0);
+ throw new ServerErrorException($"Writing dump failed (HRESULT: 0x{hr:X8})");
+ case DiagnosticsServerCommandId.OK:
+ return;
+ default:
+ throw new ServerErrorException($"Writing dump failed - server responded with unknown command");
+ }
+ }
+
+ ///
+ /// Attach a profiler.
+ ///
+ /// Timeout for attaching the profiler
+ /// Guid for the profiler to be attached
+ /// Path to the profiler to be attached
+ /// Additional data to be passed to the profiler
+ public void AttachProfiler(TimeSpan attachTimeout, Guid profilerGuid, string profilerPath, byte[] additionalData=null)
+ {
+ if (profilerGuid == null || profilerGuid == Guid.Empty)
+ {
+ throw new ArgumentException($"{nameof(profilerGuid)} must be a valid Guid");
+ }
+
+ if (String.IsNullOrEmpty(profilerPath))
+ {
+ throw new ArgumentException($"{nameof(profilerPath)} must be non-null");
+ }
+
+ byte[] serializedConfiguration = SerializeProfilerAttach((uint)attachTimeout.TotalSeconds, profilerGuid, profilerPath, additionalData);
+ var message = new IpcMessage(DiagnosticsServerCommandSet.Profiler, (byte)ProfilerCommandId.AttachProfiler, serializedConfiguration);
+ var response = IpcClient.SendMessage(_processId, message);
+ switch ((DiagnosticsServerCommandId)response.Header.CommandId)
+ {
+ case DiagnosticsServerCommandId.Error:
+ var hr = BitConverter.ToInt32(response.Payload, 0);
+ throw new ServerErrorException($"Profiler attach failed (HRESULT: 0x{hr:X8})");
+ case DiagnosticsServerCommandId.OK:
+ return;
+ default:
+ throw new ServerErrorException($"Profiler attach failed - server responded with unknown command");
+ }
+
+ // The call to set up the pipe and send the message operates on a different timeout than attachTimeout, which is for the runtime.
+ // We should eventually have a configurable timeout for the message passing, potentially either separately from the
+ // runtime timeout or respect attachTimeout as one total duration.
+ }
+
+ ///
+ /// Get all the active processes that can be attached to.
+ ///
+ ///
+ /// IEnumerable of all the active process IDs.
+ ///
+ public static IEnumerable GetPublishedProcesses()
+ {
+ return Directory.GetFiles(IpcClient.IpcRootPath)
+ .Select(namedPipe => (new FileInfo(namedPipe)).Name)
+ .Where(input => Regex.IsMatch(input, IpcClient.DiagnosticsPortPattern))
+ .Select(input => int.Parse(Regex.Match(input, IpcClient.DiagnosticsPortPattern).Groups[1].Value, NumberStyles.Integer))
+ .Distinct();
+ }
+
+ private static byte[] SerializeCoreDump(string dumpName, DumpType dumpType, bool diagnostics)
+ {
+ using (var stream = new MemoryStream())
+ using (var writer = new BinaryWriter(stream))
+ {
+ writer.WriteString(dumpName);
+ writer.Write((uint)dumpType);
+ writer.Write((uint)(diagnostics ? 1 : 0));
+
+ writer.Flush();
+ return stream.ToArray();
+ }
+ }
+
+ private static byte[] SerializeProfilerAttach(uint attachTimeout, Guid profilerGuid, string profilerPath, byte[] additionalData)
+ {
+ using (var stream = new MemoryStream())
+ using (var writer = new BinaryWriter(stream))
+ {
+ writer.Write(attachTimeout);
+ writer.Write(profilerGuid.ToByteArray());
+ writer.WriteString(profilerPath);
+
+ if (additionalData == null)
+ {
+ writer.Write(0);
+ }
+ else
+ {
+ writer.Write(additionalData.Length);
+ writer.Write(additionalData);
+ }
+
+ writer.Flush();
+ return stream.ToArray();
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClientExceptions.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClientExceptions.cs
new file mode 100644
index 0000000000..a313e3c533
--- /dev/null
+++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClientExceptions.cs
@@ -0,0 +1,31 @@
+// 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 Microsoft.Diagnostics.NETCore.Client
+{
+ public class DiagnosticsClientException : Exception
+ {
+ public DiagnosticsClientException(string msg) : base(msg) {}
+ }
+
+ // When a certian command is not supported by either the library or the target process' runtime
+ public class UnsupportedProtocolException : DiagnosticsClientException
+ {
+ public UnsupportedProtocolException(string msg) : base(msg) {}
+ }
+
+ // When the runtime is no longer availble for attaching.
+ public class ServerNotAvailableException : DiagnosticsClientException
+ {
+ public ServerNotAvailableException(string msg) : base(msg) {}
+ }
+
+ // When the runtime responded with an error
+ public class ServerErrorException : DiagnosticsClientException
+ {
+ public ServerErrorException(string msg): base(msg) {}
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DumpType.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DumpType.cs
new file mode 100644
index 0000000000..2200e84fb2
--- /dev/null
+++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DumpType.cs
@@ -0,0 +1,14 @@
+// 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.NETCore.Client
+{
+ public enum DumpType
+ {
+ Normal = 1,
+ WithHeap = 2,
+ Triage = 3,
+ Full = 4
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/EventPipeProvider.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/EventPipeProvider.cs
new file mode 100644
index 0000000000..332ecd20a3
--- /dev/null
+++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/EventPipeProvider.cs
@@ -0,0 +1,76 @@
+// 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.Tracing;
+using System.Linq;
+using System.Text;
+
+namespace Microsoft.Diagnostics.NETCore.Client
+{
+ public sealed class EventPipeProvider
+ {
+ public EventPipeProvider(string name, EventLevel eventLevel, long keywords = 0, IDictionary arguments = null)
+ {
+ Name = name;
+ EventLevel = eventLevel;
+ Keywords = keywords;
+ Arguments = arguments;
+ }
+
+ public long Keywords { get; }
+
+ public EventLevel EventLevel { get; }
+
+ public string Name { get; }
+
+ public IDictionary Arguments { get; }
+
+ public override string ToString()
+ {
+ return $"{Name}:0x{Keywords:X16}:{(uint)EventLevel}{(Arguments == null ? "" : $":{GetArgumentString()}")}";
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj == null || GetType() != obj.GetType())
+ {
+ return false;
+ }
+
+ return this == (EventPipeProvider)obj;
+ }
+
+ public override int GetHashCode()
+ {
+ int hash = 0;
+ hash ^= this.Name.GetHashCode();
+ hash ^= this.Keywords.GetHashCode();
+ hash ^= this.EventLevel.GetHashCode();
+ hash ^= GetArgumentString().GetHashCode();
+ return hash;
+ }
+
+ public static bool operator ==(EventPipeProvider left, EventPipeProvider right)
+ {
+ return left.ToString() == right.ToString();
+ }
+
+ public static bool operator !=(EventPipeProvider left, EventPipeProvider right)
+ {
+ return !(left == right);
+ }
+
+ internal string GetArgumentString()
+ {
+ if (Arguments == null)
+ {
+ return "";
+ }
+ return string.Join(";", Arguments.Select(a => $"{a.Key}={a.Value}"));
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/EventPipeSession.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/EventPipeSession.cs
new file mode 100644
index 0000000000..fc6fa847d4
--- /dev/null
+++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/EventPipeSession.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 System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+
+namespace Microsoft.Diagnostics.NETCore.Client
+{
+ public class EventPipeSession : IDisposable
+ {
+ private IEnumerable _providers;
+ private bool _requestRundown;
+ private int _circularBufferMB;
+ private long _sessionId;
+ private int _processId;
+ private bool disposedValue = false; // To detect redundant calls
+
+ internal EventPipeSession(int processId, IEnumerable providers, bool requestRundown, int circularBufferMB)
+ {
+ _processId = processId;
+ _providers = providers;
+ _requestRundown = requestRundown;
+ _circularBufferMB = circularBufferMB;
+
+ var config = new EventPipeSessionConfiguration(circularBufferMB, EventPipeSerializationFormat.NetTrace, providers, requestRundown);
+ var message = new IpcMessage(DiagnosticsServerCommandSet.EventPipe, (byte)EventPipeCommandId.CollectTracing2, config.SerializeV2());
+ EventStream = IpcClient.SendMessage(processId, message, out var response);
+ switch ((DiagnosticsServerCommandId)response.Header.CommandId)
+ {
+ case DiagnosticsServerCommandId.OK:
+ _sessionId = BitConverter.ToInt64(response.Payload, 0);
+ break;
+ case DiagnosticsServerCommandId.Error:
+ var hr = BitConverter.ToInt32(response.Payload, 0);
+ throw new ServerErrorException($"EventPipe session start failed (HRESULT: 0x{hr:X8})");
+ default:
+ throw new ServerErrorException($"EventPipe session start failed - Server responded with unknown command");
+ }
+ }
+
+ public Stream EventStream { get; }
+
+ ///
+ /// Stops the given session
+ ///
+ public void Stop()
+ {
+ Debug.Assert(_sessionId > 0);
+
+ byte[] payload = BitConverter.GetBytes(_sessionId);
+ var response = IpcClient.SendMessage(_processId, new IpcMessage(DiagnosticsServerCommandSet.EventPipe, (byte)EventPipeCommandId.StopTracing, payload));
+
+ switch ((DiagnosticsServerCommandId)response.Header.CommandId)
+ {
+ case DiagnosticsServerCommandId.OK:
+ return;
+ case DiagnosticsServerCommandId.Error:
+ var hr = BitConverter.ToInt32(response.Payload, 0);
+ throw new ServerErrorException($"EventPipe session stop failed (HRESULT: 0x{hr:X8})");
+ default:
+ throw new ServerErrorException($"EventPipe session stop failed - Server responded with unknown command");
+ }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ EventStream?.Dispose();
+ }
+ disposedValue = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/EventPipeSessionConfiguration.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/EventPipeSessionConfiguration.cs
new file mode 100644
index 0000000000..abda64566a
--- /dev/null
+++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/EventPipeSessionConfiguration.cs
@@ -0,0 +1,72 @@
+// 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.NETCore.Client
+{
+ internal enum EventPipeSerializationFormat
+ {
+ NetPerf,
+ NetTrace
+ }
+
+ internal class EventPipeSessionConfiguration
+ {
+ public EventPipeSessionConfiguration(int circularBufferSizeMB, EventPipeSerializationFormat format, IEnumerable providers, bool requestRundown=true)
+ {
+ if (circularBufferSizeMB == 0)
+ throw new ArgumentException($"Buffer size cannot be zero.");
+ if (format != EventPipeSerializationFormat.NetPerf && format != EventPipeSerializationFormat.NetTrace)
+ throw new ArgumentException("Unrecognized format");
+ if (providers == null)
+ throw new ArgumentNullException(nameof(providers));
+
+ CircularBufferSizeInMB = circularBufferSizeMB;
+ Format = format;
+ RequestRundown = requestRundown;
+ _providers = new List(providers);
+ }
+
+ public bool RequestRundown { get; }
+ public int CircularBufferSizeInMB { get; }
+ public EventPipeSerializationFormat Format { get; }
+
+ public IReadOnlyCollection Providers => _providers.AsReadOnly();
+
+ private readonly List _providers;
+
+ public byte[] SerializeV2()
+ {
+ byte[] serializedData = null;
+ using (var stream = new MemoryStream())
+ using (var writer = new BinaryWriter(stream))
+ {
+ writer.Write(CircularBufferSizeInMB);
+ writer.Write((uint)Format);
+ writer.Write(RequestRundown);
+
+ writer.Write(Providers.Count());
+ foreach (var provider in Providers)
+ {
+ writer.Write(provider.Keywords);
+ writer.Write((uint)provider.EventLevel);
+
+ writer.WriteString(provider.Name);
+ writer.WriteString(provider.GetArgumentString());
+ }
+
+ writer.Flush();
+ serializedData = stream.ToArray();
+ }
+
+ return serializedData;
+ }
+
+
+ }
+}
diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcClient.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcClient.cs
new file mode 100644
index 0000000000..7d309b670d
--- /dev/null
+++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcClient.cs
@@ -0,0 +1,126 @@
+// 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;
+using System.Collections.Generic;
+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;
+using System.Text.RegularExpressions;
+
+namespace Microsoft.Diagnostics.NETCore.Client
+{
+ internal class IpcClient
+ {
+ public static string IpcRootPath { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"\\.\pipe\" : Path.GetTempPath();
+ public static string DiagnosticsPortPattern { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"^dotnet-diagnostic-(\d+)$" : @"^dotnet-diagnostic-(\d+)-(\d+)-socket$";
+
+ private static double ConnectTimeoutMilliseconds { get; } = TimeSpan.FromSeconds(3).TotalMilliseconds;
+
+ ///
+ /// Get the OS Transport to be used for communicating with a dotnet process.
+ ///
+ /// The PID of the dotnet process to get the transport for
+ /// A System.IO.Stream wrapper around the transport
+ private static Stream GetTransport(int processId)
+ {
+ try
+ {
+ var process = Process.GetProcessById(processId);
+ }
+ catch (System.ArgumentException)
+ {
+ throw new ServerNotAvailableException($"Process {processId} is not running.");
+ }
+ catch (System.InvalidOperationException)
+ {
+ throw new ServerNotAvailableException($"Process {processId} seems to be elevated.");
+ }
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ string pipeName = $"dotnet-diagnostic-{processId}";
+ var namedPipe = new NamedPipeClientStream(
+ ".", pipeName, PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.Impersonation);
+ namedPipe.Connect((int)ConnectTimeoutMilliseconds);
+ return namedPipe;
+ }
+ else
+ {
+ string ipcPort;
+ try
+ {
+ ipcPort = Directory.GetFiles(IpcRootPath, $"dotnet-diagnostic-{processId}-*-socket") // Try best match.
+ .OrderByDescending(f => new FileInfo(f).LastWriteTime)
+ .FirstOrDefault();
+ if (ipcPort == null)
+ {
+ throw new ServerNotAvailableException($"Process {processId} not running compatible .NET Core runtime.");
+ }
+ }
+ catch (InvalidOperationException)
+ {
+ throw new ServerNotAvailableException($"Process {processId} not running compatible .NET Core runtime.");
+ }
+ string path = Path.Combine(IpcRootPath, ipcPort);
+ var remoteEP = new UnixDomainSocketEndPoint(path);
+
+ var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
+ socket.Connect(remoteEP);
+ return new NetworkStream(socket);
+ }
+ }
+
+ ///
+ /// Sends a single DiagnosticsIpc Message to the dotnet process with PID processId.
+ ///
+ /// The PID of the dotnet process
+ /// The DiagnosticsIpc Message to be sent
+ /// The response DiagnosticsIpc Message from the dotnet process
+ public static IpcMessage SendMessage(int processId, IpcMessage message)
+ {
+ using (var stream = GetTransport(processId))
+ {
+ Write(stream, message);
+ return Read(stream);
+ }
+ }
+
+ ///
+ /// Sends a single DiagnosticsIpc Message to the dotnet process with PID processId
+ /// and returns the Stream for reuse in Optional Continuations.
+ ///
+ /// The PID of the dotnet process
+ /// The DiagnosticsIpc Message to be sent
+ /// out var for response message
+ /// The response DiagnosticsIpc Message from the dotnet process
+ public static Stream SendMessage(int processId, IpcMessage message, out IpcMessage response)
+ {
+ var stream = GetTransport(processId);
+ Write(stream, message);
+ response = Read(stream);
+ return stream;
+ }
+
+ private static void Write(Stream stream, byte[] buffer)
+ {
+ stream.Write(buffer, 0, buffer.Length);
+ }
+
+ private static void Write(Stream stream, IpcMessage message)
+ {
+ Write(stream, message.Serialize());
+ }
+
+ private static IpcMessage Read(Stream stream)
+ {
+ return IpcMessage.Parse(stream);
+ }
+ }
+}
diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcCommands.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcCommands.cs
new file mode 100644
index 0000000000..7c870e3c1f
--- /dev/null
+++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcCommands.cs
@@ -0,0 +1,42 @@
+// 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.Text;
+
+namespace Microsoft.Diagnostics.NETCore.Client
+{
+ internal enum DiagnosticsServerCommandSet : byte
+ {
+ Dump = 0x01,
+ EventPipe = 0x02,
+ Profiler = 0x03,
+
+ Server = 0xFF,
+ }
+
+ internal enum DiagnosticsServerCommandId : byte
+ {
+ OK = 0x00,
+ Error = 0xFF,
+ }
+
+ internal enum EventPipeCommandId : byte
+ {
+ StopTracing = 0x01,
+ CollectTracing = 0x02,
+ CollectTracing2 = 0x03,
+ }
+
+ internal enum DumpCommandId : byte
+ {
+ GenerateCoreDump = 0x01,
+ }
+
+ internal enum ProfilerCommandId : byte
+ {
+ AttachProfiler = 0x01,
+ }
+}
diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcHeader.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcHeader.cs
new file mode 100644
index 0000000000..3a8fe59287
--- /dev/null
+++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcHeader.cs
@@ -0,0 +1,75 @@
+// 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.IO;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Microsoft.Diagnostics.NETCore.Client
+{
+ internal class IpcHeader
+ {
+ IpcHeader() { }
+
+ public IpcHeader(DiagnosticsServerCommandSet commandSet, byte commandId)
+ {
+ CommandSet = (byte)commandSet;
+ CommandId = commandId;
+ }
+
+ // the number of bytes for the DiagnosticsIpc::IpcHeader type in native code
+ public static readonly UInt16 HeaderSizeInBytes = 20;
+ private static readonly UInt16 MagicSizeInBytes = 14;
+
+ public byte[] Magic = DotnetIpcV1; // byte[14] in native code
+ public UInt16 Size = HeaderSizeInBytes;
+ public byte CommandSet;
+ public byte CommandId;
+ public UInt16 Reserved = 0x0000;
+
+
+ // Helper expression to quickly get V1 magic string for comparison
+ // should be 14 bytes long
+ public static byte[] DotnetIpcV1 => Encoding.ASCII.GetBytes("DOTNET_IPC_V1" + '\0');
+
+ public byte[] Serialize()
+ {
+ using (var stream = new MemoryStream())
+ using (var writer = new BinaryWriter(stream))
+ {
+ writer.Write(Magic);
+ Debug.Assert(Magic.Length == MagicSizeInBytes);
+ writer.Write(Size);
+ writer.Write(CommandSet);
+ writer.Write(CommandId);
+ writer.Write((UInt16)0x0000);
+ writer.Flush();
+ return stream.ToArray();
+ }
+ }
+
+ public static IpcHeader TryParse(BinaryReader reader)
+ {
+ IpcHeader header = new IpcHeader
+ {
+ Magic = reader.ReadBytes(14),
+ Size = reader.ReadUInt16(),
+ CommandSet = reader.ReadByte(),
+ CommandId = reader.ReadByte(),
+ Reserved = reader.ReadUInt16()
+ };
+
+ return header;
+ }
+
+ override public string ToString()
+ {
+ return $"{{ Magic={Magic}; Size={Size}; CommandSet={CommandSet}; CommandId={CommandId}; Reserved={Reserved} }}";
+ }
+ }
+}
diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcMessage.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcMessage.cs
new file mode 100644
index 0000000000..06cd79df25
--- /dev/null
+++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcMessage.cs
@@ -0,0 +1,108 @@
+// 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.Text;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Diagnostics.NETCore.Client
+{
+ ///
+ /// Different diagnostic message types that are handled by the runtime.
+ ///
+ internal enum DiagnosticsMessageType : uint
+ {
+ ///
+ /// Initiates core dump generation
+ ///
+ GenerateCoreDump = 1,
+ ///
+ /// Starts an EventPipe session that writes events to a file when the session is stopped or the application exits.
+ ///
+ StartEventPipeTracing = 1024,
+ ///
+ /// Stops an EventPipe session.
+ ///
+ StopEventPipeTracing = 1025,
+ ///
+ /// Starts an EventPipe session that sends events out-of-proc through IPC.
+ ///
+ CollectEventPipeTracing = 1026,
+ ///
+ /// Attaches a profiler to an existing process
+ ///
+ AttachProfiler = 2048,
+ }
+
+
+ ///
+ /// Message header used to send commands to the .NET Core runtime through IPC.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct MessageHeader
+ {
+ ///
+ /// Request type.
+ ///
+ public DiagnosticsMessageType RequestType;
+
+ ///
+ /// Remote process Id.
+ ///
+ public uint Pid;
+ }
+
+
+ internal class IpcMessage
+ {
+ public IpcMessage()
+ { }
+
+ public IpcMessage(IpcHeader header, byte[] payload)
+ {
+ Payload = payload;
+ Header = header;
+ }
+
+ public IpcMessage(DiagnosticsServerCommandSet commandSet, byte commandId, byte[] payload = null)
+ : this(new IpcHeader(commandSet, commandId), payload)
+ {
+ }
+
+ public byte[] Payload { get; private set; } = null;
+ public IpcHeader Header { get; private set; } = default;
+
+ public byte[] Serialize()
+ {
+ byte[] serializedData = null;
+ // Verify things will fit in the size capacity
+ Header.Size = checked((UInt16)(IpcHeader.HeaderSizeInBytes + Payload.Length)); ;
+ byte[] headerBytes = Header.Serialize();
+
+ using (var stream = new MemoryStream())
+ using (var writer = new BinaryWriter(stream))
+ {
+ writer.Write(headerBytes);
+ writer.Write(Payload);
+ writer.Flush();
+ serializedData = stream.ToArray();
+ }
+
+ return serializedData;
+ }
+
+ public static IpcMessage Parse(Stream stream)
+ {
+ IpcMessage message = new IpcMessage();
+ using (var reader = new BinaryReader(stream, Encoding.UTF8, true))
+ {
+ message.Header = IpcHeader.TryParse(reader);
+ message.Payload = reader.ReadBytes(message.Header.Size - IpcHeader.HeaderSizeInBytes);
+ return message;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj b/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj
new file mode 100644
index 0000000000..dcb33ec0f8
--- /dev/null
+++ b/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj
@@ -0,0 +1,15 @@
+
+
+ Library
+ netcoreapp2.1
+ Microsoft.Diagnostics.NETCore.Client
+ .NET Core Diagnostics Client Library
+ 0.1.0
+ true
+ Diagnostic
+ $(Description)
+ true
+ true
+ true
+
+
diff --git a/src/Tools/Common/Commands/ProcessStatus.cs b/src/Tools/Common/Commands/ProcessStatus.cs
index cdf2111fbd..8fc3ce68a8 100644
--- a/src/Tools/Common/Commands/ProcessStatus.cs
+++ b/src/Tools/Common/Commands/ProcessStatus.cs
@@ -8,7 +8,7 @@
using System.Linq;
using System.Text;
-using Microsoft.Diagnostics.Tools.RuntimeClient;
+using Microsoft.Diagnostics.NETCore.Client;
namespace Microsoft.Internal.Common.Commands
{
@@ -22,7 +22,7 @@ public static void PrintProcessStatus(IConsole console)
try
{
StringBuilder sb = new StringBuilder();
- var processes = EventPipeClient.ListAvailablePorts()
+ var processes = DiagnosticsClient.GetPublishedProcesses()
.Select(GetProcessById)
.Where(process => process != null)
.OrderBy(process => process.ProcessName)
diff --git a/src/Tools/dotnet-counters/CounterMonitor.cs b/src/Tools/dotnet-counters/CounterMonitor.cs
index d4097ecb22..835ee6f7a7 100644
--- a/src/Tools/dotnet-counters/CounterMonitor.cs
+++ b/src/Tools/dotnet-counters/CounterMonitor.cs
@@ -2,7 +2,7 @@
// 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;
+using Microsoft.Diagnostics.NETCore.Client;
using System;
using System.Collections.Generic;
using System.CommandLine;
@@ -26,9 +26,10 @@ public class CounterMonitor
private IConsole _console;
private ICounterRenderer _renderer;
private CounterFilter filter;
- private ulong _sessionId;
private string _output;
private bool pauseCmdSet;
+ private DiagnosticsClient _diagnosticsClient;
+ private EventPipeSession _session;
public CounterMonitor()
{
@@ -59,7 +60,7 @@ private void StopMonitor()
{
try
{
- EventPipeClient.StopTracing(_processId, _sessionId);
+ _session.Stop();
}
catch (EndOfStreamException ex)
{
@@ -91,6 +92,7 @@ public async Task Monitor(CancellationToken ct, List counter_list,
_processId = processId;
_interval = refreshInterval;
_renderer = new ConsoleWriter();
+ _diagnosticsClient = new DiagnosticsClient(processId);
return await Start();
}
@@ -99,7 +101,7 @@ public async Task Monitor(CancellationToken ct, List counter_list,
{
try
{
- EventPipeClient.StopTracing(_processId, _sessionId);
+ _session.Stop();
}
catch (Exception) {} // Swallow all exceptions for now.
@@ -118,6 +120,7 @@ public async Task Collect(CancellationToken ct, List counter_list,
_processId = processId;
_interval = refreshInterval;
_output = output;
+ _diagnosticsClient = new DiagnosticsClient(processId);
if (_output.Length == 0)
{
@@ -154,30 +157,6 @@ public async Task Collect(CancellationToken ct, List counter_list,
return 1;
}
-
- // Use EventPipe CollectTracing2 command to start monitoring. This may throw.
- private EventPipeEventSource RequestTracingV2(string providerString)
- {
- var configuration = new SessionConfigurationV2(
- circularBufferSizeMB: 1000,
- format: EventPipeSerializationFormat.NetTrace,
- requestRundown: false,
- providers: Trace.Extensions.ToProviders(providerString));
- var binaryReader = EventPipeClient.CollectTracing2(_processId, configuration, out _sessionId);
- return new EventPipeEventSource(binaryReader);
- }
-
- // Use EventPipe CollectTracing command to start monitoring. This may throw.
- private EventPipeEventSource RequestTracingV1(string providerString)
- {
- var configuration = new SessionConfiguration(
- circularBufferSizeMB: 1000,
- format: EventPipeSerializationFormat.NetTrace,
- providers: Trace.Extensions.ToProviders(providerString));
- var binaryReader = EventPipeClient.CollectTracing(_processId, configuration, out _sessionId);
- return new EventPipeEventSource(binaryReader);
- }
-
private string BuildProviderString()
{
string providerString;
@@ -257,23 +236,16 @@ private async Task Start()
Task monitorTask = new Task(() => {
try
{
- EventPipeEventSource source = null;
-
- try
- {
- source = RequestTracingV2(providerString);
- }
- catch (EventPipeUnknownCommandException)
- {
- // If unknown command exception is thrown, it's likely the app being monitored is
- // running an older version of runtime that doesn't support CollectTracingV2. Try again with V1.
- source = RequestTracingV1(providerString);
- }
-
+ _session = _diagnosticsClient.StartEventPipeSession(Trace.Extensions.ToProviders(providerString), false);
+ var source = new EventPipeEventSource(_session.EventStream);
source.Dynamic.All += DynamicAllMonitor;
_renderer.EventPipeSourceConnected();
source.Process();
}
+ catch (DiagnosticsClientException ex)
+ {
+ Console.WriteLine($"Failed to start the counter session: {ex.ToString()}");
+ }
catch (Exception ex)
{
Debug.WriteLine($"[ERROR] {ex.ToString()}");
diff --git a/src/Tools/dotnet-counters/Program.cs b/src/Tools/dotnet-counters/Program.cs
index 318d40fba9..ca6d20b9f9 100644
--- a/src/Tools/dotnet-counters/Program.cs
+++ b/src/Tools/dotnet-counters/Program.cs
@@ -11,7 +11,7 @@
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
-using Microsoft.Diagnostics.Tools.RuntimeClient;
+using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Internal.Common.Commands;
namespace Microsoft.Diagnostics.Tools.Counters
diff --git a/src/Tools/dotnet-counters/dotnet-counters.csproj b/src/Tools/dotnet-counters/dotnet-counters.csproj
index b8418707eb..4dc5bed68d 100644
--- a/src/Tools/dotnet-counters/dotnet-counters.csproj
+++ b/src/Tools/dotnet-counters/dotnet-counters.csproj
@@ -17,7 +17,7 @@
-
+
diff --git a/src/Tools/dotnet-dump/Dumper.cs b/src/Tools/dotnet-dump/Dumper.cs
index 5d94f515d0..3f1c37d50e 100644
--- a/src/Tools/dotnet-dump/Dumper.cs
+++ b/src/Tools/dotnet-dump/Dumper.cs
@@ -2,7 +2,7 @@
// 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;
+using Microsoft.Diagnostics.NETCore.Client;
using System;
using System.CommandLine;
using System.Diagnostics;
@@ -61,14 +61,11 @@ public async Task Collect(IConsole console, int processId, string output, b
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
- DiagnosticsHelpers.DumpType dumpType = type == DumpTypeOption.Heap ? DiagnosticsHelpers.DumpType.WithHeap : DiagnosticsHelpers.DumpType.Normal;
+ var client = new DiagnosticsClient(processId);
+ DumpType dumpType = type == DumpTypeOption.Heap ? DumpType.WithHeap : DumpType.Normal;
// Send the command to the runtime to initiate the core dump
- var hr = DiagnosticsHelpers.GenerateCoreDump(processId, output, dumpType, diag);
- if (hr != 0)
- {
- throw new InvalidOperationException($"Core dump generation FAILED 0x{hr:X8}");
- }
+ client.WriteDump(dumpType, output, diag);
}
else {
throw new PlatformNotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}");
@@ -81,7 +78,8 @@ ex is UnauthorizedAccessException ||
ex is PlatformNotSupportedException ||
ex is InvalidDataException ||
ex is InvalidOperationException ||
- ex is NotSupportedException)
+ ex is NotSupportedException ||
+ ex is DiagnosticsClientException)
{
console.Error.WriteLine($"{ex.Message}");
return 1;
diff --git a/src/Tools/dotnet-dump/Program.cs b/src/Tools/dotnet-dump/Program.cs
index 2f2d9aa041..c206bd3365 100644
--- a/src/Tools/dotnet-dump/Program.cs
+++ b/src/Tools/dotnet-dump/Program.cs
@@ -7,7 +7,7 @@
using System.CommandLine.Invocation;
using System.IO;
using System.Threading.Tasks;
-using Microsoft.Diagnostics.Tools.RuntimeClient;
+using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Internal.Common.Commands;
namespace Microsoft.Diagnostics.Tools.Dump
diff --git a/src/Tools/dotnet-dump/dotnet-dump.csproj b/src/Tools/dotnet-dump/dotnet-dump.csproj
index c6ba3724d2..d93281273e 100644
--- a/src/Tools/dotnet-dump/dotnet-dump.csproj
+++ b/src/Tools/dotnet-dump/dotnet-dump.csproj
@@ -23,7 +23,7 @@
-
+
diff --git a/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs
index 5ad9bd9245..4be21afb8e 100644
--- a/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs
+++ b/src/Tools/dotnet-gcdump/CommandLine/CollectCommandHandler.cs
@@ -2,7 +2,6 @@
// 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;
using System;
using System.Collections.Generic;
using System.CommandLine;
diff --git a/src/Tools/dotnet-gcdump/CommandLine/ProcessStatusCommandHandler.cs b/src/Tools/dotnet-gcdump/CommandLine/ProcessStatusCommandHandler.cs
index 056be27294..4e15083f0c 100644
--- a/src/Tools/dotnet-gcdump/CommandLine/ProcessStatusCommandHandler.cs
+++ b/src/Tools/dotnet-gcdump/CommandLine/ProcessStatusCommandHandler.cs
@@ -2,7 +2,6 @@
// 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;
using Microsoft.Internal.Common.Commands;
using System;
using System.CommandLine;
diff --git a/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs b/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs
index 440a2b2e51..511bb448f2 100644
--- a/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs
+++ b/src/Tools/dotnet-gcdump/DotNetHeapDump/EventPipeDotNetHeapDumper.cs
@@ -3,13 +3,14 @@
// See the LICENSE file in the project root for more information.
using Graphs;
-using Microsoft.Diagnostics.Tools.RuntimeClient;
+using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Diagnostics.Tracing;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@@ -48,7 +49,9 @@ public static bool DumpFromEventPipe(CancellationToken ct, int processID, Memory
bool fDone = false;
log.WriteLine("{0,5:n1}s: Creating type table flushing task", getElapsed().TotalSeconds);
- using (EventPipeSession typeFlushSession = new EventPipeSession(processID, new List { new Provider("Microsoft-DotNETCore-SampleProfiler") }, false))
+ using (EventPipeSessionController typeFlushSession = new EventPipeSessionController(processID, new List {
+ new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational)
+ }, false))
{
log.WriteLine("{0,5:n1}s: Flushing the type table", getElapsed().TotalSeconds);
typeFlushSession.Source.AllEvents += (traceEvent) => {
@@ -68,7 +71,9 @@ public static bool DumpFromEventPipe(CancellationToken ct, int processID, Memory
// Start the providers and trigger the GCs.
log.WriteLine("{0,5:n1}s: Requesting a .NET Heap Dump", getElapsed().TotalSeconds);
- using EventPipeSession gcDumpSession = new EventPipeSession(processID, new List { new Provider("Microsoft-Windows-DotNETRuntime", (ulong)(ClrTraceEventParser.Keywords.GCHeapSnapshot)) });
+ using EventPipeSessionController gcDumpSession = new EventPipeSessionController(processID, new List {
+ new EventPipeProvider("Microsoft-Windows-DotNETRuntime", EventLevel.Verbose, (long)(ClrTraceEventParser.Keywords.GCHeapSnapshot))
+ });
log.WriteLine("{0,5:n1}s: gcdump EventPipe Session started", getElapsed().TotalSeconds);
int gcNum = -1;
@@ -211,35 +216,29 @@ public static bool DumpFromEventPipe(CancellationToken ct, int processID, Memory
}
}
- internal class EventPipeSession : IDisposable
+ internal class EventPipeSessionController : IDisposable
{
- private List _providers;
- private Stream _eventPipeStream;
+ private List _providers;
+ private DiagnosticsClient _client;
+ private EventPipeSession _session;
private EventPipeEventSource _source;
- private ulong _sessionId;
private int _pid;
- public ulong SessionId => _sessionId;
- public IReadOnlyList Providers => _providers.AsReadOnly();
+ public IReadOnlyList Providers => _providers.AsReadOnly();
public EventPipeEventSource Source => _source;
- public EventPipeSession(int pid, List providers, bool requestRundown = true)
+ public EventPipeSessionController(int pid, List providers, bool requestRundown = true)
{
_pid = pid;
_providers = providers;
- var config = new SessionConfigurationV2(
- circularBufferSizeMB: 1024,
- format: EventPipeSerializationFormat.NetTrace,
- requestRundown: requestRundown,
- providers
- );
- _eventPipeStream = EventPipeClient.CollectTracing2(pid, config, out _sessionId);
- _source = new EventPipeEventSource(_eventPipeStream);
+ _client = new DiagnosticsClient(pid);
+ _session = _client.StartEventPipeSession(providers, requestRundown, 1024);
+ _source = new EventPipeEventSource(_session.EventStream);
}
public void EndSession()
{
- EventPipeClient.StopTracing(_pid, _sessionId);
+ _session.Stop();
}
#region IDisposable Support
@@ -251,7 +250,7 @@ protected virtual void Dispose(bool disposing)
{
if (disposing)
{
- _eventPipeStream?.Dispose();
+ _session?.Dispose();
_source?.Dispose();
}
disposedValue = true;
diff --git a/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj b/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj
index 6c09dc9963..11d02c0a2a 100644
--- a/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj
+++ b/src/Tools/dotnet-gcdump/dotnet-gcdump.csproj
@@ -17,7 +17,7 @@
-
+
diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs
index 0776c50598..41014bc5d2 100644
--- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs
+++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs
@@ -2,7 +2,7 @@
// 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;
+using Microsoft.Diagnostics.NETCore.Client;
using System;
using System.Collections.Generic;
using System.CommandLine;
@@ -64,7 +64,7 @@ private static async Task Collect(CancellationToken ct, IConsole console, i
Dictionary enabledBy = new Dictionary();
var providerCollection = Extensions.ToProviders(providers);
- foreach (Provider providerCollectionProvider in providerCollection)
+ foreach (EventPipeProvider providerCollectionProvider in providerCollection)
{
enabledBy[providerCollectionProvider.Name] = "--providers ";
}
@@ -92,11 +92,6 @@ private static async Task Collect(CancellationToken ct, IConsole console, i
PrintProviders(providerCollection, enabledBy);
var process = Process.GetProcessById(processId);
- var configuration = new SessionConfiguration(
- circularBufferSizeMB: buffersize,
- format: EventPipeSerializationFormat.NetTrace,
- providers: providerCollection);
-
var shouldExit = new ManualResetEvent(false);
var shouldStopAfterDuration = duration != default(TimeSpan);
var failed = false;
@@ -105,11 +100,20 @@ private static async Task Collect(CancellationToken ct, IConsole console, i
ct.Register(() => shouldExit.Set());
- ulong sessionId = 0;
- using (Stream stream = EventPipeClient.CollectTracing(processId, configuration, out sessionId))
+ var diagnosticsClient = new DiagnosticsClient(processId);
using (VirtualTerminalMode vTermMode = VirtualTerminalMode.TryEnable())
{
- if (sessionId == 0)
+ EventPipeSession session = null;
+ try
+ {
+ session = diagnosticsClient.StartEventPipeSession(providerCollection, true);
+ }
+ catch (DiagnosticsClientException e)
+ {
+ Console.Error.WriteLine($"Unable to start a tracing session: {e.ToString()}");
+ }
+
+ if (session == null)
{
Console.Error.WriteLine("Unable to create session.");
return ErrorCodes.SessionCreationError;
@@ -142,7 +146,7 @@ private static async Task Collect(CancellationToken ct, IConsole console, i
while (true)
{
- int nBytesRead = stream.Read(buffer, 0, buffer.Length);
+ int nBytesRead = session.EventStream.Read(buffer, 0, buffer.Length);
if (nBytesRead <= 0)
break;
fs.Write(buffer, 0, nBytesRead);
@@ -180,7 +184,7 @@ private static async Task Collect(CancellationToken ct, IConsole console, i
if (!terminated)
{
durationTimer?.Stop();
- EventPipeClient.StopTracing(processId, sessionId);
+ session.Stop();
}
await collectingTask;
}
@@ -200,7 +204,7 @@ private static async Task Collect(CancellationToken ct, IConsole console, i
}
}
- private static void PrintProviders(IReadOnlyList providers, Dictionary enabledBy)
+ private static void PrintProviders(IReadOnlyList providers, Dictionary enabledBy)
{
Console.Out.WriteLine("");
Console.Out.Write(String.Format("{0, -40}","Provider Name")); // +4 is for the tab
@@ -209,10 +213,12 @@ private static void PrintProviders(IReadOnlyList providers, Dictionary
Console.Out.Write("Enabled By\n");
foreach (var provider in providers)
{
- Console.Out.WriteLine(String.Format("{0, -80}", $"{provider.ToDisplayString()}") + $"{enabledBy[provider.Name]}");
+ Console.Out.WriteLine(String.Format("{0, -80}", $"{GetProviderDisplayString(provider)}") + $"{enabledBy[provider.Name]}");
}
Console.Out.WriteLine();
}
+ private static string GetProviderDisplayString(EventPipeProvider provider) =>
+ String.Format("{0, -40}", provider.Name) + String.Format("0x{0, -18}", $"{provider.Keywords:X16}") + String.Format("{0, -8}", provider.EventLevel.ToString() + $"({(int)provider.EventLevel})");
private static int prevBufferWidth = 0;
private static string clearLineString = "";
diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/ConvertCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/ConvertCommand.cs
index cddfb89089..ced5842d4b 100644
--- a/src/Tools/dotnet-trace/CommandLine/Commands/ConvertCommand.cs
+++ b/src/Tools/dotnet-trace/CommandLine/Commands/ConvertCommand.cs
@@ -2,7 +2,7 @@
// 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;
+using Microsoft.Diagnostics.NETCore.Client;
using System;
using System.IO;
using System.CommandLine;
diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/ListProcessesCommandHandler.cs b/src/Tools/dotnet-trace/CommandLine/Commands/ListProcessesCommandHandler.cs
index c8afc5695d..c30ec69404 100644
--- a/src/Tools/dotnet-trace/CommandLine/Commands/ListProcessesCommandHandler.cs
+++ b/src/Tools/dotnet-trace/CommandLine/Commands/ListProcessesCommandHandler.cs
@@ -2,7 +2,6 @@
// 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;
using Microsoft.Internal.Common.Commands;
using System;
using System.CommandLine;
diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/ListProfilesCommandHandler.cs b/src/Tools/dotnet-trace/CommandLine/Commands/ListProfilesCommandHandler.cs
index f8ec9089fd..a182ae3f4a 100644
--- a/src/Tools/dotnet-trace/CommandLine/Commands/ListProfilesCommandHandler.cs
+++ b/src/Tools/dotnet-trace/CommandLine/Commands/ListProfilesCommandHandler.cs
@@ -2,7 +2,7 @@
// 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;
+using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Diagnostics.Tracing.Parsers;
using System;
using System.Collections.Generic;
@@ -39,35 +39,35 @@ public static Command ListProfilesCommand() =>
handler: CommandHandler.Create(GetProfiles),
isHidden: false);
- // FIXME: Read from a config file!
internal static IEnumerable DotNETRuntimeProfiles { get; } = new[] {
new Profile(
"cpu-sampling",
- new Provider[] {
- new Provider("Microsoft-DotNETCore-SampleProfiler"),
- new Provider("Microsoft-Windows-DotNETRuntime", (ulong)ClrTraceEventParser.Keywords.Default, EventLevel.Informational),
+ new EventPipeProvider[] {
+ new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational),
+ new EventPipeProvider("Microsoft-Windows-DotNETRuntime", EventLevel.Informational, (long)ClrTraceEventParser.Keywords.Default)
},
"Useful for tracking CPU usage and general .NET runtime information. This is the default option if no profile or providers are specified."),
new Profile(
"gc-verbose",
- new Provider[] {
- new Provider(
+ new EventPipeProvider[] {
+ new EventPipeProvider(
name: "Microsoft-Windows-DotNETRuntime",
- keywords: (ulong)ClrTraceEventParser.Keywords.GC |
- (ulong)ClrTraceEventParser.Keywords.GCHandle |
- (ulong)ClrTraceEventParser.Keywords.Exception,
- eventLevel: EventLevel.Verbose
+ eventLevel: EventLevel.Verbose,
+ keywords: (long)ClrTraceEventParser.Keywords.GC |
+ (long)ClrTraceEventParser.Keywords.GCHandle |
+ (long)ClrTraceEventParser.Keywords.Exception
),
},
"Tracks GC collections and samples object allocations."),
new Profile(
"gc-collect",
- new Provider[] {
- new Provider(
+ new EventPipeProvider[] {
+ new EventPipeProvider(
name: "Microsoft-Windows-DotNETRuntime",
- keywords: (ulong)ClrTraceEventParser.Keywords.GC |
- (ulong)ClrTraceEventParser.Keywords.Exception,
- eventLevel: EventLevel.Informational),
+ eventLevel: EventLevel.Informational,
+ keywords: (long)ClrTraceEventParser.Keywords.GC |
+ (long)ClrTraceEventParser.Keywords.Exception
+ )
},
"Tracks GC collections only at very low overhead."),
};
diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/StopCommandHandler.cs b/src/Tools/dotnet-trace/CommandLine/Commands/StopCommandHandler.cs
deleted file mode 100644
index 073ffb4058..0000000000
--- a/src/Tools/dotnet-trace/CommandLine/Commands/StopCommandHandler.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-// 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;
-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 processId, ulong sessionId)
- {
- try
- {
- EventPipeClient.StopTracing(processId, 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),
- isHidden: true);
-
- private static Option SessionIdOption() =>
- new Option(
- new[] { "--session-id" },
- @"Session Id being recorded.",
- new Argument { Name = "SessionId" });
- }
-}
diff --git a/src/Tools/dotnet-trace/Extensions.cs b/src/Tools/dotnet-trace/Extensions.cs
index a1055eeeb3..59b54664ec 100644
--- a/src/Tools/dotnet-trace/Extensions.cs
+++ b/src/Tools/dotnet-trace/Extensions.cs
@@ -2,7 +2,7 @@
// 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;
+using Microsoft.Diagnostics.NETCore.Client;
using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
@@ -14,12 +14,12 @@ internal static class Extensions
{
private static EventLevel defaultEventLevel = EventLevel.Verbose;
- public static List ToProviders(string providers)
+ public static List ToProviders(string providers)
{
if (providers == null)
throw new ArgumentNullException(nameof(providers));
return string.IsNullOrWhiteSpace(providers) ?
- new List() : providers.Split(',').Select(ToProvider).ToList();
+ new List() : providers.Split(',').Select(ToProvider).ToList();
}
private static EventLevel GetEventLevel(string token)
@@ -51,7 +51,7 @@ private static EventLevel GetEventLevel(string token)
}
}
- private static Provider ToProvider(string provider)
+ private static EventPipeProvider ToProvider(string provider)
{
if (string.IsNullOrWhiteSpace(provider))
throw new ArgumentNullException(nameof(provider));
@@ -71,8 +71,8 @@ private static Provider ToProvider(string provider)
throw new ArgumentException("Provider name was not specified.");
// Keywords
- ulong keywords = tokens.Length > 1 && !string.IsNullOrWhiteSpace(tokens[1]) ?
- Convert.ToUInt64(tokens[1], 16) : ulong.MaxValue;
+ long keywords = tokens.Length > 1 && !string.IsNullOrWhiteSpace(tokens[1]) ?
+ Convert.ToInt64(tokens[1], 16) : -1;
// Level
EventLevel eventLevel = tokens.Length > 2 && !string.IsNullOrWhiteSpace(tokens[2]) ?
@@ -80,9 +80,57 @@ private static Provider ToProvider(string provider)
// Event counters
string filterData = tokens.Length > 3 ? tokens[3] : null;
- filterData = string.IsNullOrWhiteSpace(filterData) ? null : filterData;
+ var argument = string.IsNullOrWhiteSpace(filterData) ? null : ParseArgumentString(filterData);
+ return new EventPipeProvider(providerName, eventLevel, keywords, argument);
+ }
+
+ private static Dictionary ParseArgumentString(string argument)
+ {
+ if (argument == "")
+ {
+ return null;
+ }
+ var argumentDict = new Dictionary();
- return new Provider(providerName, keywords, eventLevel, filterData);
+ int keyStart = 0;
+ int keyEnd = 0;
+ int valStart = 0;
+ int valEnd = 0;
+ int curIdx = 0;
+ bool inQuote = false;
+ foreach (var c in argument)
+ {
+ if (inQuote)
+ {
+ if (c == '\"')
+ {
+ inQuote = false;
+ }
+ }
+ else
+ {
+ if (c == '=')
+ {
+ keyEnd = curIdx;
+ valStart = curIdx+1;
+ }
+ else if (c == ';')
+ {
+ valEnd = curIdx;
+ argumentDict.Add(argument.Substring(keyStart, keyEnd-keyStart), argument.Substring(valStart, valEnd-valStart));
+ keyStart = curIdx+1; // new key starts
+ }
+ else if (c == '\"')
+ {
+ inQuote = true;
+ }
+ }
+ curIdx += 1;
+ }
+ string key = argument.Substring(keyStart, keyEnd - keyStart);
+ string val = argument.Substring(valStart);
+ argumentDict.Add(key, val);
+ return argumentDict;
}
}
}
diff --git a/src/Tools/dotnet-trace/Profile.cs b/src/Tools/dotnet-trace/Profile.cs
index 7bc28c0887..078ef9d854 100644
--- a/src/Tools/dotnet-trace/Profile.cs
+++ b/src/Tools/dotnet-trace/Profile.cs
@@ -2,7 +2,7 @@
// 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;
+using Microsoft.Diagnostics.NETCore.Client;
using System.Collections.Generic;
using System.Linq;
@@ -10,32 +10,32 @@ namespace Microsoft.Diagnostics.Tools.Trace
{
internal sealed class Profile
{
- public Profile(string name, IEnumerable providers, string description)
+ public Profile(string name, IEnumerable providers, string description)
{
Name = name;
- Providers = providers == null ? Enumerable.Empty() : new List(providers).AsReadOnly();
+ Providers = providers == null ? Enumerable.Empty() : new List(providers).AsReadOnly();
Description = description;
}
public string Name { get; }
- public IEnumerable Providers { get; }
+ public IEnumerable Providers { get; }
public string Description { get; }
- public static void MergeProfileAndProviders(Profile selectedProfile, List providerCollection, Dictionary enabledBy)
+ public static void MergeProfileAndProviders(Profile selectedProfile, List providerCollection, Dictionary enabledBy)
{
- var profileProviders = new List();
+ var profileProviders = new List();
// If user defined a different key/level on the same provider via --providers option that was specified via --profile option,
// --providers option takes precedence. Go through the list of providers specified and only add it if it wasn't specified
// via --providers options.
if (selectedProfile.Providers != null)
{
- foreach (Provider selectedProfileProvider in selectedProfile.Providers)
+ foreach (EventPipeProvider selectedProfileProvider in selectedProfile.Providers)
{
bool shouldAdd = true;
- foreach (Provider providerCollectionProvider in providerCollection)
+ foreach (EventPipeProvider providerCollectionProvider in providerCollection)
{
if (providerCollectionProvider.Name.Equals(selectedProfileProvider.Name))
{
diff --git a/src/Tools/dotnet-trace/Program.cs b/src/Tools/dotnet-trace/Program.cs
index 48d00fb327..606f7ae62c 100644
--- a/src/Tools/dotnet-trace/Program.cs
+++ b/src/Tools/dotnet-trace/Program.cs
@@ -13,9 +13,6 @@ class Program
public static Task Main(string[] args)
{
var parser = new CommandLineBuilder()
-#if DEBUG
- .AddCommand(StopCommandHandler.StopCommand())
-#endif
.AddCommand(CollectCommandHandler.CollectCommand())
.AddCommand(ListProcessesCommandHandler.ListProcessesCommand())
.AddCommand(ListProfilesCommandHandler.ListProfilesCommand())
diff --git a/src/Tools/dotnet-trace/dotnet-trace.csproj b/src/Tools/dotnet-trace/dotnet-trace.csproj
index 43dfc2dc2a..2f7e371333 100644
--- a/src/Tools/dotnet-trace/dotnet-trace.csproj
+++ b/src/Tools/dotnet-trace/dotnet-trace.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/src/tests/Microsoft.Diagnostics.NETCore.Client/CommonHelper.cs b/src/tests/Microsoft.Diagnostics.NETCore.Client/CommonHelper.cs
new file mode 100644
index 0000000000..d1109110fb
--- /dev/null
+++ b/src/tests/Microsoft.Diagnostics.NETCore.Client/CommonHelper.cs
@@ -0,0 +1,28 @@
+// 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.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+using Microsoft.Diagnostics.TestHelpers;
+
+namespace Microsoft.Diagnostics.NETCore.Client
+{
+ public class CommonHelper
+ {
+ public static string HostExe = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
+ "..\\..\\..\\..\\..\\.dotnet\\dotnet.exe" : "../../../../../.dotnet/dotnet";
+
+ public static string GetTraceePath()
+ {
+ var curPath = Directory.GetCurrentDirectory();
+;
+ var traceePath = curPath.Replace("Microsoft.Diagnostics.NETCore.Client.UnitTests", "Tracee");
+
+ return Path.Combine(traceePath, "Tracee.dll");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/tests/Microsoft.Diagnostics.NETCore.Client/EventPipeProviderTests.cs b/src/tests/Microsoft.Diagnostics.NETCore.Client/EventPipeProviderTests.cs
new file mode 100644
index 0000000000..5f66f28e32
--- /dev/null
+++ b/src/tests/Microsoft.Diagnostics.NETCore.Client/EventPipeProviderTests.cs
@@ -0,0 +1,113 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.NETCore.Client;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.Tracing;
+using Xunit;
+
+
+namespace Microsoft.Diagnostics.NETCore.Client
+{
+
+ ///
+ /// Suite of tests that test top-level commands
+ ///
+ public class EventPipeProviderTests
+ {
+ [Fact]
+ public void EqualTest1()
+ {
+ EventPipeProvider provider1 = new EventPipeProvider("myProvider", EventLevel.Informational);
+ EventPipeProvider provider2 = new EventPipeProvider("myProvider", EventLevel.Informational);
+ Assert.True(provider1 == provider2);
+ }
+
+ [Fact]
+ public void EqualTest2()
+ {
+ EventPipeProvider provider1 = new EventPipeProvider("Microsoft-Windows-DotNETRuntime", EventLevel.Verbose, (long)(-1));
+ EventPipeProvider provider2 = new EventPipeProvider("Microsoft-Windows-DotNETRuntime", EventLevel.Verbose, (long)(-1));
+ Assert.True(provider1 == provider2);
+ }
+
+ [Fact]
+ public void EqualTest3()
+ {
+ EventPipeProvider provider1 = new EventPipeProvider(
+ "System.Runtime",
+ EventLevel.Verbose,
+ (long)(-1),
+ new Dictionary() {
+ { "EventCounterIntervalSec", "1" }
+ });
+ EventPipeProvider provider2 = new EventPipeProvider(
+ "System.Runtime",
+ EventLevel.Verbose,
+ (long)(-1),
+ new Dictionary() {
+ { "EventCounterIntervalSec", "1" }
+ });
+ Assert.True(provider1 == provider2);
+ }
+
+ [Fact]
+ public void InEqualityTest()
+ {
+ var providers = new EventPipeProvider[5];
+ providers[0] = new EventPipeProvider("myProvider", EventLevel.Informational);
+ providers[1] = new EventPipeProvider("myProvider", EventLevel.Informational, (long)(-1));
+ providers[2] = new EventPipeProvider("myProvider", EventLevel.Verbose, (long)(-1));
+ providers[3] = new EventPipeProvider(
+ "myProvider",
+ EventLevel.Verbose,
+ (long)(-1),
+ new Dictionary() {
+ { "EventCounterIntervalSec", "1" }
+ });
+ providers[4] = new EventPipeProvider(
+ "myProvider",
+ EventLevel.Verbose,
+ (long)(-1),
+ new Dictionary() {
+ { "EventCounterIntervalSec", "2" }
+ });
+
+ for (var i = 0; i < providers.Length-1; i++)
+ {
+ for (var j = i+1; j < providers.Length; j++)
+ {
+ Assert.True(providers[i] != providers[j]);
+ }
+ }
+ }
+
+ [Fact]
+ public void ToStringTest1()
+ {
+ var provider = new EventPipeProvider("MyProvider", EventLevel.Verbose, (long)(0xdeadbeef));
+ Assert.Equal("MyProvider:0x00000000DEADBEEF:5", provider.ToString());
+ }
+
+ [Fact]
+ public void ToStringTest2()
+ {
+ var provider1 = new EventPipeProvider("MyProvider", EventLevel.Verbose, (long)(0xdeadbeef),
+ new Dictionary()
+ {
+ { "key1", "value1" },
+ });
+ var provider2 = new EventPipeProvider("MyProvider", EventLevel.Verbose, (long)(0xdeadbeef),
+ new Dictionary()
+ {
+ { "key1", "value1" },
+ { "key2", "value2" }
+ });
+ Assert.Equal("MyProvider:0x00000000DEADBEEF:5:key1=value1", provider1.ToString());
+ Assert.Equal("MyProvider:0x00000000DEADBEEF:5:key1=value1;key2=value2", provider2.ToString());
+ }
+ }
+}
diff --git a/src/tests/Microsoft.Diagnostics.NETCore.Client/EventPipeSessionTests.cs b/src/tests/Microsoft.Diagnostics.NETCore.Client/EventPipeSessionTests.cs
new file mode 100644
index 0000000000..6344f47491
--- /dev/null
+++ b/src/tests/Microsoft.Diagnostics.NETCore.Client/EventPipeSessionTests.cs
@@ -0,0 +1,115 @@
+// 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.Diagnostics.Tracing;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+using Microsoft.Diagnostics.Tracing;
+using Microsoft.Diagnostics.TestHelpers;
+using Microsoft.Diagnostics.NETCore.Client;
+
+namespace Microsoft.Diagnostics.NETCore.Client
+{
+ public class EventPipeSessionTests
+ {
+ private readonly ITestOutputHelper output;
+
+ public EventPipeSessionTests(ITestOutputHelper outputHelper)
+ {
+ output = outputHelper;
+ }
+
+ ///
+ /// A simple test that checks if we can create an EventPipeSession on a child process
+ ///
+ [Fact]
+ public void BasicEventPipeSessionTest()
+ {
+ TestRunner runner = new TestRunner(CommonHelper.GetTraceePath(), output);
+ runner.Start(3000);
+ DiagnosticsClient client = new DiagnosticsClient(runner.Pid);
+ using (var session = client.StartEventPipeSession(new List()
+ {
+ new EventPipeProvider("Microsoft-Windows-DotNETRuntime", EventLevel.Informational)
+ }))
+ {
+ Assert.True(session.EventStream != null);
+ }
+ runner.Stop();
+ }
+
+ ///
+ /// Checks if we can create an EventPipeSession and can get some expected events out of it.
+ ///
+ [Fact]
+ public void EventPipeSessionStreamTest()
+ {
+ TestRunner runner = new TestRunner(CommonHelper.GetTraceePath(), output);
+ runner.Start(5000);
+ DiagnosticsClient client = new DiagnosticsClient(runner.Pid);
+ runner.PrintStatus();
+ output.WriteLine($"[{DateTime.Now.ToString()}] Trying to start an EventPipe session on process {runner.Pid}");
+ using (var session = client.StartEventPipeSession(new List()
+ {
+ new EventPipeProvider("System.Runtime", EventLevel.Informational, 0, new Dictionary() {
+ { "EventCounterIntervalSec", "1" }
+ })
+ }))
+ {
+ var evntCnt = 0;
+
+ Task streamTask = Task.Run(() => {
+ var source = new EventPipeEventSource(session.EventStream);
+ source.Dynamic.All += (TraceEvent obj) => {
+ output.WriteLine("Got an event");
+ evntCnt += 1;
+ };
+ try
+ {
+ source.Process();
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("Error encountered while processing events");
+ Assert.Equal("", e.ToString());
+ }
+ finally
+ {
+ runner.Stop();
+ }
+ });
+ output.WriteLine("Waiting for stream Task");
+ streamTask.Wait(10000);
+ output.WriteLine("Done waiting for stream Task");
+ Assert.True(evntCnt > 0);
+ }
+ }
+
+ ///
+ /// Tries to start an EventPipe session on a non-existent process
+ ///
+ [Fact]
+ public void EventPipeSessionUnavailableTest()
+ {
+ List pids = new List(DiagnosticsClient.GetPublishedProcesses());
+ int arbitraryPid = 1;
+
+ DiagnosticsClient client = new DiagnosticsClient(arbitraryPid);
+
+ Assert.Throws(() => client.StartEventPipeSession(new List()
+ {
+ new EventPipeProvider("Microsoft-Windows-DotNETRuntime", EventLevel.Informational)
+ }));
+ }
+ }
+}
diff --git a/src/tests/Microsoft.Diagnostics.NETCore.Client/GetPublishedProcessesTests.cs b/src/tests/Microsoft.Diagnostics.NETCore.Client/GetPublishedProcessesTests.cs
new file mode 100644
index 0000000000..7ecc0e8281
--- /dev/null
+++ b/src/tests/Microsoft.Diagnostics.NETCore.Client/GetPublishedProcessesTests.cs
@@ -0,0 +1,75 @@
+// 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.IO;
+using System.Threading;
+using System.Runtime.InteropServices;
+using Xunit;
+using Xunit.Abstractions;
+
+using Microsoft.Diagnostics.TestHelpers;
+using Microsoft.Diagnostics.NETCore.Client;
+
+namespace Microsoft.Diagnostics.NETCore.Client
+{
+
+ ///
+ /// Suite of tests that test top-level commands
+ ///
+ public class GetPublishedProcessesTest
+ {
+ private readonly ITestOutputHelper output;
+
+ public GetPublishedProcessesTest(ITestOutputHelper outputHelper)
+ {
+ output = outputHelper;
+ }
+
+ [Fact]
+ public void PublishedProcessTest1()
+ {
+ TestRunner runner = new TestRunner(CommonHelper.GetTraceePath(), output);
+ runner.Start(3000);
+ List publishedProcesses = new List(DiagnosticsClient.GetPublishedProcesses());
+ foreach(int p in publishedProcesses)
+ {
+ output.WriteLine($"[{DateTime.Now.ToString()}] Saw published process {p}");
+ }
+ Assert.Contains(publishedProcesses, p => p == runner.Pid);
+ runner.Stop();
+ }
+
+ [Fact]
+ public void MultiplePublishedProcessTest()
+ {
+ TestRunner[] runner = new TestRunner[3];
+ int[] pids = new int[3];
+
+ for (var i = 0; i < 3; i++)
+ {
+ runner[i] = new TestRunner(CommonHelper.GetTraceePath(), output);
+ runner[i].Start(500);
+ pids[i] = runner[i].Pid;
+ }
+ List publishedProcesses = new List(DiagnosticsClient.GetPublishedProcesses());
+ foreach(int p in publishedProcesses)
+ {
+ output.WriteLine($"[{DateTime.Now.ToString()}] Saw published process {p}");
+ }
+
+ for (var i = 0; i < 3; i++)
+ {
+ Assert.Contains(publishedProcesses, p => p == pids[i]);
+ }
+
+ for (var i = 0 ; i < 3; i++)
+ {
+ runner[i].Stop();
+ }
+ }
+ }
+}
diff --git a/src/tests/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.UnitTests.csproj b/src/tests/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.UnitTests.csproj
new file mode 100644
index 0000000000..772f23d5a6
--- /dev/null
+++ b/src/tests/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.UnitTests.csproj
@@ -0,0 +1,13 @@
+
+
+
+ netcoreapp3.0
+
+
+
+
+
+
+
+
+
diff --git a/src/tests/Microsoft.Diagnostics.NETCore.Client/TestRunner.cs b/src/tests/Microsoft.Diagnostics.NETCore.Client/TestRunner.cs
new file mode 100644
index 0000000000..420bde839b
--- /dev/null
+++ b/src/tests/Microsoft.Diagnostics.NETCore.Client/TestRunner.cs
@@ -0,0 +1,86 @@
+// 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.Threading;
+using Xunit;
+using Xunit.Abstractions;
+
+using Microsoft.Diagnostics.TestHelpers;
+
+using Microsoft.Diagnostics.NETCore.Client;
+
+namespace Microsoft.Diagnostics.NETCore.Client
+{
+ public class TestRunner
+ {
+ private Process testProcess;
+ private ProcessStartInfo startInfo;
+ private ITestOutputHelper outputHelper;
+
+ public TestRunner(string testExePath, ITestOutputHelper _outputHelper=null)
+ {
+ startInfo = new ProcessStartInfo(CommonHelper.HostExe, testExePath);
+ startInfo.UseShellExecute = false;
+ startInfo.RedirectStandardOutput = true;
+ outputHelper = _outputHelper;
+ }
+
+ public void AddEnvVar(string key, string value)
+ {
+ startInfo.EnvironmentVariables[key] = value;
+ }
+
+ public void Start(int timeoutInMS=0)
+ {
+ if (outputHelper != null)
+ outputHelper.WriteLine("$[{DateTime.Now.ToString()}] Launching test: " + startInfo.FileName);
+
+ testProcess = Process.Start(startInfo);
+
+ if (testProcess == null)
+ {
+ outputHelper.WriteLine($"Could not start process: " + startInfo.FileName);
+ }
+
+ if (testProcess.HasExited)
+ {
+ outputHelper.WriteLine($"Process " + startInfo.FileName + " came back as exited");
+ }
+
+ if (outputHelper != null)
+ {
+ outputHelper.WriteLine($"[{DateTime.Now.ToString()}] Successfuly started process {testProcess.Id}");
+ outputHelper.WriteLine($"Have total {testProcess.Modules.Count} modules loaded");
+ }
+
+ outputHelper.WriteLine($"[{DateTime.Now.ToString()}] Sleeping for {timeoutInMS} ms.");
+ Thread.Sleep(timeoutInMS);
+ outputHelper.WriteLine($"[{DateTime.Now.ToString()}] Done sleeping. Ready to test.");
+ }
+
+ public void Stop()
+ {
+ testProcess.Kill();
+ }
+
+ public int Pid {
+ get { return testProcess.Id; }
+ }
+
+ public void PrintStatus()
+ {
+ if (testProcess.HasExited)
+ {
+ outputHelper.WriteLine($"Process {testProcess.Id} status: Exited");
+ }
+ else
+ {
+ outputHelper.WriteLine($"Process {testProcess.Id} status: Running");
+ }
+ }
+ }
+}
diff --git a/src/tests/Microsoft.Diagnostics.NETCore.Client/WriteDumpTests.cs b/src/tests/Microsoft.Diagnostics.NETCore.Client/WriteDumpTests.cs
new file mode 100644
index 0000000000..f438f882c1
--- /dev/null
+++ b/src/tests/Microsoft.Diagnostics.NETCore.Client/WriteDumpTests.cs
@@ -0,0 +1,124 @@
+// 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.Diagnostics.Tracing;
+using System.IO;
+using System.Runtime.InteropServices;
+using Xunit;
+using Xunit.Abstractions;
+
+using Microsoft.Diagnostics.Tracing;
+using Microsoft.Diagnostics.TestHelpers;
+using Microsoft.Diagnostics.NETCore.Client;
+
+namespace Microsoft.Diagnostics.NETCore.Client
+{
+ public class WriteDumpTests
+ {
+ private readonly ITestOutputHelper output;
+
+ public WriteDumpTests(ITestOutputHelper outputHelper)
+ {
+ output = outputHelper;
+ }
+
+ ///
+ /// A simple test that writes a single dump file
+ ///
+ [Fact]
+ public void BasicWriteDumpTest()
+ {
+ var dumpPath = "./myDump.dmp";
+ TestRunner runner = new TestRunner(CommonHelper.GetTraceePath(), output);
+ runner.Start(3000);
+ DiagnosticsClient client = new DiagnosticsClient(runner.Pid);
+
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ Assert.Throws(() => client.WriteDump(DumpType.Normal, dumpPath));
+ }
+ else
+ {
+ output.WriteLine($"Requesting dump at {DateTime.Now.ToString()}");
+ client.WriteDump(DumpType.Normal, dumpPath);
+ Assert.True(File.Exists(dumpPath));
+ File.Delete(dumpPath);
+ }
+ runner.Stop();
+ }
+
+ ///
+ /// A test that writes all the different types of dump file
+ ///
+ [Fact]
+ public void WriteAllDumpTypesTest()
+ {
+ var normalDumpPath = "./myDump-normal.dmp";
+ var heapDumpPath = "./myDump-heap.dmp";
+ var triageDumpPath = "./myDump-triage.dmp";
+ var fullDumpPath = "./myDump-full.dmp";
+ TestRunner runner = new TestRunner(CommonHelper.GetTraceePath(), output);
+ runner.Start(3000);
+ DiagnosticsClient client = new DiagnosticsClient(runner.Pid);
+
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ Assert.Throws(() => client.WriteDump(DumpType.Normal, normalDumpPath));
+ Assert.Throws(() => client.WriteDump(DumpType.WithHeap, heapDumpPath));
+ Assert.Throws(() => client.WriteDump(DumpType.Triage, triageDumpPath));
+ Assert.Throws(() => client.WriteDump(DumpType.Full, fullDumpPath));
+ }
+ else
+ {
+ // Write each type of dump
+ output.WriteLine($"Requesting dump at {DateTime.Now.ToString()}");
+ client.WriteDump(DumpType.Normal, normalDumpPath);
+ client.WriteDump(DumpType.WithHeap, heapDumpPath);
+ client.WriteDump(DumpType.Triage, triageDumpPath);
+ client.WriteDump(DumpType.Full, fullDumpPath);
+
+ // Check they were all created
+ Assert.True(File.Exists(normalDumpPath));
+ Assert.True(File.Exists(heapDumpPath));
+ Assert.True(File.Exists(triageDumpPath));
+ Assert.True(File.Exists(fullDumpPath));
+
+ // Remove them
+ File.Delete(normalDumpPath);
+ File.Delete(heapDumpPath);
+ File.Delete(triageDumpPath);
+ File.Delete(fullDumpPath);
+ }
+ runner.Stop();
+ }
+
+ ///
+ /// A test that tries to write a dump of a non-existent process
+ ///
+ [Fact]
+ public void WriteDumpFailTest()
+ {
+ List pids = new List(DiagnosticsClient.GetPublishedProcesses());
+ int arbitraryPid = 1;
+ string dumpPath = "./myDump.dmp";
+ while (pids.Contains(arbitraryPid))
+ {
+ arbitraryPid += 1;
+ }
+
+ var client = new DiagnosticsClient(arbitraryPid);
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ Assert.Throws(() => client.WriteDump(DumpType.Normal, dumpPath));
+ }
+ else
+ {
+ Assert.Throws(() => client.WriteDump(DumpType.Normal, "./myDump.dmp"));
+ }
+ }
+ }
+}
diff --git a/src/tests/Tracee/Program.cs b/src/tests/Tracee/Program.cs
new file mode 100644
index 0000000000..c6143cf4d1
--- /dev/null
+++ b/src/tests/Tracee/Program.cs
@@ -0,0 +1,21 @@
+// 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.Threading;
+
+namespace Tracee
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ // Runs for max of 30 sec
+ for(var i = 0; i < 30; i++)
+ {
+ Thread.Sleep(1000);
+ }
+ }
+ }
+}
diff --git a/src/tests/Tracee/Tracee.csproj b/src/tests/Tracee/Tracee.csproj
new file mode 100644
index 0000000000..58b042cb66
--- /dev/null
+++ b/src/tests/Tracee/Tracee.csproj
@@ -0,0 +1,7 @@
+
+
+ Exe
+ $(BuildProjectFramework)
+ netcoreapp3.0;
+
+
diff --git a/src/tests/dotnet-trace/ProfileProviderMerging.cs b/src/tests/dotnet-trace/ProfileProviderMerging.cs
index 6cf17b120b..c438728da3 100644
--- a/src/tests/dotnet-trace/ProfileProviderMerging.cs
+++ b/src/tests/dotnet-trace/ProfileProviderMerging.cs
@@ -1,10 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-
+using Microsoft.Diagnostics.NETCore.Client;
using System;
using Xunit;
-using Microsoft.Diagnostics.Tools.RuntimeClient;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics.Tracing;
@@ -21,7 +20,7 @@ public void DuplicateProvider_CorrectlyOverrides(string profileName, string prov
{
Dictionary enabledBy = new Dictionary();
- List parsedProviders = Extensions.ToProviders(providerToParse);
+ List parsedProviders = Extensions.ToProviders(providerToParse);
foreach (var provider in parsedProviders)
{
@@ -35,10 +34,9 @@ public void DuplicateProvider_CorrectlyOverrides(string profileName, string prov
Profile.MergeProfileAndProviders(selectedProfile, parsedProviders, enabledBy);
var enabledProvider = parsedProviders.SingleOrDefault(p => p.Name == "Microsoft-Windows-DotNETRuntime");
- Assert.True(enabledProvider != default(Provider));
// Assert that our specified provider overrides the version in the profile
- Assert.True(enabledProvider.Keywords == UInt64.MaxValue);
+ Assert.True(enabledProvider.Keywords == (long)(-1));
Assert.True(enabledProvider.EventLevel == EventLevel.Verbose);
Assert.True(enabledBy[enabledProvider.Name] == "--providers");
}
diff --git a/src/tests/dotnet-trace/ProviderParsing.cs b/src/tests/dotnet-trace/ProviderParsing.cs
index 8d1930d4b9..a311a57466 100644
--- a/src/tests/dotnet-trace/ProviderParsing.cs
+++ b/src/tests/dotnet-trace/ProviderParsing.cs
@@ -2,9 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using Microsoft.Diagnostics.NETCore.Client;
using System;
using Xunit;
-using Microsoft.Diagnostics.Tools.RuntimeClient;
using System.Collections.Generic;
using System.Linq;
@@ -17,26 +17,28 @@ public class ProviderParsingTests
[InlineData("VeryCoolProvider:1:5:FilterAndPayloadSpecs=\"QuotedValue\"")]
public void ValidProvider_CorrectlyParses(string providerToParse)
{
- List parsedProviders = Extensions.ToProviders(providerToParse);
+ List parsedProviders = Extensions.ToProviders(providerToParse);
Assert.True(parsedProviders.Count == 1);
- Provider provider = parsedProviders.First();
+ EventPipeProvider provider = parsedProviders.First();
Assert.True(provider.Name == "VeryCoolProvider");
Assert.True(provider.Keywords == 1);
Assert.True(provider.EventLevel == System.Diagnostics.Tracing.EventLevel.Verbose);
- Assert.True(provider.FilterData == "FilterAndPayloadSpecs=\"QuotedValue\"");
+ Assert.True(provider.Arguments.Count == 1);
+ Assert.True(provider.Arguments["FilterAndPayloadSpecs"] == "\"QuotedValue\"");
}
[Theory]
[InlineData("VeryCoolProvider:0x1:5:FilterAndPayloadSpecs=\"QuotedValue:-\r\nQuoted/Value\"")]
public void ValidProviderFilter_CorrectlyParses(string providerToParse)
{
- List parsedProviders = Extensions.ToProviders(providerToParse);
+ List parsedProviders = Extensions.ToProviders(providerToParse);
Assert.True(parsedProviders.Count == 1);
- Provider provider = parsedProviders.First();
+ EventPipeProvider provider = parsedProviders.First();
Assert.True(provider.Name == "VeryCoolProvider");
Assert.True(provider.Keywords == 1);
Assert.True(provider.EventLevel == System.Diagnostics.Tracing.EventLevel.Verbose);
- Assert.True(provider.FilterData == "FilterAndPayloadSpecs=\"QuotedValue:-\r\nQuoted/Value\"");
+ Assert.True(provider.Arguments.Count == 1);
+ Assert.True(provider.Arguments["FilterAndPayloadSpecs"] == "\"QuotedValue:-\r\nQuoted/Value\"");
}
[Theory]
@@ -57,16 +59,16 @@ public void InvalidProvider_CorrectlyThrows(string providerToParse)
[Theory]
[InlineData("VeryCoolProvider:0xFFFFFFFFFFFFFFFF:5:FilterAndPayloadSpecs=\"QuotedValue\"")]
- [InlineData("VeryCoolProvider::5:FilterAndPayloadSpecs=\"QuotedValue\"")]
public void ValidProviderKeyword_CorrectlyParses(string providerToParse)
{
- List parsedProviders = Extensions.ToProviders(providerToParse);
+ List parsedProviders = Extensions.ToProviders(providerToParse);
Assert.True(parsedProviders.Count == 1);
- Provider provider = parsedProviders.First();
+ EventPipeProvider provider = parsedProviders.First();
Assert.True(provider.Name == "VeryCoolProvider");
- Assert.True(provider.Keywords == ulong.MaxValue);
+ Assert.True(provider.Keywords == (long)(-1));
Assert.True(provider.EventLevel == System.Diagnostics.Tracing.EventLevel.Verbose);
- Assert.True(provider.FilterData == "FilterAndPayloadSpecs=\"QuotedValue\"");
+ Assert.True(provider.Arguments.Count == 1);
+ Assert.True(provider.Arguments["FilterAndPayloadSpecs"] == "\"QuotedValue\"");
}
[Theory]
@@ -74,13 +76,14 @@ public void ValidProviderKeyword_CorrectlyParses(string providerToParse)
[InlineData("VeryCoolProvider:::FilterAndPayloadSpecs=\"QuotedValue\"")]
public void ValidProviderEventLevel_CorrectlyParses(string providerToParse)
{
- List parsedProviders = Extensions.ToProviders(providerToParse);
+ List parsedProviders = Extensions.ToProviders(providerToParse);
Assert.True(parsedProviders.Count == 1);
- Provider provider = parsedProviders.First();
+ EventPipeProvider provider = parsedProviders.First();
Assert.True(provider.Name == "VeryCoolProvider");
- Assert.True(provider.Keywords == ulong.MaxValue);
+ Assert.True(provider.Keywords == (long)(-1));
Assert.True(provider.EventLevel == System.Diagnostics.Tracing.EventLevel.Verbose);
- Assert.True(provider.FilterData == "FilterAndPayloadSpecs=\"QuotedValue\"");
+ Assert.True(provider.Arguments.Count == 1);
+ Assert.True(provider.Arguments["FilterAndPayloadSpecs"] == "\"QuotedValue\"");
}
[Theory]
@@ -104,26 +107,29 @@ public void Invalidkeyword_CorrectlyThrows(string providerToParse)
[InlineData("ProviderOne:1:1:FilterAndPayloadSpecs=\"QuotedValue\",ProviderTwo:0x2:2:key=value,ProviderThree:0x3:3:key=value")]
public void MultipleValidProviders_CorrectlyParses(string providersToParse)
{
- List parsedProviders = Extensions.ToProviders(providersToParse);
+ List parsedProviders = Extensions.ToProviders(providersToParse);
Assert.True(parsedProviders.Count == 3);
- Provider providerOne = parsedProviders[0];
- Provider providerTwo = parsedProviders[1];
- Provider providerThree = parsedProviders[2];
+ EventPipeProvider providerOne = parsedProviders[0];
+ EventPipeProvider providerTwo = parsedProviders[1];
+ EventPipeProvider providerThree = parsedProviders[2];
Assert.True(providerOne.Name == "ProviderOne");
Assert.True(providerOne.Keywords == 1);
Assert.True(providerOne.EventLevel == System.Diagnostics.Tracing.EventLevel.Critical);
- Assert.True(providerOne.FilterData == "FilterAndPayloadSpecs=\"QuotedValue\"");
+ Assert.True(providerOne.Arguments.Count == 1);
+ Assert.True(providerOne.Arguments["FilterAndPayloadSpecs"] == "\"QuotedValue\"");
Assert.True(providerTwo.Name == "ProviderTwo");
Assert.True(providerTwo.Keywords == 2);
Assert.True(providerTwo.EventLevel == System.Diagnostics.Tracing.EventLevel.Error);
- Assert.True(providerTwo.FilterData == "key=value");
+ Assert.True(providerTwo.Arguments.Count == 1);
+ Assert.True(providerTwo.Arguments["key"] == "value");
Assert.True(providerThree.Name == "ProviderThree");
Assert.True(providerThree.Keywords == 3);
Assert.True(providerThree.EventLevel == System.Diagnostics.Tracing.EventLevel.Warning);
- Assert.True(providerThree.FilterData == "key=value");
+ Assert.True(providerThree.Arguments.Count == 1);
+ Assert.True(providerThree.Arguments["key"] == "value");
}
[Theory]
@@ -157,26 +163,29 @@ public void MultipleValidProvidersWithOneInvalidKeyword_CorrectlyThrows(string p
[InlineData("ProviderOne:0x1:1:FilterAndPayloadSpecs=\"QuotedValue:-\r\nQuoted/Value:-A=B;C=D;\",ProviderTwo:2:2:FilterAndPayloadSpecs=\"QuotedValue\",ProviderThree:3:3:FilterAndPayloadSpecs=\"QuotedValue:-\r\nQuoted/Value:-A=B;C=D;\"")]
public void MultipleProvidersWithComplexFilters_CorrectlyParse(string providersToParse)
{
- List parsedProviders = Extensions.ToProviders(providersToParse);
+ List parsedProviders = Extensions.ToProviders(providersToParse);
Assert.True(parsedProviders.Count == 3);
- Provider providerOne = parsedProviders[0];
- Provider providerTwo = parsedProviders[1];
- Provider providerThree = parsedProviders[2];
+ EventPipeProvider providerOne = parsedProviders[0];
+ EventPipeProvider providerTwo = parsedProviders[1];
+ EventPipeProvider providerThree = parsedProviders[2];
Assert.True(providerOne.Name == "ProviderOne");
Assert.True(providerOne.Keywords == 1);
Assert.True(providerOne.EventLevel == System.Diagnostics.Tracing.EventLevel.Critical);
- Assert.True(providerOne.FilterData == "FilterAndPayloadSpecs=\"QuotedValue:-\r\nQuoted/Value:-A=B;C=D;\"");
+ Assert.True(providerOne.Arguments.Count == 1);
+ Assert.True(providerOne.Arguments["FilterAndPayloadSpecs"] == "\"QuotedValue:-\r\nQuoted/Value:-A=B;C=D;\"");
Assert.True(providerTwo.Name == "ProviderTwo");
Assert.True(providerTwo.Keywords == 2);
Assert.True(providerTwo.EventLevel == System.Diagnostics.Tracing.EventLevel.Error);
- Assert.True(providerTwo.FilterData == "FilterAndPayloadSpecs=\"QuotedValue\"");
+ Assert.True(providerTwo.Arguments.Count == 1);
+ Assert.True(providerTwo.Arguments["FilterAndPayloadSpecs"]== "\"QuotedValue\"");
Assert.True(providerThree.Name == "ProviderThree");
Assert.True(providerThree.Keywords == 3);
Assert.True(providerThree.EventLevel == System.Diagnostics.Tracing.EventLevel.Warning);
- Assert.True(providerThree.FilterData == "FilterAndPayloadSpecs=\"QuotedValue:-\r\nQuoted/Value:-A=B;C=D;\"");
+ Assert.True(providerThree.Arguments.Count == 1);
+ Assert.True(providerThree.Arguments["FilterAndPayloadSpecs"] == "\"QuotedValue:-\r\nQuoted/Value:-A=B;C=D;\"");
}
[Theory]
@@ -184,7 +193,7 @@ public void MultipleProvidersWithComplexFilters_CorrectlyParse(string providersT
[InlineData("ProviderOne:0x1:verbose")]
public void TextLevelProviderSpecVerbose_CorrectlyParse(string providerToParse)
{
- List parsedProviders = Extensions.ToProviders(providerToParse);
+ List parsedProviders = Extensions.ToProviders(providerToParse);
Assert.True(parsedProviders.Count == 1);
Assert.True(parsedProviders[0].Name == "ProviderOne");
Assert.True(parsedProviders[0].Keywords == 1);
@@ -196,7 +205,7 @@ public void TextLevelProviderSpecVerbose_CorrectlyParse(string providerToParse)
[InlineData("ProviderOne:0x1:INFORMATIONAL")]
public void TextLevelProviderSpecInformational_CorrectlyParse(string providerToParse)
{
- List parsedProviders = Extensions.ToProviders(providerToParse);
+ List parsedProviders = Extensions.ToProviders(providerToParse);
Assert.True(parsedProviders.Count == 1);
Assert.True(parsedProviders[0].Name == "ProviderOne");
Assert.True(parsedProviders[0].Keywords == 1);
@@ -208,7 +217,7 @@ public void TextLevelProviderSpecInformational_CorrectlyParse(string providerToP
[InlineData("ProviderOne:0x1:LogAlwayS")]
public void TextLevelProviderSpecLogAlways_CorrectlyParse(string providerToParse)
{
- List parsedProviders = Extensions.ToProviders(providerToParse);
+ List parsedProviders = Extensions.ToProviders(providerToParse);
Assert.True(parsedProviders.Count == 1);
Assert.True(parsedProviders[0].Name == "ProviderOne");
Assert.True(parsedProviders[0].Keywords == 1);
@@ -220,7 +229,7 @@ public void TextLevelProviderSpecLogAlways_CorrectlyParse(string providerToParse
[InlineData("ProviderOne:0x1:ERRor")]
public void TextLevelProviderSpecError_CorrectlyParse(string providerToParse)
{
- List parsedProviders = Extensions.ToProviders(providerToParse);
+ List parsedProviders = Extensions.ToProviders(providerToParse);
Assert.True(parsedProviders.Count == 1);
Assert.True(parsedProviders[0].Name == "ProviderOne");
Assert.True(parsedProviders[0].Keywords == 1);
@@ -232,7 +241,7 @@ public void TextLevelProviderSpecError_CorrectlyParse(string providerToParse)
[InlineData("ProviderOne:0x1:CRITICAL")]
public void TextLevelProviderSpecCritical_CorrectlyParse(string providerToParse)
{
- List parsedProviders = Extensions.ToProviders(providerToParse);
+ List parsedProviders = Extensions.ToProviders(providerToParse);
Assert.True(parsedProviders.Count == 1);
Assert.True(parsedProviders[0].Name == "ProviderOne");
Assert.True(parsedProviders[0].Keywords == 1);
@@ -244,7 +253,7 @@ public void TextLevelProviderSpecCritical_CorrectlyParse(string providerToParse)
[InlineData("ProviderOne:0x1:warning")]
public void TextLevelProviderSpecWarning_CorrectlyParse(string providerToParse)
{
- List parsedProviders = Extensions.ToProviders(providerToParse);
+ List parsedProviders = Extensions.ToProviders(providerToParse);
Assert.True(parsedProviders.Count == 1);
Assert.True(parsedProviders[0].Name == "ProviderOne");
Assert.True(parsedProviders[0].Keywords == 1);