Skip to content

Commit 7b0ab63

Browse files
committed
Improve unmanaged code labeling in call tree
1 parent 5bc779f commit 7b0ab63

File tree

3 files changed

+110
-14
lines changed

3 files changed

+110
-14
lines changed

src/ProfileTool/Program.cs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2457,7 +2457,7 @@ void CollectTimelineRows(
24572457
int maxWidth)
24582458
{
24592459
var matchName = GetCallTreeMatchName(node);
2460-
var displayName = NameFormatter.FormatMethodDisplayName(matchName);
2460+
var displayName = GetCallTreeDisplayName(matchName);
24612461

24622462
var timeSpent = isRoot && useSelfTime
24632463
? GetCallTreeTime(node, useSelfTime: false)
@@ -2607,7 +2607,7 @@ string FormatCallTreeLine(
26072607
int depth = 0)
26082608
{
26092609
var matchName = GetCallTreeMatchName(node);
2610-
var displayName = matchName;
2610+
var displayName = GetCallTreeDisplayName(matchName);
26112611

26122612
// Calculate max name length based on actual depth (shallower = more space)
26132613
int maxNameLen;
@@ -2795,7 +2795,7 @@ string FormatExceptionCallTreeLine(
27952795
var matchName = GetCallTreeMatchName(node);
27962796
var displayName = isRoot && !string.IsNullOrWhiteSpace(rootLabelOverride)
27972797
? rootLabelOverride
2798-
: matchName;
2798+
: GetCallTreeDisplayName(matchName);
27992799
if (displayName.Length > 80)
28002800
{
28012801
displayName = displayName[..77] + "...";
@@ -3002,7 +3002,8 @@ string FormatCallTreeName(string displayName, string matchName, bool isLeaf)
30023002
}
30033003

30043004
const string leafHighlightColor = "plum1";
3005-
if (matchName.Contains("CastHelpers.", StringComparison.Ordinal))
3005+
if (matchName.Contains("CastHelpers.", StringComparison.Ordinal) ||
3006+
matchName.Contains("UNMANAGED_CODE_TIME", StringComparison.Ordinal))
30063007
{
30073008
return $"[{leafHighlightColor}]{escaped}[/]";
30083009
}
@@ -3034,9 +3035,20 @@ string GetCallTreeMatchName(CallTreeNode node)
30343035
return NameFormatter.FormatMethodDisplayName(node.Name);
30353036
}
30363037

3038+
string GetCallTreeDisplayName(string matchName)
3039+
{
3040+
if (matchName.Contains("UNMANAGED_CODE_TIME", StringComparison.Ordinal))
3041+
{
3042+
return "Unmanaged Code";
3043+
}
3044+
3045+
return matchName;
3046+
}
3047+
30373048
bool ShouldStopAtLeaf(string matchName)
30383049
{
30393050
return matchName.Contains("CastHelpers.", StringComparison.Ordinal) ||
3051+
matchName.Contains("UNMANAGED_CODE_TIME", StringComparison.Ordinal) ||
30403052
matchName.Contains("Array.Copy", StringComparison.Ordinal) ||
30413053
matchName.Contains("Dictionary<__Canon,__Canon>.Resize", StringComparison.Ordinal) ||
30423054
matchName.Contains("Buffer.BulkMoveWithWriteBarrier", StringComparison.Ordinal) ||

src/ProfileTool/Rendering/NameFormatter.cs

Lines changed: 91 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,10 @@ public static string FormatMethodDisplayName(string rawName)
1212
return rawName;
1313
}
1414

15-
var name = rawName;
15+
var name = StripParameterList(rawName);
1616
if (name.Contains('!'))
1717
{
18-
name = name.Split('!')[^1];
19-
}
20-
21-
var parenIdx = name.IndexOf('(');
22-
if (parenIdx > 0)
23-
{
24-
name = name[..parenIdx];
18+
name = StripParameterList(name.Split('!')[^1]);
2519
}
2620

2721
var lastDot = name.LastIndexOf('.');
@@ -35,10 +29,11 @@ public static string FormatMethodDisplayName(string rawName)
3529
return compilerGenerated;
3630
}
3731

38-
return $"{CleanTypeName(typePart)}.{methodPart}";
32+
var formatted = $"{CleanTypeName(typePart)}.{methodPart}";
33+
return EnsureReadableName(formatted);
3934
}
4035

