1use std::fmt::Write;
5
6use crate::{format::fmt_us, record::AggregateRecord};
7
8#[derive(Debug)]
9pub enum DimSource {
10 Text(&'static str),
11
12 Number {
13 field: &'static str,
14 prefix: &'static str,
15 },
16}
17
18impl DimSource {
19 pub fn field(&self) -> &'static str {
20 match self {
21 DimSource::Text(field) => field,
22 DimSource::Number {
23 field,
24 ..
25 } => field,
26 }
27 }
28}
29
30#[derive(Debug)]
31pub struct SpanSpec {
32 pub name: &'static str,
33
34 pub duration_override: Option<&'static str>,
35 pub dims: &'static [DimSource],
36 pub extras: &'static [&'static str],
37 pub render: Option<fn(&AggregateRecord, &mut String)>,
38}
39
40static SPECS: &[SpanSpec] = &[
41 SpanSpec {
42 name: "flow::engine::apply",
43 duration_override: Some("apply_time_us"),
44 dims: &[
45 DimSource::Text("node_type"),
46 DimSource::Number {
47 field: "operator_id",
48 prefix: "op",
49 },
50 ],
51 extras: &["input_rows", "output_rows", "lock_wait_us"],
52 render: Some(render_apply),
53 },
54 SpanSpec {
55 name: "flow::state::range_limited",
56 duration_override: None,
57 dims: &[
58 DimSource::Text("site"),
59 DimSource::Number {
60 field: "operator_id",
61 prefix: "op",
62 },
63 ],
64 extras: &["rows_fetched", "rows_tombstoned"],
65 render: Some(render_state_range),
66 },
67 SpanSpec {
68 name: "flow::state::range",
69 duration_override: None,
70 dims: &[
71 DimSource::Text("site"),
72 DimSource::Number {
73 field: "operator_id",
74 prefix: "op",
75 },
76 ],
77 extras: &["rows_fetched", "rows_tombstoned"],
78 render: Some(render_state_range),
79 },
80];
81
82pub fn spec_for(name: &str) -> Option<&'static SpanSpec> {
83 SPECS.iter().find(|spec| spec.name == name)
84}
85
86fn render_apply(record: &AggregateRecord, out: &mut String) {
87 let e = record.extras();
88 let _ = write!(out, " lock={} io={}->{}", fmt_us(e[2]), e[0], e[1]);
89}
90
91fn render_state_range(record: &AggregateRecord, out: &mut String) {
92 let e = record.extras();
93 let dead = match e[0] {
94 0 => 0.0,
95 fetched => e[1] as f64 * 100.0 / fetched as f64,
96 };
97 let per_call = match record.calls {
98 0 => 0,
99 calls => e[0] / calls,
100 };
101 let _ = write!(out, " fetched={} tomb={} dead={:.0}% rows/call={}", e[0], e[1], dead, per_call);
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::record::{MAX_DIMENSIONS, MAX_EXTRAS};
108
109 #[test]
110 fn every_spec_fits_the_record_it_fills() {
111 for spec in SPECS {
115 assert!(
116 spec.dims.len() <= MAX_DIMENSIONS,
117 "{} declares {} dimensions but a record holds {MAX_DIMENSIONS}",
118 spec.name,
119 spec.dims.len()
120 );
121 assert!(
122 spec.extras.len() <= MAX_EXTRAS,
123 "{} declares {} extras but a record holds {MAX_EXTRAS}",
124 spec.name,
125 spec.extras.len()
126 );
127 }
128 }
129
130 #[test]
131 fn a_spec_name_resolves_only_itself() {
132 for spec in SPECS {
135 let found = spec_for(spec.name).expect("declared spec must resolve");
136 assert_eq!(found.name, spec.name);
137 }
138 assert!(spec_for("flow::engine::process_batch").is_none(), "an unlisted span must opt out");
139 }
140
141 #[test]
142 fn only_a_deliberate_span_overrides_its_own_duration() {
143 let overriding: Vec<&str> =
147 SPECS.iter().filter(|s| s.duration_override.is_some()).map(|s| s.name).collect();
148 assert_eq!(
149 overriding,
150 vec!["flow::engine::apply"],
151 "a new duration override needs an explicit decision, not a default"
152 );
153 }
154}