Skip to content

add TaskBlock events for blocking intervals#570

Open
kaahos wants to merge 48 commits into
mainfrom
paul.fournillon/wallclock-taskblock
Open

add TaskBlock events for blocking intervals#570
kaahos wants to merge 48 commits into
mainfrom
paul.fournillon/wallclock-taskblock

Conversation

@kaahos

@kaahos kaahos commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?:

Adds datadog.TaskBlock JFR events for blocking intervals such as LockSupport.park, Object.wait, monitor contention, including recording APIs used by dd-trace-java for instrumented blocking operations such as Thread.sleep.

Motivation:

This builds on paul.fournillon/wallclock-suppression (#560) by preserving visibility into blocked spans as explicit duration events.

Additional Notes:

This PR does not add the dd-trace-java instrumentation itself; Thread.sleep emission depends on that side calling the new profiler APIs. Monitor callback support is HotSpot-specific, and virtual-thread carrier attribution is avoided.

How to test the change?:

  • ./.claude/commands/build-and-summarize :ddprof-test:testDebug -Ptests="*.wallclock.*TaskBlockTest"
  • ./.claude/commands/build-and-summarize :ddprof-lib:gtestDebug

For Datadog employees:

  • If this PR touches code that signs or publishes builds or packages, or handles
    credentials of any kind, I've requested a review from @DataDog/security-design-and-guidance.
  • This PR doesn't touch any of that.
  • JIRA: [JIRA-14354]

Unsure? Have a question? Request a review!

@datadog-datadog-prod-us1-2

datadog-datadog-prod-us1-2 Bot commented Jun 1, 2026

Copy link
Copy Markdown

Pipelines

Fix all issues with BitsAI

⚠️ Warnings

🚦 14 Pipeline jobs failed

DataDog/java-profiler | benchmarks-candidate-aarch64: [alloc]   View in Datadog   GitLab

DataDog/java-profiler | benchmarks-candidate-aarch64: [cpu,wall,alloc,memleak]   View in Datadog   GitLab

DataDog/java-profiler | benchmarks-candidate-aarch64: [cpu,wall]   View in Datadog   GitLab

View all 14 failed jobs.

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 93362c8 | Docs | Datadog PR Page | Give us feedback!

@dd-octo-sts

dd-octo-sts Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

CI Test Results

Run: #27828360029 | Commit: 5438339 | Duration: 13m 21s (longest job)

All 32 test jobs passed

Status Overview

JDK glibc-aarch64/debug glibc-amd64/debug musl-aarch64/debug musl-amd64/debug
8 - - -
8-ibm - - -
8-j9 - -
8-librca - -
8-orcl - - -
11 - - -
11-j9 - -
11-librca - -
17 - -
17-graal - -
17-j9 - -
17-librca - -
21 - -
21-graal - -
21-librca - -
25 - -
25-graal - -
25-librca - -

Legend: ✅ passed | ❌ failed | ⚪ skipped | 🚫 cancelled

Summary: Total: 32 | Passed: 32 | Failed: 0


Updated: 2026-06-19 13:40:05 UTC

@kaahos kaahos force-pushed the paul.fournillon/wallclock-taskblock branch from bcf3c2b to a46d368 Compare June 2, 2026 08:24
@kaahos kaahos force-pushed the paul.fournillon/wallclock-taskblock branch 3 times, most recently from 3f2f71a to d1e3210 Compare June 3, 2026 13:00
@kaahos

kaahos commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

@copilot review this PR

Copilot AI commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

@copilot review this PR

Reviewed and found a flaky assertion in NativeSocketTaskBlockTest: it required zero TaskBlock events globally, which can fail when an untraced socket op emits one. Fixed in d21e493 by asserting that no TaskBlock is emitted for the traced span specifically, and re-ran :ddprof-test:testDebug -Ptests="*.wallclock.*TaskBlockTest" successfully.

Copilot finished work on behalf of kaahos June 3, 2026 13:27
@kaahos kaahos marked this pull request as ready for review June 3, 2026 13:40
@kaahos kaahos requested a review from a team as a code owner June 3, 2026 13:40

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d21e4936d2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ddprof-lib/src/main/cpp/wallClock.cpp Outdated
@kaahos kaahos force-pushed the paul.fournillon/wallclock-taskblock branch 2 times, most recently from 2101007 to a94677a Compare June 3, 2026 14:05
@kaahos kaahos force-pushed the paul.fournillon/wallclock-taskblock branch from a94677a to 681582c Compare June 3, 2026 14:38

@jbachorik jbachorik left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can you add more test coverage?

Tier 1

directly threatens the design's core invariant (no test exists, and the race is real, not theoretical):

  1. Concurrent enter/exit/snapshot on the same ThreadFilter slot. enterBlockedRun, snapshotAndExitBlockedRun, and the signal-handler's markSampledThisRun are all lock-free on shared slot state, but every test drives them single-threaded. The whole feature is built on a signal handler racing the application thread — and that interleaving is never tested. This is the headline gap.

  2. Snapshot consistency under concurrent mutation. snapshotBlockRun does several independent relaxed loads (sampled_this_run, owner, anchor_sample_id, suppressed_sample_count). No test mutates state mid-snapshot to confirm the result is self-consistent (e.g. anchored=false but non-zero suppressed count).

  3. TaskBlockQueue concurrent push/pop. It's an MPSC-style sequence-cell queue (the hard-to-get-right kind), tested only single-threaded. No multi-producer race, no cell-reuse-staleness test.
    ### Tier 2 error/early-exit paths that are written but never executed by a test:

  4. NativeBlockScope constructor gates. Of its ~6 early-exit gates (!taskBlockAsyncActive, !filter.enabled, current==nullptr, non-Java thread, slot_id<0, enterBlockedRun==0), only the spanId!=0 skip is tested. The others are silent - a regression that flips one would pass CI.

  5. Null orig* function pointer → ENOSYS. The interposer handles it; no test injects null.

  6. getsockopt failure with errno ≠ ENOTSOCK - uncovered, and it's the one real perf cliff.

Tier 3

lifecycle, worth one test each:

  1. Profiler restart with changed args (wallprecheck=true then false; ASGCT↔JVMTI engine switch with precheck active) — the static-singleton counters and drain-thread lifecycle across restart are untested. Note the prior memory: restart-in-prod isn't a supported mode, which lowers this priority.
  2. JVMTI-unavailable / J9 fallback — precheck wiring on non-ASGCT engines is asserted by the agents to silently no-op; worth a test to confirm it degrades cleanly rather than leaving weight slots un-restored.

@kaahos kaahos force-pushed the paul.fournillon/wallclock-taskblock branch from 98cb129 to c278669 Compare June 18, 2026 15:57
Comment thread ddprof-lib/src/main/cpp/javaApi.cpp Outdated
Comment thread ddprof-lib/src/main/cpp/taskBlockRecorder.h
});
}

