This is a reference implementation of the OpenTelemetry Process Context specification for C and C++.
The OpenTelemetry Process Context specification defines a standard mechanism for OpenTelemetry SDKs to publish process-level resource attributes (such as service name, version, environment, etc.) in a way that can be read by out-of-process consumers, such as the OpenTelemetry eBPF Profiler.
This reference implementation provides:
- A simple API (
otel_process_ctx.h) for publishing process context data - Linux-only implementation using anonymous memory mappings with the
OTEL_CTXsignature (with a no-op fallback for other operating systems) - C and C++ compatibility - the same header works for both languages
Use the provided build script (uses CMake):
./build.shThis will create a build/ directory containing:
libotel_process_ctx.so- Shared library (C)libotel_process_ctx.a- Static library (C)libotel_process_ctx_cpp.so- Shared library (C++)libotel_process_ctx_cpp.a- Static library (C++)libotel_process_ctx_noop.so- Shared no-op library (C)libotel_process_ctx_noop.a- Static no-op library (C)libotel_process_ctx_cpp_noop.so- Shared no-op library (C++)libotel_process_ctx_cpp_noop.a- Static no-op library (C++)example_ctx- Example programexample_ctx_noop- Example program (no-op variant)
(The "cpp" versions of the library are built with the C++ compiler instead of the C compiler to make sure we remain compatible on both)
- CMake 3.10 or newer
- GCC or Clang compiler with C11 and C++11 support
The implementation supports a no-op mode via compile-time definition:
OTEL_PROCESS_CTX_NOOP- Compiles no-op versions of all functions (useful for non-Linux platforms or when the feature is not needed)OTEL_PROCESS_CTX_NO_READ- Disables read support (reduces binary size if reading is not needed)
These can be set in your own build system as needed.
-
otel_process_ctx.h- Main header file with the complete C API. Include this in your application. Contains theotel_process_ctx_datastruct and functions for publishing, dropping, and reading process contexts. -
otel_process_ctx.c- C implementation of the process context. Contains the core logic for creating anonymous memory mappings, encoding data as Protocol Buffers, and managing the context lifecycle. -
otel_process_ctx.cpp- C++ implementation (identical to.cbut compiled as C++). This ensures the code works correctly when compiled with a C++ compiler.
-
example_ctx.c- Example program demonstrating how to use the API. Shows publishing, reading, updating, and forking scenarios. Can run in "keep-running" mode for testing with the dump script. -
otel_process_ctx_dump.sh- Bash script to inspect a published process context from outside the process. Takes a PID as argument and dumps the context structure and payload. Useful for validation and debugging. Linux-only.
-
CMakeLists.txt- CMake build configuration. Defines all library variants (C/C++, shared/static, normal/no-op) and the example program. -
build.sh- Simple build script that invokes CMake and make.
process_context.proto- OpenTelemetry Process Context protobuf definitionresource.proto- OpenTelemetry Resource protobuf definitioncommon.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.
- Include the header:
#include "otel_process_ctx.h"- Prepare your context data:
otel_process_ctx_data data = {
.deployment_environment_name = "production",
.service_instance_id = "123e4567-e89b-12d3-a456-426614174000",
.service_name = "my-service",
.service_version = "1.2.3",
.telemetry_sdk_language = "c",
.telemetry_sdk_version = "0.1.0",
.telemetry_sdk_name = "example-c",
.resources = NULL // Optional additional key-value pairs
};- Publish the context:
otel_process_ctx_result result = otel_process_ctx_publish(&data);
if (!result.success) {
fprintf(stderr, "Failed to publish context: %s\n", result.error_message);
return 1;
}- Drop the context when done:
if (!otel_process_ctx_drop_current()) {
fprintf(stderr, "Failed to drop context\n");
}You can add custom resource attributes using the resources field:
const char *custom_resources[] = {
"custom.key1", "value1",
"custom.key2", "value2",
NULL // Must be NULL-terminated
};
otel_process_ctx_data data = {
// ... other fields ...
.resources = custom_resources
};#ifndef OTEL_PROCESS_CTX_NO_READ
otel_process_ctx_read_result result = otel_process_ctx_read();
if (result.success) {
printf("Service: %s\n", result.data.service_name);
printf("Version: %s\n", result.data.service_version);
// Don't forget to free the allocated strings
otel_process_ctx_read_drop(&result);
} else {
fprintf(stderr, "Failed to read: %s\n", result.error_message);
}
#endifThe header is C++-compatible using extern "C" linkage. Usage is identical to C.
otel_process_ctx_publish()andotel_process_ctx_drop_current()are NOT thread-safe. Only call these from a single thread.otel_process_ctx_read()is thread-safe for reading, but assumes no concurrent mutations.
- The memory mapping is marked with
MADV_DONTFORK, so it does not propagate to child processes. - After forking, the child process should call
otel_process_ctx_publish()again with updated data (especially a newservice_instance_id). - The child process can optionally call
otel_process_ctx_drop_current()to clean up inherited memory allocations (the payload buffer).
All strings in otel_process_ctx_data must be:
- Non-NULL
- UTF-8 encoded
- No longer than 4096 bytes (per key/value)
Empty strings are allowed.
The otel_process_ctx_dump.sh script allows you to inspect a published process context from outside the process. This is useful for:
- Verifying that your application correctly publishes context
- Debugging context data
- Understanding the on-disk format
- Run your application (or the example):
./build/example_ctx --keep-runningThis will print the PID and keep running.
- Dump the context (requires root/sudo for reading
/proc/<pid>/mem):
sudo ./otel_process_ctx_dump.sh <pid>Found OTEL context for PID 267023
Start address: 756f28ce1000
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_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|
...
If protoc is installed and the proto files are available, the script will also decode the payload:
Protobuf decode:
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.name"
value {
string_value: "my-service"
}
}
...
- Bash shell
- Root/sudo access (to read
/proc/<pid>/mem) - Standard Linux utilities:
dd,hexdump,base64,date - Optional:
protocfor Protocol Buffers decoding
- Linux only: This implementation uses Linux-specific features (anonymous memory mappings,
prctl,/procfilesystem). - Kernel version: Works best on Linux 5.17+ (which supports named anonymous mappings), but includes fallback support for older kernels.
- No-op mode: On non-Linux platforms or when
OTEL_PROCESS_CTX_NOOP=1is defined, all functions become no-ops.
The example_ctx.c program demonstrates the complete lifecycle:
- Initial publish: Publishes a context with initial data
- Read and verify: Reads back the context to verify it was published correctly
- Update: Updates the context with new data
- Fork test: Forks a child process that publishes its own context with a different
service_instance_id - Cleanup: Both parent and child drop their contexts before exiting
Run the example:
./build/example_ctxOr run it continuously for testing with the dump script:
./build/example_ctx --keep-runningWhen integrating this into an OpenTelemetry SDK or application:
-
Call publish early: Call
otel_process_ctx_publish()as early as possible in your application lifecycle, once you have the service configuration available. -
Update on configuration changes: If your service attributes change at runtime, call
otel_process_ctx_publish()again with the updated data. -
Handle forks: After forking, update the
service_instance_idand callotel_process_ctx_publish()in the child process. -
Clean up on exit: Call
otel_process_ctx_drop_current()during shutdown (optional but recommended for clean exit). -
Check result codes: Always check the
successfield of the result and log errors appropriately. -
Consider no-op builds: For non-Linux platforms, use the no-op variant or define
OTEL_PROCESS_CTX_NOOP=1.