saluki_components/encoders/buffered_incremental/
mod.rs1use std::time::Duration;
2
3use agent_data_plane_config::defaults::DEFAULT_ENCODER_FLUSH_TIMEOUT;
4use async_trait::async_trait;
5use saluki_common::task::HandleExt as _;
6use saluki_core::accounting::{MemoryBounds, MemoryBoundsBuilder};
7use saluki_core::{
8 components::{encoders::*, ComponentContext},
9 data_model::{event::EventType, payload::PayloadType},
10 observability::ComponentMetricsExt,
11};
12use saluki_error::GenericError;
13use saluki_metrics::MetricsBuilder;
14use tokio::{pin, select, time::sleep};
15use tracing::{debug, error};
16
17mod telemetry;
18use self::telemetry::ComponentTelemetry;
19
20pub struct BufferedIncrementalConfiguration<EB> {
25 flush_timeout: Duration,
35
36 encoder_builder: EB,
37}
38
39impl<EB> BufferedIncrementalConfiguration<EB>
40where
41 EB: IncrementalEncoderBuilder,
42{
43 pub fn from_encoder_builder(encoder_builder: EB) -> Self {
45 Self {
46 flush_timeout: DEFAULT_ENCODER_FLUSH_TIMEOUT,
47 encoder_builder,
48 }
49 }
50}
51
52#[async_trait]
53impl<EB> EncoderBuilder for BufferedIncrementalConfiguration<EB>
54where
55 EB: IncrementalEncoderBuilder + Sync,
56 EB::Output: Send + 'static,
57{
58 fn input_event_type(&self) -> EventType {
59 self.encoder_builder.input_event_type()
60 }
61
62 fn output_payload_type(&self) -> PayloadType {
63 self.encoder_builder.output_payload_type()
64 }
65
66 async fn build(&self, context: ComponentContext) -> Result<Box<dyn Encoder + Send>, GenericError> {
67 let metrics_builder = MetricsBuilder::from_component_context(&context);
68 let telemetry = ComponentTelemetry::from_builder(&metrics_builder);
69
70 let encoder = self.encoder_builder.build(context).await?;
71
72 let flush_timeout = match self.flush_timeout {
73 Duration::ZERO => Duration::from_millis(10),
76 dur => dur,
77 };
78
79 Ok(Box::new(BufferedIncremental {
80 encoder,
81 telemetry,
82 flush_timeout,
83 }))
84 }
85}
86
87impl<EB> MemoryBounds for BufferedIncrementalConfiguration<EB>
88where
89 EB: IncrementalEncoderBuilder,
90{
91 fn specify_bounds(&self, builder: &mut MemoryBoundsBuilder) {
92 builder
93 .minimum()
94 .with_single_value::<BufferedIncremental<EB::Output>>("component struct");
95
96 self.encoder_builder.specify_bounds(builder);
97 }
98}
99
100pub struct BufferedIncremental<E> {
101 encoder: E,
102 telemetry: ComponentTelemetry,
103 flush_timeout: Duration,
104}
105
106#[async_trait]
107impl<E> Encoder for BufferedIncremental<E>
108where
109 E: IncrementalEncoder + Send + 'static,
110{
111 async fn run(mut self: Box<Self>, context: EncoderContext) -> Result<(), GenericError> {
112 let Self {
113 encoder,
114 telemetry,
115 flush_timeout,
116 } = *self;
117
118 let thread_pool_handle = context.topology_context().global_thread_pool().clone();
120 let runner_name = format!(
121 "{}-incremental-encoder",
122 context.component_context().component_id().replace("_", "-")
123 );
124 let runner = run_incremental_encoder(context, encoder, telemetry, flush_timeout);
125 let runner_handle = thread_pool_handle.spawn_traced_named(runner_name, runner);
126
127 debug!("Buffered Incremental encoder started.");
128
129 match runner_handle.await {
133 Ok(Ok(())) => debug!("Incremental encoder task stopped."),
134 Ok(Err(e)) => error!(error = %e, "Incremental encoder task failed."),
135 Err(e) => error!(error = %e, "Incremental encoder task panicked."),
136 }
137
138 debug!("Buffered Incremental encoder stopped.");
139
140 Ok(())
141 }
142}
143
144async fn run_incremental_encoder<E>(
145 mut context: EncoderContext, mut encoder: E, telemetry: ComponentTelemetry, flush_timeout: Duration,
146) -> Result<(), GenericError>
147where
148 E: IncrementalEncoder,
149{
150 let mut health = context.take_health_handle();
151
152 health.mark_ready();
153
154 let mut pending_flush = false;
155 let pending_flush_timeout = sleep(flush_timeout);
156 pin!(pending_flush_timeout);
157
158 loop {
159 select! {
160 _ = health.live() => continue,
161 maybe_event_buffer = context.events().next() => {
162 let event_buffer = match maybe_event_buffer {
164 Some(event_buffer) => event_buffer,
165 None => break,
166 };
167
168 for event in event_buffer {
169 let event_to_retry = match encoder.process_event(event).await? {
174 ProcessResult::Continue => continue,
175 ProcessResult::FlushRequired(event) => event,
176 };
177
178 encoder.flush(context.dispatcher()).await?;
180
181 match encoder.process_event(event_to_retry).await? {
186 ProcessResult::Continue => {},
187 ProcessResult::FlushRequired(_) => {
188 error!("Failed to process event after flushing.");
189 telemetry.events_dropped_encoder().increment(1);
190 },
191 }
192 }
193
194 debug!("Processed event buffer.");
195
196 if !pending_flush {
198 pending_flush_timeout.as_mut().reset(tokio::time::Instant::now() + flush_timeout);
199 pending_flush = true;
200 }
201 },
202 _ = &mut pending_flush_timeout, if pending_flush => {
203 debug!("Flushing encoder of any pending payload(s).");
204
205 pending_flush = false;
206
207 encoder.flush(context.dispatcher()).await?;
208
209 debug!("All pending payloads flushed.");
210 }
211 }
212 }
213
214 encoder.flush(context.dispatcher()).await?;
216
217 Ok(())
218}