public Task <Stream> StartCpuTrace(int pid, int durationSeconds, CancellationToken cancellationToken) { if ((durationSeconds < 1) || (durationSeconds > MaxTraceSeconds)) { throw new InvalidOperationException("Invalid duration"); } //TODO Should we limit only 1 trace per file? var client = new DiagnosticsClient(pid); //TODO Pull event providers from the configuration. var cpuProviders = new EventPipeProvider[] { new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", System.Diagnostics.Tracing.EventLevel.Informational), new EventPipeProvider("Microsoft-Windows-DotNETRuntime", System.Diagnostics.Tracing.EventLevel.Informational, (long)Tracing.Parsers.ClrTraceEventParser.Keywords.Default) }; EventPipeSession session = client.StartEventPipeSession(cpuProviders, requestRundown: true); CancellationTokenSource linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_tokenSource.Token, cancellationToken); Task traceTask = Task.Run(async() => { try { await Task.Delay(TimeSpan.FromSeconds(durationSeconds), linkedTokenSource.Token); } finally { session.Stop(); linkedTokenSource.Dispose(); //We rely on the caller to Dispose the EventStream file. } }, CancellationToken.None); return(Task.FromResult(session.EventStream)); }
public EventPipeListener(int targetProcessId, IEnumerable <EventPipeProvider> providers) { var client = new DiagnosticsClient(targetProcessId); _listeningSession = client.StartEventPipeSession(providers); _eventSource = new EventPipeEventSource(_listeningSession.EventStream); _eventSource.Kernel.All += (TraceEvent @event) => KernelEvents?.Invoke(@event); _eventSource.Clr.All += (TraceEvent @event) => ClrEvents?.Invoke(@event); _eventSource.Dynamic.All += (TraceEvent @event) => CustomEvents?.Invoke(@event); Task.Factory.StartNew(() => _eventSource.Process(), _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); //with a code like this we can output a memory dump depending on some threshold of CPU usage /* * _eventSource.Dynamic.All += (TraceEvent obj) => * { * if (obj.EventName.Equals("EventCounters")) * { * IDictionary<string, object> payloadVal = (IDictionary<string, object>)(obj.PayloadValue(0)); * IDictionary<string, object> payloadFields = (IDictionary<string, object>)(payloadVal["Payload"]); * if (payloadFields["Name"].ToString().Equals("cpu-usage")) * { * double cpuUsage = Double.Parse(payloadFields["Mean"]); * if (cpuUsage > (double)threshold) * { * client.WriteDump(DumpType.Normal, "./minidump.dmp"); * } * } * } * } */ }
/// <summary> /// This uses CopyTo to copy the trace into a filesystem first, and then uses EventPipeEventSource /// on the file to post-process it and return the total # of events read. /// </summary> static Func <int, TestResult> UseFS(bool rundown, int bufferSize) { return((int pid) => { int eventsRead = 0; var totalTimeSw = new Stopwatch(); const string fileName = "./temp.nettrace"; EventPipeSession session = GetSession(pid, rundown, bufferSize); Console.WriteLine("Session created."); using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) { totalTimeSw.Start(); session.EventStream.CopyTo(fs); totalTimeSw.Stop(); } EventPipeEventSource epes = new EventPipeEventSource(fileName); epes.Dynamic.All += (TraceEvent data) => { eventsRead += 1; }; epes.Process(); Console.WriteLine("Read total: " + eventsRead.ToString()); Console.WriteLine("Dropped total: " + epes.EventsLost.ToString()); return new TestResult(eventsRead, epes.EventsLost, totalTimeSw.Elapsed); }); }
private static void StopSession(EventPipeSession session) { try { session.Stop(); } catch (EndOfStreamException) { // If the app we're monitoring exits abruptly, this may throw in which case we just swallow the exception and exit gracefully. } // We may time out if the process ended before we sent StopTracing command. We can just exit in that case. catch (TimeoutException) { } // On Unix platforms, we may actually get a PNSE since the pipe is gone with the process, and Runtime Client Library // does not know how to distinguish a situation where there is no pipe to begin with, or where the process has exited // before collection started and got rid of a pipe that once existed. // Since we are catching this at the end of a session we know that the pipe once existed (otherwise the exception would've // been thrown at the beginning directly) catch (PlatformNotSupportedException) { } // On non-abrupt exits, the socket may be already closed by the runtime and we won't be able to send a stop request through it. catch (ServerNotAvailableException) { } }
public Task <Stream> ProcessEvents(DiagnosticsClient client, TimeSpan duration, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); EventPipeSession session = null; try { session = client.StartEventPipeSession(_sourceConfig.GetProviders(), _sourceConfig.RequestRundown, _sourceConfig.BufferSizeInMB); } catch (EndOfStreamException e) { throw new InvalidOperationException("End of stream", e); } catch (Exception ex) when(!(ex is OperationCanceledException)) { throw new InvalidOperationException("Failed to start the event pipe session", ex); } _currentTask = Task.Run(async() => { using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); linkedSource.CancelAfter(duration); using var _ = linkedSource.Token.Register(() => _stopProcessingSource.TrySetResult(null)); // Use TaskCompletionSource instead of Task.Delay with cancellation to avoid // using exceptions for normal termination of event stream. await _stopProcessingSource.Task.ConfigureAwait(false); StopSession(session); }); return(Task.FromResult(session.EventStream)); }
public void StartListeningToCounters(string providerName) { var provider = new EventPipeProvider(providerName, EventLevel.Informational, (long)0, new Dictionary <string, string>() { { "EventCounterInterval", "1" } }); m_session = m_client.StartEventPipeSession(new List <EventPipeProvider>() { provider }); Task streamTask = Task.Run(() => { var buffer = new byte[1000]; while (true) { try { m_session.EventStream.Read(buffer, 0, 1000); } catch (Exception e) { } } }); }
private static async Task StopSessionAsync(EventPipeSession session) { // Cancel after a generous amount of time if process ended before command is sent. using CancellationTokenSource cancellationSource = new(IpcClient.ConnectTimeout); try { await session.StopAsync(cancellationSource.Token).ConfigureAwait(false); } catch (EndOfStreamException) { // If the app we're monitoring exits abruptly, this may throw in which case we just swallow the exception and exit gracefully. } // We may time out if the process ended before we sent StopTracing command. We can just exit in that case. catch (TimeoutException) { } // We may time out if the process ended before we sent StopTracing command. We can just exit in that case. catch (OperationCanceledException) { } // On Unix platforms, we may actually get a PNSE since the pipe is gone with the process, and Runtime Client Library // does not know how to distinguish a situation where there is no pipe to begin with, or where the process has exited // before collection started and got rid of a pipe that once existed. // Since we are catching this at the end of a session we know that the pipe once existed (otherwise the exception would've // been thrown at the beginning directly) catch (PlatformNotSupportedException) { } // On non-abrupt exits, the socket may be already closed by the runtime and we won't be able to send a stop request through it. catch (ServerNotAvailableException) { } }
/// <summary> /// This uses CopyToAsync to copy the trace into a filesystem first, and then uses EventPipeEventSource /// on the file to post-process it and return the total # of events read. /// </summary> static void UseFS(int pid) { int eventsRead = 0; const string fileName = "./temp.nettrace"; DiagnosticsClient client = new DiagnosticsClient(pid); EventPipeSession session = client.StartEventPipeSession(new EventPipeProvider("MySource", EventLevel.Verbose)); Console.WriteLine("session open"); using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) { Task copyTask = session.EventStream.CopyToAsync(fs); while (!copyTask.Wait(100)) { ; } } EventPipeEventSource epes = new EventPipeEventSource(fileName); epes.Dynamic.All += (TraceEvent data) => { eventsRead += 1; }; epes.Process(); Console.WriteLine("Used post processing."); Console.WriteLine("Read total: " + eventsRead.ToString()); Console.WriteLine("Dropped total: " + epes.EventsLost.ToString()); }
public static void PrintRuntimeGCEvents(int processId) { var providers = new List <EventPipeProvider>() { new EventPipeProvider("Microsoft-Windows-DotNETRuntime", EventLevel.Informational, (long)ClrTraceEventParser.Keywords.GC) }; var client = new DiagnosticsClient(processId); using (EventPipeSession session = client.StartEventPipeSession(providers, false)) { var source = new EventPipeEventSource(session.EventStream); source.Clr.All += (TraceEvent obj) => Console.WriteLine(obj.ToString()); try { source.Process(); } catch (Exception e) { Console.WriteLine("Error encountered while processing events"); Console.WriteLine(e.ToString()); } } }
public void Start(Process process) { _process = process; _counters = new SortedDictionary <string, string>(); _task = new Task(() => { var diagnosticsClient = new DiagnosticsClient(_process.Id); var providerList = new List <EventPipeProvider>() { new EventPipeProvider(name: "System.Runtime", keywords: long.MaxValue, eventLevel: EventLevel.Verbose, arguments: new Dictionary <string, string>() { { "EventCounterIntervalSec", "1" } }) }; _session = diagnosticsClient.StartEventPipeSession( providers: providerList, requestRundown: false); var source = new EventPipeEventSource(_session.EventStream); source.Dynamic.AddCallbackForProviderEvent("System.Runtime", "EventCounters", CounterEvent); source.Process(); }); _task.Start(); }
public bool Start(Action <TraceEvent> trigger, Func <TraceEvent, bool> filter = null) { if (IsStarted || Providers.Count == 0) { return(false); } this.Trigger = trigger ?? throw new ArgumentNullException(nameof(trigger)); this.Filter = filter; //foreach (var provider in Providers) //{ // var listener = new EventListener(); // EventListener.EnableEvents() //} Task.Run(() => { _session = _client.StartEventPipeSession(Providers, false); _source = new EventPipeEventSource(_session.EventStream); OnSubscribe(_source); _source.Dynamic.All += Dynamic_All; _source.Process(); }); IsStarted = true; return(true); }
public void Start() { Task.Run(() => { try { var client = new DiagnosticsClient(_pid); _session = client.StartEventPipeSession(_providers, false); using var source = new EventPipeEventSource(_session.EventStream); source.NeedLoadedDotNetRuntimes(); source.AddCallbackOnProcessStart(proc => { if (proc.ProcessID != _pid) { return; } proc.AddCallbackOnDotNetRuntimeLoad(runtime => { runtime.GCEnd += (process, gc) => { var key = $"gen-{gc.Generation}-gc-collection-count"; _collectedStats.TryGetValue(key, out var collected); _collectedStats[key] = ++collected; }; }); }); source.Dynamic.All += obj => { if (obj.EventName.Equals("EventCounters")) { var payload = (IDictionary <string, object>)obj.PayloadValue(0); var pairs = (IDictionary <string, object>)(payload["Payload"]); var name = string.Intern(pairs["Name"].ToString()); var counterType = pairs["CounterType"]; if (counterType.Equals("Sum")) { _collectedStats[name] = double.Parse(pairs["Increment"].ToString()); } if (counterType.Equals("Mean")) { _collectedStats[name] = double.Parse(pairs["Mean"].ToString()); } } }; source.Process(); } catch (ObjectDisposedException) { // ignore exception on shutdown } catch (Exception exception) { Log.Warning(exception, "Error encountered while processing events"); } }); }
public Task <Stream> ProcessEvents(int processId, TimeSpan duration, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); lock (_lock) { if (_disposed) { throw new ObjectDisposedException(nameof(DiagnosticsMonitor)); } if (_currentTask != null) { throw new InvalidOperationException("Only one stream processing is allowed"); } EventPipeSession session = null; var client = new DiagnosticsClient(processId); try { session = client.StartEventPipeSession(_sourceConfig.GetProviders(), _sourceConfig.RequestRundown, _sourceConfig.BufferSizeInMB); } catch (EndOfStreamException e) { throw new InvalidOperationException("End of stream", e); } catch (Exception ex) when(!(ex is OperationCanceledException)) { throw new InvalidOperationException("Failed to start the event pipe session", ex); } CancellationTokenSource linkedSource = CancellationTokenSource.CreateLinkedTokenSource(_stopProcessingSource.Token, cancellationToken); _currentTask = Task.Run(async() => { try { await Task.Delay(duration, linkedSource.Token); } finally { linkedSource.Dispose(); StopSession(session); } }); return(Task.FromResult(session.EventStream)); } }
public void Start() { using (var session = client.StartEventPipeSession(providers, false)) using (var source = new EventPipeEventSource(session.EventStream)) { _session = session; _source = source; _source.Clr.All += Trigger; try { _source?.Process(); } catch (DiagnosticsClientException) { } } }
public void Handle(HostSignal signal, DiagnoserActionParameters parameters) { if (signal != HostSignal.BeforeAnythingElse) { return; } var diagnosticsClient = new DiagnosticsClient(parameters.Process.Id); EventPipeSession session = diagnosticsClient.StartEventPipeSession(eventPipeProviders, true); var fileName = ArtifactFileNameHelper.GetTraceFilePath(parameters, DateTime.Now, "nettrace").EnsureFolderExists(); benchmarkToTraceFile[parameters.BenchmarkCase] = fileName; collectingTask = Task.Run(() => CopyEventStreamToFile(session, fileName, parameters.Config.GetCompositeLogger())); }
static void ThreadProc(Object arg) { Process eventWritingProc = (Process)arg; eventCounts[cur_core_count] = 0; DiagnosticsClient client = new DiagnosticsClient(eventWritingProc.Id); EventPipeSession session = client.StartEventPipeSession(new EventPipeProvider("MySource", EventLevel.Verbose, (long)-1, null)); EventPipeEventSource source = new EventPipeEventSource(session.EventStream); source.Dynamic.All += (TraceEvent data) => { if (data.EventName == "FireSmallEvent") { eventCounts[cur_core_count] += 1; } }; source.Process(); }
/// <summary> /// This uses EventPipeEventSource's Stream constructor to parse the events real-time. /// It then returns the number of events read. /// </summary> static void UseEPES(int pid) { int eventsRead = 0; DiagnosticsClient client = new DiagnosticsClient(pid); EventPipeSession session = client.StartEventPipeSession(new EventPipeProvider("MySource", EventLevel.Verbose)); Console.WriteLine("session open"); EventPipeEventSource epes = new EventPipeEventSource(session.EventStream); epes.Dynamic.All += (TraceEvent data) => { eventsRead += 1; }; epes.Process(); Console.WriteLine("Used realtime."); Console.WriteLine("Read total: " + eventsRead.ToString()); Console.WriteLine("Dropped total: " + epes.EventsLost.ToString()); }
public bool Stop() { if (!IsStarted) { return(false); } if (_source != null) { _source.Dispose(); _source = null; } if (_session != null) { _session.Dispose(); _session = null; } IsStarted = false; return(true); }
public bool Start() { if (IsStarted || Providers.Count == 0) { return(false); } Task.Run(() => { _session = _client.StartEventPipeSession(Providers, false); _source = new EventPipeEventSource(_session.EventStream); OnSubscribe(_source); _source.Dynamic.All += Dynamic_All; _source.Process(); }); IsStarted = true; return(true); }
/// <summary> /// This uses EventPipeEventSource's Stream constructor to parse the events real-time. /// It then returns the number of events read. /// </summary> private static Func <int, TestResult> UseEPES(bool rundown, int bufferSize, int slowReader) { return((int pid) => { int eventsRead = 0; var slowReadSw = new Stopwatch(); var totalTimeSw = new Stopwatch(); var interval = TimeSpan.FromSeconds(0.75); EventPipeSession session = GetSession(pid, rundown, bufferSize); Console.WriteLine("Session created."); EventPipeEventSource epes = new EventPipeEventSource(session.EventStream); epes.Dynamic.All += (TraceEvent data) => { eventsRead += 1; if (slowReader > 0) { if (slowReadSw.Elapsed > interval) { Thread.Sleep(slowReader); slowReadSw.Reset(); } } }; if (slowReader > 0) { slowReadSw.Start(); } totalTimeSw.Start(); epes.Process(); totalTimeSw.Stop(); if (slowReader > 0) { slowReadSw.Stop(); } Console.WriteLine("Read total: " + eventsRead.ToString()); Console.WriteLine("Dropped total: " + epes.EventsLost.ToString()); return new TestResult(eventsRead, epes.EventsLost, totalTimeSw.Elapsed); }); }
private bool StartSession() { int retries = 20; while (retries > 0 && _session == null) { try { _session = _client.StartEventPipeSession(Providers, false); } catch (ServerNotAvailableException) { } catch (Exception err) { Console.WriteLine(err.Message); } retries--; } return(_session != null); }
private static void StopSession(EventPipeSession session) { try { session.Stop(); } catch (EndOfStreamException) { // If the app we're monitoring exits abruptly, this may throw in which case we just swallow the exception and exit gracefully. } // We may time out if the process ended before we sent StopTracing command. We can just exit in that case. catch (TimeoutException) { } // On Unix platforms, we may actually get a PNSE since the pipe is gone with the process, and Runtime Client Library // does not know how to distinguish a situation where there is no pipe to begin with, or where the process has exited // before dotnet-counters and got rid of a pipe that once existed. // Since we are catching this in StopMonitor() we know that the pipe once existed (otherwise the exception would've // been thrown in StartMonitor directly) catch (PlatformNotSupportedException) { } }
private static void CopyEventStreamToFile(EventPipeSession session, string fileName, ILogger logger) { try { using (session) using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) { byte[] buffer = new byte[16 * 1024]; int bytesRead = 0; while ((bytesRead = session.EventStream.Read(buffer, 0, buffer.Length)) > 0) { fs.Write(buffer, 0, bytesRead); } fs.Flush(); } } catch (Exception ex) { logger.WriteLine(LogKind.Error, $"An exception occurred during reading trace stream: {ex}"); } }
public void Start(string providerName, EventLevel level, long keywords) { var provider = new EventPipeProvider(providerName, level, keywords); m_session = m_client.StartEventPipeSession(new List <EventPipeProvider>() { provider }); // Task that reads and does nothing Task streamTask = Task.Run(() => { var buffer = new byte[1000]; while (true) { try { m_session.EventStream.Read(buffer, 0, 1000); } catch (Exception e) { } } }); }
/// <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)); }
public static async Task <int> Collect(ICollection <Action <long> > sizeChangeCallbacks, CollectTrace.Tracing tracing, FileInfo output, TraceFileFormat format = TraceFileFormat.NetTrace) { var processId = Process.GetCurrentProcess().Id; var providerCollection = CreateProviderCollection(); var diagnosticsClient = new DiagnosticsClient(processId); 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); } var failed = false; var terminated = false; try { var collectingTask = new Task(() => { try { using (var fs = new FileStream(output.FullName, FileMode.Create, FileAccess.Write)) { 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); foreach (var sizeChangeCallback in sizeChangeCallbacks) { sizeChangeCallback(fs.Length); } } } } catch (Exception ex) { failed = true; Console.Error.WriteLine($"[ERROR] {ex.ToString()}"); } finally { terminated = true; } }); collectingTask.Start(); tracing.Wait(); session.Stop(); await collectingTask; //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); } }
private async Task StartTracing(Action <string> log) { var providers = new List <EventPipeProvider>() { // Runtime Metrics new EventPipeProvider( "System.Runtime", EventLevel.Informational, 0, new Dictionary <string, string>() { { "EventCounterIntervalSec", "1" } } ), // new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational), new EventPipeProvider("Microsoft-Windows-DotNETRuntime", EventLevel.Informational, 65536 | 32768),//ClrTraceEventParser.Keywords.Default) // Activity correlation new EventPipeProvider("System.Threading.Tasks.TplEventSource", keywords: 511, eventLevel: EventLevel.LogAlways), }; log($"Starting event pipe session for pid {Process.GetCurrentProcess().Id}"); EventPipeSession session = null; var client = new DiagnosticsClient(Process.GetCurrentProcess().Id); try { session = client.StartEventPipeSession(providers); } catch (EndOfStreamException) { } // If the process has already exited, a ServerNotAvailableException will be thrown. catch (ServerNotAvailableException) { } catch (Exception) { // We can't even start the session, wait until the process boots up again to start another metrics thread } void StopSession() { try { session.Stop(); } catch (EndOfStreamException) { // If the app we're monitoring exits abruptly, this may throw in which case we just swallow the exception and exit gracefully. } // We may time out if the process ended before we sent StopTracing command. We can just exit in that case. catch (TimeoutException) { } // On Unix platforms, we may actually get a PNSE since the pipe is gone with the process, and Runtime Client Library // does not know how to distinguish a situation where there is no pipe to begin with, or where the process has exited // before dotnet-counters and got rid of a pipe that once existed. // Since we are catching this in StopMonitor() we know that the pipe once existed (otherwise the exception would've // been thrown in StartMonitor directly) catch (PlatformNotSupportedException) { } // If the process has already exited, a ServerNotAvailableException will be thrown. // This can always race with tye shutting down and a process being restarted on exiting. catch (ServerNotAvailableException) { } } var _ = _cts.Token.Register(() => StopSession()); try { using var traceOutput = File.Create("trace.nettrace"); await session.EventStream.CopyToAsync(traceOutput); } catch (DiagnosticsClientException) { } catch (Exception) { // This fails if stop is called or if the process dies } finally { session?.Dispose(); } }
public static int RunTest() { bool success = true; int allTypesEventCount = 0; int arrayTypeEventCount = 0; int emptyEventCount = 0; int simpleEventCount = 0; try { List <EventPipeProvider> providers = new List <EventPipeProvider> { new EventPipeProvider("MySuperAwesomeEventPipeProvider", EventLevel.Verbose) }; using (EventPipeSession session = ProfilerControlHelpers.AttachEventPipeSessionToSelf(providers)) { TriggerMethod(); ManualResetEvent allEventsReceivedEvent = new ManualResetEvent(false); var source = new EventPipeEventSource(session.EventStream); source.Dynamic.All += (TraceEvent traceEvent) => { if (traceEvent.ProviderName != "MySuperAwesomeEventPipeProvider") { return; } if (traceEvent.EventName == "AllTypesEvent") { success &= ValidateAllTypesEvent(traceEvent); ++allTypesEventCount; } else if (traceEvent.EventName == "EmptyEvent") { success &= ValidateEmptyEvent(traceEvent); ++emptyEventCount; } else if (traceEvent.EventName == "SimpleEvent") { success &= ValidateSimpleEvent(traceEvent, simpleEventCount); ++simpleEventCount; } else if (traceEvent.EventName == "ArrayTypeEvent") { success &= ValidateArrayTypeEvent(traceEvent); ++arrayTypeEventCount; } if (AllEventsReceived(allTypesEventCount, arrayTypeEventCount, emptyEventCount, simpleEventCount)) { allEventsReceivedEvent.Set(); } }; Thread processingThread = new Thread(new ThreadStart(() => { source.Process(); })); processingThread.Start(); // The events are fired in the JITCompilationStarted callback for TriggerMethod, // so by the time we are here, all events should be fired. session.Stop(); allEventsReceivedEvent.WaitOne(TimeSpan.FromSeconds(90)); processingThread.Join(); } } catch (Exception e) { Console.WriteLine($"Exception {e.Message} when trying to attach"); success = false; } if (success && AllEventsReceived(allTypesEventCount, arrayTypeEventCount, emptyEventCount, simpleEventCount)) { return(100); } else { Console.WriteLine("Test validation failed (EventPipeClient.exe)"); Console.WriteLine($" success={success}"); Console.WriteLine($" allTypesEventCount={allTypesEventCount} "); Console.WriteLine($" arrayTypeEventCount={arrayTypeEventCount} "); Console.WriteLine($" emptyEventCount={emptyEventCount}"); Console.WriteLine($" simpleEventCount={simpleEventCount}"); return(-1); } }
/// <summary> /// Reports a stack trace /// </summary> /// <param name="ct">The cancellation token</param> /// <param name="console"></param> /// <param name="processId">The process to report the stack from.</param> /// <param name="name">The name of process to report the stack from.</param> /// <param name="output">The output path for the collected trace data.</param> /// <param name="duration">The duration of to trace the target for. </param> /// <returns></returns> private static async Task <int> Report(CancellationToken ct, IConsole console, int processId, string name, TimeSpan duration) { string tempNetTraceFilename = Path.GetRandomFileName() + ".nettrace"; string tempEtlxFilename = ""; try { // Either processName or processId has to be specified. if (!string.IsNullOrEmpty(name)) { if (processId != 0) { Console.WriteLine("Can only specify either --name or --process-id option."); return(-1); } processId = CommandUtils.FindProcessIdWithName(name); if (processId < 0) { return(-1); } } if (processId < 0) { console.Error.WriteLine("Process ID should not be negative."); return(-1); } else if (processId == 0) { console.Error.WriteLine("--process-id is required"); return(-1); } var client = new DiagnosticsClient(processId); var providers = new List <EventPipeProvider>() { new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational) }; // collect a *short* trace with stack samples // the hidden '--duration' flag can increase the time of this trace in case 10ms // is too short in a given environment, e.g., resource constrained systems // N.B. - This trace INCLUDES rundown. For sufficiently large applications, it may take non-trivial time to collect // the symbol data in rundown. using (EventPipeSession session = client.StartEventPipeSession(providers)) using (FileStream fs = File.OpenWrite(tempNetTraceFilename)) { Task copyTask = session.EventStream.CopyToAsync(fs); await Task.Delay(duration); session.Stop(); // check if rundown is taking more than 5 seconds and add comment to report Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(5)); Task completedTask = await Task.WhenAny(copyTask, timeoutTask); if (completedTask == timeoutTask) { console.Out.WriteLine($"# Sufficiently large applications can cause this command to take non-trivial amounts of time"); } await copyTask; } // using the generated trace file, symbolocate and compute stacks. tempEtlxFilename = TraceLog.CreateFromEventPipeDataFile(tempNetTraceFilename); using (var symbolReader = new SymbolReader(System.IO.TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath }) using (var eventLog = new TraceLog(tempEtlxFilename)) { var stackSource = new MutableTraceEventStackSource(eventLog) { OnlyManagedCodeStacks = true }; var computer = new SampleProfilerThreadTimeComputer(eventLog, symbolReader); computer.GenerateThreadTimeStacks(stackSource); var samplesForThread = new Dictionary <int, List <StackSourceSample> >(); stackSource.ForEach((sample) => { var stackIndex = sample.StackIndex; while (!stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false).StartsWith("Thread (")) { stackIndex = stackSource.GetCallerIndex(stackIndex); } // long form for: int.Parse(threadFrame["Thread (".Length..^1)]) // Thread id is in the frame name as "Thread (<ID>)" string template = "Thread ("; string threadFrame = stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false); int threadId = int.Parse(threadFrame.Substring(template.Length, threadFrame.Length - (template.Length + 1))); if (samplesForThread.TryGetValue(threadId, out var samples)) { samples.Add(sample); } else { samplesForThread[threadId] = new List <StackSourceSample>() { sample }; } }); // For every thread recorded in our trace, print the first stack foreach (var(threadId, samples) in samplesForThread) { #if DEBUG console.Out.WriteLine($"Found {samples.Count} stacks for thread 0x{threadId:X}"); #endif PrintStack(console, threadId, samples[0], stackSource); } } } catch (Exception ex) { Console.Error.WriteLine($"[ERROR] {ex.ToString()}"); return(-1); } finally { if (File.Exists(tempNetTraceFilename)) { File.Delete(tempNetTraceFilename); } if (File.Exists(tempEtlxFilename)) { File.Delete(tempEtlxFilename); } } return(0); }
private async Task <int> Start() { string providerString = BuildProviderString(); if (providerString.Length == 0) { return(1); } _renderer.Initialize(); Task monitorTask = new Task(() => { try { _session = _diagnosticsClient.StartEventPipeSession(Trace.Extensions.ToProviders(providerString), false, 10); if (shouldResumeRuntime) { _diagnosticsClient.ResumeRuntime(); } var source = new EventPipeEventSource(_session.EventStream); source.Dynamic.All += DynamicAllMonitor; _renderer.EventPipeSourceConnected(); source.Process(); } catch (DiagnosticsClientException ex) { Console.WriteLine($"Failed to start the counter session: {ex.ToString()}"); } catch (Exception ex) { Debug.WriteLine($"[ERROR] {ex.ToString()}"); } finally { shouldExit.Set(); } }); monitorTask.Start(); while (!shouldExit.WaitOne(250)) { while (true) { if (shouldExit.WaitOne(250)) { StopMonitor(); return(0); } if (Console.KeyAvailable) { break; } } ConsoleKey cmd = Console.ReadKey(true).Key; if (cmd == ConsoleKey.Q) { StopMonitor(); break; } else if (cmd == ConsoleKey.P) { pauseCmdSet = true; } else if (cmd == ConsoleKey.R) { pauseCmdSet = false; } } return(await Task.FromResult(0)); }