diff --git a/bindings/otel-thread-ctx.cc b/bindings/otel-thread-ctx.cc index 574a87cf..c98bb209 100644 --- a/bindings/otel-thread-ctx.cc +++ b/bindings/otel-thread-ctx.cc @@ -679,6 +679,28 @@ constexpr int WRAPPED_OBJECT_OFFSET = 0; #endif constexpr int TAGGED_SIZE = v8::internal::kApiTaggedSize; +// sizeof(node::ObjectWrap). Given a pointer to a CtxWrap — or any other +// ObjectWrap-derived C++ object attached to a JSObject via the V8 +// wrapped-object slot — add this offset to reach the derived class's own +// fields. For CtxWrap, that's `record_` (see the static_assert on its +// offset above). +constexpr int NATIVE_WRAP_FIELDS_OFFSET = + static_cast(sizeof(node::ObjectWrap)); + +// V8 JSMap layout: kTableOffset within the JSMap object holds a tagged +// pointer to the backing OrderedHashMap table. Not exposed in V8's +// public headers; kept in sync with +// deps/v8/src/objects/js-collection.h (JSCollection::kTableOffset) +// and the torque-generated JSCollection layout. +constexpr int JS_MAP_TABLE_OFFSET = 0x18; + +// V8 OrderedHashMap layout: the on-heap table starts with a 16-byte +// header before the element_count / deleted_element_count / +// number_of_buckets fields. Not exposed in V8's public headers; kept in +// sync with deps/v8/src/objects/ordered-hash-table.h +// (OrderedHashTable base layout). +constexpr int ORDERED_HASH_MAP_HEADER_SIZE = 0x10; + } // namespace void OtelThreadCtx::Init(Local exports) { @@ -688,17 +710,19 @@ void OtelThreadCtx::Init(Local exports) { Isolate* isolate = Isolate::GetCurrent(); Local ctx = isolate->GetCurrentContext(); - exports - ->Set(ctx, - String::NewFromUtf8Literal(isolate, - "otelThreadCtxWrappedObjectOffset"), - Integer::New(isolate, WRAPPED_OBJECT_OFFSET)) - .FromJust(); - exports - ->Set(ctx, - String::NewFromUtf8Literal(isolate, "otelThreadCtxTaggedSize"), - Integer::New(isolate, TAGGED_SIZE)) - .FromJust(); + auto publish_int = [&](const char* name, int value) { + exports + ->Set(ctx, + String::NewFromUtf8(isolate, name).ToLocalChecked(), + Integer::New(isolate, value)) + .FromJust(); + }; + publish_int("otelThreadCtxJsMapTableOffset", JS_MAP_TABLE_OFFSET); + publish_int("otelThreadCtxNativeWrapFieldsOffset", NATIVE_WRAP_FIELDS_OFFSET); + publish_int("otelThreadCtxOrderedHashMapHeaderSize", + ORDERED_HASH_MAP_HEADER_SIZE); + publish_int("otelThreadCtxTaggedSize", TAGGED_SIZE); + publish_int("otelThreadCtxWrappedObjectOffset", WRAPPED_OBJECT_OFFSET); } } // namespace dd diff --git a/ts/src/otel-thread-ctx.ts b/ts/src/otel-thread-ctx.ts index 316e1505..cbc91c93 100644 --- a/ts/src/otel-thread-ctx.ts +++ b/ts/src/otel-thread-ctx.ts @@ -40,6 +40,9 @@ export interface ProcessContextAttributes { readonly 'threadlocal.attribute_key_map': readonly string[]; readonly 'threadlocal.wrapped_object_offset': number; readonly 'threadlocal.tagged_size': number; + readonly 'threadlocal.native_wrap_fields_offset': number; + readonly 'threadlocal.js_map_table_offset': number; + readonly 'threadlocal.ordered_hash_map_header_size': number; } /** @@ -103,6 +106,9 @@ interface Addon { otelThreadCtxGetStoredAlsHash(): number; otelThreadCtxWrappedObjectOffset: number; otelThreadCtxTaggedSize: number; + otelThreadCtxNativeWrapFieldsOffset: number; + otelThreadCtxJsMapTableOffset: number; + otelThreadCtxOrderedHashMapHeaderSize: number; } const SCHEMA_VERSION = 'nodejs_v1_dev'; @@ -114,6 +120,9 @@ const SCHEMA_VERSION = 'nodejs_v1_dev'; // consistent in shape. let WRAPPED_OBJECT_OFFSET = 24; let TAGGED_SIZE = 8; +let NATIVE_WRAP_FIELDS_OFFSET = 24; +let JS_MAP_TABLE_OFFSET = 0x18; +let ORDERED_HASH_MAP_HEADER_SIZE = 0x10; /** {@inheritDoc ThreadContextCtor} */ export let ThreadContext: ThreadContextCtor; @@ -140,6 +149,9 @@ if (process.platform === 'linux') { const addon: Addon = findBinding(join(__dirname, '..', '..')); WRAPPED_OBJECT_OFFSET = addon.otelThreadCtxWrappedObjectOffset; TAGGED_SIZE = addon.otelThreadCtxTaggedSize; + NATIVE_WRAP_FIELDS_OFFSET = addon.otelThreadCtxNativeWrapFieldsOffset; + JS_MAP_TABLE_OFFSET = addon.otelThreadCtxJsMapTableOffset; + ORDERED_HASH_MAP_HEADER_SIZE = addon.otelThreadCtxOrderedHashMapHeaderSize; ThreadContext = addon.threadContext; @@ -266,5 +278,8 @@ export function getProcessContextAttributes( 'threadlocal.attribute_key_map': Object.freeze(keys.slice()), 'threadlocal.wrapped_object_offset': WRAPPED_OBJECT_OFFSET, 'threadlocal.tagged_size': TAGGED_SIZE, + 'threadlocal.native_wrap_fields_offset': NATIVE_WRAP_FIELDS_OFFSET, + 'threadlocal.js_map_table_offset': JS_MAP_TABLE_OFFSET, + 'threadlocal.ordered_hash_map_header_size': ORDERED_HASH_MAP_HEADER_SIZE, }) as ProcessContextAttributes; } diff --git a/ts/test/test-otel-thread-ctx.ts b/ts/test/test-otel-thread-ctx.ts index 070cf5c0..f6229f2c 100644 --- a/ts/test/test-otel-thread-ctx.ts +++ b/ts/test/test-otel-thread-ctx.ts @@ -733,8 +733,17 @@ function captureBytes(opts: { strictAssert.deepEqual(pca['threadlocal.attribute_key_map'], keys); strictAssert.equal(pca['threadlocal.wrapped_object_offset'], 24); strictAssert.equal(pca['threadlocal.tagged_size'], 8); + strictAssert.equal(pca['threadlocal.native_wrap_fields_offset'], 24); + strictAssert.equal(pca['threadlocal.js_map_table_offset'], 0x18); + strictAssert.equal( + pca['threadlocal.ordered_hash_map_header_size'], + 0x10, + ); strictAssert.deepEqual(Object.keys(pca).sort(), [ 'threadlocal.attribute_key_map', + 'threadlocal.js_map_table_offset', + 'threadlocal.native_wrap_fields_offset', + 'threadlocal.ordered_hash_map_header_size', 'threadlocal.schema_version', 'threadlocal.tagged_size', 'threadlocal.wrapped_object_offset',