Rename dotnet-collect to dotnet-trace, and deleted stale readme. (#140)
authorJosé Rivero <jorive@microsoft.com>
Sat, 16 Mar 2019 03:01:18 +0000 (20:01 -0700)
committerGitHub <noreply@github.com>
Sat, 16 Mar 2019 03:01:18 +0000 (20:01 -0700)
27 files changed:
.gitignore
diagnostics.sln
src/Tools/README.md [deleted file]
src/Tools/dotnet-collect/CollectionConfiguration.cs [deleted file]
src/Tools/dotnet-collect/CollectionProfile.cs [deleted file]
src/Tools/dotnet-collect/ConfigPathDetector.cs [deleted file]
src/Tools/dotnet-collect/EtwCollector.cs [deleted file]
src/Tools/dotnet-collect/EventCollector.cs [deleted file]
src/Tools/dotnet-collect/EventPipeCollector.cs [deleted file]
src/Tools/dotnet-collect/EventSpec.cs [deleted file]
src/Tools/dotnet-collect/KnownData.cs [deleted file]
src/Tools/dotnet-collect/LoggerSpec.cs [deleted file]
src/Tools/dotnet-collect/Program.cs [deleted file]
src/Tools/dotnet-collect/StringParser.cs [deleted file]
src/Tools/dotnet-collect/dotnet-collect.csproj [deleted file]
src/Tools/dotnet-trace/CollectionConfiguration.cs [new file with mode: 0644]
src/Tools/dotnet-trace/CollectionProfile.cs [new file with mode: 0644]
src/Tools/dotnet-trace/ConfigPathDetector.cs [new file with mode: 0644]
src/Tools/dotnet-trace/EtwCollector.cs [new file with mode: 0644]
src/Tools/dotnet-trace/EventCollector.cs [new file with mode: 0644]
src/Tools/dotnet-trace/EventPipeCollector.cs [new file with mode: 0644]
src/Tools/dotnet-trace/EventSpec.cs [new file with mode: 0644]
src/Tools/dotnet-trace/KnownData.cs [new file with mode: 0644]
src/Tools/dotnet-trace/LoggerSpec.cs [new file with mode: 0644]
src/Tools/dotnet-trace/Program.cs [new file with mode: 0644]
src/Tools/dotnet-trace/StringParser.cs [new file with mode: 0644]
src/Tools/dotnet-trace/dotnet-trace.csproj [new file with mode: 0644]

index 522fe0d80acd9fb6f0451aa00db6eb86f63d3157..5f458400c8dd70f5b803c144ee3d8218e95add31 100644 (file)
 [Aa]rtifacts/
 [Dd]ebug/
 [Rr]elease/
-x64/
 [Bb]in/
 [Oo]bj/
+[Pp]ackages/
+x64/
 .dotnet/
 .packages/
 .tools/
index fa4e6d875b414147d1df5015d5f99e1a2d66e847..14376379494a5a59c3ca284e67d598bd6708c6dc 100644 (file)
@@ -23,7 +23,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{B62728C8
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-analyze", "src\Tools\dotnet-analyze\dotnet-analyze.csproj", "{1576314E-F823-4A24-BC90-22282AB33353}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-collect", "src\Tools\dotnet-collect\dotnet-collect.csproj", "{D9972D61-4B43-4007-B983-C02718DD8D33}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-trace", "src\Tools\dotnet-trace\dotnet-trace.csproj", "{718350FA-2DD9-4950-BA41-D7A7F66DAC91}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-dump", "src\Tools\dotnet-dump\dotnet-dump.csproj", "{43D41DE9-7CCC-4DCB-A68A-B9099E538125}"
 EndProject
@@ -341,46 +341,6 @@ Global
                {1576314E-F823-4A24-BC90-22282AB33353}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
                {1576314E-F823-4A24-BC90-22282AB33353}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
                {1576314E-F823-4A24-BC90-22282AB33353}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Checked|Any CPU.ActiveCfg = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Checked|Any CPU.Build.0 = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Checked|ARM.ActiveCfg = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Checked|ARM.Build.0 = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Checked|ARM64.ActiveCfg = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Checked|ARM64.Build.0 = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Checked|x64.ActiveCfg = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Checked|x64.Build.0 = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Checked|x86.ActiveCfg = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Checked|x86.Build.0 = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Debug|Any CPU.Build.0 = Debug|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Debug|ARM.ActiveCfg = Debug|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Debug|ARM.Build.0 = Debug|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Debug|ARM64.ActiveCfg = Debug|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Debug|ARM64.Build.0 = Debug|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Debug|x64.ActiveCfg = Debug|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Debug|x64.Build.0 = Debug|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Debug|x86.ActiveCfg = Debug|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Debug|x86.Build.0 = Debug|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Release|Any CPU.ActiveCfg = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Release|Any CPU.Build.0 = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Release|ARM.ActiveCfg = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Release|ARM.Build.0 = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Release|ARM64.ActiveCfg = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Release|ARM64.Build.0 = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Release|x64.ActiveCfg = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Release|x64.Build.0 = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Release|x86.ActiveCfg = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.Release|x86.Build.0 = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
-               {D9972D61-4B43-4007-B983-C02718DD8D33}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
                {43D41DE9-7CCC-4DCB-A68A-B9099E538125}.Checked|Any CPU.ActiveCfg = Release|Any CPU
                {43D41DE9-7CCC-4DCB-A68A-B9099E538125}.Checked|Any CPU.Build.0 = Release|Any CPU
                {43D41DE9-7CCC-4DCB-A68A-B9099E538125}.Checked|ARM.ActiveCfg = Release|Any CPU
@@ -677,6 +637,46 @@ Global
                {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
                {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
                {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|Any CPU.Build.0 = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|ARM.ActiveCfg = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|ARM.Build.0 = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|ARM64.ActiveCfg = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|ARM64.Build.0 = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|x64.ActiveCfg = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|x64.Build.0 = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|x86.ActiveCfg = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Checked|x86.Build.0 = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|ARM.ActiveCfg = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|ARM.Build.0 = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|ARM64.Build.0 = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|x64.ActiveCfg = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|x64.Build.0 = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|x86.ActiveCfg = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Debug|x86.Build.0 = Debug|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|Any CPU.Build.0 = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|ARM.ActiveCfg = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|ARM.Build.0 = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|ARM64.ActiveCfg = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|ARM64.Build.0 = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|x64.ActiveCfg = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|x64.Build.0 = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|x86.ActiveCfg = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.Release|x86.Build.0 = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
@@ -691,7 +691,6 @@ Global
                {D52C65C4-2C7D-45E6-9F5C-6F3A96796018} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
                {B62728C8-1267-4043-B46F-5537BBAEC692} = {19FAB78C-3351-4911-8F0C-8C6056401740}
                {1576314E-F823-4A24-BC90-22282AB33353} = {B62728C8-1267-4043-B46F-5537BBAEC692}
-               {D9972D61-4B43-4007-B983-C02718DD8D33} = {B62728C8-1267-4043-B46F-5537BBAEC692}
                {43D41DE9-7CCC-4DCB-A68A-B9099E538125} = {B62728C8-1267-4043-B46F-5537BBAEC692}
                {41F59D85-FC36-3015-861B-F177863252BC} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
                {A9A7C879-C320-3327-BB84-16E1322E17AE} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
@@ -701,6 +700,7 @@ Global
                {41351955-16D5-48D7-AF4C-AF25F5FB2E78} = {B62728C8-1267-4043-B46F-5537BBAEC692}
                {ED27F39F-DF5C-4E22-87E0-EC5B5873B503} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
                {90CF2633-58F0-44EE-943B-D70207455F20} = {19FAB78C-3351-4911-8F0C-8C6056401740}
+               {718350FA-2DD9-4950-BA41-D7A7F66DAC91} = {B62728C8-1267-4043-B46F-5537BBAEC692}
        EndGlobalSection
        GlobalSection(ExtensibilityGlobals) = postSolution
                SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0}
diff --git a/src/Tools/README.md b/src/Tools/README.md
deleted file mode 100644 (file)
index f8a6112..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-# .NET Diagnostics Tools
-
-## [dotnet-dump](src/dotnet-dump)
-
-A cross-platform tool to collect memory dumps of .NET processes:
-
-```
-Captures memory dumps of .NET processes
-
-Usage: dotnet-dump [options]
-
-Options:
-  -p|--process-id <PROCESS_ID>    The ID of the process to collect a memory dump for
-  -o|--output <OUTPUT_DIRECTORY>  The directory to write the dump to. Defaults to the current working directory.
-  -?|-h|--help                    Show help information
-```
-
-This tool collects dumps of .NET processes. Currently supports Windows and Linux (using the `createdump` command). The tool currently supports capturing a dump immediately (when invoked). We plan to add support for "daemonizing" (running the tool the in the background) and having it capture a dump on certain conditions:
-
-* When the CPU usage goes over a certain amount
-* When memory usage goes over a certain amount
-* When a certain EventPipe event is emitted (this will inherently "lag" a bit since the events are buffered)
-
-## [dotnet-collect](src/dotnet-collect)
-
-A cross-platform tool for collecting data from Managed EventSources and .NET Runtime events using EventPipe
-
-```
-Collects Event Traces from .NET processes
-
-Usage: dotnet-collect [options]
-
-Options:
-  -p|--process-id <PROCESS_ID>    Filter to only the process with the specified process ID.
-  -c|--config-path <CONFIG_PATH>  The path of the EventPipe config file to write, must be named [AppName].eventpipeconfig and be in the base directory for a managed app.
-  -o|--output <OUTPUT_DIRECTORY>  The directory to write the trace to. Defaults to the current working directory.
-  --buffer <BUFFER_SIZE_IN_MB>    The size of the in-memory circular buffer in megabytes.
-  --provider <PROVIDER_SPEC>      An EventPipe provider to enable. A string in the form '<provider name>:<keywords>:<level>'.
-  -?|-h|--help                    Show help information
- ```
-
- This tool collects EventPipe traces. Currently it is limited to using the file-based configuration added in .NET Core 2.2. To use it, you must manually provide the destination path for the `eventpipeconfig` file. For example:
- ```
- dotnet-collect -c ./path/to/my/app/MyApp.eventpipeconfig --provider Microsoft-Windows-DotNETRuntime
- ```
-
- The default behavior is to put traces in the directory from which you launched `dotnet-collect`. Traces are in files of the form `[appname].[processId].netperf` and can be viewed with [PerfView](https://github.com/Microsoft/PerfView).
-
-## [dotnet-analyze](src/dotnet-analyze)
-
-An SOS-like "REPL" for exploring .NET memory dumps (based on [CLRMD](https://github.com/Microsoft/clrmd)).
-
-```
-Inspect a crash dump using interactive commands
-
-Usage: dotnet-analyze [arguments] [options]
-
-Arguments:
-<DUMP>        The path to the dump file to analyze.
-
-Options:
-  -?|-h|--help  Show help information
-```
-
-When you launch this command, a REPL prompt is provided:
-
-```
-Loading crash dump C:\Users\anurse\Desktop\dotnet-18956-20181012-153829-088.dmp...
-Ready to process analysis commands. Type 'help' to list available commands or 'help [command]' to get detailed help on a command.
-Type 'quit' or 'exit' to exit the analysis session.
->
-```
-
-The following commands are available:
-* `quit` (alias `exit`) - Exit the tool
-* `help` - List commands and help information (not yet implemented ;))
-* `threads` (alias `~`) - List thread
-* `DumpStack` - Dump managed stack trace for the current thread. A little like SOS's `!DumpStack`
-* `DumpHeap` - Dump information about objects on the heap, grouped by type. A little like SOS's `!DumpHeap -stat`
diff --git a/src/Tools/dotnet-collect/CollectionConfiguration.cs b/src/Tools/dotnet-collect/CollectionConfiguration.cs
deleted file mode 100644 (file)
index e7af348..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.Tracing;
-using System.Linq;
-using System.Text;
-
-namespace Microsoft.Diagnostics.Tools.Collect
-{
-    public class CollectionConfiguration
-    {
-        public int? ProcessId { get; set; }
-        public string OutputPath { get; set; }
-        public int? CircularMB { get; set; }
-        public IList<EventSpec> Providers { get; set; } = new List<EventSpec>();
-        public IList<LoggerSpec> Loggers { get; set; } = new List<LoggerSpec>();
-
-        internal string ToConfigString()
-        {
-            var builder = new StringBuilder();
-            if (ProcessId != null)
-            {
-                builder.AppendLine($"ProcessId={ProcessId.Value}");
-            }
-            if (!string.IsNullOrEmpty(OutputPath))
-            {
-                builder.AppendLine($"OutputPath={OutputPath}");
-            }
-            if (CircularMB != null)
-            {
-                builder.AppendLine($"CircularMB={CircularMB}");
-            }
-            if (Providers != null && Providers.Count > 0)
-            {
-                builder.AppendLine($"Providers={SerializeProviders(Enumerable.Concat(Providers, GenerateLoggerSpec(Loggers)))}");
-            }
-            return builder.ToString();
-        }
-
-        public void AddProfile(CollectionProfile profile)
-        {
-            foreach (var provider in profile.EventSpecs)
-            {
-                Providers.Add(provider);
-            }
-
-            foreach (var logger in profile.LoggerSpecs)
-            {
-                Loggers.Add(logger);
-            }
-        }
-
-        private string SerializeProviders(IEnumerable<EventSpec> providers) => string.Join(",", providers.Select(s => s.ToConfigString()));
-
-        private IEnumerable<EventSpec> GenerateLoggerSpec(IList<LoggerSpec> loggers)
-        {
-            if (loggers.Count > 0)
-            {
-                var filterSpec = new StringBuilder();
-                foreach (var logger in loggers)
-                {
-                    if (string.IsNullOrEmpty(logger.Level))
-                    {
-                        filterSpec.Append($"{logger.Prefix}");
-                    }
-                    else
-                    {
-                        filterSpec.Append($"{logger.Prefix}:{logger.Level}");
-                    }
-                    filterSpec.Append(";");
-                }
-
-                // Remove trailing ';'
-                filterSpec.Length -= 1;
-
-                yield return new EventSpec(
-                    provider: "Microsoft-Extensions-Logging",
-                    keywords: 0x04 | 0x08, // FormattedMessage | JsonMessage (source: https://github.com/aspnet/Extensions/blob/aa7fa91cfc8f6ff078b020a428bcad71ae7a32ab/src/Logging/Logging.EventSource/src/LoggingEventSource.cs#L95)
-                    level: EventLevel.LogAlways,
-                    parameters: new Dictionary<string, string>() {
-                    { "FilterSpecs", filterSpec.ToString() }
-                    });
-            }
-        }
-    }
-}
diff --git a/src/Tools/dotnet-collect/CollectionProfile.cs b/src/Tools/dotnet-collect/CollectionProfile.cs
deleted file mode 100644 (file)
index 0e3f03e..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-
-namespace Microsoft.Diagnostics.Tools.Collect
-{
-    public class CollectionProfile
-    {
-        public static readonly string DefaultProfileName = "Default";
-
-        public string Name { get; }
-        public string Description { get; }
-        public IReadOnlyList<EventSpec> EventSpecs { get; }
-        public IReadOnlyList<LoggerSpec> LoggerSpecs { get; }
-
-        public CollectionProfile(string name, string description, IEnumerable<EventSpec> eventSpecs, IEnumerable<LoggerSpec> loggerSpecs)
-        {
-            Name = name;
-            Description = description;
-            EventSpecs = eventSpecs.ToList();
-            LoggerSpecs = loggerSpecs.ToList();
-        }
-    }
-}
diff --git a/src/Tools/dotnet-collect/ConfigPathDetector.cs b/src/Tools/dotnet-collect/ConfigPathDetector.cs
deleted file mode 100644 (file)
index 050f546..0000000
+++ /dev/null
@@ -1,212 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices;
-
-namespace Microsoft.Diagnostics.Tools.Collect
-{
-    internal static class ConfigPathDetector
-    {
-        private static readonly HashSet<string> _managedExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".exe", ".dll" };
-
-        // Known .NET Platform Assemblies
-        private static readonly HashSet<string> _platformAssemblies = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
-        {
-            "System.Private.CoreLib.dll",
-            "clrjit.dll",
-        };
-
-        internal static string TryDetectConfigPath(int processId)
-        {
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
-            {
-                return Windows.TryDetectConfigPath(processId);
-            }
-            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
-            {
-                return Linux.TryDetectConfigPath(processId);
-            }
-            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
-            {
-                return OSX.TryDetectConfigPath(processId);
-            }
-            return null;
-        }
-
-        private static class OSX
-        {
-            // This is defined in proc_info.h (https://opensource.apple.com/source/xnu/xnu-1228/bsd/sys/proc_info.h)
-            private const int PROC_PIDPATHINFO_MAXSIZE = 1024 * 4;
-
-            /// <summary>
-            /// Gets the full path to the executable file identified by the specified PID
-            /// </summary>
-            /// <param name="pid">The PID of the running process</param>
-            /// <param name="buffer">A pointer to an allocated block of memory that will be filled with the process path</param>
-            /// <param name="bufferSize">The size of the buffer, should be PROC_PIDPATHINFO_MAXSIZE</param>
-            /// <returns>Returns the length of the path returned on success</returns>
-            [DllImport("libproc.dylib", SetLastError = true)]
-            private static extern unsafe int proc_pidpath(
-                int pid, 
-                byte* buffer, 
-                uint bufferSize);
-
-            /// <summary>
-            /// Gets the full path to the executable file identified by the specified PID
-            /// </summary>
-            /// <param name="pid">The PID of the running process</param>
-            /// <returns>Returns the full path to the process executable</returns>
-            internal static unsafe string proc_pidpath(int pid)
-            {
-                // The path is a fixed buffer size, so use that and trim it after
-                int result = 0;
-                byte* pBuffer = stackalloc byte[PROC_PIDPATHINFO_MAXSIZE];
-
-                // WARNING - Despite its name, don't try to pass in a smaller size than specified by PROC_PIDPATHINFO_MAXSIZE.
-                // For some reason libproc returns -1 if you specify something that's NOT EQUAL to PROC_PIDPATHINFO_MAXSIZE
-                // even if you declare your buffer to be smaller/larger than this size. 
-                result = proc_pidpath(pid, pBuffer, (uint)(PROC_PIDPATHINFO_MAXSIZE * sizeof(byte)));
-                if (result <= 0)
-                {
-                    throw new InvalidOperationException("Could not find procpath using libproc.");
-                }
-
-                // OS X uses UTF-8. The conversion may not strip off all trailing \0s so remove them here
-                return System.Text.Encoding.UTF8.GetString(pBuffer, result);
-            }
-
-            public static string TryDetectConfigPath(int processId)
-            {
-                try
-                {
-                    var path = proc_pidpath(processId);
-                    var candidateDir = Path.GetDirectoryName(path);
-                    var candidateName = Path.GetFileNameWithoutExtension(path);
-                    return Path.Combine(candidateDir, $"{candidateName}.eventpipeconfig");
-                }
-                catch (InvalidOperationException)
-                {
-                    return null;  // The pinvoke above may fail - return null in that case to handle error gracefully.
-                }
-            }
-        }
-
-        private static class Linux
-        {
-            public static string TryDetectConfigPath(int processId)
-            {
-                // Read procfs maps list
-                var lines = File.ReadAllLines($"/proc/{processId}/maps");
-
-                foreach (var line in lines)
-                {
-                    try
-                    {
-                        var parser = new StringParser(line, separator: ' ', skipEmpty: true);
-
-                        // Skip the address range
-                        parser.MoveNext();
-
-                        var permissions = parser.MoveAndExtractNext();
-
-                        // The managed entry point is Read-Only, Non-Execute and Shared.
-                        if (!string.Equals(permissions, "r--s", StringComparison.Ordinal))
-                        {
-                            continue;
-                        }
-
-                        // Skip offset, dev, and inode
-                        parser.MoveNext();
-                        parser.MoveNext();
-                        parser.MoveNext();
-
-                        // Parse the path
-                        if (!parser.MoveNext())
-                        {
-                            continue;
-                        }
-
-                        var path = parser.ExtractCurrentToEnd();
-                        var candidateDir = Path.GetDirectoryName(path);
-                        var candidateName = Path.GetFileNameWithoutExtension(path);
-                        if (File.Exists(Path.Combine(candidateDir, $"{candidateName}.deps.json")))
-                        {
-                            return Path.Combine(candidateDir, $"{candidateName}.eventpipeconfig");
-                        }
-                    }
-                    catch (ArgumentNullException) { return null; }
-                    catch (InvalidDataException) { return null; }
-                    catch (InvalidOperationException) { return null; }
-                }
-                return null;
-            }
-        }
-
-        private static class Windows
-        {
-            private static readonly HashSet<string> _knownNativeLibraries = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
-            {
-                // .NET Core Host
-                "dotnet.exe",
-                "hostfxr.dll",
-                "hostpolicy.dll",
-                "coreclr.dll",
-
-                // Windows Native Libraries
-                "ntdll.dll",
-                "kernel32.dll",
-                "kernelbase.dll",
-                "apphelp.dll",
-                "ucrtbase.dll",
-                "advapi32.dll",
-                "msvcrt.dll",
-                "sechost.dll",
-                "rpcrt4.dll",
-                "ole32.dll",
-                "combase.dll",
-                "bcryptPrimitives.dll",
-                "gdi32.dll",
-                "gdi32full.dll",
-                "msvcp_win.dll",
-                "user32.dll",
-                "win32u.dll",
-                "oleaut32.dll",
-                "shlwapi.dll",
-                "version.dll",
-                "bcrypt.dll",
-                "imm32.dll",
-                "kernel.appcore.dll",
-            };
-
-            public static string TryDetectConfigPath(int processId)
-            {
-                var process = Process.GetProcessById(processId);
-
-                // Iterate over modules
-                foreach (var module in process.Modules.Cast<ProcessModule>())
-                {
-                    // Filter out things that aren't exes and dlls (useful on Unix/macOS to skip native libraries)
-                    var extension = Path.GetExtension(module.FileName);
-                    var name = Path.GetFileName(module.FileName);
-                    if (_managedExtensions.Contains(extension) && !_knownNativeLibraries.Contains(name) && !_platformAssemblies.Contains(name))
-                    {
-                        var candidateDir = Path.GetDirectoryName(module.FileName);
-                        var appName = Path.GetFileNameWithoutExtension(module.FileName);
-
-                        // Check for the deps.json file
-                        // TODO: Self-contained apps?
-                        if (File.Exists(Path.Combine(candidateDir, $"{appName}.deps.json")))
-                        {
-                            // This is an app!
-                            return Path.Combine(candidateDir, $"{appName}.eventpipeconfig");
-                        }
-                    }
-                }
-
-                return null;
-            }
-        }
-    }
-}
diff --git a/src/Tools/dotnet-collect/EtwCollector.cs b/src/Tools/dotnet-collect/EtwCollector.cs
deleted file mode 100644 (file)
index 2154a33..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.Tracing;
-using System.IO;
-using System.Threading.Tasks;
-using Microsoft.Diagnostics.Tracing;
-using Microsoft.Diagnostics.Tracing.Session;
-
-namespace Microsoft.Diagnostics.Tools.Collect
-{
-    public class EtwCollector : EventCollector
-    {
-        private readonly CollectionConfiguration _config;
-        private TraceEventSession _session;
-
-        public EtwCollector(CollectionConfiguration config)
-        {
-            _config = config;
-        }
-
-        public override Task StartCollectingAsync()
-        {
-            // TODO: Allow a file name to be provided
-            var outputFile = _config.ProcessId == null ?
-                Path.Combine(_config.OutputPath, "dotnet-collect.etl") :
-                Path.Combine(_config.OutputPath, $"dotnet-collect.{_config.ProcessId.Value}.etl");
-            if (File.Exists(outputFile))
-            {
-                throw new InvalidOperationException($"Target file already exists: {outputFile}");
-            }
-            _session = new TraceEventSession("dotnet-collect", outputFile);
-
-            if (_config.CircularMB is int circularMb)
-            {
-                _session.CircularBufferMB = circularMb;
-            }
-
-            var options = new TraceEventProviderOptions();
-            if (_config.ProcessId is int pid)
-            {
-                options.ProcessIDFilter = new List<int>() { pid };
-            }
-
-            // Enable the providers requested
-            foreach (var provider in _config.Providers)
-            {
-                _session.EnableProvider(provider.Provider, ConvertLevel(provider.Level), provider.Keywords, options);
-            }
-
-            return Task.CompletedTask;
-        }
-
-        private TraceEventLevel ConvertLevel(EventLevel level)
-        {
-            switch (level)
-            {
-                case EventLevel.Critical: return TraceEventLevel.Critical;
-                case EventLevel.Error: return TraceEventLevel.Error;
-                case EventLevel.Informational: return TraceEventLevel.Informational;
-                case EventLevel.LogAlways: return TraceEventLevel.Always;
-                case EventLevel.Verbose: return TraceEventLevel.Verbose;
-                case EventLevel.Warning: return TraceEventLevel.Warning;
-                default:
-                    throw new InvalidOperationException($"Unknown EventLevel: {level}");
-            }
-        }
-
-        public override Task StopCollectingAsync()
-        {
-            _session.Dispose();
-            return Task.CompletedTask;
-        }
-    }
-}
diff --git a/src/Tools/dotnet-collect/EventCollector.cs b/src/Tools/dotnet-collect/EventCollector.cs
deleted file mode 100644 (file)
index 6403306..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-using System.Threading.Tasks;
-
-namespace Microsoft.Diagnostics.Tools.Collect
-{
-    public abstract class EventCollector
-    {
-        public abstract Task StartCollectingAsync();
-        public abstract Task StopCollectingAsync();
-    }
-}
diff --git a/src/Tools/dotnet-collect/EventPipeCollector.cs b/src/Tools/dotnet-collect/EventPipeCollector.cs
deleted file mode 100644 (file)
index 47c05ea..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-using System.IO;
-using System.Threading.Tasks;
-
-namespace Microsoft.Diagnostics.Tools.Collect
-{
-    public class EventPipeCollector : EventCollector
-    {
-        private readonly CollectionConfiguration _config;
-        private readonly string _configPath;
-
-        public EventPipeCollector(CollectionConfiguration config, string configPath)
-        {
-            _config = config;
-            _configPath = configPath;
-        }
-
-        public override Task StartCollectingAsync()
-        {
-            var configContent = _config.ToConfigString();
-            return File.WriteAllTextAsync(_configPath, configContent);
-        }
-
-        public override Task StopCollectingAsync()
-        {
-            File.Delete(_configPath);
-            return Task.CompletedTask;
-        }
-    }
-}
diff --git a/src/Tools/dotnet-collect/EventSpec.cs b/src/Tools/dotnet-collect/EventSpec.cs
deleted file mode 100644 (file)
index e384e70..0000000
+++ /dev/null
@@ -1,169 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.Tracing;
-using System.Globalization;
-using System.Text;
-using Microsoft.Internal.Utilities;
-
-namespace Microsoft.Diagnostics.Tools.Collect
-{
-    public class EventSpec
-    {
-        public string Provider { get; }
-        public ulong Keywords { get; }
-        public EventLevel Level { get; }
-        public IDictionary<string, string> Parameters { get; }
-
-        public EventSpec(string provider, ulong keywords, EventLevel level)
-            : this(provider, keywords, level, new Dictionary<string, string>())
-        {
-        }
-
-        public EventSpec(string provider, ulong keywords, EventLevel level, IDictionary<string, string> parameters)
-        {
-            Provider = provider;
-            Keywords = keywords;
-            Level = level;
-            Parameters = parameters;
-        }
-
-        public static bool TryParse(string input, out EventSpec spec)
-        {
-            spec = null;
-            var splat = input.Split(':');
-
-            if (splat.Length == 0)
-            {
-                return false;
-            }
-
-            var provider = splat[0];
-            var keywords = ulong.MaxValue;
-            var level = EventLevel.Verbose;
-            var parameters = new Dictionary<string, string>();
-
-            if (splat.Length > 1)
-            {
-                if (!TryParseKeywords(splat[1], provider, out keywords))
-                {
-                    return false;
-                }
-            }
-
-            if (splat.Length > 2)
-            {
-                if (!TryParseLevel(splat[2], out level))
-                {
-                    return false;
-                }
-            }
-
-            if (splat.Length > 3)
-            {
-                if (!TryParseParameters(splat[3], parameters))
-                {
-                    return false;
-                }
-            }
-
-            spec = new EventSpec(provider, keywords, level, parameters);
-            return true;
-        }
-
-        public string ToConfigString()
-        {
-            var config = $"{Provider}:0x{Keywords:X}:{(int)Level}";
-            if(Parameters.Count > 0)
-            {
-                config += $":{FormatParameters(Parameters)}";
-            }
-            return config;
-        }
-
-        private static string FormatParameters(IDictionary<string, string> parameters)
-        {
-            var builder = new StringBuilder();
-            foreach(var (key, value) in parameters)
-            {
-                builder.Append($"{key}={value};");
-            }
-
-            // Remove the trailing ';'
-            builder.Length -= 1;
-
-            return builder.ToString();
-        }
-
-        private static bool TryParseParameters(string input, IDictionary<string, string> parameters)
-        {
-            var splat = input.Split(';');
-            foreach(var item in splat)
-            {
-                var splot = item.Split('=');
-                if(splot.Length != 2)
-                {
-                    return false;
-                }
-
-                parameters[splot[0]] = splot[1];
-            }
-
-            return true;
-        }
-
-        private static bool TryParseLevel(string input, out EventLevel level)
-        {
-            level = EventLevel.Verbose;
-            if (int.TryParse(input, out var intLevel))
-            {
-                if (intLevel >= (int)EventLevel.LogAlways && intLevel <= (int)EventLevel.Verbose)
-                {
-                    level = (EventLevel)intLevel;
-                    return true;
-                }
-            }
-            else if (Enum.TryParse(input, ignoreCase: true, out level))
-            {
-                return true;
-            }
-            return false;
-        }
-
-        private static bool TryParseKeywords(string input, string provider, out ulong keywords)
-        {
-            if (string.Equals("*", input, StringComparison.Ordinal))
-            {
-                keywords = ulong.MaxValue;
-                return true;
-            }
-            else if (input.StartsWith("0x"))
-            {
-                // Keywords
-                if (ulong.TryParse(input, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out keywords))
-                {
-                    return true;
-                }
-            }
-            else if(KnownData.TryGetProvider(provider, out var knownProvider))
-            {
-                var splat = input.Split(',');
-                keywords = 0;
-                foreach(var item in splat)
-                {
-                    if(knownProvider.Keywords.TryGetValue(item, out var knownKeyword))
-                    {
-                        keywords |= knownKeyword.Value;
-                    }
-                    else
-                    {
-                        throw new CommandLineException($"Keyword '{item}' is not a well-known keyword for '{provider}'");
-                    }
-                }
-                return true;
-            }
-
-            keywords = ulong.MaxValue;
-            return false;
-        }
-    }
-}
diff --git a/src/Tools/dotnet-collect/KnownData.cs b/src/Tools/dotnet-collect/KnownData.cs
deleted file mode 100644 (file)
index 1ac0a40..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.Tracing;
-using System.Linq;
-using Microsoft.Diagnostics.Tracing.Parsers;
-
-namespace Microsoft.Diagnostics.Tools.Collect
-{
-    internal static class KnownData
-    {
-        private static readonly IReadOnlyDictionary<string, KnownProvider> _knownProviders =
-            CreateKnownProviders().ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase);
-
-        private static readonly IReadOnlyDictionary<string, CollectionProfile> _knownProfiles =
-            CreateProfiles().ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase);
-
-        private static IEnumerable<KnownProvider> CreateKnownProviders()
-        {
-            yield return CreateClrProvider();
-        }
-
-        private static IEnumerable<CollectionProfile> CreateProfiles()
-        {
-            yield return new CollectionProfile(
-                CollectionProfile.DefaultProfileName,
-                "A default set of event providers useful for diagosing problems in any .NET application.",
-                new[] {
-                    new EventSpec(ClrTraceEventParser.ProviderName, (ulong)ClrTraceEventParser.Keywords.Default, EventLevel.Informational)
-                },
-                Array.Empty<LoggerSpec>());
-
-            yield return new CollectionProfile(
-                "AspNetCore",
-                "A set of event providers useful for diagnosing problems in ASP.NET Core applications.",
-                new[]
-                {
-                    new EventSpec("Microsoft-AspNetCore-Hosting", ulong.MaxValue, EventLevel.Informational),
-                },
-                Array.Empty<LoggerSpec>());
-
-            yield return new CollectionProfile(
-                "Kestrel",
-                "Detailed events for ASP.NET Core Kestrel",
-                new[]
-                {
-                    new EventSpec("Microsoft-AspNetCore-Server-Kestrel", ulong.MaxValue, EventLevel.Verbose),
-                },
-                Array.Empty<LoggerSpec>());
-        }
-
-        private static KnownProvider CreateClrProvider()
-        {
-            return new KnownProvider(
-                ClrTraceEventParser.ProviderName,
-                ClrTraceEventParser.ProviderGuid,
-                ScanKeywordType(typeof(ClrTraceEventParser.Keywords)));
-        }
-
-        public static IReadOnlyList<CollectionProfile> GetAllProfiles() => _knownProfiles.Values.ToList();
-        public static IReadOnlyList<KnownProvider> GetAllProviders() => _knownProviders.Values.ToList();
-
-        public static bool TryGetProvider(string providerName, out KnownProvider provider) => _knownProviders.TryGetValue(providerName, out provider);
-        public static bool TryGetProfile(string profileName, out CollectionProfile profile) => _knownProfiles.TryGetValue(profileName, out profile);
-
-        private static IEnumerable<KnownKeyword> ScanKeywordType(Type keywordType)
-        {
-            var values = Enum.GetValues(keywordType).Cast<long>().ToList();
-            var keywords = values.Distinct().Select(v => new KnownKeyword(Enum.GetName(keywordType, v), (ulong)v)).ToList();
-            return keywords;
-        }
-    }
-
-    internal class KnownProvider
-    {
-        public string Name { get; }
-        public Guid Guid { get; }
-        public IReadOnlyDictionary<string, KnownKeyword> Keywords { get; }
-
-        public KnownProvider(string name, Guid guid, IEnumerable<KnownKeyword> keywords)
-        {
-            Name = name;
-            Guid = guid;
-            Keywords = keywords.ToDictionary(k => k.Name, StringComparer.OrdinalIgnoreCase);
-        }
-    }
-
-    internal class KnownKeyword
-    {
-        public string Name { get; }
-        public ulong Value { get; }
-
-        public KnownKeyword(string name, ulong value)
-        {
-            Name = name;
-            Value = value;
-        }
-    }
-}
diff --git a/src/Tools/dotnet-collect/LoggerSpec.cs b/src/Tools/dotnet-collect/LoggerSpec.cs
deleted file mode 100644 (file)
index 4e1d1e8..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace Microsoft.Diagnostics.Tools.Collect
-{
-    public class LoggerSpec
-    {
-        // Handles case normalization because key lookup is case-insensitive.
-        private static readonly Dictionary<string, string> _levelMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
-        {
-            { "Trace", "Trace" },
-            { "Debug", "Debug" },
-            { "Information", "Information" },
-            { "Warning", "Warning" },
-            { "Error", "Error" },
-            { "Critical", "Critical" },
-        };
-
-        public string Prefix { get; }
-        public string Level { get; }
-
-        public LoggerSpec(string prefix, string level)
-        {
-            Prefix = prefix;
-            Level = level;
-        }
-
-        public static bool TryParse(string input, out LoggerSpec spec)
-        {
-            var splat = input.Split(':');
-
-            var prefix = splat[0];
-            string level = null;
-            if (splat.Length > 1)
-            {
-                if (!_levelMap.TryGetValue(splat[1], out level))
-                {
-                    spec = null;
-                    return false;
-                }
-            }
-
-            spec = new LoggerSpec(prefix, level);
-            return true;
-        }
-    }
-}
diff --git a/src/Tools/dotnet-collect/Program.cs b/src/Tools/dotnet-collect/Program.cs
deleted file mode 100644 (file)
index b67b360..0000000
+++ /dev/null
@@ -1,278 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Threading.Tasks;
-using McMaster.Extensions.CommandLineUtils;
-using Microsoft.Internal.Utilities;
-
-namespace Microsoft.Diagnostics.Tools.Collect
-{
-    [Command(Name = "dotnet-collect", Description = "Collects Event Traces from .NET processes")]
-    internal class Program
-    {
-
-        [Option("-c|--config-path <CONFIG_PATH>", Description = "The path of the EventPipe config file to write, must be named [AppName].eventpipeconfig and be in the base directory for a managed app.")]
-        public string ConfigPath { get; set; }
-
-        [Option("--etw", Description = "Specify this flag to use ETW to collect events rather than using EventPipe (Windows only).")]
-        public bool Etw { get; set; }
-
-        [Required]
-        [Option("-p|--process-id <PROCESS_ID>", Description = "Filter to only the process with the specified process ID.")]
-        public int ProcessId { get; set; }
-
-        [Option("-o|--output <OUTPUT_DIRECTORY>", Description = "The directory to write the trace to. Defaults to the current working directory.")]
-        public string OutputDir { get; set; }
-
-        [Option("--buffer <BUFFER_SIZE_IN_MB>", Description = "The size of the in-memory circular buffer in megabytes.")]
-        public int? CircularMB { get; set; }
-
-        [Option("--provider <PROVIDER_SPEC>", Description = "An EventPipe provider to enable. A string in the form '<provider name>:<keywords>:<level>:<parameters>'. Can be specified multiple times to enable multiple providers.")]
-        public IList<string> Providers { get; set; }
-
-        [Option("--profile <PROFILE_NAME>", Description = "A collection profile to enable. Use '--list-profiles' to get a list of all available profiles. Can be mixed with '--provider' and specified multiple times.")]
-        public IList<string> Profiles { get; set; }
-
-        [Option("--logger <LOGGER_NAME>", Description = "A Microsoft.Extensions.Logging logger to enable. A string in the form '<logger prefix>:<level>'. Can be specified multiple times to enable multiple loggers.")]
-        public IList<string> Loggers { get; set; }
-
-        [Option("--keywords-for <PROVIDER_NAME>", Description = "Gets a list of known keywords (if any) for the specified provider.")]
-        public string KeywordsForProvider { get; set; }
-
-        [Option("--list-profiles", Description = "Gets a list of predefined collection profiles.")]
-        public bool ListProfiles { get; set; }
-
-        [Option("--no-default", Description = "Don't enable the default profile.")]
-        public bool NoDefault { get; set; }
-
-        public async Task<int> OnExecuteAsync(IConsole console, CommandLineApplication app)
-        {
-            if (ListProfiles)
-            {
-                WriteProfileList(console.Out);
-                return 0;
-            }
-
-            if (!string.IsNullOrEmpty(KeywordsForProvider))
-            {
-                return ExecuteKeywordsForAsync(console);
-            }
-
-            if(string.IsNullOrEmpty(ConfigPath))
-            {
-                ConfigPath = ConfigPathDetector.TryDetectConfigPath(ProcessId);
-                if(string.IsNullOrEmpty(ConfigPath))
-                {
-                    console.Error.WriteLine("Couldn't determine the path for the eventpipeconfig file from the process ID. Specify the '--config-path' option to provide it manually.");
-                    return 1;
-                }
-                console.WriteLine($"Detected config file path: {ConfigPath}");
-            }
-
-            var config = new CollectionConfiguration()
-            {
-                ProcessId = ProcessId,
-                CircularMB = CircularMB,
-                OutputPath = string.IsNullOrEmpty(OutputDir) ? Directory.GetCurrentDirectory() : OutputDir
-            };
-
-            if (Profiles != null && Profiles.Count > 0)
-            {
-                foreach (var profile in Profiles)
-                {
-                    if (!KnownData.TryGetProfile(profile, out var collectionProfile))
-                    {
-                        console.Error.WriteLine($"Unknown profile name: '{profile}'. See 'dotnet-collect --list-profiles' to get a list of profiles.");
-                        return 1;
-                    }
-                    config.AddProfile(collectionProfile);
-                }
-            }
-
-            if (Providers != null && Providers.Count > 0)
-            {
-                foreach (var provider in Providers)
-                {
-                    if (!EventSpec.TryParse(provider, out var providerSpec))
-                    {
-                        console.Error.WriteLine($"Invalid provider specification: '{provider}'. See 'dotnet-collect --help' for more information.");
-                        return 1;
-                    }
-                    config.Providers.Add(providerSpec);
-                }
-            }
-
-            if (Loggers != null && Loggers.Count > 0)
-            {
-                foreach (var logger in Loggers)
-                {
-                    if (!LoggerSpec.TryParse(logger, out var loggerSpec))
-                    {
-                        console.Error.WriteLine($"Invalid logger specification: '{logger}'. See 'dotnet-collect --help' for more information.");
-                        return 1;
-                    }
-                    config.Loggers.Add(loggerSpec);
-                }
-            }
-
-            if (!NoDefault)
-            {
-                // Enable the default profile if nothing is specified
-                if (!KnownData.TryGetProfile(CollectionProfile.DefaultProfileName, out var defaultProfile))
-                {
-                    console.Error.WriteLine("No providers or profiles were specified and there is no default profile available.");
-                    return 1;
-                }
-                config.AddProfile(defaultProfile);
-            }
-
-            if (!TryCreateCollector(console, config, out var collector))
-            {
-                return 1;
-            }
-
-            // Write the config file contents
-            await collector.StartCollectingAsync();
-            console.WriteLine("Tracing has started. Press Ctrl-C to stop.");
-
-            await console.WaitForCtrlCAsync();
-
-            await collector.StopCollectingAsync();
-            console.WriteLine($"Tracing stopped. Trace files written to {config.OutputPath}");
-
-            return 0;
-        }
-
-        private static void WriteProfileList(TextWriter console)
-        {
-            var profiles = KnownData.GetAllProfiles();
-            var maxNameLength = profiles.Max(p => p.Name.Length);
-            console.WriteLine("Available profiles:");
-            foreach (var profile in profiles)
-            {
-                console.WriteLine($"* {profile.Name.PadRight(maxNameLength)}  {profile.Description}");
-            }
-        }
-
-        private int ExecuteKeywordsForAsync(IConsole console)
-        {
-            if (KnownData.TryGetProvider(KeywordsForProvider, out var provider))
-            {
-                console.WriteLine($"Known keywords for {provider.Name} ({provider.Guid}):");
-                foreach (var keyword in provider.Keywords.Values)
-                {
-                    console.WriteLine($"* 0x{keyword.Value:x16} {keyword.Name}");
-                }
-                return 0;
-            }
-            else
-            {
-                console.WriteLine($"There are no known keywords for {KeywordsForProvider}.");
-                return 1;
-            }
-        }
-
-        private bool TryCreateCollector(IConsole console, CollectionConfiguration config, out EventCollector collector)
-        {
-            collector = null;
-
-            if (Etw)
-            {
-                if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
-                {
-                    console.Error.WriteLine("Error: ETW-based collection is only supported on Windows.");
-                    return false;
-                }
-
-                if (!string.IsNullOrEmpty(ConfigPath))
-                {
-                    console.Error.WriteLine("WARNING: The '-c' option is ignored when using ETW-based collection.");
-                }
-                collector = new EtwCollector(config);
-                return true;
-            }
-            else
-            {
-                if (File.Exists(ConfigPath))
-                {
-                    console.Error.WriteLine("Config file already exists, tracing is already underway by a different consumer.");
-                    return false;
-                }
-
-                collector = new EventPipeCollector(config, ConfigPath);
-                return true;
-            }
-        }
-
-        private static int Main(string[] args)
-        {
-            DebugUtil.WaitForDebuggerIfRequested(ref args);
-
-            try
-            {
-                var app = new CommandLineApplication<Program>();
-                app.Conventions.UseDefaultConventions();
-                app.ExtendedHelpText = GetExtendedHelp();
-                return app.Execute(args);
-            }
-            catch (PlatformNotSupportedException ex)
-            {
-                Console.Error.WriteLine(ex.Message);
-                return 1;
-            }
-            catch (CommandLineException clex)
-            {
-                Console.Error.WriteLine(clex.Message);
-                return 1;
-            }
-            catch (OperationCanceledException)
-            {
-                return 0;
-            }
-        }
-
-        private static string GetExtendedHelp()
-        {
-            using (var writer = new StringWriter())
-            {
-                writer.WriteLine();
-                writer.WriteLine("Profiles");
-                writer.WriteLine("  Profiles are sets of well-defined provider configurations that provide useful information.");
-                writer.WriteLine();
-                WriteProfileList(writer);
-                writer.WriteLine();
-                writer.WriteLine("Specifying Loggers:");
-                writer.WriteLine("  Use one of the following formats to specify a logger in '--logger'");
-                writer.WriteLine("    *                                                 - Enable all messages at all levels from all loggers.");
-                writer.WriteLine("    *:<level>                                         - Enable messages at the specified '<level>' or higher from all loggers.");
-                writer.WriteLine("    <loggerPrefix>                                    - Enable all messages at all levels from all loggers starting with '<loggerPrefix>'.");
-                writer.WriteLine("    <loggerPrefix>:<level>                            - Enable messages at the specified '<level>' or higher from all loggers starting with '<loggerPrefix>'.");
-                writer.WriteLine();
-                writer.WriteLine("  '<loggerPrefix>' is the prefix for a logger to enable. For example 'Microsoft.AspNetCore' to enable all ASP.NET Core loggers.");
-                writer.WriteLine("  '<level>' can be one of: Critical, Error, Warning, Informational, Debug, or Trace.");
-                writer.WriteLine();
-                writer.WriteLine("Specifying Providers:");
-                writer.WriteLine("  Use one of the following formats to specify a provider in '--provider'");
-                writer.WriteLine("    <providerName>                                    - Enable all events at all levels for the provider.");
-                writer.WriteLine("    <providerName>:<keywords>                         - Enable events matching the specified keywords for the specified provider.");
-                writer.WriteLine("    <providerName>:<keywords>:<level>                 - Enable events matching the specified keywords, at the specified level for the specified provider.");
-                writer.WriteLine("    <providerName>:<keywords>:<level>:<parameters>    - Enable events matching the specified keywords, at the specified level for the specified provider and provide key-value parameters.");
-                writer.WriteLine();
-                writer.WriteLine("  '<provider>' must be the name of the EventSource.");
-                writer.WriteLine("  '<level>' can be one of: Critical (1), Error (2), Warning (3), Informational (4), Verbose (5). Either the name or number can be specified.");
-                writer.WriteLine("  '<keywords>' is one of the following:");
-                writer.WriteLine("    A '*' character, indicating ALL keywords should be enabled (this can be very costly for some providers!)");
-                writer.WriteLine("    A comma-separated list of known keywords for a provider (use 'dotnet collect --keywords-for [providerName]' to get a list of known keywords for a provider)");
-                writer.WriteLine("    A 64-bit hexadecimal number, starting with '0x' indicating the keywords to enable");
-                writer.WriteLine("  '<parameters>' is an optional list of key-value parameters to provide to the EventPipe provider. The expected values depend on the provider you are enabling.");
-                writer.WriteLine("    This should be a list of key-value pairs, in the form: '<key1>=<value1>;<key2>=<value2>;...'. Note that some shells, such as PowerShell, require that you");
-                writer.WriteLine("    quote or escape the ';' character.");
-                return writer.GetStringBuilder().ToString();
-            }
-        }
-    }
-}
diff --git a/src/Tools/dotnet-collect/StringParser.cs b/src/Tools/dotnet-collect/StringParser.cs
deleted file mode 100644 (file)
index 5c3c643..0000000
+++ /dev/null
@@ -1,334 +0,0 @@
-// Borrowed from https://github.com/dotnet/corefx/blob/b2f960abe1d8690be9d68dd9b56ea7636fb4a38b/src/Common/src/System/IO/StringParser.cs
-
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Diagnostics;
-
-namespace System.IO
-{
-    /// <summary>
-    /// Provides a string parser that may be used instead of String.Split 
-    /// to avoid unnecessary string and array allocations.
-    /// </summary>
-    internal struct StringParser
-    {
-        /// <summary>The string being parsed.</summary>
-        private readonly string _buffer;
-
-        /// <summary>The separator character used to separate subcomponents of the larger string.</summary>
-        private readonly char _separator;
-
-        /// <summary>true if empty subcomponents should be skipped; false to treat them as valid entries.</summary>
-        private readonly bool _skipEmpty;
-
-        /// <summary>The starting index from which to parse the current entry.</summary>
-        private int _startIndex;
-
-        /// <summary>The ending index that represents the next index after the last character that's part of the current entry.</summary>
-        private int _endIndex;
-
-        /// <summary>Initialize the StringParser.</summary>
-        /// <param name="buffer">The string to parse.</param>
-        /// <param name="separator">The separator character used to separate subcomponents of <paramref name="buffer"/>.</param>
-        /// <param name="skipEmpty">true if empty subcomponents should be skipped; false to treat them as valid entries.  Defaults to false.</param>
-        public StringParser(string buffer, char separator, bool skipEmpty = false)
-        {
-            if (buffer == null)
-            {
-                throw new ArgumentNullException(nameof(buffer));
-            }
-            _buffer = buffer;
-            _separator = separator;
-            _skipEmpty = skipEmpty;
-            _startIndex = -1;
-            _endIndex = -1;
-        }
-
-        /// <summary>Moves to the next component of the string.</summary>
-        /// <returns>true if there is a next component to be parsed; otherwise, false.</returns>
-        public bool MoveNext()
-        {
-            if (_buffer == null)
-            {
-                throw new InvalidOperationException();
-            }
-
-            while (true)
-            {
-                if (_endIndex >= _buffer.Length)
-                {
-                    _startIndex = _endIndex;
-                    return false;
-                }
-
-                int nextSeparator = _buffer.IndexOf(_separator, _endIndex + 1);
-                _startIndex = _endIndex + 1;
-                _endIndex = nextSeparator >= 0 ? nextSeparator : _buffer.Length;
-
-                if (!_skipEmpty || _endIndex >= _startIndex + 1)
-                {
-                    return true;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Moves to the next component of the string.  If there isn't one, it throws an exception.
-        /// </summary>
-        public void MoveNextOrFail()
-        {
-            if (!MoveNext())
-            {
-                ThrowForInvalidData();
-            }
-        }
-
-        /// <summary>
-        /// Moves to the next component of the string and returns it as a string.
-        /// </summary>
-        /// <returns></returns>
-        public string MoveAndExtractNext()
-        {
-            MoveNextOrFail();
-            return _buffer.Substring(_startIndex, _endIndex - _startIndex);
-        }
-
-        /// <summary>
-        /// Moves to the next component of the string, which must be enclosed in the only set of top-level parentheses
-        /// in the string.  The extracted value will be everything between (not including) those parentheses.
-        /// </summary>
-        /// <returns></returns>
-        public string MoveAndExtractNextInOuterParens()
-        {
-            // Move to the next position
-            MoveNextOrFail();
-
-            // After doing so, we should be sitting at a the opening paren.
-            if (_buffer[_startIndex] != '(')
-            {
-                ThrowForInvalidData();
-            }
-
-            // Since we only allow for one top-level set of parentheses, find the last
-            // parenthesis in the string; it's paired with the opening one we just found.
-            int lastParen = _buffer.LastIndexOf(')');
-            if (lastParen == -1 || lastParen < _startIndex)
-            {
-                ThrowForInvalidData();
-            }
-
-            // Extract the contents of the parens, then move our ending position to be after the paren
-            string result = _buffer.Substring(_startIndex + 1, lastParen - _startIndex - 1);
-            _endIndex = lastParen + 1;
-
-            return result;
-        }
-
-        /// <summary>
-        /// Gets the current subcomponent of the string as a string.
-        /// </summary>
-        public string ExtractCurrent()
-        {
-            if (_buffer == null || _startIndex == -1)
-            {
-                throw new InvalidOperationException();
-            }
-            return _buffer.Substring(_startIndex, _endIndex - _startIndex);
-        }
-
-        /// <summary>Moves to the next component and parses it as an Int32.</summary>
-        public unsafe int ParseNextInt32()
-        {
-            MoveNextOrFail();
-
-            bool negative = false;
-            int result = 0;
-
-            fixed (char* bufferPtr = _buffer)
-            {
-                char* p = bufferPtr + _startIndex;
-                char* end = bufferPtr + _endIndex;
-
-                if (p == end)
-                {
-                    ThrowForInvalidData();
-                }
-
-                if (*p == '-')
-                {
-                    negative = true;
-                    p++;
-                    if (p == end)
-                    {
-                        ThrowForInvalidData();
-                    }
-                }
-
-                while (p != end)
-                {
-                    int d = *p - '0';
-                    if (d < 0 || d > 9)
-                    {
-                        ThrowForInvalidData();
-                    }
-                    result = negative ? checked((result * 10) - d) : checked((result * 10) + d);
-
-                    p++;
-                }
-            }
-
-            Debug.Assert(result == int.Parse(ExtractCurrent()), "Expected manually parsed result to match Parse result");
-            return result;
-        }
-
-        /// <summary>Moves to the next component and parses it as an Int64.</summary>
-        public unsafe long ParseNextInt64()
-        {
-            MoveNextOrFail();
-
-            bool negative = false;
-            long result = 0;
-
-            fixed (char* bufferPtr = _buffer)
-            {
-                char* p = bufferPtr + _startIndex;
-                char* end = bufferPtr + _endIndex;
-
-                if (p == end)
-                {
-                    ThrowForInvalidData();
-                }
-
-                if (*p == '-')
-                {
-                    negative = true;
-                    p++;
-                    if (p == end)
-                    {
-                        ThrowForInvalidData();
-                    }
-                }
-
-                while (p != end)
-                {
-                    int d = *p - '0';
-                    if (d < 0 || d > 9)
-                    {
-                        ThrowForInvalidData();
-                    }
-                    result = negative ? checked((result * 10) - d) : checked((result * 10) + d);
-
-                    p++;
-                }
-            }
-
-            Debug.Assert(result == long.Parse(ExtractCurrent()), "Expected manually parsed result to match Parse result");
-            return result;
-        }
-
-        /// <summary>Moves to the next component and parses it as a UInt32.</summary>
-        public unsafe uint ParseNextUInt32()
-        {
-            MoveNextOrFail();
-            if (_startIndex == _endIndex)
-            {
-                ThrowForInvalidData();
-            }
-
-            uint result = 0;
-            fixed (char* bufferPtr = _buffer)
-            {
-                char* p = bufferPtr + _startIndex;
-                char* end = bufferPtr + _endIndex;
-                while (p != end)
-                {
-                    int d = *p - '0';
-                    if (d < 0 || d > 9)
-                    {
-                        ThrowForInvalidData();
-                    }
-                    result = (uint)checked((result * 10) + d);
-
-                    p++;
-                }
-            }
-
-            Debug.Assert(result == uint.Parse(ExtractCurrent()), "Expected manually parsed result to match Parse result");
-            return result;
-        }
-
-        /// <summary>Moves to the next component and parses it as a UInt64.</summary>
-        public unsafe ulong ParseNextUInt64()
-        {
-            MoveNextOrFail();
-
-            ulong result = 0;
-            fixed (char* bufferPtr = _buffer)
-            {
-                char* p = bufferPtr + _startIndex;
-                char* end = bufferPtr + _endIndex;
-                while (p != end)
-                {
-                    int d = *p - '0';
-                    if (d < 0 || d > 9)
-                    {
-                        ThrowForInvalidData();
-                    }
-                    result = checked((result * 10ul) + (ulong)d);
-
-                    p++;
-                }
-            }
-
-            Debug.Assert(result == ulong.Parse(ExtractCurrent()), "Expected manually parsed result to match Parse result");
-            return result;
-        }
-
-        /// <summary>Moves to the next component and parses it as a Char.</summary>
-        public char ParseNextChar()
-        {
-            MoveNextOrFail();
-
-            if (_endIndex - _startIndex != 1)
-            {
-                ThrowForInvalidData();
-            }
-            char result = _buffer[_startIndex];
-
-            Debug.Assert(result == char.Parse(ExtractCurrent()), "Expected manually parsed result to match Parse result");
-            return result;
-        }
-
-        internal delegate T ParseRawFunc<T>(string buffer, ref int startIndex, ref int endIndex);
-
-        /// <summary>
-        /// Moves to the next component and hands the raw buffer and indexing data to a selector function
-        /// that can validate and return the appropriate data from the component.
-        /// </summary>
-        internal T ParseRaw<T>(ParseRawFunc<T> selector)
-        {
-            MoveNextOrFail();
-            return selector(_buffer, ref _startIndex, ref _endIndex);
-        }
-
-        /// <summary>
-        /// Gets the current subcomponent and all remaining components of the string as a string.
-        /// </summary>
-        public string ExtractCurrentToEnd()
-        {
-            if (_buffer == null || _startIndex == -1)
-            {
-                throw new InvalidOperationException();
-            }
-            return _buffer.Substring(_startIndex);
-        }
-
-        /// <summary>Throws unconditionally for invalid data.</summary>
-        private static void ThrowForInvalidData()
-        {
-            throw new InvalidDataException();
-        }
-    }
-}
diff --git a/src/Tools/dotnet-collect/dotnet-collect.csproj b/src/Tools/dotnet-collect/dotnet-collect.csproj
deleted file mode 100644 (file)
index 092fb25..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <PropertyGroup>
-    <OutputType>Exe</OutputType>
-
-    <!-- Target .NET Core 2.1 so it will run on LTS -->
-    <TargetFramework>netcoreapp2.1</TargetFramework>
-
-    <RootNamespace>Microsoft.Diagnostics.Tools.Collect</RootNamespace>
-    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-
-    <!-- Don't pack until ship engineering is done. Currently causing the official job to fail.
-    <IsPackable>true</IsPackable>
-    <PackAsTool>true</PackAsTool>
-    -->
-  </PropertyGroup>
-
-  <ItemGroup>
-    <Compile Include="..\Common\CommandLineException.cs" Link="CommandLineException.cs" />
-    <Compile Include="..\Common\ConsoleCancellation.cs" Link="ConsoleCancellation.cs" />
-    <Compile Include="..\Common\DebugUtil.cs" Link="DebugUtil.cs" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.2.5" />
-    <PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="2.0.30" />
-  </ItemGroup>
-
-</Project>
\ No newline at end of file
diff --git a/src/Tools/dotnet-trace/CollectionConfiguration.cs b/src/Tools/dotnet-trace/CollectionConfiguration.cs
new file mode 100644 (file)
index 0000000..e7af348
--- /dev/null
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Tracing;
+using System.Linq;
+using System.Text;
+
+namespace Microsoft.Diagnostics.Tools.Collect
+{
+    public class CollectionConfiguration
+    {
+        public int? ProcessId { get; set; }
+        public string OutputPath { get; set; }
+        public int? CircularMB { get; set; }
+        public IList<EventSpec> Providers { get; set; } = new List<EventSpec>();
+        public IList<LoggerSpec> Loggers { get; set; } = new List<LoggerSpec>();
+
+        internal string ToConfigString()
+        {
+            var builder = new StringBuilder();
+            if (ProcessId != null)
+            {
+                builder.AppendLine($"ProcessId={ProcessId.Value}");
+            }
+            if (!string.IsNullOrEmpty(OutputPath))
+            {
+                builder.AppendLine($"OutputPath={OutputPath}");
+            }
+            if (CircularMB != null)
+            {
+                builder.AppendLine($"CircularMB={CircularMB}");
+            }
+            if (Providers != null && Providers.Count > 0)
+            {
+                builder.AppendLine($"Providers={SerializeProviders(Enumerable.Concat(Providers, GenerateLoggerSpec(Loggers)))}");
+            }
+            return builder.ToString();
+        }
+
+        public void AddProfile(CollectionProfile profile)
+        {
+            foreach (var provider in profile.EventSpecs)
+            {
+                Providers.Add(provider);
+            }
+
+            foreach (var logger in profile.LoggerSpecs)
+            {
+                Loggers.Add(logger);
+            }
+        }
+
+        private string SerializeProviders(IEnumerable<EventSpec> providers) => string.Join(",", providers.Select(s => s.ToConfigString()));
+
+        private IEnumerable<EventSpec> GenerateLoggerSpec(IList<LoggerSpec> loggers)
+        {
+            if (loggers.Count > 0)
+            {
+                var filterSpec = new StringBuilder();
+                foreach (var logger in loggers)
+                {
+                    if (string.IsNullOrEmpty(logger.Level))
+                    {
+                        filterSpec.Append($"{logger.Prefix}");
+                    }
+                    else
+                    {
+                        filterSpec.Append($"{logger.Prefix}:{logger.Level}");
+                    }
+                    filterSpec.Append(";");
+                }
+
+                // Remove trailing ';'
+                filterSpec.Length -= 1;
+
+                yield return new EventSpec(
+                    provider: "Microsoft-Extensions-Logging",
+                    keywords: 0x04 | 0x08, // FormattedMessage | JsonMessage (source: https://github.com/aspnet/Extensions/blob/aa7fa91cfc8f6ff078b020a428bcad71ae7a32ab/src/Logging/Logging.EventSource/src/LoggingEventSource.cs#L95)
+                    level: EventLevel.LogAlways,
+                    parameters: new Dictionary<string, string>() {
+                    { "FilterSpecs", filterSpec.ToString() }
+                    });
+            }
+        }
+    }
+}
diff --git a/src/Tools/dotnet-trace/CollectionProfile.cs b/src/Tools/dotnet-trace/CollectionProfile.cs
new file mode 100644 (file)
index 0000000..0e3f03e
--- /dev/null
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.Diagnostics.Tools.Collect
+{
+    public class CollectionProfile
+    {
+        public static readonly string DefaultProfileName = "Default";
+
+        public string Name { get; }
+        public string Description { get; }
+        public IReadOnlyList<EventSpec> EventSpecs { get; }
+        public IReadOnlyList<LoggerSpec> LoggerSpecs { get; }
+
+        public CollectionProfile(string name, string description, IEnumerable<EventSpec> eventSpecs, IEnumerable<LoggerSpec> loggerSpecs)
+        {
+            Name = name;
+            Description = description;
+            EventSpecs = eventSpecs.ToList();
+            LoggerSpecs = loggerSpecs.ToList();
+        }
+    }
+}
diff --git a/src/Tools/dotnet-trace/ConfigPathDetector.cs b/src/Tools/dotnet-trace/ConfigPathDetector.cs
new file mode 100644 (file)
index 0000000..050f546
--- /dev/null
@@ -0,0 +1,212 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Diagnostics.Tools.Collect
+{
+    internal static class ConfigPathDetector
+    {
+        private static readonly HashSet<string> _managedExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".exe", ".dll" };
+
+        // Known .NET Platform Assemblies
+        private static readonly HashSet<string> _platformAssemblies = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
+        {
+            "System.Private.CoreLib.dll",
+            "clrjit.dll",
+        };
+
+        internal static string TryDetectConfigPath(int processId)
+        {
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+            {
+                return Windows.TryDetectConfigPath(processId);
+            }
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+            {
+                return Linux.TryDetectConfigPath(processId);
+            }
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+            {
+                return OSX.TryDetectConfigPath(processId);
+            }
+            return null;
+        }
+
+        private static class OSX
+        {
+            // This is defined in proc_info.h (https://opensource.apple.com/source/xnu/xnu-1228/bsd/sys/proc_info.h)
+            private const int PROC_PIDPATHINFO_MAXSIZE = 1024 * 4;
+
+            /// <summary>
+            /// Gets the full path to the executable file identified by the specified PID
+            /// </summary>
+            /// <param name="pid">The PID of the running process</param>
+            /// <param name="buffer">A pointer to an allocated block of memory that will be filled with the process path</param>
+            /// <param name="bufferSize">The size of the buffer, should be PROC_PIDPATHINFO_MAXSIZE</param>
+            /// <returns>Returns the length of the path returned on success</returns>
+            [DllImport("libproc.dylib", SetLastError = true)]
+            private static extern unsafe int proc_pidpath(
+                int pid, 
+                byte* buffer, 
+                uint bufferSize);
+
+            /// <summary>
+            /// Gets the full path to the executable file identified by the specified PID
+            /// </summary>
+            /// <param name="pid">The PID of the running process</param>
+            /// <returns>Returns the full path to the process executable</returns>
+            internal static unsafe string proc_pidpath(int pid)
+            {
+                // The path is a fixed buffer size, so use that and trim it after
+                int result = 0;
+                byte* pBuffer = stackalloc byte[PROC_PIDPATHINFO_MAXSIZE];
+
+                // WARNING - Despite its name, don't try to pass in a smaller size than specified by PROC_PIDPATHINFO_MAXSIZE.
+                // For some reason libproc returns -1 if you specify something that's NOT EQUAL to PROC_PIDPATHINFO_MAXSIZE
+                // even if you declare your buffer to be smaller/larger than this size. 
+                result = proc_pidpath(pid, pBuffer, (uint)(PROC_PIDPATHINFO_MAXSIZE * sizeof(byte)));
+                if (result <= 0)
+                {
+                    throw new InvalidOperationException("Could not find procpath using libproc.");
+                }
+
+                // OS X uses UTF-8. The conversion may not strip off all trailing \0s so remove them here
+                return System.Text.Encoding.UTF8.GetString(pBuffer, result);
+            }
+
+            public static string TryDetectConfigPath(int processId)
+            {
+                try
+                {
+                    var path = proc_pidpath(processId);
+                    var candidateDir = Path.GetDirectoryName(path);
+                    var candidateName = Path.GetFileNameWithoutExtension(path);
+                    return Path.Combine(candidateDir, $"{candidateName}.eventpipeconfig");
+                }
+                catch (InvalidOperationException)
+                {
+                    return null;  // The pinvoke above may fail - return null in that case to handle error gracefully.
+                }
+            }
+        }
+
+        private static class Linux
+        {
+            public static string TryDetectConfigPath(int processId)
+            {
+                // Read procfs maps list
+                var lines = File.ReadAllLines($"/proc/{processId}/maps");
+
+                foreach (var line in lines)
+                {
+                    try
+                    {
+                        var parser = new StringParser(line, separator: ' ', skipEmpty: true);
+
+                        // Skip the address range
+                        parser.MoveNext();
+
+                        var permissions = parser.MoveAndExtractNext();
+
+                        // The managed entry point is Read-Only, Non-Execute and Shared.
+                        if (!string.Equals(permissions, "r--s", StringComparison.Ordinal))
+                        {
+                            continue;
+                        }
+
+                        // Skip offset, dev, and inode
+                        parser.MoveNext();
+                        parser.MoveNext();
+                        parser.MoveNext();
+
+                        // Parse the path
+                        if (!parser.MoveNext())
+                        {
+                            continue;
+                        }
+
+                        var path = parser.ExtractCurrentToEnd();
+                        var candidateDir = Path.GetDirectoryName(path);
+                        var candidateName = Path.GetFileNameWithoutExtension(path);
+                        if (File.Exists(Path.Combine(candidateDir, $"{candidateName}.deps.json")))
+                        {
+                            return Path.Combine(candidateDir, $"{candidateName}.eventpipeconfig");
+                        }
+                    }
+                    catch (ArgumentNullException) { return null; }
+                    catch (InvalidDataException) { return null; }
+                    catch (InvalidOperationException) { return null; }
+                }
+                return null;
+            }
+        }
+
+        private static class Windows
+        {
+            private static readonly HashSet<string> _knownNativeLibraries = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
+            {
+                // .NET Core Host
+                "dotnet.exe",
+                "hostfxr.dll",
+                "hostpolicy.dll",
+                "coreclr.dll",
+
+                // Windows Native Libraries
+                "ntdll.dll",
+                "kernel32.dll",
+                "kernelbase.dll",
+                "apphelp.dll",
+                "ucrtbase.dll",
+                "advapi32.dll",
+                "msvcrt.dll",
+                "sechost.dll",
+                "rpcrt4.dll",
+                "ole32.dll",
+                "combase.dll",
+                "bcryptPrimitives.dll",
+                "gdi32.dll",
+                "gdi32full.dll",
+                "msvcp_win.dll",
+                "user32.dll",
+                "win32u.dll",
+                "oleaut32.dll",
+                "shlwapi.dll",
+                "version.dll",
+                "bcrypt.dll",
+                "imm32.dll",
+                "kernel.appcore.dll",
+            };
+
+            public static string TryDetectConfigPath(int processId)
+            {
+                var process = Process.GetProcessById(processId);
+
+                // Iterate over modules
+                foreach (var module in process.Modules.Cast<ProcessModule>())
+                {
+                    // Filter out things that aren't exes and dlls (useful on Unix/macOS to skip native libraries)
+                    var extension = Path.GetExtension(module.FileName);
+                    var name = Path.GetFileName(module.FileName);
+                    if (_managedExtensions.Contains(extension) && !_knownNativeLibraries.Contains(name) && !_platformAssemblies.Contains(name))
+                    {
+                        var candidateDir = Path.GetDirectoryName(module.FileName);
+                        var appName = Path.GetFileNameWithoutExtension(module.FileName);
+
+                        // Check for the deps.json file
+                        // TODO: Self-contained apps?
+                        if (File.Exists(Path.Combine(candidateDir, $"{appName}.deps.json")))
+                        {
+                            // This is an app!
+                            return Path.Combine(candidateDir, $"{appName}.eventpipeconfig");
+                        }
+                    }
+                }
+
+                return null;
+            }
+        }
+    }
+}
diff --git a/src/Tools/dotnet-trace/EtwCollector.cs b/src/Tools/dotnet-trace/EtwCollector.cs
new file mode 100644 (file)
index 0000000..d896b58
--- /dev/null
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Tracing;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.Diagnostics.Tracing;
+using Microsoft.Diagnostics.Tracing.Session;
+
+namespace Microsoft.Diagnostics.Tools.Collect
+{
+    public class EtwCollector : EventCollector
+    {
+        private readonly CollectionConfiguration _config;
+        private TraceEventSession _session;
+
+        public EtwCollector(CollectionConfiguration config)
+        {
+            _config = config;
+        }
+
+        public override Task StartCollectingAsync()
+        {
+            // TODO: Allow a file name to be provided
+            var outputFile = _config.ProcessId == null ?
+                Path.Combine(_config.OutputPath, "dotnet-trace.etl") :
+                Path.Combine(_config.OutputPath, $"dotnet-trace.{_config.ProcessId.Value}.etl");
+            if (File.Exists(outputFile))
+            {
+                throw new InvalidOperationException($"Target file already exists: {outputFile}");
+            }
+            _session = new TraceEventSession("dotnet-trace", outputFile);
+
+            if (_config.CircularMB is int circularMb)
+            {
+                _session.CircularBufferMB = circularMb;
+            }
+
+            var options = new TraceEventProviderOptions();
+            if (_config.ProcessId is int pid)
+            {
+                options.ProcessIDFilter = new List<int>() { pid };
+            }
+
+            // Enable the providers requested
+            foreach (var provider in _config.Providers)
+            {
+                _session.EnableProvider(provider.Provider, ConvertLevel(provider.Level), provider.Keywords, options);
+            }
+
+            return Task.CompletedTask;
+        }
+
+        private TraceEventLevel ConvertLevel(EventLevel level)
+        {
+            switch (level)
+            {
+                case EventLevel.Critical: return TraceEventLevel.Critical;
+                case EventLevel.Error: return TraceEventLevel.Error;
+                case EventLevel.Informational: return TraceEventLevel.Informational;
+                case EventLevel.LogAlways: return TraceEventLevel.Always;
+                case EventLevel.Verbose: return TraceEventLevel.Verbose;
+                case EventLevel.Warning: return TraceEventLevel.Warning;
+                default:
+                    throw new InvalidOperationException($"Unknown EventLevel: {level}");
+            }
+        }
+
+        public override Task StopCollectingAsync()
+        {
+            _session.Dispose();
+            return Task.CompletedTask;
+        }
+    }
+}
diff --git a/src/Tools/dotnet-trace/EventCollector.cs b/src/Tools/dotnet-trace/EventCollector.cs
new file mode 100644 (file)
index 0000000..6403306
--- /dev/null
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+
+namespace Microsoft.Diagnostics.Tools.Collect
+{
+    public abstract class EventCollector
+    {
+        public abstract Task StartCollectingAsync();
+        public abstract Task StopCollectingAsync();
+    }
+}
diff --git a/src/Tools/dotnet-trace/EventPipeCollector.cs b/src/Tools/dotnet-trace/EventPipeCollector.cs
new file mode 100644 (file)
index 0000000..47c05ea
--- /dev/null
@@ -0,0 +1,29 @@
+using System.IO;
+using System.Threading.Tasks;
+
+namespace Microsoft.Diagnostics.Tools.Collect
+{
+    public class EventPipeCollector : EventCollector
+    {
+        private readonly CollectionConfiguration _config;
+        private readonly string _configPath;
+
+        public EventPipeCollector(CollectionConfiguration config, string configPath)
+        {
+            _config = config;
+            _configPath = configPath;
+        }
+
+        public override Task StartCollectingAsync()
+        {
+            var configContent = _config.ToConfigString();
+            return File.WriteAllTextAsync(_configPath, configContent);
+        }
+
+        public override Task StopCollectingAsync()
+        {
+            File.Delete(_configPath);
+            return Task.CompletedTask;
+        }
+    }
+}
diff --git a/src/Tools/dotnet-trace/EventSpec.cs b/src/Tools/dotnet-trace/EventSpec.cs
new file mode 100644 (file)
index 0000000..e384e70
--- /dev/null
@@ -0,0 +1,169 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Tracing;
+using System.Globalization;
+using System.Text;
+using Microsoft.Internal.Utilities;
+
+namespace Microsoft.Diagnostics.Tools.Collect
+{
+    public class EventSpec
+    {
+        public string Provider { get; }
+        public ulong Keywords { get; }
+        public EventLevel Level { get; }
+        public IDictionary<string, string> Parameters { get; }
+
+        public EventSpec(string provider, ulong keywords, EventLevel level)
+            : this(provider, keywords, level, new Dictionary<string, string>())
+        {
+        }
+
+        public EventSpec(string provider, ulong keywords, EventLevel level, IDictionary<string, string> parameters)
+        {
+            Provider = provider;
+            Keywords = keywords;
+            Level = level;
+            Parameters = parameters;
+        }
+
+        public static bool TryParse(string input, out EventSpec spec)
+        {
+            spec = null;
+            var splat = input.Split(':');
+
+            if (splat.Length == 0)
+            {
+                return false;
+            }
+
+            var provider = splat[0];
+            var keywords = ulong.MaxValue;
+            var level = EventLevel.Verbose;
+            var parameters = new Dictionary<string, string>();
+
+            if (splat.Length > 1)
+            {
+                if (!TryParseKeywords(splat[1], provider, out keywords))
+                {
+                    return false;
+                }
+            }
+
+            if (splat.Length > 2)
+            {
+                if (!TryParseLevel(splat[2], out level))
+                {
+                    return false;
+                }
+            }
+
+            if (splat.Length > 3)
+            {
+                if (!TryParseParameters(splat[3], parameters))
+                {
+                    return false;
+                }
+            }
+
+            spec = new EventSpec(provider, keywords, level, parameters);
+            return true;
+        }
+
+        public string ToConfigString()
+        {
+            var config = $"{Provider}:0x{Keywords:X}:{(int)Level}";
+            if(Parameters.Count > 0)
+            {
+                config += $":{FormatParameters(Parameters)}";
+            }
+            return config;
+        }
+
+        private static string FormatParameters(IDictionary<string, string> parameters)
+        {
+            var builder = new StringBuilder();
+            foreach(var (key, value) in parameters)
+            {
+                builder.Append($"{key}={value};");
+            }
+
+            // Remove the trailing ';'
+            builder.Length -= 1;
+
+            return builder.ToString();
+        }
+
+        private static bool TryParseParameters(string input, IDictionary<string, string> parameters)
+        {
+            var splat = input.Split(';');
+            foreach(var item in splat)
+            {
+                var splot = item.Split('=');
+                if(splot.Length != 2)
+                {
+                    return false;
+                }
+
+                parameters[splot[0]] = splot[1];
+            }
+
+            return true;
+        }
+
+        private static bool TryParseLevel(string input, out EventLevel level)
+        {
+            level = EventLevel.Verbose;
+            if (int.TryParse(input, out var intLevel))
+            {
+                if (intLevel >= (int)EventLevel.LogAlways && intLevel <= (int)EventLevel.Verbose)
+                {
+                    level = (EventLevel)intLevel;
+                    return true;
+                }
+            }
+            else if (Enum.TryParse(input, ignoreCase: true, out level))
+            {
+                return true;
+            }
+            return false;
+        }
+
+        private static bool TryParseKeywords(string input, string provider, out ulong keywords)
+        {
+            if (string.Equals("*", input, StringComparison.Ordinal))
+            {
+                keywords = ulong.MaxValue;
+                return true;
+            }
+            else if (input.StartsWith("0x"))
+            {
+                // Keywords
+                if (ulong.TryParse(input, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out keywords))
+                {
+                    return true;
+                }
+            }
+            else if(KnownData.TryGetProvider(provider, out var knownProvider))
+            {
+                var splat = input.Split(',');
+                keywords = 0;
+                foreach(var item in splat)
+                {
+                    if(knownProvider.Keywords.TryGetValue(item, out var knownKeyword))
+                    {
+                        keywords |= knownKeyword.Value;
+                    }
+                    else
+                    {
+                        throw new CommandLineException($"Keyword '{item}' is not a well-known keyword for '{provider}'");
+                    }
+                }
+                return true;
+            }
+
+            keywords = ulong.MaxValue;
+            return false;
+        }
+    }
+}
diff --git a/src/Tools/dotnet-trace/KnownData.cs b/src/Tools/dotnet-trace/KnownData.cs
new file mode 100644 (file)
index 0000000..1ac0a40
--- /dev/null
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Tracing;
+using System.Linq;
+using Microsoft.Diagnostics.Tracing.Parsers;
+
+namespace Microsoft.Diagnostics.Tools.Collect
+{
+    internal static class KnownData
+    {
+        private static readonly IReadOnlyDictionary<string, KnownProvider> _knownProviders =
+            CreateKnownProviders().ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase);
+
+        private static readonly IReadOnlyDictionary<string, CollectionProfile> _knownProfiles =
+            CreateProfiles().ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase);
+
+        private static IEnumerable<KnownProvider> CreateKnownProviders()
+        {
+            yield return CreateClrProvider();
+        }
+
+        private static IEnumerable<CollectionProfile> CreateProfiles()
+        {
+            yield return new CollectionProfile(
+                CollectionProfile.DefaultProfileName,
+                "A default set of event providers useful for diagosing problems in any .NET application.",
+                new[] {
+                    new EventSpec(ClrTraceEventParser.ProviderName, (ulong)ClrTraceEventParser.Keywords.Default, EventLevel.Informational)
+                },
+                Array.Empty<LoggerSpec>());
+
+            yield return new CollectionProfile(
+                "AspNetCore",
+                "A set of event providers useful for diagnosing problems in ASP.NET Core applications.",
+                new[]
+                {
+                    new EventSpec("Microsoft-AspNetCore-Hosting", ulong.MaxValue, EventLevel.Informational),
+                },
+                Array.Empty<LoggerSpec>());
+
+            yield return new CollectionProfile(
+                "Kestrel",
+                "Detailed events for ASP.NET Core Kestrel",
+                new[]
+                {
+                    new EventSpec("Microsoft-AspNetCore-Server-Kestrel", ulong.MaxValue, EventLevel.Verbose),
+                },
+                Array.Empty<LoggerSpec>());
+        }
+
+        private static KnownProvider CreateClrProvider()
+        {
+            return new KnownProvider(
+                ClrTraceEventParser.ProviderName,
+                ClrTraceEventParser.ProviderGuid,
+                ScanKeywordType(typeof(ClrTraceEventParser.Keywords)));
+        }
+
+        public static IReadOnlyList<CollectionProfile> GetAllProfiles() => _knownProfiles.Values.ToList();
+        public static IReadOnlyList<KnownProvider> GetAllProviders() => _knownProviders.Values.ToList();
+
+        public static bool TryGetProvider(string providerName, out KnownProvider provider) => _knownProviders.TryGetValue(providerName, out provider);
+        public static bool TryGetProfile(string profileName, out CollectionProfile profile) => _knownProfiles.TryGetValue(profileName, out profile);
+
+        private static IEnumerable<KnownKeyword> ScanKeywordType(Type keywordType)
+        {
+            var values = Enum.GetValues(keywordType).Cast<long>().ToList();
+            var keywords = values.Distinct().Select(v => new KnownKeyword(Enum.GetName(keywordType, v), (ulong)v)).ToList();
+            return keywords;
+        }
+    }
+
+    internal class KnownProvider
+    {
+        public string Name { get; }
+        public Guid Guid { get; }
+        public IReadOnlyDictionary<string, KnownKeyword> Keywords { get; }
+
+        public KnownProvider(string name, Guid guid, IEnumerable<KnownKeyword> keywords)
+        {
+            Name = name;
+            Guid = guid;
+            Keywords = keywords.ToDictionary(k => k.Name, StringComparer.OrdinalIgnoreCase);
+        }
+    }
+
+    internal class KnownKeyword
+    {
+        public string Name { get; }
+        public ulong Value { get; }
+
+        public KnownKeyword(string name, ulong value)
+        {
+            Name = name;
+            Value = value;
+        }
+    }
+}
diff --git a/src/Tools/dotnet-trace/LoggerSpec.cs b/src/Tools/dotnet-trace/LoggerSpec.cs
new file mode 100644 (file)
index 0000000..4e1d1e8
--- /dev/null
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Diagnostics.Tools.Collect
+{
+    public class LoggerSpec
+    {
+        // Handles case normalization because key lookup is case-insensitive.
+        private static readonly Dictionary<string, string> _levelMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
+        {
+            { "Trace", "Trace" },
+            { "Debug", "Debug" },
+            { "Information", "Information" },
+            { "Warning", "Warning" },
+            { "Error", "Error" },
+            { "Critical", "Critical" },
+        };
+
+        public string Prefix { get; }
+        public string Level { get; }
+
+        public LoggerSpec(string prefix, string level)
+        {
+            Prefix = prefix;
+            Level = level;
+        }
+
+        public static bool TryParse(string input, out LoggerSpec spec)
+        {
+            var splat = input.Split(':');
+
+            var prefix = splat[0];
+            string level = null;
+            if (splat.Length > 1)
+            {
+                if (!_levelMap.TryGetValue(splat[1], out level))
+                {
+                    spec = null;
+                    return false;
+                }
+            }
+
+            spec = new LoggerSpec(prefix, level);
+            return true;
+        }
+    }
+}
diff --git a/src/Tools/dotnet-trace/Program.cs b/src/Tools/dotnet-trace/Program.cs
new file mode 100644 (file)
index 0000000..076e057
--- /dev/null
@@ -0,0 +1,278 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using McMaster.Extensions.CommandLineUtils;
+using Microsoft.Internal.Utilities;
+
+namespace Microsoft.Diagnostics.Tools.Collect
+{
+    [Command(Name = "dotnet-trace", Description = "Collects Event Traces from .NET processes")]
+    internal class Program
+    {
+
+        [Option("-c|--config-path <CONFIG_PATH>", Description = "The path of the EventPipe config file to write, must be named [AppName].eventpipeconfig and be in the base directory for a managed app.")]
+        public string ConfigPath { get; set; }
+
+        [Option("--etw", Description = "Specify this flag to use ETW to collect events rather than using EventPipe (Windows only).")]
+        public bool Etw { get; set; }
+
+        [Required]
+        [Option("-p|--process-id <PROCESS_ID>", Description = "Filter to only the process with the specified process ID.")]
+        public int ProcessId { get; set; }
+
+        [Option("-o|--output <OUTPUT_DIRECTORY>", Description = "The directory to write the trace to. Defaults to the current working directory.")]
+        public string OutputDir { get; set; }
+
+        [Option("--buffer <BUFFER_SIZE_IN_MB>", Description = "The size of the in-memory circular buffer in megabytes.")]
+        public int? CircularMB { get; set; }
+
+        [Option("--provider <PROVIDER_SPEC>", Description = "An EventPipe provider to enable. A string in the form '<provider name>:<keywords>:<level>:<parameters>'. Can be specified multiple times to enable multiple providers.")]
+        public IList<string> Providers { get; set; }
+
+        [Option("--profile <PROFILE_NAME>", Description = "A collection profile to enable. Use '--list-profiles' to get a list of all available profiles. Can be mixed with '--provider' and specified multiple times.")]
+        public IList<string> Profiles { get; set; }
+
+        [Option("--logger <LOGGER_NAME>", Description = "A Microsoft.Extensions.Logging logger to enable. A string in the form '<logger prefix>:<level>'. Can be specified multiple times to enable multiple loggers.")]
+        public IList<string> Loggers { get; set; }
+
+        [Option("--keywords-for <PROVIDER_NAME>", Description = "Gets a list of known keywords (if any) for the specified provider.")]
+        public string KeywordsForProvider { get; set; }
+
+        [Option("--list-profiles", Description = "Gets a list of predefined collection profiles.")]
+        public bool ListProfiles { get; set; }
+
+        [Option("--no-default", Description = "Don't enable the default profile.")]
+        public bool NoDefault { get; set; }
+
+        public async Task<int> OnExecuteAsync(IConsole console, CommandLineApplication app)
+        {
+            if (ListProfiles)
+            {
+                WriteProfileList(console.Out);
+                return 0;
+            }
+
+            if (!string.IsNullOrEmpty(KeywordsForProvider))
+            {
+                return ExecuteKeywordsForAsync(console);
+            }
+
+            if(string.IsNullOrEmpty(ConfigPath))
+            {
+                ConfigPath = ConfigPathDetector.TryDetectConfigPath(ProcessId);
+                if(string.IsNullOrEmpty(ConfigPath))
+                {
+                    console.Error.WriteLine("Couldn't determine the path for the eventpipeconfig file from the process ID. Specify the '--config-path' option to provide it manually.");
+                    return 1;
+                }
+                console.WriteLine($"Detected config file path: {ConfigPath}");
+            }
+
+            var config = new CollectionConfiguration()
+            {
+                ProcessId = ProcessId,
+                CircularMB = CircularMB,
+                OutputPath = string.IsNullOrEmpty(OutputDir) ? Directory.GetCurrentDirectory() : OutputDir
+            };
+
+            if (Profiles != null && Profiles.Count > 0)
+            {
+                foreach (var profile in Profiles)
+                {
+                    if (!KnownData.TryGetProfile(profile, out var collectionProfile))
+                    {
+                        console.Error.WriteLine($"Unknown profile name: '{profile}'. See 'dotnet-trace --list-profiles' to get a list of profiles.");
+                        return 1;
+                    }
+                    config.AddProfile(collectionProfile);
+                }
+            }
+
+            if (Providers != null && Providers.Count > 0)
+            {
+                foreach (var provider in Providers)
+                {
+                    if (!EventSpec.TryParse(provider, out var providerSpec))
+                    {
+                        console.Error.WriteLine($"Invalid provider specification: '{provider}'. See 'dotnet-trace --help' for more information.");
+                        return 1;
+                    }
+                    config.Providers.Add(providerSpec);
+                }
+            }
+
+            if (Loggers != null && Loggers.Count > 0)
+            {
+                foreach (var logger in Loggers)
+                {
+                    if (!LoggerSpec.TryParse(logger, out var loggerSpec))
+                    {
+                        console.Error.WriteLine($"Invalid logger specification: '{logger}'. See 'dotnet-trace --help' for more information.");
+                        return 1;
+                    }
+                    config.Loggers.Add(loggerSpec);
+                }
+            }
+
+            if (!NoDefault)
+            {
+                // Enable the default profile if nothing is specified
+                if (!KnownData.TryGetProfile(CollectionProfile.DefaultProfileName, out var defaultProfile))
+                {
+                    console.Error.WriteLine("No providers or profiles were specified and there is no default profile available.");
+                    return 1;
+                }
+                config.AddProfile(defaultProfile);
+            }
+
+            if (!TryCreateCollector(console, config, out var collector))
+            {
+                return 1;
+            }
+
+            // Write the config file contents
+            await collector.StartCollectingAsync();
+            console.WriteLine("Tracing has started. Press Ctrl-C to stop.");
+
+            await console.WaitForCtrlCAsync();
+
+            await collector.StopCollectingAsync();
+            console.WriteLine($"Tracing stopped. Trace files written to {config.OutputPath}");
+
+            return 0;
+        }
+
+        private static void WriteProfileList(TextWriter console)
+        {
+            var profiles = KnownData.GetAllProfiles();
+            var maxNameLength = profiles.Max(p => p.Name.Length);
+            console.WriteLine("Available profiles:");
+            foreach (var profile in profiles)
+            {
+                console.WriteLine($"* {profile.Name.PadRight(maxNameLength)}  {profile.Description}");
+            }
+        }
+
+        private int ExecuteKeywordsForAsync(IConsole console)
+        {
+            if (KnownData.TryGetProvider(KeywordsForProvider, out var provider))
+            {
+                console.WriteLine($"Known keywords for {provider.Name} ({provider.Guid}):");
+                foreach (var keyword in provider.Keywords.Values)
+                {
+                    console.WriteLine($"* 0x{keyword.Value:x16} {keyword.Name}");
+                }
+                return 0;
+            }
+            else
+            {
+                console.WriteLine($"There are no known keywords for {KeywordsForProvider}.");
+                return 1;
+            }
+        }
+
+        private bool TryCreateCollector(IConsole console, CollectionConfiguration config, out EventCollector collector)
+        {
+            collector = null;
+
+            if (Etw)
+            {
+                if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+                {
+                    console.Error.WriteLine("Error: ETW-based collection is only supported on Windows.");
+                    return false;
+                }
+
+                if (!string.IsNullOrEmpty(ConfigPath))
+                {
+                    console.Error.WriteLine("WARNING: The '-c' option is ignored when using ETW-based collection.");
+                }
+                collector = new EtwCollector(config);
+                return true;
+            }
+            else
+            {
+                if (File.Exists(ConfigPath))
+                {
+                    console.Error.WriteLine("Config file already exists, tracing is already underway by a different consumer.");
+                    return false;
+                }
+
+                collector = new EventPipeCollector(config, ConfigPath);
+                return true;
+            }
+        }
+
+        private static int Main(string[] args)
+        {
+            DebugUtil.WaitForDebuggerIfRequested(ref args);
+
+            try
+            {
+                var app = new CommandLineApplication<Program>();
+                app.Conventions.UseDefaultConventions();
+                app.ExtendedHelpText = GetExtendedHelp();
+                return app.Execute(args);
+            }
+            catch (PlatformNotSupportedException ex)
+            {
+                Console.Error.WriteLine(ex.Message);
+                return 1;
+            }
+            catch (CommandLineException clex)
+            {
+                Console.Error.WriteLine(clex.Message);
+                return 1;
+            }
+            catch (OperationCanceledException)
+            {
+                return 0;
+            }
+        }
+
+        private static string GetExtendedHelp()
+        {
+            using (var writer = new StringWriter())
+            {
+                writer.WriteLine();
+                writer.WriteLine("Profiles");
+                writer.WriteLine("  Profiles are sets of well-defined provider configurations that provide useful information.");
+                writer.WriteLine();
+                WriteProfileList(writer);
+                writer.WriteLine();
+                writer.WriteLine("Specifying Loggers:");
+                writer.WriteLine("  Use one of the following formats to specify a logger in '--logger'");
+                writer.WriteLine("    *                                                 - Enable all messages at all levels from all loggers.");
+                writer.WriteLine("    *:<level>                                         - Enable messages at the specified '<level>' or higher from all loggers.");
+                writer.WriteLine("    <loggerPrefix>                                    - Enable all messages at all levels from all loggers starting with '<loggerPrefix>'.");
+                writer.WriteLine("    <loggerPrefix>:<level>                            - Enable messages at the specified '<level>' or higher from all loggers starting with '<loggerPrefix>'.");
+                writer.WriteLine();
+                writer.WriteLine("  '<loggerPrefix>' is the prefix for a logger to enable. For example 'Microsoft.AspNetCore' to enable all ASP.NET Core loggers.");
+                writer.WriteLine("  '<level>' can be one of: Critical, Error, Warning, Informational, Debug, or Trace.");
+                writer.WriteLine();
+                writer.WriteLine("Specifying Providers:");
+                writer.WriteLine("  Use one of the following formats to specify a provider in '--provider'");
+                writer.WriteLine("    <providerName>                                    - Enable all events at all levels for the provider.");
+                writer.WriteLine("    <providerName>:<keywords>                         - Enable events matching the specified keywords for the specified provider.");
+                writer.WriteLine("    <providerName>:<keywords>:<level>                 - Enable events matching the specified keywords, at the specified level for the specified provider.");
+                writer.WriteLine("    <providerName>:<keywords>:<level>:<parameters>    - Enable events matching the specified keywords, at the specified level for the specified provider and provide key-value parameters.");
+                writer.WriteLine();
+                writer.WriteLine("  '<provider>' must be the name of the EventSource.");
+                writer.WriteLine("  '<level>' can be one of: Critical (1), Error (2), Warning (3), Informational (4), Verbose (5). Either the name or number can be specified.");
+                writer.WriteLine("  '<keywords>' is one of the following:");
+                writer.WriteLine("    A '*' character, indicating ALL keywords should be enabled (this can be very costly for some providers!)");
+                writer.WriteLine("    A comma-separated list of known keywords for a provider (use 'dotnet trace collect --keywords-for [providerName]' to get a list of known keywords for a provider)");
+                writer.WriteLine("    A 64-bit hexadecimal number, starting with '0x' indicating the keywords to enable");
+                writer.WriteLine("  '<parameters>' is an optional list of key-value parameters to provide to the EventPipe provider. The expected values depend on the provider you are enabling.");
+                writer.WriteLine("    This should be a list of key-value pairs, in the form: '<key1>=<value1>;<key2>=<value2>;...'. Note that some shells, such as PowerShell, require that you");
+                writer.WriteLine("    quote or escape the ';' character.");
+                return writer.GetStringBuilder().ToString();
+            }
+        }
+    }
+}
diff --git a/src/Tools/dotnet-trace/StringParser.cs b/src/Tools/dotnet-trace/StringParser.cs
new file mode 100644 (file)
index 0000000..5c3c643
--- /dev/null
@@ -0,0 +1,334 @@
+// Borrowed from https://github.com/dotnet/corefx/blob/b2f960abe1d8690be9d68dd9b56ea7636fb4a38b/src/Common/src/System/IO/StringParser.cs
+
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.IO
+{
+    /// <summary>
+    /// Provides a string parser that may be used instead of String.Split 
+    /// to avoid unnecessary string and array allocations.
+    /// </summary>
+    internal struct StringParser
+    {
+        /// <summary>The string being parsed.</summary>
+        private readonly string _buffer;
+
+        /// <summary>The separator character used to separate subcomponents of the larger string.</summary>
+        private readonly char _separator;
+
+        /// <summary>true if empty subcomponents should be skipped; false to treat them as valid entries.</summary>
+        private readonly bool _skipEmpty;
+
+        /// <summary>The starting index from which to parse the current entry.</summary>
+        private int _startIndex;
+
+        /// <summary>The ending index that represents the next index after the last character that's part of the current entry.</summary>
+        private int _endIndex;
+
+        /// <summary>Initialize the StringParser.</summary>
+        /// <param name="buffer">The string to parse.</param>
+        /// <param name="separator">The separator character used to separate subcomponents of <paramref name="buffer"/>.</param>
+        /// <param name="skipEmpty">true if empty subcomponents should be skipped; false to treat them as valid entries.  Defaults to false.</param>
+        public StringParser(string buffer, char separator, bool skipEmpty = false)
+        {
+            if (buffer == null)
+            {
+                throw new ArgumentNullException(nameof(buffer));
+            }
+            _buffer = buffer;
+            _separator = separator;
+            _skipEmpty = skipEmpty;
+            _startIndex = -1;
+            _endIndex = -1;
+        }
+
+        /// <summary>Moves to the next component of the string.</summary>
+        /// <returns>true if there is a next component to be parsed; otherwise, false.</returns>
+        public bool MoveNext()
+        {
+            if (_buffer == null)
+            {
+                throw new InvalidOperationException();
+            }
+
+            while (true)
+            {
+                if (_endIndex >= _buffer.Length)
+                {
+                    _startIndex = _endIndex;
+                    return false;
+                }
+
+                int nextSeparator = _buffer.IndexOf(_separator, _endIndex + 1);
+                _startIndex = _endIndex + 1;
+                _endIndex = nextSeparator >= 0 ? nextSeparator : _buffer.Length;
+
+                if (!_skipEmpty || _endIndex >= _startIndex + 1)
+                {
+                    return true;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Moves to the next component of the string.  If there isn't one, it throws an exception.
+        /// </summary>
+        public void MoveNextOrFail()
+        {
+            if (!MoveNext())
+            {
+                ThrowForInvalidData();
+            }
+        }
+
+        /// <summary>
+        /// Moves to the next component of the string and returns it as a string.
+        /// </summary>
+        /// <returns></returns>
+        public string MoveAndExtractNext()
+        {
+            MoveNextOrFail();
+            return _buffer.Substring(_startIndex, _endIndex - _startIndex);
+        }
+
+        /// <summary>
+        /// Moves to the next component of the string, which must be enclosed in the only set of top-level parentheses
+        /// in the string.  The extracted value will be everything between (not including) those parentheses.
+        /// </summary>
+        /// <returns></returns>
+        public string MoveAndExtractNextInOuterParens()
+        {
+            // Move to the next position
+            MoveNextOrFail();
+
+            // After doing so, we should be sitting at a the opening paren.
+            if (_buffer[_startIndex] != '(')
+            {
+                ThrowForInvalidData();
+            }
+
+            // Since we only allow for one top-level set of parentheses, find the last
+            // parenthesis in the string; it's paired with the opening one we just found.
+            int lastParen = _buffer.LastIndexOf(')');
+            if (lastParen == -1 || lastParen < _startIndex)
+            {
+                ThrowForInvalidData();
+            }
+
+            // Extract the contents of the parens, then move our ending position to be after the paren
+            string result = _buffer.Substring(_startIndex + 1, lastParen - _startIndex - 1);
+            _endIndex = lastParen + 1;
+
+            return result;
+        }
+
+        /// <summary>
+        /// Gets the current subcomponent of the string as a string.
+        /// </summary>
+        public string ExtractCurrent()
+        {
+            if (_buffer == null || _startIndex == -1)
+            {
+                throw new InvalidOperationException();
+            }
+            return _buffer.Substring(_startIndex, _endIndex - _startIndex);
+        }
+
+        /// <summary>Moves to the next component and parses it as an Int32.</summary>
+        public unsafe int ParseNextInt32()
+        {
+            MoveNextOrFail();
+
+            bool negative = false;
+            int result = 0;
+
+            fixed (char* bufferPtr = _buffer)
+            {
+                char* p = bufferPtr + _startIndex;
+                char* end = bufferPtr + _endIndex;
+
+                if (p == end)
+                {
+                    ThrowForInvalidData();
+                }
+
+                if (*p == '-')
+                {
+                    negative = true;
+                    p++;
+                    if (p == end)
+                    {
+                        ThrowForInvalidData();
+                    }
+                }
+
+                while (p != end)
+                {
+                    int d = *p - '0';
+                    if (d < 0 || d > 9)
+                    {
+                        ThrowForInvalidData();
+                    }
+                    result = negative ? checked((result * 10) - d) : checked((result * 10) + d);
+
+                    p++;
+                }
+            }
+
+            Debug.Assert(result == int.Parse(ExtractCurrent()), "Expected manually parsed result to match Parse result");
+            return result;
+        }
+
+        /// <summary>Moves to the next component and parses it as an Int64.</summary>
+        public unsafe long ParseNextInt64()
+        {
+            MoveNextOrFail();
+
+            bool negative = false;
+            long result = 0;
+
+            fixed (char* bufferPtr = _buffer)
+            {
+                char* p = bufferPtr + _startIndex;
+                char* end = bufferPtr + _endIndex;
+
+                if (p == end)
+                {
+                    ThrowForInvalidData();
+                }
+
+                if (*p == '-')
+                {
+                    negative = true;
+                    p++;
+                    if (p == end)
+                    {
+                        ThrowForInvalidData();
+                    }
+                }
+
+                while (p != end)
+                {
+                    int d = *p - '0';
+                    if (d < 0 || d > 9)
+                    {
+                        ThrowForInvalidData();
+                    }
+                    result = negative ? checked((result * 10) - d) : checked((result * 10) + d);
+
+                    p++;
+                }
+            }
+
+            Debug.Assert(result == long.Parse(ExtractCurrent()), "Expected manually parsed result to match Parse result");
+            return result;
+        }
+
+        /// <summary>Moves to the next component and parses it as a UInt32.</summary>
+        public unsafe uint ParseNextUInt32()
+        {
+            MoveNextOrFail();
+            if (_startIndex == _endIndex)
+            {
+                ThrowForInvalidData();
+            }
+
+            uint result = 0;
+            fixed (char* bufferPtr = _buffer)
+            {
+                char* p = bufferPtr + _startIndex;
+                char* end = bufferPtr + _endIndex;
+                while (p != end)
+                {
+                    int d = *p - '0';
+                    if (d < 0 || d > 9)
+                    {
+                        ThrowForInvalidData();
+                    }
+                    result = (uint)checked((result * 10) + d);
+
+                    p++;
+                }
+            }
+
+            Debug.Assert(result == uint.Parse(ExtractCurrent()), "Expected manually parsed result to match Parse result");
+            return result;
+        }
+
+        /// <summary>Moves to the next component and parses it as a UInt64.</summary>
+        public unsafe ulong ParseNextUInt64()
+        {
+            MoveNextOrFail();
+
+            ulong result = 0;
+            fixed (char* bufferPtr = _buffer)
+            {
+                char* p = bufferPtr + _startIndex;
+                char* end = bufferPtr + _endIndex;
+                while (p != end)
+                {
+                    int d = *p - '0';
+                    if (d < 0 || d > 9)
+                    {
+                        ThrowForInvalidData();
+                    }
+                    result = checked((result * 10ul) + (ulong)d);
+
+                    p++;
+                }
+            }
+
+            Debug.Assert(result == ulong.Parse(ExtractCurrent()), "Expected manually parsed result to match Parse result");
+            return result;
+        }
+
+        /// <summary>Moves to the next component and parses it as a Char.</summary>
+        public char ParseNextChar()
+        {
+            MoveNextOrFail();
+
+            if (_endIndex - _startIndex != 1)
+            {
+                ThrowForInvalidData();
+            }
+            char result = _buffer[_startIndex];
+
+            Debug.Assert(result == char.Parse(ExtractCurrent()), "Expected manually parsed result to match Parse result");
+            return result;
+        }
+
+        internal delegate T ParseRawFunc<T>(string buffer, ref int startIndex, ref int endIndex);
+
+        /// <summary>
+        /// Moves to the next component and hands the raw buffer and indexing data to a selector function
+        /// that can validate and return the appropriate data from the component.
+        /// </summary>
+        internal T ParseRaw<T>(ParseRawFunc<T> selector)
+        {
+            MoveNextOrFail();
+            return selector(_buffer, ref _startIndex, ref _endIndex);
+        }
+
+        /// <summary>
+        /// Gets the current subcomponent and all remaining components of the string as a string.
+        /// </summary>
+        public string ExtractCurrentToEnd()
+        {
+            if (_buffer == null || _startIndex == -1)
+            {
+                throw new InvalidOperationException();
+            }
+            return _buffer.Substring(_startIndex);
+        }
+
+        /// <summary>Throws unconditionally for invalid data.</summary>
+        private static void ThrowForInvalidData()
+        {
+            throw new InvalidDataException();
+        }
+    }
+}
diff --git a/src/Tools/dotnet-trace/dotnet-trace.csproj b/src/Tools/dotnet-trace/dotnet-trace.csproj
new file mode 100644 (file)
index 0000000..092fb25
--- /dev/null
@@ -0,0 +1,29 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+
+    <!-- Target .NET Core 2.1 so it will run on LTS -->
+    <TargetFramework>netcoreapp2.1</TargetFramework>
+
+    <RootNamespace>Microsoft.Diagnostics.Tools.Collect</RootNamespace>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+
+    <!-- Don't pack until ship engineering is done. Currently causing the official job to fail.
+    <IsPackable>true</IsPackable>
+    <PackAsTool>true</PackAsTool>
+    -->
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Compile Include="..\Common\CommandLineException.cs" Link="CommandLineException.cs" />
+    <Compile Include="..\Common\ConsoleCancellation.cs" Link="ConsoleCancellation.cs" />
+    <Compile Include="..\Common\DebugUtil.cs" Link="DebugUtil.cs" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.2.5" />
+    <PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="2.0.30" />
+  </ItemGroup>
+
+</Project>
\ No newline at end of file