From 737558ef532ac4a4e4482bcafe1a8f7fc394aa2a Mon Sep 17 00:00:00 2001 From: Yingwen Date: Fri, 28 Mar 2025 10:18:59 +0800 Subject: [PATCH] fix: support __name__ matcher in label values (#5773) --- src/servers/src/http/prometheus.rs | 25 +++++++++++++++++++++++-- tests-integration/tests/http.rs | 13 +++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/servers/src/http/prometheus.rs b/src/servers/src/http/prometheus.rs index 7ac2ccd3d2..fc823f8e76 100644 --- a/src/servers/src/http/prometheus.rs +++ b/src/servers/src/http/prometheus.rs @@ -32,6 +32,7 @@ use datatypes::scalars::ScalarVector; use datatypes::vectors::Float64Vector; use futures::future::join_all; use futures::StreamExt; +use itertools::Itertools; use promql_parser::label::{MatchOp, Matcher, Matchers, METRIC_NAME}; use promql_parser::parser::value::ValueType; use promql_parser::parser::{ @@ -1003,18 +1004,19 @@ pub async fn label_values_query( for query in queries { let promql_expr = try_call_return_response!(promql_parser::parser::parse(&query)); - let PromqlExpr::VectorSelector(VectorSelector { name, matchers, .. }) = promql_expr else { + let PromqlExpr::VectorSelector(mut vector_selector) = promql_expr else { return PrometheusJsonResponse::error( StatusCode::InvalidArguments, "expected vector selector", ); }; - let Some(name) = name else { + let Some(name) = take_metric_name(&mut vector_selector) else { return PrometheusJsonResponse::error( StatusCode::InvalidArguments, "expected metric name", ); }; + let VectorSelector { matchers, .. } = vector_selector; // Only use and filter matchers. let matchers = matchers.matchers; let result = handler @@ -1048,6 +1050,25 @@ pub async fn label_values_query( PrometheusJsonResponse::success(PrometheusResponse::LabelValues(label_values)) } +/// Take metric name from the [VectorSelector]. +/// It takes the name in the selector or removes the name matcher. +fn take_metric_name(selector: &mut VectorSelector) -> Option { + if let Some(name) = selector.name.take() { + return Some(name); + } + + let (pos, matcher) = selector + .matchers + .matchers + .iter() + .find_position(|matcher| matcher.name == "__name__" && matcher.op == MatchOp::Equal)?; + let name = matcher.value.clone(); + // We need to remove the name matcher to avoid using it as a filter in query. + selector.matchers.matchers.remove(pos); + + Some(name) +} + async fn retrieve_field_names( query_ctx: &QueryContext, manager: CatalogManagerRef, diff --git a/tests-integration/tests/http.rs b/tests-integration/tests/http.rs index 0d2c397e02..90dc059856 100644 --- a/tests-integration/tests/http.rs +++ b/tests-integration/tests/http.rs @@ -682,6 +682,19 @@ pub async fn test_prom_http_api(store_type: StorageType) { serde_json::from_value::(json!(["host1"])).unwrap() ); + // single match[] with __name__ + let res = client + .get("/v1/prometheus/api/v1/label/host/values?match[]={__name__%3D%22demo%22}&start=0&end=300") + .send() + .await; + assert_eq!(res.status(), StatusCode::OK); + let body = serde_json::from_str::(&res.text().await).unwrap(); + assert_eq!(body.status, "success"); + assert_eq!( + body.data, + serde_json::from_value::(json!(["host1"])).unwrap() + ); + // single match[] let res = client .get("/v1/prometheus/api/v1/label/idc/values?match[]=demo_metrics_with_nanos&start=0&end=600")