Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
52710cf
fix: attach obfuscation flag to stats buckets, fix obfuscation versio…
Eldolfin Jul 6, 2026
a62a150
fix: fmt
Eldolfin Jul 7, 2026
8f65b1d
fix: flush buckets which have the same obfuscation indicator together
Eldolfin Jul 7, 2026
a0bfe8c
fix(sidecar): remove useless stats-obfuscation feature
Eldolfin Jul 7, 2026
6c6f058
Revert "fix(sidecar): remove useless stats-obfuscation feature"
Eldolfin Jul 7, 2026
a44467a
fix: outdated comment, remove metadata return from otlp flush
Eldolfin Jul 7, 2026
3113a7e
fix: tests lints
Eldolfin Jul 7, 2026
909d654
fix: local review suggestions
Eldolfin Jul 7, 2026
85eaf0c
fix: fmt
Eldolfin Jul 7, 2026
05d2364
fix(test): put back obfuscation enabled in SpanConcentrator instead o…
Eldolfin Jul 7, 2026
ed0277a
fix: remove unused file results.xml
Eldolfin Jul 7, 2026
888e56c
fix error message StateExporter -> StatsExporter
Eldolfin Jul 7, 2026
9d91a3c
fix nit comment
Eldolfin Jul 7, 2026
2342ce1
refacto: cleaner flush result struct, simplify span obfuscation path
Eldolfin Jul 8, 2026
2d4eebc
feat: send obfuscated/unobfuscated stats concurrently
Eldolfin Jul 9, 2026
9f7474e
Merge branch 'main' into oscarld/fix-css-obfuscation-logic
Eldolfin Jul 10, 2026
35d265b
fix: FuturesUnordered over tokio::join!
Eldolfin Jul 10, 2026
e082005
feat!(stats): per-key cardinality limits
Eldolfin Jul 8, 2026
1c9dca9
feat: store distinct hashes instead of values
Eldolfin Jul 8, 2026
43a1b0b
fix: libdd-data-pipeline(-ffi) build
Eldolfin Jul 8, 2026
586e6d7
fix: wrong peer_tags collapse value
Eldolfin Jul 8, 2026
cc5bf3b
fix: fmt
Eldolfin Jul 8, 2026
726c57a
fix: collapse key fields before applying whole-key cardinality limits
Eldolfin Jul 8, 2026
bd6e3bc
feat: add a warning when whole-key limit is lower than per-field limits
Eldolfin Jul 8, 2026
1c46a80
fix: fmt
Eldolfin Jul 8, 2026
04befd7
fix(ffi): add libdd-trace-stats to cbindgen config to generate Cardin…
Eldolfin Jul 8, 2026
74f105a
fix: inline DEFAULT_MAX_ENTRIES_PER_BUCKET
Eldolfin Jul 10, 2026
edf1e0c
fix: link span derived primary tags ticket and pr
Eldolfin Jul 10, 2026
5cedcf4
fix: avoid useless re-hashes
Eldolfin Jul 10, 2026
83043a4
fix: warn on any cardinality limit = 0
Eldolfin Jul 10, 2026
39427ce
fix: fmt
Eldolfin Jul 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 19 additions & 19 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 10 additions & 3 deletions datadog-ipc/src/shm_stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ use zwohash::ZwoHasher;

use libdd_ddsketch::DDSketch;
use libdd_trace_protobuf::pb;
use libdd_trace_stats::span_concentrator::{FixedAggregationKey, FlushableConcentrator};
use libdd_trace_stats::span_concentrator::{
FixedAggregationKey, FlushResult, FlushableConcentrator,
};

use crate::platform::{FileBackedHandle, MappedMem, NamedShmHandle};

Expand Down Expand Up @@ -820,8 +822,13 @@ impl ShmSpanConcentrator {
}

