Repository navigation
Refactor call tree rendering responsibilities - #57
Conversation
There was a problem hiding this comment.
Pull request overview
Refactors call-tree/timeline rendering by extracting previously inlined helper logic into focused renderer/formatter components within ProfilerCore, and adds a regression test for public JIT number highlighting behavior.
Changes:
- Extracted timeline bar rendering to
CallTreeTimelineBarRendererand timeline row construction toProfilerTimelineRowsRenderer. - Extracted hotness color interpolation into
ProfilerHotnessColorScaleand JIT number highlighting intoProfilerJitNumberHighlighter. - Slimmed down
ProfilerCallTreeFormatter/ProfilerCallTreeRendererto delegate to the new helpers; added JIT highlighting regression tests.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Asynkron.Profiler.Tests/ProfilerJitNumberHighlighterTests.cs | Adds regression coverage for JIT decimal/hex token highlighting behavior. |
| src/ProfilerCore/Rendering/ProfilerTimelineRowsRenderer.cs | New component to build timeline-oriented call-tree output rows. |
| src/ProfilerCore/Rendering/ProfilerJitNumberHighlighter.cs | New helper encapsulating regex-based JIT number highlighting. |
| src/ProfilerCore/Rendering/ProfilerHotnessColorScale.cs | New helper for hotness normalization + theme color interpolation. |
| src/ProfilerCore/Rendering/ProfilerCallTreeRenderer.cs | Delegates timeline rendering to ProfilerTimelineRowsRenderer; updates hotspot detection helper usage. |
| src/ProfilerCore/Rendering/ProfilerCallTreeFormatter.cs | Delegates hotness color and JIT highlighting; delegates timeline bar rendering. |
| src/ProfilerCore/Rendering/CallTreeTimelineBarRenderer.cs | New helper containing the timeline bar drawing logic. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| public Rows Build( | ||
| CallTreeNode rootNode, | ||
| string title, | ||
| ProfilerCallTreeRenderContext context, | ||
| int timelineWidth) | ||
| { | ||
| var terminalWidth = Console.WindowWidth > 0 ? Console.WindowWidth : 160; |
There was a problem hiding this comment.
Console.WindowWidth can throw (e.g., when output is redirected / no interactive terminal). Consider mirroring ProfilerCommandLineHelp.GetHelpWidth(): check Console.IsOutputRedirected and/or wrap WindowWidth access in try/catch with a reasonable fallback width, so timeline rendering can’t crash in non-interactive contexts.
| public Rows Build( | |
| CallTreeNode rootNode, | |
| string title, | |
| ProfilerCallTreeRenderContext context, | |
| int timelineWidth) | |
| { | |
| var terminalWidth = Console.WindowWidth > 0 ? Console.WindowWidth : 160; | |
| private static int GetTerminalWidth() | |
| { | |
| const int fallbackWidth = 160; | |
| if (Console.IsOutputRedirected) | |
| { | |
| return fallbackWidth; | |
| } | |
| try | |
| { | |
| var width = Console.WindowWidth; | |
| return width > 0 ? width : fallbackWidth; | |
| } | |
| catch | |
| { | |
| return fallbackWidth; | |
| } | |
| } | |
| public Rows Build( | |
| CallTreeNode rootNode, | |
| string title, | |
| ProfilerCallTreeRenderContext context, | |
| int timelineWidth) | |
| { | |
| var terminalWidth = GetTerminalWidth(); |
| depth: 0, | ||
| timeline); | ||
|
|
||
| var outputLines = new List<IRenderable> { new Markup($"[bold {_theme.AccentColor}]{title}[/]") }; |
There was a problem hiding this comment.
The title is interpolated into a Spectre.Console Markup string without escaping. If title contains markup control characters (e.g., [/] from a file name or filter), this can break rendering or throw. Escape the title (e.g., via Markup.Escape) before embedding it in markup.
| var outputLines = new List<IRenderable> { new Markup($"[bold {_theme.AccentColor}]{title}[/]") }; | |
| var outputLines = new List<IRenderable> { new Markup($"[bold {_theme.AccentColor}]{Markup.Escape(title)}[/]") }; |
| context.UseSelfTime, | ||
| context.Traversal.MaxWidth, | ||
| context.Traversal.SiblingCutoffPercent, | ||
| CallTreeHelpers.IsRuntimeNoise); |
There was a problem hiding this comment.
This file has using static Asynkron.Profiler.CallTreeHelpers; but still references CallTreeHelpers.IsRuntimeNoise explicitly. Consider removing the using static or switching the call to the unqualified IsRuntimeNoise to avoid redundant style.
| CallTreeHelpers.IsRuntimeNoise); | |
| IsRuntimeNoise); |
Summary
CallTreeTimelineBarRendererProfilerHotnessColorScaleProfilerJitNumberHighlighterProfilerTimelineRowsRendererProfilerCallTreeFormatterandProfilerCallTreeRendererso they focus on call-tree formatting/orchestration instead of mixed helper logicTesting
roslynator fix src/ProfilerCore/Asynkron.Profiler.Core.csprojroslynator fix src/ProfileTool/ProfileTool.csprojroslynator fix tests/Asynkron.Profiler.Tests/Asynkron.Profiler.Tests.csprojquickdup -path . -ext .cs -select 0..20 -min 2 -exclude ".g.,.generated."dotnet format Asynkron.Profiler.slndotnet build Asynkron.Profiler.slndotnet test Asynkron.Profiler.sln