1use std::cmp::Ordering;
17use std::collections::{BTreeMap, HashMap};
18
19use arrow::array::{Array, AsArray};
20use arrow::datatypes::{Float64Type, TimestampMillisecondType};
21use arrow_schema::DataType;
22use axum::Json;
23use axum::http::HeaderValue;
24use axum::response::{IntoResponse, Response};
25use common_error::ext::ErrorExt;
26use common_error::status_code::StatusCode;
27use common_query::prometheus::is_prometheus_stale_nan;
28use common_query::{Output, OutputData};
29use common_recordbatch::RecordBatches;
30use datatypes::prelude::ConcreteDataType;
31use indexmap::IndexMap;
32use promql_parser::label::METRIC_NAME;
33use promql_parser::parser::value::ValueType;
34use serde::{Deserialize, Serialize};
35use serde_json::Value;
36use snafu::{OptionExt, ResultExt};
37
38use crate::error::{
39 ArrowSnafu, CollectRecordbatchSnafu, Result, UnexpectedResultSnafu, status_code_to_http_status,
40};
41use crate::http::header::{GREPTIME_DB_HEADER_METRICS, collect_plan_metrics};
42use crate::http::prometheus::{
43 PromData, PromQueryResult, PromSeriesMatrix, PromSeriesVector, PrometheusResponse,
44};
45
46#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
47pub struct PrometheusJsonResponse {
48 pub status: String,
49 #[serde(skip_serializing_if = "PrometheusResponse::is_none")]
50 #[serde(default)]
51 pub data: PrometheusResponse,
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub error: Option<String>,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 #[serde(rename = "errorType")]
56 pub error_type: Option<String>,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub warnings: Option<Vec<String>>,
59
60 #[serde(skip)]
61 pub status_code: Option<StatusCode>,
62 #[serde(skip)]
64 #[serde(default)]
65 pub resp_metrics: HashMap<String, Value>,
66}
67
68impl IntoResponse for PrometheusJsonResponse {
69 fn into_response(self) -> Response {
70 let metrics = if self.resp_metrics.is_empty() {
71 None
72 } else {
73 serde_json::to_string(&self.resp_metrics).ok()
74 };
75
76 let http_code = self.status_code.map(|c| status_code_to_http_status(&c));
77
78 let mut resp = Json(self).into_response();
79
80 if let Some(http_code) = http_code {
81 *resp.status_mut() = http_code;
82 }
83
84 if let Some(m) = metrics.and_then(|m| HeaderValue::from_str(&m).ok()) {
85 resp.headers_mut().insert(&GREPTIME_DB_HEADER_METRICS, m);
86 }
87
88 resp
89 }
90}
91
92impl PrometheusJsonResponse {
93 pub fn error<S1>(error_type: StatusCode, reason: S1) -> Self
94 where
95 S1: Into<String>,
96 {
97 PrometheusJsonResponse {
98 status: "error".to_string(),
99 data: PrometheusResponse::None,
100 error: Some(reason.into()),
101 error_type: Some(error_type.to_string()),
102 warnings: None,
103 resp_metrics: Default::default(),
104 status_code: Some(error_type),
105 }
106 }
107
108 pub fn success(data: PrometheusResponse) -> Self {
109 PrometheusJsonResponse {
110 status: "success".to_string(),
111 data,
112 error: None,
113 error_type: None,
114 warnings: None,
115 resp_metrics: Default::default(),
116 status_code: None,
117 }
118 }
119
120 pub async fn from_query_result(
122 result: Result<Output>,
123 metric_name: Option<String>,
124 result_type: ValueType,
125 ) -> Self {
126 let response: Result<Self> = try {
127 let result = result?;
128 let mut resp =
129 match result.data {
130 OutputData::RecordBatches(batches) => Self::success(
131 Self::record_batches_to_data(batches, metric_name, result_type)?,
132 ),
133 OutputData::Stream(stream) => {
134 let record_batches = RecordBatches::try_collect(stream)
135 .await
136 .context(CollectRecordbatchSnafu)?;
137 Self::success(Self::record_batches_to_data(
138 record_batches,
139 metric_name,
140 result_type,
141 )?)
142 }
143 OutputData::AffectedRows(_) => Self::error(
144 StatusCode::Unexpected,
145 "expected data result, but got affected rows",
146 ),
147 };
148
149 if let Some(physical_plan) = result.meta.plan {
150 let mut result_map = HashMap::new();
151 let mut tmp = vec![&mut result_map];
152 collect_plan_metrics(&physical_plan, &mut tmp);
153
154 let re = result_map
155 .into_iter()
156 .map(|(k, v)| (k, Value::from(v)))
157 .collect();
158 resp.resp_metrics = re;
159 }
160
161 resp
162 };
163
164 let result_type_string = result_type.to_string();
165
166 match response {
167 Ok(resp) => resp,
168 Err(err) => {
169 if err.status_code() == StatusCode::TableNotFound
171 || err.status_code() == StatusCode::TableColumnNotFound
172 {
173 Self::success(PrometheusResponse::PromData(PromData {
174 result_type: result_type_string,
175 ..Default::default()
176 }))
177 } else {
178 Self::error(err.status_code(), err.output_msg())
179 }
180 }
181 }
182 }
183
184 fn record_batches_to_data(
186 batches: RecordBatches,
187 metric_name: Option<String>,
188 result_type: ValueType,
189 ) -> Result<PrometheusResponse> {
190 if batches.iter().next().is_none() {
192 return Ok(PrometheusResponse::PromData(PromData {
193 result_type: result_type.to_string(),
194 ..Default::default()
195 }));
196 }
197
198 let mut timestamp_column_index = None;
201 let mut tag_column_indices = Vec::new();
202 let mut first_field_column_index = None;
203
204 let mut num_label_columns = 0;
205
206 for (i, column) in batches.schema().column_schemas().iter().enumerate() {
207 match column.data_type {
208 ConcreteDataType::Timestamp(datatypes::types::TimestampType::Millisecond(_))
209 if timestamp_column_index.is_none() =>
210 {
211 timestamp_column_index = Some(i);
212 }
213 ConcreteDataType::Float32(_)
215 | ConcreteDataType::Float64(_)
216 | ConcreteDataType::Int8(_)
217 | ConcreteDataType::Int16(_)
218 | ConcreteDataType::Int32(_)
219 | ConcreteDataType::Int64(_)
220 | ConcreteDataType::UInt8(_)
221 | ConcreteDataType::UInt16(_)
222 | ConcreteDataType::UInt32(_)
223 | ConcreteDataType::UInt64(_)
224 if first_field_column_index.is_none() =>
225 {
226 first_field_column_index = Some(i);
227 }
228 ConcreteDataType::String(_) => {
229 tag_column_indices.push(i);
230 num_label_columns += 1;
231 }
232 _ => {}
233 }
234 }
235
236 let timestamp_column_index = timestamp_column_index.context(UnexpectedResultSnafu {
237 reason: "no timestamp column found".to_string(),
238 })?;
239 let first_field_column_index = first_field_column_index.context(UnexpectedResultSnafu {
240 reason: "no value column found".to_string(),
241 })?;
242
243 let mut buffer = IndexMap::<Vec<(&str, &str)>, Vec<(f64, String)>>::new();
246
247 let schema = batches.schema();
248 for batch in batches.iter() {
249 let tag_columns = tag_column_indices
251 .iter()
252 .map(|i| batch.column(*i).as_string::<i32>())
253 .collect::<Vec<_>>();
254 let tag_names = tag_column_indices
255 .iter()
256 .map(|c| schema.column_name_by_index(*c))
257 .collect::<Vec<_>>();
258 let timestamp_column = batch
259 .column(timestamp_column_index)
260 .as_primitive::<TimestampMillisecondType>();
261
262 let array =
263 arrow::compute::cast(batch.column(first_field_column_index), &DataType::Float64)
264 .context(ArrowSnafu)?;
265 let field_column = array.as_primitive::<Float64Type>();
266
267 for row_index in 0..batch.num_rows() {
269 if field_column.is_valid(row_index) {
271 let v = field_column.value(row_index);
272 if is_prometheus_stale_nan(v) {
274 continue;
275 }
276
277 let mut tags = Vec::with_capacity(num_label_columns + 1);
279 if let Some(metric_name) = &metric_name {
280 tags.push((METRIC_NAME, metric_name.as_str()));
281 }
282 for (tag_column, tag_name) in tag_columns.iter().zip(tag_names.iter()) {
283 if tag_column.is_valid(row_index) {
285 tags.push((tag_name, tag_column.value(row_index)));
286 }
287 }
288
289 let timestamp_millis = timestamp_column.value(row_index);
291 let timestamp = timestamp_millis as f64 / 1000.0;
292
293 buffer
294 .entry(tags)
295 .or_default()
296 .push((timestamp, Into::<f64>::into(v).to_string()));
297 };
298 }
299 }
300
301 let mut result = match result_type {
303 ValueType::Vector => PromQueryResult::Vector(vec![]),
304 ValueType::Matrix => PromQueryResult::Matrix(vec![]),
305 ValueType::Scalar => PromQueryResult::Scalar(None),
306 ValueType::String => PromQueryResult::String(None),
307 };
308
309 buffer.into_iter().for_each(|(tags, mut values)| {
311 let metric = tags
312 .into_iter()
313 .map(|(k, v)| (k.to_string(), v.to_string()))
314 .collect::<BTreeMap<_, _>>();
315 match result {
316 PromQueryResult::Vector(ref mut v) => {
317 v.push(PromSeriesVector {
318 metric,
319 value: values.pop(),
320 });
321 }
322 PromQueryResult::Matrix(ref mut v) => {
323 if !values.is_sorted_by(|a, b| a.0 <= b.0) {
325 values.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
326 }
327
328 v.push(PromSeriesMatrix { metric, values });
329 }
330 PromQueryResult::Scalar(ref mut v) => {
331 *v = values.pop();
332 }
333 PromQueryResult::String(ref mut _v) => {
334 }
336 }
337 });
338
339 if let PromQueryResult::Matrix(ref mut v) = result {
342 v.sort_by(|a, b| a.metric.cmp(&b.metric));
343 }
344
345 let result_type_string = result_type.to_string();
346 let data = PrometheusResponse::PromData(PromData {
347 result_type: result_type_string,
348 result,
349 });
350
351 Ok(data)
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use std::sync::Arc;
358
359 use common_recordbatch::{RecordBatch, RecordBatches};
360 use datatypes::data_type::ConcreteDataType;
361 use datatypes::schema::{ColumnSchema, Schema};
362 use datatypes::vectors::{Float64Vector, TimestampMillisecondVector};
363
364 use super::*;
365
366 #[test]
367 fn matrix_response_preserves_ordinary_nan_and_filters_stale_markers() {
368 let schema = Arc::new(Schema::new(vec![
369 ColumnSchema::new(
370 "timestamp",
371 ConcreteDataType::timestamp_millisecond_datatype(),
372 false,
373 ),
374 ColumnSchema::new("value", ConcreteDataType::float64_datatype(), true),
375 ]));
376 let batch = RecordBatch::new(
377 schema.clone(),
378 vec![
379 Arc::new(TimestampMillisecondVector::from_vec(vec![
380 1_000, 2_000, 3_000, 4_000,
381 ])) as _,
382 Arc::new(Float64Vector::from(vec![
383 Some(1.0),
384 Some(f64::from_bits(0x7ff8_0000_0000_0000)),
385 Some(f64::from_bits(0x7ff0_0000_0000_0002)),
386 None,
387 ])) as _,
388 ],
389 )
390 .unwrap();
391 let batches = RecordBatches::try_new(schema, vec![batch]).unwrap();
392
393 let response =
394 PrometheusJsonResponse::record_batches_to_data(batches, None, ValueType::Matrix)
395 .unwrap();
396 let PrometheusResponse::PromData(data) = response else {
397 panic!("expected Prometheus data response");
398 };
399 let PromQueryResult::Matrix(series) = data.result else {
400 panic!("expected matrix result");
401 };
402
403 assert_eq!(series.len(), 1);
404 assert_eq!(
405 series[0].values,
406 vec![(1.0, "1".to_string()), (2.0, "NaN".to_string())]
407 );
408 }
409}