Skip to main content

vector/sinks/util/
builder.rs

1use std::{
2    convert::Infallible,
3    fmt,
4    future::Future,
5    hash::Hash,
6    num::NonZeroUsize,
7    pin::Pin,
8    sync::Arc,
9    task::{Context, Poll},
10    time::Duration,
11};
12
13use futures_util::{Stream, StreamExt, stream::Map};
14use pin_project::pin_project;
15use tower::Service;
16use tracing::Span;
17use vector_lib::{
18    ByteSizeOf,
19    event::{Finalizable, Metric},
20    partition::Partitioner,
21    stream::{
22        ConcurrentMap, Driver, DriverResponse, ExpirationQueue, PartitionedBatcher,
23        batcher::{Batcher, config::BatchConfig},
24    },
25};
26
27use super::{
28    IncrementalRequestBuilder, Normalizer, RequestBuilder, buffer::metrics::MetricNormalize,
29};
30
31fn metric_ttl(maybe_ttl_secs: Option<f64>) -> Option<Duration> {
32    maybe_ttl_secs
33        .and_then(|ttl| Duration::try_from_secs_f64(ttl).ok())
34        .filter(|ttl| !ttl.is_zero())
35}
36
37impl<T: ?Sized> SinkBuilderExt for T where T: Stream {}
38
39pub trait SinkBuilderExt: Stream {
40    /// Converts a stream of infallible results by unwrapping them.
41    ///
42    /// For a stream of `Result<T, Infallible>` items, this turns it into a stream of `T` items.
43    fn unwrap_infallible<T>(self) -> UnwrapInfallible<Self>
44    where
45        Self: Stream<Item = Result<T, Infallible>> + Sized,
46    {
47        UnwrapInfallible { st: self }
48    }
49
50    /// Batches the stream based on the given partitioner and batch settings.
51    ///
52    /// The stream will yield batches of events, with their partition key, when either a batch fills
53    /// up or times out. [`Partitioner`] operates on a per-event basis, and has access to the event
54    /// itself, and so can access any and all fields of an event.
55    ///
56    /// The `settings` closure receives the partition key for each new partition, allowing callers
57    /// to vary the batch configuration (e.g. byte size limit) per partition.
58    fn batched_partitioned<P, C, F, B>(
59        self,
60        partitioner: P,
61        timeout: Duration,
62        settings: F,
63    ) -> PartitionedBatcher<Self, P, ExpirationQueue<P::Key>, C, F, B>
64    where
65        Self: Stream<Item = P::Item> + Sized,
66        P: Partitioner + Unpin,
67        P::Key: Eq + Hash + Clone,
68        P::Item: ByteSizeOf,
69        C: BatchConfig<P::Item>,
70        F: Fn(&P::Key) -> C + Send,
71    {
72        PartitionedBatcher::new(self, partitioner, timeout, settings)
73    }
74
75    /// Batches the stream based on the given batch settings and item size calculator.
76    ///
77    /// The stream will yield batches of events, when either a batch fills
78    /// up or times out. The `item_size_calculator` determines the "size" of each input
79    /// in a batch. The units of "size" are intentionally not defined, so you can choose
80    /// whatever is needed.
81    fn batched<C>(self, config: C) -> Batcher<Self, C>
82    where
83        C: BatchConfig<Self::Item>,
84        Self: Sized,
85    {
86        Batcher::new(self, config)
87    }
88
89    /// Maps the items in the stream concurrently, up to the configured limit.
90    ///
91    /// For every item, the given mapper is invoked, and the future that is returned is spawned
92    /// and awaited concurrently.  A limit can be passed: `None` is self-describing, as it imposes
93    /// no concurrency limit, and `Some(n)` limits this stage to `n` concurrent operations at any
94    /// given time.
95    ///
96    /// If the spawned future panics, the panic will be carried through and resumed on the task
97    /// calling the stream.
98    fn concurrent_map<F, T>(self, limit: NonZeroUsize, f: F) -> ConcurrentMap<Self, T>
99    where
100        Self: Sized,
101        F: Fn(Self::Item) -> Pin<Box<dyn Future<Output = T> + Send + 'static>> + Send + 'static,
102        T: Send + 'static,
103    {
104        ConcurrentMap::new(self, Some(limit), f)
105    }
106
107    /// Constructs a [`Stream`] which transforms the input into a request suitable for sending to
108    /// downstream services.
109    ///
110    /// Each input is transformed concurrently, up to the given limit.  A limit of `n` limits
111    /// this stage to `n` concurrent operations at any given time.
112    ///
113    /// Encoding and compression are handled internally, deferring to the builder at the necessary
114    /// checkpoints for adjusting the event before encoding/compression, as well as generating the
115    /// correct request object with the result of encoding/compressing the events.
116    fn request_builder<B>(
117        self,
118        limit: NonZeroUsize,
119        builder: B,
120    ) -> ConcurrentMap<Self, Result<B::Request, B::Error>>
121    where
122        Self: Sized,
123        Self::Item: Send + 'static,
124        B: RequestBuilder<<Self as Stream>::Item> + Send + Sync + 'static,
125        B::Error: Send,
126        B::Request: Send,
127    {
128        let builder = Arc::new(builder);
129
130        // The future passed into the concurrent map is spawned in a tokio thread so we must preserve
131        // the span context in order to propagate the sink's automatic tags.
132        let span = Arc::new(Span::current());
133
134        self.concurrent_map(limit, move |input| {
135            let builder = Arc::clone(&builder);
136            let span = Arc::clone(&span);
137
138            Box::pin(async move {
139                let _entered = span.enter();
140
141                // Split the input into metadata and events.
142                let (metadata, request_metadata_builder, events) = builder.split_input(input);
143
144                // Encode the events.
145                let payload = builder.encode_events(events)?;
146
147                // Note: it would be nice for the RequestMetadataBuilder to build be created from the
148                // events here, and not need to be required by split_input(). But this then requires
149                // each Event type to implement Serialize, and that causes conflicts with the Serialize
150                // implementation for EstimatedJsonEncodedSizeOf.
151
152                // Build the request metadata.
153                let request_metadata = request_metadata_builder.build(&payload);
154
155                // Now build the actual request.
156                Ok(builder.build_request(metadata, request_metadata, payload))
157            })
158        })
159    }
160
161    /// Constructs a [`Stream`] which transforms the input into a number of requests suitable for
162    /// sending to downstream services.
163    ///
164    /// Unlike `request_builder`, which depends on the `RequestBuilder` trait,
165    /// `incremental_request_builder` depends on the `IncrementalRequestBuilder` trait, which is
166    /// designed specifically for sinks that have more stringent requirements around the generated
167    /// requests.
168    ///
169    /// As an example, the normal `request_builder` doesn't allow for a batch of input events to be
170    /// split up: all events must be split at the beginning, encoded separately (and all together),
171    /// and then reassembled into the request.  If the encoding of these events caused a payload to
172    /// be generated that was, say, too large, you would have to back out the operation entirely by
173    /// failing the batch.
174    ///
175    /// With `incremental_request_builder`, the builder is given all of the events in a single shot,
176    /// and can generate multiple payloads.  This is the maximally flexible approach to encoding,
177    /// but means that the trait doesn't provide any default methods like `RequestBuilder` does.
178    ///
179    /// Each input is transformed serially.
180    ///
181    /// Encoding and compression are handled internally, deferring to the builder at the necessary
182    /// checkpoints for adjusting the event before encoding/compression, as well as generating the
183    /// correct request object with the result of encoding/compressing the events.
184    fn incremental_request_builder<B>(
185        self,
186        mut builder: B,
187    ) -> Map<Self, Box<dyn FnMut(Self::Item) -> Vec<Result<B::Request, B::Error>> + Send + Sync>>
188    where
189        Self: Sized,
190        Self::Item: Send + 'static,
191        B: IncrementalRequestBuilder<<Self as Stream>::Item> + Send + Sync + 'static,
192        B::Error: Send,
193        B::Request: Send,
194    {
195        self.map(Box::new(move |input| {
196            builder
197                .encode_events_incremental(input)
198                .into_iter()
199                .map(|result| {
200                    result.map(|(metadata, payload)| builder.build_request(metadata, payload))
201                })
202                .collect()
203        }))
204    }
205
206    /// Normalizes a stream of [`Metric`] events with the provided normalizer.
207    ///
208    /// An implementation of [`MetricNormalize`] is used to either drop metrics which cannot be
209    /// supported by the sink, or to modify them.  Such modifications typically include converting
210    /// absolute metrics to incremental metrics by tracking the change over time for a particular
211    /// series, or emitting absolute metrics based on incremental updates.
212    fn normalized<N>(self, normalizer: N) -> Normalizer<Self, N>
213    where
214        Self: Stream<Item = Metric> + Unpin + Sized,
215        N: MetricNormalize,
216    {
217        Normalizer::new(self, normalizer)
218    }
219
220    /// Normalizes a stream of [`Metric`] events with a default normalizer.
221    ///
222    /// An implementation of [`MetricNormalize`] is used to either drop metrics which cannot be
223    /// supported by the sink, or to modify them.  Such modifications typically include converting
224    /// absolute metrics to incremental metrics by tracking the change over time for a particular
225    /// series, or emitting absolute metrics based on incremental updates.
226    fn normalized_with_default<N>(self) -> Normalizer<Self, N>
227    where
228        Self: Stream<Item = Metric> + Unpin + Sized,
229        N: MetricNormalize + Default,
230    {
231        Normalizer::new(self, N::default())
232    }
233
234    /// Normalizes a stream of [`Metric`] events with a normalizer and an optional TTL.
235    fn normalized_with_ttl<N>(self, maybe_ttl_secs: Option<f64>) -> Normalizer<Self, N>
236    where
237        Self: Stream<Item = Metric> + Unpin + Sized,
238        N: MetricNormalize + Default,
239    {
240        match metric_ttl(maybe_ttl_secs) {
241            None => Normalizer::new(self, N::default()),
242            Some(ttl) => Normalizer::new_with_ttl(self, N::default(), ttl),
243        }
244    }
245
246    /// Creates a [`Driver`] that uses the configured event stream as the input to the given
247    /// service.
248    ///
249    /// This is typically a terminal step in building a sink, bridging the gap from the processing
250    /// that must be performed by Vector (in the stream) to the underlying sink itself (the
251    /// service).
252    fn into_driver<Svc>(self, service: Svc) -> Driver<Self, Svc>
253    where
254        Self: Sized,
255        Self::Item: Finalizable,
256        Svc: Service<Self::Item>,
257        Svc::Error: fmt::Debug + 'static,
258        Svc::Future: Send + 'static,
259        Svc::Response: DriverResponse,
260    {
261        Driver::new(self, service)
262    }
263}
264
265#[pin_project]
266pub struct UnwrapInfallible<St> {
267    #[pin]
268    st: St,
269}
270
271impl<St, T> Stream for UnwrapInfallible<St>
272where
273    St: Stream<Item = Result<T, Infallible>>,
274{
275    type Item = T;
276
277    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
278        let this = self.project();
279        this.st
280            .poll_next(cx)
281            .map(|maybe| maybe.map(|result| result.unwrap()))
282    }
283}
284
285#[cfg(test)]
286mod tests {
287    use super::*;
288
289    #[test]
290    fn metric_ttl_preserves_fractional_seconds() {
291        assert_eq!(metric_ttl(Some(0.5)), Some(Duration::from_millis(500)));
292    }
293
294    #[test]
295    fn metric_ttl_ignores_invalid_or_zero_durations() {
296        assert_eq!(metric_ttl(None), None);
297        assert_eq!(metric_ttl(Some(0.0)), None);
298        assert_eq!(metric_ttl(Some(-1.0)), None);
299        assert_eq!(metric_ttl(Some(f64::NAN)), None);
300        assert_eq!(metric_ttl(Some(f64::INFINITY)), None);
301    }
302}