mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-05 00:09:35 +00:00
758 lines
26 KiB
Rust
758 lines
26 KiB
Rust
// Copyright 2023 Zinc Labs Inc.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
use std::io::Error;
|
|
|
|
use actix_web::{get, http, post, web, HttpRequest, HttpResponse};
|
|
use promql_parser::parser;
|
|
|
|
use crate::{
|
|
common::{
|
|
infra::errors,
|
|
meta::{self, http::HttpResponse as MetaHttpResponse},
|
|
utils::time::{parse_milliseconds, parse_str_to_timestamp_micros},
|
|
},
|
|
service::{metrics, promql, promql::MetricsQueryRequest},
|
|
};
|
|
|
|
/// prometheus remote-write endpoint for metrics
|
|
#[utoipa::path(
|
|
context_path = "/api",
|
|
tag = "Metrics",
|
|
operation_id = "PrometheusRemoteWrite",
|
|
security(
|
|
("Authorization"= [])
|
|
),
|
|
params(
|
|
("org_id" = String, Path, description = "Organization name"),
|
|
),
|
|
request_body(content = String, description = "prometheus WriteRequest", content_type = "application/x-protobuf"),
|
|
responses(
|
|
(status = 200, description = "Success", content_type = "application/json", body = IngestionResponse, example = json!({"code": 200})),
|
|
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
|
|
)
|
|
)]
|
|
#[post("/{org_id}/prometheus/api/v1/write")]
|
|
pub async fn remote_write(
|
|
org_id: web::Path<String>,
|
|
thread_id: web::Data<usize>,
|
|
req: HttpRequest,
|
|
body: web::Bytes,
|
|
) -> Result<HttpResponse, Error> {
|
|
let org_id = org_id.into_inner();
|
|
let content_type = req.headers().get("Content-Type").unwrap().to_str().unwrap();
|
|
if content_type == "application/x-protobuf" {
|
|
Ok(
|
|
match metrics::prom::remote_write(&org_id, **thread_id, body).await {
|
|
Ok(_) => HttpResponse::Ok().into(),
|
|
Err(e) => HttpResponse::BadRequest().json(MetaHttpResponse::error(
|
|
http::StatusCode::BAD_REQUEST.into(),
|
|
e.to_string(),
|
|
)),
|
|
},
|
|
)
|
|
} else {
|
|
Ok(HttpResponse::BadRequest().json(MetaHttpResponse::error(
|
|
http::StatusCode::BAD_REQUEST.into(),
|
|
"Bad Request".to_string(),
|
|
)))
|
|
}
|
|
}
|
|
|
|
/// prometheus instant queries
|
|
// refer: https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries
|
|
#[utoipa::path(
|
|
context_path = "/api",
|
|
tag = "Metrics",
|
|
operation_id = "PrometheusQuery",
|
|
security(
|
|
("Authorization"= [])
|
|
),
|
|
params(
|
|
("org_id" = String, Path, description = "Organization name"),
|
|
("query" = String, Query, description = "Prometheus expression query string"),
|
|
("time" = Option<String>, Query, description = "<rfc3339 | unix_timestamp>: Evaluation timestamp. Optional"),
|
|
("timeout" = Option<String>, Query, description = "Evaluation timeout"),
|
|
),
|
|
responses(
|
|
(status = 200, description = "Success", content_type = "application/json", body = HttpResponse, example = json!({
|
|
"status" : "success",
|
|
"data" : {
|
|
"resultType" : "vector",
|
|
"result" : [
|
|
{
|
|
"metric" : {
|
|
"__name__" : "up",
|
|
"job" : "prometheus",
|
|
"instance" : "localhost:9090"
|
|
},
|
|
"value": [ 1435781451.781, "1" ]
|
|
},
|
|
{
|
|
"metric" : {
|
|
"__name__" : "up",
|
|
"job" : "node",
|
|
"instance" : "localhost:9100"
|
|
},
|
|
"value" : [ 1435781451.781, "0" ]
|
|
}
|
|
]
|
|
}
|
|
})),
|
|
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
|
|
)
|
|
)]
|
|
#[get("/{org_id}/prometheus/api/v1/query")]
|
|
pub async fn query_get(
|
|
org_id: web::Path<String>,
|
|
req: web::Query<meta::prom::RequestQuery>,
|
|
) -> Result<HttpResponse, Error> {
|
|
query(&org_id.into_inner(), req.into_inner()).await
|
|
}
|
|
|
|
#[post("/{org_id}/prometheus/api/v1/query")]
|
|
pub async fn query_post(
|
|
org_id: web::Path<String>,
|
|
req: web::Query<meta::prom::RequestQuery>,
|
|
web::Form(form): web::Form<meta::prom::RequestQuery>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let req = if form.query.is_some() {
|
|
form
|
|
} else {
|
|
req.into_inner()
|
|
};
|
|
query(&org_id.into_inner(), req).await
|
|
}
|
|
|
|
async fn query(org_id: &str, req: meta::prom::RequestQuery) -> Result<HttpResponse, Error> {
|
|
let start = match req.time {
|
|
None => chrono::Utc::now().timestamp_micros(),
|
|
Some(v) => match parse_str_to_timestamp_micros(&v) {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
log::error!("parse time error: {}", e);
|
|
return Ok(HttpResponse::BadRequest().json(promql::QueryResponse {
|
|
status: promql::Status::Error,
|
|
data: None,
|
|
error_type: Some("bad_data".to_string()),
|
|
error: Some(e.to_string()),
|
|
}));
|
|
}
|
|
},
|
|
};
|
|
let end = start;
|
|
let timeout = search_timeout(req.timeout);
|
|
|
|
let req = MetricsQueryRequest {
|
|
query: req.query.unwrap_or_default(),
|
|
start,
|
|
end,
|
|
step: 300_000_000, // 5m
|
|
};
|
|
|
|
search(org_id, timeout, &req).await
|
|
}
|
|
|
|
/// prometheus range queries
|
|
// refer: https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries
|
|
#[utoipa::path(
|
|
context_path = "/api",
|
|
tag = "Metrics",
|
|
operation_id = "PrometheusRangeQuery",
|
|
security(
|
|
("Authorization"= [])
|
|
),
|
|
params(
|
|
("org_id" = String, Path, description = "Organization name"),
|
|
("query" = String, Query, description = "Prometheus expression query string"),
|
|
("start" = String, Query, description = "<rfc3339 | unix_timestamp>: Start timestamp, inclusive"),
|
|
("end" = String, Query, description = "<rfc3339 | unix_timestamp>: End timestamp, inclusive"),
|
|
("step" = Option<String>, Query, description = "Query resolution step width in duration format or float number of seconds"),
|
|
("timeout" = Option<String>, Query, description = "Evaluation timeout"),
|
|
),
|
|
responses(
|
|
(status = 200, description = "Success", content_type = "application/json", body = HttpResponse, example = json!({
|
|
"status" : "success",
|
|
"data" : {
|
|
"resultType" : "matrix",
|
|
"result" : [
|
|
{
|
|
"metric" : {
|
|
"__name__" : "up",
|
|
"job" : "prometheus",
|
|
"instance" : "localhost:9090"
|
|
},
|
|
"values" : [
|
|
[ 1435781430.781, "1" ],
|
|
[ 1435781445.781, "1" ],
|
|
[ 1435781460.781, "1" ]
|
|
]
|
|
},
|
|
{
|
|
"metric" : {
|
|
"__name__" : "up",
|
|
"job" : "node",
|
|
"instance" : "localhost:9091"
|
|
},
|
|
"values" : [
|
|
[ 1435781430.781, "0" ],
|
|
[ 1435781445.781, "0" ],
|
|
[ 1435781460.781, "1" ]
|
|
]
|
|
}
|
|
]
|
|
}
|
|
})),
|
|
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
|
|
)
|
|
)]
|
|
#[get("/{org_id}/prometheus/api/v1/query_range")]
|
|
pub async fn query_range_get(
|
|
org_id: web::Path<String>,
|
|
req: web::Query<meta::prom::RequestRangeQuery>,
|
|
) -> Result<HttpResponse, Error> {
|
|
query_range(&org_id.into_inner(), req.into_inner()).await
|
|
}
|
|
|
|
#[post("/{org_id}/prometheus/api/v1/query_range")]
|
|
pub async fn query_range_post(
|
|
org_id: web::Path<String>,
|
|
req: web::Query<meta::prom::RequestRangeQuery>,
|
|
web::Form(form): web::Form<meta::prom::RequestRangeQuery>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let req = if form.query.is_some() {
|
|
form
|
|
} else {
|
|
req.into_inner()
|
|
};
|
|
query_range(&org_id.into_inner(), req).await
|
|
}
|
|
|
|
async fn query_range(
|
|
org_id: &str,
|
|
req: meta::prom::RequestRangeQuery,
|
|
) -> Result<HttpResponse, Error> {
|
|
let start = match req.start {
|
|
None => chrono::Utc::now().timestamp_micros(),
|
|
Some(v) => match parse_str_to_timestamp_micros(&v) {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
log::error!("parse time error: {}", e);
|
|
return Ok(HttpResponse::BadRequest().json(promql::QueryResponse {
|
|
status: promql::Status::Error,
|
|
data: None,
|
|
error_type: Some("bad_data".to_string()),
|
|
error: Some(e.to_string()),
|
|
}));
|
|
}
|
|
},
|
|
};
|
|
let end = match req.end {
|
|
None => chrono::Utc::now().timestamp_micros(),
|
|
Some(v) => match parse_str_to_timestamp_micros(&v) {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
log::error!("parse time error: {}", e);
|
|
return Ok(HttpResponse::BadRequest().json(promql::QueryResponse {
|
|
status: promql::Status::Error,
|
|
data: None,
|
|
error_type: Some("bad_data".to_string()),
|
|
error: Some(e.to_string()),
|
|
}));
|
|
}
|
|
},
|
|
};
|
|
let mut step = match req.step {
|
|
None => 0,
|
|
Some(v) => match parse_milliseconds(&v) {
|
|
Ok(v) => (v * 1_000) as i64,
|
|
Err(e) => {
|
|
log::error!("parse time error: {}", e);
|
|
return Ok(HttpResponse::BadRequest().json(promql::QueryResponse {
|
|
status: promql::Status::Error,
|
|
data: None,
|
|
error_type: Some("bad_data".to_string()),
|
|
error: Some(e.to_string()),
|
|
}));
|
|
}
|
|
},
|
|
};
|
|
if step == 0 {
|
|
// Grafana: Time range / max data points = step
|
|
step = (end - start) / promql::MAX_DATA_POINTS;
|
|
}
|
|
if step < promql::micros(promql::MINIMAL_INTERVAL) {
|
|
step = promql::micros(promql::MINIMAL_INTERVAL);
|
|
}
|
|
|
|
let timeout = search_timeout(req.timeout);
|
|
|
|
let req = MetricsQueryRequest {
|
|
query: req.query.unwrap_or_default(),
|
|
start,
|
|
end,
|
|
step,
|
|
};
|
|
search(org_id, timeout, &req).await
|
|
}
|
|
|
|
/// prometheus query metric metadata
|
|
// refer: https://prometheus.io/docs/prometheus/latest/querying/api/#querying-metric-metadata
|
|
#[utoipa::path(
|
|
context_path = "/api",
|
|
tag = "Metrics",
|
|
operation_id = "PrometheusMetadata",
|
|
security(
|
|
("Authorization"= [])
|
|
),
|
|
params(
|
|
("org_id" = String, Path, description = "Organization name"),
|
|
("limit" = String, Query, description = "Maximum number of metrics to return"),
|
|
("metric" = Option<String>, Query, description = "A metric name to filter metadata for. All metric metadata is retrieved if left empty"),
|
|
),
|
|
responses(
|
|
(status = 200, description = "Success", content_type = "application/json", body = HttpResponse, example = json!({
|
|
"status": "success",
|
|
"data": {
|
|
"cortex_ring_tokens": [
|
|
{
|
|
"type": "gauge",
|
|
"help": "Number of tokens in the ring",
|
|
"unit": ""
|
|
}
|
|
],
|
|
"http_requests_total": [
|
|
{
|
|
"type": "counter",
|
|
"help": "Number of HTTP requests",
|
|
"unit": ""
|
|
},
|
|
{
|
|
"type": "counter",
|
|
"help": "Amount of HTTP requests",
|
|
"unit": ""
|
|
}
|
|
]
|
|
}
|
|
})),
|
|
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
|
|
)
|
|
)]
|
|
#[get("/{org_id}/prometheus/api/v1/metadata")]
|
|
pub async fn metadata(
|
|
org_id: web::Path<String>,
|
|
req: web::Query<meta::prom::RequestMetadata>,
|
|
) -> Result<HttpResponse, Error> {
|
|
Ok(
|
|
match metrics::prom::get_metadata(&org_id, req.into_inner()).await {
|
|
Ok(resp) => HttpResponse::Ok().json(promql::ApiFuncResponse::ok(resp)),
|
|
Err(err) => {
|
|
log::error!("get_metadata failed: {err}");
|
|
HttpResponse::InternalServerError()
|
|
.json(promql::ApiFuncResponse::<()>::err_internal(err.to_string()))
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
/// prometheus finding series by label matchers
|
|
// refer: https://prometheus.io/docs/prometheus/latest/querying/api/#finding-series-by-label-matchers
|
|
#[utoipa::path(
|
|
context_path = "/api",
|
|
tag = "Metrics",
|
|
operation_id = "PrometheusSeries",
|
|
security(
|
|
("Authorization"= [])
|
|
),
|
|
params(
|
|
("org_id" = String, Path, description = "Organization name"),
|
|
("match[]" = String, Query, description = "<series_selector>: Series selector argument that selects the series to return"),
|
|
("start" = Option<String>, Query, description = "<rfc3339 | unix_timestamp>: Start timestamp"),
|
|
("end" = Option<String>, Query, description = "<rfc3339 | unix_timestamp>: End timestamp"),
|
|
),
|
|
responses(
|
|
(status = 200, description = "Success", content_type = "application/json", body = HttpResponse, example = json!({
|
|
"status" : "success",
|
|
"data" : [
|
|
{
|
|
"__name__" : "up",
|
|
"job" : "prometheus",
|
|
"instance" : "localhost:9090"
|
|
},
|
|
{
|
|
"__name__" : "up",
|
|
"job" : "node",
|
|
"instance" : "localhost:9091"
|
|
},
|
|
{
|
|
"__name__" : "process_start_time_seconds",
|
|
"job" : "prometheus",
|
|
"instance" : "localhost:9090"
|
|
}
|
|
]
|
|
})),
|
|
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
|
|
)
|
|
)]
|
|
#[get("/{org_id}/prometheus/api/v1/series")]
|
|
pub async fn series_get(
|
|
org_id: web::Path<String>,
|
|
req: web::Query<meta::prom::RequestSeries>,
|
|
) -> Result<HttpResponse, Error> {
|
|
series(&org_id, req.into_inner()).await
|
|
}
|
|
|
|
#[post("/{org_id}/prometheus/api/v1/series")]
|
|
pub async fn series_post(
|
|
org_id: web::Path<String>,
|
|
req: web::Query<meta::prom::RequestSeries>,
|
|
web::Form(form): web::Form<meta::prom::RequestSeries>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let req = if form.matcher.is_some() || form.start.is_some() || form.end.is_some() {
|
|
form
|
|
} else {
|
|
req.into_inner()
|
|
};
|
|
series(&org_id, req).await
|
|
}
|
|
|
|
async fn series(org_id: &str, req: meta::prom::RequestSeries) -> Result<HttpResponse, Error> {
|
|
let meta::prom::RequestSeries {
|
|
matcher,
|
|
start,
|
|
end,
|
|
} = req;
|
|
let (selector, start, end) = match validate_metadata_params(matcher, start, end) {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
return Ok(
|
|
HttpResponse::BadRequest().json(promql::ApiFuncResponse::<()>::err_bad_data(e))
|
|
);
|
|
}
|
|
};
|
|
Ok(
|
|
match metrics::prom::get_series(org_id, selector, start, end).await {
|
|
Ok(resp) => HttpResponse::Ok().json(promql::ApiFuncResponse::ok(resp)),
|
|
Err(err) => {
|
|
log::error!("get_series failed: {err}");
|
|
HttpResponse::InternalServerError()
|
|
.json(promql::ApiFuncResponse::<()>::err_internal(err.to_string()))
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
/// prometheus getting label names
|
|
// refer: https://prometheus.io/docs/prometheus/latest/querying/api/#getting-label-names
|
|
#[utoipa::path(
|
|
context_path = "/api",
|
|
tag = "Metrics",
|
|
operation_id = "PrometheusLabels",
|
|
security(
|
|
("Authorization"= [])
|
|
),
|
|
params(
|
|
("org_id" = String, Path, description = "Organization name"),
|
|
("match[]" = String, Query, description = "Series selector argument that selects the series from which to read the label names"),
|
|
("start" = Option<String>, Query, description = "<rfc3339 | unix_timestamp>: Start timestamp"),
|
|
("end" = Option<String>, Query, description = "<rfc3339 | unix_timestamp>: End timestamp"),
|
|
),
|
|
responses(
|
|
(status = 200, description = "Success", content_type = "application/json", body = HttpResponse, example = json!({
|
|
"status": "success",
|
|
"data": [
|
|
"__name__",
|
|
"call",
|
|
"code",
|
|
"config",
|
|
"dialer_name",
|
|
"endpoint",
|
|
"event",
|
|
"goversion",
|
|
"handler",
|
|
"instance",
|
|
"interval",
|
|
"job",
|
|
"le",
|
|
"listener_name",
|
|
"name",
|
|
"quantile",
|
|
"reason",
|
|
"role",
|
|
"scrape_job",
|
|
"slice",
|
|
"version"
|
|
]
|
|
})),
|
|
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
|
|
)
|
|
)]
|
|
#[get("/{org_id}/prometheus/api/v1/labels")]
|
|
pub async fn labels_get(
|
|
org_id: web::Path<String>,
|
|
req: web::Query<meta::prom::RequestLabels>,
|
|
) -> Result<HttpResponse, Error> {
|
|
labels(&org_id, req.into_inner()).await
|
|
}
|
|
|
|
#[post("/{org_id}/prometheus/api/v1/labels")]
|
|
pub async fn labels_post(
|
|
org_id: web::Path<String>,
|
|
req: web::Query<meta::prom::RequestLabels>,
|
|
web::Form(form): web::Form<meta::prom::RequestLabels>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let req = if form.matcher.is_some() || form.start.is_some() || form.end.is_some() {
|
|
form
|
|
} else {
|
|
req.into_inner()
|
|
};
|
|
labels(&org_id, req).await
|
|
}
|
|
|
|
async fn labels(org_id: &str, req: meta::prom::RequestLabels) -> Result<HttpResponse, Error> {
|
|
let meta::prom::RequestLabels {
|
|
matcher,
|
|
start,
|
|
end,
|
|
} = req;
|
|
let (selector, start, end) = match validate_metadata_params(matcher, start, end) {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
return Ok(
|
|
HttpResponse::BadRequest().json(promql::ApiFuncResponse::<()>::err_bad_data(e))
|
|
);
|
|
}
|
|
};
|
|
Ok(
|
|
match metrics::prom::get_labels(org_id, selector, start, end).await {
|
|
Ok(resp) => HttpResponse::Ok().json(promql::ApiFuncResponse::ok(resp)),
|
|
Err(err) => {
|
|
log::error!("get_labels failed: {err}");
|
|
HttpResponse::InternalServerError()
|
|
.json(promql::ApiFuncResponse::<()>::err_internal(err.to_string()))
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
/// prometheus query label values
|
|
// refer: https://prometheus.io/docs/prometheus/latest/querying/api/#querying-label-values
|
|
#[utoipa::path(
|
|
context_path = "/api",
|
|
tag = "Metrics",
|
|
operation_id = "PrometheusLabelValues",
|
|
security(
|
|
("Authorization"= [])
|
|
),
|
|
params(
|
|
("org_id" = String, Path, description = "Organization name"),
|
|
("label_name" = String, Path, description = "Label name"),
|
|
("match[]" = String, Query, description = "Series selector argument that selects the series from which to read the label values"),
|
|
("start" = Option<String>, Query, description = "<rfc3339 | unix_timestamp>: Start timestamp"),
|
|
("end" = Option<String>, Query, description = "<rfc3339 | unix_timestamp>: End timestamp"),
|
|
),
|
|
responses(
|
|
(status = 200, description = "Success", content_type = "application/json", body = HttpResponse, example = json!({
|
|
"status" : "success",
|
|
"data" : [
|
|
"node",
|
|
"prometheus"
|
|
]
|
|
})),
|
|
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
|
|
)
|
|
)]
|
|
#[get("/{org_id}/prometheus/api/v1/label/{label_name}/values")]
|
|
pub async fn label_values(
|
|
path: web::Path<(String, String)>,
|
|
req: web::Query<meta::prom::RequestLabelValues>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let (org_id, label_name) = path.into_inner();
|
|
let meta::prom::RequestLabelValues {
|
|
matcher,
|
|
start,
|
|
end,
|
|
} = req.into_inner();
|
|
let (selector, start, end) = match validate_metadata_params(matcher, start, end) {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
return Ok(
|
|
HttpResponse::BadRequest().json(promql::ApiFuncResponse::<()>::err_bad_data(e))
|
|
);
|
|
}
|
|
};
|
|
Ok(
|
|
match metrics::prom::get_label_values(&org_id, label_name, selector, start, end).await {
|
|
Ok(resp) => HttpResponse::Ok().json(promql::ApiFuncResponse::ok(resp)),
|
|
Err(err) => {
|
|
log::error!("get_label_values failed: {err}");
|
|
HttpResponse::InternalServerError()
|
|
.json(promql::ApiFuncResponse::<()>::err_internal(err.to_string()))
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
fn validate_metadata_params(
|
|
matcher: Option<String>,
|
|
start: Option<String>,
|
|
end: Option<String>,
|
|
) -> Result<(Option<parser::VectorSelector>, i64, i64), String> {
|
|
let selector = match matcher {
|
|
None => None,
|
|
Some(matcher) => match parser::parse(&matcher) {
|
|
Err(err) => {
|
|
let err = format!("parse promql error: {err}");
|
|
log::error!("{err}");
|
|
return Err(err);
|
|
}
|
|
Ok(parser::Expr::VectorSelector(sel)) => {
|
|
let err = if sel.name.is_none()
|
|
&& sel
|
|
.matchers
|
|
.find_matcher_value(meta::prom::NAME_LABEL)
|
|
.is_none()
|
|
{
|
|
Some("match[] argument must start with a metric name, e.g. `match[]=up`")
|
|
} else if sel.offset.is_some() {
|
|
Some("match[]: unexpected offset modifier")
|
|
} else if sel.at.is_some() {
|
|
Some("match[]: unexpected @ modifier")
|
|
} else {
|
|
None
|
|
};
|
|
if let Some(err) = err {
|
|
log::error!("{err}");
|
|
return Err(err.to_owned());
|
|
}
|
|
Some(sel)
|
|
}
|
|
Ok(_expr) => {
|
|
let err = "vector selector expected";
|
|
log::error!("{err}");
|
|
return Err(err.to_owned());
|
|
}
|
|
},
|
|
};
|
|
let start = if start.is_none() || start.as_ref().unwrap().is_empty() {
|
|
0
|
|
} else {
|
|
parse_str_to_timestamp_micros(&start.unwrap()).map_err(|e| e.to_string())?
|
|
};
|
|
let end = if end.is_none() || end.as_ref().unwrap().is_empty() {
|
|
chrono::Utc::now().timestamp_micros()
|
|
} else {
|
|
parse_str_to_timestamp_micros(&end.unwrap()).map_err(|e| e.to_string())?
|
|
};
|
|
Ok((selector, start, end))
|
|
}
|
|
|
|
/// prometheus formatting query expressions
|
|
// refer: https://prometheus.io/docs/prometheus/latest/querying/api/#formatting-query-expressions
|
|
#[utoipa::path(
|
|
context_path = "/api",
|
|
tag = "Metrics",
|
|
operation_id = "PrometheusFormatQuery",
|
|
security(
|
|
("Authorization"= [])
|
|
),
|
|
params(
|
|
("query" = String, Query, description = "Prometheus expression query string."),
|
|
),
|
|
responses(
|
|
(status = 200, description = "Success", content_type = "application/json", body = HttpResponse, example = json!({
|
|
"status" : "success",
|
|
"data" : "foo / bar"
|
|
})),
|
|
(status = 500, description = "Failure", content_type = "application/json", body = HttpResponse),
|
|
)
|
|
)]
|
|
#[get("/{org_id}/prometheus/api/v1/format_query")]
|
|
pub async fn format_query_get(
|
|
org_id: web::Path<String>,
|
|
req: web::Query<meta::prom::RequestFormatQuery>,
|
|
) -> Result<HttpResponse, Error> {
|
|
format_query(&org_id, &req.query)
|
|
}
|
|
|
|
#[post("/{org_id}/prometheus/api/v1/format_query")]
|
|
pub async fn format_query_post(
|
|
org_id: web::Path<String>,
|
|
req: web::Query<meta::prom::RequestFormatQuery>,
|
|
web::Form(form): web::Form<meta::prom::RequestFormatQuery>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let query = if !form.query.is_empty() {
|
|
&form.query
|
|
} else {
|
|
&req.query
|
|
};
|
|
format_query(&org_id, query)
|
|
}
|
|
|
|
fn format_query(_org_id: &str, query: &str) -> Result<HttpResponse, Error> {
|
|
let expr = match promql_parser::parser::parse(query) {
|
|
Ok(expr) => expr,
|
|
Err(err) => {
|
|
return Ok(
|
|
HttpResponse::BadRequest().json(promql::ApiFuncResponse::<()>::err_bad_data(err))
|
|
);
|
|
}
|
|
};
|
|
Ok(HttpResponse::Ok().json(promql::ApiFuncResponse::ok(expr.prettify())))
|
|
}
|
|
|
|
fn search_timeout(timeout: Option<String>) -> i64 {
|
|
match timeout {
|
|
None => 0,
|
|
Some(v) => match parse_milliseconds(&v) {
|
|
Ok(v) => (v / 1000) as i64, // seconds
|
|
Err(e) => {
|
|
log::error!("parse timeout error: {}", e);
|
|
0
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
async fn search(
|
|
org_id: &str,
|
|
timeout: i64,
|
|
req: &MetricsQueryRequest,
|
|
) -> Result<HttpResponse, Error> {
|
|
match promql::search::search(org_id, req, timeout).await {
|
|
Ok(data) => Ok(HttpResponse::Ok().json(promql::QueryResponse {
|
|
status: promql::Status::Success,
|
|
data: Some(promql::QueryResult {
|
|
result_type: data.get_type().to_string(),
|
|
result: data,
|
|
}),
|
|
error_type: None,
|
|
error: None,
|
|
})),
|
|
Err(err) => {
|
|
let err = match err {
|
|
errors::Error::ErrorCode(code) => code.get_error_detail(),
|
|
_ => err.to_string(),
|
|
};
|
|
Ok(HttpResponse::BadRequest().json(promql::QueryResponse {
|
|
status: promql::Status::Error,
|
|
data: None,
|
|
error_type: Some("bad_data".to_string()),
|
|
error: Some(err),
|
|
}))
|
|
}
|
|
}
|
|
}
|