Skip to content

feat(dogstatsd): allowlist tag values by metric prefix - #2271

Open
lukesteensen wants to merge 1 commit into
mainfrom
feature/tag-value-allowlist
Open

feat(dogstatsd): allowlist tag values by metric prefix#2271
lukesteensen wants to merge 1 commit into
mainfrom
feature/tag-value-allowlist

Conversation

@lukesteensen

Copy link
Copy Markdown
Contributor

Summary

Add metric_tag_value_allowlist, which limits DogStatsD metric cardinality by retaining selected values for a tag and removing or replacing values outside the allow-list.

Rules select metrics using case-sensitive metric-name prefixes:

metric_tag_value_allowlist:
  - metric_prefix: requests.
    tag_name: customer_id
    values: [customer-1, customer-2]
    on_miss: replace
    replacement: other

This change also moves the existing metric_tag_filterlist component from component-local deserialization and GenericConfiguration access to the typed DogStatsD configuration domain. Dynamic whole-tag updates continue through a narrow Live<T> view, while value allow-list rules remain static.

Design decisions

  • Match final metric names. Prefixes match after DogStatsD mapper rewrites and statsd_metric_namespace prefixing, so rules describe names as aggregation sees them.
  • Reject overlapping prefixes for the same tag. Duplicate or nested prefixes for one tag fail at startup instead of introducing precedence or union behavior. Different tags may use overlapping prefixes.
  • Run whole-tag filtering first. An include rule must retain the value-filtered tag name or the whole-tag rule removes it before value filtering.
  • Distinguish bare and empty-valued tags. A bare tag such as customer_id has no value and remains unchanged. customer_id: has the value "" and is subject to the allow-list.
  • Keep value rules static initially. Changing metric_tag_value_allowlist requires an ADP restart. Remote Config updates to metric_tag_filterlist do not replace value rules.
  • Preserve the existing metric and tag scope. Rules apply to counters and sketch-backed metrics, including instrumented and origin tags. Other metric types pass through unchanged.
  • Validate configuration strictly. ADP rejects empty prefixes and tag names, : in tag names, overlapping same-tag prefixes, and surrounding whitespace. Empty replacements and replacement collisions remain operator policy choices.
  • Start with a linear prefix scan. Matching outcomes are cached, while rule misses are not cached so unrelated traffic cannot evict policy-relevant entries. We should benchmark before adding a prefix index or changing the cache policy.
  • Count filtered originals in telemetry. A replaced tag contributes to the filtered-tag counter; replacement insertions are not counted separately.

Change Type

  • Bug fix
  • New feature
  • Non-functional (chore, refactoring, docs)
  • Performance

How did you test this PR?

  • make fmt
  • cargo check --workspace
  • cargo check --workspace --tests
  • make test-all
    • 2,520 unit tests passed
    • 26 property tests passed
    • Workspace doctests passed
    • 49 Miri tests passed
    • 8 Loom tests passed
  • Full-workspace Clippy passed.
  • Configuration-system and configuration smoke tests passed.
  • Dependency advisory, license, unused-dependency, and feature-matrix checks passed.
  • Vale passed on the documentation changed by this PR.
  • The assembled DogStatsD topology test verifies that a real allow-list retains a listed value and removes an unlisted value after mapper and namespace processing.
  • Existing dsd-tag-filterlist correctness case
    • Baseline and comparison emitted the same 178 unique non-internal metrics.
  • git diff --check

