diff --git a/Cargo.lock b/Cargo.lock index e9b9ca337c18e..ddd05f045009d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -446,6 +446,12 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "bs58" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c95ee6bba9d950218b6cc910cf62bc9e0a171d0f4537e3627b0f54d08549b188" + [[package]] name = "bs58" version = "0.3.0" @@ -610,7 +616,6 @@ dependencies = [ "js-sys", "num-integer", "num-traits", - "serde", "time", "wasm-bindgen", ] @@ -1927,34 +1932,6 @@ dependencies = [ "scroll", ] -[[package]] -name = "grafana-data-source" -version = "0.8.0" -dependencies = [ - "async-std", - "chrono", - "derive_more", - "futures-timer 3.0.1", - "futures-util", - "hyper 0.13.2", - "lazy_static", - "log 0.4.8", - "parking_lot 0.10.0", - "serde", - "serde_json", - "tokio 0.2.11", -] - -[[package]] -name = "grafana-data-source-test" -version = "2.0.0" -dependencies = [ - "futures 0.3.4", - "futures-timer 3.0.1", - "grafana-data-source", - "rand 0.7.3", -] - [[package]] name = "h2" version = "0.1.26" @@ -2671,8 +2648,8 @@ dependencies = [ "libp2p-wasm-ext", "libp2p-websocket", "libp2p-yamux", - "parity-multiaddr", - "parity-multihash", + "parity-multiaddr 0.7.2", + "parity-multihash 0.2.3", "parking_lot 0.10.0", "pin-project", "smallvec 1.2.0", @@ -2686,7 +2663,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b874594c4b29de1a29f27871feba8e6cd13aa54a8a1e8f8c7cf3dfac5ca287c" dependencies = [ "asn1_der", - "bs58", + "bs58 0.3.0", "ed25519-dalek", "fnv", "futures 0.3.4", @@ -2695,8 +2672,8 @@ dependencies = [ "libsecp256k1", "log 0.4.8", "multistream-select", - "parity-multiaddr", - "parity-multihash", + "parity-multiaddr 0.7.2", + "parity-multihash 0.2.3", "parking_lot 0.10.0", "pin-project", "prost", @@ -2707,7 +2684,7 @@ dependencies = [ "sha2", "smallvec 1.2.0", "thiserror", - "unsigned-varint", + "unsigned-varint 0.3.0", "void", "zeroize 1.1.0", ] @@ -2782,7 +2759,7 @@ dependencies = [ "rand 0.7.3", "sha2", "smallvec 1.2.0", - "unsigned-varint", + "unsigned-varint 0.3.0", "wasm-timer", ] @@ -2817,14 +2794,14 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log 0.4.8", - "parity-multihash", + "parity-multihash 0.2.3", "prost", "prost-build", "rand 0.7.3", "sha2", "smallvec 1.2.0", "uint", - "unsigned-varint", + "unsigned-varint 0.3.0", "void", "wasm-timer", ] @@ -2864,7 +2841,7 @@ dependencies = [ "libp2p-core", "log 0.4.8", "parking_lot 0.10.0", - "unsigned-varint", + "unsigned-varint 0.3.0", ] [[package]] @@ -2917,7 +2894,7 @@ dependencies = [ "prost", "prost-build", "rw-stream-sink", - "unsigned-varint", + "unsigned-varint 0.3.0", "void", ] @@ -3327,7 +3304,7 @@ dependencies = [ "log 0.4.8", "smallvec 1.2.0", "tokio-io", - "unsigned-varint", + "unsigned-varint 0.3.0", ] [[package]] @@ -4559,6 +4536,24 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c276d76c5333b8c2579e02d49a06733a55b8282d2d9b13e8d53b6406bd7e30a" +[[package]] +name = "parity-multiaddr" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "045b3c7af871285146300da35b1932bb6e4639b66c7c98e85d06a32cbc4e8fa7" +dependencies = [ + "arrayref", + "bs58 0.2.5", + "byteorder 1.3.4", + "bytes 0.4.12", + "data-encoding", + "parity-multihash 0.1.3", + "percent-encoding 1.0.1", + "serde", + "unsigned-varint 0.2.3", + "url 1.7.2", +] + [[package]] name = "parity-multiaddr" version = "0.7.2" @@ -4566,17 +4561,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26df883298bc3f4e92528b4c5cc9f806b791955b136da3e5e939ed9de0fd958b" dependencies = [ "arrayref", - "bs58", + "bs58 0.3.0", "byteorder 1.3.4", "data-encoding", - "parity-multihash", + "parity-multihash 0.2.3", "percent-encoding 2.1.0", "serde", "static_assertions", - "unsigned-varint", + "unsigned-varint 0.3.0", "url 2.1.1", ] +[[package]] +name = "parity-multihash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3a17dc27848fd99e4f87eb0f8c9baba6ede0a6d555400c850ca45254ef4ce3" +dependencies = [ + "blake2", + "bytes 0.4.12", + "rand 0.6.5", + "sha-1", + "sha2", + "sha3", + "unsigned-varint 0.2.3", +] + [[package]] name = "parity-multihash" version = "0.2.3" @@ -4589,7 +4599,7 @@ dependencies = [ "sha-1", "sha2", "sha3", - "unsigned-varint", + "unsigned-varint 0.3.0", ] [[package]] @@ -4986,6 +4996,33 @@ dependencies = [ "unicode-xid 0.2.0", ] +[[package]] +name = "prometheus" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5567486d5778e2c6455b1b90ff1c558f29e751fc018130fa182e15828e728af1" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "protobuf", + "quick-error", + "spin", +] + +[[package]] +name = "prometheus-exporter" +version = "0.8.0" +dependencies = [ + "async-std", + "derive_more", + "futures-util", + "hyper 0.13.2", + "log 0.4.8", + "prometheus", + "tokio 0.2.11", +] + [[package]] name = "prost" version = "0.6.1" @@ -5037,6 +5074,12 @@ dependencies = [ "prost", ] +[[package]] +name = "protobuf" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6686ddd96a8dbe2687b5f2a687b2cfb520854010ec480f2d74c32e7c9873d3c5" + [[package]] name = "pwasm-utils" version = "0.12.0" @@ -5656,6 +5699,7 @@ dependencies = [ "log 0.4.8", "names", "parity-util-mem", + "prometheus-exporter", "regex", "rpassword", "sc-client-api", @@ -6189,7 +6233,7 @@ dependencies = [ "substrate-test-runtime-client", "tempfile", "thiserror", - "unsigned-varint", + "unsigned-varint 0.3.0", "void", "wasm-timer", "yamux", @@ -6379,13 +6423,13 @@ dependencies = [ "futures 0.3.4", "futures-diagnose", "futures-timer 3.0.1", - "grafana-data-source", "lazy_static", "log 0.4.8", - "parity-multiaddr", + "parity-multiaddr 0.5.0", "parity-scale-codec", "parity-util-mem", "parking_lot 0.10.0", + "prometheus-exporter", "sc-chain-spec", "sc-client", "sc-client-api", @@ -6480,7 +6524,6 @@ name = "sc-tracing" version = "2.0.0" dependencies = [ "erased-serde", - "grafana-data-source", "log 0.4.8", "parking_lot 0.10.0", "sc-telemetry", @@ -8493,6 +8536,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +[[package]] +name = "unsigned-varint" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f0023a96687fe169081e8adce3f65e3874426b7886e9234d490af2dc077959" + [[package]] name = "unsigned-varint" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index a42a8e24d0f48..2ff0692256a66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,9 +53,8 @@ members = [ "client/telemetry", "client/transaction-pool", "client/transaction-pool/graph", + "utils/prometheus", "utils/wasm-builder-runner", - "utils/grafana-data-source", - "utils/grafana-data-source/test", "frame/assets", "frame/aura", "frame/authority-discovery", diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index e176894d64c07..66b0a9a9b88f3 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -31,6 +31,7 @@ sp-core = { version = "2.0.0", path = "../../primitives/core" } sc-service = { version = "0.8", default-features = false, path = "../service" } sp-state-machine = { version = "0.8", path = "../../primitives/state-machine" } sc-telemetry = { version = "2.0.0", path = "../telemetry" } +prometheus-exporter = { path = "../../utils/prometheus" } sp-keyring = { version = "2.0.0", path = "../../primitives/keyring" } names = "0.11.0" structopt = "0.3.8" diff --git a/client/cli/src/lib.rs b/client/cli/src/lib.rs index 7495ad8e75690..1e649152ef942 100644 --- a/client/cli/src/lib.rs +++ b/client/cli/src/lib.rs @@ -623,13 +623,6 @@ where config.rpc_ws = Some(parse_address(&format!("{}:{}", ws_interface, 9944), cli.ws_port)?); } - if config.grafana_port.is_none() || cli.grafana_port.is_some() { - let grafana_interface: &str = if cli.grafana_external { "0.0.0.0" } else { "127.0.0.1" }; - config.grafana_port = Some( - parse_address(&format!("{}:{}", grafana_interface, 9955), cli.grafana_port)? - ); - } - config.rpc_ws_max_connections = cli.ws_max_connections; config.rpc_cors = cli.rpc_cors.unwrap_or_else(|| if is_dev { log::warn!("Running in --dev mode, RPC CORS has been disabled."); @@ -651,6 +644,14 @@ where } else if !cli.telemetry_endpoints.is_empty() { config.telemetry_endpoints = Some(TelemetryEndpoints::new(cli.telemetry_endpoints)); } + // Override prometheus + if cli.no_prometheus { + config.prometheus_port = None; + } else { + let prometheus_interface: &str = if cli.prometheus_external { "0.0.0.0" } else { "127.0.0.1" }; + config.prometheus_port = Some( + parse_address(&format!("{}:{}", prometheus_interface, 9615), cli.prometheus_port)?); + } config.tracing_targets = cli.import_params.tracing_targets.into(); config.tracing_receiver = cli.import_params.tracing_receiver.into(); @@ -877,4 +878,4 @@ mod tests { assert!(config.network.config_path.is_some()); assert!(!config.network.listen_addresses.is_empty()); } -} +} \ No newline at end of file diff --git a/client/cli/src/params.rs b/client/cli/src/params.rs index 9ae7bd7748114..ac0b2a7cbfde4 100644 --- a/client/cli/src/params.rs +++ b/client/cli/src/params.rs @@ -331,7 +331,6 @@ arg_enum! { pub enum TracingReceiver { Log, Telemetry, - Grafana, } } @@ -340,7 +339,6 @@ impl Into for TracingReceiver { match self { TracingReceiver::Log => sc_tracing::TracingReceiver::Log, TracingReceiver::Telemetry => sc_tracing::TracingReceiver::Telemetry, - TracingReceiver::Grafana => sc_tracing::TracingReceiver::Grafana, } } } @@ -480,11 +478,11 @@ pub struct RunCmd { #[structopt(long = "unsafe-ws-external")] pub unsafe_ws_external: bool, - /// Listen to all Grafana data source interfaces. + /// Listen to all Prometheus endpoint interfaces. /// /// Default is local. - #[structopt(long = "grafana-external")] - pub grafana_external: bool, + #[structopt(long = "prometheus-external")] + pub prometheus_external: bool, /// Specify HTTP RPC server TCP port. #[structopt(long = "rpc-port", value_name = "PORT")] @@ -508,9 +506,15 @@ pub struct RunCmd { #[structopt(long = "rpc-cors", value_name = "ORIGINS", parse(try_from_str = parse_cors))] pub rpc_cors: Option, - /// Specify Grafana data source server TCP Port. - #[structopt(long = "grafana-port", value_name = "PORT")] - pub grafana_port: Option, + /// Specify Prometheus endpoint TCP Port. + #[structopt(long = "prometheus-port", value_name = "PORT")] + pub prometheus_port: Option, + + /// Do not expose a Prometheus metric endpoint. + /// + /// Prometheus metric endpoint is enabled by default. + #[structopt(long = "no-prometheus")] + pub no_prometheus: bool, /// The human-readable name for this node. /// diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index 7f7ef80b16d71..1feaffc85717b 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -53,8 +53,8 @@ sc-rpc-server = { version = "2.0.0", path = "../rpc-servers" } sc-rpc = { version = "2.0.0", path = "../rpc" } sc-telemetry = { version = "2.0.0", path = "../telemetry" } sc-offchain = { version = "2.0.0", path = "../offchain" } -parity-multiaddr = { package = "parity-multiaddr", version = "0.7.1" } -grafana-data-source = { version = "0.8", path = "../../utils/grafana-data-source" } +parity-multiaddr = { package = "parity-multiaddr", version = "0.5.0" } +prometheus-exporter = { path = "../../utils/prometheus" } sc-tracing = { version = "2.0.0", path = "../tracing" } tracing = "0.1.10" parity-util-mem = { version = "0.5.1", default-features = false, features = ["primitive-types"] } diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index c67551afa3556..9dee787f50008 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -39,7 +39,7 @@ use sc_network::{config::BoxFinalityProofRequestBuilder, specialization::Network use parking_lot::{Mutex, RwLock}; use sp_runtime::generic::BlockId; use sp_runtime::traits::{ - Block as BlockT, NumberFor, SaturatedConversion, HasherFor, + Block as BlockT, NumberFor, SaturatedConversion, HasherFor, UniqueSaturatedInto, }; use sp_api::ProvideRuntimeApi; use sc_executor::{NativeExecutor, NativeExecutionDispatch}; @@ -53,7 +53,43 @@ use sysinfo::{get_current_pid, ProcessExt, System, SystemExt}; use sc_telemetry::{telemetry, SUBSTRATE_INFO}; use sp_transaction_pool::{MaintainedTransactionPool, ChainEvent}; use sp_blockchain; -use grafana_data_source::{self, record_metrics}; +use prometheus_exporter::{register, Gauge, U64, F64, Registry, PrometheusError, Opts, GaugeVec}; + +struct ServiceMetrics { + block_height_number: GaugeVec, + peers_count: Gauge, + ready_transactions_number: Gauge, + memory_usage_bytes: Gauge, + cpu_usage_percentage: Gauge, + network_per_sec_bytes: GaugeVec, +} + +impl ServiceMetrics { + fn register(registry: &Registry) -> Result { + Ok(Self { + block_height_number: register(GaugeVec::new( + Opts::new("block_height_number", "Height of the chain"), + &["status"] + )?, registry)?, + peers_count: register(Gauge::new( + "peers_count", "Number of network gossip peers", + )?, registry)?, + ready_transactions_number: register(Gauge::new( + "ready_transactions_number", "Number of transactions in the ready queue", + )?, registry)?, + memory_usage_bytes: register(Gauge::new( + "memory_usage_bytes", "Node memory usage", + )?, registry)?, + cpu_usage_percentage: register(Gauge::new( + "cpu_usage_percentage", "Node CPU usage", + )?, registry)?, + network_per_sec_bytes: register(GaugeVec::new( + Opts::new("network_per_sec_bytes", "Networking bytes per second"), + &["direction"] + )?, registry)?, + }) + } +} pub type BackgroundTask = Pin + Send>>; @@ -93,6 +129,7 @@ pub struct ServiceBuilder>>, marker: PhantomData<(TBl, TRtApi)>, background_tasks: Vec<(&'static str, BackgroundTask)>, + prometheus_registry: Option } /// Full client type. @@ -270,6 +307,7 @@ where TGen: RuntimeGenesis, TCSExt: Extension { remote_backend: None, background_tasks: Default::default(), marker: PhantomData, + prometheus_registry: None, }) } @@ -356,6 +394,7 @@ where TGen: RuntimeGenesis, TCSExt: Extension { remote_backend: Some(remote_blockchain), background_tasks: Default::default(), marker: PhantomData, + prometheus_registry: None, }) } } @@ -429,6 +468,7 @@ impl Self { + Self { + config: self.config, + client: self.client, + backend: self.backend, + keystore: self.keystore, + fetcher: self.fetcher, + select_chain: self.select_chain, + import_queue: self.import_queue, + finality_proof_request_builder: self.finality_proof_request_builder, + finality_proof_provider: self.finality_proof_provider, + network_protocol: self.network_protocol, + transaction_pool: self.transaction_pool, + rpc_extensions: self.rpc_extensions, + remote_backend: self.remote_backend, + background_tasks: self.background_tasks, + marker: self.marker, + prometheus_registry: Some(registry), + } + } } /// Implemented on `ServiceBuilder`. Allows running block commands, such as import/export/validate @@ -807,6 +875,7 @@ ServiceBuilder< rpc_extensions, remote_backend, background_tasks, + prometheus_registry, } = self; sp_session::generate_initial_session_keys( @@ -998,6 +1067,30 @@ ServiceBuilder< )); } + // Prometheus exporter and metrics + let metrics = if let Some(port) = config.prometheus_port { + let registry = match prometheus_registry { + Some(registry) => registry, + None => Registry::new_custom(Some("substrate".into()), None)? + }; + + let metrics = ServiceMetrics::register(®istry)?; + + let future = select( + prometheus_exporter::init_prometheus(port, registry).boxed(), + exit.clone() + ).map(drop); + + let _ = to_spawn_tx.unbounded_send(( + Box::pin(future), + From::from("prometheus-endpoint") + )); + + Some(metrics) + } else { + None + }; + // Periodically notify the telemetry. let transaction_pool_ = transaction_pool.clone(); let client_ = client.clone(); @@ -1014,6 +1107,8 @@ ServiceBuilder< let finalized_number: u64 = info.chain.finalized_number.saturated_into::(); let bandwidth_download = net_status.average_download_per_sec; let bandwidth_upload = net_status.average_upload_per_sec; + let best_seen_block = net_status.best_seen_block + .map(|num: NumberFor| num.unique_saturated_into() as u64); // get cpu usage and memory usage of this process let (cpu_usage, memory) = if let Some(self_pid) = self_pid { @@ -1042,25 +1137,22 @@ ServiceBuilder< "disk_read_per_sec" => info.usage.as_ref().map(|usage| usage.io.bytes_read).unwrap_or(0), "disk_write_per_sec" => info.usage.as_ref().map(|usage| usage.io.bytes_written).unwrap_or(0), ); - #[cfg(not(target_os = "unknown"))] - let memory_transaction_pool = parity_util_mem::malloc_size(&*transaction_pool_); - #[cfg(target_os = "unknown")] - let memory_transaction_pool = 0; - let _ = record_metrics!( - "peers" => num_peers, - "height" => best_number, - "txcount" => txpool_status.ready, - "cpu" => cpu_usage, - "memory" => memory, - "finalized_height" => finalized_number, - "bandwidth_download" => bandwidth_download, - "bandwidth_upload" => bandwidth_upload, - "used_state_cache_size" => info.usage.as_ref().map(|usage| usage.memory.state_cache).unwrap_or(0), - "used_db_cache_size" => info.usage.as_ref().map(|usage| usage.memory.database_cache).unwrap_or(0), - "disk_read_per_sec" => info.usage.as_ref().map(|usage| usage.io.bytes_read).unwrap_or(0), - "disk_write_per_sec" => info.usage.as_ref().map(|usage| usage.io.bytes_written).unwrap_or(0), - "memory_transaction_pool" => memory_transaction_pool, - ); + if let Some(metrics) = metrics.as_ref() { + metrics.memory_usage_bytes.set(memory); + metrics.cpu_usage_percentage.set(f64::from(cpu_usage)); + metrics.ready_transactions_number.set(txpool_status.ready as u64); + metrics.peers_count.set(num_peers as u64); + + metrics.network_per_sec_bytes.with_label_values(&["download"]).set(net_status.average_download_per_sec); + metrics.network_per_sec_bytes.with_label_values(&["upload"]).set(net_status.average_upload_per_sec); + + metrics.block_height_number.with_label_values(&["finalized"]).set(finalized_number); + metrics.block_height_number.with_label_values(&["best"]).set(best_number); + + if let Some(best_seen_block) = best_seen_block { + metrics.block_height_number.with_label_values(&["sync_target"]).set(best_seen_block); + } + } ready(()) }); @@ -1217,16 +1309,6 @@ ServiceBuilder< telemetry }); - // Grafana data source - if let Some(port) = config.grafana_port { - let future = select( - grafana_data_source::run_server(port).boxed(), - exit.clone() - ).map(drop); - - let _ = to_spawn_tx.unbounded_send((Box::pin(future), From::from("grafana-server"))); - } - // Instrumentation if let Some(tracing_targets) = config.tracing_targets.as_ref() { let subscriber = sc_tracing::ProfilingSubscriber::new( diff --git a/client/service/src/config.rs b/client/service/src/config.rs index f4043d533e190..ef411b5eb9999 100644 --- a/client/service/src/config.rs +++ b/client/service/src/config.rs @@ -93,8 +93,8 @@ pub struct Configuration { pub rpc_ws_max_connections: Option, /// CORS settings for HTTP & WS servers. `None` if all origins are allowed. pub rpc_cors: Option>, - /// Grafana data source http port. `None` if disabled. - pub grafana_port: Option, + /// Prometheus exporter Port. `None` if disabled. + pub prometheus_port: Option, /// Telemetry service URL. `None` if disabled. pub telemetry_endpoints: Option, /// External WASM transport for the telemetry. If `Some`, when connection to a telemetry @@ -190,7 +190,7 @@ impl Default for Configuration { rpc_ws: None, rpc_ws_max_connections: None, rpc_cors: Some(vec![]), - grafana_port: None, + prometheus_port: None, telemetry_endpoints: None, telemetry_external_transport: None, default_heap_pages: None, diff --git a/client/service/src/error.rs b/client/service/src/error.rs index 059e1c19e490d..4d0a2cef94233 100644 --- a/client/service/src/error.rs +++ b/client/service/src/error.rs @@ -53,6 +53,12 @@ impl<'a> From<&'a str> for Error { } } +impl From for Error { + fn from(e: prometheus_exporter::PrometheusError) -> Self { + Error::Other(format!("Prometheus error: {}", e)) + } +} + impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { diff --git a/client/service/test/src/lib.rs b/client/service/test/src/lib.rs index b65bccc151823..414b943594c01 100644 --- a/client/service/test/src/lib.rs +++ b/client/service/test/src/lib.rs @@ -199,7 +199,7 @@ fn node_config ( rpc_ws: None, rpc_ws_max_connections: None, rpc_cors: None, - grafana_port: None, + prometheus_port: None, telemetry_endpoints: None, telemetry_external_transport: None, default_heap_pages: None, diff --git a/client/tracing/Cargo.toml b/client/tracing/Cargo.toml index b476db6a0113d..0b6b02acd524a 100644 --- a/client/tracing/Cargo.toml +++ b/client/tracing/Cargo.toml @@ -15,7 +15,6 @@ slog = { version = "2.5.2", features = ["nested-values"] } tracing-core = "0.1.7" sc-telemetry = { version = "2.0.0", path = "../telemetry" } -grafana-data-source = { version = "0.8", path = "../../utils/grafana-data-source" } [dev-dependencies] tracing = "0.1.10" diff --git a/client/tracing/src/lib.rs b/client/tracing/src/lib.rs index cd301041d39af..c00bca9275eec 100644 --- a/client/tracing/src/lib.rs +++ b/client/tracing/src/lib.rs @@ -34,7 +34,7 @@ //! let span = tracing::span!(tracing::Level::INFO, "my_span_name", my_number = 10, a_key = "a value"); //! let _guard = span.enter(); //! ``` -//! Currently we provide `Log` (default), `Telemetry` and `Grafana` variants for `Receiver` +//! Currently we provide `Log` (default), `Telemetry` variants for `Receiver` use std::collections::HashMap; use std::fmt; @@ -53,7 +53,6 @@ use tracing_core::{ subscriber::Subscriber }; -use grafana_data_source::{self, record_metrics}; use sc_telemetry::{telemetry, SUBSTRATE_INFO}; /// Used to configure how to receive the metrics @@ -63,8 +62,6 @@ pub enum TracingReceiver { Log, /// Output to telemetry Telemetry, - /// Output to Grafana - Grafana, } impl Default for TracingReceiver { @@ -255,7 +252,6 @@ impl ProfilingSubscriber { match self.receiver { TracingReceiver::Log => print_log(span_datum), TracingReceiver::Telemetry => send_telemetry(span_datum), - TracingReceiver::Grafana => send_grafana(span_datum), } } } @@ -291,9 +287,3 @@ fn send_telemetry(span_datum: SpanDatum) { ); } -fn send_grafana(span_datum: SpanDatum) { - let name = format!("{}::{}", span_datum.target, span_datum.name); - if let Err(e) = record_metrics!(&name => span_datum.overall_time.as_nanos(),) { - log::warn!("Unable to send metrics to grafana: {:?}", e); - } -} diff --git a/utils/grafana-data-source/src/database.rs b/utils/grafana-data-source/src/database.rs deleted file mode 100644 index f20917cf785d4..0000000000000 --- a/utils/grafana-data-source/src/database.rs +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2019-2020 Parity Technologies (UK) Ltd. -// This file is part of Substrate. - -// Substrate is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Substrate is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Substrate. If not, see . - -use std::collections::HashMap; -use std::convert::TryFrom; -use crate::Error; - -pub struct Database { - base_timestamp: i64, - storage: HashMap> -} - -impl Database { - /// Create a new Database. - pub fn new() -> Self { - Self { - base_timestamp: now_millis(), - storage: HashMap::new() - } - } - - /// Produce an iterator for keys starting with a base string. - pub fn keys_starting_with<'a>(&'a self, base: &'a str) -> impl Iterator + 'a { - self.storage.keys() - .filter(move |key| key.starts_with(base)) - .cloned() - } - - /// Select `max_datapoints` datapoints that have been added between `from` and `to`. - pub fn datapoints_between(&self, key: &str, from: i64, to: i64, max_datapoints: usize) -> Option> { - self.storage.get(key) - .map(|vec| { - let from = find_index(vec, self.base_timestamp, from); - let to = find_index(vec, self.base_timestamp, to); - let slice = &vec[from .. to]; - - if max_datapoints == 0 { - Vec::new() - } else if max_datapoints >= slice.len() { - // Just convert the slice as-is - slice.iter() - .map(|dp| dp.make_absolute(self.base_timestamp)) - .collect() - } else { - // We have more datapoints than we need, so we need to skip some - (0 .. max_datapoints - 1) - .map(|i| &slice[i * slice.len() / (max_datapoints - 1)]) - .chain(slice.last()) - .map(|dp| dp.make_absolute(self.base_timestamp)) - .collect() - } - }) - } - - /// Push a new datapoint. Will error if the base timestamp hasn't been updated in `2^32` - /// milliseconds (49 days). - pub fn push(&mut self, key: &str, value: f32) -> Result<(), Error> { - self.storage.entry(key.into()) - .or_insert_with(Vec::new) - .push(Datapoint::new(self.base_timestamp, value)?); - - Ok(()) - } - - /// Set a new base timestamp, and remove metrics older than this new timestamp. Errors if the - /// difference between timestamps is greater than `2^32` milliseconds (49 days). - pub fn truncate(&mut self, new_base_timestamp: i64) -> Result<(), Error> { - // Ensure that the new base is older. - if self.base_timestamp >= new_base_timestamp { - return Ok(()); - } - - // If the old base timestamp was too long ago, the - let delta = u32::try_from(new_base_timestamp - self.base_timestamp) - .map_err(Error::Timestamp)?; - - for metric in self.storage.values_mut() { - // Find the index of the oldest allowed timestamp and cut out all those before it. - let index = find_index(&metric, self.base_timestamp, new_base_timestamp); - - *metric = metric.iter_mut() - .skip(index) - .map(|dp| { - dp.delta_timestamp -= delta; - *dp - }) - .collect(); - } - - self.base_timestamp = new_base_timestamp; - - Ok(()) - } -} - -#[derive(Clone, Copy)] -struct Datapoint { - delta_timestamp: u32, - value: f32 -} - -impl Datapoint { - fn new(base_timestamp: i64, value: f32) -> Result { - Ok(Self { - delta_timestamp: u32::try_from(now_millis() - base_timestamp) - .map_err(Error::Timestamp)?, - value - }) - } - - fn make_absolute(self, base_timestamp: i64) -> (f32, i64) { - (self.value, base_timestamp + self.delta_timestamp as i64) - } -} - -fn find_index(slice: &[Datapoint], base_timestamp: i64, timestamp: i64) -> usize { - slice.binary_search_by_key(×tamp, |datapoint| { - base_timestamp + datapoint.delta_timestamp as i64 - }).unwrap_or_else(|index| index) -} - -/// Get the current unix timestamp in milliseconds. -fn now_millis() -> i64 { - chrono::Utc::now().timestamp_millis() -} - -#[test] -fn test() { - let mut database = Database::new(); - - database.push("test", 1.0).unwrap(); - database.push("test", 2.5).unwrap(); - database.push("test", 2.0).unwrap(); - database.push("test 2", 1.0).unwrap(); - - let mut keys: Vec<_> = database.keys_starting_with("test").collect(); - keys.sort(); - - assert_eq!(keys, ["test", "test 2"]); - assert_eq!(database.keys_starting_with("test ").collect::>(), ["test 2"]); -} diff --git a/utils/grafana-data-source/src/lib.rs b/utils/grafana-data-source/src/lib.rs deleted file mode 100644 index fbba064706e0a..0000000000000 --- a/utils/grafana-data-source/src/lib.rs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2019-2020 Parity Technologies (UK) Ltd. -// This file is part of Substrate. - -// Substrate is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Substrate is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Substrate. If not, see . - -//! [Grafana] data source server -//! -//! To display node statistics with [Grafana], this module exposes a `run_server` function that -//! starts up a HTTP server that conforms to the [`grafana-json-data-source`] API. The -//! `record_metrics` macro can be used to pass metrics to this server. -//! -//! [Grafana]: https://grafana.com/ -//! [`grafana-json-data-source`]: https://github.com/simPod/grafana-json-datasource - -#![warn(missing_docs)] - -use lazy_static::lazy_static; -use parking_lot::RwLock; - -mod types; -mod server; -#[cfg(not(target_os = "unknown"))] -mod networking; -mod database; - -use database::Database; -pub use server::run_server; -use std::num::TryFromIntError; - -lazy_static! { - // The `RwLock` wrapping the metrics database. - static ref DATABASE: RwLock = RwLock::new(Database::new()); -} - -/// Write metrics to `METRICS`. -#[macro_export] -macro_rules! record_metrics( - ($($key:expr => $value:expr,)*) => { - if cfg!(not(target_os = "unknown")) { - $crate::record_metrics_slice(&[ - $( ($key, $value as f32), )* - ]) - } else { - Ok(()) - } - } -); - -/// Write metrics to `METRICS` as a slice. Intended to be only used via `record_metrics!`. -pub fn record_metrics_slice(metrics: &[(&str, f32)]) -> Result<(), Error> { - let mut database = crate::DATABASE.write(); - - for &(key, value) in metrics.iter() { - database.push(key, value)?; - } - - Ok(()) -} - -/// Error type that can be returned by either `record_metrics` or `run_server`. -#[derive(Debug, derive_more::Display, derive_more::From)] -pub enum Error { - /// Hyper internal error. - Hyper(hyper::Error), - /// Serialization/deserialization error. - Serde(serde_json::Error), - /// Http request error. - Http(hyper::http::Error), - /// Timestamp error. - Timestamp(TryFromIntError), - /// i/o error. - Io(std::io::Error) -} - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Error::Hyper(error) => Some(error), - Error::Serde(error) => Some(error), - Error::Http(error) => Some(error), - Error::Timestamp(error) => Some(error), - Error::Io(error) => Some(error) - } - } -} diff --git a/utils/grafana-data-source/src/server.rs b/utils/grafana-data-source/src/server.rs deleted file mode 100644 index 8ef5e0378478c..0000000000000 --- a/utils/grafana-data-source/src/server.rs +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2019-2020 Parity Technologies (UK) Ltd. -// This file is part of Substrate. - -// Substrate is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Substrate is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Substrate. If not, see . - -use serde::{Serialize, de::DeserializeOwned}; -use hyper::{Body, Request, Response, header, service::{service_fn, make_service_fn}, Server}; -use chrono::{Duration, Utc}; -use futures_util::{FutureExt, TryStreamExt, future::{Future, select, Either}}; -use futures_timer::Delay; -use crate::{DATABASE, Error, types::{Target, Query, TimeseriesData, Range}}; - -async fn api_response(req: Request) -> Result, Error> { - match req.uri().path() { - "/search" => { - map_request_to_response(req, |target: Target| { - // Filter and return metrics relating to the target - DATABASE.read() - .keys_starting_with(&target.target) - .collect::>() - }).await - }, - "/query" => { - map_request_to_response(req, |query: Query| { - let metrics = DATABASE.read(); - - let Query { - range: Range { from, to }, - max_datapoints, .. - } = query; - - // Return timeseries data related to the specified metrics - query.targets.iter() - .map(|target| { - let datapoints = metrics.datapoints_between(&target.target, from, to, max_datapoints) - .unwrap_or_else(Vec::new); - - TimeseriesData { - target: target.target.clone(), datapoints - } - }) - .collect::>() - }).await - }, - _ => Ok(Response::new(Body::empty())), - } -} - -async fn map_request_to_response(req: Request, transformation: T) -> Result, Error> - where - Req: DeserializeOwned, - Res: Serialize, - T: Fn(Req) -> Res + Send + Sync + 'static -{ - let body = req.into_body() - .map_ok(|bytes| bytes.to_vec()) - .try_concat() - .await - .map_err(Error::Hyper)?; - - let req = serde_json::from_slice(body.as_ref()).map_err(Error::Serde)?; - let res = transformation(req); - let string = serde_json::to_string(&res).map_err(Error::Serde)?; - - Response::builder() - .header(header::CONTENT_TYPE, "application/json") - .body(Body::from(string)) - .map_err(Error::Http) -} - -/// Given that we're not using hyper's tokio feature, we need to define out own executor. -#[derive(Clone)] -pub struct Executor; - -#[cfg(not(target_os = "unknown"))] -impl hyper::rt::Executor for Executor - where - T: Future + Send + 'static, - T::Output: Send + 'static, -{ - fn execute(&self, future: T) { - async_std::task::spawn(future); - } -} - -/// Start the data source server. -#[cfg(not(target_os = "unknown"))] -pub async fn run_server(mut address: std::net::SocketAddr) -> Result<(), Error> { - use async_std::{net, io}; - use crate::networking::Incoming; - - let listener = loop { - let listener = net::TcpListener::bind(&address).await; - match listener { - Ok(listener) => { - log::info!("Grafana data source server started at {}", address); - break listener - }, - Err(err) => match err.kind() { - io::ErrorKind::AddrInUse | io::ErrorKind::PermissionDenied if address.port() != 0 => { - log::warn!( - "Unable to bind grafana data source server to {}. Trying random port.", - address - ); - address.set_port(0); - continue; - }, - _ => return Err(err.into()), - } - } - }; - - let service = make_service_fn(|_| { - async { - Ok::<_, Error>(service_fn(api_response)) - } - }); - - let server = Server::builder(Incoming(listener.incoming())) - .executor(Executor) - .serve(service) - .boxed(); - - let every = std::time::Duration::from_secs(24 * 3600); - let clean = clean_up(every, Duration::weeks(1)) - .boxed(); - - let result = match select(server, clean).await { - Either::Left((result, _)) => result.map_err(Into::into), - Either::Right((result, _)) => result - }; - - result -} - -#[cfg(target_os = "unknown")] -pub async fn run_server(_: std::net::SocketAddr) -> Result<(), Error> { - Ok(()) -} - -/// Periodically remove old metrics. -async fn clean_up(every: std::time::Duration, before: Duration) -> Result<(), Error> { - loop { - Delay::new(every).await; - - let oldest_allowed = (Utc::now() - before).timestamp_millis(); - DATABASE.write().truncate(oldest_allowed)?; - } -} diff --git a/utils/grafana-data-source/src/types.rs b/utils/grafana-data-source/src/types.rs deleted file mode 100644 index 960fc787e3eb2..0000000000000 --- a/utils/grafana-data-source/src/types.rs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2019-2020 Parity Technologies (UK) Ltd. -// This file is part of Substrate. - -// Substrate is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Substrate is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Substrate. If not, see . - -use serde::{Serialize, Deserialize}; - -#[derive(Serialize, Deserialize)] -pub struct Target { - pub target: String, -} - -#[derive(Serialize, Deserialize)] -pub struct Query { - #[serde(rename = "maxDataPoints")] - pub max_datapoints: usize, - pub targets: Vec, - pub range: Range, -} - -#[derive(Serialize, Deserialize)] -pub struct Range { - #[serde(deserialize_with = "date_to_timestamp_ms")] - pub from: i64, - #[serde(deserialize_with = "date_to_timestamp_ms")] - pub to: i64, -} - -// Deserialize a timestamp via a `DateTime` -fn date_to_timestamp_ms<'de, D: serde::Deserializer<'de>>(timestamp: D) -> Result { - Deserialize::deserialize(timestamp) - .map(|date: chrono::DateTime| date.timestamp_millis()) -} - -#[derive(Serialize, Deserialize)] -pub struct TimeseriesData { - pub target: String, - pub datapoints: Vec<(f32, i64)> -} diff --git a/utils/grafana-data-source/test/Cargo.toml b/utils/grafana-data-source/test/Cargo.toml deleted file mode 100644 index 18c080c8d1f71..0000000000000 --- a/utils/grafana-data-source/test/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -description = "Grafana data source server test" -name = "grafana-data-source-test" -version = "2.0.0" -license = "GPL-3.0" -authors = ["Parity Technologies "] -edition = "2018" - -[dependencies] -grafana-data-source = { version = "0.8", path = ".." } -futures = "0.3" -futures-timer = "3.0.1" -rand = "0.7" diff --git a/utils/grafana-data-source/test/src/main.rs b/utils/grafana-data-source/test/src/main.rs deleted file mode 100644 index 53deaffc3beb6..0000000000000 --- a/utils/grafana-data-source/test/src/main.rs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2019-2020 Parity Technologies (UK) Ltd. -// This file is part of Substrate. - -// Substrate is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Substrate is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Substrate. If not, see . - -use grafana_data_source::{run_server, record_metrics}; -use std::time::Duration; -use rand::Rng; -use futures::{future::join, executor}; - -async fn randomness() { - loop { - futures_timer::Delay::new(Duration::from_secs(1)).await; - - let random = rand::thread_rng().gen_range(0.0, 1000.0); - - let result = record_metrics!( - "random data" => random, - "random^2" => random * random, - ); - - if let Err(error) = result { - eprintln!("{}", error); - } - } -} - -fn main() { - executor::block_on(join( - run_server("127.0.0.1:9955".parse().unwrap()), - randomness() - )).0.unwrap(); -} diff --git a/utils/grafana-data-source/Cargo.toml b/utils/prometheus/Cargo.toml similarity index 64% rename from utils/grafana-data-source/Cargo.toml rename to utils/prometheus/Cargo.toml index e1bd03700989b..6b591a8b2cbd6 100644 --- a/utils/grafana-data-source/Cargo.toml +++ b/utils/prometheus/Cargo.toml @@ -1,6 +1,6 @@ [package] -description = "Grafana data source server" -name = "grafana-data-source" +description = "Prometheus exporter server" +name = "prometheus-exporter" version = "0.8.0" license = "GPL-3.0" authors = ["Parity Technologies "] @@ -9,14 +9,9 @@ edition = "2018" [dependencies] log = "0.4.8" hyper = { version = "0.13.1", default-features = false, features = ["stream"] } +prometheus = "0.7" tokio = "0.2" futures-util = { version = "0.3.1", default-features = false, features = ["io"] } -serde_json = "1" -serde = { version = "1", features = ["derive"] } -chrono = { version = "0.4", features = ["serde"] } -lazy_static = "1.4" -parking_lot = "0.10.0" -futures-timer = "3.0.1" derive_more = "0.99" [target.'cfg(not(target_os = "unknown"))'.dependencies] diff --git a/utils/prometheus/README.md b/utils/prometheus/README.md new file mode 100644 index 0000000000000..9dd0882105c69 --- /dev/null +++ b/utils/prometheus/README.md @@ -0,0 +1,16 @@ +# Substrate Prometheus Exporter + +## Introduction + +[Prometheus](https://prometheus.io/) is one of the most widely used monitoring tools for managing highly available services supported by [Cloud Native Computing Foundation](https://www.cncf.io/). By providing Prometheus metrics in Substrate, node operators can easily adopt widely used display/alert tools such +as [Grafana](https://grafana.com/) and [Alertmanager](https://prometheus.io/docs/alerting/alertmanager/). Easy access to such monitoring tools will benefit parachain developers/operators and validators to have much higher availability of their services. + +Metrics will be served under `/metrics` on TCP port 9615 by default. + +## Quick Start + +1. From the root of the repository start Substrate `cargo run --release`. + +2. In another terminal run `curl localhost:9615/metrics` to retrieve the metrics. + +To learn how to configure Prometheus see the Prometheus [Getting Started](https://prometheus.io/docs/prometheus/latest/getting_started/) guide. \ No newline at end of file diff --git a/utils/prometheus/src/lib.rs b/utils/prometheus/src/lib.rs new file mode 100644 index 0000000000000..5d3b139eac44c --- /dev/null +++ b/utils/prometheus/src/lib.rs @@ -0,0 +1,129 @@ +// Copyright 2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +use futures_util::{FutureExt, future::Future}; +use hyper::http::StatusCode; +use hyper::{Server, Body, Request, Response, service::{service_fn, make_service_fn}}; +use prometheus::{Encoder, TextEncoder, core::Collector}; +use std::net::SocketAddr; +#[cfg(not(target_os = "unknown"))] +mod networking; + +pub use prometheus::{ + Registry, Error as PrometheusError, Opts, + core::{ + GenericGauge as Gauge, GenericCounter as Counter, + GenericGaugeVec as GaugeVec, GenericCounterVec as CounterVec, + AtomicF64 as F64, AtomicI64 as I64, AtomicU64 as U64, + } +}; + +pub fn register(metric: T, registry: &Registry) -> Result { + registry.register(Box::new(metric.clone()))?; + Ok(metric) +} + +#[derive(Debug, derive_more::Display, derive_more::From)] +pub enum Error { + /// Hyper internal error. + Hyper(hyper::Error), + /// Http request error. + Http(hyper::http::Error), + /// i/o error. + Io(std::io::Error), + #[display(fmt = "Prometheus exporter port {} already in use.", _0)] + PortInUse(SocketAddr) +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Hyper(error) => Some(error), + Error::Http(error) => Some(error), + Error::Io(error) => Some(error), + Error::PortInUse(_) => None + } + } +} + +async fn request_metrics(req: Request, registry: Registry) -> Result, Error> { + if req.uri().path() == "/metrics" { + let metric_families = registry.gather(); + let mut buffer = vec![]; + let encoder = TextEncoder::new(); + encoder.encode(&metric_families, &mut buffer).unwrap(); + + Response::builder().status(StatusCode::OK) + .header("Content-Type", encoder.format_type()) + .body(Body::from(buffer)) + .map_err(Error::Http) + } else { + Response::builder().status(StatusCode::NOT_FOUND) + .body(Body::from("Not found.")) + .map_err(Error::Http) + } + +} + +#[derive(Clone)] +pub struct Executor; + +#[cfg(not(target_os = "unknown"))] +impl hyper::rt::Executor for Executor + where + T: Future + Send + 'static, + T::Output: Send + 'static, +{ + fn execute(&self, future: T) { + async_std::task::spawn(future); + } +} + +/// Initializes the metrics context, and starts an HTTP server +/// to serve metrics. +#[cfg(not(target_os = "unknown"))] +pub async fn init_prometheus(prometheus_addr: SocketAddr, registry: Registry) -> Result<(), Error>{ + use networking::Incoming; + let listener = async_std::net::TcpListener::bind(&prometheus_addr) + .await + .map_err(|_| Error::PortInUse(prometheus_addr))?; + + log::info!("Prometheus server started at {}", prometheus_addr); + + let service = make_service_fn(move |_| { + let registry = registry.clone(); + + async move { + Ok::<_, hyper::Error>(service_fn(move |req: Request| { + request_metrics(req, registry.clone()) + })) + } + }); + + let server = Server::builder(Incoming(listener.incoming())) + .executor(Executor) + .serve(service) + .boxed(); + + let result = server.await.map_err(Into::into); + + result +} + +#[cfg(target_os = "unknown")] +pub async fn init_prometheus(_: SocketAddr, _registry: Registry) -> Result<(), Error> { + Ok(()) +} diff --git a/utils/grafana-data-source/src/networking.rs b/utils/prometheus/src/networking.rs similarity index 100% rename from utils/grafana-data-source/src/networking.rs rename to utils/prometheus/src/networking.rs