Esempio n. 1
0
        public static int ConvertFile(IConsole console, FileInfo inputFilename, TraceFileFormat format, FileInfo output)
        {
            if ((int)format <= 0)
            {
                Console.Error.WriteLine("--format is required.");
                return(ErrorCodes.ArgumentError);
            }

            if (format == TraceFileFormat.NetTrace)
            {
                Console.Error.WriteLine("Cannot convert a nettrace file to nettrace format.");
                return(ErrorCodes.ArgumentError);
            }

            if (!inputFilename.Exists)
            {
                Console.Error.WriteLine($"File '{inputFilename}' does not exist.");
                return(ErrorCodes.ArgumentError);
            }

            if (output == null)
            {
                output = inputFilename;
            }

            TraceFileFormatConverter.ConvertToFormat(format, inputFilename.FullName, output.FullName);
            return(0);
        }
Esempio n. 2
0
        public static int ConvertFile(IConsole console, FileInfo inputFilename, TraceFileFormat format, FileInfo output)
        {
            if (format == TraceFileFormat.Netperf)
            {
                throw new ArgumentException("Cannot convert to netperf format.");
            }

            if (!inputFilename.Exists)
            {
                throw new FileNotFoundException($"File '{inputFilename}' does not exist.");
            }

            if (output == null)
            {
                output = inputFilename;
            }

            TraceFileFormatConverter.ConvertToFormat(format, inputFilename.FullName, output.FullName);

            return(0);
        }
        /// <summary>
        /// Collects a diagnostic trace from a currently running process.
        /// </summary>
        /// <param name="ct">The cancellation token</param>
        /// <param name="console"></param>
        /// <param name="processId">The process to collect the trace from.</param>
        /// <param name="name">The name of process to collect the trace from.</param>
        /// <param name="output">The output path for the collected trace data.</param>
        /// <param name="buffersize">Sets the size of the in-memory circular buffer in megabytes.</param>
        /// <param name="providers">A list of EventPipe providers to be enabled. This is in the form 'Provider[,Provider]', where Provider is in the form: 'KnownProviderName[:Flags[:Level][:KeyValueArgs]]', and KeyValueArgs is in the form: '[key1=value1][;key2=value2]'</param>
        /// <param name="profile">A named pre-defined set of provider configurations that allows common tracing scenarios to be specified succinctly.</param>
        /// <param name="format">The desired format of the created trace file.</param>
        /// <param name="duration">The duration of trace to be taken. </param>
        /// <param name="clrevents">A list of CLR events to be emitted.</param>
        /// <param name="clreventlevel">The verbosity level of CLR events</param>
        /// <returns></returns>
        private static async Task <int> Collect(CancellationToken ct, IConsole console, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration, string clrevents, string clreventlevel, string name)
        {
            try
            {
                Debug.Assert(output != null);
                Debug.Assert(profile != null);

                // Either processName or processId has to be specified.
                if (name != null)
                {
                    if (processId != 0)
                    {
                        Console.WriteLine("Can only specify either --name or --process-id option.");
                        return(ErrorCodes.ArgumentError);
                    }
                    processId = CommandUtils.FindProcessIdWithName(name);
                    if (processId < 0)
                    {
                        return(ErrorCodes.ArgumentError);
                    }
                }

                if (processId < 0)
                {
                    Console.Error.WriteLine("Process ID should not be negative.");
                    return(ErrorCodes.ArgumentError);
                }
                else if (processId == 0)
                {
                    Console.Error.WriteLine("--process-id is required");
                    return(ErrorCodes.ArgumentError);
                }

                if (profile.Length == 0 && providers.Length == 0 && clrevents.Length == 0)
                {
                    Console.Out.WriteLine("No profile or providers specified, defaulting to trace profile 'cpu-sampling'");
                    profile = "cpu-sampling";
                }

                Dictionary <string, string> enabledBy = new Dictionary <string, string>();

                var providerCollection = Extensions.ToProviders(providers);
                foreach (EventPipeProvider providerCollectionProvider in providerCollection)
                {
                    enabledBy[providerCollectionProvider.Name] = "--providers ";
                }

                if (profile.Length != 0)
                {
                    var selectedProfile = ListProfilesCommandHandler.DotNETRuntimeProfiles
                                          .FirstOrDefault(p => p.Name.Equals(profile, StringComparison.OrdinalIgnoreCase));
                    if (selectedProfile == null)
                    {
                        Console.Error.WriteLine($"Invalid profile name: {profile}");
                        return(ErrorCodes.ArgumentError);
                    }

                    Profile.MergeProfileAndProviders(selectedProfile, providerCollection, enabledBy);
                }

                // Parse --clrevents parameter
                if (clrevents.Length != 0)
                {
                    // Ignore --clrevents if CLR event provider was already specified via --profile or --providers command.
                    if (enabledBy.ContainsKey(Extensions.CLREventProviderName))
                    {
                        Console.WriteLine($"The argument --clrevents {clrevents} will be ignored because the CLR provider was configured via either --profile or --providers command.");
                    }
                    else
                    {
                        var clrProvider = Extensions.ToCLREventPipeProvider(clrevents, clreventlevel);
                        providerCollection.Add(clrProvider);
                        enabledBy[Extensions.CLREventProviderName] = "--clrevents";
                    }
                }


                if (providerCollection.Count <= 0)
                {
                    Console.Error.WriteLine("No providers were specified to start a trace.");
                    return(ErrorCodes.ArgumentError);
                }

                PrintProviders(providerCollection, enabledBy);

                var process    = Process.GetProcessById(processId);
                var shouldExit = new ManualResetEvent(false);
                var shouldStopAfterDuration       = duration != default(TimeSpan);
                var rundownRequested              = false;
                System.Timers.Timer durationTimer = null;

                ct.Register(() => shouldExit.Set());

                var diagnosticsClient = new DiagnosticsClient(processId);
                using (VirtualTerminalMode vTermMode = VirtualTerminalMode.TryEnable())
                {
                    EventPipeSession session = null;
                    try
                    {
                        session = diagnosticsClient.StartEventPipeSession(providerCollection, true, (int)buffersize);
                    }
                    catch (DiagnosticsClientException e)
                    {
                        Console.Error.WriteLine($"Unable to start a tracing session: {e.ToString()}");
                    }

                    if (session == null)
                    {
                        Console.Error.WriteLine("Unable to create session.");
                        return(ErrorCodes.SessionCreationError);
                    }

                    if (shouldStopAfterDuration)
                    {
                        durationTimer           = new System.Timers.Timer(duration.TotalMilliseconds);
                        durationTimer.Elapsed  += (s, e) => shouldExit.Set();
                        durationTimer.AutoReset = false;
                    }

                    var stopwatch = new Stopwatch();
                    durationTimer?.Start();
                    stopwatch.Start();

                    LineRewriter rewriter = null;

                    using (var fs = new FileStream(output.FullName, FileMode.Create, FileAccess.Write))
                    {
                        Console.Out.WriteLine($"Process        : {process.MainModule.FileName}");
                        Console.Out.WriteLine($"Output File    : {fs.Name}");
                        if (shouldStopAfterDuration)
                        {
                            Console.Out.WriteLine($"Trace Duration : {duration.ToString(@"dd\:hh\:mm\:ss")}");
                        }
                        Console.Out.WriteLine("\n\n");

                        var  fileInfo = new FileInfo(output.FullName);
                        Task copyTask = session.EventStream.CopyToAsync(fs);

                        if (!Console.IsOutputRedirected)
                        {
                            rewriter = new LineRewriter {
                                LineToClear = Console.CursorTop - 1
                            };
                            Console.CursorVisible = false;
                        }

                        Action printStatus = () =>
                        {
                            if (!Console.IsOutputRedirected)
                            {
                                rewriter?.RewriteConsoleLine();
                            }

                            fileInfo.Refresh();
                            Console.Out.WriteLine($"[{stopwatch.Elapsed.ToString(@"dd\:hh\:mm\:ss")}]\tRecording trace {GetSize(fileInfo.Length)}");
                            Console.Out.WriteLine("Press <Enter> or <Ctrl+C> to exit...");
                            if (rundownRequested)
                            {
                                Console.Out.WriteLine("Stopping the trace. This may take up to minutes depending on the application being traced.");
                            }
                        };

                        while (!shouldExit.WaitOne(100) && !(!Console.IsInputRedirected && Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.Enter))
                        {
                            printStatus();
                        }

                        // Behavior concerning Enter moving text in the terminal buffer when at the bottom of the buffer
                        // is different between Console/Terminals on Windows and Mac/Linux
                        if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
                            !Console.IsOutputRedirected &&
                            rewriter != null &&
                            Math.Abs(Console.CursorTop - Console.BufferHeight) == 1)
                        {
                            rewriter.LineToClear--;
                        }
                        durationTimer?.Stop();
                        rundownRequested = true;
                        session.Stop();

                        do
                        {
                            printStatus();
                        } while (!copyTask.Wait(100));
                    }

                    Console.Out.WriteLine("\nTrace completed.");

                    if (format != TraceFileFormat.NetTrace)
                    {
                        TraceFileFormatConverter.ConvertToFormat(format, output.FullName);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"[ERROR] {ex.ToString()}");
                return(ErrorCodes.TracingError);
            }
            finally
            {
                if (console.GetTerminal() != null)
                {
                    Console.CursorVisible = true;
                }
            }

            return(await Task.FromResult(0));
        }
