private static void ConvertToSpeedscope(string fileToConvert, string outputFilename) { var etlxFilePath = TraceLog.CreateFromEventPipeDataFile(fileToConvert); using (var symbolReader = new SymbolReader(System.IO.TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath }) using (var eventLog = new TraceLog(etlxFilePath)) { var stackSource = new MutableTraceEventStackSource(eventLog) { OnlyManagedCodeStacks = true // EventPipe currently only has managed code stacks. }; var computer = new SampleProfilerThreadTimeComputer(eventLog, symbolReader); computer.GenerateThreadTimeStacks(stackSource); SpeedScopeStackSourceWriter.WriteStackViewAsJson(stackSource, outputFilename); } if (File.Exists(etlxFilePath)) { File.Delete(etlxFilePath); } }
private static void ConvertToSpeedscope(string fileToConvert) { var symbolReader = new SymbolReader(System.IO.TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath }; var etlxFilePath = TraceLog.CreateFromEventPipeDataFile(fileToConvert); var eventLog = new TraceLog(etlxFilePath); try { var stackSource = new MutableTraceEventStackSource(eventLog) { OnlyManagedCodeStacks = true // EventPipe currently only has managed code stacks. }; var computer = new SampleProfilerThreadTimeComputer(eventLog, symbolReader); computer.GenerateThreadTimeStacks(stackSource); var speedScopeFilePath = Path.ChangeExtension(fileToConvert, "speedscope.json"); SpeedScopeStackSourceWriter.WriteStackViewAsJson(stackSource, speedScopeFilePath); } finally { eventLog.Dispose(); if (File.Exists(etlxFilePath)) { File.Delete(etlxFilePath); } } }
// Method copied from https://github.com/dotnet/diagnostics/blob/2c23d3265dd8f642a8d6cf4bb8a135a5ff8b00c2/src/Tools/dotnet-trace/TraceFileFormatConverter.cs#L64 private static void ConvertToSpeedscope(string fileToConvert, string outputFilename, bool continueOnError = false) { var etlxFilePath = TraceLog.CreateFromEventPipeDataFile(fileToConvert, null, new TraceLogOptions() { ContinueOnError = continueOnError }); using (var symbolReader = new SymbolReader(System.IO.TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath }) using (var eventLog = new TraceLog(etlxFilePath)) { var stackSource = new MutableTraceEventStackSource(eventLog) { OnlyManagedCodeStacks = true // EventPipe currently only has managed code stacks. }; var computer = new SampleProfilerThreadTimeComputer(eventLog, symbolReader) { IncludeEventSourceEvents = false // SpeedScope handles only CPU samples, events are not supported }; computer.GenerateThreadTimeStacks(stackSource); SpeedScopeStackSourceWriter.WriteStackViewAsJson(stackSource, outputFilename); } if (File.Exists(etlxFilePath)) { File.Delete(etlxFilePath); } }
private static void Convert(TraceFileFormat format, string fileToConvert, string outputFilename, bool continueOnError = false) { var etlxFilePath = TraceLog.CreateFromEventPipeDataFile(fileToConvert, null, new TraceLogOptions() { ContinueOnError = continueOnError }); using (var symbolReader = new SymbolReader(TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath }) using (var eventLog = new TraceLog(etlxFilePath)) { var stackSource = new MutableTraceEventStackSource(eventLog) { OnlyManagedCodeStacks = true // EventPipe currently only has managed code stacks. }; var computer = new SampleProfilerThreadTimeComputer(eventLog, symbolReader) { IncludeEventSourceEvents = false // SpeedScope handles only CPU samples, events are not supported }; computer.GenerateThreadTimeStacks(stackSource); switch (format) { case TraceFileFormat.Speedscope: SpeedScopeStackSourceWriter.WriteStackViewAsJson(stackSource, outputFilename); break; case TraceFileFormat.Chromium: ChromiumStackSourceWriter.WriteStackViewAsJson(stackSource, outputFilename, compress: false); break; default: // we should never get here throw new ArgumentException($"Invalid TraceFileFormat \"{format}\""); } } if (File.Exists(etlxFilePath)) { File.Delete(etlxFilePath); } }
/// <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 static async Task <int> TopNReport(CancellationToken ct, IConsole console, string traceFile, int number, bool inclusive, bool verbose) { try { string tempEtlxFilename = TraceLog.CreateFromEventPipeDataFile(traceFile); int count = 0; int index = 0; List <CallTreeNodeBase> nodesToReport = new List <CallTreeNodeBase>(); 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); FilterParams filterParams = new FilterParams() { FoldRegExs = "CPU_TIME;UNMANAGED_CODE_TIME;{Thread (}", }; FilterStackSource filterStack = new FilterStackSource(filterParams, stackSource, ScalingPolicyKind.ScaleToData); CallTree callTree = new(ScalingPolicyKind.ScaleToData); callTree.StackSource = filterStack; List <CallTreeNodeBase> callTreeNodes = null; if (!inclusive) { callTreeNodes = callTree.ByIDSortedExclusiveMetric(); } else { callTreeNodes = callTree.ByIDSortedInclusiveMetric(); } int totalElements = callTreeNodes.Count; while (count < number && index < totalElements) { CallTreeNodeBase node = callTreeNodes[index]; index++; if (!unwantedMethodNames.Any(node.Name.Contains)) { nodesToReport.Add(node); count++; } } PrintReportHelper.TopNWriteToStdOut(nodesToReport, inclusive, verbose); } return(await Task.FromResult(0)); } catch (Exception ex) { Console.Error.WriteLine($"[ERROR] {ex.ToString()}"); } return(await Task.FromResult(0)); }
public void CanConvertProvidedTraceFiles(string zippedTraceFileName) { var debugListenersCopy = new TraceListener[Debug.Listeners.Count]; Debug.Listeners.CopyTo(debugListenersCopy, index: 0); Debug.Listeners.Clear(); string fileToUnzip = Path.Combine("inputs", "speedscope", zippedTraceFileName); string unzippedFile = Path.ChangeExtension(fileToUnzip, string.Empty); if (File.Exists(unzippedFile)) { File.Delete(unzippedFile); } ZipFile.ExtractToDirectory(fileToUnzip, Path.GetDirectoryName(fileToUnzip)); var etlxFilePath = TraceLog.CreateFromEventPipeDataFile(unzippedFile, null, new TraceLogOptions() { ContinueOnError = true }); try { using (var symbolReader = new SymbolReader(TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath }) using (var eventLog = new TraceLog(etlxFilePath)) { var stackSource = new MutableTraceEventStackSource(eventLog) { OnlyManagedCodeStacks = true // EventPipe currently only has managed code stacks. }; var computer = new SampleProfilerThreadTimeComputer(eventLog, symbolReader) { IncludeEventSourceEvents = false // SpeedScope handles only CPU samples, events are not supported }; computer.GenerateThreadTimeStacks(stackSource); var samplesPerThread = GetSortedSamplesPerThread(stackSource); var exportedFrameNameToExportedFrameId = new Dictionary <string, int>(); var exportedFrameIdToFrameTuple = new Dictionary <int, FrameInfo>(); var profileEventsPerThread = new Dictionary <string, IReadOnlyList <ProfileEvent> >(); foreach (var pair in samplesPerThread) { var sortedProfileEvents = GetProfileEvents(stackSource, pair.Value, exportedFrameNameToExportedFrameId, exportedFrameIdToFrameTuple); Assert.True(Validate(sortedProfileEvents), "The output should be always valid"); profileEventsPerThread.Add(pair.Key.Name, sortedProfileEvents); } ; } } finally { if (File.Exists(etlxFilePath)) { File.Delete(etlxFilePath); } if (File.Exists(unzippedFile)) { File.Delete(unzippedFile); } if (debugListenersCopy.Length > 0) { Debug.Listeners.AddRange(debugListenersCopy); } } }
static async Task <int> Main(string[] args) { if (args.Length == 0 || args.Length > 1 || (args.Length == 1 && _helpFlags.Contains(args[0]))) { PrintUsage(); return(-1); } var pid = int.Parse(args[0]); var tempNetTraceFilename = Path.GetRandomFileName() + ".nettrace"; var tempEtlxFilename = ""; try { var client = new DiagnosticsClient(pid); var providers = new List <EventPipeProvider>() { new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational) }; using (EventPipeSession session = client.StartEventPipeSession(providers)) using (FileStream fs = File.OpenWrite(tempNetTraceFilename)) { Task copyTask = session.EventStream.CopyToAsync(fs); await Task.Delay(10); session.Stop(); await copyTask; } 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 <string, List <StackSourceSample> >(); stackSource.ForEach((sample) => { var stackIndex = sample.StackIndex; while (!stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false).StartsWith("Thread (")) { stackIndex = stackSource.GetCallerIndex(stackIndex); } var threadName = stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false); if (samplesForThread.TryGetValue(threadName, out var samples)) { samples.Add(sample); } else { samplesForThread[threadName] = new List <StackSourceSample>() { sample }; } }); foreach (var(threadName, samples) in samplesForThread) { #if DEBUG Console.WriteLine($"Found {samples.Count} stacks for thread {threadName}"); #endif PrintStack(threadName, samples[0], stackSource); } } return(0); } catch (System.Exception e) { Console.WriteLine(e); return(-1); } finally { if (File.Exists(tempNetTraceFilename)) { File.Delete(tempNetTraceFilename); } if (File.Exists(tempEtlxFilename)) { File.Delete(tempEtlxFilename); } } }
/// <summary> /// Reads trace and prints managed stacks /// </summary> /// <param name="console"></param> /// <param name="traceFile"></param> /// <returns>Returns 0 for success and 1 for failure</returns> /// <remarks>This code is adopted from josalem code https://github.com/josalem/DotStack </remarks> public static int Analyze(IConsole console, FileInfo traceFile) { if (string.IsNullOrEmpty(traceFile.FullName)) { Console.ForegroundColor = ConsoleColor.Red; console.Error.WriteLine("-f [process-id] Process ID is required."); Console.ResetColor(); return(1); } Console.WriteLine("[Trace analysis started...]\n"); // Both the namespaces have TraceLog, here we use Microsoft.Diagnostics.Tracing.Etlx.TraceLog; // The following line creates a etlx file and then does analysis using that. string tempEtlxFilename = TraceLog.CreateFromEventPipeDataFile(traceFile.FullName); 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 <string, List <StackSourceSample> >(); stackSource.ForEach((sample) => { var stackIndex = sample.StackIndex; while (!stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false).StartsWith("Thread (")) { stackIndex = stackSource.GetCallerIndex(stackIndex); } var threadName = stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false); if (samplesForThread.TryGetValue(threadName, out var samples)) { samples.Add(sample); } else { samplesForThread[threadName] = new List <StackSourceSample>() { sample }; } }); foreach (var(threadName, samples) in samplesForThread) { PrintStack(threadName, samples[0], stackSource); } } Console.WriteLine("\n[Trace analysis completed...]"); return(0); }