KEMBAR78
add support for user JFR events by arielb1 · Pull Request #1223 · async-profiler/async-profiler · GitHub
Skip to content

Conversation

arielb1
Copy link
Collaborator

@arielb1 arielb1 commented Apr 8, 2025

This adds an (unstable) profiler.UserEvent JFR event, that can be emitted by user native code and contain custom data.

Using T_STRING for the data instead of T_BYTE/F_ARRAY due to a bug in jfr print:

java.lang.NullPointerException
        at jdk.jfr/jdk.jfr.internal.tool.PrettyWriter.printValue(PrettyWriter.java:357)
        at jdk.jfr/jdk.jfr.internal.tool.PrettyWriter.printArray(PrettyWriter.java:281)
        at jdk.jfr/jdk.jfr.internal.tool.PrettyWriter.printValue(PrettyWriter.java:325)
        at jdk.jfr/jdk.jfr.internal.tool.PrettyWriter.printFieldValue(PrettyWriter.java:273)
        at jdk.jfr/jdk.jfr.internal.tool.PrettyWriter.print(PrettyWriter.java:213)
        at jdk.jfr/jdk.jfr.internal.tool.PrettyWriter.print(PrettyWriter.java:76)
        at jdk.jfr/jdk.jfr.internal.tool.EventPrintWriter.print(EventPrintWriter.java:79)
        at jdk.jfr/jdk.jfr.internal.tool.Print.execute(Print.java:165)
        at jdk.jfr/jdk.jfr.internal.tool.Main.main(Main.java:84)

The relevant code is at
https://github.com/openjdk/jdk/blob/965e330cb4a972fd96ff04e149029d1ee0561940/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/PrettyWriter.java#L357

printArray calls printValue with a null field, and printValue only supports a null field with a value of type Byte.

Description

Related issues

Motivation and context

How has this been tested?


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

@arielb1 arielb1 force-pushed the app-jfr branch 2 times, most recently from de97888 to 50aaf13 Compare April 8, 2025 15:29
src/asprof.cpp Outdated
return ThreadLocalData::getThreadLocalData();
}

DLLEXPORT asprof_user_jfr_key asprof_create_user_jfr_key(const char *name) {
Copy link
Member

Choose a reason for hiding this comment

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

type* name everywhere

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

src/asprof.cpp Outdated
return UserEvents::registerEvent(name);
}

