Пример #1
0
 private bool BuildDiagnosticsClient()
 {
     if (ProcessLauncher.Launcher.HasChildProc)
     {
         try
         {
             _diagnosticsClient = ReversedDiagnosticsClientBuilder.Build(ProcessLauncher.Launcher, "dotnet-counters", 10);
         }
         catch (TimeoutException)
         {
             Console.Error.WriteLine("Unable to start tracing session - the target app failed to connect to the diagnostics port. This may happen if the target application is running .NET Core 3.1 or older versions. Attaching at startup is only available from .NET 5.0 or later.");
             if (!ProcessLauncher.Launcher.ChildProc.HasExited)
             {
                 ProcessLauncher.Launcher.ChildProc.Kill();
             }
             return(false);
         }
     }
     else
     {
         _diagnosticsClient = new DiagnosticsClient(_processId);
     }
     return(true);
 }
Пример #2
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)
        {
            int ret = 0;

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

                if (!ProcessLauncher.Launcher.HasChildProc)
                {
                    // 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);

                DiagnosticsClient diagnosticsClient;
                Process           process;
                if (ProcessLauncher.Launcher.HasChildProc)
                {
                    try
                    {
                        diagnosticsClient = ReversedDiagnosticsClientBuilder.Build(ProcessLauncher.Launcher, "dotnet-trace", 10);
                    }
                    catch (TimeoutException)
                    {
                        Console.Error.WriteLine("Unable to start tracing session - the target app failed to connect to the diagnostics port. This may happen if the target application is running .NET Core 3.1 or older versions. Attaching at startup is only available from .NET 5.0 or later.");
                        if (!ProcessLauncher.Launcher.ChildProc.HasExited)
                        {
                            ProcessLauncher.Launcher.ChildProc.Kill();
                        }
                        await ReversedDiagnosticsClientBuilder.Server.DisposeAsync();

                        return(ErrorCodes.SessionCreationError);
                    }
                    process = ProcessLauncher.Launcher.ChildProc;
                }
                else
                {
                    diagnosticsClient = new DiagnosticsClient(processId);
                    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 (ProcessLauncher.Launcher.HasChildProc)
                        {
                            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);
                    }
                    ret = 0;
                }
            }
            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();
                }

                if (ReversedDiagnosticsClientBuilder.Server != null)
                {
                    await ReversedDiagnosticsClientBuilder.Server.DisposeAsync();
                }
            }
            return(await Task.FromResult(ret));
        }