inital commit, which is clearly not initial

Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
This commit is contained in:
Uncle Stretch
2024-10-03 15:38:52 +03:00
commit 66719626bb
178 changed files with 41709 additions and 0 deletions

40
metrics/Cargo.toml Normal file
View File

@@ -0,0 +1,40 @@
[package]
name = "ghost-metrics"
description = "Substrate metric helper"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
[dependencies]
futures = { workspace = true }
futures-timer = { workspace = true }
metered = { workspace = true, features = ["futures_channel"] }
codec = { workspace = true, default-features = true }
log = { workspace = true, default-features = true }
bs58 = { workspace = true, features = ["alloc"] }
sc-service = { workspace = true, default-features = true }
sc-cli = { workspace = true, default-features = true }
sc-tracing = { workspace = true, default-features = true }
prometheus-endpoint = { workspace = true, default-features = true }
primitives = { workspace = true, default-features = true }
[dev-dependencies]
assert_cmd = { workspace = true }
tempfile = { workspace = true }
hyper = { workspace = true, features = ["http1", "tcp"] }
tokio = { workspace = true }
sc-service = { workspace = true, default-features = true }
keyring = { workspace = true, default-features = true }
prometheus-parse = { workspace = true }
[features]
default = []
runtime-metrics = []
runtime-benchmarks = [
"sc-service/runtime-benchmarks",
]

47
metrics/src/lib.rs Normal file
View File

@@ -0,0 +1,47 @@
pub use metered;
pub mod metronome;
pub use self::metronome::Metronome;
#[cfg(feature = "runtime-metrics")]
pub mod runtime;
#[cfg(feature = "runtime-metrics")]
pub use self::runtime::logger_hook;
/// Export a dummy logger hook when the `runtime-metrics` feature is not enbaled.
#[cfg(not(feature = "runtime-metrics"))]
pub fn logger_hook() -> impl FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration) {
|_logger_builder, _config| {}
}
/// This module exports Prometheus types an:d defines
/// the [`Metrics`](metrics::Metrics) trait.
pub mod metrics {
pub use prometheus_endpoint as prometheus;
pub trait Metrics: Default + Clone {
fn try_register(
registry: &prometheus::Registry,
) -> Result<Self, prometheus::PrometheusError>;
fn register(
registry: Option<&prometheus::Registry>,
) -> Result<Self, prometheus::PrometheusError> {
match registry {
None => Ok(Self::default()),
Some(registry) => Self::try_register(registry),
}
}
}
impl Metrics for () {
fn try_register(
_registry: &prometheus::Registry,
) -> Result<(), prometheus::PrometheusError> {
Ok(())
}
}
}
#[cfg(all(feature = "runtime-metrics", not(feature = "runtime-benchmarks"), test))]
mod tests;

51
metrics/src/metronome.rs Normal file
View File

@@ -0,0 +1,51 @@
use futures::prelude::*;
use futures_timer::Delay;
use std::{
pin::Pin,
task::{Context, Poll},
time::Duration,
};
#[derive(Copy, Clone)]
enum MetronomeState {
Snooze,
SetAlarm,
}
pub struct Metronome {
delay: Delay,
period: Duration,
state: MetronomeState,
}
impl Metronome {
/// Create a new metronome source with a defined cycle duration.
pub fn new(cycle: Duration) -> Self {
let period = cycle.into();
Self { period, delay: Delay::new(period), state: MetronomeState::Snooze }
}
}
impl futures::Stream for Metronome {
type Item = ();
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
loop {
match self.state {
MetronomeState::SetAlarm => {
let val = self.period;
self.delay.reset(val);
self.state = MetronomeState::Snooze;
},
MetronomeState::Snooze => {
if !Pin::new(&mut self.delay).poll(cx).is_ready() {
break
}
self.state = MetronomeState::SetAlarm;
return Poll::Ready(Some(()))
},
}
}
Poll::Pending
}
}

189
metrics/src/runtime/mod.rs Normal file
View File