DLLEXPORT int asprof_emit_user_jfr(asprof_user_jfr_key key, const uint8_t *data, size_t len) {
Copy link
Member

Choose a reason for hiding this comment

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

Will it ever be needed to record events with stack traces?
I initially thought event will have a bitmask of standard fields that should be recorded along with user-provided data. These fields may include threadId and stackTrace.

(It's more like a question for discussion, not necessarily for this PR).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Adding a flags parameter in case someone wants to add flags for this.

The thread id is currently included in all cases, I don't see a reason not to include thread id / timestamp always.

src/asprof.h Outdated
typedef asprof_thread_local_data* (*asprof_get_thread_local_data_t)(void);


typedef int asprof_user_jfr_key;
Copy link
Member

Choose a reason for hiding this comment

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

Maybe unsigned int?

Also, I'm not fully happy with "user" in this and other identifiers in this API (who is user?)
WDYT about asprof_event_tag? Alternative suggestions are welcome.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It can be -1 in case of failure, like in "normal" C functions.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

user = the user of async-profiler (as opposed to the developer of async profiler = you).

writePackages(buf, &lookup);
writeSymbols(buf, &lookup);
writeLogLevels(buf);
writeUserEventTypes(buf);
Copy link
Member

Choose a reason for hiding this comment

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

Write them before log levels. There is no difference for parsing, but it will be easier to spot visually if JFR file is complete or truncated, since valid JFR will always end with predefined recognizable list of log levels.

writePoolHeader(buf, T_USER_EVENT_TYPE, events.size());
for (std::map<u32, const char*>::const_iterator it = events.begin(); it != events.end(); ++it) {
flushIfNeeded(buf, RECORDING_BUFFER_LIMIT - MAX_STRING_LENGTH);
buf->putVar64(it->first);
Copy link
Member

Choose a reason for hiding this comment

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

putVar32?

<< field("string", T_STRING, "String"))

<< (type("profiler.UserEvent", T_USER_EVENT, "User Event")
<< category("Java Virtual Machine", "Profiling")
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 not really about Java Virtual Machine. I'd put it in category("Profiler")

<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
<< field("address", T_LONG, "Address", F_ADDRESS))

<< (type("profiler.UserEventType", T_USER_EVENT_TYPE, "User Event Type", true)
Copy link
Member

Choose a reason for hiding this comment

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

Move it up next to other data types and give it a name in "profiler.types." namespace.

<< (type("profiler.UserEvent", T_USER_EVENT, "User Event")
<< category("Java Virtual Machine", "Profiling")
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
<< field("sampledThread", T_THREAD, "Thread", F_CPOOL)
Copy link
Member

Choose a reason for hiding this comment

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

"eventThread" for consistency with other non-sampled events.

T_WALL_CLOCK_SAMPLE = 118,
T_MALLOC = 119,
T_FREE = 120,
T_USER_EVENT_TYPE = 121,
Copy link
Member

@apangin apangin Apr 9, 2025

Choose a reason for hiding this comment

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

If ID is between 100 and 199, a class will inherit jdk.jfr.Event, which is not correct. Data types should have IDs below 100.

Mutex UserEvents::_state_lock;

int UserEvents::registerEvent(const char *event) {
MutexLocker locker(_state_lock);
Copy link
Member

Choose a reason for hiding this comment

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

Dictionary is thread-safe.
Note, however, that numeric keys that Dictionary issues are not sequential, which is probably fine for your purpose. But if for some reason you prefer sequential keys, use std::unordered_map or something.

src/asprof.cpp Outdated
return UserEvents::registerEvent(name);
}

DLLEXPORT int asprof_emit_user_jfr(asprof_user_jfr_key type, const uint8_t* data, size_t len, uint64_t _flags) {
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 not sure if flags should be included here or in create_key function, since all events of the same type must have the same structure.
Let's just go without flags now and add them when needed.

for (std::map<u32, const char*>::const_iterator it = symbols.begin(); it != symbols.end(); ++it) {
flushIfNeeded(buf, RECORDING_BUFFER_LIMIT - MAX_STRING_LENGTH);
buf->putVar64(it->first | _base_id);
buf->putVar32(it->first | _base_id);
Copy link
Member

Choose a reason for hiding this comment

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

You've changed it in a wrong place.

<< field("name", T_STRING, "Name"))

<< (type("profiler.types.UserEventType", T_USER_EVENT_TYPE, "User-Defined Event Type", true)
<< category("Profiler")
Copy link
Member

Choose a reason for hiding this comment

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

Category is used for events - it reflects a tree node where JMC groups events of this type.
Category does not make sense for types.

<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
<< field("type", T_USER_EVENT_TYPE, "User Event Type", F_CPOOL)
// Using T_STRING with a Latin-1 string to encode raw bytes as user data.q
Copy link
Member

Choose a reason for hiding this comment

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

data.q?


void putByteString(const uint8_t* v, size_t len) {
put8(5); // STRING_ENCODING_LATIN1_BYTE_ARRAY
u32 truncated_len = len < MAX_STRING_LENGTH ? len : MAX_STRING_LENGTH;
Copy link
Member

Choose a reason for hiding this comment

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

All events written during a profiling session (except for prologue events) are rather small; async-profiler currently reserves 4096 bytes at the end of a buffer to accommodate such events. Either reserved size should be revised, or maximum length of a user event needs to be decreased. Alternatively, recordUserEvent should do an extra flushIfNeeded call in the beginning, although it's less preferable to me.

Ariel Ben-Yehuda added 3 commits April 15, 2025 10:59
This adds an (unstable) `profiler.UserEvent` JFR event, that can be emitted
by user native code and contain custom data.

Using T_STRING for the data instead of T_BYTE/F_ARRAY due to a bug in `jfr print`:

```
java.lang.NullPointerException
        at jdk.jfr/jdk.jfr.internal.tool.PrettyWriter.printValue(PrettyWriter.java:357)
        at jdk.jfr/jdk.jfr.internal.tool.PrettyWriter.printArray(PrettyWriter.java:281)
        at jdk.jfr/jdk.jfr.internal.tool.PrettyWriter.printValue(PrettyWriter.java:325)
        at jdk.jfr/jdk.jfr.internal.tool.PrettyWriter.printFieldValue(PrettyWriter.java:273)
        at jdk.jfr/jdk.jfr.internal.tool.PrettyWriter.print(PrettyWriter.java:213)
        at jdk.jfr/jdk.jfr.internal.tool.PrettyWriter.print(PrettyWriter.java:76)
        at jdk.jfr/jdk.jfr.internal.tool.EventPrintWriter.print(EventPrintWriter.java:79)
        at jdk.jfr/jdk.jfr.internal.tool.Print.execute(Print.java:165)
        at jdk.jfr/jdk.jfr.internal.tool.Main.main(Main.java:84)
```
The relevant code is at
https://github.com/openjdk/jdk/blob/965e330cb4a972fd96ff04e149029d1ee0561940/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/PrettyWriter.java#L357

`printArray` calls `printValue` with a `null` `field`, and `printValue` only
supports a `null` `field` with a value of type `Byte`.
Ariel Ben-Yehuda and others added 3 commits April 15, 2025 11:10
Signed-off-by: Andrei Pangin <1749416+apangin@users.noreply.github.com>
@apangin apangin merged commit 164eac4 into async-profiler:master Apr 15, 2025
7 of 10 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.

2 participants