use std::pin::Pin;
use std::sync::Arc;
use std::task::{ready, Context, Poll};
use std::time::Instant;
use pin_project::pin_project;
use tonic::codegen::http::{Request, Response};
use tonic::codegen::Service;
use tower::Layer;
use crate::routes::health::MetricsRegistry;
const JAMMI_GRPC_PREFIX: &str = "/jammi.v1.";
const FLIGHT_DO_GET_PATH: &str = "/arrow.flight.protocol.FlightService/DoGet";
const EVAL_PREFIX: &str = "/jammi.v1.eval.EvalService/";
const EMBEDDING_SEARCH_PATH: &str = "/jammi.v1.embedding.EmbeddingService/Search";
#[derive(Clone)]
pub struct MetricsLayer {
registry: Arc<MetricsRegistry>,
}
impl MetricsLayer {
pub fn new(registry: Arc<MetricsRegistry>) -> Self {
Self { registry }
}
}
impl<S> Layer<S> for MetricsLayer {
type Service = Metrics<S>;
fn layer(&self, inner: S) -> Self::Service {
Metrics {
inner,
registry: Arc::clone(&self.registry),
}
}
}
#[derive(Clone)]
pub struct Metrics<S> {
inner: S,
registry: Arc<MetricsRegistry>,
}
impl<S, ReqBody, ResBody> Service<Request<ReqBody>> for Metrics<S>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>>,
{
type Response = S::Response;
type Error = S::Error;
type Future = MetricsFuture<S::Future>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
let path = req.uri().path();
if path.starts_with(JAMMI_GRPC_PREFIX) {
self.registry.grpc_requests.inc();
}
if path == FLIGHT_DO_GET_PATH {
self.registry.flight_queries.inc();
}
if path.starts_with(EVAL_PREFIX) {
self.registry.eval_invocations.inc();
}
let timer =
(path == EMBEDDING_SEARCH_PATH).then(|| (Instant::now(), Arc::clone(&self.registry)));
MetricsFuture {
inner: self.inner.call(req),
timer,
}
}
}
#[pin_project]
pub struct MetricsFuture<F> {
#[pin]
inner: F,
timer: Option<(Instant, Arc<MetricsRegistry>)>,
}
impl<F, ResBody, E> std::future::Future for MetricsFuture<F>
where
F: std::future::Future<Output = Result<Response<ResBody>, E>>,
{
type Output = Result<Response<ResBody>, E>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
let response = ready!(this.inner.poll(cx));
if let Some((start, registry)) = this.timer.take() {
registry
.search_latency
.observe(start.elapsed().as_secs_f64());
}
Poll::Ready(response)
}
}