Skip to main content

mz_compute/logging/
timely.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Logging dataflows for events generated by timely dataflow.
11
12use std::cell::RefCell;
13use std::collections::BTreeMap;
14use std::rc::Rc;
15use std::time::Duration;
16
17use columnar::{Columnar, Index};
18use columnation::{Columnation, CopyRegion};
19use mz_compute_client::logging::LoggingConfig;
20use mz_ore::cast::CastFrom;
21use mz_repr::{Datum, Diff, Timestamp};
22use mz_row_spine::RowRowBuilder;
23use mz_timely_util::columnar::batcher;
24use mz_timely_util::columnar::builder::ColumnBuilder;
25use mz_timely_util::columnar::{Col2ValBatcher, columnar_exchange};
26use mz_timely_util::replay::MzReplay;
27use timely::dataflow::Scope;
28use timely::dataflow::channels::pact::{ExchangeCore, Pipeline};
29use timely::dataflow::operators::Operator;
30use timely::dataflow::operators::generic::OutputBuilder;
31use timely::dataflow::operators::generic::builder_rc::OperatorBuilder;
32use timely::dataflow::operators::generic::operator::empty;
33use timely::logging::{
34    ChannelsEvent, MessagesEvent, OperatesEvent, ParkEvent, ScheduleEvent, ShutdownEvent,
35    TimelyEvent,
36};
37use tracing::error;
38
39use crate::extensions::arrange::MzArrangeCore;
40use crate::logging::compute::{ComputeEvent, DataflowShutdown};
41use crate::logging::{
42    EventQueue, LogVariant, OutputSessionColumnar, OutputSessionVec, PermutedRowPacker, TimelyLog,
43    Update,
44};
45use crate::logging::{LogCollection, SharedLoggingState, consolidate_and_pack};
46use crate::typedefs::RowRowSpine;
47use crate::typedefs::{ConsolidateBatcher, ConsolidateKeyValBatcher};
48
49/// The return type of [`construct`].
50pub(super) struct Return {
51    /// Collections to export.
52    pub collections: BTreeMap<LogVariant, LogCollection>,
53}
54
55/// Constructs the logging dataflow fragment for timely logs.
56///
57/// Params
58/// * `scope`: The Timely scope hosting the log analysis dataflow.
59/// * `config`: Logging configuration
60/// * `event_queue`: The source to read log events from.
61pub(super) fn construct(
62    scope: Scope<'_, Timestamp>,
63    config: &LoggingConfig,
64    event_queue: EventQueue<Vec<(Duration, TimelyEvent)>>,
65    shared_state: Rc<RefCell<SharedLoggingState>>,
66) -> Return {
67    scope.scoped("timely logging", move |scope| {
68        let enable_logging = config.enable_logging;
69        let (logs, token) = if enable_logging {
70            event_queue.links.mz_replay(
71                scope,
72                "timely logs",
73                config.interval,
74                event_queue.activator,
75            )
76        } else {
77            let token: Rc<dyn std::any::Any> = Rc::new(Box::new(()));
78            (empty(scope), token)
79        };
80
81        // Build a demux operator that splits the replayed event stream up into the separate
82        // logging streams.
83        let mut demux = OperatorBuilder::new("Timely Logging Demux".to_string(), scope.clone());
84        let mut input = demux.new_input(logs, Pipeline);
85        let (operates_out, operates) = demux.new_output();
86        let mut operates_out = OutputBuilder::from(operates_out);
87        let (channels_out, channels) = demux.new_output();
88        let mut channels_out = OutputBuilder::from(channels_out);
89        let (addresses_out, addresses) = demux.new_output();
90        let mut addresses_out = OutputBuilder::from(addresses_out);
91        let (parks_out, parks) = demux.new_output();
92        let mut parks_out = OutputBuilder::from(parks_out);
93        let (messages_sent_out, messages_sent) = demux.new_output();
94        let mut messages_sent_out = OutputBuilder::from(messages_sent_out);
95        let (messages_received_out, messages_received) = demux.new_output();
96        let mut messages_received_out = OutputBuilder::from(messages_received_out);
97        let (schedules_duration_out, schedules_duration) = demux.new_output();
98        let mut schedules_duration_out = OutputBuilder::from(schedules_duration_out);
99        let (schedules_histogram_out, schedules_histogram) = demux.new_output();
100        let mut schedules_histogram_out = OutputBuilder::from(schedules_histogram_out);
101        let (batches_sent_out, batches_sent) = demux.new_output();
102        let mut batches_sent_out = OutputBuilder::from(batches_sent_out);
103        let (batches_received_out, batches_received) = demux.new_output();
104        let mut batches_received_out = OutputBuilder::from(batches_received_out);
105
106        let worker_id = scope.index();
107        let mut demux_state = DemuxState::default();
108        demux.build(|_capability| {
109            let peers = scope.peers();
110            let logging_interval_ms = std::cmp::max(1, config.interval.as_millis());
111            move |_frontiers| {
112                let mut operates = operates_out.activate();
113                let mut channels = channels_out.activate();
114                let mut addresses = addresses_out.activate();
115                let mut parks = parks_out.activate();
116                let mut messages_sent = messages_sent_out.activate();
117                let mut messages_received = messages_received_out.activate();
118                let mut batches_sent = batches_sent_out.activate();
119                let mut batches_received = batches_received_out.activate();
120                let mut schedules_duration = schedules_duration_out.activate();
121                let mut schedules_histogram = schedules_histogram_out.activate();
122
123                input.for_each(|cap, data| {
124                    let mut output_buffers = DemuxOutput {
125                        operates: operates.session_with_builder(&cap),
126                        channels: channels.session_with_builder(&cap),
127                        addresses: addresses.session_with_builder(&cap),
128                        parks: parks.session_with_builder(&cap),
129                        messages_sent: messages_sent.session_with_builder(&cap),
130                        messages_received: messages_received.session_with_builder(&cap),
131                        schedules_duration: schedules_duration.session_with_builder(&cap),
132                        schedules_histogram: schedules_histogram.session_with_builder(&cap),
133                        batches_sent: batches_sent.session_with_builder(&cap),
134                        batches_received: batches_received.session_with_builder(&cap),
135                    };
136
137                    for (time, event) in data.drain(..) {
138                        if let TimelyEvent::Messages(msg) = &event {
139                            match msg.is_send {
140                                true => assert_eq!(msg.source, worker_id),
141                                false => assert_eq!(msg.target, worker_id),
142                            }
143                        }
144
145                        DemuxHandler {
146                            state: &mut demux_state,
147                            shared_state: &mut shared_state.borrow_mut(),
148                            output: &mut output_buffers,
149                            logging_interval_ms,
150                            peers,
151                            time,
152                        }
153                        .handle(event);
154                    }
155                });
156            }
157        });
158
159        // Encode the contents of each logging stream into its expected `Row` format.
160        // We pre-arrange the logging streams to force a consolidation and reduce the amount of
161        // updates that reach `Row` encoding.
162
163        let operates = consolidate_and_pack::<
164            ConsolidateKeyValBatcher<_, _, _, _>,
165            ColumnBuilder<_>,
166            _,
167            _,
168            _,
169            _,
170        >(
171            operates,
172            TimelyLog::Operates,
173            move |data, packer, session| {
174                for ((id, name), time, diff) in data.iter() {
175                    let data = packer.pack_slice(&[
176                        Datum::UInt64(u64::cast_from(*id)),
177                        Datum::UInt64(u64::cast_from(worker_id)),
178                        Datum::String(name),
179                    ]);
180                    session.give((data, time, diff));
181                }
182            },
183        );
184
185        // TODO: `consolidate_and_pack` requires columnation, which `ChannelDatum` does not
186        // implement. Consider consolidating here once we support columnar.
187        let channels = channels.unary::<ColumnBuilder<_>, _, _, _>(
188            Pipeline,
189            "ToRow Channels",
190            |_cap, _info| {
191                let mut packer = PermutedRowPacker::new(TimelyLog::Channels);
192                move |input, output| {
193                    input.for_each_stamp(|time, data| {
194                        let mut session = output.session_with_builder(&time);
195                        for d in data.flat_map(|c| c.borrow().into_index_iter()) {
196                            let ((datum, ()), time, diff) = d;
197                            let (source_node, source_port) = datum.source;
198                            let (target_node, target_port) = datum.target;
199                            let data = packer.pack_slice(&[
200                                Datum::UInt64(u64::cast_from(datum.id)),
201                                Datum::UInt64(u64::cast_from(worker_id)),
202                                Datum::UInt64(u64::cast_from(source_node)),
203                                Datum::UInt64(u64::cast_from(source_port)),
204                                Datum::UInt64(u64::cast_from(target_node)),
205                                Datum::UInt64(u64::cast_from(target_port)),
206                                Datum::String(
207                                    std::str::from_utf8(datum.typ).expect("valid string"),
208                                ),
209                            ]);
210                            session.give((data, time, diff));
211                        }
212                    });
213                }
214            },
215        );
216
217        // Types to make rustfmt happy.
218        type KVB<K, V, T, D> = ConsolidateKeyValBatcher<K, V, T, D>;
219        type KB<K, T, D> = ConsolidateBatcher<K, T, D>;
220
221        let addresses = consolidate_and_pack::<KVB<_, _, _, _>, ColumnBuilder<_>, _, _, _, _>(
222            addresses,
223            TimelyLog::Addresses,
224            move |data, packer, session| {
225                for ((id, address), time, diff) in data.iter() {
226                    let data = packer.pack_by_index(|packer, index| match index {
227                        0 => packer.push(Datum::UInt64(u64::cast_from(*id))),
228                        1 => packer.push(Datum::UInt64(u64::cast_from(worker_id))),
229                        2 => {
230                            let list = address.iter().map(|i| Datum::UInt64(u64::cast_from(*i)));
231                            packer.push_list(list)
232                        }
233                        _ => unreachable!("Addresses relation has three columns"),
234                    });
235                    session.give((data, time, diff));
236                }
237            },
238        );
239
240        let parks = consolidate_and_pack::<KB<_, _, _>, ColumnBuilder<_>, _, _, _, _>(
241            parks,
242            TimelyLog::Parks,
243            move |data, packer, session| {
244                for ((datum, ()), time, diff) in data.iter() {
245                    let data = packer.pack_slice(&[
246                        Datum::UInt64(u64::cast_from(worker_id)),
247                        Datum::UInt64(datum.duration_pow),
248                        datum
249                            .requested_pow
250                            .map(Datum::UInt64)
251                            .unwrap_or(Datum::Null),
252                    ]);
253                    session.give((data, time, diff));
254                }
255            },
256        );
257
258        let batches_sent = consolidate_and_pack::<KB<_, _, _>, ColumnBuilder<_>, _, _, _, _>(
259            batches_sent,
260            TimelyLog::BatchesSent,
261            move |data, packer, session| {
262                for ((datum, ()), time, diff) in data.iter() {
263                    let data = packer.pack_slice(&[
264                        Datum::UInt64(u64::cast_from(datum.channel)),
265                        Datum::UInt64(u64::cast_from(worker_id)),
266                        Datum::UInt64(u64::cast_from(datum.worker)),
267                    ]);
268                    session.give((data, time, diff));
269                }
270            },
271        );
272
273        let batches_received = consolidate_and_pack::<KB<_, _, _>, ColumnBuilder<_>, _, _, _, _>(
274            batches_received,
275            TimelyLog::BatchesReceived,
276            move |data, packer, session| {
277                for ((datum, ()), time, diff) in data.iter() {
278                    let data = packer.pack_slice(&[
279                        Datum::UInt64(u64::cast_from(datum.channel)),
280                        Datum::UInt64(u64::cast_from(datum.worker)),
281                        Datum::UInt64(u64::cast_from(worker_id)),
282                    ]);
283                    session.give((data, time, diff));
284                }
285            },
286        );
287
288        let messages_sent = consolidate_and_pack::<KB<_, _, _>, ColumnBuilder<_>, _, _, _, _>(
289            messages_sent,
290            TimelyLog::MessagesSent,
291            move |data, packer, session| {
292                for ((datum, ()), time, diff) in data.iter() {
293                    let data = packer.pack_slice(&[
294                        Datum::UInt64(u64::cast_from(datum.channel)),
295                        Datum::UInt64(u64::cast_from(worker_id)),
296                        Datum::UInt64(u64::cast_from(datum.worker)),
297                    ]);
298                    session.give((data, time, diff));
299                }
300            },
301        );
302
303        let messages_received = consolidate_and_pack::<KB<_, _, _>, ColumnBuilder<_>, _, _, _, _>(
304            messages_received,
305            TimelyLog::MessagesReceived,
306            move |data, packer, session| {
307                for ((datum, ()), time, diff) in data.iter() {
308                    let data = packer.pack_slice(&[
309                        Datum::UInt64(u64::cast_from(datum.channel)),
310                        Datum::UInt64(u64::cast_from(datum.worker)),
311                        Datum::UInt64(u64::cast_from(worker_id)),
312                    ]);
313                    session.give((data, time, diff));
314                }
315            },
316        );
317
318        let elapsed = consolidate_and_pack::<KB<_, _, _>, ColumnBuilder<_>, _, _, _, _>(
319            schedules_duration,
320            TimelyLog::Elapsed,
321            move |data, packer, session| {
322                for ((operator, ()), time, diff) in data.iter() {
323                    let data = packer.pack_slice(&[
324                        Datum::UInt64(u64::cast_from(*operator)),
325                        Datum::UInt64(u64::cast_from(worker_id)),
326                    ]);
327                    session.give((data, time, diff));
328                }
329            },
330        );
331
332        let histogram = consolidate_and_pack::<KB<_, _, _>, ColumnBuilder<_>, _, _, _, _>(
333            schedules_histogram,
334            TimelyLog::Histogram,
335            move |data, packer, session| {
336                for ((datum, ()), time, diff) in data.iter() {
337                    let data = packer.pack_slice(&[
338                        Datum::UInt64(u64::cast_from(datum.operator)),
339                        Datum::UInt64(u64::cast_from(worker_id)),
340                        Datum::UInt64(datum.duration_pow),
341                    ]);
342                    session.give((data, time, diff));
343                }
344            },
345        );
346
347        let logs = {
348            use TimelyLog::*;
349            [
350                (Operates, operates),
351                (Channels, channels),
352                (Elapsed, elapsed),
353                (Histogram, histogram),
354                (Addresses, addresses),
355                (Parks, parks),
356                (MessagesSent, messages_sent),
357                (MessagesReceived, messages_received),
358                (BatchesSent, batches_sent),
359                (BatchesReceived, batches_received),
360            ]
361        };
362
363        // Build the output arrangements.
364        let mut collections = BTreeMap::new();
365        for (variant, collection) in logs {
366            let variant = LogVariant::Timely(variant);
367            if config.index_logs.contains_key(&variant) {
368                // Extract types to make rustfmt happy.
369                type Batcher<K, V, T, R, Chu, Se> = Col2ValBatcher<K, V, T, R, Chu, Se>;
370                type Builder<T, R> = RowRowBuilder<T, R>;
371                type Chu<K, V, T, R> =
372                    batcher::Chunker<mz_timely_util::columnation::ColumnationStack<((K, V), T, R)>>;
373                type Arranger<K, V, T, R> = Batcher<K, V, T, R, Chu<K, V, T, R>, Builder<T, R>>;
374                let trace = collection
375                    .mz_arrange_core::<_, Arranger<_, _, _, _>, RowRowSpine<_, _>>(
376                        ExchangeCore::<ColumnBuilder<_>, _>::new_core(
377                            columnar_exchange::<mz_repr::Row, mz_repr::Row, Timestamp, Diff>,
378                        ),
379                        &format!("Arrange {variant:?}"),
380                    )
381                    .trace;
382                let collection = LogCollection {
383                    trace,
384                    token: Rc::clone(&token),
385                };
386                collections.insert(variant, collection);
387            }
388        }
389
390        Return { collections }
391    })
392}
393
394/// State maintained by the demux operator.
395#[derive(Default)]
396struct DemuxState {
397    /// Information about live operators, indexed by operator ID.
398    operators: BTreeMap<usize, OperatesEvent>,
399    /// Maps dataflow IDs to channels in the dataflow.
400    dataflow_channels: BTreeMap<usize, Vec<ChannelsEvent>>,
401    /// Information about the last requested park.
402    last_park: Option<Park>,
403    /// Maps channel IDs to boxed slices counting the messages sent to each target worker.
404    messages_sent: BTreeMap<usize, Box<[MessageCount]>>,
405    /// Maps channel IDs to boxed slices counting the messages received from each source worker.
406    messages_received: BTreeMap<usize, Box<[MessageCount]>>,
407    /// Stores for scheduled operators the time when they were scheduled.
408    schedule_starts: Vec<(usize, Duration)>,
409    /// Maps operator IDs to a vector recording the (count, elapsed_ns) values in each histogram
410    /// bucket.
411    schedules_data: BTreeMap<usize, Vec<(isize, Diff)>>,
412}
413
414struct Park {
415    /// Time when the park occurred.
416    time: Duration,
417    /// Requested park time.
418    requested: Option<Duration>,
419}
420
421/// Organize message counts into number of batches and records.
422#[derive(Default, Copy, Clone, Debug)]
423struct MessageCount {
424    /// The number of batches sent across a channel.
425    batches: i64,
426    /// The number of records sent across a channel.
427    records: Diff,
428}
429
430/// Bundled output buffers used by the demux operator.
431//
432// We use tuples rather than dedicated `*Datum` structs for `operates` and `addresses` to avoid
433// having to manually implement `Columnation`. If `Columnation` could be `#[derive]`ed, that
434// wouldn't be an issue.
435struct DemuxOutput<'a, 'b> {
436    operates: OutputSessionVec<'a, 'b, Update<(usize, String)>>,
437    channels: OutputSessionColumnar<'a, 'b, Update<(ChannelDatum, ())>>,
438    addresses: OutputSessionVec<'a, 'b, Update<(usize, Vec<usize>)>>,
439    parks: OutputSessionVec<'a, 'b, Update<(ParkDatum, ())>>,
440    batches_sent: OutputSessionVec<'a, 'b, Update<(MessageDatum, ())>>,
441    batches_received: OutputSessionVec<'a, 'b, Update<(MessageDatum, ())>>,
442    messages_sent: OutputSessionVec<'a, 'b, Update<(MessageDatum, ())>>,
443    messages_received: OutputSessionVec<'a, 'b, Update<(MessageDatum, ())>>,
444    schedules_duration: OutputSessionVec<'a, 'b, Update<(usize, ())>>,
445    schedules_histogram: OutputSessionVec<'a, 'b, Update<(ScheduleHistogramDatum, ())>>,
446}
447
448#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Columnar)]
449struct ChannelDatum {
450    id: usize,
451    source: (usize, usize),
452    target: (usize, usize),
453    typ: String,
454}
455
456#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
457struct ParkDatum {
458    duration_pow: u64,
459    requested_pow: Option<u64>,
460}
461
462impl Columnation for ParkDatum {
463    type InnerRegion = CopyRegion<Self>;
464}
465
466#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
467struct MessageDatum {
468    channel: usize,
469    worker: usize,
470}
471
472impl Columnation for MessageDatum {
473    type InnerRegion = CopyRegion<Self>;
474}
475
476#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
477struct ScheduleHistogramDatum {
478    operator: usize,
479    duration_pow: u64,
480}
481
482impl Columnation for ScheduleHistogramDatum {
483    type InnerRegion = CopyRegion<Self>;
484}
485
486/// Event handler of the demux operator.
487struct DemuxHandler<'a, 'b, 'c> {
488    /// State kept by the demux operator.
489    state: &'a mut DemuxState,
490    /// State shared across log receivers.
491    shared_state: &'a mut SharedLoggingState,
492    /// Demux output buffers.
493    output: &'a mut DemuxOutput<'b, 'c>,
494    /// The logging interval specifying the time granularity for the updates.
495    logging_interval_ms: u128,
496    /// The number of timely workers.
497    peers: usize,
498    /// The current event time.
499    time: Duration,
500}
501
502impl DemuxHandler<'_, '_, '_> {
503    /// Return the timestamp associated with the current event, based on the event time and the
504    /// logging interval.
505    fn ts(&self) -> Timestamp {
506        let time_ms = self.time.as_millis();
507        let interval = self.logging_interval_ms;
508        let rounded = (time_ms / interval + 1) * interval;
509        rounded.try_into().expect("must fit")
510    }
511
512    /// Handle the given timely event.
513    fn handle(&mut self, event: TimelyEvent) {
514        use TimelyEvent::*;
515
516        match event {
517            Operates(e) => self.handle_operates(e),
518            Channels(e) => self.handle_channels(e),
519            Shutdown(e) => self.handle_shutdown(e),
520            Park(e) => self.handle_park(e),
521            Messages(e) => self.handle_messages(e),
522            Schedule(e) => self.handle_schedule(e),
523            _ => (),
524        }
525    }
526
527    fn handle_operates(&mut self, event: OperatesEvent) {
528        let ts = self.ts();
529        let datum = (event.id, event.name.clone());
530        self.output.operates.give((datum, ts, Diff::ONE));
531
532        let datum = (event.id, event.addr.clone());
533        self.output.addresses.give((datum, ts, Diff::ONE));
534
535        self.state.operators.insert(event.id, event);
536    }
537
538    fn handle_channels(&mut self, event: ChannelsEvent) {
539        let ts = self.ts();
540        let datum = ChannelDatumReference {
541            id: event.id,
542            source: event.source,
543            target: event.target,
544            typ: &event.typ,
545        };
546        self.output.channels.give(((datum, ()), ts, Diff::ONE));
547
548        let datum = (event.id, event.scope_addr.clone());
549        self.output.addresses.give((datum, ts, Diff::ONE));
550
551        let dataflow_index = event.scope_addr[0];
552        self.state
553            .dataflow_channels
554            .entry(dataflow_index)
555            .or_default()
556            .push(event);
557    }
558
559    fn handle_shutdown(&mut self, event: ShutdownEvent) {
560        // Dropped operators should result in a negative record for
561        // the `operates` collection, cancelling out the initial
562        // operator announcement.
563        // Remove logging for this operator.
564
565        let Some(operator) = self.state.operators.remove(&event.id) else {
566            error!(operator_id = ?event.id, "missing operator entry at time of shutdown");
567            return;
568        };
569
570        // Retract operator information.
571        let ts = self.ts();
572        let datum = (operator.id, operator.name);
573        self.output.operates.give((datum, ts, Diff::MINUS_ONE));
574
575        // Retract schedules information for the operator
576        if let Some(schedules) = self.state.schedules_data.remove(&event.id) {
577            for (bucket, (count, elapsed_ns)) in IntoIterator::into_iter(schedules)
578                .enumerate()
579                .filter(|(_, (count, _))| *count != 0)
580            {
581                self.output
582                    .schedules_duration
583                    .give(((event.id, ()), ts, Diff::from(-elapsed_ns)));
584
585                let datum = ScheduleHistogramDatum {
586                    operator: event.id,
587                    duration_pow: 1 << bucket,
588                };
589                let diff = Diff::cast_from(-count);
590                self.output
591                    .schedules_histogram
592                    .give(((datum, ()), ts, diff));
593            }
594        }
595
596        if operator.addr.len() == 1 {
597            let dataflow_index = operator.addr[0];
598            self.handle_dataflow_shutdown(dataflow_index);
599        }
600
601        let datum = (operator.id, operator.addr);
602        self.output.addresses.give((datum, ts, Diff::MINUS_ONE));
603    }
604
605    fn handle_dataflow_shutdown(&mut self, dataflow_index: usize) {
606        // Notify compute logging about the shutdown.
607        self.shared_state.compute_logger.as_ref().map(|logger| {
608            logger.log(&(ComputeEvent::DataflowShutdown(DataflowShutdown { dataflow_index })))
609        });
610
611        // When a dataflow shuts down, we need to retract all its channels.
612        let Some(channels) = self.state.dataflow_channels.remove(&dataflow_index) else {
613            return;
614        };
615
616        let ts = self.ts();
617        for channel in channels {
618            // Retract channel description.
619            let datum = ChannelDatumReference {
620                id: channel.id,
621                source: channel.source,
622                target: channel.target,
623                typ: &channel.typ,
624            };
625            self.output
626                .channels
627                .give(((datum, ()), ts, Diff::MINUS_ONE));
628
629            let datum = (channel.id, channel.scope_addr);
630            self.output.addresses.give((datum, ts, Diff::MINUS_ONE));
631
632            // Retract messages logged for this channel.
633            if let Some(sent) = self.state.messages_sent.remove(&channel.id) {
634                for (target_worker, count) in sent.iter().enumerate() {
635                    let datum = MessageDatum {
636                        channel: channel.id,
637                        worker: target_worker,
638                    };
639                    self.output
640                        .messages_sent
641                        .give(((datum, ()), ts, Diff::from(-count.records)));
642                    self.output
643                        .batches_sent
644                        .give(((datum, ()), ts, Diff::from(-count.batches)));
645                }
646            }
647            if let Some(received) = self.state.messages_received.remove(&channel.id) {
648                for (source_worker, count) in received.iter().enumerate() {
649                    let datum = MessageDatum {
650                        channel: channel.id,
651                        worker: source_worker,
652                    };
653                    self.output.messages_received.give((
654                        (datum, ()),
655                        ts,
656                        Diff::from(-count.records),
657                    ));
658                    self.output.batches_received.give((
659                        (datum, ()),
660                        ts,
661                        Diff::from(-count.batches),
662                    ));
663                }
664            }
665        }
666    }
667
668    fn handle_park(&mut self, event: ParkEvent) {
669        match event {
670            ParkEvent::Park(requested) => {
671                let park = Park {
672                    time: self.time,
673                    requested,
674                };
675                let existing = self.state.last_park.replace(park);
676                if existing.is_some() {
677                    error!("park without a succeeding unpark");
678                }
679            }
680            ParkEvent::Unpark => {
681                let Some(park) = self.state.last_park.take() else {
682                    error!("unpark without a preceding park");
683                    return;
684                };
685
686                let duration_ns = self.time.saturating_sub(park.time).as_nanos();
687                let duration_pow =
688                    u64::try_from(duration_ns.next_power_of_two()).expect("must fit");
689                let requested_pow = park
690                    .requested
691                    .map(|r| u64::try_from(r.as_nanos().next_power_of_two()).expect("must fit"));
692
693                let ts = self.ts();
694                let datum = ParkDatum {
695                    duration_pow,
696                    requested_pow,
697                };
698                self.output.parks.give(((datum, ()), ts, Diff::ONE));
699            }
700        }
701    }
702
703    fn handle_messages(&mut self, event: MessagesEvent) {
704        let ts = self.ts();
705        let count = Diff::from(event.record_count);
706
707        if event.is_send {
708            let datum = MessageDatum {
709                channel: event.channel,
710                worker: event.target,
711            };
712            self.output.messages_sent.give(((datum, ()), ts, count));
713            self.output.batches_sent.give(((datum, ()), ts, Diff::ONE));
714
715            let sent_counts = self
716                .state
717                .messages_sent
718                .entry(event.channel)
719                .or_insert_with(|| vec![Default::default(); self.peers].into_boxed_slice());
720            sent_counts[event.target].records += count;
721            sent_counts[event.target].batches += 1;
722        } else {
723            let datum = MessageDatum {
724                channel: event.channel,
725                worker: event.source,
726            };
727            self.output.messages_received.give(((datum, ()), ts, count));
728            self.output
729                .batches_received
730                .give(((datum, ()), ts, Diff::ONE));
731
732            let received_counts = self
733                .state
734                .messages_received
735                .entry(event.channel)
736                .or_insert_with(|| vec![Default::default(); self.peers].into_boxed_slice());
737            received_counts[event.source].records += count;
738            received_counts[event.source].batches += 1;
739        }
740    }
741
742    fn handle_schedule(&mut self, event: ScheduleEvent) {
743        match event.start_stop {
744            timely::logging::StartStop::Start => {
745                self.state.schedule_starts.push((event.id, self.time));
746            }
747            timely::logging::StartStop::Stop => {
748                let Some((old_id, start_time)) = self.state.schedule_starts.pop() else {
749                    error!(operator_id = ?event.id, "schedule stop without preceding start");
750                    return;
751                };
752
753                if old_id != event.id {
754                    error!(start_id = ?old_id, stop_id = ?event.id, "schedule stop without preceding start");
755                    return;
756                }
757
758                let elapsed_ns = self.time.saturating_sub(start_time).as_nanos();
759                let elapsed_i64 = i64::try_from(elapsed_ns).expect("must fit");
760                let elapsed_diff = Diff::from(elapsed_i64);
761                let elapsed_pow = u64::try_from(elapsed_ns.next_power_of_two()).expect("must fit");
762
763                let ts = self.ts();
764                let datum = event.id;
765                self.output
766                    .schedules_duration
767                    .give(((datum, ()), ts, elapsed_diff));
768
769                let datum = ScheduleHistogramDatum {
770                    operator: event.id,
771                    duration_pow: elapsed_pow,
772                };
773                self.output
774                    .schedules_histogram
775                    .give(((datum, ()), ts, Diff::ONE));
776
777                // Record count and elapsed time for later retraction.
778                let index = usize::cast_from(elapsed_pow.trailing_zeros());
779                let data = self.state.schedules_data.entry(event.id).or_default();
780                grow_vec(data, index);
781                let (count, duration) = &mut data[index];
782                *count += 1;
783                *duration += elapsed_diff;
784            }
785        }
786    }
787}
788
789/// Grow the given vector so it fits the given index.
790///
791/// This does nothing if the vector is already large enough.
792fn grow_vec<T>(vec: &mut Vec<T>, index: usize)
793where
794    T: Clone + Default,
795{
796    if vec.len() <= index {
797        vec.resize(index + 1, Default::default());
798    }
799}