Skip to content

Refactor name formatting and speedscope parsing - #54

Closed
rogeralsing wants to merge 1 commit into
mainfrom
codex/20260321-003201-69bde6ff
Closed

rogeralsing wants to merge 1 commit into
mainfrom
codex/20260321-003201-69bde6ff

Conversation

@rogeralsing

Copy link
Copy Markdown
Contributor

Summary

  • Split NameFormatter into focused helpers for compiler-generated method names and type-display normalization.
  • Split SpeedscopeParser into a thin facade plus document reading, unit resolution, aggregation state, and separate evented/sampled processors.
  • Added characterization tests for formatter behavior and for speedscope invalid-shape, timing, unit-conversion, unknown-frame, and mixed-profile label handling.
  • Updated Asynkron.Profiler.Core.csproj so the shared formatter helpers are compiled into the core assembly.

Testing

  • dotnet format Asynkron.Profiler.sln --verify-no-changes
  • dotnet build Asynkron.Profiler.sln -warnaserror --disable-build-servers
  • dotnet test Asynkron.Profiler.sln --disable-build-servers
  • quickdup -path src -ext .cs -select 0..20 -min 2 -exclude ".g.,.generated."

Notes

  • roslynator fix Asynkron.Profiler.sln was attempted as part of the pre-PR flow, but the current roslynator.dotnet.cli release is incompatible with this repo's .NET 10 preview SDK and fails while loading the solution. The repo was therefore validated with dotnet format plus warning-as-error build/test instead.

Copilot AI review requested due to automatic review settings March 21, 2026 00:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors the profiler’s name formatting and speedscope JSON parsing into smaller, focused components, while adding characterization tests to lock in expected behavior across invalid inputs and unit/timing scenarios.

Changes:

  • Split name formatting into CompilerGeneratedMethodDisplayFormatter and TypeDisplayNameFormatter, with NameFormatter delegating to them.
  • Split speedscope parsing into a thin SpeedscopeParser facade plus document reading, unit resolution, aggregation state, and separate processors for evented vs sampled profiles.
  • Added/expanded tests covering invalid document shapes, missing samples, timing/self-time bounds, unit conversions, unknown frames, and mixed profile labeling.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/Asynkron.Profiler.Tests/SpeedscopeParserTests.cs Adds characterization coverage for invalid shapes, timing/self-time, unit conversion, unknown frames, and mixed profiles.
tests/Asynkron.Profiler.Tests/NameFormatterTests.cs Extends test matrix for compiler-generated lambdas and nested-type display normalization.
src/ProfilerCore/Asynkron.Profiler.Core.csproj Ensures the new formatter helpers are compiled into the core assembly.
src/ProfileTool/Rendering/NameFormatter.cs Delegates compiler-generated method and type formatting to new helper classes.
src/ProfileTool/Rendering/CompilerGeneratedMethodDisplayFormatter.cs New helper for state machine and lambda-style compiler-generated method name normalization.
src/ProfileTool/Rendering/TypeDisplayNameFormatter.cs New helper for stripping namespaces, normalizing generics/arrays, and nested type display.
src/ProfileTool/Cpu/SpeedscopeParser.cs Refactored into a facade calling reader/unit resolver/processors/state.
src/ProfileTool/Cpu/SpeedscopeDocumentReader.cs New reader for validating/reading document shape and frames/weights.
src/ProfileTool/Cpu/SpeedscopeProfileUnitResolver.cs New unit parsing + time scaling logic.
src/ProfileTool/Cpu/SpeedscopeAggregationState.cs New shared aggregation state and result construction.
src/ProfileTool/Cpu/SpeedscopeEventedProfileProcessor.cs New processor for evented profiles (timing + self-time).
src/ProfileTool/Cpu/SpeedscopeSampledProfileProcessor.cs New processor for sampled profiles (weights + call/time accumulation).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +95 to +99
child.Calls += callWeight;
state.AddFrameTime(frameIdx, timeWeight);
state.AddFrameCalls(frameIdx, callWeight);
return child;
}

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In sampled processing, child.Calls += callWeight bypasses CallTreeNode.IncrementCalls() clamping and can overflow int (yielding negative/incorrect call counts) for large profiles. Consider clamping when adding callWeight (and similarly for per-frame call aggregation) so call counts never exceed int.MaxValue.

Suggested change
child.Calls += callWeight;
state.AddFrameTime(frameIdx, timeWeight);
state.AddFrameCalls(frameIdx, callWeight);
return child;
}
var effectiveCallWeight = ClampAndIncrementCalls(child, callWeight);
state.AddFrameTime(frameIdx, timeWeight);
state.AddFrameCalls(frameIdx, effectiveCallWeight);
return child;
}
private static int ClampAndIncrementCalls(CallTreeNode node, int callWeight)
{
if (callWeight <= 0)
{
return 0;
}
var currentCalls = node.Calls;
if (currentCalls >= int.MaxValue)
{
// Already at or above the maximum representable call count.
node.Calls = int.MaxValue;
return 0;
}
var maxIncrement = int.MaxValue - currentCalls;
var effectiveIncrement = callWeight > maxIncrement ? maxIncrement : callWeight;
node.Calls = currentCalls + effectiveIncrement;
return effectiveIncrement;
}

Copilot uses AI. Check for mistakes.
public void AddFrameCalls(int frameIdx, int calls)
{
_frameCounts.TryGetValue(frameIdx, out var currentCalls);
_frameCounts[frameIdx] = currentCalls + calls;

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AddFrameCalls adds into an int dictionary with currentCalls + calls and can overflow for large inputs (producing negative call counts and breaking ordering/labels). Consider clamping to int.MaxValue (or switching the counters to long if higher fidelity is needed) to match CallTreeNode.IncrementCalls() overflow handling.

Suggested change
_frameCounts[frameIdx] = currentCalls + calls;
long newValue = (long)currentCalls + calls;
if (newValue > int.MaxValue)
{
newValue = int.MaxValue;
}
_frameCounts[frameIdx] = (int)newValue;

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +23
var timeout = TimeSpan.FromMilliseconds(100);
var normalized = Regex.Replace(
name,
@"\b(?:[A-Za-z_][A-Za-z0-9_]*\.)+(?<type>[A-Za-z_][A-Za-z0-9_]*)",
"${type}",
RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture,
timeout);

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This formatter uses several Regex.Replace calls with inline patterns on every invocation. Elsewhere in the repo regex patterns are typically implemented via [GeneratedRegex] (or otherwise cached) to avoid repeated regex parsing/cache churn; consider switching these patterns to generated/cached regexes for consistency and performance.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants