saluki_core/topology/interconnect/
consumer.rs1use saluki_metrics::{static_metrics, Counter, Histogram};
2use tokio::sync::mpsc;
3
4use super::Dispatchable;
5use crate::components::ComponentContext;
6
7#[static_metrics(prefix = component, labels(component_id, component_type))]
8#[derive(Clone)]
9struct ConsumerMetrics {
10 events_received_total: Counter,
11 #[metric(level = trace)]
12 events_received_size: Histogram,
13}
14
15impl ConsumerMetrics {
16 fn from_component_context(context: ComponentContext) -> Self {
17 Self::new(context.component_id(), context.component_type().as_str())
18 }
19}
20
21pub struct Consumer<T> {
25 inner: mpsc::Receiver<T>,
26 metrics: ConsumerMetrics,
27}
28
29impl<T> Consumer<T>
30where
31 T: Dispatchable,
32{
33 pub fn new(context: ComponentContext, inner: mpsc::Receiver<T>) -> Self {
35 Self {
36 inner,
37 metrics: ConsumerMetrics::from_component_context(context),
38 }
39 }
40
41 pub async fn next(&mut self) -> Option<T> {
45 match self.inner.recv().await {
46 Some(item) => {
47 self.metrics.events_received_total().increment(item.item_count() as u64);
48 self.metrics.events_received_size().record(item.item_count() as f64);
49 Some(item)
50 }
51 None => None,
52 }
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use metrics::{Key, Label};
59 use metrics_util::{
60 debugging::{DebugValue, DebuggingRecorder},
61 CompositeKey, MetricKind,
62 };
63 use ordered_float::OrderedFloat;
64
65 use super::*;
66
67 #[derive(Clone, Debug, Eq, PartialEq)]
68 struct DispatchableEvent<T> {
69 item_count: usize,
70 data: T,
71 }
72
73 impl<T: Clone> DispatchableEvent<T> {
74 fn new(data: T) -> Self {
75 Self { item_count: 1, data }
76 }
77
78 fn with_item_count(item_count: usize, data: T) -> Self {
79 Self { item_count, data }
80 }
81 }
82
83 impl<T: Clone> Dispatchable for DispatchableEvent<T> {
84 fn item_count(&self) -> usize {
85 self.item_count
86 }
87 }
88
89 fn create_consumer<T: Clone>(
90 channel_size: usize,
91 ) -> (Consumer<DispatchableEvent<T>>, mpsc::Sender<DispatchableEvent<T>>) {
92 let component_context = ComponentContext::test_source("consumer_test");
93
94 let (tx, rx) = mpsc::channel(channel_size);
95 let consumer = Consumer::new(component_context, rx);
96
97 (consumer, tx)
98 }
99
100 fn get_consumer_metric_composite_key(kind: MetricKind, name: &'static str) -> CompositeKey {
101 static LABELS: &[Label] = &[
103 Label::from_static_parts("component_id", "consumer_test"),
104 Label::from_static_parts("component_type", "source"),
105 ];
106 let key = Key::from_static_parts(name, LABELS);
107 CompositeKey::new(kind, key)
108 }
109
110 #[tokio::test]
111 async fn next() {
112 let (mut consumer, tx) = create_consumer(1);
113
114 let input_item = DispatchableEvent::new("hello world");
116 tx.send(input_item.clone()).await.expect("should not fail to send item");
117
118 let output_item = consumer.next().await.expect("should receive item");
119 assert_eq!(output_item, input_item);
120
121 drop(tx);
123
124 assert!(consumer.next().await.is_none());
125 }
126
127 #[tokio::test]
128 async fn metrics() {
129 let events_received_key =
130 get_consumer_metric_composite_key(MetricKind::Counter, ConsumerMetrics::events_received_total_name());
131 let events_received_size_key =
132 get_consumer_metric_composite_key(MetricKind::Histogram, ConsumerMetrics::events_received_size_name());
133
134 let recorder = DebuggingRecorder::new();
135 let snapshotter = recorder.snapshotter();
136 let (mut consumer, tx) = metrics::with_local_recorder(&recorder, || create_consumer(1));
137
138 let single_item = DispatchableEvent::new("single item");
140 tx.send(single_item.clone())
141 .await
142 .expect("should not fail to send item");
143
144 let output_item = consumer.next().await.expect("should receive item");
145 assert_eq!(output_item, single_item);
146
147 let current_metrics = snapshotter.snapshot().into_hashmap();
149 let (_, _, events_received) = current_metrics
150 .get(&events_received_key)
151 .expect("should have events received metric");
152 let (_, _, events_received_size) = current_metrics
153 .get(&events_received_size_key)
154 .expect("should have events received size metric");
155 assert_eq!(events_received, &DebugValue::Counter(1));
156 let expected_sizes = vec![OrderedFloat(1.0)];
157 assert_eq!(events_received_size, &DebugValue::Histogram(expected_sizes));
158
159 let multiple_items = DispatchableEvent::with_item_count(42, "multiple_items");
161 tx.send(multiple_items.clone())
162 .await
163 .expect("should not fail to send item");
164
165 let output_item = consumer.next().await.expect("should receive item");
166 assert_eq!(output_item, multiple_items);
167
168 let current_metrics = snapshotter.snapshot().into_hashmap();
170 let (_, _, events_received) = current_metrics
171 .get(&events_received_key)
172 .expect("should have events received metric");
173 let (_, _, events_received_size) = current_metrics
174 .get(&events_received_size_key)
175 .expect("should have events received size metric");
176 assert_eq!(events_received, &DebugValue::Counter(42));
177
178 let expected_sizes = vec![OrderedFloat(42.0)];
179 assert_eq!(events_received_size, &DebugValue::Histogram(expected_sizes));
180 }
181}