Remove ParallelStack.Runtime nuget reference and copy its implementation in dotnet...
authorchrisnas <chrisnas@users.noreply.github.com>
Wed, 17 Mar 2021 01:28:16 +0000 (02:28 +0100)
committerGitHub <noreply@github.com>
Wed, 17 Mar 2021 01:28:16 +0000 (18:28 -0700)
Authored-by: Christophe Nasarre <c.nasarresoulier@criteo.com>
src/Microsoft.Diagnostics.ExtensionCommands/Microsoft.Diagnostics.ExtensionCommands.csproj
src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacks.Runtime/IRenderer.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacks.Runtime/ParallelStack.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacks.Runtime/RendererBase.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacks.Runtime/RendererHelpers.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacks.Runtime/StackFrame.cs [new file with mode: 0644]

index 3c8adc2a5a8f5af86e19f53b55c34b3cde456cb3..4a47509c5f2f5ddbd24c44445d413cbeeb6ecd41 100644 (file)
     <IncludeSymbols>true</IncludeSymbols>
     <IsShippingAssembly>false</IsShippingAssembly>
   </PropertyGroup>
-  
+
   <ItemGroup>
     <PackageReference Include="Microsoft.Diagnostics.Runtime" Version="$(MicrosoftDiagnosticsRuntimeVersion)" />
-    <PackageReference Include="ParallelStacks.Runtime" Version="$(ParallelStacksRuntimeVersion)" />
   </ItemGroup>
-  
+
   <ItemGroup>
     <ProjectReference Include="..\Microsoft.Diagnostics.DebugServices\Microsoft.Diagnostics.DebugServices.csproj" />
   </ItemGroup>
