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