saluki_components/encoders/buffered_incremental/
mod.rs1use std::time::{Duration, Instant};
2
3use agent_data_plane_config::defaults::DEFAULT_ENCODER_FLUSH_TIMEOUT;
4use async_trait::async_trait;
5use saluki_common::sync::shutdown::ShutdownCoordinator;
6use saluki_core::accounting::{MemoryBounds, MemoryBoundsBuilder};
7use saluki_core::runtime;
8use saluki_core::{
9 components::{encoders::*, BuildContext},
10 data_model::{event::EventType, payload::PayloadType},
11 observability::ComponentMetricsExt,
12 topology::PayloadsDispatcher,
13};
14use saluki_error::GenericError;
15use saluki_metrics::MetricsBuilder;
16use tokio::{pin, select, time::sleep};
17use tracing::{debug, error};
18
19mod telemetry;
20use self::telemetry::ComponentTelemetry;
21
22pub struct BufferedIncrementalConfiguration<EB> {
27 flush_timeout: Duration,
37
38 encoder_builder: EB,
39}
40
41impl<EB> BufferedIncrementalConfiguration<EB>
42where
43 EB: IncrementalEncoderBuilder,
44{
45 pub fn from_encoder_builder(encoder_builder: EB) -> Self {
47 Self {
48 flush_timeout: DEFAULT_ENCODER_FLUSH_TIMEOUT,
49 encoder_builder,
50 }
51 }
52}
53
54#[async_trait]
55impl<EB> EncoderBuilder for BufferedIncrementalConfiguration<EB>
56where
57 EB: IncrementalEncoderBuilder + Sync,
58 EB::Output: Send + 'static,
59{
60 fn input_event_type(&self) -> EventType {
61 self.encoder_builder.input_event_type()
62 }
63
64 fn output_payload_type(&self) -> PayloadType {
65 self.encoder_builder.output_payload_type()
66 }
67
68 async fn build(&self, context: BuildContext) -> Result<Box<dyn Encoder + Send>, GenericError> {
69 let metrics_builder = MetricsBuilder::from_component_context(context.component_context());
70 let telemetry = ComponentTelemetry::from_builder(&metrics_builder);
71
72 let encoder = self.encoder_builder.build(context).await?;
73
74 let flush_timeout = match self.flush_timeout {
75 Duration::ZERO => Duration::from_millis(10),
78 dur => dur,
79 };
80
81 Ok(Box::new(BufferedIncremental {
82 encoder,
83 telemetry,
84 flush_timeout,
85 }))
86 }
87}
88
89impl<EB> MemoryBounds for BufferedIncrementalConfiguration<EB>
90where
91 EB: IncrementalEncoderBuilder,
92{
93 fn specify_bounds(&self, builder: &mut MemoryBoundsBuilder) {
94 builder
95 .minimum()
96 .with_single_value::<BufferedIncremental<EB::Output>>("component struct");
97
98 self.encoder_builder.specify_bounds(builder);
99 }
100}
101
102pub struct BufferedIncremental<E> {
103 encoder: E,
104 telemetry: ComponentTelemetry,
105 flush_timeout: Duration,
106}
107
108#[async_trait]
109impl<E> Encoder for BufferedIncremental<E>
110where
111 E: IncrementalEncoder + Send + 'static,
112{
113 async fn run(mut self: Box<Self>, context: EncoderContext) -> Result<(), GenericError> {
114 let Self {
115 encoder,
116 telemetry,
117 flush_timeout,
118 } = *self;
119
120 let worker_pool = context.topology_context().global_thread_pool().clone();
130
131 let mut shutdown_coordinator = ShutdownCoordinator::default();
132 let task_shutdown_handle = shutdown_coordinator.register();
133
134 runtime::worker("incremental_encoder", async move {
135 let _task_shutdown_handle = task_shutdown_handle;
151 run_incremental_encoder(context, encoder, telemetry, flush_timeout).await
152 })
153 .on_runtime(worker_pool)
154 .needed()
155 .spawn();
156
157 debug!("Buffered Incremental encoder started.");
158
159 shutdown_coordinator.shutdown_and_wait().await;
160
161 debug!("Buffered Incremental encoder stopped.");
162
163 Ok(())
164 }
165}
166
167async fn run_incremental_encoder<E>(
168 mut context: EncoderContext, mut encoder: E, telemetry: ComponentTelemetry, flush_timeout: Duration,
169) -> Result<(), GenericError>
170where
171 E: IncrementalEncoder,
172{
173 let mut health = context.take_health_handle();
174
175 health.mark_ready();
176
177 let mut pending_flush = false;
178 let mut pending_events = 0;
179 let pending_flush_timeout = sleep(flush_timeout);
180 pin!(pending_flush_timeout);
181
182 loop {
183 select! {
184 _ = health.live() => continue,
185 maybe_event_buffer = context.events().next() => {
186 let event_buffer = match maybe_event_buffer {
188 Some(event_buffer) => event_buffer,
189 None => break,
190 };
191
192 for event in event_buffer {
193 let event_to_retry = match encoder.process_event(event).await? {
198 ProcessResult::Continue => {
199 pending_events += 1;
200 continue;
201 }
202 ProcessResult::FlushRequired(event) => event,
203 };
204
205 flush_encoder(&mut encoder, context.dispatcher(), &telemetry, &mut pending_events).await?;
207
208 match encoder.process_event(event_to_retry).await? {
213 ProcessResult::Continue => pending_events += 1,
214 ProcessResult::FlushRequired(_) => {
215 error!("Failed to process event after flushing.");
216 telemetry.events_dropped_encoder().increment(1);
217 },
218 }
219 }
220
221 debug!("Processed event buffer.");
222
223 if !pending_flush {
225 pending_flush_timeout.as_mut().reset(tokio::time::Instant::now() + flush_timeout);
226 pending_flush = true;
227 }
228 },
229 _ = &mut pending_flush_timeout, if pending_flush => {
230 debug!("Flushing encoder of any pending payload(s).");
231
232 pending_flush = false;
233
234 flush_encoder(&mut encoder, context.dispatcher(), &telemetry, &mut pending_events).await?;
235
236 debug!("All pending payloads flushed.");
237 }
238 }
239 }
240
241 flush_encoder(&mut encoder, context.dispatcher(), &telemetry, &mut pending_events).await?;
243
244 Ok(())
245}
246
247async fn flush_encoder<E>(
252 encoder: &mut E, dispatcher: &PayloadsDispatcher, telemetry: &ComponentTelemetry, pending_events: &mut u64,
253) -> Result<(), GenericError>
254where
255 E: IncrementalEncoder,
256{
257 let flush_start = Instant::now();
258 encoder.flush(dispatcher).await?;
259
260 if *pending_events > 0 {
261 telemetry.record_flush(*pending_events, flush_start.elapsed());
262 *pending_events = 0;
263 }
264
265 Ok(())
266}