harmos_runtime/streams/
batches.rs1use std::any::Any;
18use std::collections::VecDeque;
19use std::future::Future;
20use std::marker::PhantomData;
21use std::pin::Pin;
22use std::sync::{Arc, Mutex};
23use std::task::{Context, Poll};
24use harmos_domain::Aggregate;
26use harmos_storage::signal::Summary as Running;
27use tokio::sync::watch;
28use super::demand::Holding;
30use crate::aggregate;
31use crate::streams::BatchDefinition;
32
33type Next = Pin<Box<dyn Future<Output = (Cursor, Option<BatchItem>)> + Send>>;
40
41#[derive(Clone, Debug, PartialEq)]
46pub struct Measurement {
47 pub field: &'static str,
48 pub statistics: Option<Running>,
49 pub invalid: u64,
50 pub overflow: bool,
51}
52
53#[derive(Clone, Debug, PartialEq)]
54pub struct Summary {
55 pub count: u64,
56 pub missed: u64,
57 pub partial: bool,
58 pub fields: Vec<Measurement>,
59}
60
61pub struct Batches<S> {
62 cursor: Option<Cursor>,
63 next: Option<Next>,
64 marker: PhantomData<fn() -> S>,
65 _held: Holding,
66}
67
68struct Cursor {
69 feed: Arc<Feed>,
70 bell: watch::Receiver<()>,
71 next: u64,
72}
73
74pub(crate) struct Feed {
75 ring: Mutex<Retained>,
76 bell: watch::Sender<()>,
77}
78
79struct Retained {
80 rows: VecDeque<Arc<Summary>>,
81 next: u64,
82 closed: bool,
83}
84
85pub(crate) struct State {
86 definition: BatchDefinition,
87 count: u64,
88 missed: u64,
89 fields: Vec<Accumulator>,
90 pub(crate) feed: Arc<Feed>,
91 closed: bool,
92}
93
94#[derive(Clone, Default)]
95struct Accumulator {
96 running: Running,
97 invalid: u64,
98}
99
100#[derive(Clone, Debug, PartialEq)]
103pub enum BatchItem {
104 Summary(Arc<Summary>),
105 Gap { missed: u64 },
106}
107
108impl Summary {
113 pub fn complete(&self) -> bool {
114 !self.partial && self.missed == 0
115 }
116}
117
118impl<S> Batches<S> {
119 pub async fn next(&mut self) -> Option<BatchItem> {
120 std::future::poll_fn(|cx| futures_core::Stream::poll_next(Pin::new(&mut *self), cx)).await
121 }
122}
123
124impl Cursor {
125 async fn advance(mut self) -> (Self, Option<BatchItem>) {
126 loop {
127 let (item, closed) = {
128 let ring = self.feed.ring.lock().expect("summary ring is healthy");
129 let floor = ring.next - ring.rows.len() as u64;
130 let item = if self.next < floor {
131 let missed = floor - self.next;
132 self.next = floor;
133 Some(BatchItem::Gap { missed })
134 } else if self.next < ring.next {
135 let row = Arc::clone(&ring.rows[(self.next - floor) as usize]);
136 self.next += 1;
137 Some(BatchItem::Summary(row))
138 } else {
139 None
140 };
141 (item, ring.closed)
142 };
143 if item.is_some() || closed {
144 return (self, item);
145 }
146 if self.bell.changed().await.is_err() {
147 return (self, None);
148 }
149 }
150 }
151}
152
153impl<S> futures_core::Stream for Batches<S> {
154 type Item = BatchItem;
155 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
156 let this = self.get_mut();
157 if this.next.is_none() {
158 let Some(cursor) = this.cursor.take() else {
159 return Poll::Ready(None);
160 };
161 this.next = Some(Box::pin(cursor.advance()));
162 }
163 match this.next.as_mut().expect("advance installed").as_mut().poll(cx) {
164 Poll::Pending => Poll::Pending,
165 Poll::Ready((cursor, item)) => {
166 this.next = None;
167 if item.is_some() {
168 this.cursor = Some(cursor);
169 }
170 Poll::Ready(item)
171 }
172 }
173 }
174}
175
176impl Feed {
177 pub(crate) fn following<S>(self: &Arc<Self>, held: Holding) -> Batches<S> {
178 let bell = self.bell.subscribe();
179 let next = self.ring.lock().expect("summary ring is healthy").next;
180 Batches { cursor: Some(Cursor { feed: Arc::clone(self), bell, next }), next: None, marker: PhantomData, _held: held }
181 }
182}
183
184impl State {
185 pub(crate) fn new(definition: BatchDefinition) -> Self {
186 let (bell, _) = watch::channel(());
187 Self {
188 definition,
189 count: 0,
190 missed: 0,
191 fields: vec![Accumulator::default(); definition.fields().len()],
192 feed: Arc::new(Feed { ring: Mutex::new(Retained { rows: VecDeque::with_capacity(64), next: 0, closed: false }), bell }),
193 closed: false,
194 }
195 }
196
197 pub(crate) fn publish(&mut self, row: &dyn Any) {
198 if self.closed {
199 return;
200 }
201 let mut fields = self.fields.iter_mut();
202 self.definition.measure(row, &mut |value| fields.next().expect("one accumulator per declared measure").push(value));
203 self.count += 1;
204 if self.count == self.definition.samples() {
205 self.flush(false);
206 }
207 }
208
209 pub(crate) fn miss(&mut self, count: u64) {
210 if !self.closed {
211 self.missed = self.missed.saturating_add(count);
212 }
213 }
214
215 fn flush(&mut self, partial: bool) {
216 let summary = Summary {
217 count: self.count,
218 missed: self.missed,
219 partial,
220 fields: self
221 .fields
222 .iter_mut()
223 .zip(self.definition.fields())
224 .map(|(accumulator, field)| {
225 let result = accumulator.finish(field);
226 *accumulator = Accumulator::default();
227 result
228 })
229 .collect(),
230 };
231 self.count = 0;
232 self.missed = 0;
233 let mut ring = self.feed.ring.lock().expect("summary ring is healthy");
234 if ring.rows.len() == 64 {
235 ring.rows.pop_front();
236 }
237 ring.rows.push_back(Arc::new(summary));
238 ring.next += 1;
239 drop(ring);
240 self.feed.bell.send_replace(());
241 }
242
243 pub(crate) fn close(&mut self) {
244 if self.closed {
245 return;
246 }
247 self.closed = true;
248 if self.count > 0 || self.missed > 0 {
249 self.flush(true);
250 }
251 self.feed.ring.lock().expect("summary ring is healthy").closed = true;
252 self.feed.bell.send_replace(());
253 }
254}
255
256impl Measurement {
257 pub fn aggregate(&self, kind: Aggregate) -> Option<f64> {
260 aggregate::of_summary(self.statistics.as_ref()?, kind)
261 }
262}
263
264impl Accumulator {
265 fn push(&mut self, value: f64) {
266 if value.is_finite() {
267 self.running.push(value);
268 } else {
269 self.invalid += 1;
270 }
271 }
272
273 fn finish(&self, field: &'static str) -> Measurement {
274 let running = self.running;
275 let overflow = !running.is_empty()
276 && [Some(running.sum()), running.mean(), running.variance(), running.rms()]
277 .into_iter()
278 .any(|statistic| !statistic.is_some_and(f64::is_finite));
279 let statistics = (!running.is_empty() && !overflow).then_some(running);
280 Measurement { field, statistics, invalid: self.invalid, overflow }
281 }
282}
283
284