make check-all passes formatting and full-workspace Clippy, then stops on an existing Vale spelling error in unchanged docs/development/contributing.md (PR's). The remaining check targets were run separately and passed.

References

N/A

@lukesteensen
lukesteensen requested a review from a team as a code owner August 5, 2026 15:30
@dd-octo-sts dd-octo-sts Bot added the area/docs Reference documentation. label Aug 5, 2026

@datadog-prod-us1-6 datadog-prod-us1-6 Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Datadog Autotest: PASS

More details

The allowlist implementation and typed configuration path cover the reviewed prefix, replacement, bare/empty tag, origin-tag, overlap, cache, and mapper/namespace scenarios without a clear diff-only regression. Focused Rust execution was unavailable because the sandbox could not fetch the pinned hyper-http-proxy git dependency; no additional tests recommended: existing adversarial unit coverage already exercises the relevant branches.

Was this helpful? React 👍 or 👎

Open Bits AI session

🤖 Datadog Autotest · Commit a63f659 · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a63f659d92

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/agent-data-plane-config-system/src/saluki_only.rs Outdated
@lukesteensen
lukesteensen force-pushed the feature/tag-value-allowlist branch from a63f659 to f92621d Compare August 5, 2026 15:42
@pr-commenter

pr-commenter Bot commented Aug 5, 2026

Copy link
Copy Markdown

Binary Size Analysis (Agent Data Plane)

Baseline: 0f47357 · Comparison: 257b400 · diff
Analysis Configuration: stripped binaries · Pass/Fail Threshold: +5%
Sizes: 41.27 MiB (baseline) vs 41.14 MiB (comparison)
Size Change: -134.50 KiB (-0.32%)

✅ Binary size difference within threshold

Changes by Module
Module File Size Symbols
core -72.61 KiB 4745
serde_json +31.94 KiB 353
[sections] -31.50 KiB 9
tokio -30.57 KiB 1525
http_body_util -25.11 KiB 99
agent_data_plane::dogstatsd_contexts::artifact -24.86 KiB 24
figment -18.86 KiB 134
agent_data_plane::internal::remote_agent +18.84 KiB 119
anon.1ac75c3e2cc4e42e4c4d3caf2e351f47.630.llvm.2617875662619396550 +17.43 KiB 1
anon.6c8cc82583d787f2710e1f8f86e78987.13.llvm.13314861055718887123 -17.17 KiB 1
agent_data_plane_config_system::saluki_only::_ +16.87 KiB 4
agent_data_plane_config::domains::dogstatsd +15.96 KiB 22
comfy_table -14.67 KiB 19
smallvec +13.14 KiB 72
chrono +12.63 KiB 15
agent_data_plane_config_system::saluki_env_overlay::PathRecorder -12.45 KiB 23
agent_data_plane_config::shared::_ -12.38 KiB 16
anon.eaa35ebdc5998855717b6f667ad22404.791.llvm.3148331925466594304 -11.93 KiB 1
anon.1ac75c3e2cc4e42e4c4d3caf2e351f47.669.llvm.2617875662619396550 +11.84 KiB 1
agent_data_plane::internal::env +10.84 KiB 204
Detailed Symbol Changes
    FILE SIZE        VM SIZE    
 --------------  -------------- 
  [NEW] +42.8Ki  [NEW] +42.7Ki    agent_data_plane::cli::run::handle_run_command::_{{closure}}::h6d892181b81751eb
  [NEW] +40.4Ki  [NEW] +40.3Ki    agent_data_plane::cli::run::create_topology::_{{closure}}::h4e6ee5a870242c28
  [NEW] +31.1Ki  [NEW] +30.9Ki    datadog_agent_commons::ipc::client::RemoteAgentClient::from_client_configuration::_{{closure}}::_{{closure}}::_{{closure}}::h15e5c55b2534bd2f
  [NEW] +31.1Ki  [NEW] +30.9Ki    agent_data_plane::cli::dogstatsd::run_dogstatsd_command::_{{closure}}::h6b215381893ce646
  [NEW] +26.7Ki  [NEW] +26.5Ki    _<figment::value::de::ConfiguredValueDe<I> as serde_core::de::Deserializer>::deserialize_struct::hff3ee7185b08af36
  [NEW] +25.9Ki  [NEW] +25.8Ki    _<figment::value::de::ConfiguredValueDe<I> as serde_core::de::Deserializer>::deserialize_struct::hf46c6b57f60ea399
  [NEW] +25.9Ki  [NEW] +25.8Ki    agent_data_plane::state::metrics::rules::get_datadog_agent_remappings::hf494852330a7d7e5
  [NEW] +25.0Ki  [NEW] +24.8Ki    agent_data_plane::internal::remote_agent::run_remote_agent_registration_loop::_{{closure}}::h5e51a25fd3e89d0b
  [NEW] +22.3Ki  [NEW] +22.1Ki    agent_data_plane::main::_{{closure}}::heac4885bfc658838
  [NEW] +20.7Ki  [NEW] +20.5Ki    agent_data_plane::cli::debug::handle_debug_command::_{{closure}}::h9acf3dd4891f6833
  [DEL] -21.0Ki  [DEL] -20.9Ki    agent_data_plane::cli::debug::handle_debug_command::_{{closure}}::ha491094e358ebf93
  [DEL] -21.7Ki  [DEL] -21.6Ki    agent_data_plane::main::_{{closure}}::h6b51b32b5abcd802
  [DEL] -25.1Ki  [DEL] -25.0Ki    agent_data_plane::internal::remote_agent::run_remote_agent_registration_loop::_{{closure}}::ha8741207da6d0025
  -2.2% -25.8Ki  -2.2% -25.8Ki    [section .gcc_except_table]
  [DEL] -26.7Ki  [DEL] -26.5Ki    datadog_agent_commons::ipc::client::RemoteAgentClient::from_client_configuration::_{{closure}}::_{{closure}}::_{{closure}}::h3146495241b183c8
  [DEL] -26.7Ki  [DEL] -26.5Ki    core::ptr::drop_in_place<agent_data_plane::cli::run::handle_run_command::{{closure}}>::hac8ba744a6354ad7
  [DEL] -28.5Ki  [DEL] -28.3Ki    agent_data_plane::dogstatsd_contexts::artifact::for_each_record::h39b2832027986acc
  [DEL] -30.6Ki  [DEL] -30.4Ki    agent_data_plane::cli::dogstatsd::run_dogstatsd_command::_{{closure}}::h8e6a26bba5766166
  [DEL] -40.3Ki  [DEL] -40.2Ki    agent_data_plane::cli::run::create_topology::_{{closure}}::h23953cd93c271a64
  [DEL] -42.2Ki  [DEL] -42.1Ki    agent_data_plane::cli::run::handle_run_command::_{{closure}}::h8c5bc31d01253285
  -1.2%  -137Ki  -1.1% -95.2Ki    [18133 Others]
  -0.3%  -134Ki  -0.3% -92.0Ki    TOTAL

@pr-commenter

pr-commenter Bot commented Aug 5, 2026

Copy link
Copy Markdown

Regression Detector (Agent Data Plane)

Run ID: 9ac5b2a1-b92e-4aef-9f90-33daff887b8a
Baseline: 0f47357a · Comparison: 257b4007 · diff

Optimization Goals: ✅ No significant changes detected

Fine details of change detection per experiment (5)

Experiments configured erratic: true are tagged (ignored) and skipped when determining which experiments regressed or improved. Experiments which are detected as erratic at runtime are tagged (erratic) to flag that the run's sample dispersion was high, but their regression / improvement signal still counts.

experiment goal Δ mean % links
quality_gates_rss_dsd_ultraheavy memory ⚪ +0.96 metrics profiles logs
quality_gates_rss_dsd_medium memory ⚪ +0.49 metrics profiles logs
quality_gates_rss_dsd_heavy memory ⚪ +0.34 metrics profiles logs
quality_gates_rss_dsd_low memory ⚪ +0.07 metrics profiles logs
quality_gates_rss_idle memory ⚪ -0.17 metrics profiles logs
Bounds Checks: ✅ Passed (5)
experiment check replicates observed links
quality_gates_rss_dsd_heavy memory_usage 10/10 ✅ 228 MiB ≤ 250 MiB metrics profiles logs
quality_gates_rss_dsd_low memory_usage 10/10 ✅ 51 MiB ≤ 60 MiB metrics profiles logs
quality_gates_rss_dsd_medium memory_usage 10/10 ✅ 90.4 MiB ≤ 100 MiB metrics profiles logs
quality_gates_rss_dsd_ultraheavy memory_usage 10/10 ✅ 384 MiB ≤ 420 MiB metrics profiles logs
quality_gates_rss_idle memory_usage 10/10 ✅ 31.8 MiB ≤ 40 MiB metrics profiles logs
Explanation

A change is flagged as a regression when |Δ mean %| > 5.00% in the regressing direction for its optimization goal AND SMP marks the experiment as a regression (is_regression: true). Improvements use the matching criteria for the improving direction. Experiments configured erratic: true (tagged (ignored)) are skipped outright; experiments detected as erratic at runtime (tagged (erratic)) still count, since that flag describes sample dispersion rather than directional certainty. The Δ mean % cell is colored accordingly: 🟢 = improvement, 🔴 = regression, ⚪ = neutral. Reduction in CPU or memory is an improvement; reduction in ingress throughput is a regression. Experiments tagged (no analysis) show ⚠️ n/a: SMP ran them but produced no analysis, usually because a replicate failed and exhausted its retries. Check the SMP report for that experiment's replicate failures.

@webern
webern self-requested a review August 10, 2026 13:04

@webern webern left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks great over all, except for one sticking point which is what should happen if bad config reaches the component (currently a panic).

Also I have a question. Is this feature development that is mirroring an Agent behavior? Or is this "green field" feature development on the ADP-side only?

This is an important question for testing because currently we would use a correctness test for integration to see that we match Agent behavior. But we don't have a great mechanism for testing greenfield behavior at the moment.

ChainedConfiguration::default().with_transform_builder("dogstatsd_mapper", dsd_mapper_config);
let dsd_tag_filterlist_config = TagFilterlistConfiguration::from_configuration(config)
let dogstatsd_config = config_system.live(|config| &config.domains.dogstatsd);
let dsd_tag_filterlist_config = TagFilterlistConfiguration::from_configuration(dogstatsd_config)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Good change. Switching from GenericConfiguration to Live<dogstatsd::Domain>.

Comment on lines -1022 to -1026
"metric_tag_filterlist": [{
"metric_name": "tenant.mapped.requests",
"action": "exclude",
"tags": ["remove"]
}],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Makes sense because Metric Tag Filter list switched to typed config. 👍

Comment thread lib/agent-data-plane-config-system/src/saluki_env_overlay.rs
Comment thread lib/agent-data-plane-config-system/src/saluki_only.rs
Comment on lines +411 to +412
self.filters = compile_all_filters(&new_entries, &self.value_allowlists)
.expect("the static value allow-list was validated when the transform was built");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This makes me a little bit nervous. If the validation at the config level is sufficient, then ideally the compile_all_filters function would not need to return a Result. But if it legitimately can fail because the config layer cannot be expected to sufficiently validate incoming filters, then something better than panicking probably needs to be done.

I think the problem to resolve is this:

  • If the config layer can't completely validate the incoming filter list, and
  • later, the component determines the list to be invalid...

There isn't really a graceful way out. You have accepted configuration that the component cannot express. If that's really the case then perhaps a panic is the right thing to do. But a better solution would be to try and make sure a bad config cannot be accepted by the system.

Note that, the way the config system is set up right now, on system startup, bad config fails the system. Later once we are serving, a bad partial config update (i.e. a remote config change) will report an error but we will continue running on the last good config.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This makes me nervous because I don't actually think it's correct?

If we assume that we validated the value allowlist at first by virtue of making it to this point, there's still the logic in add_value_allowlists that checks for metric prefix overlap, which might occur at runtime if the new incoming metric tag filter configuration changes significantly?

Maybe I'm misreading it, but that looks sus to me... and it's not immediately clear how to reconcile that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, tried to rework this to be clearer. I won't claim my understanding of the config system is 100% yet, but we should now be fully parsing and validating in the config system itself. Runtime updates should also end up replacing the full key after validation, so partial updates can't introduce an overlap.

@pr-commenter

pr-commenter Bot commented Aug 11, 2026

Copy link
Copy Markdown

Regression Detector (Agent Data Plane, full suite)

Optimization Goals: ⚠️ Report unavailable

The benchmark run did not produce a usable report: outputs/report.json is missing

Check the run-benchmarks-adp job logs for details.


use self::telemetry::Telemetry;
struct CompiledTagValueAllowlist {
allowed_values: HashSet<String, FoldHashState>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we just import FastHashSet and use the alias directly here? I realize we're explicitly importing hashbrown versions here, vs the recommended aliases pointing at stdlib versions, but it doesn't seem like we actually use anything from hashbrown that we can't get from stdlib, so...

(Same with HashMap further down.)

/// - Same metric name + conflicting actions → `exclude` wins.
pub fn compile_filters(entries: &[MetricTagFilterEntry]) -> CompiledFilters {
let mut filters: CompiledFilters = HashMap::with_hasher(FoldHashState::default());
let mut filters = CompiledFilters {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should just be able to derive Default on CompiledFilters and then use CompiledFilters::default() here.

/// # Errors
///
/// Returns an error when two entries for the same tag have overlapping metric prefixes.
pub fn add_value_allowlists(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A general comment on the logic here: can we just do all of the string trimming automatically on behalf of the user?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I revisited this and got rid of the trimming to be consistent with what we do for the full tag feature. Seemed a little overly aggressive on second thought.

Comment on lines +411 to +412
self.filters = compile_all_filters(&new_entries, &self.value_allowlists)
.expect("the static value allow-list was validated when the transform was built");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This makes me nervous because I don't actually think it's correct?

If we assume that we validated the value allowlist at first by virtue of making it to this point, there's still the logic in add_value_allowlists that checks for metric prefix overlap, which might occur at runtime if the new incoming metric tag filter configuration changes significantly?

Maybe I'm misreading it, but that looks sus to me... and it's not immediately clear how to reconcile that.

@webern webern left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice changes. LGTM.

@lukesteensen
lukesteensen force-pushed the feature/tag-value-allowlist branch from 2b7632e to 257b400 Compare August 14, 2026 20:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docs Reference documentation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants