Skip to content

Add frame-level stack transforms to stacks - #10

Merged
lhotari merged 1 commit into
mainfrom
stacks-transforms
Sep 23, 2026
Merged

lhotari merged 1 commit into
mainfrom
stacks-transforms

Conversation

@lhotari

@lhotari lhotari commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Bottom of the 0.5.0 correlator stack (GitHub stack #15; spec: stacks-transforms.md). Based on main, which has the picocli command line from #8.

What changes

  • New stacks transforms that change a kept interval's Java stack, never which intervals are kept: --trim-root, --root-at (+ --root-at-unmatched bucket|keep), --leaf-at, --collapse-leaf (+ --collapse-leaf-label frame|category), --hide, --canonical-names, --thread-frame none|name|pool. Each REGEX option also has a -from FILE form.
  • Fixed order: canonical names → hide → trim-root → root-at → leaf-at → collapse-leaf, then --package-names/--thread-frame as display. Filters still match the untransformed stack. Lines that transform alike merge, so totals never change. With no transform, the output is byte-identical to before.
  • Bundled presets preset:jvm-infra, preset:jvm-wait-machinery, preset:jvm-idle work in every -from option, including --include-from/--exclude-from. stacks --list-presets prints them with their caveat headers (e.g. musl symbolization, WorkerThread::run).
  • --collapsed-input FILE runs any collapsed file (e.g. the converter's CPU view) through the same filters and transforms. It strips _[j]/_[i]/_[0]/_[1] markers and turns slashed class names into dotted ones. Weights keep their unit. Options that need profile data are refused.
  • --summary gains a transforms object: patterns with their sources, linesBefore/linesAfter, framesBefore/framesAfter, and the noApplicationFrame weight and share.
  • New opt-in task testFixtureAcceptance -PjonoffcpuFixtures=DIR. The fixtures are recordings kept outside the repo, so the task is skipped without the property.

Verification

  • ./gradlew spotlessCheck :jonoffcpu-correlator:check -PjonoffcpuFixtures=…/jonoffcpu-plans/fixtures passes.
  • Every row of the spec's reference table is reproduced exactly (lines and weight-averaged depth), CPU via --collapsed-input and off-CPU busy slice via --exclude-from preset:jvm-idle --exclude <bookkeeper take>:
Transform CPU lines / depth Off-CPU busy lines / depth
none 9,839 / 28.9 164 / 23.0
--hide preset:jvm-infra 9,605 / 20.7 157 / 17.6
--trim-root preset:jvm-infra 9,829 / 21.2 164 / 18.4
--collapse-leaf preset:jvm-wait-machinery 7,392 / 24.1 109 / 9.6
trim-root + collapse-leaf 7,381 / 16.4 109 / 5.0
--root-at '^org\.apache\.' 5,231 / 9.8 79 / 8.2
root-at + collapse-leaf 4,925 / 9.6 78 / 4.1
root-at + leaf-at 1,234 / 7.3 66 / 3.6
  • New StackTransformsTest covers the spec's unit cases: merging with intervals and nanos added, --exclude seeing trimmed frames, the [no application frame] bucket, canonical names, pool frames, category labels, collapsed-input normalisation, presets and totals under every transform combined.

Spec deviations to note

  • The spec's example "A;B;C;x.App.m;D with --trim-root '^(A|C)$' → unchanged" contradicts its own rule. A matches, so the root-side run [A] is trimmed, giving B;C;x.App.m;D. The implementation follows the rule, which is also what reproduces the reference table; the test documents both cases.
  • --collapse-leaf-label category keeps the entry frame itself when no category applies (e.g. CompletableFuture$Signaller.block); the spec lists no category for that case.

--include/--exclude decide which intervals a slice keeps, but most of every
Java stack is structure that says nothing about the wait: nine Netty
event-loop frames before the first application frame, and five to ten
lock, park or monitor frames under the one that names the wait. stacks
gains transforms that change what a kept interval's stack looks like:

- --trim-root removes the longest root-side run of matching frames,
  --root-at starts the stack at its root-most match ([no application
  frame] when there is none, or unchanged with --root-at-unmatched keep),
  --leaf-at cuts below the leaf-most match, --collapse-leaf replaces the
  leaf-side wait machinery by the frame that entered it (or a category
  such as [lock] or [monitor] with --collapse-leaf-label category), --hide
  removes frames anywhere, --canonical-names removes generated-class
  addresses, and --thread-frame name|pool starts each line with the thread
  or its pool. Each REGEX option has a -from FILE form.
- They apply in that fixed order after the filters, which still see the
  untransformed stack. Lines that transform alike merge, so no total
  changes, and without a transform the output is byte-identical.
- Every -from option, the filters' included, takes preset:NAME, a bundled
  pattern list: jvm-infra, jvm-wait-machinery and jvm-idle, each with a
  header stating its purpose and caveats. stacks --list-presets prints
  them.
- --collapsed-input FILE applies the same filters and transforms to any
  collapsed file, such as the converter's CPU view, normalising its
  _[j]-style markers and slashed class names; weights keep their unit.
- The --summary file gains a transforms object: each option's patterns
  with their source, lines and weight-averaged depth before and after,
  and the [no application frame] weight and share.

On the Pulsar broker fixtures every row of the spec's reference table is
reproduced exactly, e.g. the busy off-CPU slice goes from 164 lines at a
mean depth of 23.0 to 78 lines at 4.1 with --root-at and --collapse-leaf,
and the CPU view from 9,839 lines at 28.9 to 4,925 at 9.6. The new
testFixtureAcceptance task checks them when given -PjonoffcpuFixtures=DIR.
@lhotari
lhotari changed the base branch from cli-picocli to main September 23, 2026 20:22
@lhotari
lhotari added this pull request to stack #15 September 23, 2026 20:22
@lhotari
lhotari merged commit 8fa9237 into main Sep 23, 2026
@lhotari
lhotari deleted the stacks-transforms branch September 24, 2026 13:12
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.

1 participant