Skip to content

Commit 2c49bab

Browse files
authored
Add OTEP #4947 OTEL-compatible context storage (#347)
1 parent 74b51a5 commit 2c49bab

42 files changed

Lines changed: 2219 additions & 1649 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ddprof-lib/src/main/cpp/arguments.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,7 @@ class Arguments {
223223
_context_attributes({}),
224224
_lightweight(false),
225225
_enable_method_cleanup(true),
226-
_remote_symbolication(false) { }
227-
226+
_remote_symbolication(false) {}
228227

229228
~Arguments();
230229

ddprof-lib/src/main/cpp/context.cpp

Lines changed: 0 additions & 51 deletions
This file was deleted.

ddprof-lib/src/main/cpp/context.h

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@
1818
#define _CONTEXT_H
1919

2020
#include "arch.h"
21-
#include "arguments.h"
22-
#include "common.h"
23-
#include "os.h"
24-
#include "vmEntry.h"
2521

2622
static const u32 DD_TAGS_CAPACITY = 10;
2723

@@ -31,27 +27,11 @@ typedef struct {
3127

3228
class alignas(DEFAULT_CACHE_LINE_SIZE) Context {
3329
public:
34-
volatile u64 spanId;
35-
volatile u64 rootSpanId;
36-
volatile u64 checksum;
30+
u64 spanId;
31+
u64 rootSpanId;
3732
Tag tags[DD_TAGS_CAPACITY];
3833

3934
Tag get_tag(int i) { return tags[i]; }
4035
};
4136

42-
class Contexts {
43-
44-
public:
45-
static Context& initializeContextTls();
46-
static Context& get();
47-
48-
static u64 checksum(u64 spanId, u64 rootSpanId) {
49-
u64 swappedRootSpanId = ((rootSpanId & 0xFFFFFFFFULL) << 32) | (rootSpanId >> 32);
50-
u64 computed = (spanId * KNUTH_MULTIPLICATIVE_CONSTANT) ^ (swappedRootSpanId * KNUTH_MULTIPLICATIVE_CONSTANT);
51-
return computed == 0 ? 0xffffffffffffffffull : computed;
52-
}
53-
};
54-
55-
DLLEXPORT extern thread_local Context context_tls_v1;
56-
5737
#endif /* _CONTEXT_H */
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright 2026, Datadog, Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "context_api.h"
18+
#include "context.h"
19+
#include "guards.h"
20+
#include "otel_context.h"
21+
#include "otel_process_ctx.h"
22+
#include "profiler.h"
23+
#include "thread.h"
24+
#include <cstring>
25+
26+
// Reserved attribute index for local root span ID in OTEL attrs_data.
27+
// Only used within this translation unit; not part of the public ContextApi header.
28+
static const uint8_t LOCAL_ROOT_SPAN_ATTR_INDEX = 0;
29+
30+
/**
31+
* Initialize context TLS for the current thread on first use.
32+
* Must be called with signals blocked to prevent musl TLS deadlock:
33+
* on musl, the first write to a TLS variable triggers lazy slot allocation,
34+
* which acquires an internal lock that is also held during signal delivery,
35+
* causing deadlock if a signal fires mid-init.
36+
* The OtelThreadContextRecord is already zero-initialized by the ProfiledThread ctor.
37+
*/
38+
void ContextApi::initializeContextTLS(ProfiledThread* thrd) {
39+
SignalBlocker blocker;
40+
// Set the TLS pointer permanently to this thread's record.
41+
// This first write triggers musl's TLS slot initialization (see above).
42+
// The pointer remains stable for the thread's lifetime; external profilers
43+
// rely solely on the valid flag for consistency, not pointer nullness.
44+
otel_thread_ctx_v1 = thrd->getOtelContextRecord();
45+
thrd->markContextInitialized();
46+
}
47+
48+
bool ContextApi::get(u64& span_id, u64& root_span_id) {
49+
ProfiledThread* thrd = ProfiledThread::currentSignalSafe();
50+
if (thrd == nullptr || !thrd->isContextInitialized()) {
51+
return false;
52+
}
53+
54+
OtelThreadContextRecord* record = thrd->getOtelContextRecord();
55+
if (__atomic_load_n(&record->valid, __ATOMIC_ACQUIRE) != 1) {
56+
return false;
57+
}
58+
u64 val = 0;
59+
for (int i = 0; i < 8; i++) { val = (val << 8) | record->span_id[i]; }
60+
span_id = val;
61+
62+
root_span_id = thrd->getOtelLocalRootSpanId();
63+
return true;
64+
}
65+
66+
Context ContextApi::snapshot() {
67+
ProfiledThread* thrd = ProfiledThread::currentSignalSafe();
68+
if (thrd == nullptr) {
69+
return {};
70+
}
71+
size_t numAttrs = Profiler::instance()->numContextAttributes();
72+
return thrd->snapshotContext(numAttrs);
73+
}
74+
75+
void ContextApi::registerAttributeKeys(const char** keys, int count) {
76+
// Clip to DD_TAGS_CAPACITY: that is the actual sidecar slot limit and the
77+
// maximum keyIndex accepted by ThreadContext.setContextAttribute.
78+
int n = count < (int)DD_TAGS_CAPACITY ? count : (int)DD_TAGS_CAPACITY;
79+
80+
// Build NULL-terminated key array for the process context config.
81+
// Index LOCAL_ROOT_SPAN_ATTR_INDEX (0) is reserved for local_root_span_id; user keys start at index 1.
82+
// otel_process_ctx_publish copies all strings, so no strdup is needed.
83+
const char* key_ptrs[DD_TAGS_CAPACITY + 2]; // +1 reserved, +1 null
84+
key_ptrs[LOCAL_ROOT_SPAN_ATTR_INDEX] = "datadog.local_root_span_id";
85+
for (int i = 0; i < n; i++) {
86+
key_ptrs[i + 1] = keys[i];
87+
}
88+
key_ptrs[n + 1] = nullptr;
89+
90+
otel_thread_ctx_config_data config = {
91+
.schema_version = "tlsdesc_v1_dev",
92+
.attribute_key_map = key_ptrs,
93+
};
94+
95+
#ifndef OTEL_PROCESS_CTX_NO_READ
96+
otel_process_ctx_read_result read_result = otel_process_ctx_read();
97+
if (read_result.success) {
98+
otel_process_ctx_data data = {
99+
.deployment_environment_name = read_result.data.deployment_environment_name,
100+
.service_instance_id = read_result.data.service_instance_id,
101+
.service_name = read_result.data.service_name,
102+
.service_version = read_result.data.service_version,
103+
.telemetry_sdk_language = read_result.data.telemetry_sdk_language,
104+
.telemetry_sdk_version = read_result.data.telemetry_sdk_version,
105+
.telemetry_sdk_name = read_result.data.telemetry_sdk_name,
106+
.resource_attributes = read_result.data.resource_attributes,
107+
.extra_attributes = read_result.data.extra_attributes,
108+
.thread_ctx_config = &config,
109+
};
110+
111+
otel_process_ctx_publish(&data);
112+
otel_process_ctx_read_drop(&read_result);
113+
}
114+
#endif
115+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2026, Datadog, Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef _CONTEXT_API_H
18+
#define _CONTEXT_API_H
19+
20+
#include "arch.h"
21+
#include "context.h"
22+
#include <cstdint>
23+
24+
class ProfiledThread;
25+
26+
/**
27+
* Unified context API for trace/span context storage.
28+
*
29+
* Uses OTEP #4947 TLS pointer (otel_thread_ctx_v1) for all
30+
* context reads and writes. The OTEP record is embedded in ProfiledThread
31+
* and discovered by external profilers via ELF dynsym.
32+
*/
33+
class ContextApi {
34+
public:
35+
/**
36+
* Initialize context TLS for the given thread on first use.
37+
* Must be called with signals blocked (SignalBlocker).
38+
*/
39+
static void initializeContextTLS(ProfiledThread* thrd);
40+
41+
/**
42+
* Read span ID and local root span ID for the current thread.
43+
*
44+
* Used by signal handlers to get the current trace context.
45+
* Returns false if the OTEP valid flag is not set (record being mutated
46+
* or thread not yet initialized). Does not modify span_id or root_span_id
47+
* on failure; callers must pre-initialize output parameters to their
48+
* desired default. Does not detect torn reads (the valid flag guards that).
49+
*
50+
* Unlike snapshot(), this reads only spanId and rootSpanId — use
51+
* snapshot() when tag encodings are also needed.
52+
*
53+
* @param span_id Output: the span ID
54+
* @param root_span_id Output: the root span ID (from sidecar)
55+
* @return true if the valid flag was set and context was read
56+
*/
57+
static bool get(u64& span_id, u64& root_span_id);
58+
59+
/**
60+
* Snapshot the current thread's full context into a Context struct.
61+
*
62+
* Populates a Context with spanId, rootSpanId (from sidecar)
63+
* and tag encodings (from sidecar) so that writeContextSnapshot()
64+
* works for both live and deferred event paths. Unlike get(), this
65+
* also captures custom attribute tag encodings.
66+
*
67+
* @return A Context struct representing the current thread's context
68+
*/
69+
static Context snapshot();
70+
71+
/**
72+
* Register attribute key names and publish them in the process context.
73+
* Must be called before setAttribute().
74+
* Keys beyond DD_TAGS_CAPACITY are silently clipped.
75+
*
76+
* @param keys Array of key name strings
77+
* @param count Number of keys (clipped to DD_TAGS_CAPACITY)
78+
*/
79+
static void registerAttributeKeys(const char** keys, int count);
80+
81+
};
82+
83+
#endif /* _CONTEXT_API_H */

ddprof-lib/src/main/cpp/counters.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
X(CONTEXT_STORAGE_PAGES, "context_storage_pages") \
4242
X(CONTEXT_BOUNDS_MISS_INITS, "context_bounds_miss_inits") \
4343
X(CONTEXT_BOUNDS_MISS_GETS, "context_bounds_miss_gets") \
44-
X(CONTEXT_CHECKSUM_REJECT_GETS, "context_checksum_reject_gets") \
4544
X(CONTEXT_NULL_PAGE_GETS, "context_null_page_gets") \
4645
X(CONTEXT_ALLOC_FAILS, "context_alloc_fails") \
4746
X(CALLTRACE_STORAGE_BYTES, "calltrace_storage_bytes") \

0 commit comments

Comments
 (0)