1use std::collections::hash_map::Entry;
16
17use ahash::{HashMap, HashMapExt, HashSet, HashSetExt};
18#[cfg(test)]
19use api::greptime_proto::io::prometheus::write::v2::BucketSpan;
20#[cfg(test)]
21use api::greptime_proto::io::prometheus::write::v2::histogram::{Count, ZeroCount};
22use api::greptime_proto::io::prometheus::write::v2::{
23 Exemplar, Histogram, Metadata, Sample, metadata,
24};
25#[cfg(test)]
26use api::greptime_proto::io::prometheus::write::v2::{Request, TimeSeries};
27#[cfg(test)]
28use api::v1::ColumnSchema;
29use api::v1::value::ValueData;
30use api::v1::{ColumnDataType, RowInsertRequest, Rows, SemanticType, Value};
31use bytes::{Buf, Bytes};
32use common_grpc::precision::Precision;
33#[cfg(test)]
34use common_query::native_histogram::*;
35use common_query::native_histogram::{
36 NATIVE_HISTOGRAM_FIELD, encode_native_histogram, native_histogram_column_schema,
37};
38use common_query::prelude::{greptime_native_histogram, greptime_timestamp, greptime_value};
39use pipeline::{ContextOpt, ContextReq};
40use prost::encoding::{
41 DecodeContext, WireType, decode_key, decode_varint, message, skip_field, uint32,
42};
43use prost::{DecodeError, Message};
44use snafu::{OptionExt, ResultExt, ensure};
45use table::requests::{
46 METADATA_QUALITY_DECLARED, SEMANTIC_METRIC_METADATA_QUALITY, SEMANTIC_METRIC_TYPE,
47 SEMANTIC_METRIC_UNIT,
48};
49
50use crate::error::{self, Result};
51use crate::prom_remote_write::row_builder::PromCtx;
52use crate::prom_remote_write::validation::validate_label_name;
53use crate::prom_remote_write::{REMOTE_WRITE_V2_VERSION, try_decompress};
54#[allow(deprecated)]
55use crate::prom_store::{
56 DATABASE_LABEL, DATABASE_LABEL_ALT, METRIC_NAME_LABEL, PHYSICAL_TABLE_LABEL,
57 PHYSICAL_TABLE_LABEL_ALT, SCHEMA_LABEL,
58};
59use crate::row_writer::{self, TableData};
60use crate::semantic::{
61 METRIC_TYPE_COUNTER, METRIC_TYPE_GAUGE, METRIC_TYPE_GAUGE_HISTOGRAM, METRIC_TYPE_HISTOGRAM,
62 METRIC_TYPE_INFO, METRIC_TYPE_STATESET, METRIC_TYPE_SUMMARY, SemanticIndexes,
63 openmetrics_unit_to_ucum,
64};
65
66type PromTags<'a> = Vec<(&'a str, String)>;
67type ResolvedSeriesLabels<'a> = (PromCtx, String, PromTags<'a>);
68const TIME_SERIES_LABELS_REFS_TAG: u32 = 1;
69const TIME_SERIES_SAMPLES_TAG: u32 = 2;
70const TIME_SERIES_HISTOGRAMS_TAG: u32 = 3;
71const TIME_SERIES_EXEMPLARS_TAG: u32 = 4;
72const TIME_SERIES_METADATA_TAG: u32 = 5;
73
74struct BorrowedRequest<'a> {
75 symbols: Vec<&'a str>,
76 timeseries: Vec<&'a [u8]>,
77}
78
79impl<'a> BorrowedRequest<'a> {
80 fn decode(mut buf: &'a [u8]) -> std::result::Result<Self, DecodeError> {
81 let mut symbols = Vec::new();
82 let mut timeseries = Vec::new();
83
84 while buf.has_remaining() {
85 let (tag, wire_type) = decode_key(&mut buf)?;
86 match tag {
87 4 => {
88 let value =
89 take_length_delimited(wire_type, &mut buf).map_err(|mut error| {
90 error.push("Request", "symbols");
91 error
92 })?;
93 let symbol = std::str::from_utf8(value).map_err(|_| {
94 let mut error =
95 DecodeError::new("invalid string value: data is not UTF-8 encoded");
96 error.push("Request", "symbols");
97 error
98 })?;
99 symbols.push(symbol);
100 }
101 5 => {
102 let series =
103 take_length_delimited(wire_type, &mut buf).map_err(|mut error| {
104 error.push("Request", "timeseries");
105 error
106 })?;
107 timeseries.push(series);
108 }
109 _ => skip_field(wire_type, tag, &mut buf, DecodeContext::default())?,
110 }
111 }
112
113 Ok(Self {
114 symbols,
115 timeseries,
116 })
117 }
118}
119
120pub(crate) struct RemoteWriteV2WriteRequests {
121 pub samples: ContextReq,
122 pub histograms: ContextReq,
123 pub sample_count: u64,
124 pub histogram_count: u64,
125 pub semantic_index: SemanticIndexes,
128}
129
130pub(crate) fn decode_remote_write_v2(
131 is_zstd: bool,
132 body: Bytes,
133 native_histograms_enabled: bool,
134) -> Result<RemoteWriteV2WriteRequests> {
135 let decode_timer = crate::metrics::METRIC_HTTP_PROM_STORE_CODEC_ELAPSED
136 .with_label_values(&["decode", REMOTE_WRITE_V2_VERSION])
137 .start_timer();
138
139 let buf = if let Ok(buf) = try_decompress(is_zstd, &body[..]) {
142 buf
143 } else {
144 try_decompress(!is_zstd, &body[..])?
145 };
146 let request = BorrowedRequest::decode(&buf).context(error::DecodePromRemoteRequestSnafu)?;
147 drop(decode_timer);
148
149 let _convert_timer = crate::metrics::METRIC_HTTP_PROM_STORE_CODEC_ELAPSED
150 .with_label_values(&["convert", REMOTE_WRITE_V2_VERSION])
151 .start_timer();
152 convert_remote_write_v2(request, native_histograms_enabled)
153}
154
155fn convert_remote_write_v2(
156 request: BorrowedRequest<'_>,
157 native_histograms_enabled: bool,
158) -> Result<RemoteWriteV2WriteRequests> {
159 ensure!(
160 request.symbols.first().copied() == Some(""),
161 error::InvalidPromRemoteRequestSnafu {
162 msg: "remote write v2 symbols must start with an empty string".to_string(),
163 }
164 );
165
166 let mut sample_tables = HashMap::<PromCtx, HashMap<String, TableData>>::new();
167 let mut histogram_tables = HashMap::<PromCtx, HashMap<String, TableData>>::new();
168 let mut label_names = HashSet::new();
169 let mut sample_count_total = 0;
170 let mut histogram_count_total = 0;
171 let mut labels_refs = Vec::new();
172 let mut metadata = Metadata::default();
173 let mut scratch = LeafScratch::default();
174 let mut semantic_index = SemanticIndexes::default();
175
176 for series in request.timeseries {
177 let counts = scan_series(series, &mut labels_refs, &mut metadata)
178 .context(error::DecodePromRemoteRequestSnafu)?;
179
180 ensure!(
181 native_histograms_enabled || counts.histograms == 0,
182 error::InvalidPromRemoteRequestSnafu {
183 msg: "prometheus remote write v2 native histogram ingestion is experimental; set prom_store.experimental_enable_prometheus_native_histogram = true to enable it"
184 .to_string(),
185 }
186 );
187
188 if counts.samples == 0 && counts.histograms == 0 {
189 decode_series_leaves(series, None, Vec::new(), 0, &mut scratch)?;
190 continue;
191 }
192
193 let (prom_ctx, table_name, tags) =
194 resolve_series_labels(&request.symbols, &labels_refs, &mut label_names)?;
195 ensure_no_internal_histogram_labels(&tags)?;
196 record_series_metadata(
197 &mut semantic_index,
198 &request.symbols,
199 &metadata,
200 &prom_ctx,
201 &table_name,
202 )?;
203 let has_other_value_type = if counts.samples > 0 {
204 counts.histograms > 0
205 || histogram_tables
206 .get(&prom_ctx)
207 .is_some_and(|tables| tables.contains_key(&table_name))
208 } else {
209 sample_tables
210 .get(&prom_ctx)
211 .is_some_and(|tables| tables.contains_key(&table_name))
212 };
213 ensure!(
214 !has_other_value_type,
215 error::InvalidPromRemoteRequestSnafu {
216 msg: format!(
217 "remote write v2 metric `{table_name}` contains both samples and native histograms"
218 ),
219 }
220 );
221
222 let column_count =
223 tags.len()
224 .checked_add(2)
225 .with_context(|| error::InvalidPromRemoteRequestSnafu {
226 msg: "remote write v2 series has too many labels".to_string(),
227 })?;
228 let (writer, row_count) = if counts.samples > 0 {
229 (
230 SeriesWriter::Samples(get_or_create_table_data(
231 &mut sample_tables,
232 prom_ctx,
233 table_name,
234 column_count,
235 counts.samples,
236 )),
237 counts.samples,
238 )
239 } else {
240 (
241 SeriesWriter::Histograms(get_or_create_table_data(
242 &mut histogram_tables,
243 prom_ctx,
244 table_name,
245 column_count,
246 counts.histograms,
247 )),
248 counts.histograms,
249 )
250 };
251
252 decode_series_leaves(series, Some(writer), tags, row_count, &mut scratch)?;
253 sample_count_total = checked_total(sample_count_total, counts.samples, "sample")?;
254 histogram_count_total =
255 checked_total(histogram_count_total, counts.histograms, "histogram")?;
256 }
257
258 Ok(RemoteWriteV2WriteRequests {
259 samples: into_context_req(sample_tables),
260 histograms: into_context_req(histogram_tables),
261 sample_count: sample_count_total,
262 histogram_count: histogram_count_total,
263 semantic_index,
264 })
265}
266
267fn record_series_metadata(
277 index: &mut SemanticIndexes,
278 symbols: &[&str],
279 series_metadata: &Metadata,
280 prom_ctx: &PromCtx,
281 table_name: &str,
282) -> Result<()> {
283 if series_metadata.help_ref != 0 {
285 symbol_ref(symbols, series_metadata.help_ref, "metadata help")?;
286 }
287 let unit = if series_metadata.unit_ref != 0 {
288 Some(symbol_ref(
289 symbols,
290 series_metadata.unit_ref,
291 "metadata unit",
292 )?)
293 } else {
294 None
295 };
296 let metric_type = metric_type_value(series_metadata.r#type);
297 let ucum = unit.and_then(|unit| openmetrics_unit_to_ucum(unit.trim()));
298 if metric_type.is_none() && ucum.is_none() {
299 return Ok(());
300 }
301
302 let index = index.index_for(prom_ctx.schema.as_deref());
303 if let Some(metric_type) = metric_type {
304 index.record_scalar(table_name, SEMANTIC_METRIC_TYPE, metric_type);
305 index.record_scalar(
306 table_name,
307 SEMANTIC_METRIC_METADATA_QUALITY,
308 METADATA_QUALITY_DECLARED,
309 );
310 }
311 if let Some(ucum) = ucum {
312 index.record_scalar(table_name, SEMANTIC_METRIC_UNIT, ucum);
313 }
314 Ok(())
315}
316
317fn metric_type_value(wire_type: i32) -> Option<&'static str> {
320 match metadata::MetricType::try_from(wire_type).ok()? {
321 metadata::MetricType::Unspecified => None,
322 metadata::MetricType::Counter => Some(METRIC_TYPE_COUNTER),
323 metadata::MetricType::Gauge => Some(METRIC_TYPE_GAUGE),
324 metadata::MetricType::Histogram => Some(METRIC_TYPE_HISTOGRAM),
325 metadata::MetricType::Gaugehistogram => Some(METRIC_TYPE_GAUGE_HISTOGRAM),
326 metadata::MetricType::Summary => Some(METRIC_TYPE_SUMMARY),
327 metadata::MetricType::Info => Some(METRIC_TYPE_INFO),
328 metadata::MetricType::Stateset => Some(METRIC_TYPE_STATESET),
329 }
330}
331
332#[derive(Default)]
333struct SeriesCounts {
334 samples: usize,
335 histograms: usize,
336}
337
338fn scan_series(
339 mut buf: &[u8],
340 labels_refs: &mut Vec<u32>,
341 metadata: &mut Metadata,
342) -> std::result::Result<SeriesCounts, DecodeError> {
343 labels_refs.clear();
344 metadata.clear();
345 let mut counts = SeriesCounts::default();
346
347 while buf.has_remaining() {
348 let (tag, wire_type) = decode_key(&mut buf)?;
349 match tag {
350 TIME_SERIES_LABELS_REFS_TAG => {
351 uint32::merge_repeated(wire_type, labels_refs, &mut buf, DecodeContext::default())
352 .map_err(|mut error| {
353 error.push("TimeSeries", "labels_refs");
354 error
355 })?
356 }
357 TIME_SERIES_SAMPLES_TAG => {
358 take_length_delimited(wire_type, &mut buf).map_err(|mut error| {
359 error.push("TimeSeries", "samples");
360 error
361 })?;
362 counts.samples = counts.samples.checked_add(1).ok_or_else(|| {
363 DecodeError::new("remote write v2 sample count overflows usize")
364 })?;
365 }
366 TIME_SERIES_HISTOGRAMS_TAG => {
367 take_length_delimited(wire_type, &mut buf).map_err(|mut error| {
368 error.push("TimeSeries", "histograms");
369 error
370 })?;
371 counts.histograms = counts.histograms.checked_add(1).ok_or_else(|| {
372 DecodeError::new("remote write v2 histogram count overflows usize")
373 })?;
374 }
375 TIME_SERIES_EXEMPLARS_TAG => {
376 take_length_delimited(wire_type, &mut buf)?;
377 }
378 TIME_SERIES_METADATA_TAG => {
379 message::merge(wire_type, metadata, &mut buf, DecodeContext::default()).map_err(
380 |mut error| {
381 error.push("TimeSeries", "metadata");
382 error
383 },
384 )?
385 }
386 _ => skip_field(wire_type, tag, &mut buf, DecodeContext::default())?,
387 }
388 }
389
390 Ok(counts)
391}
392
393#[derive(Default)]
394struct LeafScratch {
395 sample: Sample,
396 histogram: Histogram,
397 exemplar: Exemplar,
398}
399
400enum SeriesWriter<'a> {
401 Samples(&'a mut TableData),
402 Histograms(&'a mut TableData),
403}
404
405fn decode_series_leaves(
406 mut buf: &[u8],
407 mut writer: Option<SeriesWriter<'_>>,
408 mut tags: PromTags<'_>,
409 mut rows_remaining: usize,
410 scratch: &mut LeafScratch,
411) -> Result<()> {
412 let mut sample_row_template = None;
413
414 while buf.has_remaining() {
415 let (tag, wire_type) = decode_key(&mut buf).context(error::DecodePromRemoteRequestSnafu)?;
416 match tag {
417 TIME_SERIES_LABELS_REFS_TAG => {
418 skip_field(wire_type, tag, &mut buf, DecodeContext::default())
419 .context(error::DecodePromRemoteRequestSnafu)?
420 }
421 TIME_SERIES_SAMPLES_TAG => {
422 scratch.sample.clear();
423 message::merge(
424 wire_type,
425 &mut scratch.sample,
426 &mut buf,
427 DecodeContext::default(),
428 )
429 .map_err(|mut error| {
430 error.push("TimeSeries", "samples");
431 error
432 })
433 .context(error::DecodePromRemoteRequestSnafu)?;
434 rows_remaining = rows_remaining.checked_sub(1).with_context(|| {
435 error::InvalidPromRemoteRequestSnafu {
436 msg: "remote write v2 sample count changed between scans".to_string(),
437 }
438 })?;
439 if let Some(SeriesWriter::Samples(table_data)) = &mut writer {
440 if sample_row_template.is_none() {
441 let timestamp_index = table_data.ensure_column_by_name(
442 greptime_timestamp(),
443 ColumnDataType::TimestampMillisecond,
444 SemanticType::Timestamp,
445 )?;
446 let value_index = table_data.ensure_column_by_name(
447 greptime_value(),
448 ColumnDataType::Float64,
449 SemanticType::Field,
450 )?;
451 let mut row = table_data.alloc_one_row();
452 row_writer::write_tags(
453 table_data,
454 std::mem::take(&mut tags).into_iter(),
455 &mut row,
456 )?;
457 sample_row_template = Some((row, timestamp_index, value_index));
458 }
459
460 if let Some((row, timestamp_index, value_index)) = sample_row_template.as_mut()
461 {
462 row[*timestamp_index].value_data = Some(
463 ValueData::TimestampMillisecondValue(scratch.sample.timestamp),
464 );
465 row[*value_index].value_data =
466 Some(ValueData::F64Value(scratch.sample.value));
467 let row = if rows_remaining == 0 {
468 std::mem::take(row)
469 } else {
470 row.clone()
471 };
472 table_data.add_row(row);
473 }
474 }
475 }
476 TIME_SERIES_HISTOGRAMS_TAG => {
477 scratch.histogram.clear();
478 message::merge(
479 wire_type,
480 &mut scratch.histogram,
481 &mut buf,
482 DecodeContext::default(),
483 )
484 .map_err(|mut error| {
485 error.push("TimeSeries", "histograms");
486 error
487 })
488 .context(error::DecodePromRemoteRequestSnafu)?;
489 rows_remaining = rows_remaining.checked_sub(1).with_context(|| {
490 error::InvalidPromRemoteRequestSnafu {
491 msg: "remote write v2 histogram count changed between scans".to_string(),
492 }
493 })?;
494 if let Some(SeriesWriter::Histograms(table_data)) = &mut writer {
495 if rows_remaining == 0 {
496 write_native_histogram(
497 table_data,
498 &scratch.histogram,
499 std::mem::take(&mut tags).into_iter(),
500 )?;
501 } else {
502 write_native_histogram(
503 table_data,
504 &scratch.histogram,
505 tags.iter().cloned(),
506 )?;
507 }
508 }
509 }
510 TIME_SERIES_EXEMPLARS_TAG => {
511 scratch.exemplar.clear();
512 message::merge(
513 wire_type,
514 &mut scratch.exemplar,
515 &mut buf,
516 DecodeContext::default(),
517 )
518 .map_err(|mut error| {
519 error.push("TimeSeries", "exemplars");
520 error
521 })
522 .context(error::DecodePromRemoteRequestSnafu)?;
523 }
524 TIME_SERIES_METADATA_TAG => {
525 take_length_delimited(wire_type, &mut buf)
526 .map_err(|mut error| {
527 error.push("TimeSeries", "metadata");
528 error
529 })
530 .context(error::DecodePromRemoteRequestSnafu)?;
531 }
532 _ => skip_field(wire_type, tag, &mut buf, DecodeContext::default())
533 .context(error::DecodePromRemoteRequestSnafu)?,
534 }
535 }
536
537 ensure!(
538 rows_remaining == 0,
539 error::InvalidPromRemoteRequestSnafu {
540 msg: "remote write v2 row count changed between scans".to_string(),
541 }
542 );
543
544 Ok(())
545}
546
547fn take_length_delimited<'a>(
548 wire_type: WireType,
549 buf: &mut &'a [u8],
550) -> std::result::Result<&'a [u8], DecodeError> {
551 if wire_type != WireType::LengthDelimited {
552 return Err(DecodeError::new(format!(
553 "invalid wire type: {wire_type:?} (expected LengthDelimited)"
554 )));
555 }
556
557 let len = decode_varint(buf)?;
558 let len =
559 usize::try_from(len).map_err(|_| DecodeError::new("length delimiter exceeds usize"))?;
560 if len > buf.len() {
561 return Err(DecodeError::new("buffer underflow"));
562 }
563 let (value, remaining) = buf.split_at(len);
564 *buf = remaining;
565 Ok(value)
566}
567
568fn checked_total(total: u64, count: usize, name: &str) -> Result<u64> {
569 let count =
570 u64::try_from(count)
571 .ok()
572 .with_context(|| error::InvalidPromRemoteRequestSnafu {
573 msg: format!("remote write v2 {name} count exceeds u64"),
574 })?;
575 total
576 .checked_add(count)
577 .with_context(|| error::InvalidPromRemoteRequestSnafu {
578 msg: format!("remote write v2 {name} count overflows u64"),
579 })
580}
581
582fn get_or_create_table_data(
583 tables: &mut HashMap<PromCtx, HashMap<String, TableData>>,
584 prom_ctx: PromCtx,
585 table_name: String,
586 column_count: usize,
587 row_count: usize,
588) -> &mut TableData {
589 match tables.entry(prom_ctx).or_default().entry(table_name) {
590 Entry::Occupied(entry) => {
591 let table_data = entry.into_mut();
592 table_data.reserve_rows(row_count);
593 table_data
594 }
595 Entry::Vacant(entry) => entry.insert(TableData::new(column_count, row_count)),
596 }
597}
598
599fn write_native_histogram<'a>(
600 table_data: &mut TableData,
601 histogram: &Histogram,
602 tags: impl Iterator<Item = (&'a str, String)>,
603) -> Result<()> {
604 let value = encode_native_histogram(histogram).map_err(|error| {
605 error::InvalidPromRemoteRequestSnafu {
606 msg: format!("remote write v2 {error}"),
607 }
608 .build()
609 })?;
610 let column_schema = native_histogram_column_schema().map_err(|error| {
611 error::InvalidPromRemoteRequestSnafu {
612 msg: format!("remote write v2 {error}"),
613 }
614 .build()
615 })?;
616
617 let mut row = table_data.alloc_one_row();
621 row_writer::write_ts_to_millis(
622 table_data,
623 greptime_timestamp(),
624 Some(histogram.timestamp),
625 Precision::Millisecond,
626 &mut row,
627 )?;
628 row_writer::write_by_schema(
629 table_data,
630 std::iter::once((column_schema, Some(value))),
631 &mut row,
632 )?;
633
634 row_writer::write_tags(table_data, tags, &mut row)?;
635 table_data.add_row(row);
636
637 Ok(())
638}
639
640fn ensure_no_internal_histogram_labels(tags: &PromTags<'_>) -> Result<()> {
641 for (name, _) in tags {
643 ensure!(
644 *name != greptime_native_histogram() && *name != NATIVE_HISTOGRAM_FIELD,
645 error::InvalidPromRemoteRequestSnafu {
646 msg: format!(
647 "remote write v2 label `{name}` conflicts with an internal native histogram label"
648 ),
649 }
650 );
651 }
652
653 Ok(())
654}
655
656fn resolve_series_labels<'a>(
657 symbols: &'a [&str],
658 labels_refs: &[u32],
659 label_names: &mut HashSet<&'a str>,
660) -> Result<ResolvedSeriesLabels<'a>> {
661 ensure!(
662 labels_refs.len().is_multiple_of(2),
663 error::InvalidPromRemoteRequestSnafu {
664 msg: "remote write v2 labels_refs must contain name/value pairs".to_string(),
665 }
666 );
667
668 let mut prom_ctx = PromCtx::default();
669 let mut table_name = None;
670 let mut tags = Vec::with_capacity(labels_refs.len() / 2);
671 label_names.clear();
672
673 for pair in labels_refs.chunks_exact(2) {
674 let name = symbol_ref(symbols, pair[0], "label name")?;
675 let value = symbol_ref(symbols, pair[1], "label value")?;
676 validate_label(name)?;
677 ensure!(
678 label_names.insert(name),
679 error::InvalidPromRemoteRequestSnafu {
680 msg: format!("remote write v2 label name `{name}` is repeated"),
681 }
682 );
683
684 if name == METRIC_NAME_LABEL {
685 table_name = Some(value.to_string());
686 continue;
687 }
688 if apply_remote_write_special_label(name, value, &mut prom_ctx) {
689 continue;
690 }
691
692 tags.push((name, value.to_string()));
693 }
694
695 let table_name = table_name.with_context(|| error::InvalidPromRemoteRequestSnafu {
696 msg: "missing '__name__' label in time-series".to_string(),
697 })?;
698 ensure!(
699 !table_name.is_empty(),
700 error::InvalidPromRemoteRequestSnafu {
701 msg: "remote write v2 label `__name__` value must not be empty".to_string(),
702 }
703 );
704
705 Ok((prom_ctx, table_name, tags))
706}
707
708fn validate_label(name: &str) -> Result<()> {
709 ensure!(
710 validate_label_name(name.as_bytes()),
711 error::InvalidPromRemoteRequestSnafu {
712 msg: format!("remote write v2 invalid label name `{name}`"),
713 }
714 );
715
716 Ok(())
717}
718
719fn symbol_ref<'a>(symbols: &'a [&str], idx: u32, field: &str) -> Result<&'a str> {
720 let idx = usize::try_from(idx)
721 .ok()
722 .with_context(|| error::InvalidPromRemoteRequestSnafu {
723 msg: format!("remote write v2 {field} symbol reference exceeds usize"),
724 })?;
725 symbols
726 .get(idx)
727 .copied()
728 .with_context(|| error::InvalidPromRemoteRequestSnafu {
729 msg: format!(
730 "remote write v2 {field} symbol reference {idx} is out of range, symbols len: {}",
731 symbols.len()
732 ),
733 })
734}
735
736#[allow(deprecated)]
737fn apply_remote_write_special_label(name: &str, value: &str, prom_ctx: &mut PromCtx) -> bool {
738 match name {
739 SCHEMA_LABEL => {
740 prom_ctx.schema = Some(value.to_string());
741 true
742 }
743 DATABASE_LABEL | DATABASE_LABEL_ALT => {
744 if prom_ctx.schema.is_none() {
745 prom_ctx.schema = Some(value.to_string());
746 }
747 true
748 }
749 PHYSICAL_TABLE_LABEL | PHYSICAL_TABLE_LABEL_ALT => {
750 prom_ctx.physical_table = Some(value.to_string());
751 true
752 }
753 _ => false,
754 }
755}
756
757fn into_context_req(tables: HashMap<PromCtx, HashMap<String, TableData>>) -> ContextReq {
758 let mut ctx_req = ContextReq::default();
759 for (prom_ctx, tables) in tables {
760 let mut opt = ContextOpt::default();
761 if let Some(schema) = prom_ctx.schema {
762 opt.set_schema(schema);
763 }
764 if let Some(physical_table) = prom_ctx.physical_table {
765 opt.set_physical_table(physical_table);
766 }
767
768 ctx_req.add_rows(
769 opt,
770 tables.into_iter().map(|(table_name, table_data)| {
771 table_data_to_row_insert_request(table_name, table_data)
772 }),
773 );
774 }
775 ctx_req
776}
777
778fn table_data_to_row_insert_request(table_name: String, table_data: TableData) -> RowInsertRequest {
779 let num_columns = table_data.num_columns();
780 let (schema, mut rows) = table_data.into_schema_and_rows();
781 for row in &mut rows {
782 if num_columns > row.values.len() {
783 row.values.resize(num_columns, Value { value_data: None });
784 }
785 }
786
787 RowInsertRequest {
788 table_name,
789 rows: Some(Rows { schema, rows }),
790 }
791}
792
793#[cfg(any(test, feature = "testing"))]
794pub mod test_util {
795 use api::greptime_proto::io::prometheus::write::v2::{Histogram, Request, Sample, TimeSeries};
796 use api::v1::RowInsertRequest;
797 use bytes::Bytes;
798 use prost::Message;
799 use snafu::ResultExt;
800
801 use crate::error::{self, Result};
802 use crate::prom_remote_write::try_decompress;
803 use crate::prom_store::snappy_compress;
804
805 pub fn request_with_labels_and_samples(
806 labels: Vec<(&str, &str)>,
807 samples: Vec<Sample>,
808 ) -> Request {
809 request_with_labels(labels, samples, Vec::new())
810 }
811
812 pub fn request_with_labels_and_histograms(
813 labels: Vec<(&str, &str)>,
814 histograms: Vec<Histogram>,
815 ) -> Request {
816 request_with_labels(labels, Vec::new(), histograms)
817 }
818
819 pub fn decode_request(is_zstd: bool, body: Bytes) -> Result<Request> {
820 let buf = if let Ok(buf) = try_decompress(is_zstd, &body[..]) {
821 buf
822 } else {
823 try_decompress(!is_zstd, &body[..])?
824 };
825 Request::decode(&buf[..]).context(error::DecodePromRemoteRequestSnafu)
826 }
827
828 pub fn write_requests(
829 request: Request,
830 ) -> Result<(Vec<RowInsertRequest>, Vec<RowInsertRequest>, u64, u64)> {
831 let body = Bytes::from(snappy_compress(&request.encode_to_vec())?);
832 decode_write_requests(false, body, true)
833 }
834
835 pub fn decode_write_requests(
836 is_zstd: bool,
837 body: Bytes,
838 native_histograms_enabled: bool,
839 ) -> Result<(Vec<RowInsertRequest>, Vec<RowInsertRequest>, u64, u64)> {
840 let requests = super::decode_remote_write_v2(is_zstd, body, native_histograms_enabled)?;
841 Ok((
842 requests.samples.all_req().collect(),
843 requests.histograms.all_req().collect(),
844 requests.sample_count,
845 requests.histogram_count,
846 ))
847 }
848
849 pub fn decode_uncompressed_write_requests(
850 body: &[u8],
851 native_histograms_enabled: bool,
852 ) -> Result<(Vec<RowInsertRequest>, Vec<RowInsertRequest>, u64, u64)> {
853 let request =
854 super::BorrowedRequest::decode(body).context(error::DecodePromRemoteRequestSnafu)?;
855 let requests = super::convert_remote_write_v2(request, native_histograms_enabled)?;
856 Ok((
857 requests.samples.all_req().collect(),
858 requests.histograms.all_req().collect(),
859 requests.sample_count,
860 requests.histogram_count,
861 ))
862 }
863
864 pub fn histogram(timestamp: i64) -> Histogram {
865 Histogram {
866 timestamp,
867 ..Default::default()
868 }
869 }
870
871 fn request_with_labels(
872 labels: Vec<(&str, &str)>,
873 samples: Vec<Sample>,
874 histograms: Vec<Histogram>,
875 ) -> Request {
876 let mut symbols = vec!["".to_string()];
877 let mut labels_refs = Vec::with_capacity(labels.len() * 2);
878 for (name, value) in labels {
879 labels_refs.push(push_symbol(&mut symbols, name));
880 labels_refs.push(push_symbol(&mut symbols, value));
881 }
882
883 Request {
884 symbols,
885 timeseries: vec![TimeSeries {
886 labels_refs,
887 samples,
888 histograms,
889 exemplars: Vec::new(),
890 metadata: None,
891 }],
892 }
893 }
894
895 fn push_symbol(symbols: &mut Vec<String>, symbol: &str) -> u32 {
896 if let Some(idx) = symbols.iter().position(|s| s == symbol) {
897 return idx as u32;
898 }
899
900 let idx = symbols.len();
901 symbols.push(symbol.to_string());
902 idx as u32
903 }
904}
905
906#[cfg(test)]
907mod tests {
908 use std::sync::Arc;
909
910 use api::v1::value::ValueData;
911 use common_query::prelude::{greptime_timestamp, greptime_value, set_default_prefix};
912 use session::context::QueryContext;
913
914 use super::*;
915 use crate::error;
916 use crate::http::prom_store::PHYSICAL_TABLE_PARAM;
917 use crate::prom_store::{DATABASE_LABEL, PHYSICAL_TABLE_LABEL};
918
919 #[test]
920 fn test_decode_remote_write_v2_request() {
921 let request = Request {
922 symbols: vec![
923 "".to_string(),
924 "__name__".to_string(),
925 "http_requests_total".to_string(),
926 ],
927 timeseries: vec![TimeSeries {
928 labels_refs: vec![1, 2],
929 samples: vec![Sample {
930 value: 42.0,
931 timestamp: 1000,
932 start_timestamp: 0,
933 }],
934 histograms: Vec::new(),
935 exemplars: Vec::new(),
936 metadata: Some(Metadata {
937 r#type: metadata::MetricType::Counter as i32,
938 help_ref: 0,
939 unit_ref: 0,
940 }),
941 }],
942 };
943 let body =
944 Bytes::from(crate::prom_store::snappy_compress(&request.encode_to_vec()).unwrap());
945
946 let decoded = test_util::decode_request(false, body.clone()).unwrap();
947
948 assert_eq!(decoded.symbols, request.symbols);
949 assert_eq!(decoded.timeseries.len(), 1);
950 assert_eq!(decoded.timeseries[0].labels_refs, vec![1, 2]);
951 assert_eq!(decoded.timeseries[0].samples.len(), 1);
952 assert_eq!(decoded.timeseries[0].samples[0].value, 42.0);
953 assert_eq!(decoded.timeseries[0].metadata.as_ref().unwrap().r#type, 1);
954 assert_eq!(
955 decode_remote_write_v2(true, body, true)
956 .unwrap()
957 .sample_count,
958 1
959 );
960 }
961
962 #[test]
963 fn test_fused_decoder_accepts_arbitrary_field_order_and_split_labels() {
964 let mut first_sample = Sample {
965 value: 42.0,
966 timestamp: 1000,
967 start_timestamp: 500,
968 }
969 .encode_to_vec();
970 first_sample.extend(varint_field(90, 1));
971 let second_sample = Sample {
972 value: 43.0,
973 timestamp: 2000,
974 start_timestamp: 1000,
975 }
976 .encode_to_vec();
977
978 let mut series = encoded_message_field(2, &first_sample);
979 series.extend(packed_u32_field(1, &[1]));
980 series.extend(varint_field(90, 1));
981 series.extend(encoded_message_field(2, &second_sample));
982 series.extend(varint_field(1, 2));
983
984 let mut wire = encoded_message_field(5, &series);
985 wire.extend(string_field(4, b""));
986 wire.extend(varint_field(90, 1));
987 wire.extend(string_field(4, METRIC_NAME_LABEL.as_bytes()));
988 wire.extend(encoded_message_field(5, &packed_u32_field(1, &[99])));
989 wire.extend(string_field(4, b"http_requests_total"));
990
991 let requests = decode_wire(&wire, true).unwrap();
992 assert_eq!(requests.sample_count, 2);
993 assert_eq!(requests.histogram_count, 0);
994 let rows = requests.samples.all_req().next().unwrap().rows.unwrap();
995 assert_eq!(rows.rows.len(), 2);
996 assert_eq!(
997 rows.schema
998 .iter()
999 .map(|column| column.column_name.as_str())
1000 .collect::<Vec<_>>(),
1001 vec![greptime_timestamp(), greptime_value()]
1002 );
1003 assert_eq!(
1004 rows.rows[0].values[1].value_data,
1005 Some(ValueData::F64Value(42.0))
1006 );
1007 assert_eq!(
1008 rows.rows[1].values[1].value_data,
1009 Some(ValueData::F64Value(43.0))
1010 );
1011 }
1012
1013 #[test]
1014 fn test_fused_decoder_accepts_histogram_before_labels_and_unknown_fields() {
1015 let mut histogram = Histogram {
1016 count: Some(Count::CountInt(0)),
1017 zero_count: Some(ZeroCount::ZeroCountInt(0)),
1018 timestamp: 2000,
1019 start_timestamp: 1000,
1020 ..Default::default()
1021 }
1022 .encode_to_vec();
1023 histogram.extend(varint_field(90, 1));
1024
1025 let mut series = encoded_message_field(3, &histogram);
1026 series.extend(packed_u32_field(1, &[1, 2]));
1027 let wire = request_wire(&["", METRIC_NAME_LABEL, "metric"], &[series]);
1028
1029 let requests = decode_wire(&wire, true).unwrap();
1030 assert_eq!(requests.histogram_count, 1);
1031 let rows = requests.histograms.all_req().next().unwrap().rows.unwrap();
1032 assert_eq!(
1033 histogram_field_value(&rows, 0, START_TIMESTAMP_FIELD),
1034 Some(ValueData::TimestampMillisecondValue(1000))
1035 );
1036 }
1037
1038 #[test]
1039 fn test_fused_decoder_rejects_malformed_wire() {
1040 let mut wrong_request_wire = varint_field(4, 0);
1041 let invalid_utf8 = string_field(4, &[0xff]);
1042
1043 let mut truncated_request = Vec::new();
1044 prost::encoding::encode_key(5, WireType::LengthDelimited, &mut truncated_request);
1045 prost::encoding::encode_varint(2, &mut truncated_request);
1046 truncated_request.push(0);
1047
1048 let mut oversized_request = Vec::new();
1049 prost::encoding::encode_key(4, WireType::LengthDelimited, &mut oversized_request);
1050 prost::encoding::encode_varint(u64::MAX, &mut oversized_request);
1051
1052 let malformed_varint = vec![0x80; 10];
1053
1054 let mut wrong_labels_wire = Vec::new();
1055 prost::encoding::encode_key(1, WireType::SixtyFourBit, &mut wrong_labels_wire);
1056 wrong_labels_wire.extend([0; 8]);
1057 wrong_labels_wire = request_wire(&[""], &[std::mem::take(&mut wrong_labels_wire)]);
1058
1059 let wrong_series_wire = request_wire(&[""], &[varint_field(2, 0)]);
1060
1061 let mut truncated_series = Vec::new();
1062 prost::encoding::encode_key(2, WireType::LengthDelimited, &mut truncated_series);
1063 prost::encoding::encode_varint(2, &mut truncated_series);
1064 truncated_series.push(0x08);
1065 let truncated_series = request_wire(&[""], &[truncated_series]);
1066
1067 let mut oversized_series = Vec::new();
1068 prost::encoding::encode_key(3, WireType::LengthDelimited, &mut oversized_series);
1069 prost::encoding::encode_varint(u64::MAX, &mut oversized_series);
1070 let oversized_series = request_wire(&[""], &[oversized_series]);
1071
1072 let invalid_sample = series_wire(&[1, 2], 2, &varint_field(1, 1));
1073 let invalid_sample = request_wire(&["", METRIC_NAME_LABEL, "metric"], &[invalid_sample]);
1074 let invalid_histogram = series_wire(&[1, 2], 3, &varint_field(3, 1));
1075 let invalid_histogram =
1076 request_wire(&["", METRIC_NAME_LABEL, "metric"], &[invalid_histogram]);
1077
1078 for (name, wire) in [
1079 (
1080 "wrong request wire type",
1081 std::mem::take(&mut wrong_request_wire),
1082 ),
1083 ("invalid utf8", invalid_utf8),
1084 ("truncated request", truncated_request),
1085 ("oversized request length", oversized_request),
1086 ("malformed varint", malformed_varint),
1087 ("wrong labels wire type", wrong_labels_wire),
1088 ("wrong series wire type", wrong_series_wire),
1089 ("truncated series", truncated_series),
1090 ("oversized series length", oversized_series),
1091 ("invalid sample", invalid_sample),
1092 ("invalid histogram", invalid_histogram),
1093 ] {
1094 let error = decode_wire_error(&wire, true, name);
1095 assert!(
1096 matches!(error, error::Error::DecodePromRemoteRequest { .. }),
1097 "{name}: {error}"
1098 );
1099 }
1100 }
1101
1102 #[test]
1103 fn test_fused_decoder_rejects_malformed_ignored_messages() {
1104 for tag in [4, 5] {
1105 let mut series = series_wire(&[1, 2], 2, &Sample::default().encode_to_vec());
1106 series.extend(encoded_message_field(tag, &[0x08]));
1107 let wire = request_wire(&["", METRIC_NAME_LABEL, "metric"], &[series]);
1108
1109 let error = decode_wire_error(&wire, true, "malformed ignored message");
1110 assert!(matches!(
1111 error,
1112 error::Error::DecodePromRemoteRequest { .. }
1113 ));
1114 }
1115 }
1116
1117 #[test]
1118 fn test_fused_decoder_ignores_exemplar_symbol_refs() {
1119 let request = Request {
1120 symbols: vec![
1121 String::new(),
1122 METRIC_NAME_LABEL.to_string(),
1123 "metric".to_string(),
1124 ],
1125 timeseries: vec![TimeSeries {
1126 labels_refs: vec![1, 2],
1127 samples: vec![Sample::default()],
1128 exemplars: vec![Exemplar {
1129 labels_refs: vec![99],
1130 ..Default::default()
1131 }],
1132 ..Default::default()
1133 }],
1134 };
1135
1136 assert_eq!(decode_test_request(request).unwrap().sample_count, 1);
1137 }
1138
1139 #[test]
1140 fn test_fused_decoder_preserves_empty_request_and_series_behavior() {
1141 let request = Request {
1142 symbols: vec![
1143 String::new(),
1144 METRIC_NAME_LABEL.to_string(),
1145 "metric".to_string(),
1146 "job".to_string(),
1147 ],
1148 timeseries: vec![
1149 TimeSeries {
1150 labels_refs: vec![99, 99],
1151 ..Default::default()
1152 },
1153 TimeSeries {
1154 labels_refs: vec![1],
1155 ..Default::default()
1156 },
1157 TimeSeries {
1158 labels_refs: vec![3, 2, 3, 2],
1159 ..Default::default()
1160 },
1161 TimeSeries::default(),
1162 ],
1163 };
1164
1165 let requests = decode_test_request(request).unwrap();
1166 assert_eq!(requests.sample_count, 0);
1167 assert_eq!(requests.histogram_count, 0);
1168
1169 let requests = decode_test_request(Request {
1170 symbols: vec![String::new()],
1171 timeseries: Vec::new(),
1172 })
1173 .unwrap();
1174 assert_eq!(requests.sample_count, 0);
1175 assert!(decode_wire(&[], true).is_err());
1176 assert!(decode_wire(&[0x0a, 0x00], true).is_err());
1177 }
1178
1179 #[test]
1180 fn test_fused_decoder_rejects_duplicate_resolved_label_names() {
1181 let request = Request {
1182 symbols: vec![
1183 String::new(),
1184 METRIC_NAME_LABEL.to_string(),
1185 "metric".to_string(),
1186 "job".to_string(),
1187 "job".to_string(),
1188 "api".to_string(),
1189 "worker".to_string(),
1190 ],
1191 timeseries: vec![TimeSeries {
1192 labels_refs: vec![1, 2, 3, 5, 4, 6],
1193 samples: vec![Sample::default()],
1194 ..Default::default()
1195 }],
1196 };
1197
1198 assert_invalid(
1199 "duplicate resolved labels",
1200 request,
1201 "label name `job` is repeated",
1202 );
1203 }
1204
1205 #[test]
1206 fn test_fused_decoder_pins_experimental_error_precedence() {
1207 let histogram = series_wire(&[1, 2], 3, &Histogram::default().encode_to_vec());
1208 let malformed_sample = series_wire(&[1, 2], 2, &[0x08]);
1209
1210 let wire = request_wire(
1211 &["", METRIC_NAME_LABEL, "metric"],
1212 &[histogram.clone(), malformed_sample.clone()],
1213 );
1214 let error = decode_wire_error(&wire, false, "histogram before malformed series");
1215 assert!(error.to_string().contains("ingestion is experimental"));
1216
1217 let wire = request_wire(
1218 &["", METRIC_NAME_LABEL, "metric"],
1219 &[malformed_sample, histogram.clone()],
1220 );
1221 let error = decode_wire_error(&wire, false, "malformed series before histogram");
1222 assert!(matches!(
1223 error,
1224 error::Error::DecodePromRemoteRequest { .. }
1225 ));
1226
1227 let missing_name = series_wire(&[3, 4], 2, &Sample::default().encode_to_vec());
1228 let wire = request_wire(
1229 &["", METRIC_NAME_LABEL, "metric", "job", "api"],
1230 &[missing_name, histogram],
1231 );
1232 let error = decode_wire_error(&wire, false, "conversion error before histogram");
1233 assert!(error.to_string().contains("missing '__name__'"));
1234 }
1235
1236 #[test]
1237 fn test_into_context_req_samples() {
1238 let ctx_req = decode_test_request(test_util::request_with_labels_and_samples(
1239 vec![
1240 (METRIC_NAME_LABEL, "http_requests_total"),
1241 ("job", "api"),
1242 ("instance", "localhost:9090"),
1243 ],
1244 vec![
1245 Sample {
1246 value: 42.0,
1247 timestamp: 1000,
1248 start_timestamp: 0,
1249 },
1250 Sample {
1251 value: 43.0,
1252 timestamp: 2000,
1253 start_timestamp: 0,
1254 },
1255 ],
1256 ))
1257 .unwrap();
1258
1259 assert_eq!(ctx_req.sample_count, 2);
1260 assert_eq!(ctx_req.histogram_count, 0);
1261 assert_eq!(ctx_req.histograms.all_req().count(), 0);
1262 let mut inserts = ctx_req.samples.all_req().collect::<Vec<_>>();
1263 assert_eq!(inserts.len(), 1);
1264
1265 let request = inserts.pop().unwrap();
1266 assert_eq!(request.table_name, "http_requests_total");
1267 let rows = request.rows.unwrap();
1268 assert_eq!(rows.rows.len(), 2);
1269 assert_eq!(
1270 rows.schema
1271 .iter()
1272 .map(|col| col.column_name.as_str())
1273 .collect::<Vec<_>>(),
1274 vec![greptime_timestamp(), greptime_value(), "job", "instance"]
1275 );
1276 assert_eq!(
1277 rows.rows[0].values[0].value_data,
1278 Some(ValueData::TimestampMillisecondValue(1000))
1279 );
1280 assert_eq!(
1281 rows.rows[0].values[1].value_data,
1282 Some(ValueData::F64Value(42.0))
1283 );
1284 assert_eq!(
1285 rows.rows[0].values[2].value_data,
1286 Some(ValueData::StringValue("api".to_string()))
1287 );
1288 assert_eq!(
1289 rows.rows[0].values[3].value_data,
1290 Some(ValueData::StringValue("localhost:9090".to_string()))
1291 );
1292 assert_eq!(
1293 rows.rows[1].values[0].value_data,
1294 Some(ValueData::TimestampMillisecondValue(2000))
1295 );
1296 assert_eq!(
1297 rows.rows[1].values[1].value_data,
1298 Some(ValueData::F64Value(43.0))
1299 );
1300 assert_eq!(
1301 rows.rows[1].values[2].value_data,
1302 Some(ValueData::StringValue("api".to_string()))
1303 );
1304 assert_eq!(
1305 rows.rows[1].values[3].value_data,
1306 Some(ValueData::StringValue("localhost:9090".to_string()))
1307 );
1308 }
1309
1310 #[test]
1311 fn test_into_context_req_special_labels() {
1312 let ctx_req = decode_test_request(test_util::request_with_labels_and_samples(
1313 vec![
1314 (METRIC_NAME_LABEL, "cpu_usage"),
1315 (DATABASE_LABEL, "tenant_a"),
1316 (PHYSICAL_TABLE_LABEL, "metrics_physical"),
1317 ("job", "api"),
1318 ],
1319 vec![Sample {
1320 value: 1.0,
1321 timestamp: 1000,
1322 start_timestamp: 0,
1323 }],
1324 ))
1325 .unwrap();
1326
1327 let mut iter = ctx_req
1328 .samples
1329 .as_req_iter(Arc::new(QueryContext::with("greptime", "public")));
1330 let (ctx, reqs) = iter.next().unwrap();
1331 assert!(iter.next().is_none());
1332
1333 assert_eq!(ctx.current_schema(), "tenant_a");
1334 assert_eq!(
1335 ctx.extension(PHYSICAL_TABLE_PARAM),
1336 Some("metrics_physical")
1337 );
1338 assert_eq!(reqs.inserts.len(), 1);
1339
1340 let rows = reqs.inserts[0].rows.as_ref().unwrap();
1341 assert_eq!(
1342 rows.schema
1343 .iter()
1344 .map(|col| col.column_name.as_str())
1345 .collect::<Vec<_>>(),
1346 vec![greptime_timestamp(), greptime_value(), "job"]
1347 );
1348 }
1349
1350 #[test]
1351 fn test_into_context_req_rejects_invalid_requests() {
1352 let mut cases = Vec::new();
1353
1354 cases.push((
1355 "missing metric name",
1356 request_with_sample(vec![("job", "api")]),
1357 "missing '__name__'",
1358 ));
1359
1360 let mut request = request_with_sample(vec![(METRIC_NAME_LABEL, "metric")]);
1361 request.timeseries[0].labels_refs.push(1);
1362 cases.push((
1363 "odd label refs",
1364 request,
1365 "labels_refs must contain name/value pairs",
1366 ));
1367
1368 let mut request = request_with_sample(vec![(METRIC_NAME_LABEL, "metric")]);
1369 request.timeseries[0].labels_refs[1] = 99;
1370 cases.push((
1371 "out of range symbol ref",
1372 request,
1373 "symbol reference 99 is out of range",
1374 ));
1375
1376 let mut request = request_with_sample(vec![(METRIC_NAME_LABEL, "metric")]);
1377 request.symbols[0] = "not-empty".to_string();
1378 cases.push((
1379 "non-empty first symbol",
1380 request,
1381 "symbols must start with an empty string",
1382 ));
1383
1384 cases.push((
1385 "repeated label name",
1386 request_with_sample(vec![
1387 (METRIC_NAME_LABEL, "metric"),
1388 ("job", "api"),
1389 ("job", "worker"),
1390 ]),
1391 "label name `job` is repeated",
1392 ));
1393
1394 cases.push((
1395 "empty label name",
1396 request_with_sample(vec![(METRIC_NAME_LABEL, "metric"), ("", "api")]),
1397 "invalid label name",
1398 ));
1399
1400 cases.push((
1401 "invalid label name",
1402 request_with_sample(vec![(METRIC_NAME_LABEL, "metric"), ("has-dash", "api")]),
1403 "invalid label name",
1404 ));
1405
1406 cases.push((
1407 "dotted label name",
1408 request_with_sample(vec![(METRIC_NAME_LABEL, "metric"), ("service.name", "api")]),
1409 "invalid label name",
1410 ));
1411
1412 cases.push((
1413 "non-ascii label name",
1414 request_with_sample(vec![(METRIC_NAME_LABEL, "metric"), ("区域", "api")]),
1415 "invalid label name",
1416 ));
1417
1418 cases.push((
1419 "empty metric name",
1420 request_with_sample(vec![(METRIC_NAME_LABEL, "")]),
1421 "label `__name__` value must not be empty",
1422 ));
1423
1424 cases.push((
1425 "internal histogram label on samples",
1426 request_with_sample(vec![
1427 (METRIC_NAME_LABEL, "metric"),
1428 (greptime_native_histogram(), "user_value"),
1429 ]),
1430 "conflicts with an internal native histogram label",
1431 ));
1432
1433 cases.push((
1434 "int count with float zero count",
1435 request_with_histogram(Histogram {
1436 count: Some(Count::CountInt(1)),
1437 zero_count: Some(ZeroCount::ZeroCountFloat(0.5)),
1438 ..Default::default()
1439 }),
1440 "count and zero_count must use the same integer or float family",
1441 ));
1442
1443 cases.push((
1444 "float count with int zero count",
1445 request_with_histogram(Histogram {
1446 count: Some(Count::CountFloat(1.0)),
1447 zero_count: Some(ZeroCount::ZeroCountInt(1)),
1448 ..Default::default()
1449 }),
1450 "count and zero_count must use the same integer or float family",
1451 ));
1452
1453 cases.push((
1454 "reducible schema",
1455 request_with_histogram(Histogram {
1456 schema: 9,
1457 ..Default::default()
1458 }),
1459 "schema 9 must be reduced before ingestion",
1460 ));
1461
1462 cases.push((
1463 "unsupported schema",
1464 request_with_histogram(Histogram {
1465 schema: 53,
1466 ..Default::default()
1467 }),
1468 "schema 53 is unsupported",
1469 ));
1470
1471 cases.push((
1472 "standard schema with custom values",
1473 request_with_histogram(Histogram {
1474 schema: 1,
1475 custom_values: vec![1.0],
1476 ..Default::default()
1477 }),
1478 "standard native histogram must not use custom_values",
1479 ));
1480
1481 cases.push((
1482 "custom values with inf",
1483 request_with_histogram(Histogram {
1484 schema: CUSTOM_BUCKETS_SCHEMA,
1485 custom_values: vec![f64::INFINITY],
1486 ..Default::default()
1487 }),
1488 "custom_values must not contain +Inf or NaN",
1489 ));
1490
1491 cases.push((
1492 "custom values not sorted",
1493 request_with_histogram(Histogram {
1494 schema: CUSTOM_BUCKETS_SCHEMA,
1495 custom_values: vec![2.0, 1.0],
1496 ..Default::default()
1497 }),
1498 "custom_values must be sorted",
1499 ));
1500
1501 cases.push((
1502 "custom schema with zero bucket",
1503 request_with_histogram(Histogram {
1504 schema: CUSTOM_BUCKETS_SCHEMA,
1505 zero_count: Some(ZeroCount::ZeroCountInt(1)),
1506 ..Default::default()
1507 }),
1508 "custom native histogram must not use a zero bucket",
1509 ));
1510
1511 cases.push((
1512 "custom schema with negative buckets",
1513 request_with_histogram(Histogram {
1514 schema: CUSTOM_BUCKETS_SCHEMA,
1515 negative_spans: vec![BucketSpan {
1516 offset: -1,
1517 length: 1,
1518 }],
1519 negative_deltas: vec![1],
1520 ..Default::default()
1521 }),
1522 "custom native histogram must not use negative buckets",
1523 ));
1524
1525 cases.push((
1526 "span count mismatch",
1527 request_with_histogram(Histogram {
1528 positive_spans: vec![BucketSpan {
1529 offset: 0,
1530 length: 2,
1531 }],
1532 positive_deltas: vec![1],
1533 ..Default::default()
1534 }),
1535 "positive spans describe 2 buckets, found 1",
1536 ));
1537
1538 cases.push((
1539 "negative offset after first span",
1540 request_with_histogram(Histogram {
1541 count: Some(Count::CountInt(2)),
1542 positive_spans: vec![
1543 BucketSpan {
1544 offset: 0,
1545 length: 1,
1546 },
1547 BucketSpan {
1548 offset: -1,
1549 length: 1,
1550 },
1551 ],
1552 positive_deltas: vec![1, 0],
1553 ..Default::default()
1554 }),
1555 "positive span 2 has negative offset -1",
1556 ));
1557
1558 cases.push((
1559 "negative custom span offset",
1560 request_with_histogram(Histogram {
1561 count: Some(Count::CountInt(1)),
1562 schema: CUSTOM_BUCKETS_SCHEMA,
1563 custom_values: vec![1.0],
1564 positive_spans: vec![BucketSpan {
1565 offset: -1,
1566 length: 1,
1567 }],
1568 positive_deltas: vec![1],
1569 ..Default::default()
1570 }),
1571 "positive span 1 has negative offset -1",
1572 ));
1573
1574 cases.push((
1575 "integer bucket total mismatch",
1576 request_with_histogram(Histogram {
1577 count: Some(Count::CountInt(0)),
1578 positive_spans: vec![BucketSpan {
1579 offset: 0,
1580 length: 1,
1581 }],
1582 positive_deltas: vec![1],
1583 ..Default::default()
1584 }),
1585 "has 1 observations in buckets, count is 0",
1586 ));
1587
1588 cases.push((
1589 "negative float count",
1590 request_with_histogram(Histogram {
1591 count: Some(Count::CountFloat(-1.0)),
1592 ..Default::default()
1593 }),
1594 "float count must not be negative",
1595 ));
1596
1597 cases.push((
1598 "negative float zero count",
1599 request_with_histogram(Histogram {
1600 count: Some(Count::CountFloat(0.0)),
1601 zero_count: Some(ZeroCount::ZeroCountFloat(-1.0)),
1602 ..Default::default()
1603 }),
1604 "float zero_count must not be negative",
1605 ));
1606
1607 cases.push((
1608 "negative float bucket count",
1609 request_with_histogram(Histogram {
1610 count: Some(Count::CountFloat(0.0)),
1611 positive_spans: vec![BucketSpan {
1612 offset: 0,
1613 length: 1,
1614 }],
1615 positive_counts: vec![-1.0],
1616 ..Default::default()
1617 }),
1618 "positive bucket 1 count must not be negative",
1619 ));
1620
1621 cases.push((
1622 "custom span index out of range",
1623 request_with_histogram(Histogram {
1624 schema: CUSTOM_BUCKETS_SCHEMA,
1625 custom_values: vec![1.0],
1626 positive_spans: vec![BucketSpan {
1627 offset: 2,
1628 length: 1,
1629 }],
1630 positive_deltas: vec![1],
1631 ..Default::default()
1632 }),
1633 "positive bucket index 2 is out of range",
1634 ));
1635
1636 for (name, request, expected) in cases {
1637 assert_invalid(name, request, expected);
1638 }
1639 }
1640
1641 #[test]
1642 fn test_into_context_req_allows_nan_observations_outside_buckets() {
1643 decode_test_request(request_with_histogram(Histogram {
1644 count: Some(Count::CountInt(2)),
1645 sum: f64::NAN,
1646 positive_spans: vec![BucketSpan {
1647 offset: 0,
1648 length: 1,
1649 }],
1650 positive_deltas: vec![1],
1651 ..Default::default()
1652 }))
1653 .unwrap();
1654 }
1655
1656 #[test]
1657 fn test_into_context_req_allows_empty_label_values() {
1658 let ctx_req = decode_test_request(test_util::request_with_labels_and_samples(
1659 vec![(METRIC_NAME_LABEL, "metric"), ("job", "")],
1660 vec![Sample {
1661 value: 1.0,
1662 timestamp: 1000,
1663 start_timestamp: 0,
1664 }],
1665 ))
1666 .unwrap();
1667
1668 let rows = ctx_req.samples.all_req().next().unwrap().rows.unwrap();
1669 let job_idx = column_index(&rows.schema, "job");
1670 assert_eq!(
1671 rows.rows[0].values[job_idx].value_data,
1672 Some(ValueData::StringValue(String::new()))
1673 );
1674 }
1675
1676 #[test]
1677 fn test_into_context_req_rejects_same_metric_samples_and_histograms() {
1678 let mut request = test_util::request_with_labels_and_samples(
1679 vec![(METRIC_NAME_LABEL, "metric")],
1680 vec![Sample {
1681 value: 1.0,
1682 timestamp: 1000,
1683 start_timestamp: 0,
1684 }],
1685 );
1686 request.timeseries[0].histograms.push(Histogram::default());
1687
1688 assert_invalid(
1689 "same metric samples and histograms",
1690 request,
1691 "contains both samples and native histograms",
1692 );
1693
1694 let mut request = test_util::request_with_labels_and_samples(
1695 vec![(METRIC_NAME_LABEL, "metric")],
1696 vec![Sample {
1697 value: 1.0,
1698 timestamp: 1000,
1699 start_timestamp: 0,
1700 }],
1701 );
1702 request.timeseries.push(TimeSeries {
1703 labels_refs: request.timeseries[0].labels_refs.clone(),
1704 histograms: vec![Histogram::default()],
1705 ..Default::default()
1706 });
1707
1708 assert_invalid(
1709 "same metric samples and histograms across series",
1710 request,
1711 "contains both samples and native histograms",
1712 );
1713 }
1714
1715 #[test]
1716 fn test_into_context_req_rejects_metric_kind_conflict_across_label_sets() {
1717 let request = Request {
1718 symbols: vec![
1719 "".to_string(),
1720 METRIC_NAME_LABEL.to_string(),
1721 "metric".to_string(),
1722 "job".to_string(),
1723 "api".to_string(),
1724 "worker".to_string(),
1725 ],
1726 timeseries: vec![
1727 TimeSeries {
1728 labels_refs: vec![1, 2, 3, 4],
1729 samples: vec![Sample {
1730 value: 1.0,
1731 timestamp: 1000,
1732 start_timestamp: 0,
1733 }],
1734 ..Default::default()
1735 },
1736 TimeSeries {
1737 labels_refs: vec![1, 2, 3, 5],
1738 histograms: vec![Histogram::default()],
1739 ..Default::default()
1740 },
1741 ],
1742 };
1743
1744 assert_invalid(
1745 "same metric kind conflict across label sets",
1746 request,
1747 "contains both samples and native histograms",
1748 );
1749 }
1750
1751 #[test]
1752 fn test_into_context_req_validates_exponential_overflow_bucket_index() {
1753 for schema in [-4, 0, 8] {
1754 let max_index = exponential_overflow_bucket_index(schema).unwrap();
1755 for positive in [true, false] {
1756 let mut histogram = Histogram {
1757 schema,
1758 count: Some(Count::CountInt(1)),
1759 ..Default::default()
1760 };
1761 if positive {
1762 histogram.positive_spans = vec![BucketSpan {
1763 offset: max_index,
1764 length: 1,
1765 }];
1766 histogram.positive_deltas = vec![1];
1767 } else {
1768 histogram.negative_spans = vec![BucketSpan {
1769 offset: max_index,
1770 length: 1,
1771 }];
1772 histogram.negative_deltas = vec![1];
1773 }
1774 decode_test_request(request_with_histogram(histogram.clone())).unwrap();
1775
1776 let beyond = max_index + 1;
1777 if positive {
1778 histogram.positive_spans[0].offset = beyond;
1779 } else {
1780 histogram.negative_spans[0].offset = beyond;
1781 }
1782 assert_invalid(
1783 "exponential overflow bucket index",
1784 request_with_histogram(histogram),
1785 &format!("bucket index {beyond} is out of range"),
1786 );
1787 }
1788 }
1789 }
1790
1791 #[test]
1792 fn test_into_context_req_converts_histograms_and_ignores_exemplars() {
1793 let request = Request {
1794 symbols: vec![
1795 "".to_string(),
1796 METRIC_NAME_LABEL.to_string(),
1797 "sample_metric".to_string(),
1798 "histogram_metric".to_string(),
1799 ],
1800 timeseries: vec![
1801 TimeSeries {
1802 labels_refs: vec![1, 2],
1803 samples: vec![Sample {
1804 value: 1.0,
1805 timestamp: 1000,
1806 start_timestamp: 0,
1807 }],
1808 ..Default::default()
1809 },
1810 TimeSeries {
1811 labels_refs: vec![1, 3],
1812 histograms: vec![Histogram::default()],
1813 exemplars: vec![Exemplar::default()],
1814 ..Default::default()
1815 },
1816 ],
1817 };
1818
1819 let ctx_req = decode_test_request(request).unwrap();
1820
1821 assert_eq!(ctx_req.sample_count, 1);
1822 assert_eq!(ctx_req.histogram_count, 1);
1823 assert_eq!(ctx_req.samples.all_req().count(), 1);
1824 assert_eq!(ctx_req.histograms.all_req().count(), 1);
1825 }
1826
1827 #[test]
1828 fn test_into_context_req_converts_histogram_only_series() {
1829 let mut request =
1830 test_util::request_with_labels_and_samples(vec![(METRIC_NAME_LABEL, "metric")], vec![]);
1831 request.timeseries[0].histograms.push(Histogram::default());
1832
1833 let ctx_req = decode_test_request(request).unwrap();
1834
1835 assert_eq!(ctx_req.sample_count, 0);
1836 assert_eq!(ctx_req.histogram_count, 1);
1837 assert_eq!(ctx_req.samples.all_req().count(), 0);
1838 let mut inserts = ctx_req.histograms.all_req().collect::<Vec<_>>();
1839 assert_eq!(inserts.len(), 1);
1840
1841 let request = inserts.pop().unwrap();
1842 assert_eq!(request.table_name, "metric");
1843 let rows = request.rows.unwrap();
1844 assert_eq!(rows.rows.len(), 1);
1845 assert_eq!(
1846 rows.schema
1847 .iter()
1848 .map(|col| col.column_name.as_str())
1849 .collect::<Vec<_>>(),
1850 vec![greptime_timestamp(), greptime_native_histogram()]
1851 );
1852 assert_eq!(
1853 rows.rows[0].values[0].value_data,
1854 Some(ValueData::TimestampMillisecondValue(0))
1855 );
1856 assert_eq!(
1857 histogram_field_value(&rows, 0, SCHEMA_FIELD),
1858 Some(ValueData::I32Value(0))
1859 );
1860 assert_eq!(
1861 histogram_field_value(&rows, 0, COUNT_I64_FIELD),
1862 Some(ValueData::I64Value(0))
1863 );
1864 assert_eq!(histogram_field_value(&rows, 0, COUNT_F64_FIELD), None);
1865 }
1866
1867 #[test]
1868 fn test_into_context_req_preserves_histogram_start_timestamp() {
1869 let ctx_req = decode_test_request(test_util::request_with_labels_and_histograms(
1870 vec![(METRIC_NAME_LABEL, "metric")],
1871 vec![Histogram {
1872 timestamp: 2000,
1873 start_timestamp: 1000,
1874 ..Default::default()
1875 }],
1876 ))
1877 .unwrap();
1878
1879 let mut inserts = ctx_req.histograms.all_req().collect::<Vec<_>>();
1880 let rows = inserts.pop().unwrap().rows.unwrap();
1881
1882 assert_eq!(
1883 histogram_field_value(&rows, 0, START_TIMESTAMP_FIELD),
1884 Some(ValueData::TimestampMillisecondValue(1000))
1885 );
1886 }
1887
1888 #[test]
1889 fn test_into_context_req_preserves_exponential_zero_threshold() {
1890 for zero_threshold in [-1.0, f64::NAN] {
1891 let ctx_req = decode_test_request(request_with_histogram(Histogram {
1892 zero_threshold,
1893 ..Default::default()
1894 }))
1895 .unwrap();
1896 let rows = ctx_req.histograms.all_req().next().unwrap().rows.unwrap();
1897 let Some(ValueData::F64Value(actual)) =
1898 histogram_field_value(&rows, 0, ZERO_THRESHOLD_FIELD)
1899 else {
1900 panic!("expected zero threshold");
1901 };
1902
1903 assert_eq!(zero_threshold.to_bits(), actual.to_bits());
1904 }
1905 }
1906
1907 #[test]
1908 fn test_into_context_req_rejects_internal_histogram_labels() {
1909 let mut request = test_util::request_with_labels_and_samples(
1910 vec![
1911 (METRIC_NAME_LABEL, "metric"),
1912 (greptime_native_histogram(), "user_value"),
1913 ],
1914 vec![],
1915 );
1916 request.timeseries[0].histograms.push(Histogram::default());
1917
1918 let err = match decode_test_request(request) {
1919 Ok(_) => panic!("expected invalid request error"),
1920 Err(err) => err,
1921 };
1922 assert_eq!(
1923 err.to_string(),
1924 "Invalid prometheus remote request, msg: remote write v2 label `greptime_native_histogram` conflicts with an internal native histogram label"
1925 );
1926 }
1927
1928 #[test]
1929 fn test_rejects_legacy_histogram_label_after_prefix_change() {
1930 set_default_prefix(Some("custom")).unwrap();
1931 assert_eq!(greptime_native_histogram(), "custom_native_histogram");
1932
1933 let err = ensure_no_internal_histogram_labels(&vec![(
1934 NATIVE_HISTOGRAM_FIELD,
1935 "user_value".to_string(),
1936 )])
1937 .unwrap_err();
1938 assert!(
1939 err.to_string()
1940 .contains("conflicts with an internal native histogram label")
1941 );
1942 }
1943
1944 #[test]
1945 fn test_into_context_req_converts_int_and_float_histograms_to_one_schema() {
1946 let float_histogram = Histogram {
1947 count: Some(api::greptime_proto::io::prometheus::write::v2::histogram::Count::CountFloat(6.0)),
1948 zero_count: Some(
1949 api::greptime_proto::io::prometheus::write::v2::histogram::ZeroCount::ZeroCountFloat(
1950 0.5,
1951 ),
1952 ),
1953 positive_counts: vec![2.0, 3.5],
1954 positive_spans: vec![api::greptime_proto::io::prometheus::write::v2::BucketSpan {
1955 offset: 3,
1956 length: 2,
1957 }],
1958 timestamp: 2000,
1959 ..Default::default()
1960 };
1961 let request = Request {
1962 symbols: vec![
1963 "".to_string(),
1964 METRIC_NAME_LABEL.to_string(),
1965 "metric".to_string(),
1966 ],
1967 timeseries: vec![
1968 TimeSeries {
1969 labels_refs: vec![1, 2],
1970 histograms: vec![test_util::histogram(1000)],
1971 ..Default::default()
1972 },
1973 TimeSeries {
1974 labels_refs: vec![1, 2],
1975 histograms: vec![float_histogram],
1976 ..Default::default()
1977 },
1978 ],
1979 };
1980
1981 let ctx_req = decode_test_request(request).unwrap();
1982
1983 assert_eq!(ctx_req.histogram_count, 2);
1984 let mut inserts = ctx_req.histograms.all_req().collect::<Vec<_>>();
1985 assert_eq!(inserts.len(), 1);
1986 let rows = inserts.pop().unwrap().rows.unwrap();
1987 assert_eq!(rows.rows.len(), 2);
1988 assert_eq!(
1989 rows.schema
1990 .iter()
1991 .map(|col| col.column_name.as_str())
1992 .collect::<Vec<_>>(),
1993 vec![greptime_timestamp(), greptime_native_histogram()]
1994 );
1995
1996 assert_eq!(
1997 histogram_field_value(&rows, 0, COUNT_I64_FIELD),
1998 Some(ValueData::I64Value(0))
1999 );
2000 assert_eq!(histogram_field_value(&rows, 0, COUNT_F64_FIELD), None);
2001 assert!(matches!(
2002 histogram_field_value(&rows, 0, POSITIVE_BUCKETS_I64_FIELD),
2003 Some(ValueData::ListValue(_))
2004 ));
2005 assert!(is_empty_list(histogram_field_value(
2006 &rows,
2007 0,
2008 POSITIVE_BUCKETS_F64_FIELD
2009 )));
2010
2011 assert_eq!(histogram_field_value(&rows, 1, COUNT_I64_FIELD), None);
2012 assert_eq!(
2013 histogram_field_value(&rows, 1, COUNT_F64_FIELD),
2014 Some(ValueData::F64Value(6.0))
2015 );
2016 assert!(is_empty_list(histogram_field_value(
2017 &rows,
2018 1,
2019 POSITIVE_BUCKETS_I64_FIELD
2020 )));
2021 assert!(matches!(
2022 histogram_field_value(&rows, 1, POSITIVE_BUCKETS_F64_FIELD),
2023 Some(ValueData::ListValue(_))
2024 ));
2025 }
2026
2027 fn decode_wire(
2028 wire: &[u8],
2029 native_histograms_enabled: bool,
2030 ) -> Result<RemoteWriteV2WriteRequests> {
2031 let body = Bytes::from(crate::prom_store::snappy_compress(wire).unwrap());
2032 decode_remote_write_v2(false, body, native_histograms_enabled)
2033 }
2034
2035 fn decode_wire_error(wire: &[u8], native_histograms_enabled: bool, name: &str) -> error::Error {
2036 match decode_wire(wire, native_histograms_enabled) {
2037 Ok(_) => panic!("{name}: expected decoder error"),
2038 Err(error) => error,
2039 }
2040 }
2041
2042 fn request_wire(symbols: &[&str], series: &[Vec<u8>]) -> Vec<u8> {
2043 let mut wire = Vec::new();
2044 for symbol in symbols {
2045 wire.extend(string_field(4, symbol.as_bytes()));
2046 }
2047 for series in series {
2048 wire.extend(encoded_message_field(5, series));
2049 }
2050 wire
2051 }
2052
2053 fn series_wire(labels_refs: &[u32], leaf_tag: u32, leaf: &[u8]) -> Vec<u8> {
2054 let mut series = packed_u32_field(1, labels_refs);
2055 series.extend(encoded_message_field(leaf_tag, leaf));
2056 series
2057 }
2058
2059 fn string_field(tag: u32, value: &[u8]) -> Vec<u8> {
2060 encoded_message_field(tag, value)
2061 }
2062
2063 fn encoded_message_field(tag: u32, value: &[u8]) -> Vec<u8> {
2064 let mut field = Vec::new();
2065 prost::encoding::encode_key(tag, WireType::LengthDelimited, &mut field);
2066 prost::encoding::encode_varint(u64::try_from(value.len()).unwrap(), &mut field);
2067 field.extend_from_slice(value);
2068 field
2069 }
2070
2071 fn varint_field(tag: u32, value: u64) -> Vec<u8> {
2072 let mut field = Vec::new();
2073 prost::encoding::encode_key(tag, WireType::Varint, &mut field);
2074 prost::encoding::encode_varint(value, &mut field);
2075 field
2076 }
2077
2078 fn packed_u32_field(tag: u32, values: &[u32]) -> Vec<u8> {
2079 let mut packed = Vec::new();
2080 for value in values {
2081 prost::encoding::encode_varint(u64::from(*value), &mut packed);
2082 }
2083 encoded_message_field(tag, &packed)
2084 }
2085
2086 fn request_with_sample(labels: Vec<(&str, &str)>) -> Request {
2087 test_util::request_with_labels_and_samples(
2088 labels,
2089 vec![Sample {
2090 value: 1.0,
2091 timestamp: 1000,
2092 start_timestamp: 0,
2093 }],
2094 )
2095 }
2096
2097 fn request_with_histogram(histogram: Histogram) -> Request {
2098 test_util::request_with_labels_and_histograms(
2099 vec![(METRIC_NAME_LABEL, "metric")],
2100 vec![histogram],
2101 )
2102 }
2103
2104 fn decode_test_request(request: Request) -> Result<RemoteWriteV2WriteRequests> {
2105 decode_test_request_with_histograms(request, true)
2106 }
2107
2108 fn decode_test_request_with_histograms(
2109 request: Request,
2110 native_histograms_enabled: bool,
2111 ) -> Result<RemoteWriteV2WriteRequests> {
2112 let body =
2113 Bytes::from(crate::prom_store::snappy_compress(&request.encode_to_vec()).unwrap());
2114 decode_remote_write_v2(false, body, native_histograms_enabled)
2115 }
2116
2117 fn assert_invalid(name: &str, request: Request, expected: &str) {
2118 let err = match decode_test_request(request) {
2119 Ok(_) => panic!("{name}: expected invalid request error"),
2120 Err(err) => err,
2121 };
2122 assert!(
2123 matches!(err, error::Error::InvalidPromRemoteRequest { .. }),
2124 "{name}: expected invalid request error, got {err}"
2125 );
2126 assert!(
2127 err.to_string().contains(expected),
2128 "{name}: expected error containing {expected:?}, got {err}"
2129 );
2130 }
2131
2132 fn column_index(schema: &[ColumnSchema], column_name: &str) -> usize {
2133 schema
2134 .iter()
2135 .position(|column| column.column_name == column_name)
2136 .unwrap()
2137 }
2138
2139 fn histogram_field_value(rows: &Rows, row_idx: usize, field_name: &str) -> Option<ValueData> {
2140 let histogram_idx = column_index(&rows.schema, greptime_native_histogram());
2141 let Some(ValueData::StructValue(histogram)) =
2142 &rows.rows[row_idx].values[histogram_idx].value_data
2143 else {
2144 panic!("expected native histogram struct value");
2145 };
2146 let field_idx = NATIVE_HISTOGRAM_FIELD_NAMES
2147 .iter()
2148 .position(|name| *name == field_name)
2149 .unwrap();
2150 histogram.items[field_idx].value_data.clone()
2151 }
2152
2153 fn is_empty_list(value: Option<ValueData>) -> bool {
2154 matches!(value, Some(ValueData::ListValue(list)) if list.items.is_empty())
2155 }
2156
2157 fn push_test_symbol(symbols: &mut Vec<String>, symbol: &str) -> u32 {
2158 if let Some(idx) = symbols.iter().position(|s| s == symbol) {
2159 return idx as u32;
2160 }
2161 let idx = symbols.len();
2162 symbols.push(symbol.to_string());
2163 idx as u32
2164 }
2165
2166 type MetadataSeries<'a> = (Vec<(&'a str, &'a str)>, i32, Option<&'a str>);
2168
2169 fn metadata_request(series: Vec<MetadataSeries<'_>>) -> Request {
2170 let mut symbols = vec!["".to_string()];
2171 let timeseries = series
2172 .into_iter()
2173 .map(|(labels, metric_type, unit)| {
2174 let mut labels_refs = Vec::with_capacity(labels.len() * 2);
2175 for (name, value) in labels {
2176 labels_refs.push(push_test_symbol(&mut symbols, name));
2177 labels_refs.push(push_test_symbol(&mut symbols, value));
2178 }
2179 let unit_ref = unit.map_or(0, |unit| push_test_symbol(&mut symbols, unit));
2180 TimeSeries {
2181 labels_refs,
2182 samples: vec![Sample {
2183 value: 1.0,
2184 timestamp: 1000,
2185 start_timestamp: 0,
2186 }],
2187 histograms: Vec::new(),
2188 exemplars: Vec::new(),
2189 metadata: Some(Metadata {
2190 r#type: metric_type,
2191 help_ref: 0,
2192 unit_ref,
2193 }),
2194 }
2195 })
2196 .collect();
2197 Request {
2198 symbols,
2199 timeseries,
2200 }
2201 }
2202
2203 type DecodedIndex = std::collections::BTreeMap<
2204 String,
2205 std::collections::BTreeMap<String, std::collections::BTreeMap<String, String>>,
2206 >;
2207
2208 fn decoded_index(request: Request) -> DecodedIndex {
2209 let encoded = decode_test_request(request)
2210 .unwrap()
2211 .semantic_index
2212 .encode("public")
2213 .expect("non-empty semantic index");
2214 serde_json::from_str(&encoded).unwrap()
2215 }
2216
2217 #[test]
2218 fn test_metadata_stamps_semantic_index() {
2219 use table::requests::METADATA_QUALITY_DECLARED;
2220
2221 let index = decoded_index(metadata_request(vec![
2222 (
2223 vec![(METRIC_NAME_LABEL, "http_requests_total")],
2224 metadata::MetricType::Counter as i32,
2225 Some("seconds"),
2226 ),
2227 (
2228 vec![(METRIC_NAME_LABEL, "queue_depth")],
2229 metadata::MetricType::Gauge as i32,
2230 Some("requests"),
2232 ),
2233 ]));
2234
2235 let tables = &index["public"];
2236 let typed = &tables["http_requests_total"];
2237 assert_eq!(typed[SEMANTIC_METRIC_TYPE], "counter");
2238 assert_eq!(
2239 typed[SEMANTIC_METRIC_METADATA_QUALITY],
2240 METADATA_QUALITY_DECLARED
2241 );
2242 assert_eq!(typed[SEMANTIC_METRIC_UNIT], "s");
2243
2244 let unitless = &tables["queue_depth"];
2245 assert_eq!(unitless[SEMANTIC_METRIC_TYPE], "gauge");
2246 assert!(!unitless.contains_key(SEMANTIC_METRIC_UNIT));
2247 }
2248
2249 #[test]
2250 fn test_metadata_unspecified_stamps_unit_but_not_type() {
2251 let index = decoded_index(metadata_request(vec![(
2252 vec![(METRIC_NAME_LABEL, "untyped_total")],
2253 metadata::MetricType::Unspecified as i32,
2254 Some("seconds"),
2255 )]));
2256 let untyped = &index["public"]["untyped_total"];
2257 assert_eq!(untyped[SEMANTIC_METRIC_UNIT], "s");
2258 assert!(!untyped.contains_key(SEMANTIC_METRIC_TYPE));
2259 assert!(!untyped.contains_key(SEMANTIC_METRIC_METADATA_QUALITY));
2260
2261 let requests = decode_test_request(metadata_request(vec![(
2262 vec![(METRIC_NAME_LABEL, "untyped_unitless_total")],
2263 metadata::MetricType::Unspecified as i32,
2264 None,
2265 )]))
2266 .unwrap();
2267 assert!(requests.semantic_index.is_empty());
2268
2269 let requests = decode_test_request(test_util::request_with_labels_and_samples(
2271 vec![(METRIC_NAME_LABEL, "bare_total")],
2272 vec![Sample {
2273 value: 1.0,
2274 timestamp: 1000,
2275 start_timestamp: 0,
2276 }],
2277 ))
2278 .unwrap();
2279 assert!(requests.semantic_index.is_empty());
2280 }
2281
2282 #[test]
2283 fn test_metadata_type_conflict_collapses_to_mixed() {
2284 let index = decoded_index(metadata_request(vec![
2285 (
2286 vec![(METRIC_NAME_LABEL, "flappy_metric"), ("job", "a")],
2287 metadata::MetricType::Counter as i32,
2288 None,
2289 ),
2290 (
2291 vec![(METRIC_NAME_LABEL, "flappy_metric"), ("job", "b")],
2292 metadata::MetricType::Gauge as i32,
2293 None,
2294 ),
2295 ]));
2296 assert_eq!(
2297 index["public"]["flappy_metric"][SEMANTIC_METRIC_TYPE],
2298 "mixed"
2299 );
2300 }
2301
2302 #[test]
2303 fn test_metadata_schema_overrides_stay_apart() {
2304 let index = decoded_index(metadata_request(vec![
2307 (
2308 vec![(METRIC_NAME_LABEL, "cpu_usage")],
2309 metadata::MetricType::Counter as i32,
2310 None,
2311 ),
2312 (
2313 vec![
2314 (METRIC_NAME_LABEL, "cpu_usage"),
2315 (DATABASE_LABEL, "tenant_b"),
2316 ],
2317 metadata::MetricType::Gauge as i32,
2318 None,
2319 ),
2320 ]));
2321 assert_eq!(
2322 index["public"]["cpu_usage"][SEMANTIC_METRIC_TYPE],
2323 "counter"
2324 );
2325 assert_eq!(
2326 index["tenant_b"]["cpu_usage"][SEMANTIC_METRIC_TYPE],
2327 "gauge"
2328 );
2329 }
2330
2331 #[test]
2332 fn test_metadata_out_of_range_refs_are_rejected() {
2333 let mut request = metadata_request(vec![(
2334 vec![(METRIC_NAME_LABEL, "broken_total")],
2335 metadata::MetricType::Counter as i32,
2336 None,
2337 )]);
2338 request.timeseries[0].metadata.as_mut().unwrap().unit_ref = 999;
2339 let err = decode_test_request(request).err().unwrap();
2340 assert!(err.to_string().contains("out of range"), "{err}");
2341
2342 let mut request = metadata_request(vec![(
2345 vec![(METRIC_NAME_LABEL, "broken_total")],
2346 metadata::MetricType::Unspecified as i32,
2347 None,
2348 )]);
2349 request.timeseries[0].metadata.as_mut().unwrap().unit_ref = 999;
2350 let err = decode_test_request(request).err().unwrap();
2351 assert!(err.to_string().contains("out of range"), "{err}");
2352
2353 let mut request = metadata_request(vec![(
2355 vec![(METRIC_NAME_LABEL, "broken_total")],
2356 metadata::MetricType::Counter as i32,
2357 None,
2358 )]);
2359 request.timeseries[0].metadata.as_mut().unwrap().help_ref = 999;
2360 let err = decode_test_request(request).err().unwrap();
2361 assert!(err.to_string().contains("out of range"), "{err}");
2362 }
2363}