1use std::sync::Arc;
16
17use api::prom_store::remote::ReadRequest;
18use api::v1::RowInsertRequests;
19use async_trait::async_trait;
20use axum::Extension;
21use axum::body::Bytes;
22use axum::extract::{Query, State};
23use axum::http::{HeaderMap, HeaderValue, StatusCode, header};
24use axum::response::IntoResponse;
25use axum_extra::TypedHeader;
26use common_catalog::consts::DEFAULT_SCHEMA_NAME;
27use common_query::prelude::GREPTIME_PHYSICAL_TABLE;
28use common_telemetry::tracing;
29use mime_guess::mime;
30use pipeline::util::to_pipeline_version;
31use pipeline::{ContextReq, PipelineDefinition};
32use prometheus::HistogramTimer;
33use prost::Message;
34use serde::{Deserialize, Serialize};
35use session::context::{Channel, QueryContext, QueryContextRef};
36use snafu::prelude::*;
37use table::requests::{
38 METADATA_QUALITY_INFERRED, SEMANTIC_METRIC_METADATA_QUALITY, SEMANTIC_SIGNAL_TYPE,
39 SEMANTIC_SOURCE, SEMANTIC_SOURCE_VERSION, SIGNAL_TYPE_METRIC, SOURCE_PROMETHEUS,
40};
41
42use crate::error::{self, InternalSnafu, PipelineSnafu, Result};
43use crate::http::extractor::PipelineInfo;
44use crate::http::header::{
45 CONTENT_TYPE_PROTOBUF_STR, GREPTIME_DB_HEADER_METRICS, write_cost_header_map,
46};
47use crate::pending_rows_batcher::PendingRowsBatcher;
48use crate::prom_remote_write::decode::PromSeriesProcessor;
49use crate::prom_remote_write::decode_remote_write_request;
50use crate::prom_remote_write::v2::{decode_remote_write_v2_request, into_write_requests};
51use crate::prom_remote_write::validation::PromValidationMode;
52use crate::prom_store::snappy_decompress;
53use crate::query_handler::{PipelineHandlerRef, PromStoreProtocolHandlerRef, PromStoreResponse};
54
55pub const PHYSICAL_TABLE_PARAM: &str = "physical_table";
56pub const DEFAULT_ENCODING: &str = "snappy";
57pub const VM_ENCODING: &str = "zstd";
58pub const VM_PROTO_VERSION: &str = "1";
59const REMOTE_WRITE_V1_VERSION: &str = "1.0";
60const REMOTE_WRITE_V2_VERSION: &str = "2.0";
61const REMOTE_WRITE_V1_PROTO: &str = "prometheus.WriteRequest";
62const REMOTE_WRITE_V2_PROTO: &str = "io.prometheus.write.v2.Request";
63const CONTENT_TYPE_PROTO_PARAM: &str = "proto";
64const REMOTE_WRITE_V2_SAMPLES_WRITTEN_HEADER: &str = "x-prometheus-remote-write-samples-written";
65const REMOTE_WRITE_V2_HISTOGRAMS_WRITTEN_HEADER: &str =
66 "x-prometheus-remote-write-histograms-written";
67const REMOTE_WRITE_V2_EXEMPLARS_WRITTEN_HEADER: &str =
68 "x-prometheus-remote-write-exemplars-written";
69
70#[derive(Clone)]
71pub struct PromStoreState {
72 pub prom_store_handler: PromStoreProtocolHandlerRef,
73 pub pipeline_handler: Option<PipelineHandlerRef>,
74 pub prom_store_with_metric_engine: bool,
75 pub prom_validation_mode: PromValidationMode,
76 pub experimental_enable_prometheus_native_histogram: bool,
77 pub pending_rows_batcher: Option<Arc<PendingRowsBatcher>>,
78}
79
80#[derive(Debug, Serialize, Deserialize)]
81pub struct RemoteWriteQuery {
82 pub db: Option<String>,
83 pub physical_table: Option<String>,
86 pub get_vm_proto_version: Option<String>,
88}
89
90impl Default for RemoteWriteQuery {
91 fn default() -> RemoteWriteQuery {
92 Self {
93 db: Some(DEFAULT_SCHEMA_NAME.to_string()),
94 physical_table: Some(GREPTIME_PHYSICAL_TABLE.to_string()),
95 get_vm_proto_version: None,
96 }
97 }
98}
99
100#[axum_macros::debug_handler]
101#[tracing::instrument(
102 skip_all,
103 fields(protocol = "prometheus", request_type = "remote_write")
104)]
105pub async fn remote_write(
106 State(state): State<PromStoreState>,
107 Query(params): Query<RemoteWriteQuery>,
108 Extension(query_ctx): Extension<QueryContext>,
109 content_type: Option<TypedHeader<headers::ContentType>>,
110 pipeline_info: PipelineInfo,
111 content_encoding: TypedHeader<headers::ContentEncoding>,
112 body: Bytes,
113) -> Result<axum::response::Response> {
114 let is_zstd = content_encoding.contains(VM_ENCODING);
115
116 match remote_write_proto(content_type) {
117 RemoteWriteProto::V1 => {
118 remote_write_v1(state, params, query_ctx, pipeline_info, is_zstd, body).await
119 }
120 RemoteWriteProto::V2 => {
121 if let Some(response) = unsupported_remote_write_v2_encoding_response(&content_encoding)
122 {
123 return Ok(response);
124 }
125 remote_write_v2(state, params, query_ctx, pipeline_info, is_zstd, body).await
126 }
127 RemoteWriteProto::Unsupported(content_type) => Ok((
128 StatusCode::UNSUPPORTED_MEDIA_TYPE,
129 format!("unsupported prometheus remote write content type: {content_type}"),
130 )
131 .into_response()),
132 }
133}
134
135async fn remote_write_v1(
136 state: PromStoreState,
137 params: RemoteWriteQuery,
138 query_ctx: QueryContext,
139 pipeline_info: PipelineInfo,
140 is_zstd: bool,
141 body: Bytes,
142) -> Result<axum::response::Response> {
143 let PromStoreState {
144 prom_store_handler,
145 pipeline_handler,
146 prom_store_with_metric_engine,
147 prom_validation_mode,
148 experimental_enable_prometheus_native_histogram: _,
149 pending_rows_batcher,
150 } = state;
151
152 if let Some(response) = vm_proto_version_response(¶ms) {
153 return Ok(response);
154 }
155
156 let (db, query_ctx, _timer) =
157 prepare_remote_write_context(¶ms, query_ctx, REMOTE_WRITE_V1_VERSION);
158
159 let mut processor = PromSeriesProcessor::default_processor();
160
161 if let Some(pipeline_name) = pipeline_info.pipeline_name {
162 let pipeline_def = PipelineDefinition::from_name(
163 &pipeline_name,
164 to_pipeline_version(pipeline_info.pipeline_version.as_deref())
165 .context(PipelineSnafu)?,
166 None,
167 )
168 .context(PipelineSnafu)?;
169 let pipeline_handler = pipeline_handler.context(InternalSnafu {
170 err_msg: "pipeline handler is not set".to_string(),
171 })?;
172
173 processor.set_pipeline(pipeline_handler, query_ctx.clone(), pipeline_def);
174 }
175
176 let mut req = decode_remote_write_request(is_zstd, body, prom_validation_mode, &mut processor)?;
177
178 let req = if processor.use_pipeline {
179 processor.exec_pipeline().await?
180 } else {
181 req.as_insert_requests()
182 };
183 let batches = into_prom_write_batches(req, query_ctx);
184
185 let outcome = match write_prometheus_rows_with_progress(
186 prom_store_handler,
187 pending_rows_batcher,
188 prom_store_with_metric_engine,
189 batches,
190 )
191 .await
192 {
193 Ok(outcome) => outcome,
194 Err(error) => {
195 record_remote_write_samples(&db, error.rows_written);
196 return Err(error.error);
197 }
198 };
199 record_remote_write_samples(&db, outcome.rows_written);
200
201 Ok((
202 StatusCode::NO_CONTENT,
203 write_cost_header_map(outcome.write_cost),
204 )
205 .into_response())
206}
207
208async fn remote_write_v2(
209 state: PromStoreState,
210 params: RemoteWriteQuery,
211 query_ctx: QueryContext,
212 pipeline_info: PipelineInfo,
213 is_zstd: bool,
214 body: Bytes,
215) -> Result<axum::response::Response> {
216 let PromStoreState {
217 prom_store_handler,
218 pipeline_handler: _,
219 prom_store_with_metric_engine,
220 prom_validation_mode: _,
221 experimental_enable_prometheus_native_histogram,
222 pending_rows_batcher,
223 } = state;
224
225 if let Some(response) = vm_proto_version_response(¶ms) {
226 return Ok(response);
227 }
228
229 let _ = pipeline_info;
232
233 let (db, query_ctx, _timer) =
234 prepare_remote_write_context(¶ms, query_ctx, REMOTE_WRITE_V2_VERSION);
235
236 let request = match decode_remote_write_v2_request(is_zstd, body) {
237 Ok(request) => request,
238 Err(error) => return Ok(remote_write_v2_error_response(error, 0, 0, 0)),
239 };
240 if !experimental_enable_prometheus_native_histogram && request_has_native_histograms(&request) {
241 return Ok(remote_write_v2_error_response(
242 error::InvalidPromRemoteRequestSnafu {
243 msg: "prometheus remote write v2 native histogram ingestion is experimental; set http.experimental_enable_prometheus_native_histogram = true to enable it"
244 .to_string(),
245 }
246 .build(),
247 0,
248 0,
249 0,
250 ));
251 }
252 let req = match into_write_requests(request) {
253 Ok(req) => req,
254 Err(error) => return Ok(remote_write_v2_error_response(error, 0, 0, 0)),
255 };
256 let sample_count = req.sample_count;
257 let histogram_count = req.histogram_count;
258 let sample_batches = into_prom_write_batches(req.samples, query_ctx.clone());
259 let histogram_batches = into_prom_write_batches(req.histograms, query_ctx);
260 let outcome = match write_prometheus_v2_rows_with_progress(
261 prom_store_handler,
262 pending_rows_batcher,
263 prom_store_with_metric_engine,
264 sample_batches,
265 histogram_batches,
266 )
267 .await
268 {
269 Ok(outcome) => outcome,
270 Err(error) => {
271 record_remote_write_samples(&db, error.samples_written);
272 record_remote_write_histograms(&db, error.histograms_written);
273 return Ok(remote_write_v2_error_response(
274 error.error,
275 error.samples_written,
276 error.histograms_written,
277 0,
278 ));
279 }
280 };
281 debug_assert_eq!(outcome.samples_written, sample_count);
282 debug_assert_eq!(outcome.histograms_written, histogram_count);
283 record_remote_write_samples(&db, outcome.samples_written);
284 record_remote_write_histograms(&db, outcome.histograms_written);
285
286 let mut headers = write_cost_header_map(outcome.write_cost);
287 append_remote_write_v2_written_headers(
288 &mut headers,
289 outcome.samples_written,
290 outcome.histograms_written,
291 0,
292 );
293
294 Ok((StatusCode::NO_CONTENT, headers).into_response())
295}
296
297fn request_has_native_histograms(
298 request: &api::greptime_proto::io::prometheus::write::v2::Request,
299) -> bool {
300 request
301 .timeseries
302 .iter()
303 .any(|series| !series.histograms.is_empty())
304}
305
306fn vm_proto_version_response(params: &RemoteWriteQuery) -> Option<axum::response::Response> {
307 params
308 .get_vm_proto_version
309 .as_ref()
310 .map(|_| VM_PROTO_VERSION.into_response())
311}
312
313fn prepare_remote_write_context(
314 params: &RemoteWriteQuery,
315 mut query_ctx: QueryContext,
316 remote_write_version: &str,
317) -> (String, Arc<QueryContext>, HistogramTimer) {
318 let db = params.db.clone().unwrap_or_default();
319 query_ctx.set_channel(Channel::Prometheus);
320 let physical_table = params
321 .physical_table
322 .clone()
323 .unwrap_or_else(|| GREPTIME_PHYSICAL_TABLE.to_string());
324 query_ctx.set_extension(PHYSICAL_TABLE_PARAM, physical_table);
325 query_ctx.set_extension(SEMANTIC_SIGNAL_TYPE, SIGNAL_TYPE_METRIC);
330 query_ctx.set_extension(SEMANTIC_SOURCE, SOURCE_PROMETHEUS);
331 query_ctx.set_extension(SEMANTIC_SOURCE_VERSION, remote_write_version);
332 query_ctx.set_extension(SEMANTIC_METRIC_METADATA_QUALITY, METADATA_QUALITY_INFERRED);
333 let query_ctx = Arc::new(query_ctx);
334 let timer = crate::metrics::METRIC_HTTP_PROM_STORE_WRITE_ELAPSED
335 .with_label_values(&[db.as_str()])
336 .start_timer();
337
338 (db, query_ctx, timer)
339}
340
341struct PromWriteOutcome {
342 write_cost: usize,
343 rows_written: u64,
344}
345
346struct PromWriteError {
347 error: error::Error,
348 rows_written: u64,
349}
350
351struct PromWriteV2Outcome {
352 write_cost: usize,
353 samples_written: u64,
354 histograms_written: u64,
355}
356
357struct PromWriteV2Error {
358 error: error::Error,
359 samples_written: u64,
360 histograms_written: u64,
361}
362
363type PromWriteBatch = (QueryContextRef, RowInsertRequests);
364
365#[async_trait]
366trait PromWriteBatcher: Send + Sync {
367 async fn submit(&self, requests: RowInsertRequests, ctx: QueryContextRef) -> Result<u64>;
368}
369
370#[async_trait]
371impl PromWriteBatcher for PendingRowsBatcher {
372 async fn submit(&self, requests: RowInsertRequests, ctx: QueryContextRef) -> Result<u64> {
373 PendingRowsBatcher::submit(self, requests, ctx).await
374 }
375}
376
377fn into_prom_write_batches(req: ContextReq, query_ctx: QueryContextRef) -> Vec<PromWriteBatch> {
378 req.as_req_iter(query_ctx).collect()
379}
380
381async fn preflight_prometheus_rows(
382 prom_store_handler: &PromStoreProtocolHandlerRef,
383 batches: &mut [PromWriteBatch],
384) -> Result<()> {
385 for (ctx, reqs) in batches {
386 prom_store_handler.pre_write(reqs, ctx.clone()).await?;
387 *ctx = Arc::new(ctx.fork());
390 }
391 Ok(())
392}
393
394async fn write_prometheus_rows_with_progress(
399 prom_store_handler: PromStoreProtocolHandlerRef,
400 pending_rows_batcher: Option<Arc<PendingRowsBatcher>>,
401 prom_store_with_metric_engine: bool,
402 mut batches: Vec<PromWriteBatch>,
403) -> std::result::Result<PromWriteOutcome, PromWriteError> {
404 if prom_store_with_metric_engine && let Some(batcher) = pending_rows_batcher {
405 preflight_prometheus_rows(&prom_store_handler, &mut batches)
406 .await
407 .map_err(|error| PromWriteError {
408 error,
409 rows_written: 0,
410 })?;
411 let mut rows_written = 0;
412 for (temp_ctx, reqs) in batches {
413 let rows = batcher
414 .submit(reqs, temp_ctx)
415 .await
416 .map_err(|error| PromWriteError {
417 error,
418 rows_written,
419 })?;
420 rows_written += rows;
421 }
422 return Ok(PromWriteOutcome {
423 write_cost: 0,
424 rows_written,
425 });
426 }
427
428 let row_counts = batches
429 .iter()
430 .map(|(_, request)| prom_write_row_count(request))
431 .collect::<Vec<_>>();
432 let batch_count = batches.len();
433 let outputs = prom_store_handler
434 .write_all(batches, prom_store_with_metric_engine)
435 .await
436 .map_err(|error| PromWriteError {
437 error,
438 rows_written: 0,
439 })?;
440 let output_count = outputs.len();
441 let mut write_cost = 0;
442 let mut rows_written = 0;
443 for (output, rows) in outputs.into_iter().zip(row_counts) {
444 let output = output.map_err(|error| PromWriteError {
445 error,
446 rows_written,
447 })?;
448 write_cost += output.meta.cost;
449 rows_written += rows;
450 }
451 if output_count != batch_count {
452 return Err(PromWriteError {
453 error: incomplete_prom_write_error(),
454 rows_written,
455 });
456 }
457
458 Ok(PromWriteOutcome {
459 write_cost,
460 rows_written,
461 })
462}
463
464async fn write_prometheus_v2_rows_with_progress(
465 prom_store_handler: PromStoreProtocolHandlerRef,
466 pending_rows_batcher: Option<Arc<PendingRowsBatcher>>,
467 prom_store_with_metric_engine: bool,
468 sample_batches: Vec<PromWriteBatch>,
469 histogram_batches: Vec<PromWriteBatch>,
470) -> std::result::Result<PromWriteV2Outcome, PromWriteV2Error> {
471 if histogram_batches.is_empty() {
472 return write_prometheus_rows_with_progress(
473 prom_store_handler,
474 pending_rows_batcher,
475 prom_store_with_metric_engine,
476 sample_batches,
477 )
478 .await
479 .map(|outcome| PromWriteV2Outcome {
480 write_cost: outcome.write_cost,
481 samples_written: outcome.rows_written,
482 histograms_written: 0,
483 })
484 .map_err(|error| PromWriteV2Error {
485 error: error.error,
486 samples_written: error.rows_written,
487 histograms_written: 0,
488 });
489 }
490
491 if prom_store_with_metric_engine && let Some(batcher) = pending_rows_batcher {
492 return write_batched_prometheus_v2_rows_with_progress(
493 prom_store_handler,
494 batcher.as_ref(),
495 prom_store_with_metric_engine,
496 sample_batches,
497 histogram_batches,
498 )
499 .await;
500 }
501
502 let sample_batch_count = sample_batches.len();
503 let mut batches = sample_batches;
504 batches.extend(histogram_batches);
505 let row_counts = batches
506 .iter()
507 .map(|(_, request)| prom_write_row_count(request))
508 .collect::<Vec<_>>();
509 let batch_count = batches.len();
510 let outputs = prom_store_handler
511 .write_all(batches, prom_store_with_metric_engine)
512 .await
513 .map_err(|error| PromWriteV2Error {
514 error,
515 samples_written: 0,
516 histograms_written: 0,
517 })?;
518
519 let mut write_cost = 0;
520 let mut samples_written = 0;
521 let mut histograms_written = 0;
522 let mut output_count = 0;
523 for (index, (output, rows)) in outputs.into_iter().zip(row_counts).enumerate() {
524 let output = output.map_err(|error| PromWriteV2Error {
525 error,
526 samples_written,
527 histograms_written,
528 })?;
529 write_cost += output.meta.cost;
530 if index < sample_batch_count {
531 samples_written += rows;
532 } else {
533 histograms_written += rows;
534 }
535 output_count += 1;
536 }
537 if output_count != batch_count {
538 return Err(PromWriteV2Error {
539 error: incomplete_prom_write_error(),
540 samples_written,
541 histograms_written,
542 });
543 }
544
545 Ok(PromWriteV2Outcome {
546 write_cost,
547 samples_written,
548 histograms_written,
549 })
550}
551
552async fn write_batched_prometheus_v2_rows_with_progress<B: PromWriteBatcher + ?Sized>(
553 prom_store_handler: PromStoreProtocolHandlerRef,
554 batcher: &B,
555 prom_store_with_metric_engine: bool,
556 sample_batches: Vec<PromWriteBatch>,
557 histogram_batches: Vec<PromWriteBatch>,
558) -> std::result::Result<PromWriteV2Outcome, PromWriteV2Error> {
559 let sample_batch_count = sample_batches.len();
560 let mut batches = sample_batches;
561 batches.extend(histogram_batches);
562 preflight_prometheus_rows(&prom_store_handler, &mut batches)
563 .await
564 .map_err(|error| PromWriteV2Error {
565 error,
566 samples_written: 0,
567 histograms_written: 0,
568 })?;
569
570 let mut samples_written = 0;
571 let mut histograms_written = 0;
572 let mut write_cost = 0;
573 let mut batches = batches.into_iter();
574 for (ctx, requests) in batches.by_ref().take(sample_batch_count) {
575 let rows = batcher
576 .submit(requests, ctx)
577 .await
578 .map_err(|error| PromWriteV2Error {
579 error,
580 samples_written,
581 histograms_written,
582 })?;
583 samples_written += rows;
584 }
585 for (ctx, requests) in batches {
586 let rows = prom_write_row_count(&requests);
587 let output = prom_store_handler
588 .write_prepared(requests, ctx, prom_store_with_metric_engine)
589 .await
590 .map_err(|error| PromWriteV2Error {
591 error,
592 samples_written,
593 histograms_written,
594 })?;
595 write_cost += output.meta.cost;
596 histograms_written += rows;
597 }
598
599 Ok(PromWriteV2Outcome {
600 write_cost,
601 samples_written,
602 histograms_written,
603 })
604}
605
606fn prom_write_row_count(request: &RowInsertRequests) -> u64 {
607 request
608 .inserts
609 .iter()
610 .filter_map(|insert| insert.rows.as_ref().map(|rows| rows.rows.len() as u64))
611 .sum()
612}
613
614fn incomplete_prom_write_error() -> error::Error {
615 InternalSnafu {
616 err_msg: "prometheus write handler returned before processing every batch".to_string(),
617 }
618 .build()
619}
620
621fn record_remote_write_samples(db: &str, rows: u64) {
622 if rows == 0 {
623 return;
624 }
625 crate::metrics::PROM_STORE_REMOTE_WRITE_SAMPLES
626 .with_label_values(&[db])
627 .inc_by(rows);
628}
629
630fn record_remote_write_histograms(db: &str, rows: u64) {
631 if rows == 0 {
632 return;
633 }
634 crate::metrics::PROM_STORE_REMOTE_WRITE_HISTOGRAMS
635 .with_label_values(&[db])
636 .inc_by(rows);
637}
638
639fn remote_write_v2_error_response(
640 error: error::Error,
641 samples: u64,
642 histograms: u64,
643 exemplars: u64,
644) -> axum::response::Response {
645 let mut response = error.into_response();
646 append_remote_write_v2_written_headers(response.headers_mut(), samples, histograms, exemplars);
647 response
648}
649
650fn append_remote_write_v2_written_headers(
651 headers: &mut HeaderMap,
652 samples: u64,
653 histograms: u64,
654 exemplars: u64,
655) {
656 headers.insert(
657 REMOTE_WRITE_V2_SAMPLES_WRITTEN_HEADER,
658 HeaderValue::from_str(&samples.to_string()).expect("u64 header value is valid"),
659 );
660 headers.insert(
661 REMOTE_WRITE_V2_HISTOGRAMS_WRITTEN_HEADER,
662 HeaderValue::from_str(&histograms.to_string()).expect("u64 header value is valid"),
663 );
664 headers.insert(
665 REMOTE_WRITE_V2_EXEMPLARS_WRITTEN_HEADER,
666 HeaderValue::from_str(&exemplars.to_string()).expect("u64 header value is valid"),
667 );
668}
669
670enum RemoteWriteProto {
671 V1,
672 V2,
673 Unsupported(mime::Mime),
674}
675
676fn remote_write_proto(content_type: Option<TypedHeader<headers::ContentType>>) -> RemoteWriteProto {
678 let Some(TypedHeader(content_type)) = content_type else {
679 return RemoteWriteProto::V1;
680 };
681
682 let mime_type: mime::Mime = content_type.into();
683 if !mime_type
684 .essence_str()
685 .eq_ignore_ascii_case(CONTENT_TYPE_PROTOBUF_STR)
686 {
687 return RemoteWriteProto::Unsupported(mime_type);
688 }
689
690 for (name, value) in mime_type.params() {
691 if !name.as_str().eq_ignore_ascii_case(CONTENT_TYPE_PROTO_PARAM) {
692 continue;
693 }
694
695 return match value.as_str() {
696 REMOTE_WRITE_V1_PROTO => RemoteWriteProto::V1,
697 REMOTE_WRITE_V2_PROTO => RemoteWriteProto::V2,
698 _ => RemoteWriteProto::Unsupported(mime_type.clone()),
699 };
700 }
701
702 RemoteWriteProto::V1
703}
704
705fn unsupported_remote_write_v2_encoding_response(
706 content_encoding: &headers::ContentEncoding,
707) -> Option<axum::response::Response> {
708 if content_encoding.contains(DEFAULT_ENCODING) || content_encoding.contains(VM_ENCODING) {
709 return None;
710 }
711
712 Some((
713 StatusCode::UNSUPPORTED_MEDIA_TYPE,
714 format!(
715 "unsupported prometheus remote write content encoding: only {DEFAULT_ENCODING} and {VM_ENCODING} are supported"
716 ),
717 )
718 .into_response())
719}
720
721impl IntoResponse for PromStoreResponse {
722 fn into_response(self) -> axum::response::Response {
723 let mut header_map = HeaderMap::new();
724 header_map.insert(&header::CONTENT_TYPE, self.content_type);
725 header_map.insert(&header::CONTENT_ENCODING, self.content_encoding);
726
727 let metrics = if self.resp_metrics.is_empty() {
728 None
729 } else {
730 serde_json::to_string(&self.resp_metrics).ok()
731 };
732 if let Some(m) = metrics.and_then(|m| HeaderValue::from_str(&m).ok()) {
733 header_map.insert(&GREPTIME_DB_HEADER_METRICS, m);
734 }
735
736 (header_map, self.body).into_response()
737 }
738}
739
740#[axum_macros::debug_handler]
741#[tracing::instrument(
742 skip_all,
743 fields(protocol = "prometheus", request_type = "remote_read")
744)]
745pub async fn remote_read(
746 State(state): State<PromStoreState>,
747 Query(params): Query<RemoteWriteQuery>,
748 Extension(mut query_ctx): Extension<QueryContext>,
749 body: Bytes,
750) -> Result<PromStoreResponse> {
751 let db = params.db.clone().unwrap_or_default();
752 query_ctx.set_channel(Channel::Prometheus);
753
754 let request = decode_remote_read_request(body).await?;
755
756 let query_ctx = Arc::new(query_ctx);
757 let _timer = crate::metrics::METRIC_HTTP_PROM_STORE_READ_ELAPSED
758 .with_label_values(&[db.as_str()])
759 .start_timer();
760
761 state.prom_store_handler.read(request, query_ctx).await
762}
763
764async fn decode_remote_read_request(body: Bytes) -> Result<ReadRequest> {
765 let buf = snappy_decompress(&body[..])?;
766
767 ReadRequest::decode(&buf[..]).context(error::DecodePromRemoteRequestSnafu)
768}
769
770#[cfg(test)]
771mod tests {
772 use std::sync::Mutex;
773
774 use api::prom_store::remote::ReadRequest;
775 use api::v1::{Row, RowInsertRequest, Rows};
776 use async_trait::async_trait;
777 use common_query::Output;
778 use pipeline::GreptimePipelineParams;
779 use session::context::{QueryContext, QueryContextRef};
780
781 use super::*;
782 use crate::prom_remote_write::validation::PromValidationMode;
783 use crate::prom_store::Metrics;
784 use crate::query_handler::PromStoreProtocolHandler;
785
786 #[test]
787 fn test_remote_write_proto() {
788 assert!(matches!(
789 remote_write_proto(content_type(
790 "application/x-protobuf;proto=io.prometheus.write.v2.Request"
791 )),
792 RemoteWriteProto::V2
793 ));
794 assert!(matches!(
795 remote_write_proto(content_type(
796 "application/x-protobuf; proto=\"io.prometheus.write.v2.Request\""
797 )),
798 RemoteWriteProto::V2
799 ));
800 assert!(matches!(
801 remote_write_proto(content_type(
802 "APPLICATION/X-PROTOBUF;proto=io.prometheus.write.v2.Request"
803 )),
804 RemoteWriteProto::V2
805 ));
806 assert!(matches!(
807 remote_write_proto(content_type("application/x-protobuf")),
808 RemoteWriteProto::V1
809 ));
810 assert!(matches!(
811 remote_write_proto(content_type(
812 "application/x-protobuf;proto=prometheus.WriteRequest"
813 )),
814 RemoteWriteProto::V1
815 ));
816 assert!(matches!(
817 remote_write_proto(content_type(
818 "application/x-protobuf;proto=unknown.WriteRequest"
819 )),
820 RemoteWriteProto::Unsupported(_)
821 ));
822 assert!(matches!(
823 remote_write_proto(content_type(
824 "application/json;proto=io.prometheus.write.v2.Request"
825 )),
826 RemoteWriteProto::Unsupported(_)
827 ));
828 assert!(matches!(remote_write_proto(None), RemoteWriteProto::V1));
829 }
830
831 fn content_type(value: &str) -> Option<TypedHeader<headers::ContentType>> {
832 Some(TypedHeader(std::str::FromStr::from_str(value).unwrap()))
833 }
834
835 #[test]
836 fn test_prepare_remote_write_context_stamps_semantics() {
837 let (_, query_ctx, _timer) = prepare_remote_write_context(
838 &RemoteWriteQuery::default(),
839 QueryContext::with("greptime", "public"),
840 REMOTE_WRITE_V2_VERSION,
841 );
842
843 assert_eq!(
844 query_ctx.extension(SEMANTIC_SIGNAL_TYPE),
845 Some(SIGNAL_TYPE_METRIC)
846 );
847 assert_eq!(
848 query_ctx.extension(SEMANTIC_SOURCE),
849 Some(SOURCE_PROMETHEUS)
850 );
851 assert_eq!(
852 query_ctx.extension(SEMANTIC_SOURCE_VERSION),
853 Some(REMOTE_WRITE_V2_VERSION)
854 );
855 assert_eq!(
856 query_ctx.extension(SEMANTIC_METRIC_METADATA_QUALITY),
857 Some(METADATA_QUALITY_INFERRED)
858 );
859 }
860
861 #[tokio::test]
862 async fn test_mixed_v2_preflights_all_then_batches_only_samples() {
863 let events = Arc::new(Mutex::new(Vec::new()));
864 let handler: PromStoreProtocolHandlerRef = Arc::new(RecordingPromStoreHandler {
865 events: events.clone(),
866 });
867 let batcher = RecordingPromWriteBatcher {
868 events: events.clone(),
869 };
870
871 let Ok(outcome) = write_batched_prometheus_v2_rows_with_progress(
872 handler,
873 &batcher,
874 true,
875 vec![test_prom_write_batch("sample")],
876 vec![test_prom_write_batch("histogram")],
877 )
878 .await
879 else {
880 panic!("mixed remote write should succeed")
881 };
882
883 assert_eq!(1, outcome.samples_written);
884 assert_eq!(1, outcome.histograms_written);
885 assert_eq!(
886 vec![
887 "pre:sample".to_string(),
888 "pre:histogram".to_string(),
889 "batch:sample".to_string(),
890 "direct:histogram".to_string(),
891 ],
892 *events.lock().unwrap()
893 );
894 }
895
896 fn test_prom_write_batch(table_name: &str) -> PromWriteBatch {
897 (
898 Arc::new(QueryContext::with("greptime", "public")),
899 RowInsertRequests {
900 inserts: vec![RowInsertRequest {
901 table_name: table_name.to_string(),
902 rows: Some(Rows {
903 schema: Vec::new(),
904 rows: vec![Row { values: Vec::new() }],
905 }),
906 }],
907 },
908 )
909 }
910
911 fn record_write_event(events: &Mutex<Vec<String>>, phase: &str, request: &RowInsertRequests) {
912 events.lock().unwrap().push(format!(
913 "{phase}:{}",
914 request.inserts.first().unwrap().table_name
915 ));
916 }
917
918 struct RecordingPromWriteBatcher {
919 events: Arc<Mutex<Vec<String>>>,
920 }
921
922 #[async_trait]
923 impl PromWriteBatcher for RecordingPromWriteBatcher {
924 async fn submit(&self, requests: RowInsertRequests, _ctx: QueryContextRef) -> Result<u64> {
925 record_write_event(&self.events, "batch", &requests);
926 Ok(prom_write_row_count(&requests))
927 }
928 }
929
930 struct RecordingPromStoreHandler {
931 events: Arc<Mutex<Vec<String>>>,
932 }
933
934 #[async_trait]
935 impl PromStoreProtocolHandler for RecordingPromStoreHandler {
936 async fn pre_write(
937 &self,
938 request: &RowInsertRequests,
939 _ctx: QueryContextRef,
940 ) -> Result<()> {
941 record_write_event(&self.events, "pre", request);
942 Ok(())
943 }
944
945 async fn write_prepared(
946 &self,
947 request: RowInsertRequests,
948 _ctx: QueryContextRef,
949 _with_metric_engine: bool,
950 ) -> Result<Output> {
951 record_write_event(&self.events, "direct", &request);
952 Ok(Output::new_with_affected_rows(0))
953 }
954
955 async fn write(
956 &self,
957 _request: RowInsertRequests,
958 _ctx: QueryContextRef,
959 _with_metric_engine: bool,
960 ) -> Result<Output> {
961 unreachable!("mixed v2 writes use preflighted execution")
962 }
963
964 async fn write_all(
965 &self,
966 _requests: Vec<(QueryContextRef, RowInsertRequests)>,
967 _with_metric_engine: bool,
968 ) -> Result<Vec<Result<Output>>> {
969 unreachable!("mixed v2 writes preserve sample and histogram routing")
970 }
971
972 async fn read(
973 &self,
974 _request: ReadRequest,
975 _ctx: QueryContextRef,
976 ) -> Result<PromStoreResponse> {
977 unimplemented!()
978 }
979
980 async fn ingest_metrics(&self, _metrics: Metrics) -> Result<()> {
981 unimplemented!()
982 }
983 }
984
985 #[tokio::test]
986 async fn test_remote_write_v2_ignores_pipeline() {
987 let request = api::greptime_proto::io::prometheus::write::v2::Request {
988 symbols: vec![String::new()],
989 timeseries: Vec::new(),
990 };
991 let body =
992 Bytes::from(crate::prom_store::snappy_compress(&request.encode_to_vec()).unwrap());
993
994 let response = remote_write_v2(
995 test_state(),
996 RemoteWriteQuery::default(),
997 QueryContext::with("greptime", "public"),
998 pipeline_info(Some("pipeline")),
999 false,
1000 body,
1001 )
1002 .await
1003 .unwrap();
1004
1005 assert_eq!(response.status(), StatusCode::NO_CONTENT);
1006 assert_eq!(
1007 Some("0"),
1008 response
1009 .headers()
1010 .get(REMOTE_WRITE_V2_SAMPLES_WRITTEN_HEADER)
1011 .map(|x| x.to_str().unwrap())
1012 );
1013 }
1014
1015 fn test_state() -> PromStoreState {
1016 PromStoreState {
1017 prom_store_handler: Arc::new(NoopPromStoreHandler),
1018 pipeline_handler: None,
1019 prom_store_with_metric_engine: false,
1020 prom_validation_mode: PromValidationMode::Strict,
1021 experimental_enable_prometheus_native_histogram: false,
1022 pending_rows_batcher: None,
1023 }
1024 }
1025
1026 fn pipeline_info(pipeline_name: Option<&str>) -> PipelineInfo {
1027 PipelineInfo {
1028 pipeline_name: pipeline_name.map(ToString::to_string),
1029 pipeline_version: None,
1030 pipeline_params: GreptimePipelineParams::default(),
1031 }
1032 }
1033
1034 struct NoopPromStoreHandler;
1035
1036 #[async_trait]
1037 impl PromStoreProtocolHandler for NoopPromStoreHandler {
1038 async fn pre_write(
1039 &self,
1040 _request: &RowInsertRequests,
1041 _ctx: QueryContextRef,
1042 ) -> Result<()> {
1043 Ok(())
1044 }
1045
1046 async fn write_prepared(
1047 &self,
1048 _request: RowInsertRequests,
1049 _ctx: QueryContextRef,
1050 _with_metric_engine: bool,
1051 ) -> Result<Output> {
1052 unreachable!("empty remote write v2 request should not write")
1053 }
1054
1055 async fn write(
1056 &self,
1057 _request: RowInsertRequests,
1058 _ctx: QueryContextRef,
1059 _with_metric_engine: bool,
1060 ) -> Result<Output> {
1061 unreachable!("empty remote write v2 request should not write")
1062 }
1063
1064 async fn write_all(
1065 &self,
1066 requests: Vec<(QueryContextRef, RowInsertRequests)>,
1067 _with_metric_engine: bool,
1068 ) -> Result<Vec<Result<Output>>> {
1069 assert!(requests.is_empty());
1070 Ok(Vec::new())
1071 }
1072
1073 async fn read(
1074 &self,
1075 _request: ReadRequest,
1076 _ctx: QueryContextRef,
1077 ) -> Result<PromStoreResponse> {
1078 unimplemented!()
1079 }
1080
1081 async fn ingest_metrics(&self, _metrics: Metrics) -> Result<()> {
1082 unimplemented!()
1083 }
1084 }
1085}