impl FlushableConcentrator for ShmSpanConcentrator {
fn flush_buckets(&mut self, force: bool) -> (Vec<pb::ClientStatsBucket>, u64) {
(self.drain_buckets(force), 0)
fn flush_buckets(&mut self, force: bool) -> FlushResult {
// The SHM concentrator does not perform client-side obfuscation.
FlushResult {
obfuscated_buckets: vec![],
unobfuscated_buckets: self.drain_buckets(force),
collapsed_spans: 0,
}
}
}

Expand Down
6 changes: 0 additions & 6 deletions datadog-sidecar/src/service/stats_flusher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,6 @@ fn make_exporter(
s.meta.clone(),
endpoint,
NativeCapabilities::new_client(),
// Sidecar does not perform client-side stats obfuscation. Pass a disabled
// default so the `datadog-obfuscation-version` header is never sent.
#[cfg(feature = "stats-obfuscation")]
Arc::new(arc_swap::ArcSwap::from_pointee(
libdd_trace_stats::span_concentrator::StatsComputationObfuscationConfig::default(),
)),
#[cfg(feature = "stats-obfuscation")]
"0",
None,
Expand Down
2 changes: 1 addition & 1 deletion libdd-data-pipeline-ffi/cbindgen.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ must_use = "DDOG_CHECK_RETURN"

[parse]
parse_deps = true
include = ["libdd-common", "libdd-common-ffi", "libdd-shared-runtime", "libdd-data-pipeline", "libdd-trace-utils"]
include = ["libdd-common", "libdd-common-ffi", "libdd-shared-runtime", "libdd-data-pipeline", "libdd-trace-utils", "libdd-trace-stats"]
37 changes: 28 additions & 9 deletions libdd-data-pipeline-ffi/src/trace_exporter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use libdd_common_ffi::{
CharSlice,
{slice::AsBytes, slice::ByteSlice},
};
use libdd_data_pipeline::trace_exporter::stats::CardinalityLimitConfig;
use libdd_data_pipeline::trace_exporter::{
TelemetryConfig, TelemetryInstrumentationSessions, TraceExporter as GenericTraceExporter,
TraceExporterInputFormat, TraceExporterOutputFormat,
Expand Down Expand Up @@ -90,7 +91,7 @@ pub struct TraceExporterConfig {
otlp_protocol: Option<OtlpProtocol>,
output_to_log: bool,
log_max_line_size: Option<usize>,
stats_cardinality_limit: Option<usize>,
stats_cardinality_limits: Option<CardinalityLimitConfig>,
}

#[no_mangle]
Expand Down Expand Up @@ -555,11 +556,11 @@ pub unsafe extern "C" fn ddog_trace_exporter_config_set_otlp_protocol(
#[no_mangle]
pub unsafe extern "C" fn ddog_trace_exporter_config_set_stats_cardinality_limit(
config: Option<&mut TraceExporterConfig>,
limit: usize,
limits: CardinalityLimitConfig,
) -> Option<Box<ExporterError>> {
catch_panic!(
if let Some(handle) = config {
handle.stats_cardinality_limit = Some(limit);
handle.stats_cardinality_limits = Some(limits);
Comment thread
Eldolfin marked this conversation as resolved.
None
} else {
gen_error!(ErrorCode::InvalidArgument)
Expand Down Expand Up @@ -645,7 +646,7 @@ pub unsafe extern "C" fn ddog_trace_exporter_new(
.set_output_format(config.output_format)
.set_connection_timeout(config.connection_timeout);

if let Some(limit) = config.stats_cardinality_limit {
if let Some(limit) = config.stats_cardinality_limits {
builder.set_stats_cardinality_limit(limit);
}

Expand Down Expand Up @@ -782,7 +783,7 @@ mod tests {
assert!(cfg.connection_timeout.is_none());
assert!(!cfg.output_to_log);
assert_eq!(cfg.log_max_line_size, None);
assert_eq!(cfg.stats_cardinality_limit, None);
assert_eq!(cfg.stats_cardinality_limits, None);

ddog_trace_exporter_config_free(cfg);
}
Expand Down Expand Up @@ -1501,16 +1502,34 @@ mod tests {
fn config_stats_cardinality_limit_test() {
unsafe {
// Null config → InvalidArgument
let error = ddog_trace_exporter_config_set_stats_cardinality_limit(None, 100);
let error = ddog_trace_exporter_config_set_stats_cardinality_limit(
None,
CardinalityLimitConfig {
whole_key_limit: 100,
..Default::default()
},
);
assert_eq!(error.as_ref().unwrap().code, ErrorCode::InvalidArgument);
ddog_trace_exporter_error_free(error);

// Valid config → value stored
let mut config = Some(TraceExporterConfig::default());
let error =
ddog_trace_exporter_config_set_stats_cardinality_limit(config.as_mut(), 500);
let error = ddog_trace_exporter_config_set_stats_cardinality_limit(
config.as_mut(),
CardinalityLimitConfig {
whole_key_limit: 500,
..Default::default()
},
);
assert_eq!(error, None);
assert_eq!(config.unwrap().stats_cardinality_limit, Some(500));
assert_eq!(
config
.unwrap()
.stats_cardinality_limits
.unwrap()
.whole_key_limit,
500
);
}
}

Expand Down
Empty file removed libdd-data-pipeline/results.xml
Empty file.
2 changes: 1 addition & 1 deletion libdd-data-pipeline/src/otlp/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ pub struct OtlpStatsExporter<C: HttpClientCapability + SleepCapability> {
impl<C: HttpClientCapability + SleepCapability> OtlpStatsExporter<C> {
/// Flush the concentrator and export stats; returns `Ok(true)` if anything was sent.
async fn send(&self, force_flush: bool, max_retries: u32) -> anyhow::Result<bool> {
let (buckets, _collapsed_count) = {
let buckets = {
#[allow(clippy::unwrap_used)]
let mut c = self.concentrator.lock().unwrap();
c.flush_with_otlp_exact(SystemTime::now(), force_flush)
Expand Down
14 changes: 9 additions & 5 deletions libdd-data-pipeline/src/trace_exporter/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use libdd_dogstatsd_client::new;
use libdd_shared_runtime::SharedRuntime;
#[cfg(not(target_arch = "wasm32"))]
use libdd_shared_runtime::{BlockingRuntime, ForkSafeRuntime};
use libdd_trace_stats::span_concentrator::CardinalityLimitConfig;
use libdd_trace_utils::trace_filter::TraceFilterer;
use std::sync::Arc;
use std::time::Duration;
Expand Down Expand Up @@ -75,7 +76,7 @@ pub struct TraceExporterBuilder<R: SharedRuntime> {
peer_tags_aggregation: bool,
compute_stats_by_span_kind: bool,
peer_tags: Vec<String>,
stats_cardinality_limit: Option<usize>,
stats_cardinality_limits: Option<CardinalityLimitConfig>,
#[cfg(feature = "stats-obfuscation")]
client_side_stats_obfuscation_enabled: bool,
#[cfg(feature = "telemetry")]
Expand Down Expand Up @@ -143,7 +144,7 @@ impl<R: SharedRuntime> TraceExporterBuilder<R> {
peer_tags_aggregation: false,
compute_stats_by_span_kind: false,
peer_tags: Vec::new(),
stats_cardinality_limit: None,
stats_cardinality_limits: None,
#[cfg(feature = "stats-obfuscation")]
client_side_stats_obfuscation_enabled: false,
#[cfg(feature = "telemetry")]
Expand Down Expand Up @@ -333,8 +334,11 @@ impl<R: SharedRuntime> TraceExporterBuilder<R> {
/// This bounds memory usage when the trace population has very high cardinality.
///
/// Has no effect unless stats computation is enabled.
pub fn set_stats_cardinality_limit(&mut self, cardinality_limit: usize) -> &mut Self {
self.stats_cardinality_limit = Some(cardinality_limit);
pub fn set_stats_cardinality_limit(
&mut self,
cardinality_limits: CardinalityLimitConfig,
) -> &mut Self {
self.stats_cardinality_limits = Some(cardinality_limits);
self
}

Expand Down Expand Up @@ -820,7 +824,7 @@ impl<R: SharedRuntime> TraceExporterBuilder<R> {
common_stats_tags: vec![libdatadog_version],
client_side_stats: StatsComputationConfig {
status: ArcSwap::new(stats.into()),
stats_cardinality_limit: self.stats_cardinality_limit,
stats_cardinality_limits: self.stats_cardinality_limits,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply configured limits to OTLP stats

For exporters configured with OTLP metrics, the stats concentrator is created earlier with None for the new cardinality limits, and check_agent_info returns early when otlp_stats_enabled, so the value stored here never reaches that path. In that configuration, callers using set_stats_cardinality_limit still get the defaults for OTLP trace metrics instead of their configured per-key limits; pass self.stats_cardinality_limits into the OTLP SpanConcentrator::new call as well.

Useful? React with 👍 / 👎.

#[cfg(feature = "stats-obfuscation")]
obfuscation_config: Arc::new(ArcSwap::from_pointee(
StatsComputationObfuscationConfig::default(),
Expand Down
6 changes: 3 additions & 3 deletions libdd-data-pipeline/src/trace_exporter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ impl<
metadata: &self.metadata,
endpoint_url: &self.endpoint.url,
shared_runtime: &*self.shared_runtime,
stats_cardinality_limit: self.client_side_stats.stats_cardinality_limit,
stats_cardinality_limits: self.client_side_stats.stats_cardinality_limits,
dogstatsd: if self.health_metrics_enabled {
self.dogstatsd.clone()
} else {
Expand Down Expand Up @@ -875,8 +875,8 @@ impl<
&& self.v1_active.swap(false, Ordering::Relaxed)
{
warn!(
"V1 trace send returned 404; agent no longer advertises {V1_TRACES_ENDPOINT} — falling back to V0.4"
);
"V1 trace send returned 404; agent no longer advertises {V1_TRACES_ENDPOINT} — falling back to V0.4"
);
self.info_response_observer.manual_trigger();
}
}
Expand Down
13 changes: 7 additions & 6 deletions libdd-data-pipeline/src/trace_exporter/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
//! including starting/stopping stats workers, managing the span concentrator,
//! and processing traces for stats collection.

pub use libdd_trace_stats::span_concentrator::CardinalityLimitConfig;

#[cfg(not(target_arch = "wasm32"))]
use super::add_path;
use super::TracerMetadata;
Expand Down Expand Up @@ -49,7 +51,7 @@ pub(crate) struct StatsContext<'a, R: SharedRuntime> {
pub metadata: &'a TracerMetadata,
pub endpoint_url: &'a http::Uri,
pub shared_runtime: &'a R,
pub stats_cardinality_limit: Option<usize>,
pub stats_cardinality_limits: Option<CardinalityLimitConfig>,
/// Optional DogStatsD client forwarded to the [`StatsExporter`].
pub dogstatsd: Option<std::sync::Arc<libdd_dogstatsd_client::Client>>,
/// Optional telemetry handle forwarded to the [`StatsExporter`].
Expand Down Expand Up @@ -77,7 +79,7 @@ pub(crate) enum StatsComputationStatus {
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub(crate) struct StatsComputationConfig {
pub(crate) status: ArcSwap<StatsComputationStatus>,
pub(crate) stats_cardinality_limit: Option<usize>,
pub(crate) stats_cardinality_limits: Option<CardinalityLimitConfig>,
#[cfg(feature = "stats-obfuscation")]
pub(crate) obfuscation_config: SharedStatsComputationObfuscationConfig,
/// Builder-level opt-in. When false, stats obfuscation stays off
Expand Down Expand Up @@ -107,7 +109,7 @@ fn is_obfuscation_active(agent_info: &AgentInfo) -> bool {
agent_info
.info
.obfuscation_version
.is_some_and(|v| v >= 1 && v <= SUPPORTED_OBFUSCATION_VERSION)
.is_some_and(|v| v >= 1 && v == SUPPORTED_OBFUSCATION_VERSION)
}

#[cfg(not(target_arch = "wasm32"))]
Expand Down Expand Up @@ -142,7 +144,7 @@ pub(crate) fn start_stats_computation<
std::time::SystemTime::now(),
span_kinds,
peer_tags,
ctx.stats_cardinality_limit,
ctx.stats_cardinality_limits,
#[cfg(feature = "stats-obfuscation")]
Some(client_side_stats.obfuscation_config.clone()),
)));
Expand Down Expand Up @@ -170,8 +172,6 @@ fn create_and_start_stats_worker<
Endpoint::from_url(add_path(ctx.endpoint_url, STATS_ENDPOINT)),
capabilities.clone(),
#[cfg(feature = "stats-obfuscation")]
client_side_stats.obfuscation_config.clone(),
#[cfg(feature = "stats-obfuscation")]
SUPPORTED_OBFUSCATION_VERSION_STR,
#[cfg(feature = "telemetry")]
ctx.telemetry.clone(),
Expand Down Expand Up @@ -258,6 +258,7 @@ fn update_obfuscation_config(
) {
let obfuscation_active =
client_side_stats.obfuscation_enabled && is_obfuscation_active(agent_info);
// FIXME(APMSP-3720): there is more than this to obfuscation config
let sql_obfuscation_mode = (|| {
agent_info
.info
Expand Down
1 change: 1 addition & 0 deletions libdd-trace-stats/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ tokio = { version = "1.23", features = ["macros", "time"], default-features = fa
tokio-util = "0.7.11"
tracing = { version = "0.1", default-features = false }
async-trait = "0.1.85"
futures = { version = "0.3.32", default-features = false, features = ["alloc"] }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
libdd-capabilities-impl = { version = "3.0.0", path = "../libdd-capabilities-impl", default-features = false }
Expand Down
Loading
Loading