refactor(metrics): move metrics into a separate crates and reuse names in dashboard generation

This commit is contained in:
Daniil Polyakov 2026-07-28 02:00:11 +03:00
parent 462de8f11d
commit ea36fa2e93
17 changed files with 144 additions and 55 deletions

23
Cargo.lock generated
View File

@ -2297,6 +2297,8 @@ name = "dashboard_gen"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"json-pretty-compact", "json-pretty-compact",
"sequencer_core_metrics",
"sequencer_service_metrics",
"serde", "serde",
"serde_json", "serde_json",
] ]
@ -9503,16 +9505,15 @@ dependencies = [
"logos-blockchain-key-management-system-service", "logos-blockchain-key-management-system-service",
"logos-blockchain-zone-sdk", "logos-blockchain-zone-sdk",
"mempool", "mempool",
"metrics",
"num-bigint 0.4.6", "num-bigint 0.4.6",
"ping_core", "ping_core",
"programs", "programs",
"rand 0.8.6", "rand 0.8.6",
"risc0-zkvm", "risc0-zkvm",
"sequencer_core_metrics",
"serde", "serde",
"serde_json", "serde_json",
"storage", "storage",
"strum 0.28.0",
"system_accounts", "system_accounts",
"tempfile", "tempfile",
"test_programs", "test_programs",
@ -9524,6 +9525,15 @@ dependencies = [
"vault_core", "vault_core",
] ]
[[package]]
name = "sequencer_core_metrics"
version = "0.1.0"
dependencies = [
"common",
"metrics",
"strum 0.28.0",
]
[[package]] [[package]]
name = "sequencer_service" name = "sequencer_service"
version = "0.1.0" version = "0.1.0"
@ -9540,16 +9550,23 @@ dependencies = [
"lee", "lee",
"log", "log",
"mempool", "mempool",
"metrics",
"metrics-exporter-prometheus", "metrics-exporter-prometheus",
"programs", "programs",
"sequencer_core", "sequencer_core",
"sequencer_service_metrics",
"sequencer_service_protocol", "sequencer_service_protocol",
"sequencer_service_rpc", "sequencer_service_rpc",
"tokio", "tokio",
"tokio-util", "tokio-util",
] ]
[[package]]
name = "sequencer_service_metrics"
version = "0.1.0"
dependencies = [
"metrics",
]
[[package]] [[package]]
name = "sequencer_service_protocol" name = "sequencer_service_protocol"
version = "0.1.0" version = "0.1.0"

View File

