web3-proxy/web3_proxy/src/frontend/status.rs

158 lines
4.8 KiB
Rust
Raw Normal View History

2022-10-18 00:47:58 +03:00
//! Used by admins for health checks and inspecting global statistics.
//!
//! For ease of development, users can currently access these endponts.
//! They will eventually move to another port.
2023-05-17 00:58:00 +03:00
use super::{ResponseCache, ResponseCacheKey};
2023-05-15 20:48:59 +03:00
use crate::app::{Web3ProxyApp, APP_USER_AGENT};
2023-05-17 01:27:18 +03:00
use axum::{
body::{Bytes, Full},
http::StatusCode,
response::{IntoResponse, Response},
Extension,
};
2022-10-27 03:12:42 +03:00
use axum_macros::debug_handler;
use once_cell::sync::Lazy;
2022-09-10 05:59:07 +03:00
use serde_json::json;
2023-05-15 20:48:59 +03:00
use std::{convert::Infallible, sync::Arc};
static HEALTH_OK: Lazy<Bytes> = Lazy::new(|| Bytes::from("OK\n"));
static HEALTH_NOT_OK: Lazy<Bytes> = Lazy::new(|| Bytes::from(":(\n"));
static BACKUPS_NEEDED_TRUE: Lazy<Bytes> = Lazy::new(|| Bytes::from("true\n"));
static BACKUPS_NEEDED_FALSE: Lazy<Bytes> = Lazy::new(|| Bytes::from("false\n"));
2023-05-17 01:27:18 +03:00
static CONTENT_TYPE_JSON: &str = "application/json";
static CONTENT_TYPE_PLAIN: &str = "text/plain";
2022-10-18 00:47:58 +03:00
/// Health check page for load balancers to use.
2022-10-20 01:26:33 +03:00
#[debug_handler]
pub async fn health(
Extension(app): Extension<Arc<Web3ProxyApp>>,
2023-05-17 00:58:00 +03:00
Extension(cache): Extension<Arc<ResponseCache>>,
) -> impl IntoResponse {
2023-05-17 01:27:18 +03:00
let (code, content_type, body) = cache
2023-05-17 00:58:00 +03:00
.get_or_insert_async::<Infallible, _>(&ResponseCacheKey::Health, async move {
2023-05-15 20:48:59 +03:00
Ok(_health(app).await)
})
.await
2023-05-17 01:27:18 +03:00
.unwrap();
Response::builder()
.status(code)
.header("content-type", content_type)
.body(Full::from(body))
.unwrap()
}
2023-05-15 20:48:59 +03:00
// TODO: _health doesn't need to be async, but _quick_cache_ttl needs an async function
#[inline]
2023-05-17 01:27:18 +03:00
async fn _health(app: Arc<Web3ProxyApp>) -> (StatusCode, &'static str, Bytes) {
if app.balanced_rpcs.synced() {
2023-05-17 01:27:18 +03:00
(StatusCode::OK, CONTENT_TYPE_PLAIN, HEALTH_OK.clone())
2022-06-29 21:22:53 +03:00
} else {
2023-05-17 01:27:18 +03:00
(
StatusCode::SERVICE_UNAVAILABLE,
CONTENT_TYPE_PLAIN,
HEALTH_NOT_OK.clone(),
)
2022-06-29 21:22:53 +03:00
}
}
/// Easy alerting if backup servers are in use.
#[debug_handler]
pub async fn backups_needed(
Extension(app): Extension<Arc<Web3ProxyApp>>,
2023-05-17 00:58:00 +03:00
Extension(cache): Extension<Arc<ResponseCache>>,
) -> impl IntoResponse {
2023-05-17 01:27:18 +03:00
let (code, content_type, body) = cache
2023-05-17 00:58:00 +03:00
.get_or_insert_async::<Infallible, _>(&ResponseCacheKey::BackupsNeeded, async move {
Ok(_backups_needed(app).await)
})
2023-05-15 20:48:59 +03:00
.await
2023-05-17 01:27:18 +03:00
.unwrap();
Response::builder()
.status(code)
.header("content-type", content_type)
.body(Full::from(body))
.unwrap()
}
2023-05-15 20:48:59 +03:00
#[inline]
2023-05-17 01:27:18 +03:00
async fn _backups_needed(app: Arc<Web3ProxyApp>) -> (StatusCode, &'static str, Bytes) {
let code = {
let consensus_rpcs = app
.balanced_rpcs
.watch_consensus_rpcs_sender
.borrow()
.clone();
if let Some(ref consensus_rpcs) = consensus_rpcs {
if consensus_rpcs.backups_needed {
StatusCode::INTERNAL_SERVER_ERROR
} else {
StatusCode::OK
}
} else {
// if no consensus, we still "need backups". we just don't have any. which is worse
StatusCode::INTERNAL_SERVER_ERROR
}
};
if matches!(code, StatusCode::OK) {
2023-05-17 01:27:18 +03:00
(code, CONTENT_TYPE_PLAIN, BACKUPS_NEEDED_FALSE.clone())
} else {
2023-05-17 01:27:18 +03:00
(code, CONTENT_TYPE_PLAIN, BACKUPS_NEEDED_TRUE.clone())
}
}
2022-10-18 00:47:58 +03:00
/// Very basic status page.
///
2023-05-15 20:48:59 +03:00
/// TODO: replace this with proper stats and monitoring. frontend uses it for their public dashboards though
2022-10-20 01:26:33 +03:00
#[debug_handler]
2022-11-16 23:17:33 +03:00
pub async fn status(
Extension(app): Extension<Arc<Web3ProxyApp>>,
2023-05-17 00:58:00 +03:00
Extension(cache): Extension<Arc<ResponseCache>>,
2022-11-16 23:17:33 +03:00
) -> impl IntoResponse {
2023-05-17 01:27:18 +03:00
let (code, content_type, body) = cache
2023-05-17 00:58:00 +03:00
.get_or_insert_async::<Infallible, _>(&ResponseCacheKey::Status, async move {
2023-05-15 20:48:59 +03:00
Ok(_status(app).await)
})
.await
2023-05-17 01:27:18 +03:00
.unwrap();
Response::builder()
.status(code)
.header("content-type", content_type)
.body(Full::from(body))
.unwrap()
}
2023-05-15 20:48:59 +03:00
// TODO: _status doesn't need to be async, but _quick_cache_ttl needs an async function
#[inline]
2023-05-17 01:27:18 +03:00
async fn _status(app: Arc<Web3ProxyApp>) -> (StatusCode, &'static str, Bytes) {
// TODO: what else should we include? uptime, cache hit rates, cpu load, memory used
// TODO: the hostname is probably not going to change. only get once at the start?
let body = json!({
"version": APP_USER_AGENT,
"chain_id": app.config.chain_id,
"balanced_rpcs": app.balanced_rpcs,
"private_rpcs": app.private_rpcs,
"bundler_4337_rpcs": app.bundler_4337_rpcs,
"hostname": app.hostname,
});
let body = body.to_string().into_bytes();
let body = Bytes::from(body);
let code = if app.balanced_rpcs.synced() {
StatusCode::OK
} else {
StatusCode::INTERNAL_SERVER_ERROR
};
2022-09-10 05:59:07 +03:00
2023-05-17 01:27:18 +03:00
(code, CONTENT_TYPE_JSON, body)
2022-06-05 22:58:47 +03:00
}