Skip to content

Commit 1b86b42

Browse files
ivoanjochristos68k
andauthored
Wrap OTel Resource message with top-level ProcessContext message (#53)
**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:** Currently every attribute provided to the library ends up inside the resource; I plan to later extend this to allow attributes to be provided that end up inside the `extra_attributes`. **How to test the change?** The built-in decoder and `otel_process_ctx_dump.sh` have been updated to match. --------- Co-authored-by: Christos Kalkanis <christos.kalkanis@elastic.co>
1 parent bc4d0d2 commit 1b86b42

File tree

4 files changed

+104
-42
lines changed

4 files changed

+104
-42
lines changed

process-context/c-and-cpp/README.md

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ These can be set in your own build system as needed.
7272

7373
### Protocol Buffers Definitions
7474

75-
- **`resource.proto`** - OpenTelemetry Resource protobuf definition (extracted from OpenTelemetry protocol)
75+
- **`process_context.proto`** - OpenTelemetry Process Context protobuf definition
76+
- **`resource.proto`** - OpenTelemetry Resource protobuf definition
7677
- **`common.proto`** - OpenTelemetry common types protobuf definition (AnyValue, KeyValue, etc.)
7778

7879
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,43 +209,44 @@ sudo ./otel_process_ctx_dump.sh <pid>
208209
```
209210
Found OTEL context for PID 267023
210211
Start address: 756f28ce1000
211-
00000000 4f 54 45 4c 5f 43 54 58 02 00 00 00 0b 68 55 47 |OTEL_CTX.....hUG|
212-
00000010 70 24 7d 18 50 01 00 00 a0 82 6d 7e 6a 5f 00 00 |p$}.P.....m~j_..|
212+
00000000 4f 54 45 4c 5f 43 54 58 02 00 00 00 53 01 00 00 |OTEL_CTX....S...|
213+
00000010 4c 27 98 d5 cc aa 91 18 a0 b2 28 1e ee 5c 00 00 |L'........(..\..|
213214
00000020
214215
Parsed struct:
215216
otel_process_ctx_signature : "OTEL_CTX"
216217
otel_process_ctx_version : 2
217-
otel_process_ctx_published_at_ns : 1764606693650819083 (2025-12-01 16:31:33 GMT)
218-
otel_process_payload_size : 336
219-
otel_process_payload : 0x00005f6a7e6d82a0
220-
Payload dump (336 bytes):
221-
00000000 0a 25 0a 1b 64 65 70 6c 6f 79 6d 65 6e 74 2e 65 |.%..deployment.e|
222-
00000010 6e 76 69 72 6f 6e 6d 65 6e 74 2e 6e 61 6d 65 12 |nvironment.name.|
218+
otel_process_payload_size : 339
219+
otel_process_ctx_published_at_ns : 1770383925266884428 (2026-02-06 13:18:45 GMT)
220+
otel_process_payload : 0x00005cee1e28b2a0
221+
Payload dump (339 bytes):
222+
00000000 0a d0 02 0a 25 0a 1b 64 65 70 6c 6f 79 6d 65 6e |....%..deploymen|
223+
00000010 74 2e 65 6e 76 69 72 6f 6e 6d 65 6e 74 2e 6e 61 |t.environment.na|
223224
...
224225
```
225226

226227
If `protoc` is installed and the proto files are available, the script will also decode the payload:
227228

228229
```
229230
Protobuf decode:
230-
attributes {
231-
key: "deployment.environment.name"
232-
value {
233-
string_value: "prod"
231+
resource {
232+
attributes {
233+
key: "deployment.environment.name"
234+
value {
235+
string_value: "prod"
236+
}
234237
}
235-
}
236-
attributes {
237-
key: "service.instance.id"
238-
value {
239-
string_value: "123d8444-2c7e-46e3-89f6-6217880f7123"
238+
attributes {
239+
key: "service.instance.id"
240+
value {
241+
string_value: "123d8444-2c7e-46e3-89f6-6217880f7123"
242+
}
240243
}
241-
}
242-
attributes {
243-
key: "service.name"
244-
value {
245-
string_value: "my-service"
244+
attributes {
245+
key: "service.name"
246+
value {
247+
string_value: "my-service"
248+
}
246249
}
247-
}
248250
...
249251
```
250252

@@ -298,13 +300,3 @@ When integrating this into an OpenTelemetry SDK or application:
298300
5. **Check result codes**: Always check the `success` field of the result and log errors appropriately.
299301

300302
6. **Consider no-op builds**: For non-Linux platforms, use the no-op variant or define `OTEL_PROCESS_CTX_NOOP=1`.
301-
302-
## Specification Compliance
303-
304-
This implementation follows the [OpenTelemetry Process Context specification](https://github.com/open-telemetry/opentelemetry-specification/pull/4719/). Key aspects:
305-
306-
- Uses the `OTEL_CTX` signature for discoverability
307-
- Stores data in version 2 format (packed struct + protobuf payload)
308-
- Encodes resource attributes using OpenTelemetry Protocol Buffers Resource format
309-
- Supports both standard semantic conventions and custom resource attributes
310-
- Provides proper memory ordering guarantees for concurrent readers

process-context/c-and-cpp/otel_process_ctx.c

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -362,8 +362,8 @@ static void write_attribute(char **ptr, const char *key, const char *value) {
362362

363363
// Encode the payload as protobuf bytes.
364364
//
365-
// This method implements an extremely compact but limited protobuf encoder for the Resource message.
366-
// It encodes all fields as attributes (KeyValue pairs).
365+
// This method implements an extremely compact but limited protobuf encoder for the ProcessContext message.
366+
// It encodes all fields as Resource attributes (KeyValue pairs).
367367
// For extra compact code, it fixes strings at up to 4096 bytes.
368368
static otel_process_ctx_result otel_process_ctx_encode_protobuf_payload(char **out, uint32_t *out_size, otel_process_ctx_data data) {
369369
const char *pairs[] = {
@@ -387,14 +387,20 @@ static otel_process_ctx_result otel_process_ctx_encode_protobuf_payload(char **o
387387
if (!validation_result.success) return validation_result;
388388
}
389389

390-
size_t total_size = pairs_size + resources_pairs_size;
390+
size_t resource_size = pairs_size + resources_pairs_size;
391+
// ProcessContext wrapper: tag (1 byte) + resource length varint + resource content
392+
size_t total_size = 1 + protobuf_varint_size(resource_size) + resource_size;
391393

392394
char *encoded = (char *) calloc(total_size, 1);
393395
if (!encoded) {
394396
return (otel_process_ctx_result) {.success = false, .error_message = "Failed to allocate memory for payload (" __FILE__ ":" ADD_QUOTES(__LINE__) ")"};
395397
}
396398
char *ptr = encoded;
397399

400+
// ProcessContext.resource (field 1)
401+
write_protobuf_tag(&ptr, 1);
402+
write_protobuf_varint(&ptr, resource_size);
403+
398404
for (size_t i = 0; pairs[i * 2] != NULL; i++) {
399405
write_attribute(&ptr, pairs[i * 2], pairs[i * 2 + 1]);
400406
}
@@ -497,19 +503,28 @@ static otel_process_ctx_result otel_process_ctx_encode_protobuf_payload(char **o
497503

498504
*data_out = empty_data;
499505

506+
// Parse ProcessContext wrapper - expect field 1 (resource)
507+
uint8_t process_ctx_field;
508+
if (!read_protobuf_tag(&ptr, end_ptr, &process_ctx_field) || process_ctx_field != 1) return false;
509+
510+
uint16_t resource_len;
511+
if (!read_protobuf_varint(&ptr, end_ptr, &resource_len)) return false;
512+
char *resource_end = ptr + resource_len;
513+
if (resource_end > end_ptr) return false;
514+
500515
size_t resource_index = 0;
501516
size_t resource_capacity = 201; // Allocate space for 100 pairs + NULL terminator entry
502517
data_out->resources = (const char **) calloc(resource_capacity, sizeof(char *));
503518
if (data_out->resources == NULL) return false;
504519

505-
while (ptr < end_ptr) {
520+
while (ptr < resource_end) {
506521
uint8_t field_number;
507-
if (!read_protobuf_tag(&ptr, end_ptr, &field_number) || field_number != 1) return false;
522+
if (!read_protobuf_tag(&ptr, resource_end, &field_number) || field_number != 1) return false;
508523

509524
uint16_t kv_len;
510-
if (!read_protobuf_varint(&ptr, end_ptr, &kv_len)) return false;
525+
if (!read_protobuf_varint(&ptr, resource_end, &kv_len)) return false;
511526
char *kv_end = ptr + kv_len;
512-
if (kv_end > end_ptr) return false;
527+
if (kv_end > resource_end) return false;
513528

514529
bool key_found = false;
515530
bool value_found = false;

process-context/c-and-cpp/otel_process_ctx_dump.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ dd if="/proc/$pid/mem" bs=1 count="$payload_size" skip=$((16#$payload_ptr_hex))
7979

8080
if command -v protoc >/dev/null 2>&1; then
8181
echo "Protobuf decode:"
82-
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
82+
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
8383
else
8484
echo
8585
echo "protoc not available - skipping protobuf decode"
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2019, OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
syntax = "proto3";
16+
17+
// TODO: Is this in the right namespace? Since this is not only for profiling,
18+
// `opentelemetry.proto.profiles` doesn't seem the right place, but perhaps common ain't
19+
// it either? Feedback very welcome!
20+
package opentelemetry.proto.common.v1;
21+
22+
import "common.proto";
23+
import "resource.proto";
24+
25+
option csharp_namespace = "OpenTelemetry.Proto.Common.V1";
26+
option java_multiple_files = true;
27+
option java_package = "io.opentelemetry.proto.common.v1";
28+
option java_outer_classname = "ProcessContextProto";
29+
option go_package = "go.opentelemetry.io/proto/otlp/common/v1";
30+
31+
// ProcessContext represents the payload for the process context sharing mechanism.
32+
//
33+
// This message is designed to be published by OpenTelemetry SDKs via a memory-mapped
34+
// region, allowing external readers (such as the OpenTelemetry eBPF Profiler) to
35+
// discover and read resource attributes from instrumented processes without requiring
36+
// direct integration or process activity.
37+
message ProcessContext {
38+
// The resource attributes describing this process.
39+
//
40+
// Attribute keys MUST be unique (it is not allowed to have more than one
41+
// attribute with the same key). The behavior of software that receives
42+
// duplicated keys can be unpredictable.
43+
//
44+
// Attributes SHOULD follow OpenTelemetry semantic conventions where applicable.
45+
// See: https://opentelemetry.io/docs/specs/semconv/
46+
opentelemetry.proto.resource.v1.Resource resource = 1;
47+
48+
// Additional attributes to share with external readers that are not part of
49+
// the standard Resource. [Optional]
50+
//
51+
// This field allows publishers to include supplementary key-value pairs that
52+
// may be useful for external readers but are not part of the SDK's configured
53+
// Resource.
54+
repeated opentelemetry.proto.common.v1.KeyValue extra_attributes = 2;
55+
}

0 commit comments

Comments
 (0)