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_row_spine::RowRowBuilder;
24use mz_timely_util::columnar::batcher;
25use mz_timely_util::columnar::builder::ColumnBuilder;
26use mz_timely_util::columnar::{Col2ValBatcher, columnar_exchange};
27use prometheus::proto::MetricType;
28use timely::dataflow::Scope;
29use timely::dataflow::channels::pact::ExchangeCore;
30use timely::dataflow::operators::generic::OutputBuilder;
31use timely::dataflow::operators::generic::builder_rc::OperatorBuilder;
32
33use crate::extensions::arrange::MzArrangeCore;
34use crate::logging::{
35 ComputeLog, LogCollection, LogVariant, PermutedRowPacker, downgrade_to_interval_boundary,
36 emit_snapshot_diff,
37};
38use crate::typedefs::RowRowSpine;
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 Col2ValBatcher<_, _, _, _, batcher::Chunker<_>, RowRowBuilder<_, _>>,
146 RowRowSpine<_, _>,
147 >(exchange, "Arrange PrometheusMetrics")
148 .trace;
149 let token: Rc<dyn std::any::Any> = Rc::new(());
150 let collection = LogCollection { trace, token };
151 collections.insert(variant, collection);
152
153 Return { collections }
154}
155
156fn flatten_metrics(
158 families: Vec<prometheus::proto::MetricFamily>,
159) -> BTreeMap<SnapshotKey, SnapshotValue> {
160 let mut snapshot = BTreeMap::new();
161
162 for family in families {
163 let base_name = family.name();
164 let help = family.help();
165 let metric_type = family.get_field_type();
166 let type_str = match metric_type {
167 MetricType::COUNTER => "counter",
168 MetricType::GAUGE => "gauge",
169 MetricType::HISTOGRAM => "histogram",
170 MetricType::SUMMARY => "summary",
171 MetricType::UNTYPED => "untyped",
172 };
173
174 for metric in family.get_metric() {
175 let base_labels: Vec<(String, String)> = metric
176 .get_label()
177 .iter()
178 .map(|l| (l.name().to_string(), l.value().to_string()))
179 .collect();
180
181 match metric_type {
182 MetricType::COUNTER => {
183 let value = metric.get_counter().value();
184 insert_row(
185 &mut snapshot,
186 base_name.to_string(),
187 base_labels,
188 value,
189 type_str,
190 help,
191 );
192 }
193 MetricType::GAUGE => {
194 let value = metric.get_gauge().value();
195 insert_row(
196 &mut snapshot,
197 base_name.to_string(),
198 base_labels,
199 value,
200 type_str,
201 help,
202 );
203 }
204 MetricType::HISTOGRAM => {
205 let histogram = metric.get_histogram();
206
207 for bucket in histogram.get_bucket() {
209 let mut labels = base_labels.clone();
210 labels.push(("le".to_string(), format_f64(bucket.upper_bound())));
211 insert_row(
212 &mut snapshot,
213 format!("{base_name}_bucket"),
214 labels,
215 f64::cast_lossy(bucket.cumulative_count()),
216 type_str,
217 help,
218 );
219 }
220
221 insert_row(
223 &mut snapshot,
224 format!("{base_name}_sum"),
225 base_labels.clone(),
226 histogram.get_sample_sum(),
227 type_str,
228 help,
229 );
230
231 insert_row(
233 &mut snapshot,
234 format!("{base_name}_count"),
235 base_labels,
236 f64::cast_lossy(histogram.get_sample_count()),
237 type_str,
238 help,
239 );
240 }
241 MetricType::SUMMARY => {
242 let summary = metric.get_summary();
243
244 for quantile in summary.get_quantile() {
246 let mut labels = base_labels.clone();
247 labels.push(("quantile".to_string(), format_f64(quantile.quantile())));
248 insert_row(
249 &mut snapshot,
250 base_name.to_string(),
251 labels,
252 quantile.value(),
253 type_str,
254 help,
255 );
256 }
257
258 insert_row(
260 &mut snapshot,
261 format!("{base_name}_sum"),
262 base_labels.clone(),
263 summary.sample_sum(),
264 type_str,
265 help,
266 );
267
268 insert_row(
270 &mut snapshot,
271 format!("{base_name}_count"),
272 base_labels,
273 f64::cast_lossy(summary.sample_count()),
274 type_str,
275 help,
276 );
277 }
278 MetricType::UNTYPED => {
279 soft_panic_or_log!("unexpected untyped metric: {base_name}");
280 }
281 }
282 }
283 }
284
285 snapshot
286}
287
288fn format_f64(v: f64) -> String {
290 if v == f64::INFINITY {
291 "+Inf".to_string()
292 } else if v == f64::NEG_INFINITY {
293 "-Inf".to_string()
294 } else {
295 v.to_string()
296 }
297}
298
299fn insert_row(
301 snapshot: &mut BTreeMap<SnapshotKey, SnapshotValue>,
302 name: String,
303 mut labels: Vec<(String, String)>,
304 value: f64,
305 metric_type: &'static str,
306 help: &str,
307) {
308 labels.sort();
309
310 snapshot.insert((name, labels), (value, metric_type, help.to_string()));
311}
312
313fn pack_row<'a>(
315 packer: &'a mut PermutedRowPacker,
316 metric_name: &str,
317 metric_type: &str,
318 labels: &[(String, String)],
319 value: f64,
320 help: &str,
321 process_id: usize,
322) -> (&'a mz_repr::RowRef, &'a mz_repr::RowRef) {
323 packer.pack_by_index(|row_packer, index| match index {
324 0 => row_packer.push(Datum::UInt64(u64::cast_from(process_id))),
326 1 => row_packer.push(Datum::String(metric_name)),
328 2 => row_packer.push(Datum::String(metric_type)),
330 3 => {
332 row_packer.push_dict(labels.iter().map(|(k, v)| (k.as_str(), Datum::String(v))));
333 }
334 4 => row_packer.push(Datum::Float64(value.into())),
336 5 => row_packer.push(Datum::String(help)),
338 _ => unreachable!("unexpected column index {index}"),
339 })
340}