From 79e62c799938c08009d461812a9cc124a71eaaba Mon Sep 17 00:00:00 2001 From: Ivo Anjo Date: Wed, 4 Feb 2026 11:56:12 +0000 Subject: [PATCH 1/3] Wrap OTel Resource message with top-level ProcessContext message **What does this PR do?** This PR updates the C/C++ reference implementation to introduce top-level `ProcessContext` message that wraps the previous `Resource` message. **Motivation:** Having a top-level `ProcessContext` message gives us more flexibility to evolve the `ProcessContext` in the future without directly depending on `Resource`, as well as to introduce attributes that are not considered part of the `Resource` and that may need to be updated from time to time. **Additional Notes:** N/A **How to test the change?** The built-in decoder and `otel_process_ctx_dump.sh` have been updated to match. --- process-context/c-and-cpp/otel_process_ctx.c | 29 ++++++++--- .../c-and-cpp/otel_process_ctx_dump.sh | 2 +- .../c-and-cpp/process_context.proto | 52 +++++++++++++++++++ 3 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 process-context/c-and-cpp/process_context.proto diff --git a/process-context/c-and-cpp/otel_process_ctx.c b/process-context/c-and-cpp/otel_process_ctx.c index cf527b3..590fbce 100644 --- a/process-context/c-and-cpp/otel_process_ctx.c +++ b/process-context/c-and-cpp/otel_process_ctx.c @@ -362,8 +362,8 @@ static void write_attribute(char **ptr, const char *key, const char *value) { // Encode the payload as protobuf bytes. // -// This method implements an extremely compact but limited protobuf encoder for the Resource message. -// It encodes all fields as attributes (KeyValue pairs). +// This method implements an extremely compact but limited protobuf encoder for the ProcessContext message. +// It encodes all fields as Resource attributes (KeyValue pairs). // For extra compact code, it fixes strings at up to 4096 bytes. static otel_process_ctx_result otel_process_ctx_encode_protobuf_payload(char **out, uint32_t *out_size, otel_process_ctx_data data) { const char *pairs[] = { @@ -387,7 +387,9 @@ static otel_process_ctx_result otel_process_ctx_encode_protobuf_payload(char **o if (!validation_result.success) return validation_result; } - size_t total_size = pairs_size + resources_pairs_size; + size_t resource_size = pairs_size + resources_pairs_size; + // ProcessContext wrapper: tag (1 byte) + resource length varint + resource content + size_t total_size = 1 + protobuf_varint_size(resource_size) + resource_size; char *encoded = (char *) calloc(total_size, 1); if (!encoded) { @@ -395,6 +397,10 @@ static otel_process_ctx_result otel_process_ctx_encode_protobuf_payload(char **o } char *ptr = encoded; + // ProcessContext.resource (field 1) + write_protobuf_tag(&ptr, 1); + write_protobuf_varint(&ptr, resource_size); + for (size_t i = 0; pairs[i * 2] != NULL; i++) { write_attribute(&ptr, pairs[i * 2], pairs[i * 2 + 1]); } @@ -497,19 +503,28 @@ static otel_process_ctx_result otel_process_ctx_encode_protobuf_payload(char **o *data_out = empty_data; + // Parse ProcessContext wrapper - expect field 1 (resource) + uint8_t process_ctx_field; + if (!read_protobuf_tag(&ptr, end_ptr, &process_ctx_field) || process_ctx_field != 1) return false; + + uint16_t resource_len; + if (!read_protobuf_varint(&ptr, end_ptr, &resource_len)) return false; + char *resource_end = ptr + resource_len; + if (resource_end > end_ptr) return false; + size_t resource_index = 0; size_t resource_capacity = 201; // Allocate space for 100 pairs + NULL terminator entry data_out->resources = (const char **) calloc(resource_capacity, sizeof(char *)); if (data_out->resources == NULL) return false; - while (ptr < end_ptr) { + while (ptr < resource_end) { uint8_t field_number; - if (!read_protobuf_tag(&ptr, end_ptr, &field_number) || field_number != 1) return false; + if (!read_protobuf_tag(&ptr, resource_end, &field_number) || field_number != 1) return false; uint16_t kv_len; - if (!read_protobuf_varint(&ptr, end_ptr, &kv_len)) return false; + if (!read_protobuf_varint(&ptr, resource_end, &kv_len)) return false; char *kv_end = ptr + kv_len; - if (kv_end > end_ptr) return false; + if (kv_end > resource_end) return false; bool key_found = false; bool value_found = false; diff --git a/process-context/c-and-cpp/otel_process_ctx_dump.sh b/process-context/c-and-cpp/otel_process_ctx_dump.sh index 7e84682..8d45295 100755 --- a/process-context/c-and-cpp/otel_process_ctx_dump.sh +++ b/process-context/c-and-cpp/otel_process_ctx_dump.sh @@ -79,7 +79,7 @@ dd if="/proc/$pid/mem" bs=1 count="$payload_size" skip=$((16#$payload_ptr_hex)) if command -v protoc >/dev/null 2>&1; then echo "Protobuf decode:" - dd if="/proc/$pid/mem" bs=1 count="$payload_size" skip=$((16#$payload_ptr_hex)) status=none | protoc --decode=opentelemetry.proto.resource.v1.Resource resource.proto common.proto + dd if="/proc/$pid/mem" bs=1 count="$payload_size" skip=$((16#$payload_ptr_hex)) status=none | protoc --decode=opentelemetry.proto.profiles.v1development.ProcessContext process_context.proto resource.proto common.proto else echo echo "protoc not available - skipping protobuf decode" diff --git a/process-context/c-and-cpp/process_context.proto b/process-context/c-and-cpp/process_context.proto new file mode 100644 index 0000000..b19e05e --- /dev/null +++ b/process-context/c-and-cpp/process_context.proto @@ -0,0 +1,52 @@ +// Copyright 2019, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package opentelemetry.proto.profiles.v1development; + +import "common.proto"; +import "resource.proto"; + +option csharp_namespace = "OpenTelemetry.Proto.Profiles.V1Development"; +option java_multiple_files = true; +option java_package = "io.opentelemetry.proto.profiles.v1development"; +option java_outer_classname = "ProcessContextProto"; +option go_package = "go.opentelemetry.io/proto/otlp/profiles/v1development"; + +// ProcessContext represents the payload for the process context sharing mechanism. +// +// This message is designed to be published by OpenTelemetry SDKs via a memory-mapped +// region, allowing external readers (such as the OpenTelemetry eBPF Profiler) to +// discover and read resource attributes from instrumented processes without requiring +// direct integration or process activity. +message ProcessContext { + // The resource attributes describing this process. + // + // Attribute keys MUST be unique (it is not allowed to have more than one + // attribute with the same key). The behavior of software that receives + // duplicated keys can be unpredictable. + // + // Attributes SHOULD follow OpenTelemetry semantic conventions where applicable. + // See: https://opentelemetry.io/docs/specs/semconv/ + opentelemetry.proto.resource.v1.Resource resource = 1; + + // Additional attributes to share with external readers that are not part of + // the standard Resource. [Optional] + // + // This field allows publishers to include supplementary key-value pairs that + // may be useful for external readers but are not part of the SDK's configured + // Resource. + repeated opentelemetry.proto.common.v1.KeyValue extra_attributes = 2; +} From 94d5b78b3a88ae7aa303b4d8f3fd6c8068470c8d Mon Sep 17 00:00:00 2001 From: Ivo Anjo Date: Fri, 6 Feb 2026 13:20:37 +0000 Subject: [PATCH 2/3] Update README for reference implementation --- process-context/c-and-cpp/README.md | 60 +++++++++++++---------------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/process-context/c-and-cpp/README.md b/process-context/c-and-cpp/README.md index a875cb6..d479f52 100644 --- a/process-context/c-and-cpp/README.md +++ b/process-context/c-and-cpp/README.md @@ -72,7 +72,8 @@ These can be set in your own build system as needed. ### Protocol Buffers Definitions -- **`resource.proto`** - OpenTelemetry Resource protobuf definition (extracted from OpenTelemetry protocol) +- **`process_context.proto`** - OpenTelemetry Process Context protobuf definition +- **`resource.proto`** - OpenTelemetry Resource protobuf definition - **`common.proto`** - OpenTelemetry common types protobuf definition (AnyValue, KeyValue, etc.) These proto files are included for reference and for use with `protoc` when decoding the payload with the dump script. The implementation includes its own minimal protobuf encoder/decoder and does not depend on the protobuf library. @@ -208,18 +209,18 @@ sudo ./otel_process_ctx_dump.sh ``` Found OTEL context for PID 267023 Start address: 756f28ce1000 -00000000 4f 54 45 4c 5f 43 54 58 02 00 00 00 0b 68 55 47 |OTEL_CTX.....hUG| -00000010 70 24 7d 18 50 01 00 00 a0 82 6d 7e 6a 5f 00 00 |p$}.P.....m~j_..| +00000000 4f 54 45 4c 5f 43 54 58 02 00 00 00 53 01 00 00 |OTEL_CTX....S...| +00000010 4c 27 98 d5 cc aa 91 18 a0 b2 28 1e ee 5c 00 00 |L'........(..\..| 00000020 Parsed struct: otel_process_ctx_signature : "OTEL_CTX" otel_process_ctx_version : 2 - otel_process_ctx_published_at_ns : 1764606693650819083 (2025-12-01 16:31:33 GMT) - otel_process_payload_size : 336 - otel_process_payload : 0x00005f6a7e6d82a0 -Payload dump (336 bytes): -00000000 0a 25 0a 1b 64 65 70 6c 6f 79 6d 65 6e 74 2e 65 |.%..deployment.e| -00000010 6e 76 69 72 6f 6e 6d 65 6e 74 2e 6e 61 6d 65 12 |nvironment.name.| + otel_process_payload_size : 339 + otel_process_ctx_published_at_ns : 1770383925266884428 (2026-02-06 13:18:45 GMT) + otel_process_payload : 0x00005cee1e28b2a0 +Payload dump (339 bytes): +00000000 0a d0 02 0a 25 0a 1b 64 65 70 6c 6f 79 6d 65 6e |....%..deploymen| +00000010 74 2e 65 6e 76 69 72 6f 6e 6d 65 6e 74 2e 6e 61 |t.environment.na| ... ``` @@ -227,24 +228,25 @@ If `protoc` is installed and the proto files are available, the script will also ``` Protobuf decode: -attributes { - key: "deployment.environment.name" - value { - string_value: "prod" +resource { + attributes { + key: "deployment.environment.name" + value { + string_value: "prod" + } } -} -attributes { - key: "service.instance.id" - value { - string_value: "123d8444-2c7e-46e3-89f6-6217880f7123" + attributes { + key: "service.instance.id" + value { + string_value: "123d8444-2c7e-46e3-89f6-6217880f7123" + } } -} -attributes { - key: "service.name" - value { - string_value: "my-service" + attributes { + key: "service.name" + value { + string_value: "my-service" + } } -} ... ``` @@ -298,13 +300,3 @@ When integrating this into an OpenTelemetry SDK or application: 5. **Check result codes**: Always check the `success` field of the result and log errors appropriately. 6. **Consider no-op builds**: For non-Linux platforms, use the no-op variant or define `OTEL_PROCESS_CTX_NOOP=1`. - -## Specification Compliance - -This implementation follows the [OpenTelemetry Process Context specification](https://github.com/open-telemetry/opentelemetry-specification/pull/4719/). Key aspects: - -- Uses the `OTEL_CTX` signature for discoverability -- Stores data in version 2 format (packed struct + protobuf payload) -- Encodes resource attributes using OpenTelemetry Protocol Buffers Resource format -- Supports both standard semantic conventions and custom resource attributes -- Provides proper memory ordering guarantees for concurrent readers From ff3ddeddf7500cfdcc5da156ae39ac227e35c575 Mon Sep 17 00:00:00 2001 From: Ivo Anjo Date: Fri, 6 Feb 2026 13:26:13 +0000 Subject: [PATCH 3/3] Update namespace after discussion in profiling SIG --- process-context/c-and-cpp/otel_process_ctx_dump.sh | 2 +- process-context/c-and-cpp/process_context.proto | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/process-context/c-and-cpp/otel_process_ctx_dump.sh b/process-context/c-and-cpp/otel_process_ctx_dump.sh index 8d45295..ed936e8 100755 --- a/process-context/c-and-cpp/otel_process_ctx_dump.sh +++ b/process-context/c-and-cpp/otel_process_ctx_dump.sh @@ -79,7 +79,7 @@ dd if="/proc/$pid/mem" bs=1 count="$payload_size" skip=$((16#$payload_ptr_hex)) if command -v protoc >/dev/null 2>&1; then echo "Protobuf decode:" - dd if="/proc/$pid/mem" bs=1 count="$payload_size" skip=$((16#$payload_ptr_hex)) status=none | protoc --decode=opentelemetry.proto.profiles.v1development.ProcessContext process_context.proto resource.proto common.proto + dd if="/proc/$pid/mem" bs=1 count="$payload_size" skip=$((16#$payload_ptr_hex)) status=none | protoc --decode=opentelemetry.proto.common.v1.ProcessContext process_context.proto resource.proto common.proto else echo echo "protoc not available - skipping protobuf decode" diff --git a/process-context/c-and-cpp/process_context.proto b/process-context/c-and-cpp/process_context.proto index b19e05e..f24a10d 100644 --- a/process-context/c-and-cpp/process_context.proto +++ b/process-context/c-and-cpp/process_context.proto @@ -14,16 +14,19 @@ syntax = "proto3"; -package opentelemetry.proto.profiles.v1development; +// TODO: Is this in the right namespace? Since this is not only for profiling, +// `opentelemetry.proto.profiles` doesn't seem the right place, but perhaps common ain't +// it either? Feedback very welcome! +package opentelemetry.proto.common.v1; import "common.proto"; import "resource.proto"; -option csharp_namespace = "OpenTelemetry.Proto.Profiles.V1Development"; +option csharp_namespace = "OpenTelemetry.Proto.Common.V1"; option java_multiple_files = true; -option java_package = "io.opentelemetry.proto.profiles.v1development"; +option java_package = "io.opentelemetry.proto.common.v1"; option java_outer_classname = "ProcessContextProto"; -option go_package = "go.opentelemetry.io/proto/otlp/profiles/v1development"; +option go_package = "go.opentelemetry.io/proto/otlp/common/v1"; // ProcessContext represents the payload for the process context sharing mechanism. //