Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
98fc6e5
feat(datadog-ffe): add flagevaluation EVP payload module + Cargo feat…
leoromanovsky Jun 12, 2026
89a2ba7
feat(datadog-sidecar): add FfeFlagEvaluationBatch sidecar action + EV…
leoromanovsky Jun 12, 2026
de8e987
fix(ffe): make flagevaluation batch bincode-safe over the sidecar IPC
leoromanovsky Jun 14, 2026
d02b30a
style(ffe): rustfmt + fix clippy expect-on-Option in enqueue_actions_…
leoromanovsky Jun 15, 2026
22f5642
fix(ffe): align flagevaluation sidecar delivery with worker schema
leoromanovsky Jun 16, 2026
c73a381
chore(ffe): apply flagevaluation rustfmt
leoromanovsky Jun 16, 2026
5e0ebf9
Merge remote-tracking branch 'origin/main' into leo.romanovsky/ffl-24…
leoromanovsky Jun 16, 2026
b576ea7
coalesce flagevaluation batches in sidecar
leoromanovsky Jun 17, 2026
53f81e5
fix(ffe): bound flagevaluation sidecar delivery
leoromanovsky Jun 17, 2026
fdfacd2
Merge remote-tracking branch 'origin/main' into leo.romanovsky/ffl-24…
leoromanovsky Jun 19, 2026
11ca09c
Update flagevaluation EVP endpoint contract
leoromanovsky Jun 19, 2026
3350d4b
Fix flagevaluation flusher clippy handling
leoromanovsky Jun 19, 2026
e16802b
Share FFE EVP proxy transport
leoromanovsky Jun 23, 2026
60fe825
fix(datadog-sidecar): satisfy clippy for EVP constants
leoromanovsky Jun 23, 2026
46734bc
fix(ffe): bound flagevaluation evp payloads
leoromanovsky Jun 23, 2026
2fa796b
fix(sidecar): emit flagevaluation telemetry counters
leoromanovsky Jun 23, 2026
4d38cc5
refactor(sidecar): share EVP proxy constants
leoromanovsky Jun 23, 2026
8dd0418
Use standard queue for FFE flag evaluations
leoromanovsky Jun 25, 2026
47474b0
Move FFE EVP writer logic into datadog-ffe
leoromanovsky Jun 25, 2026
7f642c3
Move FFE flag evaluation sender into datadog-ffe
leoromanovsky Jun 25, 2026
5f14270
Add flagevaluation EVP benchmarks
leoromanovsky Jun 28, 2026
22e356e
Merge remote-tracking branch 'origin/main' into leo.romanovsky/ffl-24…
leoromanovsky Jul 1, 2026
a57a94a
fix(datadog-ffe): align flagevaluation EVP payload limit
leoromanovsky Jul 1, 2026
2b6779a
fix(sidecar): gate FFE flag evaluation flushes
leoromanovsky Jul 3, 2026
3f59d64
fix(sidecar): name flag evaluation count metrics
leoromanovsky Jul 3, 2026
7898485
fix(sidecar): prune flag evaluation FFI context
leoromanovsky Jul 3, 2026
309ea95
fix(ffe): scope flag evaluation placeholder stripping
leoromanovsky Jul 3, 2026
bdbe29c
chore(ffe): simplify flag evaluation payload bookkeeping
leoromanovsky Jul 3, 2026
f1eb1ef
fix(ffe): lower flag evaluation sender failures to debug
leoromanovsky Jul 3, 2026
3ea3104
fix(ffe): preserve generic coalescer defaults
leoromanovsky Jul 3, 2026
2972e30
docs(ffe): explain manual default impls
leoromanovsky Jul 3, 2026
2f74955
chore(sidecar): apply nightly rustfmt
leoromanovsky Jul 3, 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
6 changes: 6 additions & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion datadog-ffe-test-suite/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ publish = false
bench = false

