The Datadog Java Profiler uses two distinct type systems for event identification:
EventTypeenum (from upstream async-profiler)ASGCT_CallFrameType(BCI_* constants)
This document explains the relationship between these systems and how Datadog's implementation differs from upstream async-profiler.
Defined in ddprof-lib/src/main/cpp/event.h:
enum EventType {
PERF_SAMPLE, // 0
EXECUTION_SAMPLE, // 1
WALL_CLOCK_SAMPLE, // 2
MALLOC_SAMPLE, // 3
INSTRUMENTED_METHOD, // 4
METHOD_TRACE, // 5
ALLOC_SAMPLE, // 6
ALLOC_OUTSIDE_TLAB, // 7
LIVE_OBJECT, // 8
LOCK_SAMPLE, // 9
PARK_SAMPLE, // 10
PROFILING_WINDOW, // 11
USER_EVENT, // 12
};Defined in ddprof-lib/src/main/cpp/vmEntry.h:
enum ASGCT_CallFrameType {
BCI_CPU = 0, // cpu time
BCI_WALL = -10, // wall time
BCI_NATIVE_FRAME = -11, // native function name (char*)
BCI_ALLOC = -12, // name of the allocated class
BCI_ALLOC_OUTSIDE_TLAB = -13, // name of the class allocated outside TLAB
BCI_LIVENESS = -14, // name of the allocated class
BCI_LOCK = -15, // class name of the locked object
BCI_PARK = -16, // class name of the park() blocker
BCI_THREAD_ID = -17, // method_id designates a thread
BCI_ERROR = -18, // method_id is an error string
};Uses EventType consistently throughout:
-
Function signature (cpp/profiler.h:213):
u64 recordSample(void* ucontext, u64 counter, EventType event_type, Event* event);
-
Event-to-frame-type conversion when building call traces:
// Convert EventType to BCI_* frame type jint frame_type = BCI_ALLOC - (event_type - ALLOC_SAMPLE);
-
Type-safe comparisons:
if (event_type <= MALLOC_SAMPLE) { ... } if (event_type >= ALLOC_SAMPLE && event_type <= ALLOC_OUTSIDE_TLAB) { ... }
Uses BCI_* values directly as event identifiers:
-
Modified function signature (ddprof-lib/src/main/cpp/profiler.h:247-248):
void recordSample(void *ucontext, u64 weight, int tid, jint event_type, u64 call_trace_id, Event *event);
-
Direct BCI_ comparisons*:
if (event_type == BCI_CPU && _cpu_engine == &perf_events) { ... } if (event_type == BCI_CPU || event_type == BCI_WALL) { ... }
-
Call sites pass BCI_ values*:
Profiler::instance()->recordSample(ucontext, _interval, tid, BCI_CPU, 0, &event); Profiler::instance()->recordSample(ucontext, last_sample, tid, BCI_WALL, call_trace_id, &event);
Datadog's StackWalker::walkVM() (inherited from upstream) expects EventType but receives BCI_* values. Previously, this used an unsafe cast:
// Old approach - undefined behavior for negative BCI_* values
static_cast<EventType>(event_type)This cast is technically undefined behavior because:
- BCI_WALL = -10 is not a valid EventType enum value (0-12)
- Casting negative integers to enums with only positive values is undefined in C++
- It worked by accident because numeric comparisons still functioned
A conversion function maps BCI_* values to appropriate EventType values:
inline EventType eventTypeFromBCI(jint bci_type) {
switch (bci_type) {
case BCI_CPU: return EXECUTION_SAMPLE;
case BCI_WALL: return WALL_CLOCK_SAMPLE;
case BCI_ALLOC: return ALLOC_SAMPLE;
case BCI_ALLOC_OUTSIDE_TLAB: return ALLOC_OUTSIDE_TLAB;
case BCI_LIVENESS: return LIVE_OBJECT;
case BCI_LOCK: return LOCK_SAMPLE;
case BCI_PARK: return PARK_SAMPLE;
default: return EXECUTION_SAMPLE;
}
}Usage in profiler.cpp:
num_frames += StackWalker::walkVM(ucontext, frames + num_frames,
max_remaining, _features,
eventTypeFromBCI(event_type), &truncated);| BCI_* Constant | Value | EventType | Value | Notes |
|---|---|---|---|---|
| BCI_CPU | 0 | EXECUTION_SAMPLE | 1 | CPU samples (perf/itimer) |
| BCI_WALL | -10 | WALL_CLOCK_SAMPLE | 2 | Wall clock samples |
| BCI_ALLOC | -12 | ALLOC_SAMPLE | 6 | TLAB allocations |
| BCI_ALLOC_OUTSIDE_TLAB | -13 | ALLOC_OUTSIDE_TLAB | 7 | Non-TLAB allocations |
| BCI_LIVENESS | -14 | LIVE_OBJECT | 8 | Live heap objects |
| BCI_LOCK | -15 | LOCK_SAMPLE | 9 | Monitor contention |
| BCI_PARK | -16 | PARK_SAMPLE | 10 | Thread park events |
ddprof-lib/src/main/cpp/event.h- EventType enumddprof-lib/src/main/cpp/vmEntry.h- ASGCT_CallFrameType (BCI_* constants)
ddprof-lib/src/main/cpp/profiler.h:61-96- eventTypeFromBCI() implementation
ddprof-lib/src/main/cpp/profiler.cpp:716,719- StackWalker::walkVM() callsddprof-lib/src/main/cpp/profiler.cpp:690,717- BCI_* comparisonsddprof-lib/src/main/cpp/flightRecorder.cpp:1620-1642- Event recording switch
ddprof-lib/src/main/cpp/itimer.cpp:55- BCI_CPUddprof-lib/src/main/cpp/ctimer_linux.cpp:171- BCI_CPUddprof-lib/src/main/cpp/perfEvents_linux.cpp:749- BCI_CPUddprof-lib/src/main/cpp/wallClock.cpp:105- BCI_WALL
-
Upstream design: async-profiler uses EventType for event categorization and converts to BCI_* frame types only when building call traces.
-
Datadog divergence: Datadog's fork uses BCI_* values as the primary event identifiers throughout the codebase.
Using BCI_* values directly has advantages:
- Frame type alignment: BCI_* values are already frame types, eliminating conversion steps
- Semantic clarity: BCI_CPU, BCI_WALL are more descriptive than numeric EventType values
- Simpler call sites: Event sources directly specify the frame type they produce
However, it creates the conversion requirement when calling upstream code that expects EventType.
- Choose the identifier: Use BCI_* constant for the event type identifier
- Update conversion function: Add mapping in eventTypeFromBCI()
- Update FlightRecorder: Add case to recordEvent() switch statement
- Add call sites: Pass BCI_* value to recordSample()
- All event_type comparisons use symbolic names (BCI_CPU, not 0)
- No raw static_cast on event_type parameters
- New BCI_* values have eventTypeFromBCI() mappings
- FlightRecorder::recordEvent() handles the new event type
- Call sites pass correct BCI_* constant
Convert back to using EventType throughout Datadog's code:
- Pros: Type safety, upstream compatibility
- Cons: Large refactoring, frame type conversion overhead
Keep using BCI_* with conversion function:
- Pros: Minimal changes, semantically clear
- Cons: Type system mismatch persists
Fork StackWalker to accept BCI_* directly:
- Pros: Eliminates conversion, full type consistency
- Cons: Maintenance burden, harder to merge upstream changes
The eventTypeFromBCI() conversion function provides a clean bridge between Datadog's BCI_*-based event identification and upstream's EventType-based stack walking logic. This approach:
- Eliminates undefined behavior from raw casts
- Documents the intentional type system divergence
- Maintains compatibility with upstream StackWalker code
- Preserves the semantic benefits of BCI_* identifiers
When working with event types, always use the conversion function when calling code that expects EventType, and use BCI_* constants directly in Datadog-specific code.