private static void PrintStack(IConsole console, int threadId, StackSourceSample stackSourceSample, StackSource stackSource) { console.Out.WriteLine($"Thread (0x{threadId:X}):"); var stackIndex = stackSourceSample.StackIndex; while (!stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), verboseName: false).StartsWith("Thread (")) { console.Out.WriteLine($" {stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), verboseName: false)}" .Replace("UNMANAGED_CODE_TIME", "[Native Frames]")); stackIndex = stackSource.GetCallerIndex(stackIndex); } console.Out.WriteLine(); }
private static void PrintStack(string threadName, StackSourceSample stackSourceSample, StackSource stackSource) { Console.WriteLine($"Stack for {threadName}:"); var stackIndex = stackSourceSample.StackIndex; while (!stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), verboseName: false).StartsWith("Thread (")) { Console.WriteLine($" {stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), verboseName: false)}"); stackIndex = stackSource.GetCallerIndex(stackIndex); } Console.WriteLine(); }
/// <summary> /// we want to identify the thread for every sample to prevent from /// overlaping of samples for the concurrent code so we group the samples by Threads /// this method also sorts the samples by relative time (ascending) /// </summary> internal static IReadOnlyDictionary <ThreadInfo, List <Sample> > GetSortedSamplesPerThread(StackSource stackSource) { var samplesPerThread = new Dictionary <ThreadInfo, List <Sample> >(); stackSource.ForEach(sample => { var stackIndex = sample.StackIndex; while (stackIndex != StackSourceCallStackIndex.Invalid) { var frameName = stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false); // we walk the stack up until we find the Thread name if (!frameName.StartsWith("Thread (")) { stackIndex = stackSource.GetCallerIndex(stackIndex); continue; } // we assume that the next caller is always process var processStackIndex = stackSource.GetCallerIndex(stackIndex); var processFrameName = processStackIndex == StackSourceCallStackIndex.Invalid ? "Unknown" : stackSource.GetFrameName(stackSource.GetFrameIndex(processStackIndex), false); var threadInfo = new ThreadInfo(frameName, processFrameName); if (!samplesPerThread.TryGetValue(threadInfo, out var samples)) { samplesPerThread[threadInfo] = samples = new List <Sample>(); } samples.Add(new Sample(sample.StackIndex, sample.TimeRelativeMSec, sample.Metric, -1, -1)); return; } // Sample with no Thread assigned - it's most probably a "Process" sample, we just ignore it }); foreach (var samples in samplesPerThread.Values) { // all samples in the StackSource should be sorted, but we want to ensure it samples.Sort(CompareSamplesByTime); } return(samplesPerThread); }
public static void WriteStacks(StackSource source, XmlWriter writer) { writer.WriteStartElement("StackSource"); writer.WriteStartElement("Frames"); writer.WriteAttributeString("Count", source.CallFrameIndexLimit.ToString()); for (int i = 0; i < source.CallFrameIndexLimit; i++) { writer.WriteStartElement("Frame"); writer.WriteAttributeString("ID", i.ToString()); var frameName = source.GetFrameName((StackSourceFrameIndex)i, true); writer.WriteString(frameName); writer.WriteEndElement(); // Frame } writer.WriteEndElement(); // Frames writer.WriteStartElement("Stacks"); writer.WriteAttributeString("Count", source.CallStackIndexLimit.ToString()); for (int i = 0; i < source.CallStackIndexLimit; i++) { writer.WriteStartElement("Stack"); writer.WriteAttributeString("ID", i.ToString()); var FrameID = source.GetFrameIndex((StackSourceCallStackIndex)i); var callerID = source.GetCallerIndex((StackSourceCallStackIndex)i); writer.WriteAttributeString("CallerID", ((int)callerID).ToString()); writer.WriteAttributeString("FrameID", ((int)FrameID).ToString()); writer.WriteEndElement(); // Stack } writer.WriteEndElement(); // Stacks writer.WriteStartElement("Samples"); writer.WriteAttributeString("Count", source.SampleIndexLimit.ToString()); // We use the invariant culture, otherwise if we encode in France and decode // in English we get parse errors (this happened!); var invariantCulture = CultureInfo.InvariantCulture; source.ForEach(delegate(StackSourceSample sample) { // <Sample ID="1" Time="3432.23" StackID="2" Metric="1" EventKind="CPUSample" /> writer.WriteStartElement("Sample"); writer.WriteAttributeString("ID", ((int)sample.SampleIndex).ToString()); writer.WriteAttributeString("Time", sample.TimeRelativeMSec.ToString("f3", invariantCulture)); writer.WriteAttributeString("StackID", ((int)sample.StackIndex).ToString()); if (sample.Metric != 1) { var asInt = (int)sample.Metric; if (sample.Metric == asInt) { writer.WriteAttributeString("Metric", asInt.ToString()); } else { writer.WriteAttributeString("Metric", sample.Metric.ToString("f3", invariantCulture)); } } writer.WriteEndElement(); }); writer.WriteEndElement(); // Samples writer.WriteEndElement(); // StackSource }
/// <summary> /// all the samples that we have are leafs (last sample in the call stack) /// this method expands those samples to full information /// it walks the stack up to the begining and adds a sample for every method on the stack /// it's required to build full information /// </summary> internal static IReadOnlyDictionary <int, List <Sample> > WalkTheStackAndExpandSamples(StackSource stackSource, IEnumerable <Sample> leafs, Dictionary <string, int> exportedFrameNameToExportedFrameId) { var frameIdToSamples = new Dictionary <int, List <Sample> >(); // we use stack here bacause we want a certain order: from the root to the leaf var stackIndexesToHandle = new Stack <StackSourceCallStackIndex>(); foreach (var leafSample in leafs) { // walk the stack first var stackIndex = leafSample.StackIndex; while (stackIndex != StackSourceCallStackIndex.Invalid) { stackIndexesToHandle.Push(stackIndex); stackIndex = stackSource.GetCallerIndex(stackIndex); } // add sample for every method on the stack int depth = -1; int callerFrameId = -1; while (stackIndexesToHandle.Count > 0) { stackIndex = stackIndexesToHandle.Pop(); depth++; var frameIndex = stackSource.GetFrameIndex(stackIndex); if (frameIndex == StackSourceFrameIndex.Broken || frameIndex == StackSourceFrameIndex.Invalid) { continue; } var frameName = stackSource.GetFrameName(frameIndex, false); if (string.IsNullOrEmpty(frameName)) { continue; } if (!exportedFrameNameToExportedFrameId.TryGetValue(frameName, out int exportedFrameId)) { exportedFrameNameToExportedFrameId.Add(frameName, exportedFrameId = exportedFrameNameToExportedFrameId.Count); } if (!frameIdToSamples.TryGetValue(exportedFrameId, out var samples)) { frameIdToSamples.Add(exportedFrameId, samples = new List <Sample>()); } // the time and metric are the same as for the leaf sample // the difference is stack index (not really used from here), caller frame id and depth (used for sorting the exported data) samples.Add(new Sample(stackIndex, callerFrameId, leafSample.RelativeTime, leafSample.Metric, depth)); callerFrameId = exportedFrameId; } } return(frameIdToSamples); }
/// <summary> /// we want to identify the thread for every sample to prevent from /// overlaping of samples for the concurrent code so we group the samples by Threads /// this method also sorts the samples by relative time (ascending) /// </summary> internal static IReadOnlyDictionary <string, List <Sample> > GetSortedSamplesPerThread(StackSource stackSource) { var samplesPerThread = new Dictionary <string, List <Sample> >(); stackSource.ForEach(sample => { var stackIndex = sample.StackIndex; while (stackIndex != StackSourceCallStackIndex.Invalid) { var frameName = stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false); // we walk the stack up until we find the Thread name if (!frameName.StartsWith("Thread (")) { stackIndex = stackSource.GetCallerIndex(stackIndex); continue; } if (!samplesPerThread.TryGetValue(frameName, out var samples)) { samplesPerThread[frameName] = samples = new List <Sample>(); } samples.Add(new Sample(sample.StackIndex, -1, sample.TimeRelativeMSec, sample.Metric, -1)); return; } throw new InvalidOperationException("Sample with no Thread assigned!"); }); foreach (var samples in samplesPerThread.Values) { // all samples in the StackSource should be sorted, but we want to ensure it samples.Sort((x, y) => x.RelativeTime.CompareTo(y.RelativeTime)); } return(samplesPerThread); }
public static void WriteStacks(StackSource source, XmlWriter writer) { writer.WriteStartElement("StackSource"); writer.WriteStartElement("Frames"); writer.WriteAttributeString("Count", source.CallFrameIndexLimit.ToString()); for (int i = 0; i < source.CallFrameIndexLimit; i++) { writer.WriteStartElement("Frame"); writer.WriteAttributeString("ID", i.ToString()); var frameName = source.GetFrameName((StackSourceFrameIndex)i, true); // Check for the optimization tier. The frame name would contain the optimization tier in the form: // Module![OptimizationTier]Symbol // Extract the optimization tier into an attribute and convert the frame name to this form for storage: // Module!Symbol if (frameName != null && frameName.Length >= 4) { int openBracketIndex = frameName.IndexOf("![") + 1; if (openBracketIndex > 0) { int closeBracketIndex = frameName.IndexOf(']', openBracketIndex + 1); if (closeBracketIndex - openBracketIndex > 1) { var optimizationTierStr = frameName.Substring(openBracketIndex + 1, closeBracketIndex - openBracketIndex - 1); if (Enum.TryParse <OptimizationTier>(optimizationTierStr, out var optimizationTier)) { if (optimizationTier != OptimizationTier.Unknown) { writer.WriteAttributeString("OptimizationTier", optimizationTierStr); } frameName = frameName.Substring(0, openBracketIndex) + frameName.Substring(closeBracketIndex + 1); } } } } writer.WriteString(frameName); writer.WriteEndElement(); // Frame } writer.WriteEndElement(); // Frames writer.WriteStartElement("Stacks"); writer.WriteAttributeString("Count", source.CallStackIndexLimit.ToString()); for (int i = 0; i < source.CallStackIndexLimit; i++) { writer.WriteStartElement("Stack"); writer.WriteAttributeString("ID", i.ToString()); var FrameID = source.GetFrameIndex((StackSourceCallStackIndex)i); var callerID = source.GetCallerIndex((StackSourceCallStackIndex)i); writer.WriteAttributeString("CallerID", ((int)callerID).ToString()); writer.WriteAttributeString("FrameID", ((int)FrameID).ToString()); writer.WriteEndElement(); // Stack } writer.WriteEndElement(); // Stacks writer.WriteStartElement("Samples"); writer.WriteAttributeString("Count", source.SampleIndexLimit.ToString()); // We use the invariant culture, otherwise if we encode in France and decode // in English we get parse errors (this happened!); var invariantCulture = CultureInfo.InvariantCulture; source.ForEach(delegate(StackSourceSample sample) { // <Sample ID="1" Time="3432.23" StackID="2" Metric="1" EventKind="CPUSample" /> writer.WriteStartElement("Sample"); writer.WriteAttributeString("ID", ((int)sample.SampleIndex).ToString()); writer.WriteAttributeString("Time", sample.TimeRelativeMSec.ToString("f3", invariantCulture)); writer.WriteAttributeString("StackID", ((int)sample.StackIndex).ToString()); if (sample.Metric != 1) { var asInt = (int)sample.Metric; if (sample.Metric == asInt) { writer.WriteAttributeString("Metric", asInt.ToString()); } else { writer.WriteAttributeString("Metric", sample.Metric.ToString("f3", invariantCulture)); } } writer.WriteEndElement(); }); writer.WriteEndElement(); // Samples writer.WriteEndElement(); // StackSource }
/// <summary> /// all the samples that we have are leafs (last sample in the call stack) /// this method walks the stack up to the begining and merges the samples and outputs them in proper order /// </summary> internal static IReadOnlyList <ProfileEvent> GetProfileEvents(StackSource stackSource, IReadOnlyList <Sample> leafs, Dictionary <string, int> exportedFrameNameToExportedFrameId, Dictionary <int, FrameInfo> exportedFrameIdToExportedNameAndCallerId) { var results = new List <ProfileEvent>(leafs.Count * 20); var previousSamples = new List <Sample>(30); var currentSamples = new List <Sample>(30); // we use stack here bacause we want a certain order: from the root to the leaf var stackIndexesToHandle = new Stack <StackSourceCallStackIndex>(); foreach (var leafSample in leafs) { // walk the stack first var stackIndex = leafSample.StackIndex; while (stackIndex != StackSourceCallStackIndex.Invalid) { stackIndexesToHandle.Push(stackIndex); stackIndex = stackSource.GetCallerIndex(stackIndex); } // add sample for every method on the stack int depth = -1; int callerFrameId = -1; while (stackIndexesToHandle.Count > 0) { stackIndex = stackIndexesToHandle.Pop(); depth++; var frameIndex = stackSource.GetFrameIndex(stackIndex); if (frameIndex == StackSourceFrameIndex.Broken || frameIndex == StackSourceFrameIndex.Invalid) { continue; } var frameName = stackSource.GetFrameName(frameIndex, false); if (string.IsNullOrEmpty(frameName)) { continue; } if (!exportedFrameNameToExportedFrameId.TryGetValue(frameName, out int exportedFrameId)) { exportedFrameNameToExportedFrameId.Add(frameName, exportedFrameId = exportedFrameNameToExportedFrameId.Count); } // the time and metric are the same as for the leaf sample currentSamples.Add(new Sample(stackIndex, leafSample.RelativeTime, leafSample.Metric, depth, exportedFrameId)); if (!exportedFrameIdToExportedNameAndCallerId.ContainsKey(exportedFrameId)) { // in the future we could identify the categories in a more advance way // and split JIT, GC, Runtime, Libraries and ASP.NET Code into separate categories int index = frameName.IndexOf('!'); string category = index > 0 ? frameName.Substring(0, index) : string.Empty; string shortName = index > 0 ? frameName.Substring(index + 1) : frameName; exportedFrameIdToExportedNameAndCallerId.Add(exportedFrameId, new FrameInfo(callerFrameId, shortName, category)); } callerFrameId = exportedFrameId; } HandleSamples(previousSamples, currentSamples, results); } // close the remaining samples double lastKnownTimestamp = results.Count > 0 ? results[results.Count - 1].RelativeTime : 0.0; for (int i = previousSamples.Count - 1; i >= 0; i--) { var sample = previousSamples[i]; lastKnownTimestamp = Math.Max(lastKnownTimestamp, sample.RelativeTime + sample.Metric); results.Add(new ProfileEvent(ProfileEventType.Close, sample.FrameId, lastKnownTimestamp, sample.Depth)); } return(results); }
private static bool BuildInternalTempRepresentation(StackSource stackSource, string fileToWrite, string rootFunction) { StringBuilder flameChartStringBuilder = new StringBuilder(); bool enableRootingOnFunction = !string.IsNullOrWhiteSpace(rootFunction); // Write out the flame chart format, one line per stack // eg: corerun;foo;bar;baz 1 stackSource.ForEach(sample => { Stack <StackSourceCallStackIndex> callStackIndices = new Stack <StackSourceCallStackIndex>(); callStackIndices.Push(sample.StackIndex); StackSourceCallStackIndex callerIdx = stackSource.GetCallerIndex(sample.StackIndex); while (callerIdx != StackSourceCallStackIndex.Invalid) { callStackIndices.Push(callerIdx); callerIdx = stackSource.GetCallerIndex(callerIdx); } bool firstOne = true; bool foundRootFunction = false; while (callStackIndices.Count > 0) { var currFrame = callStackIndices.Pop(); var frameIdx = stackSource.GetFrameIndex(currFrame); var frameName = stackSource.GetFrameName(frameIdx, false); // If we're rooting on a function, skip the frames above it if (enableRootingOnFunction && !foundRootFunction) { if (frameName.Contains(rootFunction)) { foundRootFunction = true; } else { continue; } } if (!firstOne) { flameChartStringBuilder.Append(";"); } flameChartStringBuilder.Append(frameName); firstOne = false; } flameChartStringBuilder.Append(" 1"); flameChartStringBuilder.AppendLine(); }); using (TextWriter writer = File.CreateText(fileToWrite)) { try { writer.Write(flameChartStringBuilder.ToString()); } catch (IOException) { return(false); } } return(true); }
/// <summary> /// all the samples that we have are leafs (last sample in the call stack) /// this method expands those samples to full information /// it walks the stack up to the begining and adds a sample for every method on the stack /// it's required to build full information /// </summary> internal static IReadOnlyDictionary <int, List <Sample> > WalkTheStackAndExpandSamples(StackSource stackSource, IEnumerable <Sample> leafs, Dictionary <string, int> exportedFrameNameToExportedFrameId, Dictionary <int, FrameInfo> exportedFrameIdToExportedNameAndCallerId) { var frameIdToSamples = new Dictionary <int, List <Sample> >(); // we use stack here bacause we want a certain order: from the root to the leaf var stackIndexesToHandle = new Stack <StackSourceCallStackIndex>(); foreach (var leafSample in leafs) { // walk the stack first var stackIndex = leafSample.StackIndex; while (stackIndex != StackSourceCallStackIndex.Invalid) { stackIndexesToHandle.Push(stackIndex); stackIndex = stackSource.GetCallerIndex(stackIndex); } // add sample for every method on the stack int depth = -1; int callerFrameId = -1; var callerStackIndex = StackSourceCallStackIndex.Invalid; while (stackIndexesToHandle.Count > 0) { stackIndex = stackIndexesToHandle.Pop(); depth++; var frameIndex = stackSource.GetFrameIndex(stackIndex); if (frameIndex == StackSourceFrameIndex.Broken || frameIndex == StackSourceFrameIndex.Invalid) { continue; } var frameName = stackSource.GetFrameName(frameIndex, false); if (string.IsNullOrEmpty(frameName)) { continue; } if (!exportedFrameNameToExportedFrameId.TryGetValue(frameName, out int exportedFrameId)) { exportedFrameNameToExportedFrameId.Add(frameName, exportedFrameId = exportedFrameNameToExportedFrameId.Count); } if (!frameIdToSamples.TryGetValue(exportedFrameId, out var samples)) { frameIdToSamples.Add(exportedFrameId, samples = new List <Sample>()); } // the time and metric are the same as for the leaf sample // the difference is stack index (not really used from here), caller frame id and depth (used for sorting the exported data) samples.Add(new Sample(stackIndex, callerStackIndex, leafSample.RelativeTime, leafSample.Metric, depth)); if (!exportedFrameIdToExportedNameAndCallerId.ContainsKey(exportedFrameId)) { // in the future we could identify the categories in a more advance way // and split JIT, GC, Runtime, Libraries and ASP.NET Code into separate categories int index = frameName.IndexOf('!'); string category = index > 0 ? frameName.Substring(0, index) : string.Empty; string shortName = index > 0 ? frameName.Substring(index + 1) : frameName; exportedFrameIdToExportedNameAndCallerId.Add(exportedFrameId, new FrameInfo(callerFrameId, shortName, category)); } callerStackIndex = stackIndex; callerFrameId = exportedFrameId; } } return(frameIdToSamples); }
public static Dictionary <string, CallTreeItem> GetCallTree(StackSource stacks, out float timePerStack) { var callTree = new Dictionary <string, CallTreeItem>(StringComparer.OrdinalIgnoreCase); var currentStack = new HashSet <string>(); void AddRecursively(StackSourceCallStackIndex index, int depth = 0, CallTreeItem parent = null) { var name = stacks.GetFrameName(stacks.GetFrameIndex(index), false); if (name == "BROKEN") { return; } var isRecursion = !currentStack.Add(name); var caller = stacks.GetCallerIndex(index); if (!callTree.TryGetValue(name, out var item)) { callTree.Add(name, item = new CallTreeItem(name)); } if (!isRecursion) { item.IncSamples++; } if (depth == 0) { item.Samples++; } else { item.AddCallee(parent); } parent?.AddCaller(item); if (caller != StackSourceCallStackIndex.Invalid) { AddRecursively(caller, depth + 1, item); } if (!isRecursion) { currentStack.Remove(name); } } var metric = float.NaN; stacks.ForEach(stack => { if (float.IsNaN(metric)) { metric = stack.Metric; } if (metric != stack.Metric) { throw new Exception(); } if (stack.Count != 1) { throw new Exception(); } AddRecursively(stack.StackIndex); AddRecursively(stack.StackIndex); }); timePerStack = metric; return(callTree); }