-
Notifications
You must be signed in to change notification settings - Fork 937
add support for user JFR events #1223
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
de97888
to
50aaf13
Compare
src/asprof.cpp
Outdated
return ThreadLocalData::getThreadLocalData(); | ||
} | ||
|
||
DLLEXPORT asprof_user_jfr_key asprof_create_user_jfr_key(const char *name) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type* name
everywhere
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
src/flightRecorder.cpp
Outdated
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
putVar32?
src/jfrMetadata.cpp
Outdated
<< field("string", T_STRING, "String")) | ||
|
||
<< (type("profiler.UserEvent", T_USER_EVENT, "User Event") | ||
<< category("Java Virtual Machine", "Profiling") |
There was a problem hiding this comment.
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")
src/jfrMetadata.cpp
Outdated
<< 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) |
There was a problem hiding this comment.
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.
src/jfrMetadata.cpp
Outdated
<< (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) |
There was a problem hiding this comment.
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.
src/jfrMetadata.h
Outdated
T_WALL_CLOCK_SAMPLE = 118, | ||
T_MALLOC = 119, | ||
T_FREE = 120, | ||
T_USER_EVENT_TYPE = 121, |
There was a problem hiding this comment.
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.
src/userEvents.cpp
Outdated
Mutex UserEvents::_state_lock; | ||
|
||
int UserEvents::registerEvent(const char *event) { | ||
MutexLocker locker(_state_lock); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
src/flightRecorder.cpp
Outdated
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); |
There was a problem hiding this comment.
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.
src/jfrMetadata.cpp
Outdated
<< field("name", T_STRING, "Name")) | ||
|
||
<< (type("profiler.types.UserEventType", T_USER_EVENT_TYPE, "User-Defined Event Type", true) | ||
<< category("Profiler") |
There was a problem hiding this comment.
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.
src/jfrMetadata.cpp
Outdated
<< 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
data.q
?
src/flightRecorder.cpp
Outdated
|
||
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; |
There was a problem hiding this comment.
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.
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`.
Signed-off-by: Andrei Pangin <1749416+apangin@users.noreply.github.com>
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
: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
callsprintValue
with anull
field
, andprintValue
only supports anull
field
with a value of typeByte
.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.