1use std::sync::LazyLock;
20use std::sync::atomic::{AtomicU64, Ordering};
21use std::time::{Duration, Instant};
22
23use mz_controller_types::ReplicaId;
24use serde::{Deserialize, Serialize};
25
26use mz_repr::{GlobalId, RelationDesc, Row, SqlScalarType};
27
28pub static MZ_SOURCE_STATISTICS_RAW_DESC: LazyLock<RelationDesc> = LazyLock::new(|| {
29 RelationDesc::builder()
30 .with_column("id", SqlScalarType::String.nullable(false))
32 .with_column("replica_id", SqlScalarType::String.nullable(true))
34 .with_column("messages_received", SqlScalarType::UInt64.nullable(false))
40 .with_column("bytes_received", SqlScalarType::UInt64.nullable(false))
43 .with_column("updates_staged", SqlScalarType::UInt64.nullable(false))
46 .with_column("updates_committed", SqlScalarType::UInt64.nullable(false))
49 .with_column("records_indexed", SqlScalarType::UInt64.nullable(false))
55 .with_column("bytes_indexed", SqlScalarType::UInt64.nullable(false))
58 .with_column(
62 "rehydration_latency",
63 SqlScalarType::Interval.nullable(true),
64 )
65 .with_column(
73 "snapshot_records_known",
74 SqlScalarType::UInt64.nullable(true),
75 )
76 .with_column(
83 "snapshot_records_staged",
84 SqlScalarType::UInt64.nullable(true),
85 )
86 .with_column("snapshot_committed", SqlScalarType::Bool.nullable(false))
91 .with_column("offset_known", SqlScalarType::UInt64.nullable(true))
100 .with_column("offset_committed", SqlScalarType::UInt64.nullable(true))
103 .finish()
104});
105
106pub static MZ_SINK_STATISTICS_RAW_DESC: LazyLock<RelationDesc> = LazyLock::new(|| {
107 RelationDesc::builder()
108 .with_column("id", SqlScalarType::String.nullable(false))
110 .with_column("replica_id", SqlScalarType::String.nullable(true))
112 .with_column("messages_staged", SqlScalarType::UInt64.nullable(false))
118 .with_column("messages_committed", SqlScalarType::UInt64.nullable(false))
121 .with_column("bytes_staged", SqlScalarType::UInt64.nullable(false))
124 .with_column("bytes_committed", SqlScalarType::UInt64.nullable(false))
127 .finish()
128});
129
130pub trait StorageMetric {
135 fn summarize<'a, I>(values: I) -> Self
138 where
139 I: IntoIterator<Item = &'a Self>,
140 Self: Sized + 'a;
141
142 fn incorporate(&mut self, other: Self, id: GlobalId, field_name: &'static str);
146}
147
148#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
150pub struct Counter(u64);
151
152impl StorageMetric for Counter {
153 fn summarize<'a, I>(values: I) -> Self
154 where
155 I: IntoIterator<Item = &'a Self>,
156 Self: Sized + 'a,
157 {
158 Self(values.into_iter().map(|c| c.0).sum())
160 }
161
162 fn incorporate(&mut self, other: Self, _id: GlobalId, _field_name: &'static str) {
163 self.0 += other.0
165 }
166}
167
168impl From<u64> for Counter {
169 fn from(f: u64) -> Self {
170 Counter(f)
171 }
172}
173
174#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
176pub struct ResettingLatency(Option<i64>);
177
178impl From<Option<i64>> for ResettingLatency {
179 fn from(f: Option<i64>) -> Self {
180 ResettingLatency(f)
181 }
182}
183
184impl StorageMetric for ResettingLatency {
185 fn summarize<'a, I>(values: I) -> Self
186 where
187 I: IntoIterator<Item = &'a Self>,
188 Self: Sized + 'a,
189 {
190 let mut max = 0;
191 for value in values {
192 match value.0 {
193 None => return Self(None),
195 Some(value) => max = std::cmp::max(max, value),
197 }
198 }
199
200 Self(Some(max))
201 }
202
203 fn incorporate(&mut self, other: Self, _id: GlobalId, _field_name: &'static str) {
204 self.0 = other.0;
206 }
207}
208
209impl ResettingLatency {
210 fn reset(&mut self) {
211 self.0 = None;
212 }
213}
214
215#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
217pub struct ResettingNullableTotal(Option<u64>);
218
219impl StorageMetric for ResettingNullableTotal {
220 fn summarize<'a, I>(values: I) -> Self
221 where
222 I: IntoIterator<Item = &'a Self>,
223 Self: Sized + 'a,
224 {
225 let mut sum = 0;
226 for value in values {
227 match value.0 {
228 None => return Self(None),
230 Some(value) => sum += value,
232 }
233 }
234
235 Self(Some(sum))
236 }
237
238 fn incorporate(&mut self, other: Self, _id: GlobalId, _field_name: &'static str) {
239 match (&mut self.0, other.0) {
240 (None, other) => {
241 self.0 = other;
242 }
243 (Some(this), Some(other)) => *this = other,
245 (Some(_), None) => {
246 }
248 }
249 }
250}
251
252impl From<Option<u64>> for ResettingNullableTotal {
253 fn from(f: Option<u64>) -> Self {
254 ResettingNullableTotal(f)
255 }
256}
257
258impl ResettingNullableTotal {
259 fn reset(&mut self) {
260 self.0 = None;
261 }
262}
263
264#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
266pub struct ResettingTotal(u64);
267
268impl StorageMetric for ResettingTotal {
269 fn summarize<'a, I>(values: I) -> Self
270 where
271 I: IntoIterator<Item = &'a Self>,
272 Self: Sized + 'a,
273 {
274 Self(values.into_iter().map(|c| c.0).sum())
276 }
277
278 fn incorporate(&mut self, other: Self, _id: GlobalId, _field_name: &'static str) {
279 self.0 = other.0;
281 }
282}
283
284impl From<u64> for ResettingTotal {
285 fn from(f: u64) -> Self {
286 ResettingTotal(f)
287 }
288}
289
290impl ResettingTotal {
291 fn reset(&mut self) {
292 self.0 = 0;
293 }
294}
295
296#[derive(Clone, Debug, Default)]
302struct RegressionLog {
303 last_error: Option<Instant>,
304}
305
306impl RegressionLog {
307 const ERROR_INTERVAL: Duration = Duration::from_secs(15 * 60);
308
309 fn log(&mut self, message: std::fmt::Arguments<'_>) {
310 let now = Instant::now();
311 match self.last_error {
312 Some(last) if now.duration_since(last) < Self::ERROR_INTERVAL => {
313 tracing::warn!("{message}")
314 }
315 _ => {
316 self.last_error = Some(now);
317 tracing::error!("{message}")
318 }
319 }
320 }
321}
322
323impl PartialEq for RegressionLog {
325 fn eq(&self, _other: &Self) -> bool {
326 true
327 }
328}
329
330#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
332pub struct Boolean(bool, #[serde(skip)] RegressionLog);
333
334impl StorageMetric for Boolean {
335 fn summarize<'a, I>(values: I) -> Self
336 where
337 I: IntoIterator<Item = &'a Self>,
338 Self: Sized + 'a,
339 {
340 Self(
342 values.into_iter().fold(true, |base, new| base & new.0),
343 RegressionLog::default(),
344 )
345 }
346
347 fn incorporate(&mut self, other: Self, id: GlobalId, field_name: &'static str) {
348 #[allow(clippy::bool_comparison)]
352 if other.0 < self.0 {
353 self.1.log(format_args!(
354 "boolean gauge for field {field_name} of {id} erroneously regressed from true to false",
355 ));
356 return;
357 }
358 self.0 = other.0;
359 }
360}
361
362impl From<bool> for Boolean {
363 fn from(f: bool) -> Self {
364 Boolean(f, RegressionLog::default())
365 }
366}
367
368#[derive(Debug, Serialize, Deserialize, Default)]
370pub struct Total {
371 total: Option<u64>,
373 #[serde(skip)]
376 regressions:
377 Option<mz_ore::metrics::DeleteOnDropCounter<prometheus::core::AtomicU64, Vec<String>>>,
378 #[serde(skip)]
379 regression_log: RegressionLog,
380}
381
382impl From<Option<u64>> for Total {
383 fn from(f: Option<u64>) -> Self {
384 Total {
385 total: f,
386 regressions: None,
387 regression_log: RegressionLog::default(),
388 }
389 }
390}
391
392impl Clone for Total {
393 fn clone(&self) -> Self {
394 Self {
395 total: self.total,
396 regressions: None,
397 regression_log: self.regression_log.clone(),
398 }
399 }
400}
401
402impl PartialEq for Total {
403 fn eq(&self, other: &Self) -> bool {
404 self.total == other.total
405 }
406}
407
408impl Total {
409 fn pack(&self) -> u64 {
411 self.total.unwrap_or_default()
412 }
413}
414
415impl StorageMetric for Total {
416 fn summarize<'a, I>(values: I) -> Self
417 where
418 I: IntoIterator<Item = &'a Self>,
419 Self: Sized + 'a,
420 {
421 let mut any_none = false;
424
425 let inner = values
426 .into_iter()
427 .filter_map(|i| {
428 any_none |= i.total.is_none();
429 i.total.as_ref()
430 })
431 .sum();
432
433 Self {
436 total: (!any_none).then_some(inner),
437 regressions: None,
438 regression_log: RegressionLog::default(),
439 }
440 }
441
442 fn incorporate(&mut self, other: Self, id: GlobalId, field_name: &'static str) {
443 match (&mut self.total, other.total) {
444 (_, None) => {}
445 (None, Some(other)) => self.total = Some(other),
446 (Some(this), Some(other)) => {
447 if other < *this {
448 if let Some(metric) = &self.regressions {
449 metric.inc()
450 } else {
451 self.regression_log.log(format_args!(
452 "total gauge {field_name} of {id} erroneously regressed from {} to {}",
453 this, other
454 ));
455 }
456 return;
457 }
458 *this = other
459 }
460 }
461 }
462}
463
464#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
466pub struct Gauge<T>(T);
467
468impl<T> Gauge<T> {
469 pub fn gauge<F>(f: F) -> Self
471 where
472 T: From<F>,
473 {
474 Gauge(f.into())
475 }
476}
477
478impl<T: StorageMetric> StorageMetric for Gauge<T> {
479 fn summarize<'a, I>(values: I) -> Self
480 where
481 I: IntoIterator<Item = &'a Self>,
482 Self: Sized + 'a,
483 {
484 Gauge(T::summarize(values.into_iter().map(|i| &i.0)))
485 }
486
487 fn incorporate(&mut self, other: Self, id: GlobalId, field_name: &'static str) {
488 self.0.incorporate(other.0, id, field_name)
489 }
490}
491
492pub trait PackableStats {
495 fn pack(&self, packer: mz_repr::RowPacker<'_>);
497 fn unpack(
499 row: Row,
500 metrics: &crate::metrics::StorageControllerMetrics,
501 ) -> (GlobalId, Option<ReplicaId>, Self);
502}
503
504pub trait ExpirableStats {
506 fn last_updated(&self) -> Instant;
509}
510
511pub trait ZeroInitializedStats {
521 fn needs_zero_initialization(&self) -> bool;
523
524 fn mark_zero_initialized(&mut self);
526
527 fn zero_stat(&self) -> Self;
530}
531
532#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
536pub struct SourceStatisticsUpdate {
537 pub id: GlobalId,
538
539 pub messages_received: Counter,
540 pub bytes_received: Counter,
541 pub updates_staged: Counter,
542 pub updates_committed: Counter,
543
544 pub records_indexed: Gauge<ResettingTotal>,
545 pub bytes_indexed: Gauge<ResettingTotal>,
546 pub rehydration_latency_ms: Gauge<ResettingLatency>,
547 pub snapshot_records_known: Gauge<ResettingNullableTotal>,
548 pub snapshot_records_staged: Gauge<ResettingNullableTotal>,
549
550 pub snapshot_committed: Gauge<Boolean>,
551 pub offset_known: Gauge<Total>,
570 pub offset_committed: Gauge<Total>,
571}
572
573impl SourceStatisticsUpdate {
574 pub fn new(id: GlobalId) -> Self {
575 Self {
576 id,
577 messages_received: Default::default(),
578 bytes_received: Default::default(),
579 updates_staged: Default::default(),
580 updates_committed: Default::default(),
581 records_indexed: Default::default(),
582 bytes_indexed: Default::default(),
583 rehydration_latency_ms: Default::default(),
584 snapshot_records_known: Default::default(),
585 snapshot_records_staged: Default::default(),
586 snapshot_committed: Default::default(),
587 offset_known: Default::default(),
588 offset_committed: Default::default(),
589 }
590 }
591
592 pub fn summarize<'a, I, F>(values: F) -> Self
593 where
594 I: IntoIterator<Item = &'a Self>,
595 F: Fn() -> I,
596 Self: 'a,
597 {
598 SourceStatisticsUpdate {
599 id: values().into_iter().next().unwrap().id,
600 messages_received: Counter::summarize(
601 values().into_iter().map(|s| &s.messages_received),
602 ),
603 bytes_received: Counter::summarize(values().into_iter().map(|s| &s.bytes_received)),
604 updates_staged: Counter::summarize(values().into_iter().map(|s| &s.updates_staged)),
605 updates_committed: Counter::summarize(
606 values().into_iter().map(|s| &s.updates_committed),
607 ),
608 records_indexed: Gauge::summarize(values().into_iter().map(|s| &s.records_indexed)),
609 bytes_indexed: Gauge::summarize(values().into_iter().map(|s| &s.bytes_indexed)),
610 rehydration_latency_ms: Gauge::summarize(
611 values().into_iter().map(|s| &s.rehydration_latency_ms),
612 ),
613 snapshot_records_known: Gauge::summarize(
614 values().into_iter().map(|s| &s.snapshot_records_known),
615 ),
616 snapshot_records_staged: Gauge::summarize(
617 values().into_iter().map(|s| &s.snapshot_records_staged),
618 ),
619 snapshot_committed: Gauge::summarize(
620 values().into_iter().map(|s| &s.snapshot_committed),
621 ),
622 offset_known: Gauge::summarize(values().into_iter().map(|s| &s.offset_known)),
623 offset_committed: Gauge::summarize(values().into_iter().map(|s| &s.offset_committed)),
624 }
625 }
626
627 pub fn reset_counters(&mut self) {
629 self.messages_received.0 = 0;
630 self.bytes_received.0 = 0;
631 self.updates_staged.0 = 0;
632 self.updates_committed.0 = 0;
633 }
634
635 pub fn reset_gauges(&mut self) {
637 self.records_indexed.0.reset();
638 self.bytes_indexed.0.reset();
639 self.rehydration_latency_ms.0.reset();
640 self.snapshot_records_known.0.reset();
641 self.snapshot_records_staged.0.reset();
642 }
643
644 pub fn incorporate(&mut self, other: SourceStatisticsUpdate) {
645 let SourceStatisticsUpdate {
646 id,
647 messages_received,
648 bytes_received,
649 updates_staged,
650 updates_committed,
651 records_indexed,
652 bytes_indexed,
653 rehydration_latency_ms,
654 snapshot_records_known,
655 snapshot_records_staged,
656 snapshot_committed,
657 offset_known,
658 offset_committed,
659 ..
660 } = self;
661
662 messages_received.incorporate(other.messages_received, *id, "messages_received");
663 bytes_received.incorporate(other.bytes_received, *id, "bytes_received");
664 updates_staged.incorporate(other.updates_staged, *id, "updates_staged");
665 updates_committed.incorporate(other.updates_committed, *id, "updates_committed");
666 records_indexed.incorporate(other.records_indexed, *id, "records_indexed");
667 bytes_indexed.incorporate(other.bytes_indexed, *id, "bytes_indexed");
668 rehydration_latency_ms.incorporate(
669 other.rehydration_latency_ms,
670 *id,
671 "rehydration_latency_ms",
672 );
673 snapshot_records_known.incorporate(
674 other.snapshot_records_known,
675 *id,
676 "snapshot_records_known",
677 );
678 snapshot_records_staged.incorporate(
679 other.snapshot_records_staged,
680 *id,
681 "snapshot_records_staged",
682 );
683 snapshot_committed.incorporate(other.snapshot_committed, *id, "snapshot_committed");
684 offset_known.incorporate(other.offset_known, *id, "offset_known");
685 offset_committed.incorporate(other.offset_committed, *id, "offset_committed");
686 }
687
688 pub fn incorporate_counters(&mut self, other: SourceStatisticsUpdate) {
690 let SourceStatisticsUpdate {
691 id,
692 messages_received,
693 bytes_received,
694 updates_staged,
695 updates_committed,
696 ..
697 } = self;
698
699 messages_received.incorporate(other.messages_received, *id, "messages_received");
700 bytes_received.incorporate(other.bytes_received, *id, "bytes_received");
701 updates_staged.incorporate(other.updates_staged, *id, "updates_staged");
702 updates_committed.incorporate(other.updates_committed, *id, "updates_committed");
703 }
704
705 pub fn with_metrics(mut self, metrics: &crate::metrics::StorageControllerMetrics) -> Self {
707 self.offset_known.0.regressions = Some(metrics.regressed_offset_known(self.id));
708 self
709 }
710}
711
712impl PackableStats for SourceStatisticsUpdate {
713 fn pack(&self, mut packer: mz_repr::RowPacker<'_>) {
714 use mz_repr::Datum;
715 packer.push(Datum::from(self.id.to_string().as_str()));
717 packer.push(Datum::from(self.messages_received.0));
719 packer.push(Datum::from(self.bytes_received.0));
720 packer.push(Datum::from(self.updates_staged.0));
721 packer.push(Datum::from(self.updates_committed.0));
722 packer.push(Datum::from(self.records_indexed.0.0));
724 packer.push(Datum::from(self.bytes_indexed.0.0));
725 let rehydration_latency = self
726 .rehydration_latency_ms
727 .0
728 .0
729 .map(|ms| mz_repr::adt::interval::Interval::new(0, 0, ms * 1000));
730 packer.push(Datum::from(rehydration_latency));
731 packer.push(Datum::from(self.snapshot_records_known.0.0));
732 packer.push(Datum::from(self.snapshot_records_staged.0.0));
733 packer.push(Datum::from(self.snapshot_committed.0.0));
735 packer.push(Datum::from(self.offset_known.0.pack()));
736 packer.push(Datum::from(self.offset_committed.0.pack()));
737 }
738
739 fn unpack(
740 row: Row,
741 metrics: &crate::metrics::StorageControllerMetrics,
742 ) -> (GlobalId, Option<ReplicaId>, Self) {
743 let mut iter = row.iter();
744 let mut s = Self {
745 id: iter.next().unwrap().unwrap_str().parse().unwrap(),
746
747 messages_received: iter.next().unwrap().unwrap_uint64().into(),
748 bytes_received: iter.next().unwrap().unwrap_uint64().into(),
749 updates_staged: iter.next().unwrap().unwrap_uint64().into(),
750 updates_committed: iter.next().unwrap().unwrap_uint64().into(),
751
752 records_indexed: Gauge::gauge(iter.next().unwrap().unwrap_uint64()),
753 bytes_indexed: Gauge::gauge(iter.next().unwrap().unwrap_uint64()),
754 rehydration_latency_ms: Gauge::gauge(
755 <Option<mz_repr::adt::interval::Interval>>::try_from(iter.next().unwrap())
756 .unwrap()
757 .map(|int| int.micros / 1000),
758 ),
759 snapshot_records_known: Gauge::gauge(
760 <Option<u64>>::try_from(iter.next().unwrap()).unwrap(),
761 ),
762 snapshot_records_staged: Gauge::gauge(
763 <Option<u64>>::try_from(iter.next().unwrap()).unwrap(),
764 ),
765
766 snapshot_committed: Gauge::gauge(iter.next().unwrap().unwrap_bool()),
767 offset_known: Gauge::gauge(Some(iter.next().unwrap().unwrap_uint64())),
768 offset_committed: Gauge::gauge(Some(iter.next().unwrap().unwrap_uint64())),
769 };
770
771 s.offset_known.0.regressions = Some(metrics.regressed_offset_known(s.id));
772 (s.id, None, s)
773 }
774}
775
776#[derive(Clone, Debug, PartialEq)]
782pub struct ControllerSourceStatistics {
783 pub id: GlobalId,
784
785 pub replica_id: Option<ReplicaId>,
789
790 pub messages_received: Counter,
791 pub bytes_received: Counter,
792 pub updates_staged: Counter,
793 pub updates_committed: Counter,
794
795 pub records_indexed: Gauge<ResettingTotal>,
796 pub bytes_indexed: Gauge<ResettingTotal>,
797 pub rehydration_latency_ms: Gauge<ResettingLatency>,
798 pub snapshot_records_known: Gauge<ResettingNullableTotal>,
799 pub snapshot_records_staged: Gauge<ResettingNullableTotal>,
800
801 pub snapshot_committed: Gauge<Boolean>,
802 pub offset_known: Gauge<Total>,
803 pub offset_committed: Gauge<Total>,
804
805 pub last_updated: Instant,
814
815 needs_zero_initialization: bool,
817}
818
819impl ControllerSourceStatistics {
820 pub fn new(id: GlobalId, replica_id: Option<ReplicaId>) -> Self {
821 Self {
822 id,
823 replica_id,
824 messages_received: Default::default(),
825 bytes_received: Default::default(),
826 updates_staged: Default::default(),
827 updates_committed: Default::default(),
828 records_indexed: Default::default(),
829 bytes_indexed: Default::default(),
830 rehydration_latency_ms: Default::default(),
831 snapshot_records_known: Default::default(),
832 snapshot_records_staged: Default::default(),
833 snapshot_committed: Default::default(),
834 offset_known: Default::default(),
835 offset_committed: Default::default(),
836 last_updated: Instant::now(),
837 needs_zero_initialization: true,
838 }
839 }
840
841 pub fn incorporate(&mut self, update: SourceStatisticsUpdate) {
844 let ControllerSourceStatistics {
845 id,
846 replica_id: _,
847 messages_received,
848 bytes_received,
849 updates_staged,
850 updates_committed,
851 records_indexed,
852 bytes_indexed,
853 rehydration_latency_ms,
854 snapshot_records_known,
855 snapshot_records_staged,
856 snapshot_committed,
857 offset_known,
858 offset_committed,
859 last_updated,
860 needs_zero_initialization: _,
861 } = self;
862
863 messages_received.incorporate(update.messages_received, *id, "messages_received");
864 bytes_received.incorporate(update.bytes_received, *id, "bytes_received");
865 updates_staged.incorporate(update.updates_staged, *id, "updates_staged");
866 updates_committed.incorporate(update.updates_committed, *id, "updates_committed");
867 records_indexed.incorporate(update.records_indexed, *id, "records_indexed");
868 bytes_indexed.incorporate(update.bytes_indexed, *id, "bytes_indexed");
869 rehydration_latency_ms.incorporate(
870 update.rehydration_latency_ms,
871 *id,
872 "rehydration_latency_ms",
873 );
874 snapshot_records_known.incorporate(
875 update.snapshot_records_known,
876 *id,
877 "snapshot_records_known",
878 );
879 snapshot_records_staged.incorporate(
880 update.snapshot_records_staged,
881 *id,
882 "snapshot_records_staged",
883 );
884 snapshot_committed.incorporate(update.snapshot_committed, *id, "snapshot_committed");
885 offset_known.incorporate(update.offset_known, *id, "offset_known");
886 offset_committed.incorporate(update.offset_committed, *id, "offset_committed");
887
888 *last_updated = Instant::now();
889 }
890}
891
892impl PackableStats for ControllerSourceStatistics {
893 fn pack(&self, mut packer: mz_repr::RowPacker<'_>) {
894 use mz_repr::Datum;
895 packer.push(Datum::from(self.id.to_string().as_str()));
897 if let Some(replica_id) = self.replica_id {
899 packer.push(Datum::from(replica_id.to_string().as_str()));
900 } else {
901 packer.push(Datum::Null);
902 }
903 packer.push(Datum::from(self.messages_received.0));
905 packer.push(Datum::from(self.bytes_received.0));
906 packer.push(Datum::from(self.updates_staged.0));
907 packer.push(Datum::from(self.updates_committed.0));
908 packer.push(Datum::from(self.records_indexed.0.0));
910 packer.push(Datum::from(self.bytes_indexed.0.0));
911 let rehydration_latency = self
912 .rehydration_latency_ms
913 .0
914 .0
915 .map(|ms| mz_repr::adt::interval::Interval::new(0, 0, ms * 1000));
916 packer.push(Datum::from(rehydration_latency));
917 packer.push(Datum::from(self.snapshot_records_known.0.0));
918 packer.push(Datum::from(self.snapshot_records_staged.0.0));
919 packer.push(Datum::from(self.snapshot_committed.0.0));
921 packer.push(Datum::from(self.offset_known.0.pack()));
922 packer.push(Datum::from(self.offset_committed.0.pack()));
923 }
924
925 fn unpack(
926 row: Row,
927 metrics: &crate::metrics::StorageControllerMetrics,
928 ) -> (GlobalId, Option<ReplicaId>, Self) {
929 let mut iter = row.iter();
930 let id = iter.next().unwrap().unwrap_str().parse().unwrap();
931 let replica_id_or_null = iter.next().unwrap();
932
933 let replica_id = if replica_id_or_null.is_null() {
934 None
935 } else {
936 Some(
937 replica_id_or_null
938 .unwrap_str()
939 .parse::<ReplicaId>()
940 .unwrap(),
941 )
942 };
943
944 let mut s = Self {
945 id,
946 replica_id,
947
948 messages_received: iter.next().unwrap().unwrap_uint64().into(),
949 bytes_received: iter.next().unwrap().unwrap_uint64().into(),
950 updates_staged: iter.next().unwrap().unwrap_uint64().into(),
951 updates_committed: iter.next().unwrap().unwrap_uint64().into(),
952
953 records_indexed: Gauge::gauge(iter.next().unwrap().unwrap_uint64()),
954 bytes_indexed: Gauge::gauge(iter.next().unwrap().unwrap_uint64()),
955 rehydration_latency_ms: Gauge::gauge(
956 <Option<mz_repr::adt::interval::Interval>>::try_from(iter.next().unwrap())
957 .unwrap()
958 .map(|int| int.micros / 1000),
959 ),
960 snapshot_records_known: Gauge::gauge(
961 <Option<u64>>::try_from(iter.next().unwrap()).unwrap(),
962 ),
963 snapshot_records_staged: Gauge::gauge(
964 <Option<u64>>::try_from(iter.next().unwrap()).unwrap(),
965 ),
966
967 snapshot_committed: Gauge::gauge(iter.next().unwrap().unwrap_bool()),
968 offset_known: Gauge::gauge(Some(iter.next().unwrap().unwrap_uint64())),
969 offset_committed: Gauge::gauge(Some(iter.next().unwrap().unwrap_uint64())),
970 last_updated: Instant::now(),
971 needs_zero_initialization: false,
974 };
975
976 s.offset_known.0.regressions = Some(metrics.regressed_offset_known(s.id));
977 (s.id, replica_id, s)
978 }
979}
980
981impl ExpirableStats for ControllerSourceStatistics {
982 fn last_updated(&self) -> Instant {
983 self.last_updated
984 }
985}
986
987impl ZeroInitializedStats for ControllerSourceStatistics {
988 fn needs_zero_initialization(&self) -> bool {
989 self.needs_zero_initialization
990 }
991
992 fn mark_zero_initialized(&mut self) {
993 self.needs_zero_initialization = false;
994 }
995
996 fn zero_stat(&self) -> Self {
997 ControllerSourceStatistics::new(self.id, self.replica_id)
998 }
999}
1000
1001#[derive(Clone, Debug, PartialEq)]
1007pub struct ControllerSinkStatistics {
1008 pub id: GlobalId,
1009 pub replica_id: ReplicaId,
1010
1011 pub messages_staged: Counter,
1012 pub messages_committed: Counter,
1013 pub bytes_staged: Counter,
1014 pub bytes_committed: Counter,
1015
1016 pub last_updated: Instant,
1018
1019 needs_zero_initialization: bool,
1021}
1022
1023impl ControllerSinkStatistics {
1024 pub fn new(id: GlobalId, replica_id: ReplicaId) -> Self {
1025 Self {
1026 id,
1027 replica_id,
1028 messages_staged: Default::default(),
1029 messages_committed: Default::default(),
1030 bytes_staged: Default::default(),
1031 bytes_committed: Default::default(),
1032 last_updated: Instant::now(),
1033 needs_zero_initialization: true,
1034 }
1035 }
1036
1037 pub fn incorporate(&mut self, update: SinkStatisticsUpdate) {
1039 let ControllerSinkStatistics {
1040 id,
1041 replica_id: _,
1042 messages_staged,
1043 messages_committed,
1044 bytes_staged,
1045 bytes_committed,
1046 last_updated,
1047 needs_zero_initialization: _,
1048 } = self;
1049
1050 messages_staged.incorporate(update.messages_staged, *id, "messages_staged");
1051 bytes_staged.incorporate(update.bytes_staged, *id, "bytes_staged");
1052 messages_committed.incorporate(update.messages_committed, *id, "messages_committed");
1053 bytes_committed.incorporate(update.bytes_committed, *id, "bytes_committed");
1054
1055 *last_updated = Instant::now();
1056 }
1057}
1058
1059impl PackableStats for ControllerSinkStatistics {
1060 fn pack(&self, mut packer: mz_repr::RowPacker<'_>) {
1061 use mz_repr::Datum;
1062 packer.push(Datum::from(self.id.to_string().as_str()));
1063 packer.push(Datum::from(self.replica_id.to_string().as_str()));
1064 packer.push(Datum::from(self.messages_staged.0));
1065 packer.push(Datum::from(self.messages_committed.0));
1066 packer.push(Datum::from(self.bytes_staged.0));
1067 packer.push(Datum::from(self.bytes_committed.0));
1068 }
1069
1070 fn unpack(
1071 row: Row,
1072 _metrics: &crate::metrics::StorageControllerMetrics,
1073 ) -> (GlobalId, Option<ReplicaId>, Self) {
1074 let mut iter = row.iter();
1075 let s = Self {
1076 id: iter.next().unwrap().unwrap_str().parse().unwrap(),
1077 replica_id: iter.next().unwrap().unwrap_str().parse().unwrap(),
1078 messages_staged: iter.next().unwrap().unwrap_uint64().into(),
1079 messages_committed: iter.next().unwrap().unwrap_uint64().into(),
1080 bytes_staged: iter.next().unwrap().unwrap_uint64().into(),
1081 bytes_committed: iter.next().unwrap().unwrap_uint64().into(),
1082 last_updated: Instant::now(),
1083 needs_zero_initialization: false,
1086 };
1087 (s.id, Some(s.replica_id), s)
1088 }
1089}
1090
1091impl ExpirableStats for ControllerSinkStatistics {
1092 fn last_updated(&self) -> Instant {
1093 self.last_updated
1094 }
1095}
1096
1097impl ZeroInitializedStats for ControllerSinkStatistics {
1098 fn needs_zero_initialization(&self) -> bool {
1099 self.needs_zero_initialization
1100 }
1101
1102 fn mark_zero_initialized(&mut self) {
1103 self.needs_zero_initialization = false;
1104 }
1105
1106 fn zero_stat(&self) -> Self {
1107 ControllerSinkStatistics::new(self.id, self.replica_id)
1108 }
1109}
1110
1111#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
1115pub struct SinkStatisticsUpdate {
1116 pub id: GlobalId,
1117
1118 pub messages_staged: Counter,
1119 pub messages_committed: Counter,
1120 pub bytes_staged: Counter,
1121 pub bytes_committed: Counter,
1122}
1123
1124impl SinkStatisticsUpdate {
1125 pub fn new(id: GlobalId) -> Self {
1126 Self {
1127 id,
1128 messages_staged: Default::default(),
1129 messages_committed: Default::default(),
1130 bytes_staged: Default::default(),
1131 bytes_committed: Default::default(),
1132 }
1133 }
1134
1135 pub fn incorporate(&mut self, other: SinkStatisticsUpdate) {
1136 let SinkStatisticsUpdate {
1137 id,
1138 messages_staged,
1139 messages_committed,
1140 bytes_staged,
1141 bytes_committed,
1142 ..
1143 } = self;
1144
1145 messages_staged.incorporate(other.messages_staged, *id, "messages_staged");
1146 messages_committed.incorporate(other.messages_committed, *id, "messages_committed");
1147 bytes_staged.incorporate(other.bytes_staged, *id, "bytes_staged");
1148 bytes_committed.incorporate(other.bytes_committed, *id, "bytes_committed");
1149 }
1150
1151 pub fn incorporate_counters(&mut self, other: SinkStatisticsUpdate) {
1153 let SinkStatisticsUpdate {
1154 id,
1155 messages_staged,
1156 messages_committed,
1157 bytes_staged,
1158 bytes_committed,
1159 ..
1160 } = self;
1161
1162 messages_staged.incorporate(other.messages_staged, *id, "messages_staged");
1163 messages_committed.incorporate(other.messages_committed, *id, "messages_committed");
1164 bytes_staged.incorporate(other.bytes_staged, *id, "bytes_staged");
1165 bytes_committed.incorporate(other.bytes_committed, *id, "bytes_committed");
1166 }
1167
1168 pub fn summarize<'a, I, F>(values: F) -> Self
1169 where
1170 I: IntoIterator<Item = &'a Self>,
1171 F: Fn() -> I,
1172 Self: 'a,
1173 {
1174 SinkStatisticsUpdate {
1175 id: values().into_iter().next().unwrap().id,
1176 messages_staged: Counter::summarize(values().into_iter().map(|s| &s.messages_staged)),
1177 messages_committed: Counter::summarize(
1178 values().into_iter().map(|s| &s.messages_committed),
1179 ),
1180 bytes_staged: Counter::summarize(values().into_iter().map(|s| &s.bytes_staged)),
1181 bytes_committed: Counter::summarize(values().into_iter().map(|s| &s.bytes_committed)),
1182 }
1183 }
1184
1185 pub fn reset_counters(&mut self) {
1187 self.messages_staged.0 = 0;
1188 self.messages_committed.0 = 0;
1189 self.bytes_staged.0 = 0;
1190 self.bytes_committed.0 = 0;
1191 }
1192
1193 pub fn reset_gauges(&self) {}
1195}
1196
1197impl PackableStats for SinkStatisticsUpdate {
1198 fn pack(&self, mut packer: mz_repr::RowPacker<'_>) {
1199 use mz_repr::Datum;
1200 packer.push(Datum::from(self.id.to_string().as_str()));
1201 packer.push(Datum::from(self.messages_staged.0));
1202 packer.push(Datum::from(self.messages_committed.0));
1203 packer.push(Datum::from(self.bytes_staged.0));
1204 packer.push(Datum::from(self.bytes_committed.0));
1205 }
1206
1207 fn unpack(
1208 row: Row,
1209 _metrics: &crate::metrics::StorageControllerMetrics,
1210 ) -> (GlobalId, Option<ReplicaId>, Self) {
1211 let mut iter = row.iter();
1212 let s = Self {
1213 id: iter.next().unwrap().unwrap_str().parse().unwrap(),
1215 messages_staged: iter.next().unwrap().unwrap_uint64().into(),
1217 messages_committed: iter.next().unwrap().unwrap_uint64().into(),
1218 bytes_staged: iter.next().unwrap().unwrap_uint64().into(),
1219 bytes_committed: iter.next().unwrap().unwrap_uint64().into(),
1220 };
1221 (s.id, None, s)
1222 }
1223}
1224
1225#[derive(Default, Debug)]
1227pub struct WebhookStatistics {
1228 pub messages_received: AtomicU64,
1229 pub bytes_received: AtomicU64,
1230 pub updates_staged: AtomicU64,
1231 pub updates_committed: AtomicU64,
1232}
1233
1234impl WebhookStatistics {
1235 pub fn drain_into_update(&self, id: GlobalId) -> SourceStatisticsUpdate {
1238 SourceStatisticsUpdate {
1239 id,
1240 messages_received: self.messages_received.swap(0, Ordering::Relaxed).into(),
1241 bytes_received: self.bytes_received.swap(0, Ordering::Relaxed).into(),
1242 updates_staged: self.updates_staged.swap(0, Ordering::Relaxed).into(),
1243 updates_committed: self.updates_committed.swap(0, Ordering::Relaxed).into(),
1244 records_indexed: Gauge::gauge(0),
1245 bytes_indexed: Gauge::gauge(0),
1246 rehydration_latency_ms: Gauge::gauge(None),
1247 snapshot_records_known: Gauge::gauge(None),
1248 snapshot_records_staged: Gauge::gauge(None),
1249 snapshot_committed: Gauge::gauge(true),
1250 offset_known: Gauge::gauge(None::<u64>),
1251 offset_committed: Gauge::gauge(None::<u64>),
1252 }
1253 }
1254}
1255
1256#[cfg(test)]
1257mod tests {
1258 use super::*;
1259
1260 #[mz_ore::test]
1261 fn regression_log_errors_once_per_interval() {
1262 let mut log = RegressionLog::default();
1263 log.log(format_args!("first"));
1264 let first = log.last_error.expect("first regression logs an error");
1265 log.log(format_args!("second"));
1266 assert_eq!(
1267 log.last_error,
1268 Some(first),
1269 "second regression within the interval only warns"
1270 );
1271 }
1272}