Example #1
0
 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();
 }
Example #2
0
        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);
        }
Example #4
0
        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);
        }
Example #7
0
        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);
        }
Example #11
0
        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);
        }