Skip to content

Commit 04e7de5

Browse files
committed
enhancement(o11y): emit tag filterlist cache hit/miss/evict counters (#1820)
## Summary Adds telemetry for cache evictions as well as exposes the hits, misses, and evictions telemetry for tag filter component to Core Agent via Remote Agent Interface. ## Test plan - [ ] `cargo test -p saluki-common` — cache infrastructure tests pass - [ ] `cargo test -p agent-data-plane` — remapping output and help text tests pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: jesse.szwedko <jesse.szwedko@datadoghq.com>
1 parent ab09426 commit 04e7de5

4 files changed

Lines changed: 81 additions & 8 deletions

File tree

bin/agent-data-plane/src/state/metrics/mod.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,21 @@ mod tests {
830830
),
831831
23.0,
832832
)),
833+
Event::Metric(Metric::counter(
834+
Context::from_static_parts("adp.cache_hits_total", &["cache_id:tag_filterlist/context_cache"]),
835+
13.0,
836+
)),
837+
Event::Metric(Metric::counter(
838+
Context::from_static_parts("adp.cache_misses_total", &["cache_id:tag_filterlist/context_cache"]),
839+
17.0,
840+
)),
841+
Event::Metric(Metric::counter(
842+
Context::from_static_parts(
843+
"adp.cache_items_evicted_total",
844+
&["cache_id:tag_filterlist/context_cache"],
845+
),
846+
19.0,
847+
)),
833848
];
834849

835850
for metric in metrics {
@@ -847,11 +862,15 @@ mod tests {
847862
assert!(output.contains("tag_filterlist__size 9"));
848863
assert!(output.contains("tag_filterlist__updates 11"));
849864
assert!(output.contains("aggregator__filtered_tags 23"));
865+
assert!(output.contains("aggregator__filtered_tags_cache_hit 13"));
866+
assert!(output.contains("aggregator__filtered_tags_cache_miss 17"));
867+
assert!(output.contains("aggregator__filtered_tags_cache_evict 19"));
850868
assert!(!output.contains("datadog__agent__filterlist__size"));
851869
assert!(!output.contains("datadog__agent__filterlist__updates"));
852870
assert!(!output.contains("datadog__agent__dogstatsd__listener_filtered_points"));
853871
assert!(!output.contains("datadog__agent__aggregator__dogstatsd_filtered_metrics"));
854872
assert!(!output.contains("component_id="));
873+
assert!(!output.contains("cache_id="));
855874
}
856875

857876
#[test]
@@ -907,5 +926,17 @@ mod tests {
907926
find("aggregator.filtered_tags"),
908927
Some("How many tags were filtered from a metric sample")
909928
);
929+
assert_eq!(
930+
find("aggregator.filtered_tags_cache_hit"),
931+
Some("How many times we hit the cache on filtering tags")
932+
);
933+
assert_eq!(
934+
find("aggregator.filtered_tags_cache_miss"),
935+
Some("How many times we missed the cache on filtering tags")
936+
);
937+
assert_eq!(
938+
find("aggregator.filtered_tags_cache_evict"),
939+
Some("How many times an entry was evicted from the tag filter cache")
940+
);
910941
}
911942
}