@ -18,9 +18,11 @@ members = [
"lez/system_accounts", "lez/system_accounts",
"lez/chain_state", "lez/chain_state",
"lez/sequencer/core", "lez/sequencer/core",
"lez/sequencer/core/metrics",
"lez/sequencer/service", "lez/sequencer/service",
"lez/sequencer/service/protocol", "lez/sequencer/service/protocol",
"lez/sequencer/service/rpc", "lez/sequencer/service/rpc",
"lez/sequencer/service/metrics",
"lez/indexer/core", "lez/indexer/core",
"lez/indexer/service", "lez/indexer/service",
"lez/indexer/service/protocol", "lez/indexer/service/protocol",
@ -79,8 +81,10 @@ mempool = { path = "lez/mempool" }
storage = { path = "lez/storage" } storage = { path = "lez/storage" }
key_protocol = { path = "lee/key_protocol" } key_protocol = { path = "lee/key_protocol" }
sequencer_core = { path = "lez/sequencer/core" } sequencer_core = { path = "lez/sequencer/core" }
sequencer_core_metrics = { path = "lez/sequencer/core/metrics" }
sequencer_service_protocol = { path = "lez/sequencer/service/protocol" } sequencer_service_protocol = { path = "lez/sequencer/service/protocol" }
sequencer_service_rpc = { path = "lez/sequencer/service/rpc" } sequencer_service_rpc = { path = "lez/sequencer/service/rpc" }
sequencer_service_metrics = { path = "lez/sequencer/service/metrics" }
sequencer_service = { path = "lez/sequencer/service" } sequencer_service = { path = "lez/sequencer/service" }
indexer_core = { path = "lez/indexer/core" } indexer_core = { path = "lez/indexer/core" }
indexer_service = { path = "lez/indexer/service" } indexer_service = { path = "lez/indexer/service" }

View File

@ -12,6 +12,7 @@ lee.workspace = true
lee_core.workspace = true lee_core.workspace = true
chain_state.workspace = true chain_state.workspace = true
common.workspace = true common.workspace = true
sequencer_core_metrics = { workspace = true, features = ["writing"] }
storage.workspace = true storage.workspace = true
mempool.workspace = true mempool.workspace = true
logos-blockchain-zone-sdk.workspace = true logos-blockchain-zone-sdk.workspace = true
@ -34,7 +35,6 @@ humantime-serde.workspace = true
tempfile.workspace = true tempfile.workspace = true
chrono.workspace = true chrono.workspace = true
log.workspace = true log.workspace = true
metrics.workspace = true
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
tokio-util.workspace = true tokio-util.workspace = true
rand.workspace = true rand.workspace = true
@ -46,7 +46,6 @@ num-bigint.workspace = true
risc0-zkvm.workspace = true risc0-zkvm.workspace = true
futures.workspace = true futures.workspace = true
itertools.workspace = true itertools.workspace = true
strum.workspace = true
[features] [features]
default = [] default = []

View File

@ -0,0 +1,19 @@
[package]
name = "sequencer_core_metrics"
version = "0.1.0"
edition = "2024"
license = { workspace = true }
[lints]
workspace = true
[features]
default = []
# Enable metrics writing
writing = ["dep:common", "dep:metrics", "dep:strum"]
[dependencies]
common = { workspace = true, optional = true }
metrics = { workspace = true, optional = true }
strum = { workspace = true, optional = true }

View File

@ -0,0 +1,9 @@
//! This crate provides all metrics exposed by the sequencer core crate.
#[cfg(feature = "writing")]
pub use writing::*;
pub mod names;
#[cfg(feature = "writing")]
pub mod writing;

View File

@ -0,0 +1,6 @@
pub const BLOCK_CREATION_TIME: &str = "block_creation_time";
pub const BLOCK_COUNT: &str = "block_count";
pub const MEMPOOL_SIZE: &str = "mempool_size";
pub const MEMPOOL_TRANSACTION_APPLICATION_TIME: &str = "mempool_transaction_application_time";
pub const TRANSACTIONS_PER_BLOCK: &str = "transactions_per_block";
pub const FAILED_TRANSACTION_COUNT: &str = "failed_transaction_count";

View File

@ -1,5 +1,3 @@
//! This module provides all metrics exposed by the sequencer core crate.
#![expect( #![expect(
clippy::cast_precision_loss, clippy::cast_precision_loss,
clippy::as_conversions, clippy::as_conversions,
@ -11,15 +9,12 @@ use std::time::Duration;
use common::transaction::TxKind; use common::transaction::TxKind;
use metrics::{Counter, Unit, counter, gauge, histogram}; use metrics::{Counter, Unit, counter, gauge, histogram};
use crate::TransactionOrigin; use crate::names;
mod names { #[derive(Clone, Copy, strum::IntoStaticStr)]
pub const BLOCK_CREATION_TIME: &str = "block_creation_time"; pub enum TransactionOrigin {
pub const BLOCK_COUNT: &str = "block_count"; User,
pub const MEMPOOL_SIZE: &str = "mempool_size"; Sequencer,
pub const MEMPOOL_TRANSACTION_APPLICATION_TIME: &str = "mempool_transaction_application_time";
pub const TRANSACTIONS_PER_BLOCK: &str = "transactions_per_block";
pub const FAILED_TRANSACTION_COUNT: &str = "failed_transaction_count";
} }
pub fn record_block_creation_time(duration: Duration) { pub fn record_block_creation_time(duration: Duration) {

View File

@ -50,7 +50,6 @@ pub mod block_publisher;
pub mod block_store; pub mod block_store;
pub mod config; pub mod config;
pub mod cross_zone_watcher; pub mod cross_zone_watcher;
mod metrics;
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
pub mod mock; pub mod mock;
@ -73,7 +72,7 @@ const RETIRE_DISPATCH_AFTER_FAILURES: u32 = 3;
const MAX_DISPATCHES_PER_BLOCK: usize = 16; const MAX_DISPATCHES_PER_BLOCK: usize = 16;
/// The origin of a transaction. /// The origin of a transaction.
#[derive(Clone, Copy, strum::IntoStaticStr)] #[derive(Clone, Copy)]
pub enum TransactionOrigin { pub enum TransactionOrigin {
/// Basic transactions submitted by users via RPC. /// Basic transactions submitted by users via RPC.
User, User,
@ -81,6 +80,15 @@ pub enum TransactionOrigin {
Sequencer, Sequencer,
} }
impl From<TransactionOrigin> for sequencer_core_metrics::TransactionOrigin {
fn from(origin: TransactionOrigin) -> Self {
match origin {
TransactionOrigin::User => Self::User,
TransactionOrigin::Sequencer => Self::Sequencer,
}
}
}
#[derive(Clone, Debug, BorshDeserialize)] #[derive(Clone, Debug, BorshDeserialize)]
struct DepositMetadata { struct DepositMetadata {
recipient_id: lee::AccountId, recipient_id: lee::AccountId,
@ -300,7 +308,7 @@ impl<BP: BlockPublisherTrait> SequencerCore<BP> {
watchers, watchers,
}; };
crate::metrics::set_block_count(sequencer_core.chain_height()); sequencer_core_metrics::set_block_count(sequencer_core.chain_height());
(sequencer_core, mempool_handle) (sequencer_core, mempool_handle)
} }
@ -805,7 +813,7 @@ impl<BP: BlockPublisherTrait> SequencerCore<BP> {
let clock_tx = clock_invocation(new_block_timestamp); let clock_tx = clock_invocation(new_block_timestamp);
let clock_lee_tx = LeeTransaction::Public(clock_tx.clone()); let clock_lee_tx = LeeTransaction::Public(clock_tx.clone());
crate::metrics::record_mempool_size(self.mempool.len()); sequencer_core_metrics::record_mempool_size(self.mempool.len());
// Everything drained from the store first, then user work. `from_store` // Everything drained from the store first, then user work. `from_store`
// is not the same as a `Sequencer` origin: it says the transaction has a // is not the same as a `Sequencer` origin: it says the transaction has a
// record behind it and so needs no requeue, where the origin only says // record behind it and so needs no requeue, where the origin only says
@ -885,8 +893,8 @@ impl<BP: BlockPublisherTrait> SequencerCore<BP> {
new_block_timestamp, new_block_timestamp,
&mut withdrawals, &mut withdrawals,
); );
crate::metrics::record_mempool_transaction_application_time( sequencer_core_metrics::record_mempool_transaction_application_time(
origin, origin.into(),
tx.kind(), tx.kind(),
before_tx_apply.elapsed(), before_tx_apply.elapsed(),
); );
@ -894,7 +902,7 @@ impl<BP: BlockPublisherTrait> SequencerCore<BP> {
if applied { if applied {
valid_transactions.push(tx); valid_transactions.push(tx);
} else { } else {
crate::metrics::increment_failed_transaction_count(); sequencer_core_metrics::increment_failed_transaction_count();
// A failed transaction is simply left out of the block, except a // A failed transaction is simply left out of the block, except a
// dispatch: that one is re-fed from the store every turn, so one // dispatch: that one is re-fed from the store every turn, so one
// that can never execute would fail on every block for ever. // that can never execute would fail on every block for ever.
@ -910,7 +918,7 @@ impl<BP: BlockPublisherTrait> SequencerCore<BP> {
.transition_from_public_transaction(&clock_tx, new_block_height, new_block_timestamp) .transition_from_public_transaction(&clock_tx, new_block_height, new_block_timestamp)
.context("Clock transaction failed. Aborting block production.")?; .context("Clock transaction failed. Aborting block production.")?;
valid_transactions.push(clock_lee_tx); valid_transactions.push(clock_lee_tx);
crate::metrics::record_transactions_per_block(valid_transactions.len()); sequencer_core_metrics::record_transactions_per_block(valid_transactions.len());
let hashable_data = HashableBlockData { let hashable_data = HashableBlockData {
block_id: new_block_height, block_id: new_block_height,
@ -929,8 +937,8 @@ impl<BP: BlockPublisherTrait> SequencerCore<BP> {
now.elapsed().as_secs() now.elapsed().as_secs()
); );
crate::metrics::record_block_creation_time(now.elapsed()); sequencer_core_metrics::record_block_creation_time(now.elapsed());
crate::metrics::increment_block_count(); sequencer_core_metrics::increment_block_count();
Ok(BlockWithMeta { block, withdrawals }) Ok(BlockWithMeta { block, withdrawals })
} }

View File

@ -15,6 +15,7 @@ mempool.workspace = true
sequencer_core = { workspace = true, features = ["testnet"] } sequencer_core = { workspace = true, features = ["testnet"] }
sequencer_service_protocol.workspace = true sequencer_service_protocol.workspace = true
sequencer_service_rpc = { workspace = true, features = ["server"] } sequencer_service_rpc = { workspace = true, features = ["server"] }
sequencer_service_metrics = { workspace = true, features = ["writing"] }
programs.workspace = true programs.workspace = true
clap = { workspace = true, features = ["derive", "env"] } clap = { workspace = true, features = ["derive", "env"] }
@ -22,7 +23,6 @@ anyhow.workspace = true
env_logger.workspace = true env_logger.workspace = true
hex.workspace = true hex.workspace = true
log.workspace = true log.workspace = true
metrics.workspace = true
metrics-exporter-prometheus.workspace = true metrics-exporter-prometheus.workspace = true
tokio.workspace = true tokio.workspace = true
tokio-util.workspace = true tokio-util.workspace = true

View File

@ -0,0 +1,16 @@
[package]
name = "sequencer_service_metrics"
version = "0.1.0"
edition = "2024"
license = { workspace = true }
[lints]
workspace = true
[features]
default = []
# Enable metrics writing
writing = ["dep:metrics"]
[dependencies]
metrics = { workspace = true, optional = true }

View File

@ -0,0 +1,9 @@
//! This crate provides all metrics exposed by the sequencer core crate.
#[cfg(feature = "writing")]
pub use writing::*;
pub mod names;
#[cfg(feature = "writing")]
pub mod writing;

View File

@ -0,0 +1 @@
pub const SUBMITTED_TRANSACTION_COUNT: &str = "submitted_transaction_count";

View File

@ -1,10 +1,6 @@
//! This module provides all metrics exposed by the sequencer service crate.
use metrics::{Unit, counter}; use metrics::{Unit, counter};
mod names { use crate::names;
pub const SUBMITTED_TRANSACTION_COUNT: &str = "submitted_transaction_count";
}
pub fn increment_submitted_transaction_count() { pub fn increment_submitted_transaction_count() {
counter!( counter!(

View File

@ -21,7 +21,6 @@ use sequencer_service_rpc::RpcServer as _;
use tokio::{sync::Mutex, task::JoinHandle}; use tokio::{sync::Mutex, task::JoinHandle};
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
mod metrics;
pub mod service; pub mod service;
const REQUEST_BODY_MAX_SIZE: ByteSize = ByteSize::mib(10); const REQUEST_BODY_MAX_SIZE: ByteSize = ByteSize::mib(10);

View File

@ -93,7 +93,7 @@ impl<BC: BlockPublisherTrait + Send + Sync + 'static> sequencer_service_rpc::Rpc
.await .await
.expect("Mempool is closed, this is a bug"); .expect("Mempool is closed, this is a bug");
crate::metrics::increment_submitted_transaction_count(); sequencer_service_metrics::increment_submitted_transaction_count();
Ok(tx_hash) Ok(tx_hash)
} }

View File

@ -8,6 +8,9 @@ license = { workspace = true }
workspace = true workspace = true
[dependencies] [dependencies]
sequencer_core_metrics.workspace = true
sequencer_service_metrics.workspace = true
serde = { workspace = true, features = ["derive", "alloc"] } serde = { workspace = true, features = ["derive", "alloc"] }
serde_json.workspace = true serde_json.workspace = true
json-pretty-compact = "0.1.2" json-pretty-compact = "0.1.2"

View File

@ -16,18 +16,6 @@ use dashboard_gen::{
use json_pretty_compact::PrettyCompactFormatter; use json_pretty_compact::PrettyCompactFormatter;
use serde::Serialize as _; use serde::Serialize as _;
// Rendered Prometheus metric names. In a real setup these would be re-exported
// from `sequencer_core::metrics` (base name + unit/counter suffix rule) so that
// a rename in the recorder is a compile error here — that is the whole point of
// generating dashboards from the same codebase that emits the metrics.
const BLOCK_COUNT: &str = "block_count_total";
const BLOCK_CREATION_TIME: &str = "block_creation_time_seconds";
const TX_APPLY_TIME: &str = "mempool_transaction_application_time_seconds";
const MEMPOOL_SIZE: &str = "mempool_size";
const TX_PER_BLOCK: &str = "transactions_per_block";
const SUBMITTED_TX: &str = "submitted_transaction_count_total";
const FAILED_TX: &str = "failed_transaction_count_total";
const PERCENTILES: &[u32] = &[50, 90, 95, 99]; const PERCENTILES: &[u32] = &[50, 90, 95, 99];
fn sequencer_dashboard() -> Dashboard { fn sequencer_dashboard() -> Dashboard {
@ -41,11 +29,16 @@ fn sequencer_dashboard() -> Dashboard {
.unit("short") .unit("short")
.decimals(0) .decimals(0)
.color(Color::fixed("blue")) .color(Color::fixed("blue"))
.target(Target::new(BLOCK_COUNT).legend("height")), .target(
Target::new(sequencer_core_metrics::names::BLOCK_COUNT).legend("height"),
),
Panel::timeseries("Block production rate") Panel::timeseries("Block production rate")
.width(18) .width(18)
.unit("short") .unit("short")
.target(rate_per_min(BLOCK_COUNT, "blocks/min")), .target(rate_per_min(
sequencer_core_metrics::names::BLOCK_COUNT,
"blocks/min",
)),
], ],
) )
.row( .row(
@ -53,8 +46,11 @@ fn sequencer_dashboard() -> Dashboard {
[Panel::timeseries("Block creation time") [Panel::timeseries("Block creation time")
.width(24) .width(24)
.unit("s") .unit("s")
.targets(percentiles(BLOCK_CREATION_TIME, PERCENTILES)) .targets(percentiles(
.target(avg(BLOCK_CREATION_TIME)) sequencer_core_metrics::names::BLOCK_CREATION_TIME,
PERCENTILES,
))
.target(avg(sequencer_core_metrics::names::BLOCK_CREATION_TIME))
.with_override( .with_override(
FieldOverride::by_name("avg") FieldOverride::by_name("avg")
.dashed_line() .dashed_line()
@ -68,7 +64,7 @@ fn sequencer_dashboard() -> Dashboard {
.width(12) .width(12)
.unit("s") .unit("s")
.targets(percentiles_labeled( .targets(percentiles_labeled(
TX_APPLY_TIME, sequencer_core_metrics::names::MEMPOOL_TRANSACTION_APPLICATION_TIME,
PERCENTILES, PERCENTILES,
" · {{kind}} · {{origin}}", " · {{kind}} · {{origin}}",
)), )),
@ -76,7 +72,10 @@ fn sequencer_dashboard() -> Dashboard {
.width(12) .width(12)
.unit("short") .unit("short")
.span_nulls() .span_nulls()
.target(Target::new(MEMPOOL_SIZE).legend("mempool size")), .target(
Target::new(sequencer_core_metrics::names::MEMPOOL_SIZE)
.legend("mempool size"),
),
], ],
) )
.row( .row(
@ -85,8 +84,11 @@ fn sequencer_dashboard() -> Dashboard {
Panel::timeseries("Transactions per block") Panel::timeseries("Transactions per block")
.width(12) .width(12)
.unit("short") .unit("short")
.targets(percentiles(TX_PER_BLOCK, PERCENTILES)) .targets(percentiles(
.target(avg(TX_PER_BLOCK)) sequencer_core_metrics::names::TRANSACTIONS_PER_BLOCK,
PERCENTILES,
))
.target(avg(sequencer_core_metrics::names::TRANSACTIONS_PER_BLOCK))
.with_override( .with_override(
FieldOverride::by_name("avg") FieldOverride::by_name("avg")
.dashed_line() .dashed_line()
@ -95,8 +97,14 @@ fn sequencer_dashboard() -> Dashboard {
Panel::timeseries("Transaction throughput (per minute)") Panel::timeseries("Transaction throughput (per minute)")
.width(12) .width(12)
.unit("short") .unit("short")
.target(rate_per_min(SUBMITTED_TX, "submitted")) .target(rate_per_min(
.target(rate_per_min(FAILED_TX, "failed")) sequencer_service_metrics::names::SUBMITTED_TRANSACTION_COUNT,
"submitted",
))
.target(rate_per_min(
sequencer_core_metrics::names::FAILED_TRANSACTION_COUNT,
"failed",
))
.with_override(FieldOverride::by_name("failed").color(Color::fixed("red"))) .with_override(FieldOverride::by_name("failed").color(Color::fixed("red")))
.with_override( .with_override(
FieldOverride::by_name("submitted").color(Color::fixed("green")), FieldOverride::by_name("submitted").color(Color::fixed("green")),