Skip to content

Keep native frames intact under --package-names - #5

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

lhotari merged 1 commit into
mainfrom
stacks-native-frames

Conversation

@lhotari

@lhotari lhotari commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

Problem

stacks --package-names abbreviate|drop (#4) promises to change only package-qualified Java frames, but it rewrote most native frames that async-profiler records inside the Java stack:

Full name abbreviate (before) drop (before)
libjvm.so.Unsafe_Park l.so.Unsafe_Park so.Unsafe_Park
libjvm.so.ZWorkers::run l.so.ZWorkers::run so.ZWorkers::run
libnio.so.Java_sun_nio_ch_EPoll_wait l.so.Java_sun_nio_ch_EPoll_wait so.Java_sun_nio_ch_EPoll_wait

The library is the only thing that says whether run belongs to HotSpot, a JNI library or the profiler. The rewrite replaced it with so., and symbols from different libraries could merge. There were three causes:

  • the collapsed key drops each frame's type;
  • the profile tagged every Java-stack frame JAVA;
  • the package pattern accepted libjvm. as a package.

Change

  • Frame type decides. The correlator records which frames of each collapsed key async-profiler typed as Java code (Interpreted, JIT compiled, C1 compiled, Inlined). If stacks that share a key disagree about a frame, that frame counts as native.
  • New frame kind. The profile tags the other frames with a new kind, JFR_NATIVE (wire value 4), and moves to schema_version 2.
    • Schema 1 profiles still read.
    • An older correlator refuses a new profile with Unsupported stack profile schema.
    • merge combines stacks with the same frame names whatever kinds its inputs gave them, and a frame any input calls native stays native.
  • Only JAVA frames are rewritten. A name rule protects schema 1 profiles and backs up the kinds: a name with ::, a shared-library segment (.so., .so.6.), a leading / or [, or a space is never rewritten.
  • Hidden-class lambdas. A hidden class's .0x… suffix (Foo$$Lambda.0x0000000081a16ff8.run) now stays part of the class, so drop removes its package too. Before, these frames kept their full package.
  • Export. export adds a last column, java_stack_kinds (javaStackKinds in JSON Lines), holding java or native per frame. Existing columns are unchanged.
  • Docs. The proto comments, OFFLINE.md and README §5 are updated. README §5 gains a table that includes a native row.

Collapsed keys and frame names are unchanged, so the default rendering still reproduces jonoffcpu-offcpu-stacks.collapsed byte for byte.

Verification

  • ./gradlew spotlessCheck :jonoffcpu-correlator:check passes. The new fixtures cover:
    • the spec's frame table, for both kinds and all three modes;
    • the type-to-kind mapping, including disagreement between stacks;
    • a schema 1 profile round trip and merge;
    • refusal of the new kind in a schema 1 file and of an unknown schema;
    • identical filter totals in every mode;
    • the export column.
  • Pulsar broker profile written before the fix (schema 1, name rule only):
    • Before the fix, abbreviate and drop produced 1,844 so./l.so. frame occurrences. Now there are none.
    • All 264 native frames of the full render keep their names in both modes.
    • The default and full renders are byte-identical to the old correlator's.
    • --exclude summary totals are identical in all three modes.
  • Profile re-correlated from a broker capture (schema 2, with kinds):
    • The export tags 484 frame names native and 1,433 Java, with no conflicts.
    • The default render equals the collapsed file.
    • The old correlator refuses the profile with Unsupported stack profile schema.

stacks --package-names abbreviate|drop rewrote the native frames async-profiler
records inside the Java stack: libjvm.so.Unsafe_Park became l.so.Unsafe_Park or
so.Unsafe_Park, losing the library and merging symbols of different
libraries. The collapsed key drops each frame's type, the profile tagged every
Java-stack frame JAVA, and the package pattern read "libjvm." as a package.

- The correlator records which frames of each collapsed key async-profiler
  typed as Java code (Interpreted, JIT compiled, C1 compiled, Inlined). Stacks
  that share a key must agree for a frame to count as Java.
- The profile tags the others with a new frame kind, JFR_NATIVE (wire value
  4), and moves to schema_version 2. Schema 1 profiles still read; an older
  correlator refuses a new profile with "Unsupported stack profile schema".
  merge combines stacks with the same names whatever kinds its inputs gave
  them, and a frame any input calls native stays native.
- --package-names only rewrites JAVA frames. A name rule protects schema 1
  profiles and backs up the kinds: a name with "::", a shared-library segment
  (".so.", ".so.6."), a leading "/" or "[", or a space is never rewritten.
- A hidden class's ".0x..." suffix (Foo$$Lambda.0x0000000081a16ff8.run) now
  stays part of the class, so drop removes its package too.
- export adds a last column, java_stack_kinds (javaStackKinds in JSON Lines),
  with java or native per frame. Existing columns are unchanged.

Collapsed keys and frame names are unchanged, so the default rendering still
reproduces jonoffcpu-offcpu-stacks.collapsed byte for byte. On a Pulsar broker
profile, the old code produced 1,844 so./l.so. frame occurrences under
abbreviate and drop; now all 264 native frames keep their names, and filter
totals are identical in all three modes.
@lhotari
lhotari added this pull request to stack #7 September 23, 2026 16:25
@lhotari
lhotari merged commit 5f3cb64 into main Sep 23, 2026
5 checks passed
@lhotari
lhotari deleted the stacks-native-frames 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