1use std::collections::BTreeMap;
13use std::rc::Rc;
14use std::time::{Duration, Instant};
15
16use mz_compute_types::dyncfgs::COMPUTE_PROMETHEUS_INTROSPECTION_SCRAPE_INTERVAL;
17use mz_dyncfg::ConfigSet;
18use mz_ore::cast::{CastFrom, CastLossy};
19use mz_ore::collections::CollectionExt;
20use mz_ore::metrics::MetricsRegistry;
21use mz_ore::soft_panic_or_log;
22use mz_repr::{Datum, Timestamp};
23use mz_timely_util::columnar::batcher;
24use mz_timely_util::columnar::builder::ColumnBuilder;
25use mz_timely_util::columnar::{Col2ValBatcher, columnar_exchange};
26use prometheus::proto::MetricType;
27use timely::dataflow::Scope;
28use timely::dataflow::channels::pact::ExchangeCore;
29use timely::dataflow::operators::generic::OutputBuilder;
30use timely::dataflow::operators::generic::builder_rc::OperatorBuilder;
31
32use crate::extensions::arrange::MzArrangeCore;
33use crate::logging::{
34 ComputeLog, LogCollection, LogVariant, PermutedRowPacker, downgrade_to_interval_boundary,
35 emit_snapshot_diff,
36};
37use crate::typedefs::RowRowSpine;
38use mz_row_spine::RowRowBuilder;
39
40pub(super) struct Return {
42 pub collections: BTreeMap<LogVariant, LogCollection>,
44}
45
46type SnapshotKey = (String, Vec<(String, String)>);
48type SnapshotValue = (f64, &'static str, String);
50
51pub(super) fn construct(
53 scope: Scope<'_, Timestamp>,
54 config: &mz_compute_client::logging::LoggingConfig,
55 metrics_registry: MetricsRegistry,
56 now: Instant,
57 start_offset: Duration,
58 worker_config: Rc<ConfigSet>,
59 workers_per_process: usize,
60) -> Return {
61 let variant = LogVariant::Compute(ComputeLog::PrometheusMetrics);
62 let mut collections = BTreeMap::new();
63 let interval = config.interval;
64 let interval_ms = std::cmp::max(1, interval.as_millis());
65
66 if !config.index_logs.contains_key(&variant) {
67 return Return { collections };
68 }
69
70 let process_id = scope.index() / workers_per_process;
71 let enable = scope.index() % workers_per_process == 0;
72
73 let mut builder = OperatorBuilder::new("PrometheusMetrics".to_string(), scope.clone());
76 let (output, stream) = builder.new_output();
77 let mut output = OutputBuilder::<_, ColumnBuilder<_>>::from(output);
78
79 let operator_info = builder.operator_info();
80 builder.build(move |capabilities| {
81 let mut cap = enable.then_some(capabilities.into_element());
85 let activator = scope.activator_for(operator_info.address);
86
87 let mut prev_snapshot: BTreeMap<SnapshotKey, SnapshotValue> = BTreeMap::new();
88 let mut next_scrape = Instant::now();
89 let mut packer = PermutedRowPacker::new(ComputeLog::PrometheusMetrics);
90
91 move |_frontiers| {
92 let Some(cap) = &mut cap else { return };
93
94 let ts =
95 downgrade_to_interval_boundary(cap, &activator, now, start_offset, interval_ms);
96
97 if Instant::now() < next_scrape {
101 return;
102 }
103
104 let prom_interval =
105 COMPUTE_PROMETHEUS_INTROSPECTION_SCRAPE_INTERVAL.get(&worker_config);
106 let effective_interval = prom_interval.max(interval);
107 next_scrape = Instant::now() + effective_interval;
108
109 let new_snapshot = if !prom_interval.is_zero() {
112 let metric_families = metrics_registry.gather();
113 flatten_metrics(metric_families)
114 } else {
115 BTreeMap::new()
116 };
117
118 let mut output = output.activate();
120 let mut session = output.session_with_builder(&cap);
121 emit_snapshot_diff(
122 &mut session,
123 &mut packer,
124 &prev_snapshot,
125 &new_snapshot,
126 ts,
127 |packer, key, value| {
128 pack_row(
129 packer, &key.0, value.1, &key.1, value.0, &value.2, process_id,
130 )
131 },
132 );
133
134 prev_snapshot = new_snapshot;
135 }
136 });
137
138 let exchange = ExchangeCore::<ColumnBuilder<_>, _>::new_core(
140 columnar_exchange::<mz_repr::Row, mz_repr::Row, Timestamp, mz_repr::Diff>,
141 );
142 let trace = stream
143 .mz_arrange_core::<
144 _,
145 batcher::Chunker<_>,
146 Col2ValBatcher<_, _, _, _>,
147 RowRowBuilder<_, _>,
148 RowRowSpine<_, _>,
149 >(exchange, "Arrange PrometheusMetrics")
150 .trace;
151 let token: Rc<dyn std::any::Any> = Rc::new(());
152 let collection = LogCollection { trace, token };
153 collections.insert(variant, collection);
154
155 Return { collections }
156}
157
158fn flatten_metrics(
160 families: Vec<prometheus::proto::MetricFamily>,
161) -> BTreeMap<SnapshotKey, SnapshotValue> {
162 let mut snapshot = BTreeMap::new();
163
164 for family in families {
165 let base_name = family.name();
166 let help = family.help();
167 let metric_type = family.get_field_type();
168 let type_str = match metric_type {
169 MetricType::COUNTER => "counter",
170 MetricType::GAUGE => "gauge",
171 MetricType::HISTOGRAM => "histogram",
172 MetricType::SUMMARY => "summary",
173 MetricType::UNTYPED => "untyped",
174 };
175
176 for metric in family.get_metric() {
177 let base_labels: Vec<(String, String)> = metric
178 .get_label()
179 .iter()
180 .map(|l| (l.name().to_string(), l.value().to_string()))
181 .collect();
182
183 match metric_type {
184 MetricType::COUNTER => {
185 let value = metric.get_counter().value();
186 insert_row(
187 &mut snapshot,
188 base_name.to_string(),
189 base_labels,
190 value,
191 type_str,
192 help,
193 );
194 }
195 MetricType::GAUGE => {
196 let value = metric.get_gauge().value();
197 insert_row(
198 &mut snapshot,
199 base_name.to_string(),
200 base_labels,
201 value,
202 type_str,
203 help,
204 );
205 }
206 MetricType::HISTOGRAM => {
207 let histogram = metric.get_histogram();
208
209 for bucket in histogram.get_bucket() {
211 let mut labels = base_labels.clone();
212 labels.push(("le".to_string(), format_f64(bucket.upper_bound())));
213 insert_row(
214 &mut snapshot,
215 format!("{base_name}_bucket"),
216 labels,
217 f64::cast_lossy(bucket.cumulative_count()),
218 type_str,
219 help,
220 );
221 }
222
223 insert_row(
225 &mut snapshot,
226 format!("{base_name}_sum"),
227 base_labels.clone(),
228 histogram.get_sample_sum(),
229 type_str,
230 help,
231 );
232
233 insert_row(
235 &mut snapshot,
236 format!("{base_name}_count"),
237 base_labels,
238 f64::cast_lossy(histogram.get_sample_count()),
239 type_str,
240 help,
241 );
242 }
243 MetricType::SUMMARY => {
244 let summary = metric.get_summary();
245
246 for quantile in summary.get_quantile() {
248 let mut labels = base_labels.clone();
249 labels.push(("quantile".to_string(), format_f64(quantile.quantile())));
250 insert_row(
251 &mut snapshot,
252 base_name.to_string(),
253 labels,
254 quantile.value(),
255 type_str,
256 help,
257 );
258 }
259
260 insert_row(
262 &mut snapshot,
263 format!("{base_name}_sum"),
264 base_labels.clone(),
265 summary.sample_sum(),
266 type_str,
267 help,
268 );
269
270 insert_row(
272 &mut snapshot,
273 format!("{base_name}_count"),
274 base_labels,
275 f64::cast_lossy(summary.sample_count()),
276 type_str,
277 help,
278 );
279 }
280 MetricType::UNTYPED => {
281 soft_panic_or_log!("unexpected untyped metric: {base_name}");
282 }
283 }
284 }
285 }
286
287 snapshot
288}
289
290fn format_f64(v: f64) -> String {
292 if v == f64::INFINITY {
293 "+Inf".to_string()
294 } else if v == f64::NEG_INFINITY {
295 "-Inf".to_string()
296 } else {
297 v.to_string()
298 }
299}
300
301fn insert_row(
303 snapshot: &mut BTreeMap<SnapshotKey, SnapshotValue>,
304 name: String,
305 mut labels: Vec<(String, String)>,
306 value: f64,
307 metric_type: &'static str,
308 help: &str,
309) {
310 labels.sort();
311
312 snapshot.insert((name, labels), (value, metric_type, help.to_string()));
313}
314
315fn pack_row<'a>(
317 packer: &'a mut PermutedRowPacker,
318 metric_name: &str,
319 metric_type: &str,
320 labels: &[(String, String)],
321 value: f64,
322 help: &str,
323 process_id: usize,
324) -> (&'a mz_repr::RowRef, &'a mz_repr::RowRef) {
325 packer.pack_by_index(|row_packer, index| match index {
326 0 => row_packer.push(Datum::UInt64(u64::cast_from(process_id))),
328 1 => row_packer.push(Datum::String(metric_name)),
330 2 => row_packer.push(Datum::String(metric_type)),
332 3 => {
334 row_packer.push_dict(labels.iter().map(|(k, v)| (k.as_str(), Datum::String(v))));
335 }
336 4 => row_packer.push(Datum::Float64(value.into())),
338 5 => row_packer.push(Datum::String(help)),
340 _ => unreachable!("unexpected column index {index}"),
341 })
342}