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