feat(O2): instrument query handler with Prometheus metrics
- Track query requests, errors, auth failures, rate limits - Track embedding failures and embedding call duration - Track result counts, empty results - In-flight gauge for concurrent queries - Timer for query duration histogram - Metrics: Q1-Q12 (12 metrics instrumented)
This commit is contained in:
@@ -118,17 +118,26 @@ pub async fn unified_query_handler(
|
|||||||
body: web::Json<UnifiedQueryRequest>,
|
body: web::Json<UnifiedQueryRequest>,
|
||||||
state: web::Data<AppState>,
|
state: web::Data<AppState>,
|
||||||
) -> HttpResponse {
|
) -> HttpResponse {
|
||||||
|
use crate::metrics::*;
|
||||||
|
QUERY_REQUESTS_TOTAL.inc();
|
||||||
|
QUERY_IN_FLIGHT.inc();
|
||||||
|
let _timer = Timer::new(&QUERY_DURATION);
|
||||||
let start_time = std::time::Instant::now();
|
let start_time = std::time::Instant::now();
|
||||||
|
|
||||||
// 1. Validate JWT + rate limit
|
// 1. Validate JWT + rate limit
|
||||||
if let Err(response) = crate::handlers::middleware::validate_and_rate_limit(
|
if let Err(response) = crate::handlers::middleware::validate_and_rate_limit(
|
||||||
&req, &state, "query", 500
|
&req, &state, "query", 500
|
||||||
) {
|
) {
|
||||||
|
QUERY_AUTH_FAILURES.inc();
|
||||||
|
QUERY_ERRORS_TOTAL.inc();
|
||||||
|
QUERY_IN_FLIGHT.dec();
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Validate input
|
// 2. Validate input
|
||||||
if let Err(response) = validate_unified_request(&body) {
|
if let Err(response) = validate_unified_request(&body) {
|
||||||
|
QUERY_ERRORS_TOTAL.inc();
|
||||||
|
QUERY_IN_FLIGHT.dec();
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,9 +145,16 @@ pub async fn unified_query_handler(
|
|||||||
body.search_type, body.query, body.entity_type, body.relation_type);
|
body.search_type, body.query, body.entity_type, body.relation_type);
|
||||||
|
|
||||||
// 3. Embed query once (reused for all search types)
|
// 3. Embed query once (reused for all search types)
|
||||||
|
let embed_start = std::time::Instant::now();
|
||||||
let query_embedding = match state.embeddings.embed_one(&body.query).await {
|
let query_embedding = match state.embeddings.embed_one(&body.query).await {
|
||||||
Ok(emb) => emb.to_vec(),
|
Ok(emb) => {
|
||||||
|
QUERY_EMBEDDING_DURATION.observe(embed_start.elapsed().as_secs_f64());
|
||||||
|
emb.to_vec()
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
QUERY_EMBEDDING_FAILURES.inc();
|
||||||
|
QUERY_ERRORS_TOTAL.inc();
|
||||||
|
QUERY_IN_FLIGHT.dec();
|
||||||
error!("Embedding failed: {}", e);
|
error!("Embedding failed: {}", e);
|
||||||
return crate::handlers::response_builder::internal_error(
|
return crate::handlers::response_builder::internal_error(
|
||||||
"Failed to embed query"
|
"Failed to embed query"
|
||||||
@@ -152,12 +168,15 @@ pub async fn unified_query_handler(
|
|||||||
"edges" => search_edges(&body, &state, &query_embedding, start_time).await,
|
"edges" => search_edges(&body, &state, &query_embedding, start_time).await,
|
||||||
"hybrid" => search_hybrid(&body, &state, &query_embedding, start_time).await,
|
"hybrid" => search_hybrid(&body, &state, &query_embedding, start_time).await,
|
||||||
_ => {
|
_ => {
|
||||||
|
QUERY_ERRORS_TOTAL.inc();
|
||||||
|
QUERY_IN_FLIGHT.dec();
|
||||||
return crate::handlers::response_builder::bad_request(
|
return crate::handlers::response_builder::bad_request(
|
||||||
"search_type must be 'entities', 'edges', or 'hybrid'"
|
"search_type must be 'entities', 'edges', or 'hybrid'"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
QUERY_IN_FLIGHT.dec();
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,6 +266,10 @@ async fn search_entities(
|
|||||||
|
|
||||||
info!("Unified query (entities): {} results in {}ms", count, elapsed);
|
info!("Unified query (entities): {} results in {}ms", count, elapsed);
|
||||||
|
|
||||||
|
// O2: Track result counts
|
||||||
|
crate::metrics::QUERY_RESULTS_TOTAL.inc_by(count as u64);
|
||||||
|
if count == 0 { crate::metrics::QUERY_EMPTY_RESULTS.inc(); }
|
||||||
|
|
||||||
let response = UnifiedQueryResponse {
|
let response = UnifiedQueryResponse {
|
||||||
query: req.query.clone(),
|
query: req.query.clone(),
|
||||||
search_type: "entities".to_string(),
|
search_type: "entities".to_string(),
|
||||||
@@ -305,6 +328,9 @@ async fn search_edges(
|
|||||||
|
|
||||||
info!("Unified query (edges): {} results in {}ms", count, elapsed);
|
info!("Unified query (edges): {} results in {}ms", count, elapsed);
|
||||||
|
|
||||||
|
crate::metrics::QUERY_RESULTS_TOTAL.inc_by(count as u64);
|
||||||
|
if count == 0 { crate::metrics::QUERY_EMPTY_RESULTS.inc(); }
|
||||||
|
|
||||||
let response = UnifiedQueryResponse {
|
let response = UnifiedQueryResponse {
|
||||||
query: req.query.clone(),
|
query: req.query.clone(),
|
||||||
search_type: "edges".to_string(),
|
search_type: "edges".to_string(),
|
||||||
@@ -350,6 +376,9 @@ async fn search_hybrid(
|
|||||||
|
|
||||||
info!("Unified query (hybrid): {} results in {}ms", count, elapsed);
|
info!("Unified query (hybrid): {} results in {}ms", count, elapsed);
|
||||||
|
|
||||||
|
crate::metrics::QUERY_RESULTS_TOTAL.inc_by(count as u64);
|
||||||
|
if count == 0 { crate::metrics::QUERY_EMPTY_RESULTS.inc(); }
|
||||||
|
|
||||||
let response = UnifiedQueryResponse {
|
let response = UnifiedQueryResponse {
|
||||||
query: req.query.clone(),
|
query: req.query.clone(),
|
||||||
search_type: "hybrid".to_string(),
|
search_type: "hybrid".to_string(),
|
||||||
|
|||||||
Reference in New Issue
Block a user