use axum_prometheus::{
metrics::{describe_counter, describe_gauge, describe_histogram, gauge, Unit},
metrics_exporter_prometheus::{Matcher, PrometheusBuilder, PrometheusHandle},
utils::SECONDS_DURATION_BUCKETS,
GenericMetricLayer, PrometheusMetricLayer, AXUM_HTTP_REQUESTS_DURATION_SECONDS,
};
use tokio_util::sync::CancellationToken;
use crate::db::DBPools;
pub const SUBMISSIONS_TOTAL_COUNTER: &str = "submissions_total_count";
pub const SUBMISSIONS_COMPLETED_COUNTER: &str = "submissions_completed_count";
pub const SUBMISSIONS_FAILED_COUNTER: &str = "submissions_failed_count";
pub const SUBMISSIONS_DURATION_COMPLETE_HISTOGRAM: &str = "submissions_complete_duration_seconds";
pub const SUBMISSIONS_DURATION_FAIL_HISTOGRAM: &str = "submissions_fail_duration_seconds";
pub const CHUNKS_COMPLETED_COUNTER: &str = "chunks_completed_count";
pub const CHUNKS_FAILED_COUNTER: &str = "chunks_failed_count";
pub const CHUNKS_RETRIED_COUNTER: &str = "chunks_retried_count";
pub const CHUNKS_SKIPPED_COUNTER: &str = "chunks_skipped_count";
pub const CHUNKS_TOTAL_COUNTER: &str = "chunks_total_count";
pub const CHUNKS_BACKLOG_GAUGE: &str = "chunks_in_backlog_count";
pub const CHUNKS_DURATION_COMPLETED_HISTOGRAM: &str = "chunks_duration_completed_seconds";
pub const CHUNKS_DURATION_FAILED_HISTOGRAM: &str = "chunks_duration_failure_seconds";
pub const RESERVER_RESERVATIONS_SUCCEEDED_COUNTER: &str = "reserver_reservations_succeeded_count";
pub const RESERVER_RESERVATIONS_FAILED_COUNTER: &str = "reserver_reservations_failed_count";
pub const RESERVER_CHUNKS_RESERVED_GAUGE: &str = "reserver_chunks_reserved_count";
pub const CONSUMERS_CONNECTED_GAUGE: &str = "consumers_connected_count";
pub const CONSUMER_FETCH_AND_RESERVE_CHUNKS_HISTOGRAM: &str =
"consumer_fetch_and_reserve_chunks_duration_seconds";
pub const CONSUMER_COMPLETE_CHUNK_DURATION: &str = "consumer_complete_chunk_duration_seconds";
pub const CONSUMER_FAIL_CHUNK_DURATION: &str = "consumer_fail_chunk_duration_seconds";
pub const OPERATIONS_BACKLOG_GAUGE: &str = "operations_in_backlog_count";
pub fn describe_metrics() {
describe_counter!(SUBMISSIONS_TOTAL_COUNTER, Unit::Count, "Total count of submissions (in backlog + completed + failed), i.e. total that ever entered the system");
describe_counter!(
SUBMISSIONS_COMPLETED_COUNTER,
Unit::Count,
"Number of submissions completed successfully"
);
describe_counter!(
SUBMISSIONS_FAILED_COUNTER,
Unit::Count,
"Number of submissions failed permanently"
);
describe_histogram!(SUBMISSIONS_DURATION_COMPLETE_HISTOGRAM, Unit::Seconds, "Time between a submission entering the system and its final chunk being completed. Does not count failed submissions.");
describe_counter!(
CHUNKS_COMPLETED_COUNTER,
Unit::Count,
"Number of chunks completed"
);
describe_counter!(
CHUNKS_FAILED_COUNTER,
Unit::Count,
"Number of chunks failed permanently (retries exhausted). Does not include skipped chunks"
);
describe_counter!(
CHUNKS_RETRIED_COUNTER,
Unit::Count,
"Number of chunks that failed temporarily and will be retried"
);
describe_counter!(
CHUNKS_SKIPPED_COUNTER,
Unit::Count,
"Number of chunks skipped (because another chunk in the submission failed)"
);
describe_counter!(CHUNKS_TOTAL_COUNTER, Unit::Count, "Total count of chunks (in backlog + completed + failed), i.e. total that ever entered the system");
describe_gauge!(CHUNKS_BACKLOG_GAUGE, Unit::Count, "Number of chunks in the backlog. Note that this is a _gauge_ reflecting the accurate state of the DB");
describe_histogram!(CHUNKS_DURATION_COMPLETED_HISTOGRAM, Unit::Seconds, "How long it took from the moment a chunk was reserved until 'complete_chunk' was called, as measured from Opsqueue (so including network overhead and reading/writing to the object_store to get the chunk data)");
describe_histogram!(CHUNKS_DURATION_FAILED_HISTOGRAM, Unit::Seconds, "How long it took from the moment a chunk was reserved until 'fail_chunk' was called, as measured from Opsqueue (so including network overhead and reading/writing to the object_store to get the chunk data). Includes chunks that are retried.");
describe_gauge!(RESERVER_CHUNKS_RESERVED_GAUGE, Unit::Count, "Number of chunks currently reserved by the reserver, i.e. being worked on by the consumers");
describe_gauge!(
CONSUMERS_CONNECTED_GAUGE,
Unit::Count,
"Number of healthy websocket connections between the system and consumers"
);
describe_histogram!(CONSUMER_FETCH_AND_RESERVE_CHUNKS_HISTOGRAM, Unit::Seconds, "Time spent by Opsqueue (SQLite + reserver) to reserve `limit` chunks for a consumer using strategy `strategy`");
describe_histogram!(
CONSUMER_COMPLETE_CHUNK_DURATION,
Unit::Seconds,
"Time spent by Opsqueue to mark a given chunk as completed"
);
describe_histogram!(
CONSUMER_FAIL_CHUNK_DURATION,
Unit::Seconds,
"Time spent by Opsqueue to mark a given chunk as failed"
);
describe_gauge!(
OPERATIONS_BACKLOG_GAUGE,
Unit::Count,
"Slight overestimation of the number of operations in the backlog (chunk_size * chunk_count for each submission). For simplicity, we always consider the last chunk full. This is a gauge reflecting the rough current DB state."
);
}
pub type PrometheusConfig = (
GenericMetricLayer<'static, PrometheusHandle, axum_prometheus::Handle>,
PrometheusHandle,
);
#[must_use]
pub fn setup_prometheus() -> (
GenericMetricLayer<'static, PrometheusHandle, axum_prometheus::Handle>,
PrometheusHandle,
) {
let metric_layer = PrometheusMetricLayer::new();
let metric_handle = PrometheusBuilder::new()
.set_buckets_for_metric(
Matcher::Full(AXUM_HTTP_REQUESTS_DURATION_SECONDS.to_string()),
SECONDS_DURATION_BUCKETS,
)
.expect("Building Prometheus failed")
.install_recorder()
.expect("Installing global Prometheus recorder failed");
describe_metrics();
(metric_layer, metric_handle)
}
pub fn time_delta_as_f64(td: chrono::TimeDelta) -> f64 {
const NANOS_PER_SEC: f64 = 1_000_000_000.0;
(td.num_seconds() as f64) + (td.subsec_nanos() as f64) / NANOS_PER_SEC
}
pub async fn calculate_scaling_metrics(db_pool: &DBPools) -> anyhow::Result<()> {
let mut conn = db_pool.reader_conn().await?;
let chunks_backlog_count: u64 = crate::common::chunk::db::count_chunks(&mut conn)
.await?
.into();
gauge!(CHUNKS_BACKLOG_GAUGE).set(chunks_backlog_count as f64);
let ops_backlog_count: f64 =
crate::common::chunk::db::count_ops_in_backlog_estimate(&mut conn).await?;
gauge!(OPERATIONS_BACKLOG_GAUGE).set(ops_backlog_count);
Ok(())
}
pub async fn periodically_calculate_scaling_metrics(
db_pool: &DBPools,
cancellation_token: &CancellationToken,
) {
const METRICS_INTERVAL: std::time::Duration = std::time::Duration::from_secs(5);
loop {
tokio::select! {
_ = cancellation_token.cancelled() => break,
_ = tokio::time::sleep(METRICS_INTERVAL) => {
if let Err(e) = calculate_scaling_metrics(db_pool).await {
tracing::error!("Error calculating scaling metrics: {}", e);
}
}
}
}
}