diff --git a/rust/main/hyperlane-base/src/contract_sync/metrics.rs b/rust/main/hyperlane-base/src/contract_sync/metrics.rs index 54e3c7a2cb..7c906b28f1 100644 --- a/rust/main/hyperlane-base/src/contract_sync/metrics.rs +++ b/rust/main/hyperlane-base/src/contract_sync/metrics.rs @@ -26,6 +26,9 @@ pub struct ContractSyncMetrics { /// See `last_known_message_nonce` in CoreMetrics. pub message_nonce: IntGaugeVec, + /// Contract sync liveness metric + pub liveness_metrics: IntGaugeVec, + /// Metrics for SequenceAware and RateLimited cursors. pub cursor_metrics: Arc, } @@ -49,6 +52,14 @@ impl ContractSyncMetrics { ) .expect("failed to register stored_events metric"); + let liveness_metrics = metrics + .new_int_gauge( + "contract_sync_liveness", + "Last timestamp observed by contract sync", + &["data_type", "chain"], + ) + .expect("failed to register liveness metric"); + let message_nonce = metrics.last_known_message_nonce(); let cursor_metrics = Arc::new(CursorMetrics::new(metrics)); @@ -56,6 +67,7 @@ impl ContractSyncMetrics { indexed_height, stored_events, message_nonce, + liveness_metrics, cursor_metrics, } } diff --git a/rust/main/hyperlane-base/src/contract_sync/mod.rs b/rust/main/hyperlane-base/src/contract_sync/mod.rs index 26f6c9d673..d79b9a2616 100644 --- a/rust/main/hyperlane-base/src/contract_sync/mod.rs +++ b/rust/main/hyperlane-base/src/contract_sync/mod.rs @@ -1,5 +1,6 @@ use std::{ collections::HashSet, fmt::Debug, hash::Hash, marker::PhantomData, sync::Arc, time::Duration, + time::UNIX_EPOCH, }; use axum::async_trait; @@ -98,8 +99,13 @@ where .metrics .stored_events .with_label_values(&[label, chain_name]); + let liveness_metric = self + .metrics + .liveness_metrics + .with_label_values(&[label, chain_name]); loop { + Self::update_liveness_metric(&liveness_metric); if let Some(rx) = opts.tx_id_receiver.as_mut() { self.fetch_logs_from_receiver(rx, &stored_logs_metric).await; } @@ -120,6 +126,15 @@ where info!(chain = chain_name, label, "contract sync loop exit"); } + fn update_liveness_metric(liveness_metric: &GenericGauge) { + liveness_metric.set( + UNIX_EPOCH + .elapsed() + .map(|d| d.as_secs() as i64) + .unwrap_or(0), + ); + } + #[instrument(fields(domain=self.domain().name()), skip(self, recv, stored_logs_metric))] async fn fetch_logs_from_receiver( &self,