From ada0e9e5d0abd4b62b2c0ffea4d06f068cc6379a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Chabowski?= <88321181+rafal-ch@users.noreply.github.com> Date: Tue, 15 Oct 2024 00:24:36 +0200 Subject: [PATCH] Add more metrics (#2310) Closes https://github.com/FuelLabs/fuel-core/issues/807 ## Description This PR adds a couple of additional metrics (block importer and p2p) and also contains slight refactor of how we initialize bucket sized for histogram based metrics. The `metrics` command-line parameter has been replaced with `disable-metrics`. Metrics are now enabled by default, with the option to disable them entirely or on a per-module basis. This change is _breaking_ for all dependencies that use CLI to setup the `fuel-core-client` ``` --disable-metrics Disables all metrics, or specify a comma-separated list of modules to disable metrics for specific ones. Available options: importer, p2p, producer, txpool, graphql [env: METRICS=] [default: ] ``` Startup logs also show the metrics config: ``` 2024-10-14T20:17:37.536840Z INFO fuel_core_bin::cli::run: 308: Metrics config: Disable modules: txpool ``` ## Checklist - [X] Breaking changes are clearly marked as such in the PR description and changelog - [X] New behavior is reflected in tests ### Before requesting review - [X] I have reviewed the code myself --------- Co-authored-by: acerone85 Co-authored-by: rymnc <43716372+rymnc@users.noreply.github.com> Co-authored-by: Green Baneling --- CHANGELOG.md | 4 + Cargo.lock | 5 + bin/fuel-core/Cargo.toml | 2 + bin/fuel-core/src/cli/run.rs | 112 ++++++++++++++++-- .../src/service/adapters/block_importer.rs | 5 +- crates/fuel-core/src/service/config.rs | 2 +- crates/metrics/Cargo.toml | 3 + crates/metrics/src/buckets.rs | 65 ++++++++++ crates/metrics/src/config.rs | 51 ++++++++ crates/metrics/src/futures/future_tracker.rs | 4 +- crates/metrics/src/graphql_metrics.rs | 7 +- crates/metrics/src/importer.rs | 44 ++++++- crates/metrics/src/lib.rs | 12 +- crates/services/importer/src/config.rs | 6 +- crates/services/importer/src/importer.rs | 75 +++++++++--- crates/services/p2p/Cargo.toml | 1 + crates/services/p2p/src/p2p_service.rs | 89 +++++++++++--- crates/types/src/services/executor.rs | 8 ++ 18 files changed, 437 insertions(+), 58 deletions(-) create mode 100644 crates/metrics/src/buckets.rs create mode 100644 crates/metrics/src/config.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d7afae994a..f22fef5263f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed - [2334](https://github.com/FuelLabs/fuel-core/pull/2334): Prepare the GraphQL service for the switching to `async` methods. +- [2310](https://github.com/FuelLabs/fuel-core/pull/2310): New metrics: "The gas prices used in a block" (`importer_gas_price_for_block`), "The total gas used in a block" (`importer_gas_per_block`), "The total fee (gwei) paid by transactions in a block" (`importer_fee_per_block_gwei`), "The total number of transactions in a block" (`importer_transactions_per_block`), P2P metrics for swarm and protocol. + +#### Breaking +- [2310](https://github.com/FuelLabs/fuel-core/pull/2310): The `metrics` command-line parameter has been replaced with `disable-metrics`. Metrics are now enabled by default, with the option to disable them entirely or on a per-module basis. - [2341](https://github.com/FuelLabs/fuel-core/pull/2341): Updated all pagination queries to work with the async stream instead of the sync iterator. - [2340](https://github.com/FuelLabs/fuel-core/pull/2340): Avoid long heavy tasks in the GraphQL service by splitting work into batches. - [2350](https://github.com/FuelLabs/fuel-core/pull/2350): Limited the number of threads used by the GraphQL service. diff --git a/Cargo.lock b/Cargo.lock index 0df5ae80960..91b6d165854 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3284,6 +3284,7 @@ dependencies = [ "fuel-core", "fuel-core-chain-config", "fuel-core-compression", + "fuel-core-metrics", "fuel-core-poa", "fuel-core-storage", "fuel-core-types 0.39.0", @@ -3515,10 +3516,14 @@ dependencies = [ name = "fuel-core-metrics" version = "0.39.0" dependencies = [ + "derive_more", + "once_cell", "parking_lot", "pin-project-lite", "prometheus-client", "regex", + "strum 0.25.0", + "strum_macros 0.25.3", "tokio", "tracing", ] diff --git a/bin/fuel-core/Cargo.toml b/bin/fuel-core/Cargo.toml index aa111e7637c..76bbb6d0367 100644 --- a/bin/fuel-core/Cargo.toml +++ b/bin/fuel-core/Cargo.toml @@ -28,6 +28,7 @@ dotenvy = { version = "0.15", optional = true } fuel-core = { workspace = true, features = ["wasm-executor"] } fuel-core-chain-config = { workspace = true } fuel-core-compression = { workspace = true } +fuel-core-metrics = { workspace = true } fuel-core-poa = { workspace = true } fuel-core-types = { workspace = true, features = ["std"] } hex = { workspace = true } @@ -53,6 +54,7 @@ itertools = { workspace = true } pretty_assertions = { workspace = true } rand = { workspace = true } serde = { workspace = true } +strum = { workspace = true } tempfile = { workspace = true } test-case = { workspace = true } diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index 7733e99b9a7..6dc0349f4f8 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -51,6 +51,10 @@ use fuel_core_chain_config::{ SnapshotMetadata, SnapshotReader, }; +use fuel_core_metrics::config::{ + DisableConfig, + Module, +}; use fuel_core_poa::signer::SignMode; use fuel_core_types::blockchain::header::StateTransitionBytecodeVersion; use pyroscope::{ @@ -236,8 +240,8 @@ pub struct Command { #[cfg(feature = "p2p")] pub sync_args: p2p::SyncArgs, - #[arg(long = "metrics", env)] - pub metrics: bool, + #[arg(long = "disable-metrics", value_delimiter = ',', help = fuel_core_metrics::config::help_string(), env)] + pub disabled_metrics: Vec, #[clap(long = "verify-max-da-lag", default_value = "10", env)] pub max_da_lag: u64, @@ -294,7 +298,7 @@ impl Command { p2p_args, #[cfg(feature = "p2p")] sync_args, - metrics, + disabled_metrics: metrics, max_da_lag, max_wait_time, tx_pool, @@ -305,6 +309,14 @@ impl Command { profiling: _, } = self; + let enabled_metrics = metrics.list_of_enabled(); + + if !enabled_metrics.is_empty() { + info!("`{:?}` metrics are enabled", enabled_metrics); + } else { + info!("All metrics are disabled"); + } + let addr = net::SocketAddr::new(graphql.ip, graphql.port); let snapshot_reader = match snapshot.as_ref() { @@ -320,7 +332,10 @@ impl Command { let relayer_cfg = relayer_args.into_config(); #[cfg(feature = "p2p")] - let p2p_cfg = p2p_args.into_config(chain_config.chain_name.clone(), metrics)?; + let p2p_cfg = p2p_args.into_config( + chain_config.chain_name.clone(), + metrics.is_enabled(Module::P2P), + )?; let trigger: Trigger = poa_trigger.into(); @@ -428,8 +443,9 @@ impl Command { state_rewind_policy, }; - let block_importer = - fuel_core::service::config::fuel_core_importer::Config::new(); + let block_importer = fuel_core::service::config::fuel_core_importer::Config::new( + metrics.is_enabled(Module::Importer), + ); let da_compression = match da_compression { Some(retention) => { @@ -549,7 +565,7 @@ impl Command { }, block_producer: ProducerConfig { coinbase_recipient, - metrics, + metrics: metrics.is_enabled(Module::Producer), }, starting_gas_price, gas_price_change_percent, @@ -659,3 +675,85 @@ fn start_pyroscope_agent( }) .transpose() } + +#[cfg(test)] +#[allow(non_snake_case)] +#[allow(clippy::bool_assert_comparison)] +mod tests { + use super::*; + use strum::IntoEnumIterator; + + fn parse_command(args: &[&str]) -> anyhow::Result { + Ok(Command::try_parse_from([""].iter().chain(args))?) + } + + #[test] + fn parse_disabled_metrics__no_value_enables_everything() { + // Given + let args = []; + + // When + let command = parse_command(&args).unwrap(); + + // Then + let config = command.disabled_metrics; + Module::iter().for_each(|module| { + assert_eq!(config.is_enabled(module), true); + }); + } + + #[test] + fn parse_disabled_metrics__all() { + // Given + let args = ["--disable-metrics", "all"]; + + // When + let command = parse_command(&args).unwrap(); + + // Then + let config = command.disabled_metrics; + Module::iter().for_each(|module| { + assert_eq!(config.is_enabled(module), false); + }); + } + + #[test] + fn parse_disabled_metrics__mixed_args() { + // Given + let args = [ + "--disable-metrics", + "txpool,importer", + "--disable-metrics", + "graphql", + ]; + + // When + let command = parse_command(&args).unwrap(); + + // Then + let config = command.disabled_metrics; + assert_eq!(config.is_enabled(Module::TxPool), false); + assert_eq!(config.is_enabled(Module::Importer), false); + assert_eq!(config.is_enabled(Module::GraphQL), false); + assert_eq!(config.is_enabled(Module::P2P), true); + assert_eq!(config.is_enabled(Module::Producer), true); + } + + #[test] + fn parse_disabled_metrics__bad_values() { + // Given + let args = ["--disable-metrics", "txpool,alpha,bravo"]; + + // When + let command = parse_command(&args); + + // Then + let err = command.expect_err("should fail to parse"); + assert_eq!( + err.to_string(), + "error: invalid value 'alpha' for \ + '--disable-metrics ': Matching variant not found\ + \n\nFor more information, try '--help'.\n" + ); + } +} diff --git a/crates/fuel-core/src/service/adapters/block_importer.rs b/crates/fuel-core/src/service/adapters/block_importer.rs index 62006c7847e..5b8a06e7f1e 100644 --- a/crates/fuel-core/src/service/adapters/block_importer.rs +++ b/crates/fuel-core/src/service/adapters/block_importer.rs @@ -62,8 +62,11 @@ impl BlockImporterAdapter { executor: ExecutorAdapter, verifier: VerifierAdapter, ) -> Self { + let metrics = config.metrics; let importer = Importer::new(chain_id, config, database, executor, verifier); - importer.init_metrics(); + if metrics { + importer.init_metrics(); + } Self { block_importer: Arc::new(importer), } diff --git a/crates/fuel-core/src/service/config.rs b/crates/fuel-core/src/service/config.rs index 1e242c75e03..2dd6602a979 100644 --- a/crates/fuel-core/src/service/config.rs +++ b/crates/fuel-core/src/service/config.rs @@ -104,7 +104,7 @@ impl Config { #[cfg(feature = "test-helpers")] pub fn local_node_with_reader(snapshot_reader: SnapshotReader) -> Self { - let block_importer = fuel_core_importer::Config::new(); + let block_importer = fuel_core_importer::Config::new(false); let latest_block = snapshot_reader.last_block_config(); // In tests, we always want to use the native executor as a default configuration. let native_executor_version = latest_block diff --git a/crates/metrics/Cargo.toml b/crates/metrics/Cargo.toml index 7851071b268..447c48c4ef9 100644 --- a/crates/metrics/Cargo.toml +++ b/crates/metrics/Cargo.toml @@ -11,10 +11,13 @@ repository = { workspace = true } description = "Fuel metrics" [dependencies] +once_cell = { workspace = true } parking_lot = { workspace = true } pin-project-lite = { workspace = true } prometheus-client = { workspace = true } regex = "1" +strum = { workspace = true } +strum_macros = { workspace = true } tracing = { workspace = true } [dev-dependencies] diff --git a/crates/metrics/src/buckets.rs b/crates/metrics/src/buckets.rs new file mode 100644 index 00000000000..0b51e1b70c0 --- /dev/null +++ b/crates/metrics/src/buckets.rs @@ -0,0 +1,65 @@ +use std::{ + collections::HashMap, + sync::OnceLock, +}; +#[cfg(test)] +use strum_macros::EnumIter; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(test, derive(EnumIter))] +pub(crate) enum Buckets { + Timing, +} +static BUCKETS: OnceLock>> = OnceLock::new(); +pub(crate) fn buckets(b: Buckets) -> impl Iterator { + BUCKETS.get_or_init(initialize_buckets)[&b].iter().copied() +} + +#[rustfmt::skip] +fn initialize_buckets() -> HashMap> { + [ + ( + Buckets::Timing, + vec![ + 0.005, + 0.010, + 0.025, + 0.050, + 0.100, + 0.250, + 0.500, + 1.000, + 2.500, + 5.000, + 10.000, + ], + ), + ] + .into_iter() + .collect() +} + +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::buckets::Buckets; + + use super::initialize_buckets; + + #[test] + fn buckets_are_defined_for_every_variant() { + let actual_buckets = initialize_buckets(); + let actual_buckets = actual_buckets.keys().collect::>(); + + let required_buckets: Vec<_> = Buckets::iter().collect(); + + assert_eq!(required_buckets.len(), actual_buckets.len()); + + let all_buckets_defined = required_buckets + .iter() + .all(|required_bucket| actual_buckets.contains(&required_bucket)); + + assert!(all_buckets_defined) + } +} diff --git a/crates/metrics/src/config.rs b/crates/metrics/src/config.rs new file mode 100644 index 00000000000..77c7fd297ff --- /dev/null +++ b/crates/metrics/src/config.rs @@ -0,0 +1,51 @@ +use once_cell::sync::Lazy; +use strum::IntoEnumIterator; +use strum_macros::{ + Display, + EnumIter, + EnumString, +}; + +#[derive(Debug, Display, Clone, Copy, PartialEq, EnumString, EnumIter)] +#[strum(serialize_all = "lowercase")] +pub enum Module { + All, + Importer, + P2P, + Producer, + TxPool, /* TODO[RC]: Not used. Add support in https://github.com/FuelLabs/fuel-core/pull/2321 */ + GraphQL, // TODO[RC]: Not used... yet. +} + +/// Configuration for disabling metrics. +pub trait DisableConfig { + /// Returns `true` if the given module is enabled. + fn is_enabled(&self, module: Module) -> bool; + + /// Returns the list of enabled modules. + fn list_of_enabled(&self) -> Vec; +} + +impl DisableConfig for Vec { + fn is_enabled(&self, module: Module) -> bool { + !self.contains(&module) && !self.contains(&Module::All) + } + + fn list_of_enabled(&self) -> Vec { + Module::iter() + .filter(|module| self.is_enabled(*module) && *module != Module::All) + .collect() + } +} + +static HELP_STRING: Lazy = Lazy::new(|| { + let all_modules: Vec<_> = Module::iter().map(|module| module.to_string()).collect(); + format!( + "Comma-separated list of modules or 'all' to disable all metrics. Available options: {}, all", + all_modules.join(", ") + ) +}); + +pub fn help_string() -> &'static str { + &HELP_STRING +} diff --git a/crates/metrics/src/futures/future_tracker.rs b/crates/metrics/src/futures/future_tracker.rs index 26f00d1b59c..a5e73aa864a 100644 --- a/crates/metrics/src/futures/future_tracker.rs +++ b/crates/metrics/src/futures/future_tracker.rs @@ -150,7 +150,9 @@ impl Future for FutureTracker { #[cfg(test)] mod tests { - use super::*; + use std::time::Duration; + + use crate::futures::future_tracker::FutureTracker; #[tokio::test] async fn empty_future() { diff --git a/crates/metrics/src/graphql_metrics.rs b/crates/metrics/src/graphql_metrics.rs index e87687aa67e..383d649109a 100644 --- a/crates/metrics/src/graphql_metrics.rs +++ b/crates/metrics/src/graphql_metrics.rs @@ -1,6 +1,9 @@ use crate::{ + buckets::{ + buckets, + Buckets, + }, global_registry, - timing_buckets, }; use prometheus_client::{ encoding::EncodeLabelSet, @@ -30,7 +33,7 @@ impl GraphqlMetrics { let tx_count_gauge = Gauge::default(); let queries_complexity = Histogram::new(buckets_complexity()); let requests = Family::::new_with_constructor(|| { - Histogram::new(timing_buckets().iter().cloned()) + Histogram::new(buckets(Buckets::Timing)) }); let mut registry = global_registry().registry.lock(); registry.register("graphql_request_duration_seconds", "", requests.clone()); diff --git a/crates/metrics/src/importer.rs b/crates/metrics/src/importer.rs index 3f0bb4caec0..0cf460edc3a 100644 --- a/crates/metrics/src/importer.rs +++ b/crates/metrics/src/importer.rs @@ -1,6 +1,9 @@ use crate::{ + buckets::{ + buckets, + Buckets, + }, global_registry, - timing_buckets, }; use prometheus_client::metrics::{ gauge::Gauge, @@ -15,14 +18,21 @@ pub struct ImporterMetrics { pub block_height: Gauge, pub latest_block_import_timestamp: Gauge, pub execute_and_commit_duration: Histogram, + pub gas_per_block: Gauge, + pub fee_per_block: Gauge, + pub transactions_per_block: Gauge, + pub gas_price: Gauge, } impl Default for ImporterMetrics { fn default() -> Self { let block_height_gauge = Gauge::default(); let latest_block_import_ms = Gauge::default(); - let execute_and_commit_duration = - Histogram::new(timing_buckets().iter().cloned()); + let execute_and_commit_duration = Histogram::new(buckets(Buckets::Timing)); + let gas_per_block = Gauge::default(); + let fee_per_block = Gauge::default(); + let transactions_per_block = Gauge::default(); + let gas_price = Gauge::default(); let mut registry = global_registry().registry.lock(); registry.register( @@ -43,10 +53,38 @@ impl Default for ImporterMetrics { execute_and_commit_duration.clone(), ); + registry.register( + "importer_gas_per_block", + "The total gas used in a block", + gas_per_block.clone(), + ); + + registry.register( + "importer_fee_per_block_gwei", + "The total fee (gwei) paid by transactions in a block", + fee_per_block.clone(), + ); + + registry.register( + "importer_transactions_per_block", + "The total number of transactions in a block", + transactions_per_block.clone(), + ); + + registry.register( + "importer_gas_price_for_block", + "The gas prices used in a block", + transactions_per_block.clone(), + ); + Self { block_height: block_height_gauge, latest_block_import_timestamp: latest_block_import_ms, execute_and_commit_duration, + gas_per_block, + fee_per_block, + transactions_per_block, + gas_price, } } } diff --git a/crates/metrics/src/lib.rs b/crates/metrics/src/lib.rs index f7e4e22f7ca..399b34c51e5 100644 --- a/crates/metrics/src/lib.rs +++ b/crates/metrics/src/lib.rs @@ -19,6 +19,8 @@ pub struct GlobalRegistry { pub registry: parking_lot::Mutex, } +mod buckets; +pub mod config; pub mod core_metrics; pub mod futures; pub mod graphql_metrics; @@ -26,16 +28,6 @@ pub mod importer; pub mod p2p_metrics; pub mod txpool_metrics; -// recommended bucket defaults for logging response times -static BUCKETS: OnceLock> = OnceLock::new(); -pub fn timing_buckets() -> &'static Vec { - BUCKETS.get_or_init(|| { - vec![ - 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, - ] - }) -} - static GLOBAL_REGISTER: OnceLock = OnceLock::new(); pub fn global_registry() -> &'static GlobalRegistry { diff --git a/crates/services/importer/src/config.rs b/crates/services/importer/src/config.rs index f959724abb2..d5854d5141f 100644 --- a/crates/services/importer/src/config.rs +++ b/crates/services/importer/src/config.rs @@ -1,18 +1,20 @@ #[derive(Debug, Clone)] pub struct Config { pub max_block_notify_buffer: usize, + pub metrics: bool, } impl Config { - pub fn new() -> Self { + pub fn new(metrics: bool) -> Self { Self { max_block_notify_buffer: 1 << 10, + metrics, } } } impl Default for Config { fn default() -> Self { - Self::new() + Self::new(false) } } diff --git a/crates/services/importer/src/importer.rs b/crates/services/importer/src/importer.rs index d877390cbaa..4479c4efd37 100644 --- a/crates/services/importer/src/importer.rs +++ b/crates/services/importer/src/importer.rs @@ -25,6 +25,10 @@ use fuel_core_types::{ primitives::BlockId, SealedBlock, }, + fuel_tx::{ + field::MintGasPrice, + Transaction, + }, fuel_types::{ BlockHeight, ChainId, @@ -34,8 +38,10 @@ use fuel_core_types::{ ImportResult, UncommittedResult, }, - executor, - executor::ValidationResult, + executor::{ + self, + ValidationResult, + }, Uncommitted, }, }; @@ -58,6 +64,7 @@ use tokio::sync::{ Semaphore, TryAcquireError, }; +use tracing::warn; #[cfg(test)] pub mod test; @@ -126,6 +133,8 @@ pub struct Importer { /// the resolution of the previous one. active_import_results: Arc, process_thread: rayon::ThreadPool, + /// Enables prometheus metrics for this fuel-service + metrics: bool, } impl Importer { @@ -155,6 +164,7 @@ impl Importer { active_import_results: Arc::new(Semaphore::new(max_block_notify_buffer)), guard: Semaphore::new(1), process_thread, + metrics: config.metrics, } } @@ -341,18 +351,9 @@ where db_after_execution.commit()?; - // update the importer metrics after the block is successfully committed - importer_metrics() - .block_height - .set(*actual_next_height.deref() as i64); - let current_time = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs_f64(); - importer_metrics() - .latest_block_import_timestamp - .set(current_time); - + if self.metrics { + Self::update_metrics(&result, &actual_next_height); + } tracing::info!("Committed block {:#x}", result.sealed_block.entity.id()); let result = ImporterResult { @@ -391,6 +392,52 @@ where .latest_block_import_timestamp .set(current_time); } + + fn update_metrics(result: &ImportResult, actual_next_height: &BlockHeight) { + let (total_gas_used, total_fee): (u64, u64) = result + .tx_status + .iter() + .map(|tx_result| { + (*tx_result.result.total_gas(), *tx_result.result.total_fee()) + }) + .fold((0_u64, 0_u64), |(acc_gas, acc_fee), (used_gas, fee)| { + ( + acc_gas.saturating_add(used_gas), + acc_fee.saturating_add(fee), + ) + }); + let maybe_last_tx = result.sealed_block.entity.transactions().last(); + if let Some(last_tx) = maybe_last_tx { + if let Transaction::Mint(mint) = last_tx { + importer_metrics() + .gas_price + .set((*mint.gas_price()).try_into().unwrap_or(i64::MAX)); + } else { + warn!("Last transaction is not a mint transaction"); + } + } + + let total_transactions = result.tx_status.len(); + importer_metrics() + .block_height + .set(*actual_next_height.deref() as i64); + let current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs_f64(); + importer_metrics() + .latest_block_import_timestamp + .set(current_time); + importer_metrics() + .gas_per_block + .set(total_gas_used.try_into().unwrap_or(i64::MAX)); + importer_metrics() + .fee_per_block + .set(total_fee.try_into().unwrap_or(i64::MAX)); + importer_metrics() + .transactions_per_block + .set(total_transactions.try_into().unwrap_or(i64::MAX)); + } } impl Importer diff --git a/crates/services/p2p/Cargo.toml b/crates/services/p2p/Cargo.toml index 718e3bbf0d7..5455e83c5f3 100644 --- a/crates/services/p2p/Cargo.toml +++ b/crates/services/p2p/Cargo.toml @@ -36,6 +36,7 @@ libp2p = { version = "0.53.2", default-features = false, features = [ "tokio", "yamux", "websocket", + "metrics", ] } libp2p-mplex = "0.41.0" postcard = { workspace = true, features = ["use-std"] } diff --git a/crates/services/p2p/src/p2p_service.rs b/crates/services/p2p/src/p2p_service.rs index eb5a5a75a6c..024cc006785 100644 --- a/crates/services/p2p/src/p2p_service.rs +++ b/crates/services/p2p/src/p2p_service.rs @@ -36,7 +36,10 @@ use crate::{ }, TryPeerId, }; -use fuel_core_metrics::p2p_metrics::increment_unique_peers; +use fuel_core_metrics::{ + global_registry, + p2p_metrics::increment_unique_peers, +}; use fuel_core_types::{ fuel_types::BlockHeight, services::p2p::peer_reputation::AppScore, @@ -51,6 +54,10 @@ use libp2p::{ TopicHash, }, identify, + metrics::{ + Metrics, + Recorder, + }, multiaddr::Protocol, request_response::{ self, @@ -123,6 +130,9 @@ pub struct FuelP2PService { /// Whether or not metrics collection is enabled metrics: bool, + /// libp2p metrics registry + libp2p_metrics_registry: Option, + /// Holds peers' information, and manages existing connections peer_manager: PeerManager, } @@ -203,6 +213,8 @@ impl FuelP2PService { config: Config, codec: PostcardCodec, ) -> anyhow::Result { + let metrics = config.metrics; + let gossipsub_data = GossipsubData::with_topics(GossipsubTopics::new(&config.network_name)); let network_metadata = NetworkMetadata { gossipsub_data }; @@ -217,7 +229,7 @@ impl FuelP2PService { let tcp_config = tcp::Config::new().port_reuse(true); let behaviour = FuelBehaviour::new(&config, codec.clone())?; - let mut swarm = SwarmBuilder::with_existing_identity(config.keypair.clone()) + let swarm_builder = SwarmBuilder::with_existing_identity(config.keypair.clone()) .with_tokio() .with_tcp( tcp_config, @@ -225,21 +237,41 @@ impl FuelP2PService { libp2p::yamux::Config::default, ) .map_err(|_| anyhow::anyhow!("Failed to build Swarm"))? - .with_dns()? - .with_behaviour(|_| behaviour)? - .with_swarm_config(|cfg| { - if let Some(timeout) = config.connection_idle_timeout { - cfg.with_idle_connection_timeout(timeout) - } else { - cfg - } - }) - .build(); + .with_dns()?; + + let mut libp2p_metrics_registry = None; + let mut swarm = if metrics { + // we use the global registry to store the metrics without needing to create a new one + // since libp2p already creates sub-registries + let mut registry = global_registry().registry.lock(); + libp2p_metrics_registry = Some(Metrics::new(&mut registry)); + + swarm_builder + .with_bandwidth_metrics(&mut registry) + .with_behaviour(|_| behaviour)? + .with_swarm_config(|cfg| { + if let Some(timeout) = config.connection_idle_timeout { + cfg.with_idle_connection_timeout(timeout) + } else { + cfg + } + }) + .build() + } else { + swarm_builder + .with_behaviour(|_| behaviour)? + .with_swarm_config(|cfg| { + if let Some(timeout) = config.connection_idle_timeout { + cfg.with_idle_connection_timeout(timeout) + } else { + cfg + } + }) + .build() + }; let local_peer_id = swarm.local_peer_id().to_owned(); - let metrics = config.metrics; - if let Some(public_address) = config.public_address.clone() { swarm.add_external_address(public_address); } @@ -260,6 +292,7 @@ impl FuelP2PService { inbound_requests_table: HashMap::default(), network_metadata, metrics, + libp2p_metrics_registry, peer_manager: PeerManager::new( reserved_peers_updates, reserved_peers, @@ -312,6 +345,15 @@ impl FuelP2PService { } } + pub fn update_libp2p_metrics(&self, event: &E) + where + Metrics: Recorder, + { + if let Some(registry) = self.libp2p_metrics_registry.as_ref() { + self.update_metrics(|| registry.record(event)); + } + } + #[cfg(feature = "test-helpers")] pub fn multiaddrs(&self) -> Vec { let local_peer = self.local_peer_id; @@ -492,7 +534,10 @@ impl FuelP2PService { ); None } - _ => None, + _ => { + self.update_libp2p_metrics(&event); + None + } } } @@ -517,13 +562,23 @@ impl FuelP2PService { event: FuelBehaviourEvent, ) -> Option { match event { - FuelBehaviourEvent::Gossipsub(event) => self.handle_gossipsub_event(event), + FuelBehaviourEvent::Gossipsub(event) => { + self.update_libp2p_metrics(&event); + self.handle_gossipsub_event(event) + } FuelBehaviourEvent::PeerReport(event) => self.handle_peer_report_event(event), FuelBehaviourEvent::RequestResponse(event) => { self.handle_request_response_event(event) } - FuelBehaviourEvent::Identify(event) => self.handle_identify_event(event), + FuelBehaviourEvent::Identify(event) => { + self.update_libp2p_metrics(&event); + self.handle_identify_event(event) + } FuelBehaviourEvent::Heartbeat(event) => self.handle_heartbeat_event(event), + FuelBehaviourEvent::Discovery(event) => { + self.update_libp2p_metrics(&event); + None + } _ => None, } } diff --git a/crates/types/src/services/executor.rs b/crates/types/src/services/executor.rs index b8c0dfda407..357e3697638 100644 --- a/crates/types/src/services/executor.rs +++ b/crates/types/src/services/executor.rs @@ -232,6 +232,14 @@ impl TransactionExecutionResult { } } + /// Get the total fee paid by the transaction. + pub fn total_fee(&self) -> &u64 { + match self { + TransactionExecutionResult::Success { total_fee, .. } + | TransactionExecutionResult::Failed { total_fee, .. } => total_fee, + } + } + #[cfg(feature = "std")] /// Get the reason of the failed transaction execution. pub fn reason(receipts: &[Receipt], state: &Option) -> String {