+
+  <ItemGroup>
+    <Folder Include="ParallelStacks.Runtime\" />
+  </ItemGroup>
 </Project>
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacks.Runtime/IRenderer.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacks.Runtime/IRenderer.cs
new file mode 100644 (file)
index 0000000..2cffd90
--- /dev/null
@@ -0,0 +1,85 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace ParallelStacks.Runtime
+{
+    /// <summary>
+    /// The method of this interface are called to render each part of the parallel call stacks
+    /// </summary>
+    /// <remarks>
+    /// Each method is responsible for adding color, tags or decoration on each element of the parallel stacks
+    /// </remarks>
+    public interface IRenderer
+    {
+        /// <summary>
+        /// Max number of thread IDs to display at the end of each stack frame group.
+        /// This is important in the case of 100+ threads applications.
+        /// </summary>
+        /// <remarks>
+        /// Use -1 if there should not be any limit.
+        /// </remarks>
+        int DisplayThreadIDsCountLimit { get; }
+
+        /// <summary>
+        /// Render empty line
+        /// </summary>
+        /// <param name="text"></param>
+        void Write(string text);
+
+        /// <summary>
+        /// Render count at the beginning of each line
+        /// </summary>
+        /// <param name="count"></param>
+        void WriteCount(string count);
+
+        /// <summary>
+        /// Render namespace of each method type
+        /// </summary>
+        /// <param name="ns"></param>
+        void WriteNamespace(string ns);
+
+        /// <summary>
+        /// Render each type in method signatures
+        /// </summary>
+        /// <param name="type"></param>
+        void WriteType(string type);
+
+        /// <summary>
+        /// Render separators such as ( and .
+        /// </summary>
+        /// <param name="separator"></param>
+        void WriteSeparator(string separator);
+
+        /// <summary>
+        /// Render dark signature element such as ByRef
+        /// </summary>
+        /// <param name="separator"></param>
+        void WriteDark(string separator);
+
+        /// <summary>
+        /// Render method name
+        /// </summary>
+        /// <param name="method"></param>
+        void WriteMethod(string method);
+
+        /// <summary>
+        /// Render method type (not including namespace)
+        /// </summary>
+        /// <param name="type"></param>
+        void WriteMethodType(string type);
+
+        /// <summary>
+        /// Render separator between different stack frame blocks
+        /// </summary>
+        /// <param name="text"></param>
+        void WriteFrameSeparator(string text);
+
+        /// <summary>
+        /// Render a thread id that will appear for each stack frames group (at the end of WriteFrameSeparator)
+        /// For example, in HTML it could be used to add a link to show details such as ClrStack -p
+        /// </summary>
+        /// <param name="threadID"></param>
+        string FormatTheadId(uint threadID);
+    }
+}
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacks.Runtime/ParallelStack.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacks.Runtime/ParallelStack.cs
new file mode 100644 (file)
index 0000000..2478096
--- /dev/null
@@ -0,0 +1,158 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using Microsoft.Diagnostics.Runtime;
+
+namespace ParallelStacks.Runtime
+{
+    public class ParallelStack
+    {
+        public static ParallelStack Build(ClrRuntime runtime)
+        {
+            var ps = new ParallelStack();
+            var stackFrames = new List<ClrStackFrame>(64);
+            foreach (var thread in runtime.Threads)
+            {
+                stackFrames.Clear();
+                foreach (var stackFrame in thread.EnumerateStackTrace().Reverse())
+                {
+                    if ((stackFrame.Kind != ClrStackFrameKind.ManagedMethod) || (stackFrame.Method == null))
+                        continue;
+
+                    stackFrames.Add(stackFrame);
+                }
+
+                if (stackFrames.Count == 0)
+                    continue;
+
+                ps.AddStack(thread.OSThreadId, stackFrames.ToArray());
+            }
+
+            return ps;
+        }
+
+        public static ParallelStack Build(string dumpFile, string dacFilePath)
+        {
+            DataTarget dataTarget = null;
+            ParallelStack ps = null;
+            try
+            {
+                if (
+                    (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) ||
+                    (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+                )
+                {
+                    dataTarget = DataTarget.LoadDump(dumpFile);
+                }
+                else
+                {
+                    throw new InvalidOperationException("Unsupported platform...");
+                }
+
+                var runtime = CreateRuntime(dataTarget, dacFilePath);
+                if (runtime == null)
+                {
+                    return null;
+                }
+
+                ps = ParallelStack.Build(runtime);
+            }
+            finally
+            {
+                dataTarget?.Dispose();
+            }
+
+            return ps;
+        }
+
+        public static ParallelStack Build(int pid, string dacFilePath)
+        {
+            DataTarget dataTarget = null;
+            ParallelStack ps = null;
+            try
+            {
+                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+                {
+                    dataTarget = DataTarget.AttachToProcess(pid, true);
+                }
+                else
+                if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+                {
+                    // ClrMD implementation for Linux is available only for Passive
+                    dataTarget = DataTarget.AttachToProcess(pid, true);
+                }
+                else
+                {
+                    throw new InvalidOperationException("Unsupported platform...");
+                }
+
+                var runtime = CreateRuntime(dataTarget, dacFilePath);
+                if (runtime == null)
+                {
+                    return null;
+                }
+
+                ps = ParallelStack.Build(runtime);
+            }
+            finally
+            {
+                dataTarget?.Dispose();
+            }
+
+            return ps;
+        }
+
+        private static ClrRuntime CreateRuntime(DataTarget dataTarget, string dacFilePath)
+        {
+            // check bitness first
+            bool isTarget64Bit = (dataTarget.DataReader.PointerSize == 8);
+            if (Environment.Is64BitProcess != isTarget64Bit)
+            {
+                throw new InvalidOperationException(
+                    $"Architecture mismatch:  This tool is {(Environment.Is64BitProcess ? "64 bit" : "32 bit")} but target is {(isTarget64Bit ? "64 bit" : "32 bit")}");
+            }
+
+            var version = dataTarget.ClrVersions[0];
+            var runtime = (dacFilePath != null) ? version.CreateRuntime(dacFilePath) : version.CreateRuntime();
+            return runtime;
+        }
+        
+        private ParallelStack(ClrStackFrame frame = null)
+        {
+            Stacks = new List<ParallelStack>();
+            ThreadIds = new List<uint>();
+            Frame = (frame == null) ? null : new StackFrame(frame);
+        }
+
+        public List<ParallelStack> Stacks { get; }
+
+        public StackFrame Frame { get; }
+
+        public List<uint> ThreadIds { get; set; }
+
+        private void AddStack(uint threadId, ClrStackFrame[] frames, int index = 0)
+        {
+            ThreadIds.Add(threadId);
+            var firstFrame = frames[index].Method?.Signature;
+            var callstack = Stacks.FirstOrDefault(s => s.Frame.Text == firstFrame);
+            if (callstack == null)
+            {
+                callstack = new ParallelStack(frames[index]);
+                Stacks.Add(callstack);
+            }
+
+            if (index == frames.Length - 1)
+            {
+                callstack.ThreadIds.Add(threadId);
+                return;
+            }
+
+            callstack.AddStack(threadId, frames, index + 1);
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacks.Runtime/RendererBase.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacks.Runtime/RendererBase.cs
new file mode 100644 (file)
index 0000000..8bd26df
--- /dev/null
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace ParallelStacks.Runtime
+{
+    public abstract class RendererBase : IRenderer
+    {
+        private readonly int _limit;
+
+        protected RendererBase(int limit)
+        {
+            _limit = limit;
+        }
+
+        public int DisplayThreadIDsCountLimit => _limit;
+
+        public abstract void Write(string text);
+        public abstract void WriteCount(string count);
+        public abstract void WriteNamespace(string ns);
+        public abstract void WriteType(string type);
+        public abstract void WriteSeparator(string separator);
+        public abstract void WriteDark(string separator);
+        public abstract void WriteMethod(string method);
+        public abstract void WriteMethodType(string type);
+        public abstract void WriteFrameSeparator(string text);
+        public abstract string FormatTheadId(uint threadID);
+    }
+}
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacks.Runtime/RendererHelpers.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacks.Runtime/RendererHelpers.cs
new file mode 100644 (file)
index 0000000..4fbd5a0
--- /dev/null
@@ -0,0 +1,99 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace ParallelStacks.Runtime
+{
+    public static class RendererHelpers
+    {
+        public static void Render(this ParallelStack stacks, IRenderer visitor)
+        {
+            RenderStack(stacks, visitor);
+        }
+
+        private const int Padding = 5;
+        private static void RenderStack(ParallelStack stack, IRenderer visitor, int increment = 0)
+        {
+            var alignment = new string(' ', Padding * increment);
+            if (stack.Stacks.Count == 0)
+            {
+                var lastFrame = stack.Frame;
+                visitor.Write($"{Environment.NewLine}{alignment}");
+                visitor.WriteFrameSeparator($" ~~~~ {FormatThreadIdList(visitor, stack.ThreadIds)}");
+                visitor.WriteCount($"{Environment.NewLine}{alignment}{stack.ThreadIds.Count,Padding} ");
+
+                RenderFrame(lastFrame, visitor);
+                return;
+            }
+
+            foreach (var nextStackFrame in stack.Stacks.OrderBy(s => s.ThreadIds.Count))
+            {
+                RenderStack(nextStackFrame, visitor,
+                    (nextStackFrame.ThreadIds.Count == stack.ThreadIds.Count) ? increment : increment + 1);
+            }
+
+            var currentFrame = stack.Frame;
+            visitor.WriteCount($"{Environment.NewLine}{alignment}{stack.ThreadIds.Count,Padding} ");
+            RenderFrame(currentFrame, visitor);
+        }
+
+        private static string FormatThreadIdList(IRenderer visitor, List<uint> threadIds)
+        {
+            var count = threadIds.Count;
+            var limit = visitor.DisplayThreadIDsCountLimit;
+            limit = Math.Min(count, limit);
+            if (limit < 0)
+                return string.Join(",", threadIds.Select(tid => visitor.FormatTheadId(tid)));
+            else
+            {
+                var result = string.Join(",", threadIds.GetRange(0, limit).Select(tid => visitor.FormatTheadId(tid)));
+                if (count > limit)
+                    result += "...";
+                return result;
+            }
+        }
+
+        private static void RenderFrame(StackFrame frame, IRenderer visitor)
+        {
+            if (!string.IsNullOrEmpty(frame.TypeName))
+            {
+                var namespaces = frame.TypeName.Split('.');
+                for (int i = 0; i < namespaces.Length - 1; i++)
+                {
+                    visitor.WriteNamespace(namespaces[i]);
+                    visitor.WriteSeparator(".");
+                }
+                visitor.WriteMethodType(namespaces[namespaces.Length - 1]);
+                visitor.WriteSeparator(".");
+            }
+
+            visitor.WriteMethod(frame.MethodName);
+            visitor.WriteSeparator("(");
+
+            var parameters = frame.Signature;
+            for (int current = 0; current < parameters.Count; current++)
+            {
+                var parameter = parameters[current];
+                // handle byref case
+                var pos = parameter.LastIndexOf(" ByRef");
+                if (pos != -1)
+                {
+                    visitor.WriteType(parameter.Substring(0, pos));
+                    visitor.WriteDark(" ByRef");
+                }
+                else
+                {
+                    visitor.WriteType(parameter);
+                }
+                if (current < parameters.Count - 1) visitor.WriteSeparator(", ");
+            }
+            visitor.WriteSeparator(")");
+        }
+
+
+    }
+}
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacks.Runtime/StackFrame.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacks.Runtime/StackFrame.cs
new file mode 100644 (file)
index 0000000..1ee3a56
--- /dev/null
@@ -0,0 +1,249 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+using Microsoft.Diagnostics.Runtime;
+
+namespace ParallelStacks
+{
+    public class StackFrame
+    {
+        public string TypeName { get; private set; }
+        public string MethodName { get; private set; }
+        public List<string> Signature { get; }
+
+        public string Text { get; }
+
+        public StackFrame(ClrStackFrame frame)
+        {
+            var signature = frame.Method?.Signature;
+            Text = string.IsNullOrEmpty(signature) ? "?" : string.Intern(signature);
+            Signature = new List<string>();
+            ComputeNames(frame);
+        }
+
+        private void ComputeNames(ClrStackFrame frame)
+        {
+            // start by parsing (short)type name
+            var typeName = frame.Method.Type.Name;
+            if (string.IsNullOrEmpty(typeName))
+            {
+                // IL generated frames
+                TypeName = string.Empty;
+            }
+            else
+            {
+                TypeName = typeName;
+            }
+
+            // generic methods are not well formatted by ClrMD
+            // foo<...>()  =>   foo[[...]]()
+            var fullName = frame.Method?.Signature;
+            MethodName = frame.Method.Name;
+            if (MethodName.EndsWith("]]"))
+            {
+                // fix ClrMD bug with method name
+                MethodName = GetGenericMethodName(fullName);
+            }
+
+            Signature.AddRange(BuildSignature(fullName));
+        }
+
+        public static string GetShortTypeName(string typeName, int start, int end)
+        {
+            return GetNextTypeName(typeName, ref start, ref end);
+        }
+
+        // this helper is called in 2 situations to analyze a method signature parameter:
+        //  - compute the next type in a generic definition
+        //  - start from a full type name
+        // in all cases:
+        //  - end   = the index of the last character (could be far beyond the end of the next type name in case of generic)
+        //  - start = first character of the type name
+        public static string GetNextTypeName(string typeName, ref int start, ref int end)
+        {
+            if (string.IsNullOrEmpty(typeName))
+                return string.Empty;
+
+            var sb = new StringBuilder();
+
+            // need to make the difference between generic and non generic parameters
+            //      *.Int32                                         --> Int32
+            //      *.IList`1<*.String>                             --> IList + continue on *.String>
+            //      *.IDictionary`2<*.Int32,*.IList`1<*.String>>    --> IDictionary + continue on *.Int32,*.IList`1<*.String>>
+            //      *.Int32,*.IList`1<*.String>>                    --> Int32 + continue on *.IList`1<*.String>>
+            //      *.Int32>                                        --> Int32
+            //  1. look for generic 
+            //  2. if not, look for , as separator of generic parameters
+            var pos = typeName.IndexOf('`', start, end - start);
+            var next = typeName.IndexOf(',', start, end - start);
+
+            // simple case of 1 type name (with maybe no namespace)
+            if ((pos == -1) && (next == -1))
+            {
+                AppendTypeNameWithoutNamespace(sb, typeName, start, end);
+
+                // it was the last type name
+                start = end;
+
+                return sb.ToString();
+            }
+
+            // this is the last type
+            if (next == -1)
+            {
+                // *.IList`1<...>,xxx
+                // *.IList`1<xxx,...>
+                return GetGenericTypeName(typeName, ref start, ref end);
+            }
+
+            // at least 1 type name (even before a generic type)
+            if (pos == -1)
+            {
+                // *.Int32,xxx  with xxx could contain a generic 
+                AppendTypeNameWithoutNamespace(sb, typeName, start, next-1);
+
+                // skip this type
+                start = next + 1;
+
+                return sb.ToString();
+            }
+
+            // a generic type before another type or a generic type with more than 1 parameter
+            if (pos < next)
+            {
+                // *.IList`1<...>,xxx
+                // *.IList`1<xxx,...>
+                return GetGenericTypeName(typeName, ref start, ref end);
+            }
+
+            // a non generic type before another type parameter
+            // *.Int32,xxx
+            AppendTypeNameWithoutNamespace(sb, typeName, start, next-1);
+
+            // skip this type
+            start = next + 1;
+
+            return sb.ToString();
+        }
+
+        public static string GetGenericTypeName(string typeName, ref int start, ref int end)
+        {
+            // System.Collections.Generic.IList`1<System.Collections.Generic.IEnumerable`1<System.String>>
+            // System.Collections.Generic.IDictionary`2<Int32,System.String>
+            var sb = new StringBuilder();
+
+            // look for ` to get the name and the count of generic parameters
+            var pos = typeName.IndexOf('`', start, end - start);
+
+            // build the name                                       V-- don't want ` in the name
+            AppendTypeNameWithoutNamespace(sb, typeName, start, pos-1);
+            sb.Append('<');
+
+            // go to the first generic parameter
+            start = typeName.IndexOf('<', pos, end - pos) + 1;
+
+            // get each generic parameter
+            while (start < end)
+            {
+                var genericParameter = GetNextTypeName(typeName, ref start, ref end);
+                sb.Append(genericParameter);
+                if (start < end)
+                {
+                    sb.Append(',');
+                }
+            }
+
+            return sb.ToString();
+        }
+
+        public static void AppendTypeNameWithoutNamespace(StringBuilder sb, string typeName, int start, int end)
+        {
+            var pos = typeName.LastIndexOf('.', end, end - start);
+            if (pos == -1)
+            {   // no namespace
+                sb.Append(typeName, start, end - start + 1);
+            }
+            else
+            {
+                // skip the namespace
+                sb.Append(typeName, pos + 1, end - pos);
+            }
+        }
+
+        public static IEnumerable<string> BuildSignature(string fullName)
+        {
+            // {namespace.}type.method[[]](..., ..., ...)
+            var parameters = new List<string>();
+            var pos = fullName.LastIndexOf('(');
+            if (pos == -1)
+            {
+                return parameters;
+            }
+
+            // look for each parameter, one after the other
+            int next = pos;
+            string parameter = string.Empty;
+            while (next != (fullName.Length - 1))
+            {
+                next = fullName.IndexOf(", ", pos);
+                if (next == -1)
+                {
+                    next = fullName.IndexOf(')'); // should be the last character of the string
+                    Debug.Assert(next == fullName.Length - 1);
+                }
+
+                //                             skip   .      ,
+                parameter = GetParameter(fullName, pos + 1, next - 1);
+                if (parameter != null) parameters.Add(parameter);
+
+                pos = next + 1;
+            }
+
+            return parameters;
+        }
+
+        public static string GetParameter(string fullName, int start, int end)
+        {
+            const string BYREF = " ByRef";
+            //   ()  no parameter
+            if (start >= end)
+                return null;
+
+            var sb = new StringBuilder();
+
+            // handle ByRef case
+            var isByRef = false;
+            if (fullName.LastIndexOf(BYREF, end) == end - BYREF.Length)
+            {
+                isByRef = true;
+                end -= BYREF.Length;
+            }
+
+            var typeName = GetShortTypeName(fullName, start, end);
+            sb.Append(typeName);
+
+            if (isByRef)
+                sb.Append(BYREF);
+
+            return sb.ToString();
+        }
+
+        public static string GetGenericMethodName(string fullName)
+        {
+            // foo[[...]] --> foo<...>
+            // namespace.type.Foo[[System.String, Int32]](System.Collections.Generic.IDictionary`2<Int32,System.String>)
+            var pos = fullName.IndexOf("[[");
+            if (pos == -1)
+            {
+                return fullName;
+            }
+
+            var start = fullName.LastIndexOf('.', pos);
+            return fullName.Substring(start + 1, pos - start - 1);
+        }
+    }
+}