Skip to main content

risingwave_connector/source/monitor/
metrics.rs

1// Copyright 2023 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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::metrics::{
22    LabelGuardedHistogramVec, LabelGuardedIntCounterVec, LabelGuardedIntGaugeVec,
23};
24use risingwave_common::monitor::GLOBAL_METRICS_REGISTRY;
25use risingwave_common::{
26    register_guarded_histogram_vec_with_registry, register_guarded_int_counter_vec_with_registry,
27    register_guarded_int_gauge_vec_with_registry,
28};
29
30use crate::source::kafka::stats::RdKafkaStats;
31
32/// Low-cardinality connector ack failure categories.
33///
34/// Keep this list bounded. Do not add raw connector error messages, topics,
35/// partitions, or split identifiers as metric label values.
36#[derive(Debug, Clone, Copy)]
37pub enum ConnectorAckFailureType {
38    Error,
39    Timeout,
40    EmptyMessageId,
41    ChannelMissing,
42    ChannelSendError,
43    DecodeError,
44    BrokerError,
45}
46
47impl ConnectorAckFailureType {
48    pub fn as_str(self) -> &'static str {
49        match self {
50            Self::Error => "error",
51            Self::Timeout => "timeout",
52            Self::EmptyMessageId => "empty_message_id",
53            Self::ChannelMissing => "channel_missing",
54            Self::ChannelSendError => "channel_send_error",
55            Self::DecodeError => "decode_error",
56            Self::BrokerError => "broker_error",
57        }
58    }
59}
60
61#[derive(Debug, Clone)]
62pub struct EnumeratorMetrics {
63    pub high_watermark: LabelGuardedIntGaugeVec,
64    /// Kafka consumer group delete attempts that failed during source enumerator cleanup.
65    ///
66    /// The `consumer_group` label is fragment-derived and emitted only on cleanup failures.
67    pub kafka_consumer_group_delete_failure_count: IntCounterVec,
68    /// PostgreSQL CDC confirmed flush LSN monitoring
69    pub pg_cdc_confirmed_flush_lsn: LabelGuardedIntGaugeVec,
70    /// PostgreSQL CDC upstream max LSN monitoring
71    pub pg_cdc_upstream_max_lsn: LabelGuardedIntGaugeVec,
72    /// MySQL CDC binlog file sequence number (min)
73    pub mysql_cdc_binlog_file_seq_min: LabelGuardedIntGaugeVec,
74    /// MySQL CDC binlog file sequence number (max)
75    pub mysql_cdc_binlog_file_seq_max: LabelGuardedIntGaugeVec,
76    /// SQL Server CDC upstream minimum LSN
77    pub sqlserver_cdc_upstream_min_lsn: LabelGuardedIntGaugeVec,
78    /// SQL Server CDC upstream maximum LSN
79    pub sqlserver_cdc_upstream_max_lsn: LabelGuardedIntGaugeVec,
80}
81
82pub static GLOBAL_ENUMERATOR_METRICS: LazyLock<EnumeratorMetrics> =
83    LazyLock::new(|| EnumeratorMetrics::new(&GLOBAL_METRICS_REGISTRY));
84
85impl EnumeratorMetrics {
86    fn new(registry: &Registry) -> Self {
87        let high_watermark = register_guarded_int_gauge_vec_with_registry!(
88            "source_kafka_high_watermark",
89            "High watermark for a exec per partition",
90            &["source_id", "partition"],
91            registry,
92        )
93        .unwrap();
94
95        let kafka_consumer_group_delete_failure_count = register_int_counter_vec_with_registry!(
96            "source_kafka_consumer_group_delete_failure_count",
97            "Total number of Kafka consumer group delete attempts that failed during source enumerator cleanup",
98            &["source_id", "consumer_group"],
99            registry,
100        )
101        .unwrap();
102
103        let pg_cdc_confirmed_flush_lsn = register_guarded_int_gauge_vec_with_registry!(
104            "pg_cdc_confirmed_flush_lsn",
105            "PostgreSQL CDC confirmed flush LSN",
106            &["source_id", "slot_name"],
107            registry,
108        )
109        .unwrap();
110
111        let pg_cdc_upstream_max_lsn = register_guarded_int_gauge_vec_with_registry!(
112            "pg_cdc_upstream_max_lsn",
113            "PostgreSQL CDC upstream max LSN (pg_current_wal_lsn)",
114            &["source_id", "slot_name"],
115            registry,
116        )
117        .unwrap();
118
119        let mysql_cdc_binlog_file_seq_min = register_guarded_int_gauge_vec_with_registry!(
120            "mysql_cdc_binlog_file_seq_min",
121            "MySQL CDC upstream binlog file sequence number (minimum/oldest)",
122            &["hostname", "port"],
123            registry,
124        )
125        .unwrap();
126
127        let mysql_cdc_binlog_file_seq_max = register_guarded_int_gauge_vec_with_registry!(
128            "mysql_cdc_binlog_file_seq_max",
129            "MySQL CDC upstream binlog file sequence number (maximum/newest)",
130            &["hostname", "port"],
131            registry,
132        )
133        .unwrap();
134
135        let sqlserver_cdc_upstream_min_lsn = register_guarded_int_gauge_vec_with_registry!(
136            "sqlserver_cdc_upstream_min_lsn",
137            "SQL Server CDC upstream minimum LSN",
138            &["source_id"],
139            registry,
140        )
141        .unwrap();
142
143        let sqlserver_cdc_upstream_max_lsn = register_guarded_int_gauge_vec_with_registry!(
144            "sqlserver_cdc_upstream_max_lsn",
145            "SQL Server CDC upstream maximum LSN",
146            &["source_id"],
147            registry,
148        )
149        .unwrap();
150
151        EnumeratorMetrics {
152            high_watermark,
153            kafka_consumer_group_delete_failure_count,
154            pg_cdc_confirmed_flush_lsn,
155            pg_cdc_upstream_max_lsn,
156            mysql_cdc_binlog_file_seq_min,
157            mysql_cdc_binlog_file_seq_max,
158            sqlserver_cdc_upstream_min_lsn,
159            sqlserver_cdc_upstream_max_lsn,
160        }
161    }
162
163    pub fn unused() -> Self {
164        Default::default()
165    }
166}
167
168impl Default for EnumeratorMetrics {
169    fn default() -> Self {
170        GLOBAL_ENUMERATOR_METRICS.clone()
171    }
172}
173
174#[derive(Debug, Clone)]
175pub struct SourceMetrics {
176    pub partition_input_count: LabelGuardedIntCounterVec,
177
178    // **Note**: for normal messages, the metric is the message's payload size.
179    // For messages from load generator, the metric is the size of stream chunk.
180    pub partition_input_bytes: LabelGuardedIntCounterVec,
181    /// Report latest message id
182    pub latest_message_id: LabelGuardedIntGaugeVec,
183    pub partition_eof_count: LabelGuardedIntCounterVec,
184    pub partition_eof_offset: LabelGuardedIntGaugeVec,
185    pub rdkafka_native_metric: Arc<RdKafkaStats>,
186
187    pub direct_cdc_event_lag_latency: LabelGuardedHistogramVec,
188
189    pub parquet_source_skip_row_count: LabelGuardedIntCounterVec,
190    pub file_source_input_row_count: LabelGuardedIntCounterVec,
191    pub file_source_dirty_split_count: LabelGuardedIntGaugeVec,
192    pub file_source_failed_split_count: LabelGuardedIntCounterVec,
193
194    // kinesis source
195    pub kinesis_throughput_exceeded_count: LabelGuardedIntCounterVec,
196    pub kinesis_timeout_count: LabelGuardedIntCounterVec,
197    pub kinesis_rebuild_shard_iter_count: LabelGuardedIntCounterVec,
198    pub kinesis_early_terminate_shard_count: LabelGuardedIntCounterVec,
199    pub kinesis_lag_latency_ms: LabelGuardedHistogramVec,
200
201    /// Total connector ack failures after checkpoint commit by bounded failure category.
202    connector_ack_failure_count: IntCounterVec,
203    /// Total successful connector acks after checkpoint commit.
204    connector_ack_success_count: IntCounterVec,
205}
206
207pub static GLOBAL_SOURCE_METRICS: LazyLock<SourceMetrics> =
208    LazyLock::new(|| SourceMetrics::new(&GLOBAL_METRICS_REGISTRY));
209
210impl SourceMetrics {
211    pub fn inc_connector_ack_failure_count(
212        &self,
213        source_name: &str,
214        connector_type: &'static str,
215        failure_type: ConnectorAckFailureType,
216    ) {
217        self.connector_ack_failure_count
218            .with_label_values(&[source_name, connector_type, failure_type.as_str()])
219            .inc();
220    }
221
222    pub fn inc_connector_ack_success_count(&self, source_name: &str, connector_type: &'static str) {
223        self.connector_ack_success_count
224            .with_label_values(&[source_name, connector_type])
225            .inc();
226    }
227
228    fn new(registry: &Registry) -> Self {
229        let partition_input_count = register_guarded_int_counter_vec_with_registry!(
230            "source_partition_input_count",
231            "Total number of rows that have been input from specific partition",
232            &[
233                "actor_id",
234                "source_id",
235                "partition",
236                "source_name",
237                "fragment_id"
238            ],
239            registry
240        )
241        .unwrap();
242        let partition_input_bytes = register_guarded_int_counter_vec_with_registry!(
243            "source_partition_input_bytes",
244            "Total bytes that have been input from specific partition",
245            &[
246                "actor_id",
247                "source_id",
248                "partition",
249                "source_name",
250                "fragment_id"
251            ],
252            registry
253        )
254        .unwrap();
255        let latest_message_id = register_guarded_int_gauge_vec_with_registry!(
256            "source_latest_message_id",
257            "Latest message id for a exec per partition",
258            &["source_id", "actor_id", "partition"],
259            registry,
260        )
261        .unwrap();
262        let partition_eof_count = register_guarded_int_counter_vec_with_registry!(
263            "source_partition_eof_count",
264            "Total number of EOF events received from specific partition",
265            &["source_id", "partition", "source_name", "fragment_id"],
266            registry
267        )
268        .unwrap();
269        let partition_eof_offset = register_guarded_int_gauge_vec_with_registry!(
270            "source_partition_eof_offset",
271            "Latest resolved EOF offset for specific partition",
272            &["source_id", "partition", "source_name", "fragment_id"],
273            registry
274        )
275        .unwrap();
276
277        let opts = histogram_opts!(
278            "source_cdc_event_lag_duration_milliseconds",
279            "source_cdc_lag_latency",
280            exponential_buckets(1.0, 2.0, 21).unwrap(), // max 1048s
281        );
282
283        let parquet_source_skip_row_count = register_guarded_int_counter_vec_with_registry!(
284            "parquet_source_skip_row_count",
285            "Total number of rows that have been set to null in parquet source",
286            &["actor_id", "source_id", "source_name", "fragment_id"],
287            registry
288        )
289        .unwrap();
290
291        let direct_cdc_event_lag_latency =
292            register_guarded_histogram_vec_with_registry!(opts, &["table_name"], registry).unwrap();
293
294        let rdkafka_native_metric = Arc::new(RdKafkaStats::new(registry.clone()));
295
296        let file_source_input_row_count = register_guarded_int_counter_vec_with_registry!(
297            "file_source_input_row_count",
298            "Total number of rows that have been read in file source",
299            &["source_id", "source_name", "actor_id", "fragment_id"],
300            registry
301        )
302        .unwrap();
303        let file_source_dirty_split_count = register_guarded_int_gauge_vec_with_registry!(
304            "file_source_dirty_split_count",
305            "Current number of dirty file splits in file source",
306            &["source_id", "source_name", "actor_id", "fragment_id"],
307            registry
308        )
309        .unwrap();
310        let file_source_failed_split_count = register_guarded_int_counter_vec_with_registry!(
311            "file_source_failed_split_count",
312            "Total number of file splits marked dirty in file source",
313            &["source_id", "source_name", "actor_id", "fragment_id"],
314            registry
315        )
316        .unwrap();
317
318        let kinesis_throughput_exceeded_count = register_guarded_int_counter_vec_with_registry!(
319            "kinesis_throughput_exceeded_count",
320            "Total number of times throughput exceeded in kinesis source",
321            &["source_id", "source_name", "fragment_id", "shard_id"],
322            registry
323        )
324        .unwrap();
325
326        let kinesis_timeout_count = register_guarded_int_counter_vec_with_registry!(
327            "kinesis_timeout_count",
328            "Total number of times timeout in kinesis source",
329            &["source_id", "source_name", "fragment_id", "shard_id"],
330            registry
331        )
332        .unwrap();
333
334        let kinesis_rebuild_shard_iter_count = register_guarded_int_counter_vec_with_registry!(
335            "kinesis_rebuild_shard_iter_count",
336            "Total number of times rebuild shard iter in kinesis source",
337            &["source_id", "source_name", "fragment_id", "shard_id"],
338            registry
339        )
340        .unwrap();
341
342        let kinesis_early_terminate_shard_count = register_guarded_int_counter_vec_with_registry!(
343            "kinesis_early_terminate_shard_count",
344            "Total number of times early terminate shard in kinesis source",
345            &["source_id", "source_name", "fragment_id", "shard_id"],
346            registry
347        )
348        .unwrap();
349
350        let kinesis_lag_latency_ms = register_guarded_histogram_vec_with_registry!(
351            "kinesis_lag_latency_ms",
352            "Lag latency in kinesis source",
353            &["source_id", "source_name", "fragment_id", "shard_id"],
354            registry
355        )
356        .unwrap();
357
358        let connector_ack_failure_count = register_int_counter_vec_with_registry!(
359            "source_connector_ack_failure_count",
360            "Total number of connector ack failures after checkpoint commit by bounded failure category",
361            &["source_name", "connector_type", "error_type"],
362            registry
363        )
364        .unwrap();
365        let connector_ack_success_count = register_int_counter_vec_with_registry!(
366            "source_connector_ack_success_count",
367            "Total number of successful connector acks after checkpoint commit",
368            &["source_name", "connector_type"],
369            registry
370        )
371        .unwrap();
372
373        SourceMetrics {
374            partition_input_count,
375            partition_input_bytes,
376            latest_message_id,
377            partition_eof_count,
378            partition_eof_offset,
379            rdkafka_native_metric,
380            direct_cdc_event_lag_latency,
381            parquet_source_skip_row_count,
382            file_source_input_row_count,
383            file_source_dirty_split_count,
384            file_source_failed_split_count,
385
386            kinesis_throughput_exceeded_count,
387            kinesis_timeout_count,
388            kinesis_rebuild_shard_iter_count,
389            kinesis_early_terminate_shard_count,
390            kinesis_lag_latency_ms,
391
392            connector_ack_failure_count,
393            connector_ack_success_count,
394        }
395    }
396}
397
398impl Default for SourceMetrics {
399    fn default() -> Self {
400        GLOBAL_SOURCE_METRICS.clone()
401    }
402}