KEMBAR78
Add CPUTimeSample event support to jfrconv by krk · Pull Request #1475 · async-profiler/async-profiler · GitHub
Skip to content

Conversation

krk
Copy link
Contributor

@krk krk commented Sep 11, 2025

This PR adds support for the jdk.CPUTimeSample event, an experimental feature introduced in OpenJDK 25.

This new mode provides a more accurate CPU profiling alternative to the default ExecutionSample in the JDK. It uses thread CPU time, which is particularly useful for analyzing CPU-bound applications.

Motivation and context

CPUTimeSample event support was added to OpenJDK in openjdk/jdk#25654, we can extend the JFR converter to support this new event type.

How has this been tested?

By manually generating flamegraphs and collapsed output.


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@krk krk marked this pull request as draft September 11, 2025 13:38
@krk krk marked this pull request as ready for review September 11, 2025 14:07
Comment on lines 83 to 90
} catch (IllegalArgumentException e) {
f.set(this, CpuSampleType.ExecutionSample);

// Next arg was not a valid value for the enum
if (hasNextArg) {
i--;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

It's a hack. Let's not change behavior of an existing option.
I think --cpu can be a "smart" option that automatically detects what event to use. To force using a specific event, there can be a separate option.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We won't need this with --cpu-time. As for making --cpu a smart option, leaving that out of the current PR.


public enum ThreadStateFilter {
Wall, Cpu, CpuTime, Default
} No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

whitespace

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, didn't we have a linter for this?

Copy link
Contributor

Choose a reason for hiding this comment

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

that's clang-format which is different from clang-tidy

threadStates = getThreadStates(false);
threadStates = getThreadStates(ThreadStateFilter.Wall);
} else {
threadStates = getThreadStates(ThreadStateFilter.Default);
Copy link
Contributor

Choose a reason for hiding this comment

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

let's remove this branch & keep threadStates = null, I don't see why it would be needed
You can do

!(event instanceof ExecutionSample) || threadStates == null || threadStates.get(((ExecutionSample) event).threadState)

Copy link
Member

Choose a reason for hiding this comment

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

Right, threadStates == null was an intentional micro-optimization for the common case when no filtering is needed.
BTW, threadStates == null can also substitute instanceof check: just don't set threadStates for event classes other than ExecutionSample.

Copy link
Contributor

Choose a reason for hiding this comment

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

Currently if you do bad usages such as --alloc --cpu it would exit with a casting exception

The correct solution IMO would adding a validation to prevent such cases

The simplest would be throwing a clear exception when the threadStates aren't null & the sample class is anything other than ExecutionSample

for (Event event; (event = jfr.readEvent(eventClass)) != null; ) {
if (event.time >= startTicks && event.time <= endTicks) {
if (threadStates == null || threadStates.get(((ExecutionSample) event).threadState)) {
if (!(event instanceof ExecutionSample) || threadStates.get(((ExecutionSample) event).threadState)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

yeah this was a risky code :)
If you do --alloc --cpu it would have failed previously

threadStates = getThreadStates(false);
threadStates = getThreadStates(ThreadStateFilter.Wall);
} else {
threadStates = getThreadStates(ThreadStateFilter.Default);
Copy link
Member

Choose a reason for hiding this comment

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

Right, threadStates == null was an intentional micro-optimization for the common case when no filtering is needed.
BTW, threadStates == null can also substitute instanceof check: just don't set threadStates for event classes other than ExecutionSample.

}

protected BitSet getThreadStates(boolean cpu) {
protected BitSet getThreadStates(ThreadStateFilter filter) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm confused about this extra enum and all the related complications.
Will it work if you just add if (args.cpuTime) branch above and leave this method intact?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need to make the distinction between cpu and cpuTime and lack of both thereof, boolean wasn't cutting it.

Since now we are parsing CPUTimeSamples as ExecutionSamples, we need to exclude CPUTimeSamples with getThreadState if mode is cpuTime. Both sample types can be in the jfr at the same time.

Copy link
Member

Choose a reason for hiding this comment

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

No, you don't need to call this function for cpuTime - that's the point.
Your ThreadStateFilter serves as unnecessary bridge to connect two pieces of logic that should not be separate in the first place.

E.g., instead of

if (a) {
    filter = F1;
} else if (b) {
    filter = F2;
} else if (c) {
    filter = F3;
}

if (filter == F1) {
    // do_A
} else if (filter == F2) {
    // do_B
} else if (filter == F3) {
    // do_C
}

you could just write

if (a) {
    // do_A
} else if (b) {
    // do_B
} else if (c) {
    // do_C
}

package one.jfr.event;

public class ExecutionSample extends Event {
public static final int CpuTimeSample = 254;
Copy link
Contributor

Choose a reason for hiding this comment

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

can you add some context behind why this value was selected,
It does look out of place when first checking the code

Copy link
Member

Choose a reason for hiding this comment

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

I added comment

@apangin apangin merged commit f9b7810 into async-profiler:master Sep 30, 2025
23 of 24 checks passed
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.

4 participants