Skip to content

Profiler: F# match code keeps its file, and the FsOrderLedger burn loop is sampled where it runs - #409

Merged
MelbourneDeveloper merged 1 commit into
mainfrom
fix/408-profiler-hidden-lines
Oct 20, 2026
Merged

MelbourneDeveloper merged 1 commit into
mainfrom
fix/408-profiler-hidden-lines

Conversation

@MelbourneDeveloper

@MelbourneDeveloper MelbourneDeveloper commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

TLDR

Fixes two defects that made the F# profiler suites flaky (#408). An F# CPU sample in a match's decision tree now keeps its method's file, and FsOrderLedger's burn loop is now sampled where it actually runs.

Details

  • lspkit e6e4172 → 3cd5896 (Profiler: a CPU sample in hidden F# match code keeps its method's file lspkit#19, fixes lspkit#18):
    • The .NET engine's Symbolizer gives a frame on a hidden sequence point its method's nearest visible sequence point, or the method's first, read from the PDB through SourceLocator. A method with no visible point, such as a compiler-generated closure, stays unlocated.
    • Before this, such frames lost their file, so a function split into a located hot function and an unlocated twin. That was 61% of FsPricingEngine's Csv.parseLine, and it failed the Windows profiler chunk on Razor review fixes (#399–#407) and Blazor and Razor docs, film and post #406 (run 39364686929).
    • lspkit's spec ([PROF-DOTNET-CPU-SYMBOLS]) and plan state the rule.
    • Its fixture HotLeakAppFs gains Matching.fs and a classify command. The Program.fs lines SharpLsp's profiler-fsharp-e2e pins (up to 97) are unchanged.
  • FsOrderLedger Gate.fs and Program.fs: the burn tick no longer takes the process-wide gate.
    • It only reads (priceAll snapshots under the ledger's own lock).
    • EventPipe's sampler stops a thread at its next safe point, so the uncontended lock call took 98% of the burn thread's samples. dotnet-counters showed 0 lock contentions per second.
    • That left priceAll at the 2% hot-function threshold, and "the burn loop is hot" failed two runs in three on macOS.
    • Line counts are unchanged, so PRICE_ALL (Ledger.fs 63–70) still holds.
  • docs/plans/PROFILER-PLAN.md: both defects join the F# real-program list.

How Do The Automated Tests Prove It Works?

  • lspkit's cpu_samples_in_fsharp_match_code_keep_their_file: before the fix, Matching.classify came back twice, with 948 samples located and 179 unlocated. After the fix it passes unchanged. lspkit CI is green on the PR (run 39371497743) and on main (run 39371510065).
  • FsPricingEngine through this branch's published engine (run parse): Csv.parseLine went from 1592 located and 2507 unlocated samples to 3271 located and none unlocated.
  • "the burn loop is the hotspot while memory tracks the same program" (profiler-fsharp-soak-cpu-e2e):
    • Unchanged test. It failed two of three local runs before the fixture fix, both on "the burn loop is hot".
    • It passes three of three after it.
    • A direct engine trace puts priceAll on 100% of the burn thread's samples, up from 1.6%.
  • The full profiler VS Code chunk passes locally on macOS against this branch: 323/323. This PR's pipeline re-checks it on Linux and Windows, where the F# CPU and soak suites assert each named function's file.

Closes #408.

@MelbourneDeveloper
MelbourneDeveloper enabled auto-merge (squash) October 20, 2026 05:05
@MelbourneDeveloper
MelbourneDeveloper merged commit 2b64f88 into main Oct 20, 2026
110 of 112 checks passed
@MelbourneDeveloper
MelbourneDeveloper deleted the fix/408-profiler-hidden-lines branch October 20, 2026 05:38
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.

Profiler: F# samples in match code lose their source file, splitting hot functions

1 participant