41-
return CleanTypeName(name);
36+
return EnsureReadableName(CleanTypeName(name));
4237
}
4338

4439
public static string FormatTypeDisplayName(string rawName)
@@ -82,6 +77,92 @@ private static string CleanTypeName(string name)
8277
return normalized;
8378
}
8479

80+
private static string StripParameterList(string name)
81+
{
82+
if (string.IsNullOrWhiteSpace(name))
83+
{
84+
return name;
85+
}
86+
87+
var trimmed = name.Trim();
88+
var parenIdx = trimmed.IndexOf('(');
89+
if (parenIdx >= 0)
90+
{
91+
trimmed = trimmed[..parenIdx];
92+
}
93+
else
94+
{
95+
var cutIdx = FindFirstTopLevelSeparator(trimmed);
96+
if (cutIdx >= 0)
97+
{
98+
trimmed = trimmed[..cutIdx];
99+
}
100+
}
101+
102+
return trimmed.TrimEnd(')', '&', ',', ' ');
103+
}
104+
105+
private static string EnsureReadableName(string name)
106+
{
107+
var trimmed = name?.Trim() ?? string.Empty;
108+
if (!HasLetter(trimmed))
109+
{
110+
return "Unmanaged Code";
111+
}
112+
113+
return trimmed;
114+
}
115+
116+
private static int FindFirstTopLevelSeparator(string name)
117+
{
118+
var squareDepth = 0;
119+
var angleDepth = 0;
120+
121+
for (var i = 0; i < name.Length; i++)
122+
{
123+
var ch = name[i];
124+
switch (ch)
125+
{
126+
case '[':
127+
squareDepth++;
128+
break;
129+
case ']':
130+
if (squareDepth > 0)
131+
{
132+
squareDepth--;
133+
}
134+
break;
135+
case '<':
136+
angleDepth++;
137+
break;
138+
case '>':
139+
if (angleDepth > 0)
140+
{
141+
angleDepth--;
142+
}
143+
break;
144+
case ',' when squareDepth == 0 && angleDepth == 0:
145+
case ')' when squareDepth == 0 && angleDepth == 0:
146+
return i;
147+
}
148+
}
149+
150+
return -1;
151+
}
152+
153+
private static bool HasLetter(string value)
154+
{
155+
foreach (var ch in value)
156+
{
157+
if (char.IsLetter(ch))
158+
{
159+
return true;
160+
}
161+
}
162+
163+
return false;
164+
}
165+
85166
private static string? FormatCompilerGeneratedMethod(string typePart, string methodPart)
86167
{
87168
if (string.IsNullOrWhiteSpace(typePart) || string.IsNullOrWhiteSpace(methodPart))

tests/Asynkron.Profiler.Tests/NameFormatterTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ public sealed class NameFormatterTests
1010
[InlineData("PromiseConstructor.<AttachStatics>b__5_0", "PromiseConstructor.AttachStatics lambda")]
1111
[InlineData("Asynkron.JsEngine.Ast.TypedAstEvaluator+TypedFunction.InvokeWithContext2(System.Int32)",
1212
"TypedAstEvaluator.TypedFunction.InvokeWithContext2")]
13+
[InlineData("0)", "Unmanaged Code")]
14+
[InlineData("0[]&,int32)", "Unmanaged Code")]
15+
[InlineData("0&,unsigned int)", "Unmanaged Code")]
1316
public void FormatsMethodNames(string raw, string expected)
1417
{
1518
var actual = NameFormatter.FormatMethodDisplayName(raw);

0 commit comments

Comments
 (0)