1use std::sync::{Arc, LazyLock};
16
17use prometheus::{
18 IntCounterVec, Registry, exponential_buckets, histogram_opts,
19 register_int_counter_vec_with_registry,
20};
21use risingwave_common::config::MetricLevel;
22use risingwave_common::metrics::{
23 GaugeAggregation, IntGaugeVecRelabelExt, LabelGuardedHistogramVec, LabelGuardedIntCounterVec,
24 LabelGuardedIntGaugeVec, MetricVecRelabelExt, RelabeledAggregatedIntGaugeVec,
25 RelabeledGuardedIntCounterVec,
26};
27use risingwave_common::monitor::GLOBAL_METRICS_REGISTRY;
28use risingwave_common::{
29 register_guarded_histogram_vec_with_registry, register_guarded_int_counter_vec_with_registry,
30 register_guarded_int_gauge_vec_with_registry,
31};
32
33use crate::connector_metrics_level;
34use crate::source::kafka::stats::RdKafkaStats;
35
36#[derive(Debug, Clone, Copy)]
41pub enum ConnectorAckFailureType {
42 Error,
43 Timeout,
44 EmptyMessageId,
45 ChannelMissing,
46 ChannelSendError,
47 DecodeError,
48 BrokerError,
49}
50
51impl ConnectorAckFailureType {
52 pub fn as_str(self) -> &'static str {
53 match self {
54 Self::Error => "error",
55 Self::Timeout => "timeout",
56 Self::EmptyMessageId => "empty_message_id",
57 Self::ChannelMissing => "channel_missing",
58 Self::ChannelSendError => "channel_send_error",
59 Self::DecodeError => "decode_error",
60 Self::BrokerError => "broker_error",
61 }
62 }
63}
64
65#[derive(Debug, Clone)]
66pub struct EnumeratorMetrics {
67 pub high_watermark: LabelGuardedIntGaugeVec,
68 pub kafka_consumer_group_delete_failure_count: IntCounterVec,
72 pub pg_cdc_confirmed_flush_lsn: LabelGuardedIntGaugeVec,
74 pub pg_cdc_upstream_max_lsn: LabelGuardedIntGaugeVec,
76 pub mysql_cdc_binlog_file_seq_min: LabelGuardedIntGaugeVec,
78 pub mysql_cdc_binlog_file_seq_max: LabelGuardedIntGaugeVec,
80 pub mariadb_cdc_binlog_file_seq_min: LabelGuardedIntGaugeVec,
82 pub mariadb_cdc_binlog_file_seq_max: LabelGuardedIntGaugeVec,
84 pub sqlserver_cdc_upstream_min_lsn: LabelGuardedIntGaugeVec,
86 pub sqlserver_cdc_upstream_max_lsn: LabelGuardedIntGaugeVec,
88}
89
90pub static GLOBAL_ENUMERATOR_METRICS: LazyLock<EnumeratorMetrics> =
91 LazyLock::new(|| EnumeratorMetrics::new(&GLOBAL_METRICS_REGISTRY));
92
93impl EnumeratorMetrics {
94 fn new(registry: &Registry) -> Self {
95 let high_watermark = register_guarded_int_gauge_vec_with_registry!(
96 "source_kafka_high_watermark",
97 "High watermark for a exec per partition",
98 &["source_id", "partition"],
99 registry,
100 )
101 .unwrap();
102
103 let kafka_consumer_group_delete_failure_count = register_int_counter_vec_with_registry!(
104 "source_kafka_consumer_group_delete_failure_count",
105 "Total number of Kafka consumer group delete attempts that failed during source enumerator cleanup",
106 &["source_id", "consumer_group"],
107 registry,
108 )
109 .unwrap();
110
111 let pg_cdc_confirmed_flush_lsn = register_guarded_int_gauge_vec_with_registry!(
112 "pg_cdc_confirmed_flush_lsn",
113 "PostgreSQL CDC confirmed flush LSN",
114 &["source_id", "slot_name"],
115 registry,
116 )
117 .unwrap();
118
119 let pg_cdc_upstream_max_lsn = register_guarded_int_gauge_vec_with_registry!(
120 "pg_cdc_upstream_max_lsn",
121 "PostgreSQL CDC upstream max LSN (pg_current_wal_lsn)",
122 &["source_id", "slot_name"],
123 registry,
124 )
125 .unwrap();
126
127 let mysql_cdc_binlog_file_seq_min = register_guarded_int_gauge_vec_with_registry!(
128 "mysql_cdc_binlog_file_seq_min",
129 "MySQL CDC upstream binlog file sequence number (minimum/oldest)",
130 &["source_id", "hostname", "port"],
131 registry,
132 )
133 .unwrap();
134
135 let mysql_cdc_binlog_file_seq_max = register_guarded_int_gauge_vec_with_registry!(
136 "mysql_cdc_binlog_file_seq_max",
137 "MySQL CDC upstream binlog file sequence number (maximum/newest)",
138 &["source_id", "hostname", "port"],
139 registry,
140 )
141 .unwrap();
142
143 let mariadb_cdc_binlog_file_seq_min = register_guarded_int_gauge_vec_with_registry!(
144 "mariadb_cdc_binlog_file_seq_min",
145 "MariaDB CDC upstream binlog file sequence number (minimum/oldest)",
146 &["source_id", "hostname", "port"],
147 registry,
148 )
149 .unwrap();
150
151 let mariadb_cdc_binlog_file_seq_max = register_guarded_int_gauge_vec_with_registry!(
152 "mariadb_cdc_binlog_file_seq_max",
153 "MariaDB CDC upstream binlog file sequence number (maximum/newest)",
154 &["source_id", "hostname", "port"],
155 registry,
156 )
157 .unwrap();
158
159 let sqlserver_cdc_upstream_min_lsn = register_guarded_int_gauge_vec_with_registry!(
160 "sqlserver_cdc_upstream_min_lsn",
161 "SQL Server CDC upstream minimum LSN",
162 &["source_id"],
163 registry,
164 )
165 .unwrap();
166
167 let sqlserver_cdc_upstream_max_lsn = register_guarded_int_gauge_vec_with_registry!(
168 "sqlserver_cdc_upstream_max_lsn",
169 "SQL Server CDC upstream maximum LSN",
170 &["source_id"],
171 registry,
172 )
173 .unwrap();
174
175 EnumeratorMetrics {
176 high_watermark,
177 kafka_consumer_group_delete_failure_count,
178 pg_cdc_confirmed_flush_lsn,
179 pg_cdc_upstream_max_lsn,
180 mysql_cdc_binlog_file_seq_min,
181 mysql_cdc_binlog_file_seq_max,
182 mariadb_cdc_binlog_file_seq_min,
183 mariadb_cdc_binlog_file_seq_max,
184 sqlserver_cdc_upstream_min_lsn,
185 sqlserver_cdc_upstream_max_lsn,
186 }
187 }
188
189 pub fn unused() -> Self {
190 Default::default()
191 }
192}
193
194impl Default for EnumeratorMetrics {
195 fn default() -> Self {
196 GLOBAL_ENUMERATOR_METRICS.clone()
197 }
198}
199
200#[derive(Debug, Clone)]
201pub struct SourceMetrics {
202 pub partition_input_count: RelabeledGuardedIntCounterVec,
203
204 pub partition_input_bytes: RelabeledGuardedIntCounterVec,
207 pub latest_message_id: RelabeledAggregatedIntGaugeVec,
209 pub partition_eof_count: LabelGuardedIntCounterVec,
210 pub partition_eof_offset: LabelGuardedIntGaugeVec,
211 pub rdkafka_native_metric: Arc<RdKafkaStats>,
212
213 pub direct_cdc_event_lag_latency: LabelGuardedHistogramVec,
214
215 pub parquet_source_skip_row_count: RelabeledGuardedIntCounterVec,
216 pub file_source_input_row_count: RelabeledGuardedIntCounterVec,
217 pub file_source_dirty_split_count: RelabeledAggregatedIntGaugeVec,
218 pub file_source_failed_split_count: RelabeledGuardedIntCounterVec,
219
220 pub kinesis_throughput_exceeded_count: LabelGuardedIntCounterVec,
222 pub kinesis_timeout_count: LabelGuardedIntCounterVec,
223 pub kinesis_rebuild_shard_iter_count: LabelGuardedIntCounterVec,
224 pub kinesis_early_terminate_shard_count: LabelGuardedIntCounterVec,
225 pub kinesis_lag_latency_ms: LabelGuardedHistogramVec,
226
227 connector_ack_failure_count: IntCounterVec,
229 connector_ack_success_count: IntCounterVec,
231}
232
233pub static GLOBAL_SOURCE_METRICS: LazyLock<SourceMetrics> =
234 LazyLock::new(|| SourceMetrics::new(&GLOBAL_METRICS_REGISTRY, connector_metrics_level()));
235
236impl SourceMetrics {
237 pub fn inc_connector_ack_failure_count(
238 &self,
239 source_name: &str,
240 connector_type: &'static str,
241 failure_type: ConnectorAckFailureType,
242 ) {
243 self.connector_ack_failure_count
244 .with_label_values(&[source_name, connector_type, failure_type.as_str()])
245 .inc();
246 }
247
248 pub fn inc_connector_ack_success_count(&self, source_name: &str, connector_type: &'static str) {
249 self.connector_ack_success_count
250 .with_label_values(&[source_name, connector_type])
251 .inc();
252 }
253
254 fn new(registry: &Registry, metric_level: MetricLevel) -> Self {
255 let partition_input_count = register_guarded_int_counter_vec_with_registry!(
256 "source_partition_input_count",
257 "Total number of rows that have been input from specific partition",
258 &[
259 "actor_id",
260 "source_id",
261 "partition",
262 "source_name",
263 "fragment_id"
264 ],
265 registry
266 )
267 .unwrap()
268 .relabel_debug_1(metric_level);
269 let partition_input_bytes = register_guarded_int_counter_vec_with_registry!(
270 "source_partition_input_bytes",
271 "Total bytes that have been input from specific partition",
272 &[
273 "actor_id",
274 "source_id",
275 "partition",
276 "source_name",
277 "fragment_id"
278 ],
279 registry
280 )
281 .unwrap()
282 .relabel_debug_1(metric_level);
283 let latest_message_id = register_guarded_int_gauge_vec_with_registry!(
286 "source_latest_message_id",
287 "Latest message id for a exec per partition",
288 &["actor_id", "source_id", "partition"],
289 registry,
290 )
291 .unwrap()
292 .relabel_debug_1_with_aggregation(metric_level, GaugeAggregation::Max);
293 let partition_eof_count = register_guarded_int_counter_vec_with_registry!(
294 "source_partition_eof_count",
295 "Total number of EOF events received from specific partition",
296 &["source_id", "partition", "source_name", "fragment_id"],
297 registry
298 )
299 .unwrap();
300 let partition_eof_offset = register_guarded_int_gauge_vec_with_registry!(
301 "source_partition_eof_offset",
302 "Latest resolved EOF offset for specific partition",
303 &["source_id", "partition", "source_name", "fragment_id"],
304 registry
305 )
306 .unwrap();
307
308 let opts = histogram_opts!(
309 "source_cdc_event_lag_duration_milliseconds",
310 "source_cdc_lag_latency",
311 exponential_buckets(1.0, 2.0, 21).unwrap(), );
313
314 let parquet_source_skip_row_count = register_guarded_int_counter_vec_with_registry!(
315 "parquet_source_skip_row_count",
316 "Total number of rows that have been set to null in parquet source",
317 &["actor_id", "source_id", "source_name", "fragment_id"],
318 registry
319 )
320 .unwrap()
321 .relabel_debug_1(metric_level);
322
323 let direct_cdc_event_lag_latency =
324 register_guarded_histogram_vec_with_registry!(opts, &["table_name"], registry).unwrap();
325
326 let rdkafka_native_metric = Arc::new(RdKafkaStats::new(registry.clone()));
327
328 let file_source_input_row_count = register_guarded_int_counter_vec_with_registry!(
329 "file_source_input_row_count",
330 "Total number of rows that have been read in file source",
331 &["actor_id", "source_id", "source_name", "fragment_id"],
332 registry
333 )
334 .unwrap()
335 .relabel_debug_1(metric_level);
336 let file_source_dirty_split_count = register_guarded_int_gauge_vec_with_registry!(
337 "file_source_dirty_split_count",
338 "Current number of dirty file splits in file source",
339 &["actor_id", "source_id", "source_name", "fragment_id"],
340 registry
341 )
342 .unwrap()
343 .relabel_debug_1_with_aggregation(metric_level, GaugeAggregation::Sum);
344 let file_source_failed_split_count = register_guarded_int_counter_vec_with_registry!(
345 "file_source_failed_split_count",
346 "Total number of file splits marked dirty in file source",
347 &["actor_id", "source_id", "source_name", "fragment_id"],
348 registry
349 )
350 .unwrap()
351 .relabel_debug_1(metric_level);
352
353 let kinesis_throughput_exceeded_count = register_guarded_int_counter_vec_with_registry!(
354 "kinesis_throughput_exceeded_count",
355 "Total number of times throughput exceeded in kinesis source",
356 &["source_id", "source_name", "fragment_id", "shard_id"],
357 registry
358 )
359 .unwrap();
360
361 let kinesis_timeout_count = register_guarded_int_counter_vec_with_registry!(
362 "kinesis_timeout_count",
363 "Total number of times timeout in kinesis source",
364 &["source_id", "source_name", "fragment_id", "shard_id"],
365 registry
366 )
367 .unwrap();
368
369 let kinesis_rebuild_shard_iter_count = register_guarded_int_counter_vec_with_registry!(
370 "kinesis_rebuild_shard_iter_count",
371 "Total number of times rebuild shard iter in kinesis source",
372 &["source_id", "source_name", "fragment_id", "shard_id"],
373 registry
374 )
375 .unwrap();
376
377 let kinesis_early_terminate_shard_count = register_guarded_int_counter_vec_with_registry!(
378 "kinesis_early_terminate_shard_count",
379 "Total number of times early terminate shard in kinesis source",
380 &["source_id", "source_name", "fragment_id", "shard_id"],
381 registry
382 )
383 .unwrap();
384
385 let kinesis_lag_latency_ms = register_guarded_histogram_vec_with_registry!(
386 "kinesis_lag_latency_ms",
387 "Lag latency in kinesis source",
388 &["source_id", "source_name", "fragment_id", "shard_id"],
389 registry
390 )
391 .unwrap();
392
393 let connector_ack_failure_count = register_int_counter_vec_with_registry!(
394 "source_connector_ack_failure_count",
395 "Total number of connector ack failures after checkpoint commit by bounded failure category",
396 &["source_name", "connector_type", "error_type"],
397 registry
398 )
399 .unwrap();
400 let connector_ack_success_count = register_int_counter_vec_with_registry!(
401 "source_connector_ack_success_count",
402 "Total number of successful connector acks after checkpoint commit",
403 &["source_name", "connector_type"],
404 registry
405 )
406 .unwrap();
407
408 SourceMetrics {
409 partition_input_count,
410 partition_input_bytes,
411 latest_message_id,
412 partition_eof_count,
413 partition_eof_offset,
414 rdkafka_native_metric,
415 direct_cdc_event_lag_latency,
416 parquet_source_skip_row_count,
417 file_source_input_row_count,
418 file_source_dirty_split_count,
419 file_source_failed_split_count,
420
421 kinesis_throughput_exceeded_count,
422 kinesis_timeout_count,
423 kinesis_rebuild_shard_iter_count,
424 kinesis_early_terminate_shard_count,
425 kinesis_lag_latency_ms,
426
427 connector_ack_failure_count,
428 connector_ack_success_count,
429 }
430 }
431}
432
433impl Default for SourceMetrics {
434 fn default() -> Self {
435 GLOBAL_SOURCE_METRICS.clone()
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use super::*;
442
443 fn assert_aggregation(
444 gauge_vec: &RelabeledAggregatedIntGaugeVec,
445 first_labels: &[&str],
446 second_labels: &[&str],
447 expected: i64,
448 ) {
449 let first = gauge_vec.with_guarded_label_values(first_labels);
450 let second = gauge_vec.with_guarded_label_values(second_labels);
451 first.set(5);
452 second.set(7);
453 assert_eq!(first.get(), expected);
454 assert_eq!(second.get(), expected);
455 }
456
457 #[test]
458 fn actor_relabeled_gauges_use_the_assigned_reducers() {
459 for level in [MetricLevel::Critical, MetricLevel::Info] {
460 let registry = Registry::new();
461 let metrics = SourceMetrics::new(®istry, level);
462
463 assert_aggregation(
464 &metrics.latest_message_id,
465 &["1", "source", "partition"],
466 &["2", "source", "partition"],
467 7,
468 );
469 assert_aggregation(
470 &metrics.file_source_dirty_split_count,
471 &["1", "source", "name", "fragment"],
472 &["2", "source", "name", "fragment"],
473 12,
474 );
475 }
476 }
477
478 #[test]
479 fn latest_message_id_max_is_independent_of_reader_placement() {
480 for level in [MetricLevel::Critical, MetricLevel::Info, MetricLevel::Debug] {
481 for same_worker in [true, false] {
482 let live_registry = Registry::new();
483 let backfill_registry = Registry::new();
484 let live_metrics = SourceMetrics::new(&live_registry, level);
485 let backfill_metrics = if same_worker {
486 live_metrics.clone()
487 } else {
488 SourceMetrics::new(&backfill_registry, level)
489 };
490 let live = live_metrics.latest_message_id.with_guarded_label_values(&[
491 "1",
492 "source",
493 "partition",
494 ]);
495 let backfill = backfill_metrics
496 .latest_message_id
497 .with_guarded_label_values(&["2", "source", "partition"]);
498 live.set(1_000);
499 backfill.set(100);
500
501 let mut offsets: Vec<_> = [&live_registry, &backfill_registry]
503 .into_iter()
504 .flat_map(Registry::gather)
505 .filter(|family| family.name() == "source_latest_message_id")
506 .flat_map(|family| {
507 family
508 .get_metric()
509 .iter()
510 .map(|metric| metric.get_gauge().value() as i64)
511 .collect::<Vec<_>>()
512 })
513 .collect();
514 assert_eq!(offsets.iter().copied().max(), Some(1_000));
515 offsets.sort_unstable();
516 if same_worker && level != MetricLevel::Debug {
517 assert_eq!(offsets, [1_000]);
518 } else {
519 assert_eq!(offsets, [100, 1_000]);
520 }
521 }
522 }
523 }
524}