Record log request metrics (#1278)

* ˆ

* clippy
pull/1285/head
Mattie Conover 2 years ago committed by GitHub
parent 5929ef1956
commit 0c6e91b914
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      rust/abacus-base/src/metrics/provider.rs
  2. 139
      rust/ethers-prometheus/src/middleware/mod.rs

@ -26,6 +26,16 @@ pub(crate) fn create_provider_metrics(metrics: &CoreMetrics) -> Result<Middlewar
CONTRACT_CALL_COUNT_HELP, CONTRACT_CALL_COUNT_HELP,
CONTRACT_CALL_COUNT_LABELS, CONTRACT_CALL_COUNT_LABELS,
)?) )?)
.logs_query_duration_seconds(metrics.new_counter(
"logs_query_duration_seconds",
LOGS_QUERY_DURATION_SECONDS_HELP,
LOGS_QUERY_DURATION_SECONDS_LABELS,
)?)
.logs_query_count(metrics.new_int_counter(
"logs_query_count",
LOG_QUERY_COUNT_HELP,
LOGS_QUERY_COUNT_LABELS,
)?)
.transaction_send_duration_seconds(metrics.new_histogram( .transaction_send_duration_seconds(metrics.new_histogram(
"transaction_send_duration_seconds", "transaction_send_duration_seconds",
TRANSACTION_SEND_DURATION_SECONDS_HELP, TRANSACTION_SEND_DURATION_SECONDS_HELP,

@ -1,6 +1,7 @@
//! A middleware layer which collects metrics about operations made with a //! A middleware layer which collects metrics about operations made with a
//! provider. //! provider.
use std::clone::Clone;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::future::Future; use std::future::Future;
@ -9,6 +10,7 @@ use std::time::{Duration, Instant};
use async_trait::async_trait; use async_trait::async_trait;
use derive_builder::Builder; use derive_builder::Builder;
use ethers::abi::AbiEncode;
use ethers::prelude::*; use ethers::prelude::*;
use ethers::types::transaction::eip2718::TypedTransaction; use ethers::types::transaction::eip2718::TypedTransaction;
use ethers::utils::hex::ToHex; use ethers::utils::hex::ToHex;
@ -105,6 +107,34 @@ pub const CONTRACT_CALL_COUNT_LABELS: &[&str] = &[
/// Help string for the metric. /// Help string for the metric.
pub const CONTRACT_CALL_COUNT_HELP: &str = "Contract invocations by contract and function"; pub const CONTRACT_CALL_COUNT_HELP: &str = "Contract invocations by contract and function";
/// Expected label names for the `log_query_duration_seconds` metric.
pub const LOGS_QUERY_DURATION_SECONDS_LABELS: &[&str] = &[
"chain",
"contract_name",
"address",
"topic0",
"topic1",
"topic2",
"topic3",
];
/// Help string for the metric.
pub const LOGS_QUERY_DURATION_SECONDS_HELP: &str = "Log query durations by address and topic.";
/// Expected label names for the `log_query_count` metric.
pub const LOGS_QUERY_COUNT_LABELS: &[&str] = &[
"chain",
"contract_name",
"address",
"topic0",
"topic1",
"topic2",
"topic3",
];
/// Help string for the metric.
pub const LOG_QUERY_COUNT_HELP: &str = "Discrete number of log queries by address and topic.";
/// Expected label names for the `transaction_send_duration_seconds` metric. /// Expected label names for the `transaction_send_duration_seconds` metric.
pub const TRANSACTION_SEND_DURATION_SECONDS_LABELS: &[&str] = &["chain", "address_from"]; pub const TRANSACTION_SEND_DURATION_SECONDS_LABELS: &[&str] = &["chain", "address_from"];
/// Help string for the metric. /// Help string for the metric.
@ -165,6 +195,30 @@ pub struct MiddlewareMetrics {
#[builder(setter(into, strip_option), default)] #[builder(setter(into, strip_option), default)]
contract_call_count: Option<IntCounterVec>, contract_call_count: Option<IntCounterVec>,
/// Log query durations by address and topic.
/// - `chain`: the chain name (or chain ID if the name is unknown) of the
/// chain the logs were queried on.
/// - `contract_name`: name of the address if it is a known contract.
/// - `address`: address being filtered for log events (hex).
/// - `topic0`: topic 0 being filtered for; empty if not specified (hex).
/// - `topic1`: topic 1 being filtered for; empty if not specified (hex).
/// - `topic2`: topic 2 being filtered for; empty if not specified (hex).
/// - `topic3`: topic 3 being filtered for; empty if not specified (hex).
#[builder(setter(into, strip_option), default)]
logs_query_duration_seconds: Option<CounterVec>,
/// Discrete number of log queries by address and topic.
/// - `chain`: the chain name (or chain ID if the name is unknown) of the
/// chain the logs were queried on.
/// - `contract_name`: name of the address if it is a known contract.
/// - `address`: address being filtered for log events (hex).
/// - `topic0`: topic 0 being filtered for; empty if not specified (hex).
/// - `topic1`: topic 1 being filtered for; empty if not specified (hex).
/// - `topic2`: topic 2 being filtered for; empty if not specified (hex).
/// - `topic3`: topic 3 being filtered for; empty if not specified (hex).
#[builder(setter(into, strip_option), default)]
logs_query_count: Option<IntCounterVec>,
/// Time taken to submit the transaction (not counting time for it to be /// Time taken to submit the transaction (not counting time for it to be
/// included). /// included).
/// - `chain`: the chain name (or chain ID if the name is unknown) of the /// - `chain`: the chain name (or chain ID if the name is unknown) of the
@ -361,6 +415,91 @@ impl<M: Middleware> Middleware for PrometheusMiddleware<M> {
Ok(result?) Ok(result?)
} }
async fn get_logs(&self, filter: &Filter) -> Result<Vec<Log>, Self::Error> {
let start = Instant::now();
let result = self.inner.get_logs(filter).await;
if self.metrics.logs_query_duration_seconds.is_some()
|| self.metrics.logs_query_count.is_some()
{
let data = self.conf.read().await;
let lookup_name = |addr: &Address| -> String {
data.contracts
.get(addr)
.and_then(|c| c.name.as_deref())
.unwrap_or("unknown")
.to_owned()
};
let to_csv_str = |mut acc: String, i: String| {
acc.push(',');
acc += &i;
acc
};
let chain = chain_name(&data.chain);
let (address, contract_name) = filter
.address
.as_ref()
.map(|addr| match addr {
ValueOrArray::Value(v) => {
let name = lookup_name(v);
let addr = v.encode_hex();
(addr, name)
}
ValueOrArray::Array(a) => {
let addrs = a
.iter()
.map(ToHex::encode_hex::<String>)
.reduce(to_csv_str)
.expect("Array is empty");
let names = a
.iter()
.map(|i| lookup_name(i))
.reduce(to_csv_str)
.expect("Array is empty");
(addrs, names)
}
})
.unwrap_or_else(|| ("*".to_owned(), "unknown".to_owned()));
let topic_name = |v: &Option<H256>| -> String {
v.map(|h| h.encode_hex()).unwrap_or_else(|| "*".to_owned())
};
let topic_str = |n: usize| -> String {
filter.topics[n]
.as_ref()
.map(|t| match t {
Topic::Value(v) => topic_name(v),
Topic::Array(a) => a
.iter()
.map(topic_name)
.reduce(to_csv_str)
.expect("Array is empty"),
})
.unwrap_or_else(|| "*".to_owned())
};
let topic0 = topic_str(0);
let topic1 = topic_str(1);
let topic2 = topic_str(2);
let topic3 = topic_str(3);
let labels = hashmap! {
"chain" => chain,
"contract_name" => &contract_name,
"address" => &address,
"topic0" => &topic0,
"topic1" => &topic1,
"topic2" => &topic2,
"topic3" => &topic3
};
if let Some(m) = &self.metrics.logs_query_count {
m.with(&labels).inc();
}
if let Some(m) = &self.metrics.logs_query_duration_seconds {
m.with(&labels)
.inc_by((Instant::now() - start).as_secs_f64());
}
}
Ok(result?)
}
} }
impl<M> PrometheusMiddleware<M> { impl<M> PrometheusMiddleware<M> {

Loading…
Cancel
Save