Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion crates/charon-metrics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ pub mod names {
pub const SCANNER_BLOCKS_TOTAL: &str = "charon_scanner_blocks_total";
pub const SCANNER_POSITIONS: &str = "charon_scanner_positions";

// Listener — counts every `new_heads` arrival the moment the
// websocket subscription delivers it, before the pipeline runs.
// Distinct from `SCANNER_BLOCKS_TOTAL` (which advances per
// pipeline tick): if the pipeline stalls or the per-block work
// unit panics, the listener counter still climbs and the
// dashboard can distinguish "no blocks arriving" from "blocks
// arriving but pipeline wedged" (#328).
pub const LISTENER_BLOCKS_RECEIVED_TOTAL: &str = "charon_listener_blocks_received_total";

// Pipeline
pub const PIPELINE_BLOCK_DURATION_SECONDS: &str = "charon_pipeline_block_duration_seconds";

Expand Down Expand Up @@ -358,7 +367,11 @@ pub fn install(bind: SocketAddr) -> Result<Option<ExporterFuture>> {
fn describe_all() {
describe_counter!(
names::SCANNER_BLOCKS_TOTAL,
"Total blocks drained from chain listeners."
"Total blocks processed by the scanner pipeline (one increment per per-block tick)."
);
describe_counter!(
names::LISTENER_BLOCKS_RECEIVED_TOTAL,
"Total `new_heads` events delivered by the chain websocket. Climbs whether or not the pipeline ticks."
);
describe_gauge!(
names::SCANNER_POSITIONS,
Expand Down Expand Up @@ -443,6 +456,14 @@ pub fn record_block_scanned(chain: &str) {
counter!(names::SCANNER_BLOCKS_TOTAL, "chain" => chain.to_owned()).increment(1);
}

/// Increment the per-chain listener block-ingress counter (#328).
/// Bumped from the websocket `new_heads` handler before the pipeline
/// runs, so a flat listener counter unambiguously means "no blocks
/// arriving" rather than "pipeline stalled".
pub fn record_block_received(chain: &str) {
counter!(names::LISTENER_BLOCKS_RECEIVED_TOTAL, "chain" => chain.to_owned()).increment(1);
}

/// Set the gauge for one health bucket on one chain.
pub fn set_position_bucket(chain: &str, bucket: &str, count: u64) {
gauge!(names::SCANNER_POSITIONS, "chain" => chain.to_owned(), "bucket" => bucket.to_owned())
Expand Down
7 changes: 5 additions & 2 deletions crates/charon-scanner/src/listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,11 @@ impl BlockListener {
/// stalled consumer cannot stall the WebSocket drain loop; full channel
/// drops the event with a warning (back-pressure visible to ops).
fn publish(&mut self, number: u64, timestamp: u64, block_hash: B256, backfill: bool) {
metrics::counter!("charon_blocks_received_total", "chain" => self.name.clone())
.increment(1);
// Route through the typed helper so this counter shares the
// same name constant the dashboard and alert rules read; a
// raw string here used to drift away from `names::*` and
// never showed up on any panel (#328).
charon_metrics::record_block_received(&self.name);
debug!(
chain = %self.name,
block = number,
Expand Down
6 changes: 3 additions & 3 deletions deploy/grafana/charon.json
Original file line number Diff line number Diff line change
Expand Up @@ -447,15 +447,15 @@
"allValue": ".*",
"current": { "selected": true, "text": "All", "value": "$__all" },
"datasource": { "type": "prometheus", "uid": "${datasource}" },
"definition": "label_values(charon_scanner_blocks_total, chain)",
"description": "Chain label. Defaults to All (.*) so panels render data even before label_values is populated (fresh import or cold start).",
"definition": "label_values(charon_listener_blocks_received_total, chain)",
"description": "Chain label. Sourced from the listener counter (#328) — climbs the moment the websocket subscription delivers `new_heads`, so panels populate immediately on connect rather than waiting for the first pipeline tick.",
"hide": 0,
"includeAll": true,
"label": "Chain",
"multi": true,
"name": "chain",
"options": [],
"query": { "query": "label_values(charon_scanner_blocks_total, chain)", "refId": "StandardVariableQuery" },
"query": { "query": "label_values(charon_listener_blocks_received_total, chain)", "refId": "StandardVariableQuery" },
"refresh": 2,
"regex": "",
"skipUrlSync": false,
Expand Down
Loading