bin/agent-data-plane/src/state/metrics/rules/dogstatsd.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,24 @@ pub fn get_dogstatsd_remappings() -> Vec<RemapperRule> {
3939
"aggregator.filtered_tags",
4040
)
4141
.with_help_text("How many tags were filtered from a metric sample"),
42+
RemapperRule::by_name_and_tags(
43+
"adp.cache_hits_total",
44+
&["cache_id:tag_filterlist/context_cache"],
45+
"aggregator.filtered_tags_cache_hit",
46+
)
47+
.with_help_text("How many times we hit the cache on filtering tags"),
48+
RemapperRule::by_name_and_tags(
49+
"adp.cache_misses_total",
50+
&["cache_id:tag_filterlist/context_cache"],
51+
"aggregator.filtered_tags_cache_miss",
52+
)
53+
.with_help_text("How many times we missed the cache on filtering tags"),
54+
RemapperRule::by_name_and_tags(
55+
"adp.cache_items_evicted_total",
56+
&["cache_id:tag_filterlist/context_cache"],
57+
"aggregator.filtered_tags_cache_evict",
58+
)
59+
.with_help_text("How many times an entry was evicted from the tag filter cache"),
4260
RemapperRule::by_name_and_tags(
4361
"adp.object_pool_acquired",
4462
&["pool_name:dsd_packet_bufs"],

lib/saluki-common/src/cache/expiry.rs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::{
66

77
use crossbeam_queue::ArrayQueue;
88
use quick_cache::Lifecycle;
9+
use saluki_metrics::reexport::metrics::Counter;
910

1011
use crate::{
1112
collections::FastHashMap,
@@ -15,6 +16,7 @@ use crate::{
1516
/// Builder for creating an expiration configuration.
1617
pub struct ExpirationBuilder<K> {
1718
time_to_idle: Option<Duration>,
19+
items_evicted: Counter,
1820
_key: PhantomData<K>,
1921
}
2022

@@ -23,9 +25,10 @@ where
2325
K: Eq + std::hash::Hash,
2426
{
2527
/// Creates a new `ExpirationBuilder`.
26-
pub fn new() -> Self {
28+
pub fn new(items_evicted: Counter) -> Self {
2729
Self {
2830
time_to_idle: None,
31+
items_evicted,
2932
_key: PhantomData,
3033
}
3134
}
@@ -47,11 +50,14 @@ where
4750
/// Builds the expiration configuration.
4851
pub fn build(self) -> (Expiration<K>, ExpiryCapableLifecycle<K>) {
4952
match self.time_to_idle {
50-
None => (Expiration::disabled(), ExpiryCapableLifecycle::disabled()),
53+
None => (
54+
Expiration::disabled(),
55+
ExpiryCapableLifecycle::disabled(self.items_evicted),
56+
),
5157
Some(time_to_idle) => {
5258
let state = Arc::new(State::new(time_to_idle));
5359
let expiration = Expiration::from_state(Arc::clone(&state));
54-
let lifecycle = ExpiryCapableLifecycle::from_state(state);
60+
let lifecycle = ExpiryCapableLifecycle::with_state(state, self.items_evicted);
5561

5662
(expiration, lifecycle)
5763
}
@@ -228,15 +234,22 @@ where
228234
#[derive(Clone)]
229235
pub(super) struct ExpiryCapableLifecycle<K> {
230236
state: Option<Arc<State<K>>>,
237+
items_evicted: Counter,
231238
}
232239

233240
impl<K> ExpiryCapableLifecycle<K> {
234-
fn disabled() -> Self {
235-
Self { state: None }
241+
fn disabled(items_evicted: Counter) -> Self {
242+
Self {
243+
state: None,
244+
items_evicted,
245+
}
236246
}
237247

238-
fn from_state(state: Arc<State<K>>) -> Self {
239-
Self { state: Some(state) }
248+
fn with_state(state: Arc<State<K>>, items_evicted: Counter) -> Self {
249+
Self {
250+
state: Some(state),
251+
items_evicted,
252+
}
240253
}
241254
}
242255

@@ -251,6 +264,10 @@ where
251264

252265
#[inline]
253266
fn on_evict(&self, _state: &mut Self::RequestState, key: K, _value: V) {
267+
// Note: this fires for all capacity-driven evictions including rejected overweight
268+
// inserts (items whose weight exceeds the cache capacity). Callers using custom
269+
// weighters may see slight inflation from rejected inserts.
270+
self.items_evicted.increment(1);
254271
if let Some(state) = self.state.as_ref() {
255272
state.mark_entry_removed(key);
256273
}

lib/saluki-common/src/cache/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::{marker::PhantomData, num::NonZeroUsize, sync::Arc, time::Duration};
22

33
use saluki_error::GenericError;
4+
use saluki_metrics::reexport::metrics::Counter;
45
use saluki_metrics::static_metrics;
56
use tokio::time::sleep;
67
use tokio_util::sync::{CancellationToken, DropGuard};
@@ -26,6 +27,7 @@ static_metrics! {
2627
gauge(weight_limit),
2728
counter(hits_total),
2829
counter(misses_total),
30+
counter(items_evicted_total),
2931
debug_counter(items_inserted_total),
3032
debug_counter(items_removed_total),
3133
debug_counter(items_expired_total),
@@ -224,7 +226,12 @@ where
224226
telemetry.weight_limit().set(capacity as f64);
225227

226228
// Configure expiration if enabled.
227-
let mut expiration_builder = ExpirationBuilder::new();
229+
let eviction_counter = if self.telemetry_enabled {
230+
telemetry.items_evicted_total().clone()
231+
} else {
232+
Counter::noop()
233+
};
234+
let mut expiration_builder = ExpirationBuilder::new(eviction_counter);
228235
if let Some(time_to_idle) = self.idle_period {
229236
expiration_builder = expiration_builder.with_time_to_idle(time_to_idle);
230237
}

0 commit comments

Comments
 (0)