diff --git a/Cargo.lock b/Cargo.lock index ae770d48..3d1406ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -477,6 +477,7 @@ dependencies = [ "hyper-util", "libdd-common", "libdd-data-pipeline", + "libdd-sampling", "libdd-telemetry 2.0.0", "libdd-trace-utils", "lru", @@ -1230,6 +1231,18 @@ dependencies = [ "tracing", ] +[[package]] +name = "libdd-sampling" +version = "0.2.1" +dependencies = [ + "criterion", + "foldhash 0.1.5", + "hashbrown 0.15.5", + "lru", + "serde", + "serde_json", +] + [[package]] name = "libdd-telemetry" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 49c442f6..bd39b371 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ [workspace] members = [ + "libdd-sampling", "datadog-opentelemetry", "datadog-opentelemetry/examples/propagator", "datadog-opentelemetry/examples/simple_tracing", diff --git a/datadog-opentelemetry/Cargo.toml b/datadog-opentelemetry/Cargo.toml index 5fcd935a..f02b634b 100644 --- a/datadog-opentelemetry/Cargo.toml +++ b/datadog-opentelemetry/Cargo.toml @@ -10,6 +10,8 @@ readme.workspace = true authors.workspace = true [dependencies] +# Internal dependencies +libdd-sampling = { path = "../libdd-sampling" } # External dependencies foldhash = { workspace = true } diff --git a/datadog-opentelemetry/src/core/configuration/configuration.rs b/datadog-opentelemetry/src/core/configuration/configuration.rs index 36628910..25b3ff96 100644 --- a/datadog-opentelemetry/src/core/configuration/configuration.rs +++ b/datadog-opentelemetry/src/core/configuration/configuration.rs @@ -12,7 +12,6 @@ use std::{borrow::Cow, sync::OnceLock}; use rustc_version_runtime::version; -use crate::core::configuration::sampling_rule_config::{ParsedSamplingRules, SamplingRuleConfig}; use crate::core::configuration::sources::{ CompositeConfigSourceResult, CompositeSource, ConfigKey, ConfigSourceOrigin, }; @@ -20,6 +19,7 @@ use crate::core::configuration::supported_configurations::SupportedConfiguration use crate::core::log::LevelFilter; use crate::core::telemetry; use crate::{dd_error, dd_warn}; +use libdd_sampling::{ParsedSamplingRules, SamplingRuleConfig}; /// Different types of remote configuration updates that can trigger callbacks #[derive(Debug, Clone)] diff --git a/datadog-opentelemetry/src/core/configuration/mod.rs b/datadog-opentelemetry/src/core/configuration/mod.rs index 4732f820..fc73d142 100644 --- a/datadog-opentelemetry/src/core/configuration/mod.rs +++ b/datadog-opentelemetry/src/core/configuration/mod.rs @@ -22,10 +22,11 @@ #[allow(clippy::module_inception)] mod configuration; pub(crate) mod remote_config; -mod sampling_rule_config; mod sources; mod supported_configurations; pub use configuration::{Config, ConfigBuilder, OtlpProtocol, TracePropagationStyle}; pub(crate) use configuration::{ConfigurationProvider, RemoteConfigUpdate}; -pub use sampling_rule_config::SamplingRuleConfig; + +// Re-export from libdd-sampling +pub use libdd_sampling::SamplingRuleConfig; diff --git a/datadog-opentelemetry/src/core/constants.rs b/datadog-opentelemetry/src/core/constants.rs deleted file mode 100644 index 21fad035..00000000 --- a/datadog-opentelemetry/src/core/constants.rs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ -// SPDX-License-Identifier: Apache-2.0 - -#[allow(unused)] -pub(crate) const HIGHER_ORDER_TRACE_ID_BITS_TAG: &str = "_dd.p.tid"; - -#[allow(unused)] -pub(crate) const SPAN_KIND_TAG: &str = "span.kind"; - -pub(crate) const SAMPLING_RATE_EVENT_EXTRACTION_KEY: &str = "_dd1.sr.eausr"; - -pub(crate) const SAMPLING_PRIORITY_TAG_KEY: &str = "_sampling_priority_v1"; - -pub(crate) const SAMPLING_DECISION_MAKER_TAG_KEY: &str = "_dd.p.dm"; - -pub(crate) const SAMPLING_RULE_RATE_TAG_KEY: &str = "_dd.rule_psr"; - -pub(crate) const SAMPLING_AGENT_RATE_TAG_KEY: &str = "_dd.agent_psr"; - -pub(crate) const RL_EFFECTIVE_RATE: &str = "_dd.limit_psr"; diff --git a/datadog-opentelemetry/src/core/mod.rs b/datadog-opentelemetry/src/core/mod.rs index d3399562..0f4f11cb 100644 --- a/datadog-opentelemetry/src/core/mod.rs +++ b/datadog-opentelemetry/src/core/mod.rs @@ -4,8 +4,9 @@ //! Core components of the SDK pub mod configuration; -pub(crate) mod constants; -pub mod sampling; + +// Re-export from libdd-sampling +pub use libdd_sampling::{dd_constants as constants, dd_sampling as sampling}; mod error; diff --git a/datadog-opentelemetry/src/sampling/mod.rs b/datadog-opentelemetry/src/sampling/mod.rs index f9174c75..6ffa55b2 100644 --- a/datadog-opentelemetry/src/sampling/mod.rs +++ b/datadog-opentelemetry/src/sampling/mod.rs @@ -3,22 +3,14 @@ //! Datadog sampling logic -pub(crate) mod agent_service_sampler; -pub(crate) mod constants; -pub(crate) mod datadog_sampler; -pub(crate) mod glob_matcher; pub(crate) mod otel_mappings; -pub(crate) mod rate_limiter; -pub(crate) mod rate_sampler; -pub(crate) mod rules_sampler; -pub(crate) mod sampling_rule; -mod types; pub(crate) mod utils; +// Re-export from libdd-sampling +pub use libdd_sampling::{ + AttributeFactory, AttributeLike, DatadogSampler, SamplingData, SamplingRule, + SamplingRulesCallback, SpanProperties, TraceIdLike, ValueLike, +}; + // Re-export key public types -pub use datadog_sampler::{DatadogSampler, SamplingRulesCallback}; pub use otel_mappings::{OtelAttributeFactory, OtelSamplingData}; -pub use sampling_rule::SamplingRule; -pub use types::{ - AttributeFactory, AttributeLike, SamplingData, SpanProperties, TraceIdLike, ValueLike, -}; diff --git a/datadog-opentelemetry/src/sampling/otel_mappings.rs b/datadog-opentelemetry/src/sampling/otel_mappings.rs index 180b2165..2802052c 100644 --- a/datadog-opentelemetry/src/sampling/otel_mappings.rs +++ b/datadog-opentelemetry/src/sampling/otel_mappings.rs @@ -8,9 +8,101 @@ use crate::mappings::{ get_dd_key_for_otlp_attribute, get_otel_env, get_otel_operation_name_v2, get_otel_resource_v2, get_otel_service, get_otel_status_code, AttributeIndices, AttributeKey, OtelSpan, }; -use crate::sampling::{SamplingData, SpanProperties}; +use crate::sampling::{AttributeLike, SamplingData, SpanProperties, TraceIdLike, ValueLike}; use opentelemetry::{Key, KeyValue}; +// Wrapper types to implement external traits for external types (orphan rule compliance) + +/// Wrapper around OpenTelemetry TraceId for trait implementations +#[derive(Debug, Clone, Copy)] +pub struct OtelTraceId(opentelemetry::trace::TraceId); + +impl OtelTraceId { + pub fn new(trace_id: opentelemetry::trace::TraceId) -> Self { + Self(trace_id) + } + + #[allow(dead_code)] + pub fn inner(&self) -> &opentelemetry::trace::TraceId { + &self.0 + } +} + +impl TraceIdLike for OtelTraceId { + type Item = opentelemetry::trace::TraceId; + + fn to_u128(&self) -> u128 { + u128::from_be_bytes(self.0.to_bytes()) + } + + fn inner(&self) -> &Self::Item { + &self.0 + } +} + +/// Transparent wrapper around OpenTelemetry Value for trait implementations +#[repr(transparent)] +#[derive(Debug)] +pub struct OtelValue(opentelemetry::Value); + +impl OtelValue { + /// Convert a reference to opentelemetry::Value to a reference to OtelValue + /// This is safe because OtelValue is repr(transparent) + fn from_ref(value: &opentelemetry::Value) -> &Self { + // Safety: OtelValue is repr(transparent) over opentelemetry::Value + unsafe { &*(value as *const opentelemetry::Value as *const OtelValue) } + } +} + +impl ValueLike for OtelValue { + fn extract_float(&self) -> Option { + crate::sampling::utils::extract_float_value(&self.0) + } + + fn extract_string(&self) -> Option> { + crate::sampling::utils::extract_string_value(&self.0) + } +} + +/// Transparent wrapper around OpenTelemetry KeyValue for trait implementations +#[repr(transparent)] +#[derive(Debug)] +pub struct OtelKeyValue(opentelemetry::KeyValue); + +impl OtelKeyValue { + /// Convert a reference to opentelemetry::KeyValue to a reference to OtelKeyValue + /// This is safe because OtelKeyValue is repr(transparent) + fn from_ref(kv: &opentelemetry::KeyValue) -> &Self { + // Safety: OtelKeyValue is repr(transparent) over opentelemetry::KeyValue + unsafe { &*(kv as *const opentelemetry::KeyValue as *const OtelKeyValue) } + } +} + +impl AttributeLike for OtelKeyValue { + type Value = OtelValue; + + fn key(&self) -> &str { + self.0.key.as_str() + } + + fn value(&self) -> &Self::Value { + OtelValue::from_ref(&self.0.value) + } +} + +/// Iterator adapter that wraps OpenTelemetry KeyValue references +pub struct OtelKeyValueIter<'a> { + inner: std::slice::Iter<'a, opentelemetry::KeyValue>, +} + +impl<'a> Iterator for OtelKeyValueIter<'a> { + type Item = &'a OtelKeyValue; + + fn next(&mut self) -> Option { + self.inner.next().map(OtelKeyValue::from_ref) + } +} + pub struct PreSampledSpan<'a> { pub name: &'a str, pub span_kind: opentelemetry::trace::SpanKind, @@ -80,9 +172,9 @@ impl<'a> OtelSpan<'a> for PreSampledSpan<'a> { } impl SpanProperties for PreSampledSpan<'_> { - type Attribute = opentelemetry::KeyValue; + type Attribute = OtelKeyValue; type AttributesIter<'b> - = std::slice::Iter<'b, opentelemetry::KeyValue> + = OtelKeyValueIter<'b> where Self: 'b; @@ -107,7 +199,9 @@ impl SpanProperties for PreSampledSpan<'_> { } fn attributes(&self) -> Self::AttributesIter<'_> { - self.attributes.iter() + OtelKeyValueIter { + inner: self.attributes.iter(), + } } fn get_alternate_key<'b>(&self, key: &'b str) -> Option> { @@ -121,28 +215,6 @@ impl SpanProperties for PreSampledSpan<'_> { } } -impl crate::sampling::AttributeLike for opentelemetry::KeyValue { - type Value = opentelemetry::Value; - - fn key(&self) -> &str { - self.key.as_str() - } - - fn value(&self) -> &Self::Value { - &self.value - } -} - -impl crate::sampling::ValueLike for opentelemetry::Value { - fn extract_float(&self) -> Option { - crate::sampling::utils::extract_float_value(self) - } - - fn extract_string(&self) -> Option> { - crate::sampling::utils::extract_string_value(self) - } -} - /// OpenTelemetry Sampling Data implementation. /// /// Provides the necessary data for making sampling decisions on OpenTelemetry spans. @@ -150,7 +222,7 @@ impl crate::sampling::ValueLike for opentelemetry::Value { /// span kind, attributes, and resource information. pub struct OtelSamplingData<'a> { is_parent_sampled: Option, - trace_id: &'a opentelemetry::trace::TraceId, + trace_id: OtelTraceId, name: &'a str, span_kind: opentelemetry::trace::SpanKind, attributes: &'a [KeyValue], @@ -178,7 +250,7 @@ impl<'a> OtelSamplingData<'a> { ) -> Self { Self { is_parent_sampled, - trace_id, + trace_id: OtelTraceId::new(*trace_id), name, span_kind, attributes, @@ -188,7 +260,7 @@ impl<'a> OtelSamplingData<'a> { } impl SamplingData for OtelSamplingData<'_> { - type TraceId = opentelemetry::trace::TraceId; + type TraceId = OtelTraceId; type Properties<'b> = PreSampledSpan<'b> where @@ -198,7 +270,7 @@ impl SamplingData for OtelSamplingData<'_> { self.is_parent_sampled } fn trace_id(&self) -> &Self::TraceId { - self.trace_id + &self.trace_id } fn with_span_properties(&self, s: &S, f: F) -> T @@ -216,18 +288,6 @@ impl SamplingData for OtelSamplingData<'_> { } } -impl crate::sampling::TraceIdLike for opentelemetry::trace::TraceId { - type Item = opentelemetry::trace::TraceId; - - fn to_u128(&self) -> u128 { - u128::from_be_bytes(self.to_bytes()) - } - - fn inner(&self) -> &Self::Item { - self - } -} - /// Factory for creating OpenTelemetry KeyValue attributes. pub struct OtelAttributeFactory; @@ -448,8 +508,8 @@ mod tests { let collected: Vec<_> = span.attributes().collect(); assert_eq!(collected.len(), 2); - assert_eq!(collected[0].key.as_str(), "key1"); - assert_eq!(collected[1].key.as_str(), "key2"); + assert_eq!(collected[0].key(), "key1"); + assert_eq!(collected[1].key(), "key2"); } #[test] @@ -557,12 +617,10 @@ mod tests { // Verify the actual attributes can be found by their original keys let status_code_attr = attrs .iter() - .find(|a| a.key.as_str() == "http.response.status_code"); + .find(|a| a.key() == "http.response.status_code"); assert!(status_code_attr.is_some()); - let method_attr = attrs - .iter() - .find(|a| a.key.as_str() == "http.request.method"); + let method_attr = attrs.iter().find(|a| a.key() == "http.request.method"); assert!(method_attr.is_some()); } @@ -593,13 +651,9 @@ mod tests { assert_eq!(attrs.len(), 3); // Verify each attribute can be found by its original OTel key - assert!(attrs - .iter() - .any(|a| a.key.as_str() == "http.response.status_code")); - assert!(attrs - .iter() - .any(|a| a.key.as_str() == "http.request.method")); - assert!(attrs.iter().any(|a| a.key.as_str() == "url.full")); + assert!(attrs.iter().any(|a| a.key() == "http.response.status_code")); + assert!(attrs.iter().any(|a| a.key() == "http.request.method")); + assert!(attrs.iter().any(|a| a.key() == "url.full")); } #[test] @@ -628,10 +682,8 @@ mod tests { let attrs: Vec<_> = span.attributes().collect(); assert_eq!(attrs.len(), 2); - assert!(attrs - .iter() - .any(|a| a.key.as_str() == "http.response.status_code")); - assert!(attrs.iter().any(|a| a.key.as_str() == "custom.tag")); + assert!(attrs.iter().any(|a| a.key() == "http.response.status_code")); + assert!(attrs.iter().any(|a| a.key() == "custom.tag")); // Verify the status code is accessible assert_eq!(span.status_code(), Some(503)); diff --git a/libdd-sampling/Cargo.toml b/libdd-sampling/Cargo.toml new file mode 100644 index 00000000..1dea6a65 --- /dev/null +++ b/libdd-sampling/Cargo.toml @@ -0,0 +1,23 @@ +# Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ +# SPDX-License-Identifier: Apache-2.0 + +[package] +name = "libdd-sampling" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +readme.workspace = true +description = "Core sampling logic for Datadog tracing" +authors.workspace = true + +[dependencies] +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +hashbrown = { workspace = true } +foldhash = { workspace = true } +lru = "0.16.3" + +[dev-dependencies] +criterion = "0.5" diff --git a/datadog-opentelemetry/src/sampling/agent_service_sampler.rs b/libdd-sampling/src/agent_service_sampler.rs similarity index 91% rename from datadog-opentelemetry/src/sampling/agent_service_sampler.rs rename to libdd-sampling/src/agent_service_sampler.rs index 5100f1ad..a8841027 100644 --- a/datadog-opentelemetry/src/sampling/agent_service_sampler.rs +++ b/libdd-sampling/src/agent_service_sampler.rs @@ -6,16 +6,16 @@ use std::{ sync::{Arc, RwLock}, }; -use super::rate_sampler::RateSampler; +use crate::rate_sampler::RateSampler; #[derive(Debug, serde::Deserialize)] -pub(crate) struct AgentRates<'a> { +pub struct AgentRates<'a> { #[serde(borrow)] pub rates_by_service: Option>, } #[derive(Debug, Default, Clone)] -pub(crate) struct ServicesSampler { +pub struct ServicesSampler { inner: Arc>>, } diff --git a/datadog-opentelemetry/src/sampling/constants.rs b/libdd-sampling/src/constants.rs similarity index 100% rename from datadog-opentelemetry/src/sampling/constants.rs rename to libdd-sampling/src/constants.rs diff --git a/datadog-opentelemetry/src/sampling/datadog_sampler.rs b/libdd-sampling/src/datadog_sampler.rs similarity index 98% rename from datadog-opentelemetry/src/sampling/datadog_sampler.rs rename to libdd-sampling/src/datadog_sampler.rs index 16708d47..c6a1fe67 100644 --- a/datadog-opentelemetry/src/sampling/datadog_sampler.rs +++ b/libdd-sampling/src/datadog_sampler.rs @@ -1,18 +1,18 @@ // Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ // SPDX-License-Identifier: Apache-2.0 -use crate::core::configuration::SamplingRuleConfig; -use crate::core::constants::{ +use crate::dd_constants::{ RL_EFFECTIVE_RATE, SAMPLING_AGENT_RATE_TAG_KEY, SAMPLING_DECISION_MAKER_TAG_KEY, SAMPLING_PRIORITY_TAG_KEY, SAMPLING_RULE_RATE_TAG_KEY, }; -use crate::core::sampling::{mechanism, priority, SamplingMechanism, SamplingPriority}; +use crate::dd_sampling::{mechanism, priority, SamplingMechanism, SamplingPriority}; +use crate::sampling_rule_config::SamplingRuleConfig; /// Type alias for sampling rules update callback /// Consolidated callback type used across crates for remote config sampling updates pub type SamplingRulesCallback = Box Fn(&'a [SamplingRuleConfig]) + Send + Sync>; -use crate::sampling::{SamplingData, SpanProperties}; +use crate::types::{SamplingData, SpanProperties}; use super::agent_service_sampler::{AgentRates, ServicesSampler}; use super::rate_limiter::RateLimiter; @@ -51,7 +51,7 @@ impl DatadogSampler { self.service_samplers.update_rates(rates); } - pub(crate) fn on_agent_response(&self) -> Box Fn(&'a str) + Send + Sync> { + pub fn on_agent_response(&self) -> Box Fn(&'a str) + Send + Sync> { let service_samplers = self.service_samplers.clone(); Box::new(move |s: &str| { let Ok(new_rates) = serde_json::de::from_str::(s) else { @@ -239,7 +239,7 @@ impl DdSamplingResult { /// An optional vector of attributes to add to the sampling result pub fn to_dd_sampling_tags(&self, factory: &F) -> Option> where - F: crate::sampling::AttributeFactory, + F: crate::types::AttributeFactory, { let Some(root_info) = &self.trace_root_info else { return None; // No root info, return empty attributes @@ -281,11 +281,11 @@ impl DdSamplingResult { #[cfg(test)] mod tests { use super::*; - use crate::sampling::constants::{ + use crate::constants::{ attr::{ENV_TAG, RESOURCE_TAG}, pattern, }; - use crate::sampling::{AttributeLike, TraceIdLike, ValueLike}; + use crate::types::{AttributeLike, TraceIdLike, ValueLike}; use std::borrow::Cow; use std::collections::HashMap; @@ -522,7 +522,7 @@ mod tests { struct TestAttributeFactory; - impl crate::sampling::AttributeFactory for TestAttributeFactory { + impl crate::types::AttributeFactory for TestAttributeFactory { type Attribute = TestAttribute; fn create_i64(&self, key: &'static str, value: i64) -> Self::Attribute { @@ -1062,7 +1062,7 @@ mod tests { // Should inherit the sampling decision from parent assert!(result_sampled.get_priority().is_keep()); assert!(result_sampled - .to_dd_sampling_tags(&crate::sampling::OtelAttributeFactory) + .to_dd_sampling_tags(&TestAttributeFactory) .is_none()); // Test with non-sampled parent context @@ -1072,7 +1072,7 @@ mod tests { // Should inherit the sampling decision from parent assert!(!result_not_sampled.get_priority().is_keep()); assert!(result_not_sampled - .to_dd_sampling_tags(&crate::sampling::OtelAttributeFactory) + .to_dd_sampling_tags(&TestAttributeFactory) .is_none()); } @@ -1099,9 +1099,7 @@ mod tests { // Should sample and add attributes assert!(result.get_priority().is_keep()); - assert!(result - .to_dd_sampling_tags(&crate::sampling::OtelAttributeFactory) - .is_some()); + assert!(result.to_dd_sampling_tags(&TestAttributeFactory).is_some()); // Test with non-matching attributes let attrs_no_match = create_attributes("other-resource", "prod"); @@ -1112,7 +1110,7 @@ mod tests { // Should still sample (default behavior when no rules match) and add attributes assert!(result_no_match.get_priority().is_keep()); assert!(result_no_match - .to_dd_sampling_tags(&crate::sampling::OtelAttributeFactory) + .to_dd_sampling_tags(&TestAttributeFactory) .is_some()); } diff --git a/libdd-sampling/src/dd_constants.rs b/libdd-sampling/src/dd_constants.rs new file mode 100644 index 00000000..ffc25292 --- /dev/null +++ b/libdd-sampling/src/dd_constants.rs @@ -0,0 +1,20 @@ +// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +#[allow(unused)] +pub const HIGHER_ORDER_TRACE_ID_BITS_TAG: &str = "_dd.p.tid"; + +#[allow(unused)] +pub const SPAN_KIND_TAG: &str = "span.kind"; + +pub const SAMPLING_RATE_EVENT_EXTRACTION_KEY: &str = "_dd1.sr.eausr"; + +pub const SAMPLING_PRIORITY_TAG_KEY: &str = "_sampling_priority_v1"; + +pub const SAMPLING_DECISION_MAKER_TAG_KEY: &str = "_dd.p.dm"; + +pub const SAMPLING_RULE_RATE_TAG_KEY: &str = "_dd.rule_psr"; + +pub const SAMPLING_AGENT_RATE_TAG_KEY: &str = "_dd.agent_psr"; + +pub const RL_EFFECTIVE_RATE: &str = "_dd.limit_psr"; diff --git a/datadog-opentelemetry/src/core/sampling.rs b/libdd-sampling/src/dd_sampling.rs similarity index 96% rename from datadog-opentelemetry/src/core/sampling.rs rename to libdd-sampling/src/dd_sampling.rs index 90f32f91..a9694ab2 100644 --- a/datadog-opentelemetry/src/core/sampling.rs +++ b/libdd-sampling/src/dd_sampling.rs @@ -30,11 +30,11 @@ pub struct SamplingPriority { } impl SamplingPriority { - pub(crate) const fn from_i8(value: i8) -> Self { + pub const fn from_i8(value: i8) -> Self { Self { value } } - pub(crate) fn into_i8(self) -> i8 { + pub fn into_i8(self) -> i8 { self.value } @@ -48,7 +48,7 @@ impl SamplingPriority { /// # Examples /// /// ``` - /// use datadog_opentelemetry::core::sampling::priority; + /// use libdd_sampling::priority; /// /// assert!(priority::AUTO_KEEP.is_keep()); /// assert!(priority::USER_KEEP.is_keep()); @@ -105,15 +105,15 @@ pub struct SamplingMechanism { } impl SamplingMechanism { - pub(crate) const fn from_u8(value: u8) -> Self { + pub const fn from_u8(value: u8) -> Self { Self { value } } - pub(crate) fn into_u8(self) -> u8 { + pub fn into_u8(self) -> u8 { self.value } - pub(crate) fn to_priority(self, is_keep: bool) -> SamplingPriority { + pub fn to_priority(self, is_keep: bool) -> SamplingPriority { const AUTO_PAIR: PriorityPair = PriorityPair { keep: priority::AUTO_KEEP, reject: priority::AUTO_REJECT, diff --git a/datadog-opentelemetry/src/sampling/glob_matcher.rs b/libdd-sampling/src/glob_matcher.rs similarity index 100% rename from datadog-opentelemetry/src/sampling/glob_matcher.rs rename to libdd-sampling/src/glob_matcher.rs diff --git a/libdd-sampling/src/lib.rs b/libdd-sampling/src/lib.rs new file mode 100644 index 00000000..dbb709fd --- /dev/null +++ b/libdd-sampling/src/lib.rs @@ -0,0 +1,37 @@ +// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +//! Core sampling logic for Datadog tracing +//! +//! This crate provides generic sampling infrastructure including: +//! - Trait abstractions for trace IDs, attributes, and span properties +//! - Rate-based sampling algorithms +//! - Rate limiting functionality +//! - Glob pattern matching for sampling rules +//! - Sampling-related constants +//! - Rule-based sampling with pattern matching +//! - Agent-provided sampling rates +//! - Complete Datadog sampler implementation + +pub mod agent_service_sampler; +pub mod constants; +pub mod datadog_sampler; +pub mod dd_constants; +pub mod dd_sampling; +pub mod glob_matcher; +pub mod rate_limiter; +pub mod rate_sampler; +pub mod rules_sampler; +pub mod sampling_rule; +pub mod sampling_rule_config; +pub mod types; + +// Re-export key types for convenience +pub use agent_service_sampler::ServicesSampler; +pub use datadog_sampler::{DatadogSampler, SamplingRulesCallback}; +pub use dd_sampling::{mechanism, priority, SamplingDecision, SamplingMechanism, SamplingPriority}; +pub use sampling_rule::SamplingRule; +pub use sampling_rule_config::{ParsedSamplingRules, SamplingRuleConfig}; +pub use types::{ + AttributeFactory, AttributeLike, SamplingData, SpanProperties, TraceIdLike, ValueLike, +}; diff --git a/datadog-opentelemetry/src/sampling/rate_limiter.rs b/libdd-sampling/src/rate_limiter.rs similarity index 99% rename from datadog-opentelemetry/src/sampling/rate_limiter.rs rename to libdd-sampling/src/rate_limiter.rs index bc69c312..ff9ebbc6 100644 --- a/datadog-opentelemetry/src/sampling/rate_limiter.rs +++ b/libdd-sampling/src/rate_limiter.rs @@ -7,7 +7,7 @@ use std::time::Instant; /// A token bucket rate limiter implementation #[derive(Clone)] -pub(crate) struct RateLimiter { +pub struct RateLimiter { /// Rate limit value that doesn't need to be protected by mutex rate_limit: i32, diff --git a/datadog-opentelemetry/src/sampling/rate_sampler.rs b/libdd-sampling/src/rate_sampler.rs similarity index 99% rename from datadog-opentelemetry/src/sampling/rate_sampler.rs rename to libdd-sampling/src/rate_sampler.rs index 9d0a6fdf..35dd9056 100644 --- a/datadog-opentelemetry/src/sampling/rate_sampler.rs +++ b/libdd-sampling/src/rate_sampler.rs @@ -2,13 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 use super::constants::{numeric, rate}; -use crate::sampling::TraceIdLike; +use crate::types::TraceIdLike; use numeric::{KNUTH_FACTOR, MAX_UINT_64BITS}; use std::fmt; /// Keeps (100 * `sample_rate`)% of the traces randomly. #[derive(Clone)] -pub(crate) struct RateSampler { +pub struct RateSampler { sample_rate: f64, sampling_id_threshold: u64, } diff --git a/datadog-opentelemetry/src/sampling/rules_sampler.rs b/libdd-sampling/src/rules_sampler.rs similarity index 100% rename from datadog-opentelemetry/src/sampling/rules_sampler.rs rename to libdd-sampling/src/rules_sampler.rs diff --git a/datadog-opentelemetry/src/sampling/sampling_rule.rs b/libdd-sampling/src/sampling_rule.rs similarity index 97% rename from datadog-opentelemetry/src/sampling/sampling_rule.rs rename to libdd-sampling/src/sampling_rule.rs index 38332f52..d721630d 100644 --- a/datadog-opentelemetry/src/sampling/sampling_rule.rs +++ b/libdd-sampling/src/sampling_rule.rs @@ -1,14 +1,13 @@ // Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ // SPDX-License-Identifier: Apache-2.0 -use crate::core::configuration::SamplingRuleConfig; -use crate::sampling::{AttributeLike, SpanProperties, TraceIdLike, ValueLike}; +use crate::constants::pattern::NO_RULE; +use crate::glob_matcher::GlobMatcher; +use crate::rate_sampler::RateSampler; +use crate::sampling_rule_config::SamplingRuleConfig; +use crate::types::{AttributeLike, SpanProperties, TraceIdLike, ValueLike}; use std::collections::HashMap; -use super::constants::pattern::NO_RULE; -use super::glob_matcher::GlobMatcher; -use super::rate_sampler::RateSampler; - // HTTP status code attribute constants const HTTP_RESPONSE_STATUS_CODE: &str = "http.response.status_code"; const HTTP_STATUS_CODE: &str = "http.status_code"; diff --git a/datadog-opentelemetry/src/core/configuration/sampling_rule_config.rs b/libdd-sampling/src/sampling_rule_config.rs similarity index 96% rename from datadog-opentelemetry/src/core/configuration/sampling_rule_config.rs rename to libdd-sampling/src/sampling_rule_config.rs index 7e3f83ff..308107f9 100644 --- a/datadog-opentelemetry/src/core/configuration/sampling_rule_config.rs +++ b/libdd-sampling/src/sampling_rule_config.rs @@ -46,8 +46,8 @@ fn default_provenance() -> String { } #[derive(Debug, Default, Clone, PartialEq)] -pub(crate) struct ParsedSamplingRules { - pub(crate) rules: Vec, +pub struct ParsedSamplingRules { + pub rules: Vec, } impl Deref for ParsedSamplingRules { diff --git a/datadog-opentelemetry/src/sampling/types.rs b/libdd-sampling/src/types.rs similarity index 99% rename from datadog-opentelemetry/src/sampling/types.rs rename to libdd-sampling/src/types.rs index 1b24ee95..71e645f6 100644 --- a/datadog-opentelemetry/src/sampling/types.rs +++ b/libdd-sampling/src/types.rs @@ -13,7 +13,7 @@ use std::borrow::Cow; /// # Examples /// /// ``` -/// use datadog_opentelemetry::sampling::TraceIdLike; +/// use libdd_sampling::TraceIdLike; /// /// #[derive(Clone, PartialEq, Eq)] /// struct MyTraceId(u128);