diff --git a/Cargo.lock b/Cargo.lock index 519b6b857f..91ac6a212c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3163,7 +3163,9 @@ name = "libdd-library-config" version = "1.0.0" dependencies = [ "anyhow", + "libdd-trace-protobuf", "memfd", + "prost", "rand 0.8.5", "rmp", "rmp-serde", @@ -3184,6 +3186,7 @@ dependencies = [ "libdd-common", "libdd-common-ffi", "libdd-library-config", + "libdd-trace-protobuf", "tempfile", ] diff --git a/libdd-library-config-ffi/Cargo.toml b/libdd-library-config-ffi/Cargo.toml index 25801392b5..16fee0086c 100644 --- a/libdd-library-config-ffi/Cargo.toml +++ b/libdd-library-config-ffi/Cargo.toml @@ -15,6 +15,7 @@ bench = false libdd-common = { path = "../libdd-common" } libdd-common-ffi = { path = "../libdd-common-ffi", default-features = false } libdd-library-config = { path = "../libdd-library-config" } +libdd-trace-protobuf = { version = "1.0.0", path = "../libdd-trace-protobuf" } anyhow = "1.0" constcat = "0.4.1" diff --git a/libdd-library-config-ffi/src/lib.rs b/libdd-library-config-ffi/src/lib.rs index 9e53349f9d..0705553d9c 100644 --- a/libdd-library-config-ffi/src/lib.rs +++ b/libdd-library-config-ffi/src/lib.rs @@ -1,6 +1,7 @@ // Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/ // SPDX-License-Identifier: Apache-2.0 +pub mod otel_process_ctx; pub mod tracer_metadata; use libdd_common_ffi::{self as ffi, slice::AsBytes, CString, CharSlice, Error}; diff --git a/libdd-library-config-ffi/src/otel_process_ctx.rs b/libdd-library-config-ffi/src/otel_process_ctx.rs new file mode 100644 index 0000000000..8ae19810a1 --- /dev/null +++ b/libdd-library-config-ffi/src/otel_process_ctx.rs @@ -0,0 +1,403 @@ +// Copyright 2026-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +use libdd_common_ffi::VoidResult; +use libdd_library_config::otel_process_ctx; +use libdd_trace_protobuf::opentelemetry::proto::common::v1::{ + any_value, AnyValue, KeyValue, ProcessContext, +}; +use std::{ffi::CStr, os::raw::c_char}; + +fn mk_key_value(key: &str, value: any_value::Value) -> KeyValue { + KeyValue { + key: key.to_owned(), + value: Some(AnyValue { value: Some(value) }), + key_ref: 0, + } +} + +/// Allocates and returns a pointer to a new, empty [`ProcessContext`] on the heap. +/// +/// The caller is responsible for calling [`ddog_otel_process_ctxt_free`] to deallocate the memory. +/// +/// # Safety +/// +/// No safety requirements. +/// +/// # Returns +/// +/// A non-null pointer to a newly allocated [`ProcessContext`] instance. +#[no_mangle] +pub unsafe extern "C" fn ddog_otel_process_ctxt_new() -> *mut ProcessContext { + Box::into_raw(Box::new(ProcessContext::default())) +} + +/// Frees a [`ProcessContext`] instance previously allocated with [`ddog_otel_process_ctxt_new`]. +/// +/// # Safety +/// +/// - `ctxt` must be a valid pointer previously returned by [`ddog_otel_process_ctxt_new`] +/// - `ctxt` must NOT have been already freed by this function (double-free) +#[no_mangle] +pub unsafe extern "C" fn ddog_otel_process_ctxt_free(ctxt: *mut ProcessContext) { + if !ctxt.is_null() { + // Safety: `ctxt` is required to have come from `ddog_otel_process_ctxt_new`, which + // allocates through `Box` + let _ = unsafe { Box::from_raw(ctxt) }; + } +} + +/// Sets a string attribute on the resource of a [`ProcessContext`]. +/// +/// A given attribute must be set at most once for a given process context object, or the +/// interpretation by the reader is not well-defined. +/// +/// If any of the provided strings is not valid UTF8, this function does nothing. +/// +/// # Safety +/// +/// - `ctxt` must be a non-null pointer to a [`ProcessContext`] object obtained form +/// [`ddog_otel_process_ctxt_new`]. +/// - `key` and `value` must point to null-terminated C strings. +#[no_mangle] +pub unsafe extern "C" fn ddog_otel_process_ctxt_set_resource_attr_str( + ctxt: *mut ProcessContext, + key: *const c_char, + value: *const c_char, +) { + if ctxt.is_null() || key.is_null() || value.is_null() { + return; + } + + // Safety: this function requires `key` to be a valid null-terminated UTF-8 C string + let key = unsafe { CStr::from_ptr(key).to_str() }; + let Ok(key_str) = key else { + return; + }; + + // Safety: this function requires `value` to be a valid null-terminated UTF-8 C string + let value = unsafe { CStr::from_ptr(value).to_str() }; + let Ok(value_str) = value else { + return; + }; + + // Safety: this function requires `ctxt` to be a valid non-null pointer to a + // `ProcessContext` + let ctxt = unsafe { &mut *ctxt }; + + ctxt.resource + .get_or_insert_default() + .attributes + .push(mk_key_value( + key_str, + any_value::Value::StringValue(value_str.to_owned()), + )); +} + +/// Sets an integer attribute on the resource of a [`ProcessContext`]. +/// +/// A given attribute must be set at most once for a given process context object, or the +/// interpretation by the reader is not well-defined. +/// +/// If `key` isn't valid UTF8, this function does nothing. +/// +/// # Safety +/// +/// - `ctxt` must be a non-null pointer to a [`ProcessContext`] object obtained form +/// [`ddog_otel_process_ctxt_new`]. +/// - `key` must point to a null-terminated C string. +#[no_mangle] +pub unsafe extern "C" fn ddog_otel_process_ctxt_set_resource_attr_int( + ctxt: *mut ProcessContext, + key: *const c_char, + value: i64, +) { + if ctxt.is_null() || key.is_null() { + return; + } + + // Safety: this function requires `key` to be a valid null-terminated UTF-8 C string + let key = unsafe { CStr::from_ptr(key).to_str() }; + let Ok(key_str) = key else { + return; + }; + + // Safety: this function requires `ctxt` to be a valid non-null pointer to a + // `ProcessContext` + let ctxt = unsafe { &mut *ctxt }; + ctxt.resource + .get_or_insert_default() + .attributes + .push(mk_key_value(key_str, any_value::Value::IntValue(value))); +} + +/// Sets a double attribute on the resource of a [`ProcessContext`]. +/// +/// A given attribute must be set at most once for a given process context object, or the +/// interpretation by the reader is not well-defined. +/// +/// If `key` isn't valid UTF8, this function does nothing. +/// +/// # Safety +/// +/// - `ctxt` must be a non-null pointer to a [`ProcessContext`] object obtained form +/// [`ddog_otel_process_ctxt_new`]. +/// - `key` must point to a null-terminated C string. +#[no_mangle] +pub unsafe extern "C" fn ddog_otel_process_ctxt_set_resource_attr_double( + ctxt: *mut ProcessContext, + key: *const c_char, + value: f64, +) { + if ctxt.is_null() || key.is_null() { + return; + } + + // Safety: this function requires `key` to be a valid null-terminated UTF-8 C string + let key = unsafe { CStr::from_ptr(key).to_str() }; + let Ok(key_str) = key else { + return; + }; + + // Safety: this function requires `ctxt` to be a valid non-null pointer to a + // `ProcessContext` + let ctxt = unsafe { &mut *ctxt }; + ctxt.resource + .get_or_insert_default() + .attributes + .push(mk_key_value(key_str, any_value::Value::DoubleValue(value))); +} + +/// Sets a boolean attribute on the resource of a [`ProcessContext`]. +/// +/// A given attribute must be set at most once for a given process context object, or the +/// interpretation by the reader is not well-defined. +/// +/// If `key` isn't valid UTF8, this function does nothing. +/// +/// # Safety +/// +/// - `ctxt` must be a non-null pointer to a [`ProcessContext`] object obtained form +/// [`ddog_otel_process_ctxt_new`]. +/// - `key` must point to a null-terminated C string. +#[no_mangle] +pub unsafe extern "C" fn ddog_otel_process_ctxt_set_resource_attr_bool( + ctxt: *mut ProcessContext, + key: *const c_char, + value: bool, +) { + if ctxt.is_null() || key.is_null() { + return; + } + + // Safety: this function requires `key` to be a valid null-terminated UTF-8 C string + let key = unsafe { CStr::from_ptr(key).to_str() }; + let Ok(key_str) = key else { + return; + }; + + // Safety: this function requires `ctxt` to be a valid non-null pointer to a + // `ProcessContext` + let ctxt = unsafe { &mut *ctxt }; + ctxt.resource + .get_or_insert_default() + .attributes + .push(mk_key_value(key_str, any_value::Value::BoolValue(value))); +} + +/// Sets a string extra attribute on a [`ProcessContext`]. +/// +/// A given attribute must be set at most once for a given process context object, or the +/// interpretation by the reader is not well-defined. +/// +/// If any of the provided strings is not valid UTF8, this function does nothing. +/// +/// # Safety +/// +/// - `ctxt` must be a non-null pointer to a [`ProcessContext`] object obtained from +/// [`ddog_otel_process_ctxt_new`]. +/// - `key` and `value` must point to null-terminated C strings. +#[no_mangle] +pub unsafe extern "C" fn ddog_otel_process_ctxt_set_extra_attr_str( + ctxt: *mut ProcessContext, + key: *const c_char, + value: *const c_char, +) { + if ctxt.is_null() || key.is_null() || value.is_null() { + return; + } + + // Safety: this function requires `key` to be a valid null-terminated UTF-8 C string + let key = unsafe { CStr::from_ptr(key).to_str() }; + let Ok(key_str) = key else { + return; + }; + + // Safety: this function requires `value` to be a valid null-terminated UTF-8 C string + let value = unsafe { CStr::from_ptr(value).to_str() }; + let Ok(value_str) = value else { + return; + }; + + // Safety: this function requires `ctxt` to be a valid non-null pointer to a + // `ProcessContext` + let ctxt = unsafe { &mut *ctxt }; + + ctxt.extra_attributes.push(mk_key_value( + key_str, + any_value::Value::StringValue(value_str.to_owned()), + )); +} + +/// Sets an integer extra attribute on a [`ProcessContext`]. +/// +/// A given attribute must be set at most once for a given process context object, or the +/// interpretation by the reader is not well-defined. +/// +/// If `key` isn't valid UTF8, this function does nothing. +/// +/// # Safety +/// +/// - `ctxt` must be a non-null pointer to a [`ProcessContext`] object obtained form +/// [`ddog_otel_process_ctxt_new`]. +/// - `key` must point to a null-terminated C string. +#[no_mangle] +pub unsafe extern "C" fn ddog_otel_process_ctxt_set_extra_attr_int( + ctxt: *mut ProcessContext, + key: *const c_char, + value: i64, +) { + if ctxt.is_null() || key.is_null() { + return; + } + + // Safety: this function requires `key` to be a valid null-terminated UTF-8 C string + let key = unsafe { CStr::from_ptr(key).to_str() }; + let Ok(key_str) = key else { + return; + }; + + // Safety: this function requires `ctxt` to be a valid non-null pointer to a + // `ProcessContext` + let ctxt = unsafe { &mut *ctxt }; + + ctxt.extra_attributes + .push(mk_key_value(key_str, any_value::Value::IntValue(value))); +} + +/// Sets a double extra attribute on a [`ProcessContext`]. +/// +/// A given attribute must be set at most once for a given process context object, or the +/// interpretation by the reader is not well-defined. +/// +/// If `key` isn't valid UTF8, this function does nothing. +/// +/// # Safety +/// +/// - `ctxt` must be a non-null pointer to a [`ProcessContext`] object obtained form +/// [`ddog_otel_process_ctxt_new`]. +/// - `key` must point to a null-terminated C string. +#[no_mangle] +pub unsafe extern "C" fn ddog_otel_process_ctxt_set_extra_attr_double( + ctxt: *mut ProcessContext, + key: *const c_char, + value: f64, +) { + if ctxt.is_null() || key.is_null() { + return; + } + + // Safety: this function requires `key` to be a valid null-terminated UTF-8 C string + let key = unsafe { CStr::from_ptr(key).to_str() }; + let Ok(key_str) = key else { + return; + }; + + // Safety: this function requires `ctxt` to be a valid non-null pointer to a + // `ProcessContext` + let ctxt = unsafe { &mut *ctxt }; + + ctxt.extra_attributes + .push(mk_key_value(key_str, any_value::Value::DoubleValue(value))); +} + +/// Sets a boolean extra attribute on a [`ProcessContext`]. +/// +/// A given attribute must be set at most once for a given process context object, or the +/// interpretation by the reader is not well-defined. +/// +/// If `key` isn't valid UTF8, this function does nothing. +/// +/// # Safety +/// +/// - `ctxt` must be a non-null pointer to a [`ProcessContext`] object obtained form +/// [`ddog_otel_process_ctxt_new`]. +/// - `key` must point to a null-terminated C string. +#[no_mangle] +pub unsafe extern "C" fn ddog_otel_process_ctxt_set_extra_attr_bool( + ctxt: *mut ProcessContext, + key: *const c_char, + value: bool, +) { + if ctxt.is_null() || key.is_null() { + return; + } + + // Safety: this function requires `key` to be a valid null-terminated UTF-8 C string + let key = unsafe { CStr::from_ptr(key).to_str() }; + let Ok(key_str) = key else { + return; + }; + + // Safety: this function requires `ctxt` to be a valid non-null pointer to a + // `ProcessContext` + let ctxt = unsafe { &mut *ctxt }; + + ctxt.extra_attributes + .push(mk_key_value(key_str, any_value::Value::BoolValue(value))); +} + +/// Publishes or updates the process context so it is visible to external readers via a +/// named memory mapping. +/// +/// If this is the first call to [`ddog_otel_process_ctxt_publish`], or if +/// [`ddog_otel_process_ctxt_unpublish`] was called last, a new mapping is created following the +/// Publish protocol. +/// Otherwise, the mapping is updated following the Update protocol. +/// +/// The [`ProcessContext`] pointed to by `ctxt` is encoded directly into the mapping. The original +/// data is left unchanged. The pointer remains valid, is still owned by the caller after this +/// call, and must be freed accordingly. The context pointed to by `ctxt` can be freed as soon as +/// this function returns. +/// +/// # Safety +/// +/// - `ctxt` must be a valid non-null pointer to [`ProcessContext`]. +#[cfg(target_os = "linux")] +#[no_mangle] +pub unsafe extern "C" fn ddog_otel_process_ctxt_publish(ctxt: *const ProcessContext) -> VoidResult { + if ctxt.is_null() { + return VoidResult::Err( + anyhow::anyhow!("null pointer passed to ddog_otel_process_ctxt_publish").into(), + ); + } + + // Safety: this function requires `ctxt` to be a valid non-null pointer to a + // `ProcessContext` + let ctxt = unsafe { &*ctxt }; + otel_process_ctx::linux::publish(ctxt).into() +} + +/// Unmaps the memory region used to share the process context and closes the associated +/// file descriptor, if any. If no context has been published, this is a no-op. +/// +/// A subsequent call to [`ddog_otel_process_ctxt_publish`] will create a new mapping. +/// +/// # Safety +/// +/// No safety requirements. +#[cfg(target_os = "linux")] +#[no_mangle] +pub unsafe extern "C" fn ddog_otel_process_ctxt_unpublish() -> VoidResult { + otel_process_ctx::linux::unpublish().into() +} diff --git a/libdd-library-config/Cargo.toml b/libdd-library-config/Cargo.toml index d8a171e875..a23f79c182 100644 --- a/libdd-library-config/Cargo.toml +++ b/libdd-library-config/Cargo.toml @@ -17,12 +17,15 @@ bench = false [dependencies] serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.9.34" +prost = "0.14.1" anyhow = "1.0" rand = "0.8.3" rmp = "0.8.14" rmp-serde = "1.3.0" +libdd-trace-protobuf = { version = "1.0.0", path = "../libdd-trace-protobuf" } + [dev-dependencies] tempfile = { version = "3.3" } serial_test = "3.2" diff --git a/libdd-library-config/src/otel_process_ctx.rs b/libdd-library-config/src/otel_process_ctx.rs index 636b92837d..4ad1cb482f 100644 --- a/libdd-library-config/src/otel_process_ctx.rs +++ b/libdd-library-config/src/otel_process_ctx.rs @@ -30,9 +30,12 @@ pub mod linux { use rustix::{ fs::{ftruncate, memfd_create, MemfdFlags}, mm::{madvise, mmap, mmap_anonymous, munmap, Advice, MapFlags, ProtFlags}, - process::set_virtual_memory_region_name, + process::{getpid, set_virtual_memory_region_name, Pid}, }; + use libdd_trace_protobuf::opentelemetry::proto::common::v1::ProcessContext; + use prost::Message; + /// Current version of the process context format pub const PROCESS_CTX_VERSION: u32 = 2; /// Signature bytes for identifying process context mappings @@ -213,6 +216,9 @@ pub mod linux { /// or drop). #[allow(unused)] payload: Vec, + /// The process id of the last publisher. This is useful to detect forks(), and publish a + /// new context accordingly. + pid: Pid, } impl ProcessContextHandle { @@ -264,7 +270,11 @@ pub mod linux { let _ = mapping.set_name(); - Ok(ProcessContextHandle { mapping, payload }) + Ok(ProcessContextHandle { + mapping, + payload, + pid: getpid(), + }) } /// Updates the context after initial publication. @@ -341,15 +351,50 @@ pub mod linux { /// Publishes or updates the process context for it to be visible by external readers. /// - /// If this is the first publication, or if [unpublish] has been called last, this will follow - /// the Publish protocol of the process context specification. + /// If any of the following condition holds: + /// + /// - this is the first publication + /// - [unpublish] has been called last + /// - the previous context has been published from a different process id (that is, a `fork()` + /// happened and we're the child process) + /// + /// Then we follow the Publish protocol of the OTel process context specification (allocating a + /// fresh mapping). /// - /// Otherwise, the context is updated following the Update protocol. - pub fn publish(payload: Vec) -> anyhow::Result<()> { + /// Otherwise, if a context has been previously published from the same process and hasn't been + /// unpublished since, we follow the Update protocol. + /// + /// # Fork safety + /// + /// If we're a forked children of the original publisher, we are extremely restricted in the + /// set of operations that we can do (we must be async-signal-safe). On paper, heap allocation + /// is Undefined Behavior, for example. We assume that a forking runtime (such as Python or + /// Ruby) that doesn't follow with an immediate `exec` is already "taking that risk", so to + /// speak (typically, if no thread is ever spawned before the fork, things are mostly fine). + #[inline] + pub fn publish(context: &ProcessContext) -> anyhow::Result<()> { + publish_raw_payload(context.encode_to_vec()) + } + + fn publish_raw_payload(payload: Vec) -> anyhow::Result<()> { let mut guard = lock_context_handle()?; match &mut *guard { - Some(handler) => handler.update(payload), + Some(handler) if handler.pid == getpid() => handler.update(payload), + Some(handler) => { + let mut local_handler = ProcessContextHandle::publish(payload)?; + // If we've been forked, we need to prevent the mapping from being dropped + // normally, as it would try to unmap a region that isn't mapped anymore in the + // child process, or worse, could have been remapped to something else in the + // meantime. + // + // To do so, we get the old handler back in `local_handler` and prevent `mapping` + // from being dropped specifically. + std::mem::swap(&mut local_handler, handler); + let _: ManuallyDrop = ManuallyDrop::new(local_handler.mapping); + + Ok(()) + } None => { *guard = Some(ProcessContextHandle::publish(payload)?); Ok(()) @@ -460,7 +505,7 @@ pub mod linux { let payload_v1 = "example process context payload"; let payload_v2 = "another example process context payload of different size"; - super::publish(payload_v1.as_bytes().to_vec()) + super::publish_raw_payload(payload_v1.as_bytes().to_vec()) .expect("couldn't publish the process context"); let header = read_process_context().expect("couldn't read back the process context"); @@ -485,7 +530,8 @@ pub mod linux { let published_at_ns_v1 = header.published_at_ns; // Ensure the clock advances so the updated timestamp is strictly greater std::thread::sleep(std::time::Duration::from_nanos(10)); - super::publish(payload_v2.as_bytes().to_vec()) + + super::publish_raw_payload(payload_v2.as_bytes().to_vec()) .expect("couldn't update the process context"); let header = read_process_context().expect("couldn't read back the process context"); @@ -518,7 +564,7 @@ pub mod linux { fn unpublish_process_context() { let payload = "example process context payload"; - super::publish(payload.as_bytes().to_vec()) + super::publish_raw_payload(payload.as_bytes().to_vec()) .expect("couldn't publish the process context"); // The mapping must be discoverable right after publishing diff --git a/libdd-trace-protobuf/build.rs b/libdd-trace-protobuf/build.rs index dcecdad2f8..0c83c4ed09 100644 --- a/libdd-trace-protobuf/build.rs +++ b/libdd-trace-protobuf/build.rs @@ -261,6 +261,7 @@ fn generate_protobuf() { "src/pb/span.proto", "src/pb/stats.proto", "src/pb/remoteconfig.proto", + "src/pb/opentelemetry/proto/common/v1/process_context.proto", "src/pb/idx/tracer_payload.proto", "src/pb/idx/span.proto", ], diff --git a/libdd-trace-protobuf/src/_includes.rs b/libdd-trace-protobuf/src/_includes.rs index f2bd2da69f..1628f52c39 100644 --- a/libdd-trace-protobuf/src/_includes.rs +++ b/libdd-trace-protobuf/src/_includes.rs @@ -2,6 +2,20 @@ // SPDX-License-Identifier: Apache-2.0 // This file is @generated by prost-build. +pub mod opentelemetry { + pub mod proto { + pub mod common { + pub mod v1 { + include!("opentelemetry.proto.common.v1.rs"); + } + } + pub mod resource { + pub mod v1 { + include!("opentelemetry.proto.resource.v1.rs"); + } + } + } +} pub mod pb { include!("pb.rs"); pub mod idx { diff --git a/libdd-trace-protobuf/src/opentelemetry.proto.common.v1.rs b/libdd-trace-protobuf/src/opentelemetry.proto.common.v1.rs new file mode 100644 index 0000000000..d363f295ee --- /dev/null +++ b/libdd-trace-protobuf/src/opentelemetry.proto.common.v1.rs @@ -0,0 +1,183 @@ +// This file is @generated by prost-build. +/// Represents any type of attribute value. AnyValue may contain a +/// primitive value such as a string or integer or it may contain an arbitrary nested +/// object containing arrays, key-value lists and primitives. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AnyValue { + /// The value is one of the listed fields. It is valid for all values to be unspecified + /// in which case this AnyValue is considered to be "empty". + #[prost(oneof = "any_value::Value", tags = "1, 2, 3, 4, 5, 6, 7, 8")] + pub value: ::core::option::Option, +} +/// Nested message and enum types in `AnyValue`. +pub mod any_value { + /// The value is one of the listed fields. It is valid for all values to be unspecified + /// in which case this AnyValue is considered to be "empty". + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Value { + #[prost(string, tag = "1")] + StringValue(::prost::alloc::string::String), + #[prost(bool, tag = "2")] + BoolValue(bool), + #[prost(int64, tag = "3")] + IntValue(i64), + #[prost(double, tag = "4")] + DoubleValue(f64), + #[prost(message, tag = "5")] + ArrayValue(super::ArrayValue), + #[prost(message, tag = "6")] + KvlistValue(super::KeyValueList), + #[prost(bytes, tag = "7")] + BytesValue(::prost::alloc::vec::Vec), + /// Reference to the string value in ProfilesDictionary.string_table. + /// + /// Note: This is currently used exclusively in the Profiling signal. + /// Implementers of OTLP receivers for signals other than Profiling should + /// treat the presence of this value as a non-fatal issue. + /// Log an error or warning indicating an unexpected field intended for the + /// Profiling signal and process the data as if this value were absent or + /// empty, ignoring its semantic content for the non-Profiling signal. + /// + /// Status: \[Development\] + #[prost(int32, tag = "8")] + StringValueRef(i32), + } +} +/// ArrayValue is a list of AnyValue messages. We need ArrayValue as a message +/// since oneof in AnyValue does not allow repeated fields. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ArrayValue { + /// Array of values. The array may be empty (contain 0 elements). + #[prost(message, repeated, tag = "1")] + pub values: ::prost::alloc::vec::Vec, +} +/// KeyValueList is a list of KeyValue messages. We need KeyValueList as a message +/// since `oneof` in AnyValue does not allow repeated fields. Everywhere else where we need +/// a list of KeyValue messages (e.g. in Span) we use `repeated KeyValue` directly to +/// avoid unnecessary extra wrapping (which slows down the protocol). The 2 approaches +/// are semantically equivalent. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct KeyValueList { + /// A collection of key/value pairs of key-value pairs. The list may be empty (may + /// contain 0 elements). + /// + /// The keys MUST be unique (it is not allowed to have more than one + /// value with the same key). + /// The behavior of software that receives duplicated keys can be unpredictable. + #[prost(message, repeated, tag = "1")] + pub values: ::prost::alloc::vec::Vec, +} +/// Represents a key-value pair that is used to store Span attributes, Link +/// attributes, etc. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct KeyValue { + /// The key name of the pair. + /// key_ref MUST NOT be set if key is used. + #[prost(string, tag = "1")] + pub key: ::prost::alloc::string::String, + /// The value of the pair. + #[prost(message, optional, tag = "2")] + pub value: ::core::option::Option, + /// Reference to the string key in ProfilesDictionary.string_table. + /// key MUST NOT be set if key_ref is used. + /// + /// Note: This is currently used exclusively in the Profiling signal. + /// Implementers of OTLP receivers for signals other than Profiling should + /// treat the presence of this key as a non-fatal issue. + /// Log an error or warning indicating an unexpected field intended for the + /// Profiling signal and process the data as if this value were absent or + /// empty, ignoring its semantic content for the non-Profiling signal. + /// + /// Status: \[Development\] + #[prost(int32, tag = "3")] + pub key_ref: i32, +} +/// InstrumentationScope is a message representing the instrumentation scope information +/// such as the fully qualified name and version. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct InstrumentationScope { + /// A name denoting the Instrumentation scope. + /// An empty instrumentation scope name means the name is unknown. + #[prost(string, tag = "1")] + pub name: ::prost::alloc::string::String, + /// Defines the version of the instrumentation scope. + /// An empty instrumentation scope version means the version is unknown. + #[prost(string, tag = "2")] + pub version: ::prost::alloc::string::String, + /// Additional attributes that describe the scope. \[Optional\]. + /// 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. + #[prost(message, repeated, tag = "3")] + pub attributes: ::prost::alloc::vec::Vec, + /// The number of attributes that were discarded. Attributes + /// can be discarded because their keys are too long or because there are too many + /// attributes. If this value is 0, then no attributes were dropped. + #[prost(uint32, tag = "4")] + pub dropped_attributes_count: u32, +} +/// A reference to an Entity. +/// Entity represents an object of interest associated with produced telemetry: e.g spans, metrics, profiles, or logs. +/// +/// Status: \[Development\] +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct EntityRef { + /// The Schema URL, if known. This is the identifier of the Schema that the entity data + /// is recorded in. To learn more about Schema URL see + /// + /// + /// This schema_url applies to the data in this message and to the Resource attributes + /// referenced by id_keys and description_keys. + /// TODO: discuss if we are happy with this somewhat complicated definition of what + /// the schema_url applies to. + /// + /// This field obsoletes the schema_url field in ResourceMetrics/ResourceSpans/ResourceLogs. + #[prost(string, tag = "1")] + pub schema_url: ::prost::alloc::string::String, + /// Defines the type of the entity. MUST not change during the lifetime of the entity. + /// For example: "service" or "host". This field is required and MUST not be empty + /// for valid entities. + #[prost(string, tag = "2")] + pub r#type: ::prost::alloc::string::String, + /// Attribute Keys that identify the entity. + /// MUST not change during the lifetime of the entity. The Id must contain at least one attribute. + /// These keys MUST exist in the containing {message}.attributes. + #[prost(string, repeated, tag = "3")] + pub id_keys: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Descriptive (non-identifying) attribute keys of the entity. + /// MAY change over the lifetime of the entity. MAY be empty. + /// These attribute keys are not part of entity's identity. + /// These keys MUST exist in the containing {message}.attributes. + #[prost(string, repeated, tag = "4")] + pub description_keys: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +/// 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. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct 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: + #[prost(message, optional, tag = "1")] + pub resource: ::core::option::Option, + /// 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. + /// + /// Consider adding any keys here to the profiles semantic conventions in + /// + #[prost(message, repeated, tag = "2")] + pub extra_attributes: ::prost::alloc::vec::Vec, +} diff --git a/libdd-trace-protobuf/src/opentelemetry.proto.resource.v1.rs b/libdd-trace-protobuf/src/opentelemetry.proto.resource.v1.rs new file mode 100644 index 0000000000..32b9ceff90 --- /dev/null +++ b/libdd-trace-protobuf/src/opentelemetry.proto.resource.v1.rs @@ -0,0 +1,22 @@ +// This file is @generated by prost-build. +/// Resource information. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Resource { + /// Set of attributes that describe the resource. + /// 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. + #[prost(message, repeated, tag = "1")] + pub attributes: ::prost::alloc::vec::Vec, + /// The number of dropped attributes. If the value is 0, then + /// no attributes were dropped. + #[prost(uint32, tag = "2")] + pub dropped_attributes_count: u32, + /// Set of entities that participate in this Resource. + /// + /// Note: keys in the references MUST exist in attributes of this message. + /// + /// Status: \[Development\] + #[prost(message, repeated, tag = "3")] + pub entity_refs: ::prost::alloc::vec::Vec, +} diff --git a/libdd-trace-protobuf/src/pb/opentelemetry/proto/common/v1/common.proto b/libdd-trace-protobuf/src/pb/opentelemetry/proto/common/v1/common.proto new file mode 100644 index 0000000000..24279c2103 --- /dev/null +++ b/libdd-trace-protobuf/src/pb/opentelemetry/proto/common/v1/common.proto @@ -0,0 +1,157 @@ +// This file was vendored from open-telemetry/opentelemetry-proto at commit +// 1e725b853bc8f6b46ee62e8232e4c83017b9536f. + +// 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.common.v1; + +option csharp_namespace = "OpenTelemetry.Proto.Common.V1"; +option java_multiple_files = true; +option java_package = "io.opentelemetry.proto.common.v1"; +option java_outer_classname = "CommonProto"; +option go_package = "go.opentelemetry.io/proto/otlp/common/v1"; + +// Represents any type of attribute value. AnyValue may contain a +// primitive value such as a string or integer or it may contain an arbitrary nested +// object containing arrays, key-value lists and primitives. +message AnyValue { + // The value is one of the listed fields. It is valid for all values to be unspecified + // in which case this AnyValue is considered to be "empty". + oneof value { + string string_value = 1; + bool bool_value = 2; + int64 int_value = 3; + double double_value = 4; + ArrayValue array_value = 5; + KeyValueList kvlist_value = 6; + bytes bytes_value = 7; + // Reference to the string value in ProfilesDictionary.string_table. + // + // Note: This is currently used exclusively in the Profiling signal. + // Implementers of OTLP receivers for signals other than Profiling should + // treat the presence of this value as a non-fatal issue. + // Log an error or warning indicating an unexpected field intended for the + // Profiling signal and process the data as if this value were absent or + // empty, ignoring its semantic content for the non-Profiling signal. + // + // Status: [Development] + int32 string_value_ref = 8; + } +} + +// ArrayValue is a list of AnyValue messages. We need ArrayValue as a message +// since oneof in AnyValue does not allow repeated fields. +message ArrayValue { + // Array of values. The array may be empty (contain 0 elements). + repeated AnyValue values = 1; +} + +// KeyValueList is a list of KeyValue messages. We need KeyValueList as a message +// since `oneof` in AnyValue does not allow repeated fields. Everywhere else where we need +// a list of KeyValue messages (e.g. in Span) we use `repeated KeyValue` directly to +// avoid unnecessary extra wrapping (which slows down the protocol). The 2 approaches +// are semantically equivalent. +message KeyValueList { + // A collection of key/value pairs of key-value pairs. The list may be empty (may + // contain 0 elements). + // + // The keys MUST be unique (it is not allowed to have more than one + // value with the same key). + // The behavior of software that receives duplicated keys can be unpredictable. + repeated KeyValue values = 1; +} + +// Represents a key-value pair that is used to store Span attributes, Link +// attributes, etc. +message KeyValue { + // The key name of the pair. + // key_ref MUST NOT be set if key is used. + string key = 1; + + // The value of the pair. + AnyValue value = 2; + + // Reference to the string key in ProfilesDictionary.string_table. + // key MUST NOT be set if key_ref is used. + // + // Note: This is currently used exclusively in the Profiling signal. + // Implementers of OTLP receivers for signals other than Profiling should + // treat the presence of this key as a non-fatal issue. + // Log an error or warning indicating an unexpected field intended for the + // Profiling signal and process the data as if this value were absent or + // empty, ignoring its semantic content for the non-Profiling signal. + // + // Status: [Development] + int32 key_ref = 3; +} + +// InstrumentationScope is a message representing the instrumentation scope information +// such as the fully qualified name and version. +message InstrumentationScope { + // A name denoting the Instrumentation scope. + // An empty instrumentation scope name means the name is unknown. + string name = 1; + + // Defines the version of the instrumentation scope. + // An empty instrumentation scope version means the version is unknown. + string version = 2; + + // Additional attributes that describe the scope. [Optional]. + // 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. + repeated KeyValue attributes = 3; + + // The number of attributes that were discarded. Attributes + // can be discarded because their keys are too long or because there are too many + // attributes. If this value is 0, then no attributes were dropped. + uint32 dropped_attributes_count = 4; +} + +// A reference to an Entity. +// Entity represents an object of interest associated with produced telemetry: e.g spans, metrics, profiles, or logs. +// +// Status: [Development] +message EntityRef { + // The Schema URL, if known. This is the identifier of the Schema that the entity data + // is recorded in. To learn more about Schema URL see + // https://opentelemetry.io/docs/specs/otel/schemas/#schema-url + // + // This schema_url applies to the data in this message and to the Resource attributes + // referenced by id_keys and description_keys. + // TODO: discuss if we are happy with this somewhat complicated definition of what + // the schema_url applies to. + // + // This field obsoletes the schema_url field in ResourceMetrics/ResourceSpans/ResourceLogs. + string schema_url = 1; + + // Defines the type of the entity. MUST not change during the lifetime of the entity. + // For example: "service" or "host". This field is required and MUST not be empty + // for valid entities. + string type = 2; + + // Attribute Keys that identify the entity. + // MUST not change during the lifetime of the entity. The Id must contain at least one attribute. + // These keys MUST exist in the containing {message}.attributes. + repeated string id_keys = 3; + + // Descriptive (non-identifying) attribute keys of the entity. + // MAY change over the lifetime of the entity. MAY be empty. + // These attribute keys are not part of entity's identity. + // These keys MUST exist in the containing {message}.attributes. + repeated string description_keys = 4; +} diff --git a/libdd-trace-protobuf/src/pb/opentelemetry/proto/common/v1/process_context.proto b/libdd-trace-protobuf/src/pb/opentelemetry/proto/common/v1/process_context.proto new file mode 100644 index 0000000000..b50f3fe44c --- /dev/null +++ b/libdd-trace-protobuf/src/pb/opentelemetry/proto/common/v1/process_context.proto @@ -0,0 +1,62 @@ +// This definition has been taken from ongoing OTEP +// https://github.com/open-telemetry/opentelemetry-specification/pull/4719 at +// commit 71fce3613efe8dc1f446d865fe979af21a4e0a7e (and minimally patched) + +// 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"; + +// 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 "opentelemetry/proto/common/v1/common.proto"; +import "opentelemetry/proto/resource/v1/resource.proto"; + +option csharp_namespace = "OpenTelemetry.Proto.Common.V1"; +option java_multiple_files = true; +option java_package = "io.opentelemetry.proto.common.v1"; +option java_outer_classname = "ProcessContextProto"; +option go_package = "go.opentelemetry.io/proto/otlp/common/v1"; + +// 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. + // + // Consider adding any keys here to the profiles semantic conventions in + // https://opentelemetry.io/docs/specs/semconv/general/profiles/ + repeated opentelemetry.proto.common.v1.KeyValue extra_attributes = 2; +} diff --git a/libdd-trace-protobuf/src/pb/opentelemetry/proto/resource/v1/resource.proto b/libdd-trace-protobuf/src/pb/opentelemetry/proto/resource/v1/resource.proto new file mode 100644 index 0000000000..19a045a2e8 --- /dev/null +++ b/libdd-trace-protobuf/src/pb/opentelemetry/proto/resource/v1/resource.proto @@ -0,0 +1,48 @@ +// This file was vendored from open-telemetry/opentelemetry-proto at commit +// 1e725b853bc8f6b46ee62e8232e4c83017b9536f. + +// 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.resource.v1; + +import "opentelemetry/proto/common/v1/common.proto"; + +option csharp_namespace = "OpenTelemetry.Proto.Resource.V1"; +option java_multiple_files = true; +option java_package = "io.opentelemetry.proto.resource.v1"; +option java_outer_classname = "ResourceProto"; +option go_package = "go.opentelemetry.io/proto/otlp/resource/v1"; + +// Resource information. +message Resource { + // Set of attributes that describe the resource. + // 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. + repeated opentelemetry.proto.common.v1.KeyValue attributes = 1; + + // The number of dropped attributes. If the value is 0, then + // no attributes were dropped. + uint32 dropped_attributes_count = 2; + + // Set of entities that participate in this Resource. + // + // Note: keys in the references MUST exist in attributes of this message. + // + // Status: [Development] + repeated opentelemetry.proto.common.v1.EntityRef entity_refs = 3; +}