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