Skip to content

Commit 868708a

Browse files
authored
experiment(dsd): dedicated non-caching context resolver for no-agg DSD metrics (#595)
1 parent 9d83caa commit 868708a

File tree

4 files changed

+176
-39
lines changed

4 files changed

+176
-39
lines changed

lib/saluki-components/src/sources/dogstatsd/mod.rs

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
use std::sync::{Arc, LazyLock};
2-
use std::{num::NonZeroUsize, time::Duration};
2+
use std::time::Duration;
33

44
use async_trait::async_trait;
55
use bytes::{Buf, BufMut};
66
use bytesize::ByteSize;
77
use memory_accounting::{MemoryBounds, MemoryBoundsBuilder, UsageExpr};
88
use metrics::{Counter, Gauge, Histogram};
99
use saluki_config::GenericConfiguration;
10-
use saluki_context::{ContextResolver, ContextResolverBuilder};
1110
use saluki_core::{
1211
components::{sources::*, ComponentContext},
1312
observability::ComponentMetricsExt as _,
@@ -20,7 +19,7 @@ use saluki_core::{
2019
},
2120
};
2221
use saluki_env::WorkloadProvider;
23-
use saluki_error::{generic_error, GenericError};
22+
use saluki_error::{ErrorContext as _, GenericError};
2423
use saluki_event::metric::{MetricMetadata, MetricOrigin};
2524
use saluki_event::{metric::Metric, DataType, Event};
2625
use saluki_io::{
@@ -51,9 +50,13 @@ use self::framer::{get_framer, DsdFramer};
5150

5251
mod filters;
5352
use self::filters::{is_event_allowed, is_metric_allowed, is_service_check_allowed, EnablePayloadsFilter, Filter};
53+
5454
mod origin;
5555
use self::origin::{origin_from_metric_packet, DogStatsDOriginTagResolver, OriginEnrichmentConfiguration};
5656

57+
mod resolver;
58+
use self::resolver::ContextResolvers;
59+
5760
#[derive(Debug, Snafu)]
5861
#[snafu(context(suffix(false)))]
5962
enum Error {
@@ -322,16 +325,8 @@ impl SourceBuilder for DogStatsDConfiguration {
322325
.workload_provider
323326
.clone()
324327
.map(|provider| DogStatsDOriginTagResolver::new(self.origin_enrichment.clone(), provider));
325-
let context_string_interner_size = NonZeroUsize::new(self.context_string_interner_bytes.as_u64() as usize)
326-
.ok_or_else(|| generic_error!("context_string_interner_size must be greater than 0"))?;
327-
let context_resolver = ContextResolverBuilder::from_name("dogstatsd")
328-
.expect("resolver name is not empty")
329-
.with_interner_capacity_bytes(context_string_interner_size)
330-
.with_idle_context_expiration(Duration::from_secs(30))
331-
.with_expiration_interval(Duration::from_secs(1))
332-
.with_heap_allocations(self.allow_context_heap_allocations)
333-
.with_origin_tags_resolver(maybe_origin_tags_resolver)
334-
.build();
328+
let context_resolvers = ContextResolvers::new(self, maybe_origin_tags_resolver)
329+
.error_context("Failed to create context resolvers.")?;
335330

336331
let codec_config = DogstatsdCodecConfiguration::default()
337332
.with_timestamps(self.no_aggregation_pipeline_support)
@@ -351,7 +346,7 @@ impl SourceBuilder for DogStatsDConfiguration {
351346
FixedSizeVec::with_capacity(get_adjusted_buffer_size(self.buffer_size))
352347
}),
353348
codec,
354-
context_resolver,
349+
context_resolvers,
355350
pre_filters: Arc::new(vec![Box::new(enable_payloads_filter)]),
356351
}))
357352
}
@@ -394,7 +389,7 @@ pub struct DogStatsD {
394389
listeners: Vec<Listener>,
395390
io_buffer_pool: FixedSizeObjectPool<BytesBuffer>,
396391
codec: DogstatsdCodec,
397-
context_resolver: ContextResolver,
392+
context_resolvers: ContextResolvers,
398393
pre_filters: Arc<Vec<Box<dyn Filter + Send + Sync>>>,
399394
}
400395

@@ -403,7 +398,7 @@ struct ListenerContext {
403398
listener: Listener,
404399
io_buffer_pool: FixedSizeObjectPool<BytesBuffer>,
405400
codec: DogstatsdCodec,
406-
context_resolver: ContextResolver,
401+
context_resolvers: ContextResolvers,
407402
}
408403

