예제 #1
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>
        /// <param name="port">Path to the diagnostic port to be created.</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)
        {
            int ret = 0;

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

                if (!ProcessLauncher.Launcher.HasChildProc)
                {
                    if (CommandUtils.ValidateArguments(processId, name, diagnosticPort, out int resolvedProcessId))
                    {
                        processId = resolvedProcessId;
                    }
                    else
                    {
                        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);

                using (DiagnosticsClientHolder holder = await builder.Build(ct, processId, diagnosticPort))
                {
                    diagnosticsClient = holder.Client;
                    if (shouldResumeRuntime)
                    {
                        process = Process.GetProcessById(holder.EndpointInfo.ProcessId);
                    }
                    else
                    {
                        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());

                    using (VirtualTerminalMode vTermMode = VirtualTerminalMode.TryEnable())
                    {
                        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        : {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).ContinueWith((task) => shouldExit.Set());

                            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();
                            }

                            // 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) &&
                                    !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()}");
                ret = ErrorCodes.TracingError;
            }
            finally
            {
                if (console.GetTerminal() != null)
                {
                    Console.CursorVisible = true;
                }

                // If we launched a child proc that hasn't exited yet, terminate it before we exit.
                if (ProcessLauncher.Launcher.HasChildProc && !ProcessLauncher.Launcher.ChildProc.HasExited)
                {
                    ProcessLauncher.Launcher.ChildProc.Kill();
                }
            }
            return(await Task.FromResult(ret));
        }
예제 #2
0
 public void InvalidProvider_CorrectlyThrows(string providerToParse)
 {
     Assert.Throws <ArgumentException>(() => Extensions.ToProviders(providerToParse));
 }
예제 #3
0
 public void OutOfRangekeyword_CorrectlyThrows(string providerToParse)
 {
     Assert.Throws <OverflowException>(() => Extensions.ToProviders(providerToParse));
 }
예제 #4
0
 public void TextLevelProviderSpec_CorrectlyThrows(string providerToParse)
 {
     Assert.Throws <ArgumentException>(() => Extensions.ToProviders(providerToParse));
 }
예제 #5
0
 public void MultipleValidProvidersWithOneInvalidKeyword_CorrectlyThrows(string providersToParse)
 {
     Assert.Throws <FormatException>(() => Extensions.ToProviders(providersToParse));
 }
예제 #6
0
 public void MultipleValidProvidersWithOneOutOfRangeKeyword_CorrectlyThrows(string providersToParse)
 {
     Assert.Throws <OverflowException>(() => Extensions.ToProviders(providersToParse));
 }
예제 #7
0
 public void Invalidkeyword_CorrectlyThrows(string providerToParse)
 {
     Assert.Throws <FormatException>(() => Extensions.ToProviders(providerToParse));
 }
예제 #8
0
        /// <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.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));
        }