ssize_t NativeSocketInterposer::write_hook(int fd, const void* buf, size_t len) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Sphinx Review — MEDIUM] read()/write() GOT hooks intercept ALL process read/write calls (files, pipes, tty), not just sockets, while the interposer is active. Each call performs an fd classification lookup and, on cache miss, a getsockopt syscall on the I/O hot path.

Suggestion: Consider a cheaper first-line filter before classification (e.g. skip well-known non-socket fds 0/1/2, or only treat fds that have appeared in connect/accept/socket as candidates), or document/measure the overhead of intercepting all read/write. Confirm the getsockopt-on-miss cost is acceptable for file-heavy workloads.

return errno == ENOTSOCK ? FD_TYPE_NON_SOCKET : 0;
};

if (static_cast<size_t>(fd) >= static_cast<size_t>(FD_TYPE_CACHE_SIZE)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Sphinx Review — MEDIUM] fd values >= FD_TYPE_CACHE_SIZE (65536) are never cached; every intercepted I/O call on such an fd issues a fresh getsockopt syscall.

Suggestion: Either size the cache to the process RLIMIT_NOFILE (or use a hashed cache) or document that the syscall fast-path overhead applies to fds above the cache size. High-fd-count servers are exactly the workloads where this matters.


class TaskBlockQueue {
private:
static const size_t kCapacity = 4096;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Sphinx Review — LOW] Fixed 4096-entry async queue silently drops task-block events under contention bursts between 1ms drains.

Suggestion: Confirm 4096 is adequate for expected monitor-contention burst rates; the drop counter exists for observability. Consider documenting the drain interval/capacity relationship.

event._ctx = ctx;
if (call_trace_id != 0 || correlation_id != 0 ||
observed_state != OSThreadState::UNKNOWN) {
setTaskBlockStackReference(event, call_trace_id, correlation_id,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Sphinx Review — LOW] On the explicit-stack-reference deferred path the caller-supplied observedBlockingState is recorded verbatim with no validation; if the caller's captured state is stale relative to the target thread, the emitted event records an incorrect blocking state.

Suggestion: Document the contract that the caller is responsible for capturing observedBlockingState atomically with the block interval; the deferred path cannot validate it. No code change required if the contract is acceptable.

Comment thread ddprof-lib/src/main/cpp/taskBlockRecorder.h Outdated
* Intended for background drain threads that record events on behalf of a different (sleeping)
* thread; avoids reading OTEP TLS from the calling thread.
*/
public void recordTaskBlockFromContext(int tid, long startTicks, long endTicks,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Sphinx Review — LOW] recordTaskBlockFromContext (and recordTaskBlockWithContext) discard the boolean emit/skip result computed by the native recorder, so callers cannot detect dropped events.

Suggestion: Consider returning the boolean (or surfacing a drop counter) so the trace integration can observe whether the deferred record actually emitted.

return call(fn);
}

NativeBlockScope block(kind, fd);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Sphinx Review — LOW] When the interposer and sampler are both active, the TaskBlock duration measured by NativeBlockScope encloses the sampler's own work (probe, getpeername, LRU, recordSample), inflating the reported blocked interval, and the same syscall produces two distinct event types with no documented coordination.

Suggestion: Either snapshot the syscall boundary ticks tightly around the raw fn() call and pass them into NativeBlockScope::finish(), or document that the combined operation deliberately emits both event types and accept the bounded duration overhead. Add a test exercising both engines active at once.

@kaahos kaahos force-pushed the paul.fournillon/wallclock-taskblock branch from c278669 to 90c80bd Compare June 19, 2026 10:19
@kaahos kaahos force-pushed the paul.fournillon/wallclock-taskblock branch from 90c80bd to 93362c8 Compare June 19, 2026 13:23
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.

3 participants