409404
struct HandlerContext {
@@ -412,7 +407,7 @@ struct HandlerContext {
412407
codec: DogstatsdCodec,
413408
io_buffer_pool: FixedSizeObjectPool<BytesBuffer>,
414409
metrics: Metrics,
415-
context_resolver: ContextResolver,
410+
context_resolvers: ContextResolvers,
416411
}
417412

418413
struct Metrics {
@@ -575,7 +570,7 @@ impl Source for DogStatsD {
575570
listener,
576571
io_buffer_pool: self.io_buffer_pool.clone(),
577572
codec: self.codec.clone(),
578-
context_resolver: self.context_resolver.clone(),
573+
context_resolvers: self.context_resolvers.clone(),
579574
};
580575

581576
spawn_traced(process_listener(
@@ -620,7 +615,7 @@ async fn process_listener(
620615
mut listener,
621616
io_buffer_pool,
622617
codec,
623-
context_resolver,
618+
context_resolvers,
624619
} = listener_context;
625620
tokio::pin!(shutdown_handle);
626621

@@ -645,7 +640,7 @@ async fn process_listener(
645640
codec: codec.clone(),
646641
io_buffer_pool: io_buffer_pool.clone(),
647642
metrics: build_metrics(&listen_addr, source_context.component_context()),
648-
context_resolver: context_resolver.clone(),
643+
context_resolvers: context_resolvers.clone(),
649644
};
650645
spawn_traced(process_stream(stream, source_context.clone(), handler_context, stream_shutdown_coordinator.register(), filters.clone()));
651646
}
@@ -686,7 +681,7 @@ async fn drive_stream(
686681
codec,
687682
io_buffer_pool,
688683
metrics,
689-
mut context_resolver,
684+
mut context_resolvers,
690685
} = handler_context;
691686

692687
debug!(%listen_addr, "Stream handler started.");
@@ -767,7 +762,7 @@ async fn drive_stream(
767762
match frames.next() {
768763
Some(Ok(frame)) => {
769764
trace!(%listen_addr, %peer_addr, ?frame, "Decoded frame.");
770-
match handle_frame(&frame[..], &codec, &mut context_resolver, &metrics, &peer_addr, filters.clone()) {
765+
match handle_frame(&frame[..], &codec, &mut context_resolvers, &metrics, &peer_addr, filters.clone()) {
771766
Ok(Some(event)) => {
772767
if let Some((event, event_buffer)) = event_buffer_manager.try_push(event).await {
773768
debug!(%listen_addr, %peer_addr, "Event buffer is full. Forwarding events.");
@@ -847,7 +842,7 @@ async fn drive_stream(
847842
}
848843

849844
fn handle_frame(
850-
frame: &[u8], codec: &DogstatsdCodec, context_resolver: &mut ContextResolver, source_metrics: &Metrics,
845+
frame: &[u8], codec: &DogstatsdCodec, context_resolvers: &mut ContextResolvers, source_metrics: &Metrics,
851846
peer_addr: &ConnectionAddress, filters: Arc<Vec<Box<dyn Filter + Send + Sync>>>,
852847
) -> Result<Option<Event>, ParseError> {
853848
let parsed = match codec.decode_packet(frame) {
@@ -875,7 +870,7 @@ fn handle_frame(
875870
return Ok(None);
876871
}
877872

878-
match handle_metric_packet(metric_packet, context_resolver, peer_addr) {
873+
match handle_metric_packet(metric_packet, context_resolvers, peer_addr) {
879874
Some(metric) => {
880875
source_metrics.metrics_received().increment(events_len);
881876
Event::Metric(metric)
@@ -915,14 +910,21 @@ fn handle_frame(
915910
}
916911

917912
fn handle_metric_packet(
918-
packet: MetricPacket, context_resolver: &mut ContextResolver, peer_addr: &ConnectionAddress,
913+
packet: MetricPacket, context_resolvers: &mut ContextResolvers, peer_addr: &ConnectionAddress,
919914
) -> Option<Metric> {
920915
// Capture the origin from the packet, including any process ID information if we have it.
921916
let mut origin = origin_from_metric_packet(&packet);
922917
if let ConnectionAddress::ProcessLike(Some(creds)) = &peer_addr {
923918
origin.set_process_id(creds.pid as u32);
924919
}
925920

921+
// Choose the right context resolver based on whether or not this metric is pre-aggregated.
922+
let context_resolver = if packet.timestamp.is_some() {
923+
context_resolvers.no_agg()
924+
} else {
925+
context_resolvers.primary()
926+
};
927+
926928
// Try to resolve the context for this metric.
927929
match context_resolver.resolve(packet.metric_name, packet.tags.clone(), Some(origin)) {
928930
Some(context) => {
@@ -1012,7 +1014,7 @@ mod tests {
10121014
net::ConnectionAddress,
10131015
};
10141016

1015-
use super::handle_metric_packet;
1017+
use super::{handle_metric_packet, ContextResolvers};
10161018

10171019
#[test]
10181020
fn no_metrics_when_interner_full_allocations_disallowed() {
@@ -1024,7 +1026,8 @@ mod tests {
10241026
// We set our metric name to be longer than 31 bytes (the inlining limit) to ensure this.
10251027

10261028
let codec = DogstatsdCodec::from_configuration(DogstatsdCodecConfiguration::default());
1027-
let mut context_resolver = ContextResolverBuilder::for_tests().with_heap_allocations(false).build();
1029+
let context_resolver = ContextResolverBuilder::for_tests().with_heap_allocations(false).build();
1030+
let mut context_resolvers = ContextResolvers::manual(context_resolver.clone(), context_resolver);
10281031
let peer_addr = ConnectionAddress::from("1.1.1.1:1234".parse::<SocketAddr>().unwrap());
10291032

10301033
let input = "big_metric_name_that_cant_possibly_be_inlined:1|c|#tag1:value1,tag2:value2,tag3:value3";
@@ -1033,7 +1036,7 @@ mod tests {
10331036
panic!("Failed to parse packet.");
10341037
};
10351038

1036-
let maybe_metric = handle_metric_packet(packet, &mut context_resolver, &peer_addr);
1039+
let maybe_metric = handle_metric_packet(packet, &mut context_resolvers, &peer_addr);
10371040
assert!(maybe_metric.is_none());
10381041
}
10391042
}

lib/saluki-components/src/sources/dogstatsd/origin.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ impl Default for OriginEnrichmentConfiguration {
7474
}
7575
}
7676

77+
#[derive(Clone)]
7778
pub(super) struct DogStatsDOriginTagResolver {
7879
config: OriginEnrichmentConfiguration,
7980
workload_provider: Arc<dyn WorkloadProvider + Send + Sync>,
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use std::{num::NonZeroUsize, time::Duration};
2+
3+
use saluki_context::{ContextResolver, ContextResolverBuilder};
4+
use saluki_error::{generic_error, GenericError};
5+
6+
use super::{DogStatsDConfiguration, DogStatsDOriginTagResolver};
7+
8+
/// Context resolvers for the DogStatsD source.
9+
#[derive(Clone)]
10+
pub struct ContextResolvers {
11+
primary: ContextResolver,
12+
no_agg: ContextResolver,
13+
}
14+
15+
impl ContextResolvers {
16+
/// Creates a new `ContextResolvers` instance from the given configuration.
17+
///
18+
/// # Errors
19+
///
20+
/// If the context resolver string interner size is invalid, or there is an error creating either of the context
21+
/// resolvers, an error is returned.
22+
pub fn new(
23+
config: &DogStatsDConfiguration, maybe_origin_tags_resolver: Option<DogStatsDOriginTagResolver>,
24+
) -> Result<Self, GenericError> {
25+
// We'll use the same string interner size for both context resolvers, which does mean double the usage, but
26+
// it's simpler this way for the moment.
27+
let context_string_interner_size = NonZeroUsize::new(config.context_string_interner_bytes.as_u64() as usize)
28+
.ok_or_else(|| generic_error!("context_string_interner_size must be greater than 0"))?;
29+
30+
let primary_resolver = ContextResolverBuilder::from_name("dogstatsd_primary")?
31+
.with_interner_capacity_bytes(context_string_interner_size)
32+
.with_idle_context_expiration(Duration::from_secs(30))
33+
.with_expiration_interval(Duration::from_secs(1))
34+
.with_heap_allocations(config.allow_context_heap_allocations)
35+
.with_origin_tags_resolver(maybe_origin_tags_resolver.clone())
36+
.build();
37+
38+
let no_agg_resolver = ContextResolverBuilder::from_name("dogstatsd_no_agg")?
39+
.with_interner_capacity_bytes(context_string_interner_size)
40+
.without_caching()
41+
.with_heap_allocations(config.allow_context_heap_allocations)
42+
.with_origin_tags_resolver(maybe_origin_tags_resolver)
43+
.build();
44+
45+
Ok(ContextResolvers {
46+
primary: primary_resolver,
47+
no_agg: no_agg_resolver,
48+
})
49+
}
50+
51+
#[cfg(test)]
52+
pub fn manual(primary: ContextResolver, no_agg: ContextResolver) -> Self {
53+
ContextResolvers { primary, no_agg }
54+
}
55+
56+
/// Returns a mutable reference to the primary context resolver.
57+
///
58+
/// This context resolver should be used for "regular" metrics that require aggregation.
59+
pub fn primary(&mut self) -> &mut ContextResolver {
60+
&mut self.primary
61+
}
62+
63+
/// Returns a mutable reference to the no-aggregation context resolver.
64+
///
65+
/// This context resolver should be used for metrics that do not require aggregation, which implies the metrics had
66+
/// a timestamp specified in the payload.
67+
pub fn no_agg(&mut self) -> &mut ContextResolver {
68+
&mut self.no_agg
69+
}
70+
}

0 commit comments

Comments
 (0)