Skip to content

Commit 5cb9626

Browse files
committed
Handle unmanaged code frames consistently
1 parent 7b0ab63 commit 5cb9626

File tree

3 files changed

+91
-36
lines changed

3 files changed

+91
-36
lines changed

src/ProfileTool/Program.cs

Lines changed: 68 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -439,18 +439,23 @@ void PrintCpuResults(
439439

440440
foreach (var entry in filteredList.Take(15))
441441
{
442-
var funcName = NameFormatter.FormatMethodDisplayName(entry.Name);
442+
var funcName = FormatFunctionDisplayName(entry.Name);
443443
if (funcName.Length > 70) funcName = funcName[..67] + "...";
444444

445445
var timeMs = entry.TimeMs;
446446
var calls = entry.Calls;
447447
var timeMsText = timeMs.ToString("F2", CultureInfo.InvariantCulture);
448448
var callsText = calls.ToString("N0", CultureInfo.InvariantCulture);
449+
var funcText = Markup.Escape(funcName);
450+
if (IsUnmanagedFrame(funcName))
451+
{
452+
funcText = $"[plum1]{funcText}[/]";
453+
}
449454

450455
table.AddRow(
451456
$"[green]{timeMsText}[/]",
452457
$"[blue]{callsText}[/]",
453-
Markup.Escape(funcName)
458+
funcText
454459
);
455460
}
456461

@@ -1746,14 +1751,20 @@ void PrintExceptionResults(
17461751

17471752
foreach (var entry in catchList.Take(15))
17481753
{
1749-
var funcName = NameFormatter.FormatMethodDisplayName(entry.Name);
1754+
var funcName = FormatFunctionDisplayName(entry.Name);
17501755
if (funcName.Length > 70)
17511756
{
17521757
funcName = funcName[..67] + "...";
17531758
}
17541759

17551760
var countText = entry.Count.ToString("N0", CultureInfo.InvariantCulture);
1756-
catchTable.AddRow($"[blue]{countText}[/]", Markup.Escape(funcName));
1761+
var funcText = Markup.Escape(funcName);
1762+
if (IsUnmanagedFrame(funcName))
1763+
{
1764+
funcText = $"[plum1]{funcText}[/]";
1765+
}
1766+
1767+
catchTable.AddRow($"[blue]{countText}[/]", funcText);
17571768
}
17581769

17591770
AnsiConsole.Write(catchTable);
@@ -1818,18 +1829,23 @@ void PrintContentionResults(
18181829

18191830
foreach (var entry in filteredList.Take(15))
18201831
{
1821-
var funcName = NameFormatter.FormatMethodDisplayName(entry.Name);
1832+
var funcName = FormatFunctionDisplayName(entry.Name);
18221833
if (funcName.Length > 70)
18231834
{
18241835
funcName = funcName[..67] + "...";
18251836
}
18261837

18271838
var waitText = entry.TimeMs.ToString("F2", CultureInfo.InvariantCulture);
18281839
var countText = entry.Calls.ToString("N0", CultureInfo.InvariantCulture);
1840+
var funcText = Markup.Escape(funcName);
1841+
if (IsUnmanagedFrame(funcName))
1842+
{
1843+
funcText = $"[plum1]{funcText}[/]";
1844+
}
18291845
table.AddRow(
18301846
$"[green]{waitText}[/]",
18311847
$"[blue]{countText}[/]",
1832-
Markup.Escape(funcName));
1848+
funcText);
18331849
}
18341850

18351851
AnsiConsole.Write(table);
@@ -2124,7 +2140,7 @@ IRenderable BuildAllocationCallTree(
21242140
var children = GetVisibleAllocationChildren(root, includeRuntime, maxWidth, siblingCutoffPercent);
21252141
foreach (var child in children)
21262142
{
2127-
var isSpecialLeaf = ShouldStopAtLeaf(NameFormatter.FormatMethodDisplayName(child.Name));
2143+
var isSpecialLeaf = ShouldStopAtLeaf(FormatFunctionDisplayName(child.Name));
21282144
var childChildren = !isSpecialLeaf
21292145
? GetVisibleAllocationChildren(child, includeRuntime, maxWidth, siblingCutoffPercent)
21302146
: Array.Empty<AllocationCallTreeNode>();
@@ -2167,7 +2183,7 @@ void AddAllocationCallTreeChildren(
21672183
foreach (var child in children)
21682184
{
21692185
var nextDepth = depth + 1;
2170-
var isSpecialLeaf = ShouldStopAtLeaf(NameFormatter.FormatMethodDisplayName(child.Name));
2186+
var isSpecialLeaf = ShouldStopAtLeaf(FormatFunctionDisplayName(child.Name));
21712187
var childChildren = !isSpecialLeaf && nextDepth <= maxDepth
21722188
? GetVisibleAllocationChildren(child, includeRuntime, maxWidth, siblingCutoffPercent)
21732189
: Array.Empty<AllocationCallTreeNode>();
@@ -2254,7 +2270,7 @@ string FormatAllocationCallTreeLine(
22542270
var pctText = pct.ToString("F1", CultureInfo.InvariantCulture);
22552271
var countText = count.ToString("N0", CultureInfo.InvariantCulture);
22562272

2257-
var displayName = isRoot ? NameFormatter.FormatTypeDisplayName(node.Name) : NameFormatter.FormatMethodDisplayName(node.Name);
2273+
var displayName = isRoot ? NameFormatter.FormatTypeDisplayName(node.Name) : FormatFunctionDisplayName(node.Name);
22582274
if (displayName.Length > 80)
22592275
{
22602276
displayName = displayName[..77] + "...";
@@ -2485,6 +2501,11 @@ void CollectTimelineRows(
24852501
}
24862502

24872503
var escapedName = Markup.Escape(truncatedName);
2504+
if (ShouldStopAtLeaf(matchName))
2505+
{
2506+
escapedName = $"[plum1]{escapedName}[/]";
2507+
}
2508+
24882509
var visibleLength = statsLength + truncatedName.Length;
24892510

24902511
return ($"[dim]{Markup.Escape(prefix)}[/][green]{timeText} ms[/] [cyan]{pctText}%[/] [blue]{callsText}x[/] {escapedName}", visibleLength);
@@ -2864,7 +2885,7 @@ void Visit(CallTreeNode current, int depth)
28642885
{
28652886
if (current.FrameIdx >= 0)
28662887
{
2867-
var displayName = NameFormatter.FormatMethodDisplayName(current.Name);
2888+
var displayName = FormatFunctionDisplayName(current.Name);
28682889
if (displayName.Contains(normalizedFilter, StringComparison.OrdinalIgnoreCase) ||
28692890
current.Name.Contains(normalizedFilter, StringComparison.OrdinalIgnoreCase))
28702891
{
@@ -2993,36 +3014,47 @@ string FormatBytes(long bytes)
29933014
return (bytes / (1024d * 1024d * 1024d)).ToString("F2", CultureInfo.InvariantCulture) + " GB";
29943015
}
29953016

2996-
string FormatCallTreeName(string displayName, string matchName, bool isLeaf)
3017+
bool IsUnmanagedFrame(string name)
29973018
{
2998-
var escaped = Markup.Escape(displayName);
2999-
if (!isLeaf)
3019+
var trimmed = name?.Trim() ?? string.Empty;
3020+
if (trimmed.Length == 0)
30003021
{
3001-
return $"[white]{escaped}[/]";
3022+
return false;
30023023
}
30033024

3004-
const string leafHighlightColor = "plum1";
3005-
if (matchName.Contains("CastHelpers.", StringComparison.Ordinal) ||
3006-
matchName.Contains("UNMANAGED_CODE_TIME", StringComparison.Ordinal))
3025+
if (trimmed.Contains("UNMANAGED_CODE_TIME", StringComparison.OrdinalIgnoreCase) ||
3026+
trimmed.Contains("Unmanaged Code", StringComparison.OrdinalIgnoreCase))
30073027
{
3008-
return $"[{leafHighlightColor}]{escaped}[/]";
3028+
return true;
30093029
}
30103030

3011-
if (matchName.Contains("Array.Copy", StringComparison.Ordinal) ||
3012-
matchName.Contains("Dictionary<__Canon,__Canon>.Resize", StringComparison.Ordinal) ||
3013-
matchName.Contains("Buffer.BulkMoveWithWriteBarrier", StringComparison.Ordinal) ||
3014-
matchName.Contains("SpanHelpers.SequenceEqual", StringComparison.Ordinal) ||
3015-
matchName.Contains("HashSet<", StringComparison.Ordinal) ||
3016-
matchName.Contains("Enumerable+ArrayWhereSelectIterator<", StringComparison.Ordinal) ||
3017-
matchName.Contains("ImmutableDictionary<", StringComparison.Ordinal) ||
3018-
matchName.Contains("SegmentedArrayBuilder<__Canon>.ToArray", StringComparison.Ordinal) ||
3019-
matchName.Contains("__Canon", StringComparison.Ordinal))
3031+
foreach (var ch in trimmed)
30203032
{
3021-
return $"[{leafHighlightColor}]{escaped}[/]";
3033+
if (char.IsLetter(ch))
3034+
{
3035+
return false;
3036+
}
3037+
}
3038+
3039+
return true;
3040+
}
3041+
3042+
string FormatFunctionDisplayName(string rawName)
3043+
{
3044+
var formatted = NameFormatter.FormatMethodDisplayName(rawName);
3045+
return GetCallTreeDisplayName(formatted);
3046+
}
3047+
3048+
string FormatCallTreeName(string displayName, string matchName, bool isLeaf)
3049+
{
3050+
var escaped = Markup.Escape(displayName);
3051+
if (!isLeaf)
3052+
{
3053+
return $"[white]{escaped}[/]";
30223054
}
30233055

3024-
if (matchName.Contains("List<", StringComparison.Ordinal) &&
3025-
matchName.EndsWith(".ToArray", StringComparison.Ordinal))
3056+
const string leafHighlightColor = "plum1";
3057+
if (ShouldStopAtLeaf(matchName))
30263058
{
30273059
return $"[{leafHighlightColor}]{escaped}[/]";
30283060
}
@@ -3037,7 +3069,7 @@ string GetCallTreeMatchName(CallTreeNode node)
30373069

30383070
string GetCallTreeDisplayName(string matchName)
30393071
{
3040-
if (matchName.Contains("UNMANAGED_CODE_TIME", StringComparison.Ordinal))
3072+
if (IsUnmanagedFrame(matchName))
30413073
{
30423074
return "Unmanaged Code";
30433075
}
@@ -3047,8 +3079,8 @@ string GetCallTreeDisplayName(string matchName)
30473079

30483080
bool ShouldStopAtLeaf(string matchName)
30493081
{
3050-
return matchName.Contains("CastHelpers.", StringComparison.Ordinal) ||
3051-
matchName.Contains("UNMANAGED_CODE_TIME", StringComparison.Ordinal) ||
3082+
return IsUnmanagedFrame(matchName) ||
3083+
matchName.Contains("CastHelpers.", StringComparison.Ordinal) ||
30523084
matchName.Contains("Array.Copy", StringComparison.Ordinal) ||
30533085
matchName.Contains("Dictionary<__Canon,__Canon>.Resize", StringComparison.Ordinal) ||
30543086
matchName.Contains("Buffer.BulkMoveWithWriteBarrier", StringComparison.Ordinal) ||
@@ -3070,7 +3102,7 @@ bool MatchesFunctionFilter(string name, string? filter)
30703102
}
30713103

30723104
return name.Contains(filter, StringComparison.OrdinalIgnoreCase) ||
3073-
NameFormatter.FormatMethodDisplayName(name).Contains(filter, StringComparison.OrdinalIgnoreCase);
3105+
FormatFunctionDisplayName(name).Contains(filter, StringComparison.OrdinalIgnoreCase);
30743106
}
30753107

30763108
IReadOnlyList<ExceptionTypeSample> FilterExceptionTypes(
@@ -3112,8 +3144,8 @@ IReadOnlyList<ExceptionTypeSample> FilterExceptionTypes(
31123144
bool IsRuntimeNoise(string name)
31133145
{
31143146
var trimmed = name.TrimStart();
3115-
var formatted = NameFormatter.FormatMethodDisplayName(trimmed);
3116-
return trimmed.Contains("UNMANAGED_CODE_TIME", StringComparison.Ordinal) ||
3147+
var formatted = FormatFunctionDisplayName(trimmed);
3148+
return IsUnmanagedFrame(trimmed) ||
31173149
trimmed.Contains("(Non-Activities)", StringComparison.Ordinal) ||
31183150
trimmed.Contains("Thread", StringComparison.Ordinal) ||
31193151
trimmed.Contains("Threads", StringComparison.Ordinal) ||

src/ProfileTool/Rendering/NameFormatter.cs

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

15+
if (IsUnmanagedCode(rawName))
16+
{
17+
return "Unmanaged Code";
18+
}
19+
1520
var name = StripParameterList(rawName);
21+
if (IsUnmanagedCode(name))
22+
{
23+
return "Unmanaged Code";
24+
}
25+
1626
if (name.Contains('!'))
1727
{
1828
name = StripParameterList(name.Split('!')[^1]);
@@ -77,6 +87,17 @@ private static string CleanTypeName(string name)
7787
return normalized;
7888
}
7989

90+
private static bool IsUnmanagedCode(string name)
91+
{
92+
var trimmed = name?.Trim() ?? string.Empty;
93+
if (trimmed.Length == 0)
94+
{
95+
return false;
96+
}
97+
98+
return trimmed.Contains("UNMANAGED_CODE_TIME", StringComparison.OrdinalIgnoreCase);
99+
}
100+
80101
private static string StripParameterList(string name)
81102
{
82103
if (string.IsNullOrWhiteSpace(name))

tests/Asynkron.Profiler.Tests/NameFormatterTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ 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("UNMANAGED_CODE_TIME", "Unmanaged Code")]
14+
[InlineData("UNMANAGED_CODE_TIME (Native Frames)", "Unmanaged Code")]
1315
[InlineData("0)", "Unmanaged Code")]
1416
[InlineData("0[]&,int32)", "Unmanaged Code")]
1517
[InlineData("0&,unsigned int)", "Unmanaged Code")]

0 commit comments

Comments
 (0)