Esempio n. 4
0
        /// <summary>
        /// Collects a diagnostic trace from a currently running process.
        /// </summary>
        /// <param name="ct">The cancellation token</param>
        /// <param name="console"></param>
        /// <param name="processId">The process to collect the trace from.</param>
        /// <param name="output">The output path for the collected trace data.</param>
        /// <param name="buffersize">Sets the size of the in-memory circular buffer in megabytes.</param>
        /// <param name="providers">A list of EventPipe providers to be enabled. This is in the form 'Provider[,Provider]', where Provider is in the form: 'KnownProviderName[:Flags[:Level][:KeyValueArgs]]', and KeyValueArgs is in the form: '[key1=value1][;key2=value2]'</param>
        /// <param name="profile">A named pre-defined set of provider configurations that allows common tracing scenarios to be specified succinctly.</param>
        /// <param name="format">The desired format of the created trace file.</param>
        /// <returns></returns>
        private static async Task <int> Collect(CancellationToken ct, IConsole console, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format)
        {
            try
            {
                Debug.Assert(output != null);
                Debug.Assert(profile != null);
                if (processId <= 0)
                {
                    Console.Error.WriteLine("Process ID should not be negative.");
                    return(ErrorCodes.ArgumentError);
                }

                var selectedProfile = ListProfilesCommandHandler.DotNETRuntimeProfiles
                                      .FirstOrDefault(p => p.Name.Equals(profile, StringComparison.OrdinalIgnoreCase));
                if (selectedProfile == null)
                {
                    Console.Error.WriteLine($"Invalid profile name: {profile}");
                    return(ErrorCodes.ArgumentError);
                }

                var providerCollection = Extensions.ToProviders(providers);
                var profileProviders   = new List <Provider>();

                // If user defined a different key/level on the same provider via --providers option that was specified via --profile option,
                // --providers option takes precedence. Go through the list of providers specified and only add it if it wasn't specified
                // via --providers options.
                if (selectedProfile.Providers != null)
                {
                    foreach (Provider selectedProfileProvider in selectedProfile.Providers)
                    {
                        bool shouldAdd = true;

                        foreach (Provider providerCollectionProvider in providerCollection)
                        {
                            if (providerCollectionProvider.Name.Equals(selectedProfileProvider.Name))
                            {
                                shouldAdd = false;
                                break;
                            }
                        }

                        if (shouldAdd)
                        {
                            profileProviders.Add(selectedProfileProvider);
                        }
                    }
                }

                providerCollection.AddRange(profileProviders);

                if (providerCollection.Count <= 0)
                {
                    Console.Error.WriteLine("No providers were specified to start a trace.");
                    return(ErrorCodes.ArgumentError);
                }

                PrintProviders(providerCollection);

                var process       = Process.GetProcessById(processId);
                var configuration = new SessionConfiguration(
                    circularBufferSizeMB: buffersize,
                    format: EventPipeSerializationFormat.NetTrace,
                    providers: providerCollection);

                var shouldExit = new ManualResetEvent(false);
                var failed     = false;
                var terminated = false;

                ct.Register(() => shouldExit.Set());

                ulong sessionId = 0;
                using (Stream stream = EventPipeClient.CollectTracing(processId, configuration, out sessionId))
                    using (VirtualTerminalMode vTermMode = VirtualTerminalMode.TryEnable())
                    {
                        if (sessionId == 0)
                        {
                            Console.Error.WriteLine("Unable to create session.");
                            return(ErrorCodes.SessionCreationError);
                        }

                        var collectingTask = new Task(() => {
                            try
                            {
                                using (var fs = new FileStream(output.FullName, FileMode.Create, FileAccess.Write))
                                {
                                    Console.Out.WriteLine($"Process     : {process.MainModule.FileName}");
                                    Console.Out.WriteLine($"Output File : {fs.Name}");
                                    Console.Out.WriteLine($"\tSession Id: 0x{sessionId:X16}");
                                    lineToClear = Console.CursorTop;
                                    var buffer  = new byte[16 * 1024];

                                    while (true)
                                    {
                                        int nBytesRead = stream.Read(buffer, 0, buffer.Length);
                                        if (nBytesRead <= 0)
                                        {
                                            break;
                                        }
                                        fs.Write(buffer, 0, nBytesRead);

                                        ResetCurrentConsoleLine(vTermMode.IsEnabled);
                                        Console.Out.Write($"\tRecording trace {GetSize(fs.Length)}");

                                        Debug.WriteLine($"PACKET: {Convert.ToBase64String(buffer, 0, nBytesRead)} (bytes {nBytesRead})");
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                failed = true;
                                Console.Error.WriteLine($"[ERROR] {ex.ToString()}");
                            }
                            finally
                            {
                                terminated = true;
                                shouldExit.Set();
                            }
                        });
                        collectingTask.Start();

                        Console.Out.WriteLine("Press <Enter> or <Ctrl+C> to exit...");

                        do
                        {
                            while (!Console.KeyAvailable && !shouldExit.WaitOne(250))
                            {
                            }
                        } while (!shouldExit.WaitOne(0) && Console.ReadKey(true).Key != ConsoleKey.Enter);

                        if (!terminated)
                        {
                            EventPipeClient.StopTracing(processId, sessionId);
                        }
                        await collectingTask;
                    }

                Console.Out.WriteLine();
                Console.Out.WriteLine("Trace completed.");

                if (format != TraceFileFormat.NetTrace)
                {
                    TraceFileFormatConverter.ConvertToFormat(format, output.FullName);
                }

                return(failed ? ErrorCodes.TracingError : 0);
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"[ERROR] {ex.ToString()}");
                return(ErrorCodes.UnknownError);
            }
        }
Esempio n. 5
0
        /// <summary>
        /// Collects a diagnostic trace from a currently running process.
        /// </summary>
        /// <param name="ct">The cancellation token</param>
        /// <param name="console"></param>
        /// <param name="processId">The process to collect the trace from.</param>
        /// <param name="output">The output path for the collected trace data.</param>
        /// <param name="buffersize">Sets the size of the in-memory circular buffer in megabytes.</param>
        /// <param name="providers">A list of EventPipe providers to be enabled. This is in the form 'Provider[,Provider]', where Provider is in the form: 'KnownProviderName[:Flags[:Level][:KeyValueArgs]]', and KeyValueArgs is in the form: '[key1=value1][;key2=value2]'</param>
        /// <param name="profile">A named pre-defined set of provider configurations that allows common tracing scenarios to be specified succinctly.</param>
        /// <param name="format">The desired format of the created trace file.</param>
        /// <returns></returns>
        private static async Task <int> Collect(CancellationToken ct, IConsole console, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration)
        {
            try
            {
                Debug.Assert(output != null);
                Debug.Assert(profile != null);

                bool hasConsole = console.GetTerminal() != null;

                if (hasConsole)
                {
                    Console.Clear();
                }

                if (processId < 0)
                {
                    Console.Error.WriteLine("Process ID should not be negative.");
                    return(ErrorCodes.ArgumentError);
                }
                else if (processId == 0)
                {
                    Console.Error.WriteLine("--process-id is required");
                    return(ErrorCodes.ArgumentError);
                }

                if (profile.Length == 0 && providers.Length == 0)
                {
                    Console.Out.WriteLine("No profile or providers specified, defaulting to trace profile 'cpu-sampling'");
                    profile = "cpu-sampling";
                }

                Dictionary <string, string> enabledBy = new Dictionary <string, string>();

                var providerCollection = Extensions.ToProviders(providers);
                foreach (Provider providerCollectionProvider in providerCollection)
                {
                    enabledBy[providerCollectionProvider.Name] = "--providers ";
                }

                if (profile.Length != 0)
                {
                    var selectedProfile = ListProfilesCommandHandler.DotNETRuntimeProfiles
                                          .FirstOrDefault(p => p.Name.Equals(profile, StringComparison.OrdinalIgnoreCase));
                    if (selectedProfile == null)
                    {
                        Console.Error.WriteLine($"Invalid profile name: {profile}");
                        return(ErrorCodes.ArgumentError);
                    }

                    Profile.MergeProfileAndProviders(selectedProfile, providerCollection, enabledBy);
                }


                if (providerCollection.Count <= 0)
                {
                    Console.Error.WriteLine("No providers were specified to start a trace.");
                    return(ErrorCodes.ArgumentError);
                }

                PrintProviders(providerCollection, enabledBy);

                var process       = Process.GetProcessById(processId);
                var configuration = new SessionConfiguration(
                    circularBufferSizeMB: buffersize,
                    format: EventPipeSerializationFormat.NetTrace,
                    providers: providerCollection);

                var shouldExit = new ManualResetEvent(false);
                var shouldStopAfterDuration = duration != default(TimeSpan);
                var failed     = false;
                var terminated = false;
                System.Timers.Timer durationTimer = null;

                ct.Register(() => shouldExit.Set());

                ulong sessionId = 0;
                using (Stream stream = EventPipeClient.CollectTracing(processId, configuration, out sessionId))
                    using (VirtualTerminalMode vTermMode = VirtualTerminalMode.TryEnable())
                    {
                        if (sessionId == 0)
                        {
                            Console.Error.WriteLine("Unable to create session.");
                            return(ErrorCodes.SessionCreationError);
                        }

                        if (shouldStopAfterDuration)
                        {
                            durationTimer           = new System.Timers.Timer(duration.TotalMilliseconds);
                            durationTimer.Elapsed  += (s, e) => shouldExit.Set();
                            durationTimer.AutoReset = false;
                        }

                        var collectingTask = new Task(() =>
                        {
                            try
                            {
                                var stopwatch = new Stopwatch();
                                durationTimer?.Start();
                                stopwatch.Start();

                                using (var fs = new FileStream(output.FullName, FileMode.Create, FileAccess.Write))
                                {
                                    Console.Out.WriteLine($"Process        : {process.MainModule.FileName}");
                                    Console.Out.WriteLine($"Output File    : {fs.Name}");
                                    if (shouldStopAfterDuration)
                                    {
                                        Console.Out.WriteLine($"Trace Duration : {duration.ToString(@"dd\:hh\:mm\:ss")}");
                                    }

                                    Console.Out.WriteLine("\n\n");
                                    var buffer = new byte[16 * 1024];

                                    while (true)
                                    {
                                        int nBytesRead = stream.Read(buffer, 0, buffer.Length);
                                        if (nBytesRead <= 0)
                                        {
                                            break;
                                        }
                                        fs.Write(buffer, 0, nBytesRead);

                                        if (hasConsole)
                                        {
                                            lineToClear = Console.CursorTop - 1;
                                            ResetCurrentConsoleLine(vTermMode.IsEnabled);
                                        }

                                        Console.Out.WriteLine($"[{stopwatch.Elapsed.ToString(@"dd\:hh\:mm\:ss")}]\tRecording trace {GetSize(fs.Length)}");
                                        Console.Out.WriteLine("Press <Enter> or <Ctrl+C> to exit...");
                                        Debug.WriteLine($"PACKET: {Convert.ToBase64String(buffer, 0, nBytesRead)} (bytes {nBytesRead})");
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                failed = true;
                                Console.Error.WriteLine($"[ERROR] {ex.ToString()}");
                            }
                            finally
                            {
                                terminated = true;
                                shouldExit.Set();
                            }
                        });
                        collectingTask.Start();

                        do
                        {
                            while (!Console.KeyAvailable && !shouldExit.WaitOne(250))
                            {
                            }
                        } while (!shouldExit.WaitOne(0) && Console.ReadKey(true).Key != ConsoleKey.Enter);

                        if (!terminated)
                        {
                            durationTimer?.Stop();
                            EventPipeClient.StopTracing(processId, sessionId);
                        }
                        await collectingTask;
                    }

                Console.Out.WriteLine();
                Console.Out.WriteLine("Trace completed.");

                if (format != TraceFileFormat.NetTrace)
                {
                    TraceFileFormatConverter.ConvertToFormat(format, output.FullName);
                }

                return(failed ? ErrorCodes.TracingError : 0);
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"[ERROR] {ex.ToString()}");
                return(ErrorCodes.UnknownError);
            }
        }
        /// <summary>
        /// Collects a diagnostic trace from a currently running process or launch a child process and trace it.
        /// Append -- to the collect command to instruct the tool to run a command and trace it immediately. By default the IO from this process is hidden, but the --show-child-io option may be used to show the child process IO.
        /// </summary>
        /// <param name="ct">The cancellation token</param>
        /// <param name="console"></param>
        /// <param name="processId">The process to collect the trace from.</param>
        /// <param name="name">The name of process to collect the trace from.</param>
        /// <param name="output">The output path for the collected trace data.</param>
        /// <param name="buffersize">Sets the size of the in-memory circular buffer in megabytes.</param>
        /// <param name="providers">A list of EventPipe providers to be enabled. This is in the form 'Provider[,Provider]', where Provider is in the form: 'KnownProviderName[:Flags[:Level][:KeyValueArgs]]', and KeyValueArgs is in the form: '[key1=value1][;key2=value2]'</param>
        /// <param name="profile">A named pre-defined set of provider configurations that allows common tracing scenarios to be specified succinctly.</param>
        /// <param name="format">The desired format of the created trace file.</param>
        /// <param name="duration">The duration of trace to be taken. </param>
        /// <param name="clrevents">A list of CLR events to be emitted.</param>
        /// <param name="clreventlevel">The verbosity level of CLR events</param>
        /// <param name="port">Path to the diagnostic port to be created.</param>
        /// <param name="showchildio">Should IO from a child process be hidden.</param>
        /// <returns></returns>
        private static async Task <int> Collect(CancellationToken ct, IConsole console, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration, string clrevents, string clreventlevel, string name, string diagnosticPort, bool showchildio)
        {
            int  ret = 0;
            bool collectionStopped   = false;
            bool cancelOnEnter       = true;
            bool cancelOnCtrlC       = true;
            bool printStatusOverTime = true;

            try
            {
                Debug.Assert(output != null);
                Debug.Assert(profile != null);

                if (ProcessLauncher.Launcher.HasChildProc && showchildio)
                {
                    // If showing IO, then all IO (including CtrlC) behavior is delegated to the child process
                    cancelOnCtrlC       = false;
                    cancelOnEnter       = false;
                    printStatusOverTime = false;
                }
                else
                {
                    cancelOnCtrlC       = true;
                    cancelOnEnter       = !Console.IsInputRedirected;
                    printStatusOverTime = !Console.IsInputRedirected;
                }

                if (!cancelOnCtrlC)
                {
                    ct = CancellationToken.None;
                }

                if (!ProcessLauncher.Launcher.HasChildProc)
                {
                    if (showchildio)
                    {
                        Console.WriteLine("--show-child-io must not be specified when attaching to a process");
                        return(ErrorCodes.ArgumentError);
                    }
                    if (CommandUtils.ValidateArgumentsForAttach(processId, name, diagnosticPort, out int resolvedProcessId))
                    {
                        processId = resolvedProcessId;
                    }
                    else
                    {
                        return(ErrorCodes.ArgumentError);
                    }
                }
                else if (!CommandUtils.ValidateArgumentsForChildProcess(processId, name, diagnosticPort))
                {
                    return(ErrorCodes.ArgumentError);
                }

                if (profile.Length == 0 && providers.Length == 0 && clrevents.Length == 0)
                {
                    Console.Out.WriteLine("No profile or providers specified, defaulting to trace profile 'cpu-sampling'");
                    profile = "cpu-sampling";
                }

                Dictionary <string, string> enabledBy = new Dictionary <string, string>();

                var providerCollection = Extensions.ToProviders(providers);
                foreach (EventPipeProvider providerCollectionProvider in providerCollection)
                {
                    enabledBy[providerCollectionProvider.Name] = "--providers ";
                }

                if (profile.Length != 0)
                {
                    var selectedProfile = ListProfilesCommandHandler.DotNETRuntimeProfiles
                                          .FirstOrDefault(p => p.Name.Equals(profile, StringComparison.OrdinalIgnoreCase));
                    if (selectedProfile == null)
                    {
                        Console.Error.WriteLine($"Invalid profile name: {profile}");
                        return(ErrorCodes.ArgumentError);
                    }

                    Profile.MergeProfileAndProviders(selectedProfile, providerCollection, enabledBy);
                }

                // Parse --clrevents parameter
                if (clrevents.Length != 0)
                {
                    // Ignore --clrevents if CLR event provider was already specified via --profile or --providers command.
                    if (enabledBy.ContainsKey(Extensions.CLREventProviderName))
                    {
                        Console.WriteLine($"The argument --clrevents {clrevents} will be ignored because the CLR provider was configured via either --profile or --providers command.");
                    }
                    else
                    {
                        var clrProvider = Extensions.ToCLREventPipeProvider(clrevents, clreventlevel);
                        providerCollection.Add(clrProvider);
                        enabledBy[Extensions.CLREventProviderName] = "--clrevents";
                    }
                }


                if (providerCollection.Count <= 0)
                {
                    Console.Error.WriteLine("No providers were specified to start a trace.");
                    return(ErrorCodes.ArgumentError);
                }

                PrintProviders(providerCollection, enabledBy);

                DiagnosticsClient        diagnosticsClient;
                Process                  process;
                DiagnosticsClientBuilder builder = new DiagnosticsClientBuilder("dotnet-trace", 10);
                bool shouldResumeRuntime         = ProcessLauncher.Launcher.HasChildProc || !string.IsNullOrEmpty(diagnosticPort);
                var  shouldExit = new ManualResetEvent(false);
                ct.Register(() => shouldExit.Set());

                using (DiagnosticsClientHolder holder = await builder.Build(ct, processId, diagnosticPort, showChildIO: showchildio, printLaunchCommand: true))
                {
                    // if builder returned null, it means we received ctrl+C while waiting for clients to connect. Exit gracefully.
                    if (holder == null)
                    {
                        return(await Task.FromResult(ret));
                    }
                    diagnosticsClient = holder.Client;
                    if (shouldResumeRuntime)
                    {
                        process = Process.GetProcessById(holder.EndpointInfo.ProcessId);
                    }
                    else
                    {
                        process = Process.GetProcessById(processId);
                    }
                    string processMainModuleFileName = "";

                    // Reading the process MainModule filename can fail if the target process closes
                    // or isn't fully setup. Retry a few times to attempt to address the issue
                    for (int attempts = 0; true; attempts++)
                    {
                        try
                        {
                            processMainModuleFileName = process.MainModule.FileName;
                            break;
                        }
                        catch
                        {
                            if (attempts > 10)
                            {
                                Console.Error.WriteLine("Unable to examine process.");
                                return(ErrorCodes.SessionCreationError);
                            }
                            Thread.Sleep(200);
                        }
                    }

                    if (String.Equals(output.Name, DefaultTraceName, StringComparison.OrdinalIgnoreCase))
                    {
                        DateTime now = DateTime.Now;
                        var      processMainModuleFileInfo = new FileInfo(processMainModuleFileName);
                        output = new FileInfo($"{processMainModuleFileInfo.Name}_{now:yyyyMMdd}_{now:HHmmss}.nettrace");
                    }

                    var shouldStopAfterDuration       = duration != default(TimeSpan);
                    var rundownRequested              = false;
                    System.Timers.Timer durationTimer = null;


                    using (VirtualTerminalMode vTermMode = printStatusOverTime ? VirtualTerminalMode.TryEnable() : null)
                    {
                        EventPipeSession session = null;
                        try
                        {
                            session = diagnosticsClient.StartEventPipeSession(providerCollection, true, (int)buffersize);
                            if (shouldResumeRuntime)
                            {
                                diagnosticsClient.ResumeRuntime();
                            }
                        }
                        catch (DiagnosticsClientException e)
                        {
                            Console.Error.WriteLine($"Unable to start a tracing session: {e.ToString()}");
                        }

                        if (session == null)
                        {
                            Console.Error.WriteLine("Unable to create session.");
                            return(ErrorCodes.SessionCreationError);
                        }

                        if (shouldStopAfterDuration)
                        {
                            durationTimer           = new System.Timers.Timer(duration.TotalMilliseconds);
                            durationTimer.Elapsed  += (s, e) => shouldExit.Set();
                            durationTimer.AutoReset = false;
                        }

                        var stopwatch = new Stopwatch();
                        durationTimer?.Start();
                        stopwatch.Start();

                        LineRewriter rewriter = null;

                        using (var fs = new FileStream(output.FullName, FileMode.Create, FileAccess.Write))
                        {
                            Console.Out.WriteLine($"Process        : {processMainModuleFileName}");
                            Console.Out.WriteLine($"Output File    : {fs.Name}");
                            if (shouldStopAfterDuration)
                            {
                                Console.Out.WriteLine($"Trace Duration : {duration.ToString(@"dd\:hh\:mm\:ss")}");
                            }
                            Console.Out.WriteLine("\n\n");

                            var  fileInfo       = new FileInfo(output.FullName);
                            Task copyTask       = session.EventStream.CopyToAsync(fs);
                            Task shouldExitTask = copyTask.ContinueWith((task) => shouldExit.Set());

                            if (printStatusOverTime)
                            {
                                rewriter = new LineRewriter {
                                    LineToClear = Console.CursorTop - 1
                                };
                                Console.CursorVisible = false;
                            }

                            Action printStatus = () =>
                            {
                                if (printStatusOverTime)
                                {
                                    rewriter?.RewriteConsoleLine();
                                    fileInfo.Refresh();
                                    Console.Out.WriteLine($"[{stopwatch.Elapsed.ToString(@"dd\:hh\:mm\:ss")}]\tRecording trace {GetSize(fileInfo.Length)}");
                                    Console.Out.WriteLine("Press <Enter> or <Ctrl+C> to exit...");
                                }

                                if (rundownRequested)
                                {
                                    Console.Out.WriteLine("Stopping the trace. This may take up to minutes depending on the application being traced.");
                                }
                            };

                            while (!shouldExit.WaitOne(100) && !(cancelOnEnter && Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.Enter))
                            {
                                printStatus();
                            }

                            // if the CopyToAsync ended early (target program exited, etc.), the we don't need to stop the session.
                            if (!copyTask.Wait(0))
                            {
                                // Behavior concerning Enter moving text in the terminal buffer when at the bottom of the buffer
                                // is different between Console/Terminals on Windows and Mac/Linux
                                if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
                                    printStatusOverTime &&
                                    rewriter != null &&
                                    Math.Abs(Console.CursorTop - Console.BufferHeight) == 1)
                                {
                                    rewriter.LineToClear--;
                                }
                                collectionStopped = true;
                                durationTimer?.Stop();
                                rundownRequested = true;
                                session.Stop();

                                do
                                {
                                    printStatus();
                                } while (!copyTask.Wait(100));
                            }
                            // At this point the copyTask will have finished, so wait on the shouldExitTask in case it threw
                            // an exception or had some other interesting behavior
                            shouldExitTask.Wait();
                        }

                        Console.Out.WriteLine($"\nTrace completed.");

                        if (format != TraceFileFormat.NetTrace)
                        {
                            TraceFileFormatConverter.ConvertToFormat(format, output.FullName);
                        }
                    }

                    if (!collectionStopped && !ct.IsCancellationRequested)
                    {
                        // If the process is shutting down by itself print the return code from the process.
                        // Capture this before leaving the using, as the Dispose of the DiagnosticsClientHolder
                        // may terminate the target process causing it to have the wrong error code
                        if (ProcessLauncher.Launcher.HasChildProc && ProcessLauncher.Launcher.ChildProc.WaitForExit(5000))
                        {
                            ret = ProcessLauncher.Launcher.ChildProc.ExitCode;
                            Console.WriteLine($"Process exited with code '{ret}'.");
                            collectionStopped = true;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"[ERROR] {ex.ToString()}");
                ret = ErrorCodes.TracingError;
                collectionStopped = true;
            }
            finally
            {
                if (printStatusOverTime)
                {
                    if (console.GetTerminal() != null)
                    {
                        Console.CursorVisible = true;
                    }
                }

                if (ProcessLauncher.Launcher.HasChildProc)
                {
                    if (!collectionStopped || ct.IsCancellationRequested)
                    {
                        ret = ErrorCodes.TracingError;
                    }

                    // If we launched a child proc that hasn't exited yet, terminate it before we exit.
                    if (!ProcessLauncher.Launcher.ChildProc.HasExited)
                    {
                        ProcessLauncher.Launcher.ChildProc.Kill();
                    }
                }
            }
            return(await Task.FromResult(ret));
        }
Esempio n. 7
0
        /// <summary>
        /// Collects a diagnostic trace from a currently running process.
        /// </summary>
        /// <param name="console"></param>
        /// <param name="processId">The process to collect the trace from.</param>
        /// <param name="output">The output path for the collected trace data.</param>
        /// <param name="buffersize">Sets the size of the in-memory circular buffer in megabytes.</param>
        /// <param name="providers">A list of EventPipe providers to be enabled. This is in the form 'Provider[,Provider]', where Provider is in the form: '(GUID|KnownProviderName)[:Flags[:Level][:KeyValueArgs]]', and KeyValueArgs is in the form: '[key1=value1][;key2=value2]'</param>
        /// <param name="profile">A named pre-defined set of provider configurations that allows common tracing scenarios to be specified succinctly.</param>
        /// <param name="format">The desired format of the created trace file.</param>
        /// <returns></returns>
        private static async Task <int> Collect(IConsole console, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format)
        {
            try
            {
                if (output == null)
                {
                    throw new ArgumentNullException(nameof(output));
                }
                if (processId <= 0)
                {
                    throw new ArgumentException(nameof(processId));
                }
                if (profile == null)
                {
                    throw new ArgumentNullException(nameof(profile));
                }

                var selectedProfile = ListProfilesCommandHandler.DotNETRuntimeProfiles
                                      .FirstOrDefault(p => p.Name.Equals(profile, StringComparison.OrdinalIgnoreCase));
                if (selectedProfile == null)
                {
                    throw new ArgumentException($"Invalid profile name: {profile}");
                }

                var providerCollection = Extensions.ToProviders(providers);
                if (selectedProfile.Providers != null)
                {
                    providerCollection.AddRange(selectedProfile.Providers);
                }
                if (providerCollection.Count <= 0)
                {
                    throw new ArgumentException("No providers were specified to start a trace.");
                }

                PrintProviders(providerCollection);

                var process       = Process.GetProcessById(processId);
                var configuration = new SessionConfiguration(
                    circularBufferSizeMB: buffersize,
                    outputPath: null, // Not used on the streaming scenario.
                    providers: providerCollection);

                var shouldExit = new ManualResetEvent(false);

                ulong sessionId = 0;
                using (Stream stream = EventPipeClient.CollectTracing(processId, configuration, out sessionId))
                    using (VirtualTerminalMode vTermMode = VirtualTerminalMode.TryEnable())
                    {
                        if (sessionId == 0)
                        {
                            Console.Error.WriteLine("Unable to create session.");
                            return(-1);
                        }

                        var collectingTask = new Task(() => {
                            using (var fs = new FileStream(output.FullName, FileMode.Create, FileAccess.Write))
                            {
                                Console.Out.WriteLine($"Process     : {process.MainModule.FileName}");
                                Console.Out.WriteLine($"Output File : {fs.Name}");
                                Console.Out.WriteLine($"\tSession Id: 0x{sessionId:X16}");
                                lineToClear = Console.CursorTop;

                                while (true)
                                {
                                    var buffer     = new byte[16 * 1024];
                                    int nBytesRead = stream.Read(buffer, 0, buffer.Length);
                                    if (nBytesRead <= 0)
                                    {
                                        break;
                                    }
                                    fs.Write(buffer, 0, nBytesRead);

                                    ResetCurrentConsoleLine(vTermMode.IsEnabled);
                                    Console.Out.Write($"\tRecording trace {GetSize(fs.Length)}");

                                    Debug.WriteLine($"PACKET: {Convert.ToBase64String(buffer, 0, nBytesRead)} (bytes {nBytesRead})");
                                }
                            }
                        });
                        collectingTask.Start();

                        Console.Out.WriteLine("Press <Enter> or <Ctrl+C> to exit...");
                        Console.CancelKeyPress += (sender, args) => {
                            args.Cancel = true;
                            shouldExit.Set();
                        };

                        do
                        {
                            while (!Console.KeyAvailable && !shouldExit.WaitOne(250))
                            {
                            }
                        } while (!shouldExit.WaitOne(0) && Console.ReadKey(true).Key != ConsoleKey.Enter);

                        EventPipeClient.StopTracing(processId, sessionId);
                        collectingTask.Wait();
                    }

                Console.Out.WriteLine();
                Console.Out.WriteLine("Trace completed.");

                TraceFileFormatConverter.ConvertToFormat(format, output.FullName);

                await Task.FromResult(0);

                return(sessionId != 0 ? 0 : 1);
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"[ERROR] {ex.ToString()}");
                return(1);
            }
        }
Esempio n. 8
0
        /// <summary>
        /// Collects a diagnostic trace from a currently running process.
        /// </summary>
        /// <param name="ct">The cancellation token</param>
        /// <param name="console"></param>
        /// <param name="processId">The process to collect the trace from.</param>
        /// <param name="name">The name of process to collect the trace from.</param>
        /// <param name="output">The output path for the collected trace data.</param>
        /// <param name="buffersize">Sets the size of the in-memory circular buffer in megabytes.</param>
        /// <param name="providers">A list of EventPipe providers to be enabled. This is in the form 'Provider[,Provider]', where Provider is in the form: 'KnownProviderName[:Flags[:Level][:KeyValueArgs]]', and KeyValueArgs is in the form: '[key1=value1][;key2=value2]'</param>
        /// <param name="profile">A named pre-defined set of provider configurations that allows common tracing scenarios to be specified succinctly.</param>
        /// <param name="format">The desired format of the created trace file.</param>
        /// <param name="duration">The duration of trace to be taken. </param>
        /// <param name="clrevents">A list of CLR events to be emitted.</param>
        /// <param name="clreventlevel">The verbosity level of CLR events</param>
        /// <returns></returns>
        private static async Task <int> Collect(CancellationToken ct, IConsole console, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration, string clrevents, string clreventlevel, string name)
        {
            try
            {
                Debug.Assert(output != null);
                Debug.Assert(profile != null);

                // Either processName or processId has to be specified.
                if (name != null)
                {
                    if (processId != 0)
                    {
                        Console.WriteLine("Can only specify either --name or --process-id option.");
                        return(ErrorCodes.ArgumentError);
                    }
                    processId = CommandUtils.FindProcessIdWithName(name);
                    if (processId < 0)
                    {
                        return(ErrorCodes.ArgumentError);
                    }
                }

                if (processId < 0)
                {
                    Console.Error.WriteLine("Process ID should not be negative.");
                    return(ErrorCodes.ArgumentError);
                }
                else if (processId == 0)
                {
                    Console.Error.WriteLine("--process-id is required");
                    return(ErrorCodes.ArgumentError);
                }

                bool hasConsole = console.GetTerminal() != null;

                if (hasConsole)
                {
                    Console.Clear();
                }

                if (profile.Length == 0 && providers.Length == 0 && clrevents.Length == 0)
                {
                    Console.Out.WriteLine("No profile or providers specified, defaulting to trace profile 'cpu-sampling'");
                    profile = "cpu-sampling";
                }

                Dictionary <string, string> enabledBy = new Dictionary <string, string>();

                var providerCollection = Extensions.ToProviders(providers);
                foreach (EventPipeProvider providerCollectionProvider in providerCollection)
                {
                    enabledBy[providerCollectionProvider.Name] = "--providers ";
                }

                if (profile.Length != 0)
                {
                    var selectedProfile = ListProfilesCommandHandler.DotNETRuntimeProfiles
                                          .FirstOrDefault(p => p.Name.Equals(profile, StringComparison.OrdinalIgnoreCase));
                    if (selectedProfile == null)
                    {
                        Console.Error.WriteLine($"Invalid profile name: {profile}");
                        return(ErrorCodes.ArgumentError);
                    }

                    Profile.MergeProfileAndProviders(selectedProfile, providerCollection, enabledBy);
                }

                // Parse --clrevents parameter
                if (clrevents.Length != 0)
                {
                    // Ignore --clrevents if CLR event provider was already specified via --profile or --providers command.
                    if (enabledBy.ContainsKey(Extensions.CLREventProviderName))
                    {
                        Console.WriteLine($"The argument --clrevents {clrevents} will be ignored because the CLR provider was configured via either --profile or --providers command.");
                    }
                    else
                    {
                        var clrProvider = Extensions.ToCLREventPipeProvider(clrevents, clreventlevel);
                        providerCollection.Add(clrProvider);
                        enabledBy[Extensions.CLREventProviderName] = "--clrevents";
                    }
                }


                if (providerCollection.Count <= 0)
                {
                    Console.Error.WriteLine("No providers were specified to start a trace.");
                    return(ErrorCodes.ArgumentError);
                }

                PrintProviders(providerCollection, enabledBy);

                var process    = Process.GetProcessById(processId);
                var shouldExit = new ManualResetEvent(false);
                var shouldStopAfterDuration = duration != default(TimeSpan);
                var failed           = false;
                var terminated       = false;
                var rundownRequested = false;
                System.Timers.Timer durationTimer = null;

                ct.Register(() => shouldExit.Set());

                var diagnosticsClient = new DiagnosticsClient(processId);
                using (VirtualTerminalMode vTermMode = VirtualTerminalMode.TryEnable())
                {
                    EventPipeSession session = null;
                    try
                    {
                        session = diagnosticsClient.StartEventPipeSession(providerCollection, true);
                    }
                    catch (DiagnosticsClientException e)
                    {
                        Console.Error.WriteLine($"Unable to start a tracing session: {e.ToString()}");
                    }

                    if (session == null)
                    {
                        Console.Error.WriteLine("Unable to create session.");
                        return(ErrorCodes.SessionCreationError);
                    }

                    if (shouldStopAfterDuration)
                    {
                        durationTimer           = new System.Timers.Timer(duration.TotalMilliseconds);
                        durationTimer.Elapsed  += (s, e) => shouldExit.Set();
                        durationTimer.AutoReset = false;
                    }

                    var collectingTask = new Task(() =>
                    {
                        try
                        {
                            var stopwatch = new Stopwatch();
                            durationTimer?.Start();
                            stopwatch.Start();

                            using (var fs = new FileStream(output.FullName, FileMode.Create, FileAccess.Write))
                            {
                                Console.Out.WriteLine($"Process        : {process.MainModule.FileName}");
                                Console.Out.WriteLine($"Output File    : {fs.Name}");
                                if (shouldStopAfterDuration)
                                {
                                    Console.Out.WriteLine($"Trace Duration : {duration.ToString(@"dd\:hh\:mm\:ss")}");
                                }

                                Console.Out.WriteLine("\n\n");
                                var buffer = new byte[16 * 1024];

                                while (true)
                                {
                                    int nBytesRead = session.EventStream.Read(buffer, 0, buffer.Length);
                                    if (nBytesRead <= 0)
                                    {
                                        break;
                                    }
                                    fs.Write(buffer, 0, nBytesRead);

                                    if (!rundownRequested)
                                    {
                                        if (hasConsole)
                                        {
                                            lineToClear = Console.CursorTop - 1;
                                            ResetCurrentConsoleLine(vTermMode.IsEnabled);
                                        }

                                        Console.Out.WriteLine($"[{stopwatch.Elapsed.ToString(@"dd\:hh\:mm\:ss")}]\tRecording trace {GetSize(fs.Length)}");
                                        Console.Out.WriteLine("Press <Enter> or <Ctrl+C> to exit...");
                                        Debug.WriteLine($"PACKET: {Convert.ToBase64String(buffer, 0, nBytesRead)} (bytes {nBytesRead})");
                                    }
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            failed = true;
                            Console.Error.WriteLine($"[ERROR] {ex.ToString()}");
                        }
                        finally
                        {
                            terminated = true;
                            shouldExit.Set();
                        }
                    });
                    collectingTask.Start();

                    do
                    {
                        while (!Console.KeyAvailable && !shouldExit.WaitOne(250))
                        {
                        }
                    } while (!shouldExit.WaitOne(0) && Console.ReadKey(true).Key != ConsoleKey.Enter);

                    if (!terminated)
                    {
                        durationTimer?.Stop();
                        if (hasConsole)
                        {
                            lineToClear = Console.CursorTop;
                            ResetCurrentConsoleLine(vTermMode.IsEnabled);
                        }
                        Console.Out.WriteLine("Stopping the trace. This may take up to minutes depending on the application being traced.");
                        rundownRequested = true;
                        session.Stop();
                    }
                    await collectingTask;
                }

                Console.Out.WriteLine();
                Console.Out.WriteLine("Trace completed.");

                if (format != TraceFileFormat.NetTrace)
                {
                    TraceFileFormatConverter.ConvertToFormat(format, output.FullName);
                }

                return(failed ? ErrorCodes.TracingError : 0);
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"[ERROR] {ex.ToString()}");
                return(ErrorCodes.UnknownError);
            }
        }