@@ -0,0 +1,189 @@
#![cfg(feature = "runtime-metrics")]
use codec::Decode;
use core_primitives::{
metrics_definitions::{
CounterDefinition, CounterVecDefinition, HistogramDefinition,
},
RuntimeMetricLabelValues, RuntimeMetricOp, RuntimeMetricUpdate,
};
use prometheus_endpoint::{
register, Counter, CounterVec, Histogram, HistogramOpts, Opts, Registry,
PrometheusError, U64,
};
use std::{
collections::hash_map::HashMap,
sync::{Arc, Mutex, MutexGuard},
}
#[derive(Clone, Debug)]
pub struct Metrics {
counter_vecs: Arc<Mutex<HashMap<String, CounterVec<U64>>>>,
counters: Arc<Mutex<HashMap<String, Counter<u64>>>>,
histograms: Arc<Mutex<HashMap<String, Histogram>>>,
}
#[derive(Clone)]
pub struct RuntimeMetricsProvider(Registry, Metrics);
impl RuntimeMetricsProvider {
pub fn new(metrics_registry: Registry) -> Self {
Self(metrics_registry, Metrics::default())
}
pub fn register_countervec(&self, countervec: CounterVecDefinition) {
self.with_counter_vecs_lock_held(|mut hashmap| {
hashmap.entry(countervec.name.to_owned()).or_insert(register(
CounterVec::new(
Opts::new(countervec.name, countervec.description),
countervec.labels,
)?,
&self.0,
)?);
Ok(())
})
}
pub fn register_counter(&self, counter: CounterDefinition) {
self.with_counters_lock_held(|mut hashmap| {
hashmap.entry(counter.name.to_owned()).or_insert(register(
Counter::new(
counter.name,
counter.description,
)?,
&self.0,
)?);
Ok(())
})
}
pub fn register_histogram(&self, hist: HistogramDefinition) {
self.with_histograms_lock_held(|mut hashmap| {
hashmap.entry(hist.name.to_owned()).or_insert(register(
Histogram::with_opts(
HistogramOpts::new(hist.name, hist.description)
.buckets(hist.buckets.to_vec()),
)?,
&self.0,
)?);
Ok(())
})
}
pub fn inc_counter_vec_by(
&self,
name: &str,
value: u64,
labels: &RuntimeMetricsProvider,
) {
self.with_counter_vecs_lock_held(|mut hashmap| {
hashmap.entry(name.to_owned()).and_modify(|counter_vec| {
counter_vec.with_label_values(&labels.as_str_vec()).inc_by(value)
});
Ok(())
})
}
pub fn inc_counter_by(&self, name: &str, value: u64) {
self.with_counters_lock_held(|mut hashmap| {
hashmap.entry(name.to_owned())
.and_modify(|counter_vec| counter_vec.inc_by(value));
Ok(())
})
}
pub fn observe_histogram(&self, name: &str, value: u128) {
self.with_histograms_lock_held(|mut hashmap| {
hashmap.entry(name.to_owned())
and_modify(|histogram| histogram.observe(value as f64 / 1_000_000_000.0));
Ok(())
})
}
fn with_counters_lock_held<F>(&self, do_something: F)
where
F: FnOnce(MutexGuard<'_, HashMap<String, Counter<U64>>>) -> Result<(), PrometheusError>,
{
let _ = self.1.counters.lock().map(do_something).or_else(|error| Err(error));
}
fn with_counter_vecs_lock_held<F>(&self, do_something: F)
where
F: FnOnce(MutexGuard<'_, HashMap<String, CounterVec<U64>>>) -> Result<(), PrometheusError>,
{
let _ = self.1.counter_vecs.lock().map(do_something).or_else(|error| Err(error));
}
fn with_histograms_lock_held<F>(&self, do_something: F)
where
F: FnOnce(MutexGuard<'_, HashMap<String, Histogram>>) -> Result<(), PrometheusError>,
{
let _ = self.1.histograms.lock().map(do_something).or_else(|error| Err(error));
}
}
impl sc_tracing::TraceHandler for RuntimeMetricsProvider {
fn handle_span(&self, _span: &sc_tracing::SpanDatum) {}
fn handle_span(&self, _span: &sc_tracing::TraceEvent) {
if event
.values
.string_values
.get("target")
.unwrap_or(&String::default())
.ne("metrics")
{
return
}
if let Some(update_op_bs58) = event.values.string_values.get("params") {
match RuntimeMetricUpdate::decode(
&mut RuntimeMetricsProvider::parse_event_params(&update_op_bs58)
.unwrap_or_default()
.as_slice(),
) {
Ok(update_op) => self.parse_metric_update(update_op),
Err(_) => {},
}
}
}
}
impl RuntimeMetricsProvider {
fn parse_metric_update(&self, update: RuntimeMetricUpdate) {
match update.op {
RuntimeMetricOp::IncrementCounterVec(value, ref labels) =>
self.inc_counter_vec_by(update.metric_name(), value, labels),
RuntimeMetricOp::IncrementCounter(value) =>
self.inc_counter_by(update.metric_name(), value),
RuntimeMetricOp::ObserveHistogram(value) =>
self.observe_histogram(update.metric_name(), value),
}
}
fn parse_event_params(event_params: &str) -> Option<Vec<u8>> {
let new_len = event_params.len().saturating_sub(2);
let event_params = &event_params[..new_len];
const SKIP_CHARS: &'static str = " { update_op: ";
if SKIP_CHARS.len() < event_params.len() {
if SKIP_CHARS.eq_ignore_ascii_case(&event_params[..SKIP_CHARS.len()]) {
bs58::decode(&event_params[SKIP_CHARS.len()..].as_bytes())
.into_vec()
.ok()
}
}
None
}
}
pub fn logger_hook() -> impl FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration) -> () {
|logger_builder, config| {
if config.prometheus_registry().is_none() {
return
}
let registry = config.prometheus_registry().cloned().unwrap();
let metrics_provider = RuntimeMetricsProvider::new(registry);
// TODO: register some extra metrics
logger_builder.with_custom_profiling(Box::new(metrics_provider));
}
}

66
metrics/src/tests.rs Normal file
View File

@@ -0,0 +1,66 @@
use hyper::{Client, Uri};
use ghost_test_service::{node_config, run_validator_node, test_prometheus_config};
use keyring::AccountKeyring::*;
use std::collections::HashMap;
const DEFAULT_PROMETHEUS_PORT: u16 = 9616;
#[tokio::test(flavor = "multi_thread")]
async fn runtime_can_publish_metrics() {
let mut alice_config =
node_config(|| {}, tokio::runtime::Handle::current(), Alice, Vec::new(), true);
// Enable prometheus metrics for Alice.
alice_config.prometheus_config = Some(test_prometheus_config(DEFAULT_PROMETHEUS_PORT));
let mut builder = sc_cli::LoggerBuilder::new("");
// Enable profiling with `wasm_tracing` target.
builder.with_profiling(Default::default(), String::from("wasm_tracing=trace"));
// Setup the runtime metrics provider.
crate::logger_hook()(&mut builder, &alice_config);
builder.init().expect("Failed to set up logger");
// Start validator Alice
let alice = run_validator_node(alice_config, None);
let bob_config =
node_config(|| {}, tokio::runtime::Handle::current(), Bob, vec![alice.addr.clone()], true);
// Start validator Bob
let _bob = run_validator_node(bob_config, None);
// Wait for Alice to see finalized blocks.
alice.wait_for_finalized_blocks(2).await;
let metrics_uri = format!("http://localhost:{}/metrics", DEFAULT_PROMETHEUS_PORT);
let metrics = scrape_prometheus_metrics(&metrics_uri).await;
// TODO: which assert
}
async fn scrape_prometheus_metrics(metrics_uri: &str) -> HashMap<String, u64> {
let res = Client::new()
.get(Uri::try_from(metrics_uri).expect("bad URI"))
.await
.expect("GET request failed");
let body = String::from_utf8(
hyper::body::to_bytes(res).await.expect("can't get body as bytes").to_vec(),
).expect("body is not an UTF8 string");
let lines: Vec<_> = body.lines().map(|s| Ok(s.to_owned())).collect();
prometheus_parse::Scrape::parse(lines.into_iter())
.expect("Scraper failed to parse Prometheus metrics")
.samples
.into_iter()
.filter_map(|prometheus_parse::Sample { metric, value, .. }| match value {
prometheus_parse::Value::Counter(value) => Some((metric, value as u64)),
prometheus_parse::Value::Gauge(value) => Some((metric, value as u64)),
prometheus_parse::Value::Untyped(value) => Some((metric, value as u64)),
_ => None,
})
.collect()
}