profiles: improve JFR export example - #8349
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8349 +/- ##
============================================
- Coverage 78.77% 78.76% -0.02%
Complexity 8579 8579
============================================
Files 1009 1009
Lines 28993 28993
Branches 3599 3599
============================================
- Hits 22839 22836 -3
- Misses 5311 5312 +1
- Partials 843 845 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@open-telemetry/profiling-maintainers This PR is somewhat interesting, as one of the early examples of an end-to-end interop where the sender and receiver are written by different people working more or less in isolation from one another. It exposes some rough edges that may represent opportunities/requirements for spec changes to make it a smoother process.
|
zeitlinger
left a comment
There was a problem hiding this comment.
Nice metadata additions. Two suggestions:
Hoist hot-loop dictionary lookups. In JfrExecutionSampleEventConverter.accept() the "thread.name" key index and KeyValueAndUnitData are rebuilt for every sample event. Same in JfrLocationDataCompositor.frameToLocation() for "profile.frame.type"/"jvm" per frame. The dict dedupes so output is correct, but each call still allocates a string + KeyValueAndUnitData. Since the key/value pair is constant per converter, compute it once (e.g. in the constructor or lazily cached) and reuse the int index.
For the thread sample, only threadName/threadNameData vary — pre-compute the "thread.name" key index once.
Null sampledThread. recordedEvent.getValue("sampledThread") can be null for some ExecutionSample variants. A null guard (skip or fall back to "unknown") would harden the converter against truncated/synthetic events.
LGTM otherwise — the ValueTypeData fix and frame-type attribute look right.
9032cb5 to
5e09b77
Compare
|
Hi Gregor Thanks for taking a look.
Right. There are two subtly different cases here, where one KV is entirely constant and the other is dependent on the event's thread value. That's partly an artifact of an over-simplification I made to assume all frames are "jvm" type. If native code is involved then the value there also becomes event-dependent. Nevertheless there is still a performance argument for caching, since the number of thread names / thread types is considerably smaller than the number of events, but it's a classic space/time tradeoff to add a HashMap for these and at this early stage I'm lacking data to support it. The thread name case in particularly is concerning, as it's an unbounded key space and thus unbounded cache size. I'm going with 'the cost of churning a short lived key object is tolerable', especially since the frame's nameFrom computation is a worse example of the same issue and will likely dominate either of the others.
Can it? The asserts in OpenJDK's jfrThreadSampling.cpp seemed to indicate it's always set, but ok, no real downside to hedging anyhow. I think the main takeaway here is the testing thus far is just a handful of old JFR files I had lying around and they don't contain some obvious alternative cases - the frame name code will break on non-java frames I think, but there aren't any in the test set... The JFR event APIs make it next to impossible to mock JFR data cleanly, which is a colossal pain for testing. OpenJDK itself seems to do it by having a curated collection of (hand crafted?) JFR files in version control instead. |
Pull request dashboard statusWaiting on the author · refreshed 2026-08-21 20:19 UTC Respond to 5 review items (e.g. link a commit, explain why not, ask a follow-up): Status above doesn't look right?
|
jack-berg
left a comment
There was a problem hiding this comment.
Hey sorry it took me so long to get involved in this.
I have a better system now for cutting through the endless noise of github. If you see I miss something like this, please ping me. 🙂
| linkTable.putIfAbsent(LinkData.create("", "")); | ||
| // TODO this is, strictly speaking, probably not profile spec compliant at present. | ||
| // The spec uses "" but the Id encoders don't like that. The alpha spec may need revision... | ||
| linkTable.putIfAbsent(LinkData.create(TraceId.getInvalid(), SpanId.getInvalid())); |
There was a problem hiding this comment.
PR title advertises an example-only change, but this modifies the shared SDK dictionary null-element semantics. Worth calling out in the PR description.
| linkTable.putIfAbsent(LinkData.create("", "")); | ||
| // TODO this is, strictly speaking, probably not profile spec compliant at present. | ||
| // The spec uses "" but the Id encoders don't like that. The alpha spec may need revision... | ||
| linkTable.putIfAbsent(LinkData.create(TraceId.getInvalid(), SpanId.getInvalid())); |
There was a problem hiding this comment.
Nothing in this PR appears to reference linkTable[0]. What specifically breaks with "", and where? Ideally the fix belongs in the id encoder (accept "" as invalid) rather than inflating the placeholder here.
| // TODO this is, strictly speaking, probably not profile spec compliant at present. | ||
| // The spec uses "" but the Id encoders don't like that. The alpha spec may need revision... |
There was a problem hiding this comment.
Is there an upstream spec issue tracking this? If so, link it from the TODO; if not, worth filing one so the divergence has a paper trail and can be reverted once resolved.
| } | ||
|
|
||
| String threadName = recordedThread.getJavaName(); | ||
| int threadNameIndex = profilesDictionaryCompositor.putIfAbsent("thread.name"); |
There was a problem hiding this comment.
Narrowing zeitlinger's suggestion: even without a per-thread cache, the "thread.name" key index itself is constant. Compute once in the constructor and store as an int field. No unbounded cache concern, and it drops one HashMap lookup + string allocation per event.
|
|
||
| LocationData locationData = | ||
| LocationData.create(0, 0, List.of(lineData), Collections.emptyList()); | ||
| LocationData.create(0, 0, List.of(lineData), List.of(typeAttributeIndex)); |
There was a problem hiding this comment.
If the frame-type KV is hoisted to a field, hoist List.of(typeAttributeIndex) alongside it. Same applies to List.of(attribIndex) in JfrExecutionSampleEventConverter once the attribute index is stable per thread.
Add metadata to the OTLP message so as to make it more interpretable by receiving backends.