[dev-dependencies]
datadog-ffe = { path = "../datadog-ffe" }
datadog-ffe = { path = "../datadog-ffe", features = ["flagevaluation-evp"] }
chrono = { version = "0.4.38", default-features = false, features = ["now", "serde"] }
criterion = { version = "0.5", features = ["html_reports"] }
env_logger = "0.10"
Expand Down
155 changes: 152 additions & 3 deletions datadog-ffe-test-suite/benches/eval.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0

use criterion::{black_box, criterion_group, criterion_main, Bencher, Criterion};
use criterion::{
black_box, criterion_group, criterion_main, BatchSize, Bencher, Criterion, Throughput,
};
use datadog_ffe::telemetry::flagevaluation::{
encode_flag_evaluation_payloads, AllocationKey, ContextDD, FfeFlagEvaluationBatch,
FfeFlagEvaluationEvent, FlagEvalEventContext, FlagEvaluationEvpCoalescer, FlagKey, VariantKey,
EVP_PAYLOAD_SIZE_LIMIT,
};
use datadog_ffe::telemetry::FfeTelemetryContext;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fs, sync::Arc};
use std::{
collections::{BTreeMap, HashMap},
fs,
sync::Arc,
};

use datadog_ffe::rules_based::{
get_assignment, Attribute, Configuration, EvaluationContext, ExpectedFlagType, FlagType, Str,
Expand Down Expand Up @@ -140,5 +152,142 @@ fn bench_single_flag(c: &mut Criterion) {
group.finish();
}

criterion_group!(benches, bench_sdk_test_data, bench_single_flag);
#[derive(Clone, Copy)]
struct FlagEvalBenchProfile {
name: &'static str,
num_flags: usize,
num_users: usize,
num_fields: usize,
}

const FLAG_EVAL_BENCH_PROFILES: [FlagEvalBenchProfile; 3] = [
FlagEvalBenchProfile {
name: "typical/100flags_50users_10fields",
num_flags: 100,
num_users: 50,
num_fields: 10,
},
FlagEvalBenchProfile {
name: "stress/10flags_1000users_250fields",
num_flags: 10,
num_users: 1_000,
num_fields: 250,
},
FlagEvalBenchProfile {
name: "scale/2500flags_500users_20fields",
num_flags: 2_500,
num_users: 500,
num_fields: 20,
},
];

fn flag_eval_context() -> FfeTelemetryContext {
FfeTelemetryContext {
service: "bench-service".to_string(),
env: "ci".to_string(),
version: "1.0.0".to_string(),
}
}

fn flag_eval_attrs(num_fields: usize) -> String {
let attrs = (0..num_fields)
.map(|i| {
(
format!("field{i}"),
serde_json::Value::String("value".to_string()),
)
})
.collect::<BTreeMap<_, _>>();
serde_json::to_string(&attrs).expect("benchmark attrs must encode")
}

fn flag_eval_events(profile: FlagEvalBenchProfile) -> Vec<FfeFlagEvaluationEvent> {
let attrs = flag_eval_attrs(profile.num_fields);
let cycle_count = profile.num_flags.max(profile.num_users);
(0..cycle_count)
.map(|i| FfeFlagEvaluationEvent {
timestamp: 1_760_000_000_000,
flag: FlagKey {
key: format!("bench-flag-{}", i % profile.num_flags),
},
first_evaluation: 1_760_000_000_000 + i as i64,
last_evaluation: 1_760_000_000_000 + i as i64,
evaluation_count: 1,
variant: Some(VariantKey {
key: format!("variant-{}", i % 4),
}),
allocation: Some(AllocationKey {
key: format!("alloc-{}", i % profile.num_flags),
}),
targeting_rule: None,
targeting_key: Some(format!("bench-user-{}", i % profile.num_users)),
context: Some(FlagEvalEventContext {
evaluation: Some(attrs.clone()),
dd: Some(ContextDD {
service: "bench-service".to_string(),
}),
}),
error: None,
runtime_default_used: false,
})
.collect()
}

fn flag_eval_batch(profile: FlagEvalBenchProfile) -> FfeFlagEvaluationBatch {
FfeFlagEvaluationBatch {
context: flag_eval_context(),
flag_evaluations: flag_eval_events(profile),
}
}

fn bench_flagevaluation_evp_coalescer(c: &mut Criterion) {
let mut group = c.benchmark_group("flagevaluation_evp/coalescer");
for profile in FLAG_EVAL_BENCH_PROFILES {
let batch = flag_eval_batch(profile);
group.throughput(Throughput::Elements(batch.flag_evaluations.len() as u64));
group.bench_function(profile.name, |b| {
b.iter_batched(
|| batch.clone(),
|batch| {
let coalescer = FlagEvaluationEvpCoalescer::<String>::default();
coalescer.enqueue("agent".to_string(), black_box(batch));
let batches = coalescer.take_batches();
coalescer.finish_flush_cycle();
black_box(batches);
},
BatchSize::SmallInput,
)
});
}
group.finish();
}

fn bench_flagevaluation_evp_payloads(c: &mut Criterion) {
let mut group = c.benchmark_group("flagevaluation_evp/payloads");
for profile in FLAG_EVAL_BENCH_PROFILES {
let batch = flag_eval_batch(profile);
group.throughput(Throughput::Elements(batch.flag_evaluations.len() as u64));
group.bench_function(profile.name, |b| {
b.iter_batched(
|| batch.clone(),
|batch| {
let payloads =
encode_flag_evaluation_payloads(black_box(batch), EVP_PAYLOAD_SIZE_LIMIT)
.expect("benchmark payload should encode");
black_box(payloads);
},
BatchSize::SmallInput,
)
});
}
group.finish();
}

criterion_group!(
benches,
bench_sdk_test_data,
bench_single_flag,
bench_flagevaluation_evp_coalescer,
bench_flagevaluation_evp_payloads
);
criterion_main!(benches);
12 changes: 12 additions & 0 deletions datadog-ffe/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ derive_more = { version = "2.0.0", default-features = false, features = ["from",
log = { version = "0.4.21", default-features = false, features = ["kv", "kv_serde"] }
md5 = { version = "0.7.0", default-features = false }
libdd-common = { version = "5.0.0", path = "../libdd-common", default-features = false, features = ["require-regex-full"] }
libdd-capabilities = { path = "../libdd-capabilities", version = "2.0.0", optional = true }
http = { version = "1.1", optional = true }
tokio = { version = "1.49.0", features = ["time"], optional = true }
semver = "1.0"
serde-bool = { version = "0.1.3", default-features = false }
serde_with = { version = "3.11.0", default-features = false, features = ["base64", "hex", "macros"] }
Expand All @@ -30,9 +33,18 @@ libdd-trace-protobuf = { path = "../libdd-trace-protobuf", optional = true }
prost = { version = "0.14.1", optional = true }
pyo3 = { version = "0.28", optional = true, default-features = false, features = ["macros"] }

[dev-dependencies]
# Matches the sidecar's bincode version. Used by the flagevaluation bincode
# round-trip test, which guards against `skip_serializing_if` reintroducing the
# worker→sidecar IPC field-misalignment bug (bincode is non-self-describing).
bincode = { version = "1.3.3" }
httpmock = "0.8.0-alpha.1"
libdd-capabilities-impl = { path = "../libdd-capabilities-impl" }

[features]
default = ["remote-config"]
exposure-events = ["dep:lru"]
evaluation-metrics = ["dep:libdd-trace-protobuf", "dep:prost"]
flagevaluation-evp = ["dep:http", "dep:libdd-capabilities", "dep:tokio"]
pyo3 = ["dep:pyo3"]
remote-config = ["dep:libdd-remote-config"]
6 changes: 5 additions & 1 deletion datadog-ffe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ mod flag_type;
#[cfg(feature = "remote-config")]
mod remote_config;
pub mod rules_based;
#[cfg(any(feature = "exposure-events", feature = "evaluation-metrics"))]
#[cfg(any(
feature = "exposure-events",
feature = "evaluation-metrics",
feature = "flagevaluation-evp"
))]
pub mod telemetry;

pub use flag_type::{ExpectedFlagType, FlagType};
Loading
Loading