Skip to content

Fix arm64/aarch64 support by aligning async-profiler's clock with the JVM's - #33

Merged
lhotari merged 1 commit into
mainfrom
align-jfr-clock
Sep 27, 2026
Merged

lhotari merged 1 commit into
mainfrom
align-jfr-clock

Conversation

@lhotari

@lhotari lhotari commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Profiling with jfrsync on arm64 lost every async-profiler and jonoffcpu event from time-based analysis:

  • The two chunks of a recording counted ticks from different origins. The JVM's chunk counts JFR ticks from the JVM's start, since HotSpot uses the time stamp counter only on x86. async-profiler's appended chunk counted CLOCK_MONOTONIC from the boot. Each chunk's header was right on its own.
  • JDK 22+ readers convert every chunk with the first chunk's clock (ChunkParser reuses the first chunk's TimeConverter). A whole-recording reader therefore placed async-profiler's events days after the recording: 672,402 s on an OrbStack host.
  • The correlator rejected every sample. SignalJfrExporter reads with RecordingFile and filters on getStartTime(). On an Apache Pulsar broker capture it reported "matched 0, outside the selected JFR window 83376".

This PR moves the async-profiler submodule to jonoffcpu/async-profiler@2e1e0f1, "Align the monotonic clock with the JVM's JFR clock", now the head of the fork's jonoffcpu-dev branch.

Changes in async-profiler

  • Aligns the monotonic clock with the JVM's.
    • When the JVM's JFR frequency is 1 GHz, the profiler measures its offset from the JVM's clock. It takes the shortest of a few brackets around JVM.counterTime(), and subtracts the offset from CLOCK_MONOTONIC.
    • This also applies with clock=monotonic.
    • The synchronization is retried until it succeeds, since the JFR natives may not be registered before the JVM's first recording.
  • Synchronizes the time stamp counter with the JVM only on x86. On aarch64 any frequency was accepted, so cntvct_el0 could be picked while the JVM counted nanoseconds.
  • The Java API's Recording.timestamp() reads the JVM's clock whenever the profiler's clock is aligned with it.
  • jfrsync warns when the clocks can't be aligned, such as clock=monotonic on a JVM that uses the time stamp counter.
  • SignalSample.monotonicTimeNanos stays raw CLOCK_MONOTONIC. Only startTime moves to the JVM's origin, and the correlator's kernel-side checks keep working.

Testing

  • New JfrTests.clockAlignment: reads a jfrsync recording with the JDK's RecordingFile and checks that every event falls within the recording. It has three cases: default, -XX:-UseFastUnorderedTimeStamps, and clock=monotonic.
    • Without the change it fails on linux-aarch64 JDK 25: 222 events land on the next week.
    • With the change it passes on linux-aarch64 JDK 25 and JDK 21, and on linux-x64 JDK 25 under emulation.
  • async-profiler's jfr, span, api, signal, wall and smoke suites pass on linux-aarch64 JDK 25.
  • This repository's native build: ./gradlew -Pversion=0.8.0-SNAPSHOT -PnativeArchitectures=arm64 -PnativeLibcs=all publishToMavenLocal built both bundled libraries (glibc and musl) with the change.
  • Pre-existing failures: under x64 emulation, SpanTests.api and JfrTests.clockSource/tsc fail with and without the change.

With jfrsync=..., async-profiler appends its chunk to the JVM's
recording, and JDK 22+ readers convert every chunk of a file with the
first chunk's clock origin. On aarch64 the JVM counts JFR ticks from
its own start and async-profiler counted them from the boot, so the
correlator's JFR reader placed every SignalSample, CPU and allocation
sample days after the recording and rejected them all as outside the
selected window ("matched 0, outside the selected JFR window").

The submodule moves to the async-profiler commit that aligns the
monotonic clock with the JVM's JFR clock whenever the JVM counts
nanoseconds, synchronizes the time stamp counter with the JVM only on
x86, and warns under jfrsync when the clocks can't be aligned.
SignalSample.monotonicTimeNanos stays raw CLOCK_MONOTONIC for the
kernel-side joins.

Assisted-by: Claude Code (claude-opus-5-5)
@lhotari lhotari changed the title Update async-profiler to align its clock with the JVM's JFR clock Fix async-profiler events missing from the JFR window on aarch64/arm64 by aligning async-profiler's clock with the JVM's Sep 27, 2026
@lhotari lhotari changed the title Fix async-profiler events missing from the JFR window on aarch64/arm64 by aligning async-profiler's clock with the JVM's Fix arm64/aarch64 support by aligning async-profiler's clock with the JVM's Sep 27, 2026
@lhotari
lhotari merged commit 412dc30 into main Sep 27, 2026
10 checks passed
@lhotari
lhotari deleted the align-jfr-clock branch September 27, 2026 10:34
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