Skip to main content

command_stream/
stream_processing.rs

1//! Bounded, composable asynchronous data streams and online analytics.
2use crate::{OutputChunk, OutputStream};
3use std::collections::VecDeque;
4use std::future::Future;
5use std::sync::Arc;
6use std::time::Instant;
7use tokio::sync::mpsc;
8use tokio::task::JoinHandle;
9
10struct Task(JoinHandle<()>);
11impl Drop for Task {
12    fn drop(&mut self) {
13        self.0.abort();
14    }
15}
16
17/// Each stage has at most 32 queued values; dropping a consumer stops its producer.
18pub struct DataStream<T> {
19    receiver: mpsc::Receiver<T>,
20    task: Arc<Task>,
21}
22
23#[derive(Debug, Clone, Default)]
24pub struct StreamStats {
25    pub count: u64,
26    pub errors: u64,
27    pub error_rate: f64,
28    pub throughput: f64,
29    pub elapsed: f64,
30    pub response_count: u64,
31    pub response_mean: f64,
32    pub response_min: Option<f64>,
33    pub response_max: Option<f64>,
34}
35
36impl<T: Send + 'static> DataStream<T> {
37    fn spawn<F, Fut>(producer: F) -> Self
38    where
39        F: FnOnce(mpsc::Sender<T>) -> Fut,
40        Fut: Future<Output = ()> + Send + 'static,
41    {
42        let (tx, receiver) = mpsc::channel(32);
43        let task = Arc::new(Task(tokio::spawn(producer(tx))));
44        Self { receiver, task }
45    }
46    pub fn from_values<I>(values: I) -> Self
47    where
48        I: IntoIterator<Item = T> + Send + 'static,
49        I::IntoIter: Send,
50    {
51        Self::spawn(|tx| async move {
52            for value in values {
53                if tx.send(value).await.is_err() {
54                    break;
55                }
56            }
57        })
58    }
59    pub async fn next(&mut self) -> Option<T> {
60        self.receiver.recv().await
61    }
62    pub fn map<U, F>(self, mut transform: F) -> DataStream<U>
63    where
64        U: Send + 'static,
65        F: FnMut(T) -> U + Send + 'static,
66    {
67        self.map_async(move |value| std::future::ready(transform(value)))
68    }
69    pub fn map_async<U, F, Fut>(mut self, mut transform: F) -> DataStream<U>
70    where
71        U: Send + 'static,
72        F: FnMut(T) -> Fut + Send + 'static,
73        Fut: Future<Output = U> + Send,
74    {
75        DataStream::spawn(|tx| async move {
76            while let Some(value) = self.next().await {
77                if tx.send(transform(value).await).await.is_err() {
78                    break;
79                }
80            }
81        })
82    }
83    pub fn filter<F>(mut self, mut predicate: F) -> Self
84    where
85        F: FnMut(&T) -> bool + Send + 'static,
86    {
87        Self::spawn(|tx| async move {
88            while let Some(value) = self.next().await {
89                if predicate(&value) && tx.send(value).await.is_err() {
90                    break;
91                }
92            }
93        })
94    }
95    pub fn batch(mut self, size: usize) -> DataStream<Vec<T>> {
96        assert!(size > 0);
97        DataStream::spawn(|tx| async move {
98            let mut values = Vec::with_capacity(size);
99            while let Some(value) = self.next().await {
100                values.push(value);
101                if values.len() == size
102                    && tx
103                        .send(std::mem::replace(&mut values, Vec::with_capacity(size)))
104                        .await
105                        .is_err()
106                {
107                    return;
108                }
109            }
110            if !values.is_empty() {
111                let _ = tx.send(values).await;
112            }
113        })
114    }
115    pub fn window(mut self, size: usize) -> DataStream<Vec<T>>
116    where
117        T: Clone,
118    {
119        assert!(size > 0);
120        DataStream::spawn(|tx| async move {
121            let mut values = VecDeque::with_capacity(size);
122            while let Some(value) = self.next().await {
123                values.push_back(value);
124                if values.len() > size {
125                    values.pop_front();
126                }
127                if values.len() == size && tx.send(values.iter().cloned().collect()).await.is_err()
128                {
129                    break;
130                }
131            }
132        })
133    }
134    pub fn split<F>(mut self, mut predicate: F) -> (Self, Self)
135    where
136        F: FnMut(&T) -> bool + Send + 'static,
137    {
138        let (yes_tx, yes) = mpsc::channel(32);
139        let (no_tx, no) = mpsc::channel(32);
140        let task = Arc::new(Task(tokio::spawn(async move {
141            while let Some(value) = self.next().await {
142                if yes_tx.is_closed() && no_tx.is_closed() {
143                    break;
144                }
145                let tx = if predicate(&value) { &yes_tx } else { &no_tx };
146                let _ = tx.send(value).await;
147            }
148        })));
149        (
150            Self {
151                receiver: yes,
152                task: task.clone(),
153            },
154            Self { receiver: no, task },
155        )
156    }
157    pub fn merge(mut self, mut other: Self) -> Self {
158        Self::spawn(|tx| async move {
159            let mut left = true;
160            let mut right = true;
161            while left || right {
162                let value = tokio::select! { value = self.next(), if left => { if value.is_none() { left = false; } value },
163                value = other.next(), if right => { if value.is_none() { right = false; } value } };
164                if let Some(value) = value {
165                    if tx.send(value).await.is_err() {
166                        break;
167                    }
168                }
169            }
170        })
171    }
172    pub async fn collect(mut self) -> Vec<T> {
173        let mut values = Vec::new();
174        while let Some(value) = self.next().await {
175            values.push(value);
176        }
177        values
178    }
179    pub async fn reduce<U, F>(mut self, mut initial: U, mut reducer: F) -> U
180    where
181        F: FnMut(U, T) -> U,
182    {
183        while let Some(value) = self.next().await {
184            initial = reducer(initial, value);
185        }
186        initial
187    }
188    pub async fn for_each<F, Fut>(mut self, mut callback: F)
189    where
190        F: FnMut(T) -> Fut,
191        Fut: Future<Output = ()>,
192    {
193        while let Some(value) = self.next().await {
194            callback(value).await;
195        }
196    }
197    pub async fn analyze<E, R, U>(
198        mut self,
199        mut error: E,
200        mut response: R,
201        mut update: U,
202    ) -> StreamStats
203    where
204        E: FnMut(&T) -> bool,
205        R: FnMut(&T) -> Option<f64>,
206        U: FnMut(&StreamStats),
207    {
208        let started = Instant::now();
209        let mut stats = StreamStats::default();
210        let mut sum = 0.0;
211        while let Some(value) = self.next().await {
212            stats.count += 1;
213            stats.errors += u64::from(error(&value));
214            if let Some(time) = response(&value).filter(|time| time.is_finite()) {
215                stats.response_count += 1;
216                sum += time;
217                stats.response_min = Some(stats.response_min.map_or(time, |min| min.min(time)));
218                stats.response_max = Some(stats.response_max.map_or(time, |max| max.max(time)));
219                stats.response_mean = sum / stats.response_count as f64;
220            }
221            stats.elapsed = started.elapsed().as_secs_f64();
222            stats.error_rate = stats.errors as f64 / stats.count as f64;
223            stats.throughput = stats.count as f64 / stats.elapsed.max(0.001);
224            update(&stats);
225        }
226        stats
227    }
228}
229
230impl<T> Drop for DataStream<T> {
231    fn drop(&mut self) {
232        self.receiver.close();
233        let _ = &self.task;
234    }
235}
236
237impl OutputStream {
238    /// Frame stdout bytes into UTF-8 lines, retaining split codepoints until newline.
239    pub fn lines(mut self) -> DataStream<String> {
240        DataStream::spawn(|tx| async move {
241            let mut pending = Vec::new();
242            while let Some(chunk) = self.next().await {
243                if let OutputChunk::Stdout(bytes) = chunk {
244                    pending.extend(bytes);
245                    while let Some(end) = pending.iter().position(|byte| *byte == b'\n') {
246                        let mut line = pending.drain(..=end).collect::<Vec<_>>();
247                        line.pop();
248                        if line.last() == Some(&b'\r') {
249                            line.pop();
250                        }
251                        if tx
252                            .send(String::from_utf8_lossy(&line).into_owned())
253                            .await
254                            .is_err()
255                        {
256                            return;
257                        }
258                    }
259                }
260            }
261            if !pending.is_empty() {
262                let _ = tx
263                    .send(String::from_utf8_lossy(&pending).into_owned())
264                    .await;
265            }
266        })
267    }
268}