diff options
author | Alex Auvolat <lx@deuxfleurs.fr> | 2025-01-31 17:51:50 +0100 |
---|---|---|
committer | Alex Auvolat <lx@deuxfleurs.fr> | 2025-02-03 18:54:51 +0100 |
commit | 6a1079c4129157ae6c6e2a94b10d9c2b8f91c5b6 (patch) | |
tree | 8eb49504a176eca8c140fe7c569d98e72e38eee3 /src/api/admin/special.rs | |
parent | b1629dd355806f40669d5d00db4e8e8f86a3fae2 (diff) | |
download | garage-6a1079c4129157ae6c6e2a94b10d9c2b8f91c5b6.tar.gz garage-6a1079c4129157ae6c6e2a94b10d9c2b8f91c5b6.zip |
admin api: impl RequestHandler for MetricsRequest
Diffstat (limited to 'src/api/admin/special.rs')
-rw-r--r-- | src/api/admin/special.rs | 110 |
1 files changed, 75 insertions, 35 deletions
diff --git a/src/api/admin/special.rs b/src/api/admin/special.rs index 4717238d..79f1f4d7 100644 --- a/src/api/admin/special.rs +++ b/src/api/admin/special.rs @@ -7,12 +7,15 @@ use http::header::{ }; use hyper::{Response, StatusCode}; +#[cfg(feature = "metrics")] +use prometheus::{Encoder, TextEncoder}; + use garage_model::garage::Garage; use garage_rpc::system::ClusterHealthStatus; use garage_api_common::helpers::*; -use crate::api::{CheckDomainRequest, HealthRequest, OptionsRequest}; +use crate::api::{CheckDomainRequest, HealthRequest, MetricsRequest, OptionsRequest}; use crate::api_server::ResBody; use crate::error::*; use crate::{Admin, RequestHandler}; @@ -37,6 +40,77 @@ impl RequestHandler for OptionsRequest { } #[async_trait] +impl RequestHandler for MetricsRequest { + type Response = Response<ResBody>; + + async fn handle( + self, + _garage: &Arc<Garage>, + admin: &Admin, + ) -> Result<Response<ResBody>, Error> { + #[cfg(feature = "metrics")] + { + use opentelemetry::trace::Tracer; + + let mut buffer = vec![]; + let encoder = TextEncoder::new(); + + let tracer = opentelemetry::global::tracer("garage"); + let metric_families = tracer.in_span("admin/gather_metrics", |_| { + admin.exporter.registry().gather() + }); + + encoder + .encode(&metric_families, &mut buffer) + .ok_or_internal_error("Could not serialize metrics")?; + + Ok(Response::builder() + .status(StatusCode::OK) + .header(http::header::CONTENT_TYPE, encoder.format_type()) + .body(bytes_body(buffer.into()))?) + } + #[cfg(not(feature = "metrics"))] + Err(Error::bad_request( + "Garage was built without the metrics feature".to_string(), + )) + } +} + +#[async_trait] +impl RequestHandler for HealthRequest { + type Response = Response<ResBody>; + + async fn handle( + self, + garage: &Arc<Garage>, + _admin: &Admin, + ) -> Result<Response<ResBody>, Error> { + let health = garage.system.health(); + + let (status, status_str) = match health.status { + ClusterHealthStatus::Healthy => (StatusCode::OK, "Garage is fully operational"), + ClusterHealthStatus::Degraded => ( + StatusCode::OK, + "Garage is operational but some storage nodes are unavailable", + ), + ClusterHealthStatus::Unavailable => ( + StatusCode::SERVICE_UNAVAILABLE, + "Quorum is not available for some/all partitions, reads and writes will fail", + ), + }; + let status_str = format!( + "{}\nConsult the full health check API endpoint at /v2/GetClusterHealth for more details\n", + status_str + ); + + Ok(Response::builder() + .status(status) + .header(http::header::CONTENT_TYPE, "text/plain") + .body(string_body(status_str))?) + } +} + +#[async_trait] impl RequestHandler for CheckDomainRequest { type Response = Response<ResBody>; @@ -109,37 +183,3 @@ async fn check_domain(garage: &Arc<Garage>, domain: &str) -> Result<bool, Error> None => Ok(false), } } - -#[async_trait] -impl RequestHandler for HealthRequest { - type Response = Response<ResBody>; - - async fn handle( - self, - garage: &Arc<Garage>, - _admin: &Admin, - ) -> Result<Response<ResBody>, Error> { - let health = garage.system.health(); - - let (status, status_str) = match health.status { - ClusterHealthStatus::Healthy => (StatusCode::OK, "Garage is fully operational"), - ClusterHealthStatus::Degraded => ( - StatusCode::OK, - "Garage is operational but some storage nodes are unavailable", - ), - ClusterHealthStatus::Unavailable => ( - StatusCode::SERVICE_UNAVAILABLE, - "Quorum is not available for some/all partitions, reads and writes will fail", - ), - }; - let status_str = format!( - "{}\nConsult the full health check API endpoint at /v2/GetClusterHealth for more details\n", - status_str - ); - - Ok(Response::builder() - .status(status) - .header(http::header::CONTENT_TYPE, "text/plain") - .body(string_body(status_str))?) - } -} |