1use std::collections::hash_map::Entry;
16use std::sync::Arc;
17
18use ahash::{HashMap, HashMapExt, HashSet, HashSetExt};
19#[cfg(test)]
20use api::greptime_proto::io::prometheus::write::v2::BucketSpan;
21#[cfg(test)]
22use api::greptime_proto::io::prometheus::write::v2::histogram::{Count, ZeroCount};
23use api::greptime_proto::io::prometheus::write::v2::{
24 Exemplar, Histogram, Metadata, Sample, metadata,
25};
26#[cfg(test)]
27use api::greptime_proto::io::prometheus::write::v2::{Request, TimeSeries};
28#[cfg(test)]
29use api::v1::ColumnSchema;
30use api::v1::value::ValueData;
31use api::v1::{ColumnDataType, RowInsertRequest, Rows, SemanticType, Value};
32use bytes::{Buf, Bytes};
33use common_memory_manager::{MemoryGuard, OnExhaustedPolicy};
34#[cfg(test)]
35use common_query::native_histogram::*;
36use common_query::native_histogram::{
37 NATIVE_HISTOGRAM_FIELD, encode_native_histogram, native_histogram_column_schema,
38};
39use common_query::prelude::{greptime_native_histogram, greptime_timestamp, greptime_value};
40use pipeline::{ContextOpt, ContextReq};
41use prost::encoding::{
42 DecodeContext, WireType, decode_key, decode_varint, message, skip_field, uint32,
43};
44use prost::{DecodeError, Message};
45use snafu::{IntoError, OptionExt, ResultExt, ensure};
46use table::requests::{
47 METADATA_QUALITY_DECLARED, SEMANTIC_METRIC_METADATA_QUALITY, SEMANTIC_METRIC_TYPE,
48 SEMANTIC_METRIC_UNIT,
49};
50
51use crate::error::{self, Result};
52use crate::prom_remote_write::row_builder::PromCtx;
53use crate::prom_remote_write::{self, REMOTE_WRITE_V2_VERSION, decompress_remote_write_body};
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::request_memory_limiter::ServerMemoryLimiter;
60use crate::request_memory_metrics::RequestMemoryMetrics;
61use crate::row_writer::{self, TableData};
62use crate::semantic::{
63 METRIC_TYPE_COUNTER, METRIC_TYPE_GAUGE, METRIC_TYPE_GAUGE_HISTOGRAM, METRIC_TYPE_HISTOGRAM,
64 METRIC_TYPE_INFO, METRIC_TYPE_STATESET, METRIC_TYPE_SUMMARY, SemanticIndexes,
65 openmetrics_unit_to_ucum,
66};
67
68type PromTags<'a> = Vec<(&'a str, String)>;
69type ResolvedSeriesLabels<'a> = (PromCtx, String, PromTags<'a>);
70const TIME_SERIES_LABELS_REFS_TAG: u32 = 1;
71const TIME_SERIES_SAMPLES_TAG: u32 = 2;
72const TIME_SERIES_HISTOGRAMS_TAG: u32 = 3;
73const TIME_SERIES_EXEMPLARS_TAG: u32 = 4;
74const TIME_SERIES_METADATA_TAG: u32 = 5;
75
76struct BorrowedRequest<'a> {
77 symbols: Vec<&'a str>,
78 timeseries: Vec<&'a [u8]>,
79}
80
81impl<'a> BorrowedRequest<'a> {
82 fn decode(mut buf: &'a [u8]) -> std::result::Result<Self, DecodeError> {
83 let mut symbols = Vec::new();
84 let mut timeseries = Vec::new();
85
86 while buf.has_remaining() {
87 let (tag, wire_type) = decode_key(&mut buf)?;
88 match tag {
89 4 => {
90 let value =
91 take_length_delimited(wire_type, &mut buf).map_err(|mut error| {
92 error.push("Request", "symbols");
93 error
94 })?;
95 let symbol = std::str::from_utf8(value).map_err(|_| {
96 let mut error =
97 DecodeError::new("invalid string value: data is not UTF-8 encoded");
98 error.push("Request", "symbols");
99 error
100 })?;
101 symbols.push(symbol);
102 }
103 5 => {
104 let series =
105 take_length_delimited(wire_type, &mut buf).map_err(|mut error| {
106 error.push("Request", "timeseries");
107 error
108 })?;
109 timeseries.push(series);
110 }
111 _ => skip_field(wire_type, tag, &mut buf, DecodeContext::default())?,
112 }
113 }
114
115 Ok(Self {
116 symbols,
117 timeseries,
118 })
119 }
120}
121
122pub(crate) struct RemoteWriteV2WriteRequests {
123 pub samples: ContextReq,
124 pub histograms: ContextReq,
125 pub sample_count: u64,
126 pub histogram_count: u64,
127 pub exemplars: crate::exemplar::ExemplarRequests,
128 pub exemplar_count: u64,
129 pub semantic_index: SemanticIndexes,
132 pub memory_guard: Arc<MemoryGuard<RequestMemoryMetrics>>,
133}
134
135struct ConversionBudget<'a> {
136 limiter: &'a ServerMemoryLimiter,
137 guard: MemoryGuard<RequestMemoryMetrics>,
138 used_bytes: u64,
139 held_bytes: u64,
140}
141
142impl<'a> ConversionBudget<'a> {
143 fn new(limiter: &'a ServerMemoryLimiter, held_bytes: u64) -> Result<Self> {
144 Ok(Self {
145 limiter,
146 used_bytes: 0,
147 held_bytes,
148 guard: limiter.try_acquire(0).context(error::InternalSnafu {
149 err_msg: "cannot create an empty memory reservation",
150 })?,
151 })
152 }
153
154 fn reserve(&mut self, bytes: Option<usize>) -> Result<()> {
155 let required = bytes
156 .and_then(|bytes| u64::try_from(bytes).ok())
157 .and_then(|bytes| self.used_bytes.checked_add(bytes));
158 if let Some(required) = required {
159 let granted = self.guard.granted_bytes();
160 let additional = required.saturating_sub(granted);
161 if additional == 0 {
162 self.used_bytes = required;
163 return Ok(());
164 }
165 let limit = self.limiter.limit_bytes();
167 let (chunk, available) = if limit == 0 {
168 (64 * 1024, u64::MAX - granted)
169 } else {
170 (
171 (64 * 1024).min(limit / 64),
172 limit.saturating_sub(self.limiter.used_bytes()),
173 )
174 };
175 let reservation = additional.max(chunk).min(available).max(additional);
176 if self.guard.try_acquire_additional(reservation)
177 || (reservation > additional && self.guard.try_acquire_additional(additional))
178 {
179 self.used_bytes = required;
180 return Ok(());
181 }
182 }
183 Err(error::MemoryLimitExceededSnafu.into_error(
185 common_memory_manager::Error::MemoryLimitExceeded {
186 requested_bytes: required
187 .and_then(|bytes| self.held_bytes.checked_add(bytes))
188 .unwrap_or(u64::MAX),
189 limit_bytes: self.limiter.limit_bytes(),
190 },
191 ))
192 }
193}
194
195pub(crate) async fn decode_remote_write_v2(
196 is_zstd: bool,
197 body: Bytes,
198 limiter: &ServerMemoryLimiter,
199 body_reserved_bytes: u64,
200) -> Result<RemoteWriteV2WriteRequests> {
201 let mut requested_bytes = 0;
202 let decode = async {
203 loop {
204 match decode_remote_write_v2_once(is_zstd, &body, limiter, body_reserved_bytes).await {
205 Err(error::Error::MemoryLimitExceeded {
206 source:
207 common_memory_manager::Error::MemoryLimitExceeded {
208 requested_bytes: required,
209 limit_bytes,
210 },
211 ..
212 }) if matches!(limiter.policy(), OnExhaustedPolicy::Wait { .. })
213 && required <= limit_bytes =>
214 {
215 limiter.check_request_fits(required, body_reserved_bytes)?;
216 requested_bytes = required;
217 tokio::task::yield_now().await;
219 drop(limiter.acquire(required).await?);
220 }
221 result => return result,
222 }
223 }
224 };
225 let result = match limiter.policy() {
226 OnExhaustedPolicy::Wait { timeout } => {
227 tokio::time::timeout(timeout, decode)
229 .await
230 .unwrap_or_else(|_| {
231 Err(error::MemoryLimitExceededSnafu.into_error(
232 common_memory_manager::Error::MemoryAcquireTimeout {
233 requested_bytes,
234 waited: timeout,
235 },
236 ))
237 })
238 }
239 OnExhaustedPolicy::Fail => decode.await,
240 };
241 result.map_err(prom_remote_write::map_memory_acquire_timeout)
242}
243
244async fn decode_remote_write_v2_once(
245 is_zstd: bool,
246 body: &[u8],
247 limiter: &ServerMemoryLimiter,
248 body_reserved_bytes: u64,
249) -> Result<RemoteWriteV2WriteRequests> {
250 let decode_timer = crate::metrics::METRIC_HTTP_PROM_STORE_CODEC_ELAPSED
251 .with_label_values(&["decode", REMOTE_WRITE_V2_VERSION])
252 .start_timer();
253
254 let buf = decompress_remote_write_body(is_zstd, body, limiter, body_reserved_bytes).await?;
257 let mut scratch = ConversionBudget::new(limiter, buf.reserved_bytes())?;
258 scratch.reserve(Some(
259 conversion_scratch_bytes(&buf).context(error::DecodePromRemoteRequestSnafu)?,
260 ))?;
261 let budget = ConversionBudget::new(
262 limiter,
263 buf.reserved_bytes() + scratch.guard.granted_bytes(),
264 )?;
265 let request = BorrowedRequest::decode(&buf).context(error::DecodePromRemoteRequestSnafu)?;
266 drop(decode_timer);
267
268 let _convert_timer = crate::metrics::METRIC_HTTP_PROM_STORE_CODEC_ELAPSED
269 .with_label_values(&["convert", REMOTE_WRITE_V2_VERSION])
270 .start_timer();
271 convert_remote_write_v2(request, budget)
272}
273
274fn conversion_scratch_bytes(mut buf: &[u8]) -> std::result::Result<usize, DecodeError> {
276 const PROTOBUF_SCRATCH_EXPANSION: usize = 2 * std::mem::size_of::<i64>();
278 let mut bytes = Some(8 * std::mem::size_of::<&[u8]>());
280 while buf.has_remaining() {
281 let (tag, wire_type) = decode_key(&mut buf)?;
282 match tag {
283 4 | 5 => {
284 let value = take_length_delimited(wire_type, &mut buf)?;
285 bytes = bytes.and_then(|n| n.checked_add(2 * std::mem::size_of::<&[u8]>()));
286 if tag == 5 {
287 let mut series = value;
288 while series.has_remaining() {
289 let before = series.len();
290 let (tag, wire_type) = decode_key(&mut series)?;
291 skip_field(wire_type, tag, &mut series, DecodeContext::default())?;
292 if matches!(
293 tag,
294 TIME_SERIES_LABELS_REFS_TAG
295 | TIME_SERIES_HISTOGRAMS_TAG
296 | TIME_SERIES_EXEMPLARS_TAG
297 ) {
298 bytes = bytes.and_then(|n| {
299 n.checked_add(
300 (before - series.len())
301 .checked_mul(PROTOBUF_SCRATCH_EXPANSION)?,
302 )
303 });
304 }
305 }
306 }
307 }
308 _ => skip_field(wire_type, tag, &mut buf, DecodeContext::default())?,
309 }
310 }
311 bytes.ok_or_else(|| DecodeError::new("remote write v2 scratch size overflows usize"))
312}
313
314fn convert_remote_write_v2(
315 request: BorrowedRequest<'_>,
316 mut budget: ConversionBudget<'_>,
317) -> Result<RemoteWriteV2WriteRequests> {
318 ensure!(
319 request.symbols.first().copied() == Some(""),
320 error::InvalidPromRemoteRequestSnafu {
321 msg: "remote write v2 symbols must start with an empty string".to_string(),
322 }
323 );
324
325 let mut sample_tables = HashMap::<PromCtx, HashMap<String, TableData>>::new();
326 let mut histogram_tables = HashMap::<PromCtx, HashMap<String, TableData>>::new();
327 let mut label_names = HashSet::new();
328 let mut sample_count_total = 0;
329 let mut histogram_count_total = 0;
330 let mut exemplar_count_total = 0;
331 let mut exemplars = crate::exemplar::ExemplarRequests::new();
332 let mut labels_refs = Vec::new();
333 let mut metadata = Metadata::default();
334 let mut scratch = LeafScratch::default();
335 let mut semantic_index = SemanticIndexes::default();
336
337 for series in request.timeseries {
338 let counts = scan_series(series, &mut labels_refs, &mut metadata)
339 .context(error::DecodePromRemoteRequestSnafu)?;
340 budget.reserve(
341 counts
342 .exemplars
343 .checked_mul(2 * std::mem::size_of::<Exemplar>()),
344 )?;
345
346 if counts.samples == 0 && counts.histograms == 0 && counts.exemplars == 0 {
347 decode_series_leaves(series, None, Vec::new(), 0, &mut scratch)?;
348 continue;
349 }
350
351 let label_bytes = labels_refs.iter().try_fold(0usize, |bytes, index| {
352 bytes.checked_add(request.symbols.get(*index as usize).map_or(0, |s| s.len()))
353 });
354 budget.reserve(label_bytes.and_then(|bytes| {
355 bytes
356 .checked_mul(4)?
357 .checked_add(labels_refs.len().checked_mul(128)?)
358 }))?;
359 let (prom_ctx, table_name, tags) =
360 resolve_series_labels(&request.symbols, &labels_refs, &mut label_names)?;
361 let inherited = if counts.exemplars > 0 {
362 crate::exemplar::labels(
363 tags.iter()
364 .map(|(key, value)| ((*key).to_string(), value.clone())),
365 false,
366 |name| !name.is_empty(),
367 )?
368 } else {
369 Default::default()
370 };
371 let exemplar_metric = table_name.clone();
372 let exemplar_schema = prom_ctx.schema.clone();
373 if counts.samples > 0 || counts.histograms > 0 {
374 ensure_no_internal_histogram_labels(&tags)?;
375 budget.reserve(
376 request
377 .symbols
378 .get(metadata.unit_ref as usize)
379 .map_or(Some(0), |unit| unit.len().checked_mul(4)),
380 )?;
381 record_series_metadata(
382 &mut semantic_index,
383 &request.symbols,
384 &metadata,
385 &prom_ctx,
386 &table_name,
387 )?;
388 let has_other_value_type = if counts.samples > 0 {
389 counts.histograms > 0
390 || histogram_tables
391 .get(&prom_ctx)
392 .is_some_and(|tables| tables.contains_key(&table_name))
393 } else {
394 sample_tables
395 .get(&prom_ctx)
396 .is_some_and(|tables| tables.contains_key(&table_name))
397 };
398 ensure!(
399 !has_other_value_type,
400 error::InvalidPromRemoteRequestSnafu {
401 msg: format!(
402 "remote write v2 metric `{table_name}` contains both samples and native histograms"
403 ),
404 }
405 );
406 }
407 let column_count =
408 tags.len()
409 .checked_add(2)
410 .with_context(|| error::InvalidPromRemoteRequestSnafu {
411 msg: "remote write v2 series has too many labels".to_string(),
412 })?;
413 let existing = if counts.samples > 0 {
414 &sample_tables
415 } else {
416 &histogram_tables
417 }
418 .get(&prom_ctx)
419 .and_then(|tables| tables.get(&table_name));
420 let old_columns = existing.map_or(2, TableData::num_columns);
421 let old_rows = existing.map_or(0, TableData::num_rows);
422 let new_columns = tags
423 .iter()
424 .filter(|(name, _)| existing.is_none_or(|table| table.column_index(name).is_none()))
425 .count();
426 let row_count = counts.samples.checked_add(counts.histograms);
427 let tag_bytes = tags
428 .iter()
429 .try_fold(0usize, |bytes, (_, value)| bytes.checked_add(value.len()));
430 budget.reserve((|| {
431 let rows = row_count?;
432 let width = old_columns.checked_add(new_columns)?;
433 let cells = rows
435 .checked_add(1)?
436 .checked_mul(width)?
437 .checked_add(old_rows.checked_mul(new_columns)?)?;
438 let values = cells.checked_mul(2 * std::mem::size_of::<Value>())?;
439 let strings = tag_bytes?.checked_mul(rows.checked_add(1)?)?;
440 let row_slots = rows.checked_mul(2 * std::mem::size_of::<api::v1::Row>())?;
441 let schema = new_columns
442 .checked_mul(512)?
443 .checked_add(if existing.is_none() { 8192 } else { 0 })?;
444 let histograms = if counts.histograms > 0 {
445 series
447 .len()
448 .checked_mul(2 * std::mem::size_of::<Value>() + 32)?
449 .checked_add(
450 counts
451 .histograms
452 .checked_mul(18 * std::mem::size_of::<Value>())?,
453 )?
454 } else {
455 0
456 };
457 values
458 .checked_add(strings)?
459 .checked_add(row_slots)?
460 .checked_add(schema)?
461 .checked_add(histograms)
462 })())?;
463 let (writer, row_count) = if counts.samples > 0 {
464 (
465 Some(SeriesWriter::Samples(get_or_create_table_data(
466 &mut sample_tables,
467 prom_ctx,
468 table_name,
469 column_count,
470 counts.samples,
471 ))),
472 counts.samples,
473 )
474 } else if counts.histograms > 0 {
475 (
476 Some(SeriesWriter::Histograms(get_or_create_table_data(
477 &mut histogram_tables,
478 prom_ctx,
479 table_name,
480 column_count,
481 counts.histograms,
482 ))),
483 counts.histograms,
484 )
485 } else {
486 (None, 0)
487 };
488
489 decode_series_leaves(series, writer, tags, row_count, &mut scratch)?;
490 if !scratch.exemplars.is_empty() {
491 let inherited = Arc::new(inherited);
492 let exemplar_metric: Arc<str> = exemplar_metric.into();
493 for exemplar in scratch.exemplars.drain(..) {
494 ensure!(
495 exemplar.labels_refs.len() % 2 == 0,
496 error::InvalidPromRemoteRequestSnafu {
497 msg: "exemplar label references must contain name/value pairs",
498 }
499 );
500 let exemplar_label_bytes =
501 exemplar
502 .labels_refs
503 .iter()
504 .try_fold(0usize, |bytes, index| {
505 bytes.checked_add(
506 request.symbols.get(*index as usize).map_or(0, |s| s.len()),
507 )
508 });
509 budget.reserve((|| {
511 label_bytes?
512 .checked_add(exemplar_label_bytes?)?
513 .checked_mul(4)?
514 .checked_add(
515 labels_refs
516 .len()
517 .checked_add(exemplar.labels_refs.len())?
518 .checked_mul(128)?,
519 )?
520 .checked_add(2 * std::mem::size_of::<crate::exemplar::Exemplar>())
521 })())?;
522 let labels = exemplar
523 .labels_refs
524 .as_chunks::<2>()
525 .0
526 .iter()
527 .map(|pair| {
528 Ok((
529 symbol_ref(&request.symbols, pair[0], "exemplar label")?.to_string(),
530 symbol_ref(&request.symbols, pair[1], "exemplar value")?.to_string(),
531 ))
532 })
533 .collect::<Result<Vec<_>>>()?;
534 let observation: Result<_> = (|| {
535 Ok(crate::exemplar::Exemplar {
536 metric: exemplar_metric.clone(),
537 series_labels: inherited.clone(),
538 timestamp: crate::exemplar::timestamp_from_millis(exemplar.timestamp)?,
539 value: exemplar.value,
540 labels: crate::exemplar::labels(labels, true, |name| !name.is_empty())?,
541 })
542 })();
543 let observation = match observation {
544 Ok(observation) => observation,
545 Err(_) => {
546 crate::metrics::EXEMPLARS_DROPPED_TOTAL
547 .with_label_values(&["prometheus_v2"])
548 .inc();
549 continue;
550 }
551 };
552 exemplars
553 .entry(exemplar_schema.clone())
554 .or_default()
555 .push(observation);
556 exemplar_count_total = checked_total(exemplar_count_total, 1, "exemplar")?;
557 }
558 }
559 sample_count_total = checked_total(sample_count_total, counts.samples, "sample")?;
560 histogram_count_total =
561 checked_total(histogram_count_total, counts.histograms, "histogram")?;
562 }
563
564 budget.reserve(crate::exemplar::estimated_row_bytes(
565 exemplars.values().map(Vec::as_slice),
566 ))?;
567
568 Ok(RemoteWriteV2WriteRequests {
569 samples: into_context_req(sample_tables),
570 histograms: into_context_req(histogram_tables),
571 sample_count: sample_count_total,
572 histogram_count: histogram_count_total,
573 exemplar_count: exemplar_count_total,
574 exemplars,
575 semantic_index,
576 memory_guard: Arc::new(budget.guard),
577 })
578}
579
580fn record_series_metadata(
590 index: &mut SemanticIndexes,
591 symbols: &[&str],
592 series_metadata: &Metadata,
593 prom_ctx: &PromCtx,
594 table_name: &str,
595) -> Result<()> {
596 if series_metadata.help_ref != 0 {
598 symbol_ref(symbols, series_metadata.help_ref, "metadata help")?;
599 }
600 let unit = if series_metadata.unit_ref != 0 {
601 Some(symbol_ref(
602 symbols,
603 series_metadata.unit_ref,
604 "metadata unit",
605 )?)
606 } else {
607 None
608 };
609 let metric_type = metric_type_value(series_metadata.r#type);
610 let ucum = unit.and_then(|unit| openmetrics_unit_to_ucum(unit.trim()));
611 if metric_type.is_none() && ucum.is_none() {
612 return Ok(());
613 }
614
615 let index = index.index_for(prom_ctx.schema.as_deref());
616 if let Some(metric_type) = metric_type {
617 index.record_scalar(table_name, SEMANTIC_METRIC_TYPE, metric_type);
618 index.record_scalar(
619 table_name,
620 SEMANTIC_METRIC_METADATA_QUALITY,
621 METADATA_QUALITY_DECLARED,
622 );
623 }
624 if let Some(ucum) = ucum {
625 index.record_scalar(table_name, SEMANTIC_METRIC_UNIT, ucum);
626 }
627 Ok(())
628}
629
630fn metric_type_value(wire_type: i32) -> Option<&'static str> {
633 match metadata::MetricType::try_from(wire_type).ok()? {
634 metadata::MetricType::Unspecified => None,
635 metadata::MetricType::Counter => Some(METRIC_TYPE_COUNTER),
636 metadata::MetricType::Gauge => Some(METRIC_TYPE_GAUGE),
637 metadata::MetricType::Histogram => Some(METRIC_TYPE_HISTOGRAM),
638 metadata::MetricType::Gaugehistogram => Some(METRIC_TYPE_GAUGE_HISTOGRAM),
639 metadata::MetricType::Summary => Some(METRIC_TYPE_SUMMARY),
640 metadata::MetricType::Info => Some(METRIC_TYPE_INFO),
641 metadata::MetricType::Stateset => Some(METRIC_TYPE_STATESET),
642 }
643}
644
645#[derive(Default)]
646struct SeriesCounts {
647 exemplars: usize,
648 samples: usize,
649 histograms: usize,
650}
651
652fn scan_series(
653 mut buf: &[u8],
654 labels_refs: &mut Vec<u32>,
655 metadata: &mut Metadata,
656) -> std::result::Result<SeriesCounts, DecodeError> {
657 labels_refs.clear();
658 metadata.clear();
659 let mut counts = SeriesCounts::default();
660
661 while buf.has_remaining() {
662 let (tag, wire_type) = decode_key(&mut buf)?;
663 match tag {
664 TIME_SERIES_LABELS_REFS_TAG => {
665 uint32::merge_repeated(wire_type, labels_refs, &mut buf, DecodeContext::default())
666 .map_err(|mut error| {
667 error.push("TimeSeries", "labels_refs");
668 error
669 })?
670 }
671 TIME_SERIES_SAMPLES_TAG => {
672 take_length_delimited(wire_type, &mut buf).map_err(|mut error| {
673 error.push("TimeSeries", "samples");
674 error
675 })?;
676 counts.samples = counts.samples.checked_add(1).ok_or_else(|| {
677 DecodeError::new("remote write v2 sample count overflows usize")
678 })?;
679 }
680 TIME_SERIES_HISTOGRAMS_TAG => {
681 take_length_delimited(wire_type, &mut buf).map_err(|mut error| {
682 error.push("TimeSeries", "histograms");
683 error
684 })?;
685 counts.histograms = counts.histograms.checked_add(1).ok_or_else(|| {
686 DecodeError::new("remote write v2 histogram count overflows usize")
687 })?;
688 }
689 TIME_SERIES_EXEMPLARS_TAG => {
690 take_length_delimited(wire_type, &mut buf)?;
691 counts.exemplars = counts
692 .exemplars
693 .checked_add(1)
694 .ok_or_else(|| DecodeError::new("exemplar count overflow"))?;
695 }
696 TIME_SERIES_METADATA_TAG => {
697 message::merge(wire_type, metadata, &mut buf, DecodeContext::default()).map_err(
698 |mut error| {
699 error.push("TimeSeries", "metadata");
700 error
701 },
702 )?
703 }
704 _ => skip_field(wire_type, tag, &mut buf, DecodeContext::default())?,
705 }
706 }
707
708 Ok(counts)
709}
710
711#[derive(Default)]
712struct LeafScratch {
713 sample: Sample,
714 histogram: Histogram,
715 exemplar: Exemplar,
716 exemplars: Vec<Exemplar>,
717}
718
719enum SeriesWriter<'a> {
720 Samples(&'a mut TableData),
721 Histograms(&'a mut TableData),
722}
723
724fn decode_series_leaves(
725 mut buf: &[u8],
726 mut writer: Option<SeriesWriter<'_>>,
727 mut tags: PromTags<'_>,
728 mut rows_remaining: usize,
729 scratch: &mut LeafScratch,
730) -> Result<()> {
731 let mut sample_row_template = None;
732 let mut histogram_row_template = None;
733 scratch.exemplars.clear();
734
735 while buf.has_remaining() {
736 let (tag, wire_type) = decode_key(&mut buf).context(error::DecodePromRemoteRequestSnafu)?;
737 match tag {
738 TIME_SERIES_LABELS_REFS_TAG => {
739 skip_field(wire_type, tag, &mut buf, DecodeContext::default())
740 .context(error::DecodePromRemoteRequestSnafu)?
741 }
742 TIME_SERIES_SAMPLES_TAG => {
743 scratch.sample.clear();
744 message::merge(
745 wire_type,
746 &mut scratch.sample,
747 &mut buf,
748 DecodeContext::default(),
749 )
750 .map_err(|mut error| {
751 error.push("TimeSeries", "samples");
752 error
753 })
754 .context(error::DecodePromRemoteRequestSnafu)?;
755 rows_remaining = rows_remaining.checked_sub(1).with_context(|| {
756 error::InvalidPromRemoteRequestSnafu {
757 msg: "remote write v2 sample count changed between scans".to_string(),
758 }
759 })?;
760 if let Some(SeriesWriter::Samples(table_data)) = &mut writer {
761 if sample_row_template.is_none() {
762 let timestamp_index = table_data.ensure_column_by_name(
763 greptime_timestamp(),
764 ColumnDataType::TimestampMillisecond,
765 SemanticType::Timestamp,
766 )?;
767 let value_index = table_data.ensure_column_by_name(
768 greptime_value(),
769 ColumnDataType::Float64,
770 SemanticType::Field,
771 )?;
772 let mut row = table_data.alloc_one_row();
773 row_writer::write_tags(
774 table_data,
775 std::mem::take(&mut tags).into_iter(),
776 &mut row,
777 )?;
778 sample_row_template = Some((row, timestamp_index, value_index));
779 }
780
781 if let Some((row, timestamp_index, value_index)) = sample_row_template.as_mut()
782 {
783 row[*timestamp_index].value_data = Some(
784 ValueData::TimestampMillisecondValue(scratch.sample.timestamp),
785 );
786 row[*value_index].value_data =
787 Some(ValueData::F64Value(scratch.sample.value));
788 let row = if rows_remaining == 0 {
789 std::mem::take(row)
790 } else {
791 row.clone()
792 };
793 table_data.add_row(row);
794 }
795 }
796 }
797 TIME_SERIES_HISTOGRAMS_TAG => {
798 scratch.histogram.clear();
799 message::merge(
800 wire_type,
801 &mut scratch.histogram,
802 &mut buf,
803 DecodeContext::default(),
804 )
805 .map_err(|mut error| {
806 error.push("TimeSeries", "histograms");
807 error
808 })
809 .context(error::DecodePromRemoteRequestSnafu)?;
810 rows_remaining = rows_remaining.checked_sub(1).with_context(|| {
811 error::InvalidPromRemoteRequestSnafu {
812 msg: "remote write v2 histogram count changed between scans".to_string(),
813 }
814 })?;
815 if let Some(SeriesWriter::Histograms(table_data)) = &mut writer {
816 let value = encode_native_histogram(&scratch.histogram).map_err(|error| {
818 error::InvalidPromRemoteRequestSnafu {
819 msg: format!("remote write v2 {error}"),
820 }
821 .build()
822 })?;
823 if histogram_row_template.is_none() {
824 let timestamp_index = table_data.ensure_column_by_name(
825 greptime_timestamp(),
826 ColumnDataType::TimestampMillisecond,
827 SemanticType::Timestamp,
828 )?;
829 let value_index = match table_data.column_index(greptime_native_histogram())
830 {
831 Some(index) => index,
832 None => {
833 let schema = native_histogram_column_schema().map_err(|error| {
834 error::InvalidPromRemoteRequestSnafu {
835 msg: format!("remote write v2 {error}"),
836 }
837 .build()
838 })?;
839 table_data.ensure_column(schema)?
840 }
841 };
842 let mut row = table_data.alloc_one_row();
843 row_writer::write_tags(
844 table_data,
845 std::mem::take(&mut tags).into_iter(),
846 &mut row,
847 )?;
848 histogram_row_template = Some((row, timestamp_index, value_index));
849 }
850 if let Some((template, timestamp_index, value_index)) =
851 histogram_row_template.as_mut()
852 {
853 let mut row = if rows_remaining == 0 {
855 std::mem::take(template)
856 } else {
857 template.clone()
858 };
859 row[*timestamp_index].value_data = Some(
860 ValueData::TimestampMillisecondValue(scratch.histogram.timestamp),
861 );
862 row[*value_index].value_data = Some(value);
863 table_data.add_row(row);
864 }
865 }
866 }
867 TIME_SERIES_EXEMPLARS_TAG => {
868 scratch.exemplar.clear();
869 message::merge(
870 wire_type,
871 &mut scratch.exemplar,
872 &mut buf,
873 DecodeContext::default(),
874 )
875 .map_err(|mut error| {
876 error.push("TimeSeries", "exemplars");
877 error
878 })
879 .context(error::DecodePromRemoteRequestSnafu)?;
880 scratch
881 .exemplars
882 .push(std::mem::take(&mut scratch.exemplar));
883 }
884 TIME_SERIES_METADATA_TAG => {
885 take_length_delimited(wire_type, &mut buf)
886 .map_err(|mut error| {
887 error.push("TimeSeries", "metadata");
888 error
889 })
890 .context(error::DecodePromRemoteRequestSnafu)?;
891 }
892 _ => skip_field(wire_type, tag, &mut buf, DecodeContext::default())
893 .context(error::DecodePromRemoteRequestSnafu)?,
894 }
895 }
896
897 ensure!(
898 rows_remaining == 0,
899 error::InvalidPromRemoteRequestSnafu {
900 msg: "remote write v2 row count changed between scans".to_string(),
901 }
902 );
903
904 Ok(())
905}
906
907fn take_length_delimited<'a>(
908 wire_type: WireType,
909 buf: &mut &'a [u8],
910) -> std::result::Result<&'a [u8], DecodeError> {
911 if wire_type != WireType::LengthDelimited {
912 return Err(DecodeError::new(format!(
913 "invalid wire type: {wire_type:?} (expected LengthDelimited)"
914 )));
915 }
916
917 let len = decode_varint(buf)?;
918 let len =
919 usize::try_from(len).map_err(|_| DecodeError::new("length delimiter exceeds usize"))?;
920 if len > buf.len() {
921 return Err(DecodeError::new("buffer underflow"));
922 }
923 let (value, remaining) = buf.split_at(len);
924 *buf = remaining;
925 Ok(value)
926}
927
928fn checked_total(total: u64, count: usize, name: &str) -> Result<u64> {
929 let count =
930 u64::try_from(count)
931 .ok()
932 .with_context(|| error::InvalidPromRemoteRequestSnafu {
933 msg: format!("remote write v2 {name} count exceeds u64"),
934 })?;
935 total
936 .checked_add(count)
937 .with_context(|| error::InvalidPromRemoteRequestSnafu {
938 msg: format!("remote write v2 {name} count overflows u64"),
939 })
940}
941
942fn get_or_create_table_data(
943 tables: &mut HashMap<PromCtx, HashMap<String, TableData>>,
944 prom_ctx: PromCtx,
945 table_name: String,
946 column_count: usize,
947 row_count: usize,
948) -> &mut TableData {
949 match tables.entry(prom_ctx).or_default().entry(table_name) {
950 Entry::Occupied(entry) => {
951 let table_data = entry.into_mut();
952 table_data.reserve_rows(row_count);
953 table_data
954 }
955 Entry::Vacant(entry) => entry.insert(TableData::new(column_count, row_count)),
956 }
957}
958
959fn ensure_no_internal_histogram_labels(tags: &PromTags<'_>) -> Result<()> {
960 for (name, _) in tags {
962 ensure!(
963 *name != greptime_native_histogram() && *name != NATIVE_HISTOGRAM_FIELD,
964 error::InvalidPromRemoteRequestSnafu {
965 msg: format!(
966 "remote write v2 label `{name}` conflicts with an internal native histogram label"
967 ),
968 }
969 );
970 }
971
972 Ok(())
973}
974
975fn resolve_series_labels<'a>(
976 symbols: &'a [&str],
977 labels_refs: &[u32],
978 label_names: &mut HashSet<&'a str>,
979) -> Result<ResolvedSeriesLabels<'a>> {
980 ensure!(
981 labels_refs.len().is_multiple_of(2),
982 error::InvalidPromRemoteRequestSnafu {
983 msg: "remote write v2 labels_refs must contain name/value pairs".to_string(),
984 }
985 );
986
987 let mut prom_ctx = PromCtx::default();
988 let mut table_name = None;
989 let mut tags = Vec::with_capacity(labels_refs.len() / 2);
990 label_names.clear();
991
992 for pair in labels_refs.as_chunks::<2>().0 {
993 let name = symbol_ref(symbols, pair[0], "label name")?;
994 let value = symbol_ref(symbols, pair[1], "label value")?;
995 validate_label(name)?;
996 ensure!(
997 label_names.insert(name),
998 error::InvalidPromRemoteRequestSnafu {
999 msg: format!("remote write v2 label name `{name}` is repeated"),
1000 }
1001 );
1002
1003 if name == METRIC_NAME_LABEL {
1004 table_name = Some(value.to_string());
1005 continue;
1006 }
1007 if apply_remote_write_special_label(name, value, &mut prom_ctx) {
1008 continue;
1009 }
1010
1011 tags.push((name, value.to_string()));
1012 }
1013
1014 let table_name = table_name.with_context(|| error::InvalidPromRemoteRequestSnafu {
1015 msg: "missing '__name__' label in time-series".to_string(),
1016 })?;
1017 ensure!(
1018 !table_name.is_empty(),
1019 error::InvalidPromRemoteRequestSnafu {
1020 msg: "remote write v2 label `__name__` value must not be empty".to_string(),
1021 }
1022 );
1023
1024 Ok((prom_ctx, table_name, tags))
1025}
1026
1027fn validate_label(name: &str) -> Result<()> {
1028 ensure!(
1029 !name.is_empty(),
1030 error::InvalidPromRemoteRequestSnafu {
1031 msg: format!("remote write v2 invalid label name `{name}`"),
1032 }
1033 );
1034
1035 Ok(())
1036}
1037
1038fn symbol_ref<'a>(symbols: &'a [&str], idx: u32, field: &str) -> Result<&'a str> {
1039 let idx = usize::try_from(idx)
1040 .ok()
1041 .with_context(|| error::InvalidPromRemoteRequestSnafu {
1042 msg: format!("remote write v2 {field} symbol reference exceeds usize"),
1043 })?;
1044 symbols
1045 .get(idx)
1046 .copied()
1047 .with_context(|| error::InvalidPromRemoteRequestSnafu {
1048 msg: format!(
1049 "remote write v2 {field} symbol reference {idx} is out of range, symbols len: {}",
1050 symbols.len()
1051 ),
1052 })
1053}
1054
1055#[allow(deprecated)]
1056fn apply_remote_write_special_label(name: &str, value: &str, prom_ctx: &mut PromCtx) -> bool {
1057 match name {
1058 SCHEMA_LABEL => {
1059 prom_ctx.schema = Some(value.to_string());
1060 true
1061 }
1062 DATABASE_LABEL | DATABASE_LABEL_ALT => {
1063 if prom_ctx.schema.is_none() {
1064 prom_ctx.schema = Some(value.to_string());
1065 }
1066 true
1067 }
1068 PHYSICAL_TABLE_LABEL | PHYSICAL_TABLE_LABEL_ALT => {
1069 prom_ctx.physical_table = Some(value.to_string());
1070 true
1071 }
1072 _ => false,
1073 }
1074}
1075
1076fn into_context_req(tables: HashMap<PromCtx, HashMap<String, TableData>>) -> ContextReq {
1077 let mut ctx_req = ContextReq::default();
1078 for (prom_ctx, tables) in tables {
1079 let mut opt = ContextOpt::default();
1080 if let Some(schema) = prom_ctx.schema {
1081 opt.set_schema(schema);
1082 }
1083 if let Some(physical_table) = prom_ctx.physical_table {
1084 opt.set_physical_table(physical_table);
1085 }
1086
1087 ctx_req.add_rows(
1088 opt,
1089 tables.into_iter().map(|(table_name, table_data)| {
1090 table_data_to_row_insert_request(table_name, table_data)
1091 }),
1092 );
1093 }
1094 ctx_req
1095}
1096
1097fn table_data_to_row_insert_request(table_name: String, table_data: TableData) -> RowInsertRequest {
1098 let num_columns = table_data.num_columns();
1099 let (schema, mut rows) = table_data.into_schema_and_rows();
1100 for row in &mut rows {
1101 if num_columns > row.values.len() {
1102 row.values.resize(num_columns, Value { value_data: None });
1103 }
1104 }
1105
1106 RowInsertRequest {
1107 table_name,
1108 rows: Some(Rows { schema, rows }),
1109 }
1110}
1111
1112#[cfg(any(test, feature = "testing"))]
1113pub mod test_util {
1114 use api::greptime_proto::io::prometheus::write::v2::{Histogram, Request, Sample, TimeSeries};
1115 use api::v1::RowInsertRequest;
1116 use bytes::Bytes;
1117 use prost::Message;
1118 use snafu::ResultExt;
1119
1120 use crate::error::{self, Result};
1121 use crate::prom_remote_write::decompress_remote_write_body;
1122 use crate::prom_store::snappy_compress;
1123 use crate::request_memory_limiter::ServerMemoryLimiter;
1124
1125 pub fn request_with_labels_and_samples(
1126 labels: Vec<(&str, &str)>,
1127 samples: Vec<Sample>,
1128 ) -> Request {
1129 request_with_labels(labels, samples, Vec::new())
1130 }
1131
1132 pub fn request_with_labels_and_histograms(
1133 labels: Vec<(&str, &str)>,
1134 histograms: Vec<Histogram>,
1135 ) -> Request {
1136 request_with_labels(labels, Vec::new(), histograms)
1137 }
1138
1139 pub fn decode_request(is_zstd: bool, body: Bytes) -> Result<Request> {
1140 let buf = block_on_decode(is_zstd, body)?;
1141 Request::decode(&buf[..]).context(error::DecodePromRemoteRequestSnafu)
1142 }
1143
1144 fn block_on_decode(is_zstd: bool, body: Bytes) -> Result<Vec<u8>> {
1147 tokio::runtime::Builder::new_current_thread()
1148 .enable_all()
1149 .build()
1150 .unwrap()
1151 .block_on(async move {
1152 decompress_remote_write_body(is_zstd, &body[..], &ServerMemoryLimiter::default(), 0)
1153 .await
1154 .map(|buf| buf.to_vec())
1155 })
1156 }
1157
1158 pub fn write_requests(
1159 request: Request,
1160 ) -> Result<(Vec<RowInsertRequest>, Vec<RowInsertRequest>, u64, u64)> {
1161 let body = Bytes::from(snappy_compress(&request.encode_to_vec())?);
1162 decode_write_requests(false, body)
1163 }
1164
1165 pub fn decode_write_requests(
1166 is_zstd: bool,
1167 body: Bytes,
1168 ) -> Result<(Vec<RowInsertRequest>, Vec<RowInsertRequest>, u64, u64)> {
1169 let requests = tokio::runtime::Builder::new_current_thread()
1170 .enable_all()
1171 .build()
1172 .unwrap()
1173 .block_on(super::decode_remote_write_v2(
1174 is_zstd,
1175 body,
1176 &ServerMemoryLimiter::default(),
1177 0,
1178 ))?;
1179 Ok((
1180 requests.samples.all_req().collect(),
1181 requests.histograms.all_req().collect(),
1182 requests.sample_count,
1183 requests.histogram_count,
1184 ))
1185 }
1186
1187 pub fn decode_uncompressed_write_requests(
1188 body: &[u8],
1189 ) -> Result<(Vec<RowInsertRequest>, Vec<RowInsertRequest>, u64, u64)> {
1190 let limiter = ServerMemoryLimiter::default();
1191 let mut scratch = super::ConversionBudget::new(&limiter, 0)?;
1192 scratch.reserve(Some(
1193 super::conversion_scratch_bytes(body).context(error::DecodePromRemoteRequestSnafu)?,
1194 ))?;
1195 let budget = super::ConversionBudget::new(&limiter, scratch.guard.granted_bytes())?;
1196 let request =
1197 super::BorrowedRequest::decode(body).context(error::DecodePromRemoteRequestSnafu)?;
1198 let requests = super::convert_remote_write_v2(request, budget)?;
1199 Ok((
1200 requests.samples.all_req().collect(),
1201 requests.histograms.all_req().collect(),
1202 requests.sample_count,
1203 requests.histogram_count,
1204 ))
1205 }
1206
1207 pub fn histogram(timestamp: i64) -> Histogram {
1208 Histogram {
1209 timestamp,
1210 ..Default::default()
1211 }
1212 }
1213
1214 fn request_with_labels(
1215 labels: Vec<(&str, &str)>,
1216 samples: Vec<Sample>,
1217 histograms: Vec<Histogram>,
1218 ) -> Request {
1219 let mut symbols = vec!["".to_string()];
1220 let mut labels_refs = Vec::with_capacity(labels.len() * 2);
1221 for (name, value) in labels {
1222 labels_refs.push(push_symbol(&mut symbols, name));
1223 labels_refs.push(push_symbol(&mut symbols, value));
1224 }
1225
1226 Request {
1227 symbols,
1228 timeseries: vec![TimeSeries {
1229 labels_refs,
1230 samples,
1231 histograms,
1232 exemplars: Vec::new(),
1233 metadata: None,
1234 }],
1235 }
1236 }
1237
1238 fn push_symbol(symbols: &mut Vec<String>, symbol: &str) -> u32 {
1239 if let Some(idx) = symbols.iter().position(|s| s == symbol) {
1240 return idx as u32;
1241 }
1242
1243 let idx = symbols.len();
1244 symbols.push(symbol.to_string());
1245 idx as u32
1246 }
1247}
1248
1249#[cfg(test)]
1250mod tests {
1251 use std::sync::Arc;
1252
1253 use api::v1::value::ValueData;
1254 use common_query::prelude::{greptime_timestamp, greptime_value, set_default_prefix};
1255 use session::context::QueryContext;
1256
1257 use super::*;
1258 use crate::error;
1259 use crate::http::prom_store::PHYSICAL_TABLE_PARAM;
1260 use crate::prom_store::{DATABASE_LABEL, PHYSICAL_TABLE_LABEL};
1261
1262 #[test]
1263 fn test_decode_remote_write_v2_request() {
1264 let request = Request {
1265 symbols: vec![
1266 "".to_string(),
1267 "__name__".to_string(),
1268 "http_requests_total".to_string(),
1269 ],
1270 timeseries: vec![TimeSeries {
1271 labels_refs: vec![1, 2],
1272 samples: vec![Sample {
1273 value: 42.0,
1274 timestamp: 1000,
1275 start_timestamp: 0,
1276 }],
1277 histograms: Vec::new(),
1278 exemplars: Vec::new(),
1279 metadata: Some(Metadata {
1280 r#type: metadata::MetricType::Counter as i32,
1281 help_ref: 0,
1282 unit_ref: 0,
1283 }),
1284 }],
1285 };
1286 let body =
1287 Bytes::from(crate::prom_store::snappy_compress(&request.encode_to_vec()).unwrap());
1288
1289 let decoded = test_util::decode_request(false, body.clone()).unwrap();
1290
1291 assert_eq!(decoded.symbols, request.symbols);
1292 assert_eq!(decoded.timeseries.len(), 1);
1293 assert_eq!(decoded.timeseries[0].labels_refs, vec![1, 2]);
1294 assert_eq!(decoded.timeseries[0].samples.len(), 1);
1295 assert_eq!(decoded.timeseries[0].samples[0].value, 42.0);
1296 assert_eq!(decoded.timeseries[0].metadata.as_ref().unwrap().r#type, 1);
1297 assert_eq!(
1298 decode_v2_on_test_runtime(true, body).unwrap().sample_count,
1299 1
1300 );
1301 }
1302
1303 #[test]
1304 fn test_fused_decoder_accepts_arbitrary_field_order_and_split_labels() {
1305 let mut first_sample = Sample {
1306 value: 42.0,
1307 timestamp: 1000,
1308 start_timestamp: 500,
1309 }
1310 .encode_to_vec();
1311 first_sample.extend(varint_field(90, 1));
1312 let second_sample = Sample {
1313 value: 43.0,
1314 timestamp: 2000,
1315 start_timestamp: 1000,
1316 }
1317 .encode_to_vec();
1318
1319 let mut series = encoded_message_field(2, &first_sample);
1320 series.extend(packed_u32_field(1, &[1]));
1321 series.extend(varint_field(90, 1));
1322 series.extend(encoded_message_field(2, &second_sample));
1323 series.extend(varint_field(1, 2));
1324
1325 let mut wire = encoded_message_field(5, &series);
1326 wire.extend(string_field(4, b""));
1327 wire.extend(varint_field(90, 1));
1328 wire.extend(string_field(4, METRIC_NAME_LABEL.as_bytes()));
1329 wire.extend(encoded_message_field(5, &packed_u32_field(1, &[99])));
1330 wire.extend(string_field(4, b"http_requests_total"));
1331
1332 let requests = decode_wire(&wire).unwrap();
1333 assert_eq!(requests.sample_count, 2);
1334 assert_eq!(requests.histogram_count, 0);
1335 let rows = requests.samples.all_req().next().unwrap().rows.unwrap();
1336 assert_eq!(rows.rows.len(), 2);
1337 assert_eq!(
1338 rows.schema
1339 .iter()
1340 .map(|column| column.column_name.as_str())
1341 .collect::<Vec<_>>(),
1342 vec![greptime_timestamp(), greptime_value()]
1343 );
1344 assert_eq!(
1345 rows.rows[0].values[1].value_data,
1346 Some(ValueData::F64Value(42.0))
1347 );
1348 assert_eq!(
1349 rows.rows[1].values[1].value_data,
1350 Some(ValueData::F64Value(43.0))
1351 );
1352 }
1353
1354 #[test]
1355 fn test_fused_decoder_accepts_histogram_before_labels_and_unknown_fields() {
1356 let mut histogram = Histogram {
1357 count: Some(Count::CountInt(0)),
1358 zero_count: Some(ZeroCount::ZeroCountInt(0)),
1359 timestamp: 2000,
1360 start_timestamp: 1000,
1361 ..Default::default()
1362 }
1363 .encode_to_vec();
1364 histogram.extend(varint_field(90, 1));
1365
1366 let mut series = encoded_message_field(3, &histogram);
1367 series.extend(packed_u32_field(1, &[1, 2]));
1368 let wire = request_wire(&["", METRIC_NAME_LABEL, "metric"], &[series]);
1369
1370 let requests = decode_wire(&wire).unwrap();
1371 assert_eq!(requests.histogram_count, 1);
1372 let rows = requests.histograms.all_req().next().unwrap().rows.unwrap();
1373 assert_eq!(
1374 histogram_field_value(&rows, 0, START_TIMESTAMP_FIELD),
1375 Some(ValueData::TimestampMillisecondValue(1000))
1376 );
1377 }
1378
1379 #[test]
1380 fn test_fused_decoder_rejects_malformed_wire() {
1381 let mut wrong_request_wire = varint_field(4, 0);
1382 let invalid_utf8 = string_field(4, &[0xff]);
1383
1384 let mut truncated_request = Vec::new();
1385 prost::encoding::encode_key(5, WireType::LengthDelimited, &mut truncated_request);
1386 prost::encoding::encode_varint(2, &mut truncated_request);
1387 truncated_request.push(0);
1388
1389 let mut oversized_request = Vec::new();
1390 prost::encoding::encode_key(4, WireType::LengthDelimited, &mut oversized_request);
1391 prost::encoding::encode_varint(u64::MAX, &mut oversized_request);
1392
1393 let malformed_varint = vec![0x80; 10];
1394
1395 let mut wrong_labels_wire = Vec::new();
1396 prost::encoding::encode_key(1, WireType::SixtyFourBit, &mut wrong_labels_wire);
1397 wrong_labels_wire.extend([0; 8]);
1398 wrong_labels_wire = request_wire(&[""], &[std::mem::take(&mut wrong_labels_wire)]);
1399
1400 let wrong_series_wire = request_wire(&[""], &[varint_field(2, 0)]);
1401
1402 let mut truncated_series = Vec::new();
1403 prost::encoding::encode_key(2, WireType::LengthDelimited, &mut truncated_series);
1404 prost::encoding::encode_varint(2, &mut truncated_series);
1405 truncated_series.push(0x08);
1406 let truncated_series = request_wire(&[""], &[truncated_series]);
1407
1408 let mut oversized_series = Vec::new();
1409 prost::encoding::encode_key(3, WireType::LengthDelimited, &mut oversized_series);
1410 prost::encoding::encode_varint(u64::MAX, &mut oversized_series);
1411 let oversized_series = request_wire(&[""], &[oversized_series]);
1412
1413 let invalid_sample = series_wire(&[1, 2], 2, &varint_field(1, 1));
1414 let invalid_sample = request_wire(&["", METRIC_NAME_LABEL, "metric"], &[invalid_sample]);
1415 let invalid_histogram = series_wire(&[1, 2], 3, &varint_field(3, 1));
1416 let invalid_histogram =
1417 request_wire(&["", METRIC_NAME_LABEL, "metric"], &[invalid_histogram]);
1418
1419 for (name, wire) in [
1420 (
1421 "wrong request wire type",
1422 std::mem::take(&mut wrong_request_wire),
1423 ),
1424 ("invalid utf8", invalid_utf8),
1425 ("truncated request", truncated_request),
1426 ("oversized request length", oversized_request),
1427 ("malformed varint", malformed_varint),
1428 ("wrong labels wire type", wrong_labels_wire),
1429 ("wrong series wire type", wrong_series_wire),
1430 ("truncated series", truncated_series),
1431 ("oversized series length", oversized_series),
1432 ("invalid sample", invalid_sample),
1433 ("invalid histogram", invalid_histogram),
1434 ] {
1435 let error = decode_wire_error(&wire, name);
1436 assert!(
1437 matches!(error, error::Error::DecodePromRemoteRequest { .. }),
1438 "{name}: {error}"
1439 );
1440 }
1441 }
1442
1443 #[test]
1444 fn test_fused_decoder_rejects_malformed_ignored_messages() {
1445 for tag in [4, 5] {
1446 let mut series = series_wire(&[1, 2], 2, &Sample::default().encode_to_vec());
1447 series.extend(encoded_message_field(tag, &[0x08]));
1448 let wire = request_wire(&["", METRIC_NAME_LABEL, "metric"], &[series]);
1449
1450 let error = decode_wire_error(&wire, "malformed ignored message");
1451 assert!(matches!(
1452 error,
1453 error::Error::DecodePromRemoteRequest { .. }
1454 ));
1455 }
1456 }
1457
1458 #[test]
1459 fn test_fused_decoder_rejects_invalid_exemplar_symbol_refs() {
1460 let request = Request {
1461 symbols: vec![
1462 String::new(),
1463 METRIC_NAME_LABEL.to_string(),
1464 "metric".to_string(),
1465 ],
1466 timeseries: vec![TimeSeries {
1467 labels_refs: vec![1, 2],
1468 samples: vec![Sample::default()],
1469 exemplars: vec![Exemplar {
1470 labels_refs: vec![99],
1471 ..Default::default()
1472 }],
1473 ..Default::default()
1474 }],
1475 };
1476
1477 assert!(decode_test_request(request).is_err());
1478 }
1479
1480 #[test]
1481 fn test_invalid_exemplars_preserve_samples_and_counts() {
1482 let valid = Exemplar {
1483 labels_refs: vec![3, 4],
1484 timestamp: 1,
1485 value: 0.5,
1486 };
1487 for invalid in [
1488 Exemplar {
1489 labels_refs: vec![3, 4, 3, 4],
1490 ..valid.clone()
1491 },
1492 Exemplar {
1493 labels_refs: vec![3, 5],
1494 ..valid.clone()
1495 },
1496 Exemplar {
1497 timestamp: i64::MAX,
1498 ..valid.clone()
1499 },
1500 Exemplar {
1501 labels_refs: vec![6, 4],
1502 ..valid.clone()
1503 },
1504 ] {
1505 for retain_valid in [false, true] {
1506 let mut observations = vec![invalid.clone()];
1507 if retain_valid {
1508 observations.extend([valid.clone(), invalid.clone(), valid.clone()]);
1509 }
1510 let request = Request {
1511 symbols: vec![
1512 "".into(),
1513 METRIC_NAME_LABEL.into(),
1514 "metric".into(),
1515 "trace_id".into(),
1516 "abc".into(),
1517 "x".repeat(128),
1518 datatypes::json::JSON2_REMAINDER_FIELD_NAME.into(),
1519 ],
1520 timeseries: vec![TimeSeries {
1521 labels_refs: vec![1, 2],
1522 samples: vec![Sample::default()],
1523 exemplars: observations,
1524 ..Default::default()
1525 }],
1526 };
1527 let converted = decode_test_request(request).unwrap();
1528 assert_eq!(converted.sample_count, 1);
1529 let expected = if retain_valid { 2 } else { 0 };
1530 assert_eq!(converted.exemplar_count, expected);
1531 assert_eq!(
1532 converted.exemplars.values().map(Vec::len).sum::<usize>(),
1533 expected as usize
1534 );
1535 }
1536 }
1537 }
1538
1539 #[test]
1540 fn test_fused_decoder_preserves_empty_request_and_series_behavior() {
1541 let request = Request {
1542 symbols: vec![
1543 String::new(),
1544 METRIC_NAME_LABEL.to_string(),
1545 "metric".to_string(),
1546 "job".to_string(),
1547 ],
1548 timeseries: vec![
1549 TimeSeries {
1550 labels_refs: vec![99, 99],
1551 ..Default::default()
1552 },
1553 TimeSeries {
1554 labels_refs: vec![1],
1555 ..Default::default()
1556 },
1557 TimeSeries {
1558 labels_refs: vec![3, 2, 3, 2],
1559 ..Default::default()
1560 },
1561 TimeSeries::default(),
1562 ],
1563 };
1564
1565 let requests = decode_test_request(request).unwrap();
1566 assert_eq!(requests.sample_count, 0);
1567 assert_eq!(requests.histogram_count, 0);
1568
1569 let requests = decode_test_request(Request {
1570 symbols: vec![String::new()],
1571 timeseries: Vec::new(),
1572 })
1573 .unwrap();
1574 assert_eq!(requests.sample_count, 0);
1575 assert!(decode_wire(&[]).is_err());
1576 assert!(decode_wire(&[0x0a, 0x00]).is_err());
1577 }
1578
1579 #[test]
1580 fn test_fused_decoder_rejects_duplicate_resolved_label_names() {
1581 let request = Request {
1582 symbols: vec![
1583 String::new(),
1584 METRIC_NAME_LABEL.to_string(),
1585 "metric".to_string(),
1586 "job".to_string(),
1587 "job".to_string(),
1588 "api".to_string(),
1589 "worker".to_string(),
1590 ],
1591 timeseries: vec![TimeSeries {
1592 labels_refs: vec![1, 2, 3, 5, 4, 6],
1593 samples: vec![Sample::default()],
1594 ..Default::default()
1595 }],
1596 };
1597
1598 assert_invalid(
1599 "duplicate resolved labels",
1600 request,
1601 "label name `job` is repeated",
1602 );
1603 }
1604
1605 #[test]
1606 fn test_fused_decoder_rejects_malformed_series_with_histograms() {
1607 let histogram = series_wire(
1608 &[1, 2],
1609 3,
1610 &Histogram {
1611 count: Some(Count::CountInt(0)),
1612 zero_count: Some(ZeroCount::ZeroCountInt(0)),
1613 ..Default::default()
1614 }
1615 .encode_to_vec(),
1616 );
1617 let malformed_sample = series_wire(&[1, 3], 2, &[0x08]);
1618
1619 let wire = request_wire(
1620 &["", METRIC_NAME_LABEL, "histogram", "sample"],
1621 &[histogram.clone(), malformed_sample.clone()],
1622 );
1623 let error = decode_wire_error(&wire, "histogram before malformed series");
1624 assert!(matches!(
1625 error,
1626 error::Error::DecodePromRemoteRequest { .. }
1627 ));
1628
1629 let wire = request_wire(
1630 &["", METRIC_NAME_LABEL, "histogram", "sample"],
1631 &[malformed_sample, histogram.clone()],
1632 );
1633 let error = decode_wire_error(&wire, "malformed series before histogram");
1634 assert!(matches!(
1635 error,
1636 error::Error::DecodePromRemoteRequest { .. }
1637 ));
1638
1639 let missing_name = series_wire(&[3, 4], 2, &Sample::default().encode_to_vec());
1640 let wire = request_wire(
1641 &["", METRIC_NAME_LABEL, "metric", "job", "api"],
1642 &[missing_name, histogram],
1643 );
1644 let error = decode_wire_error(&wire, "conversion error before histogram");
1645 assert!(error.to_string().contains("missing '__name__'"));
1646 }
1647
1648 #[test]
1649 fn test_into_context_req_samples() {
1650 let ctx_req = decode_test_request(test_util::request_with_labels_and_samples(
1651 vec![
1652 (METRIC_NAME_LABEL, "http_requests_total"),
1653 ("job", "api"),
1654 ("instance", "localhost:9090"),
1655 ],
1656 vec![
1657 Sample {
1658 value: 42.0,
1659 timestamp: 1000,
1660 start_timestamp: 0,
1661 },
1662 Sample {
1663 value: 43.0,
1664 timestamp: 2000,
1665 start_timestamp: 0,
1666 },
1667 ],
1668 ))
1669 .unwrap();
1670
1671 assert_eq!(ctx_req.sample_count, 2);
1672 assert_eq!(ctx_req.histogram_count, 0);
1673 assert_eq!(ctx_req.histograms.all_req().count(), 0);
1674 let mut inserts = ctx_req.samples.all_req().collect::<Vec<_>>();
1675 assert_eq!(inserts.len(), 1);
1676
1677 let request = inserts.pop().unwrap();
1678 assert_eq!(request.table_name, "http_requests_total");
1679 let rows = request.rows.unwrap();
1680 assert_eq!(rows.rows.len(), 2);
1681 assert_eq!(
1682 rows.schema
1683 .iter()
1684 .map(|col| col.column_name.as_str())
1685 .collect::<Vec<_>>(),
1686 vec![greptime_timestamp(), greptime_value(), "job", "instance"]
1687 );
1688 assert_eq!(
1689 rows.rows[0].values[0].value_data,
1690 Some(ValueData::TimestampMillisecondValue(1000))
1691 );
1692 assert_eq!(
1693 rows.rows[0].values[1].value_data,
1694 Some(ValueData::F64Value(42.0))
1695 );
1696 assert_eq!(
1697 rows.rows[0].values[2].value_data,
1698 Some(ValueData::StringValue("api".to_string()))
1699 );
1700 assert_eq!(
1701 rows.rows[0].values[3].value_data,
1702 Some(ValueData::StringValue("localhost:9090".to_string()))
1703 );
1704 assert_eq!(
1705 rows.rows[1].values[0].value_data,
1706 Some(ValueData::TimestampMillisecondValue(2000))
1707 );
1708 assert_eq!(
1709 rows.rows[1].values[1].value_data,
1710 Some(ValueData::F64Value(43.0))
1711 );
1712 assert_eq!(
1713 rows.rows[1].values[2].value_data,
1714 Some(ValueData::StringValue("api".to_string()))
1715 );
1716 assert_eq!(
1717 rows.rows[1].values[3].value_data,
1718 Some(ValueData::StringValue("localhost:9090".to_string()))
1719 );
1720 }
1721
1722 #[test]
1723 fn test_into_context_req_special_labels() {
1724 let ctx_req = decode_test_request(test_util::request_with_labels_and_samples(
1725 vec![
1726 (METRIC_NAME_LABEL, "cpu_usage"),
1727 (DATABASE_LABEL, "tenant_a"),
1728 (PHYSICAL_TABLE_LABEL, "metrics_physical"),
1729 ("job", "api"),
1730 ],
1731 vec![Sample {
1732 value: 1.0,
1733 timestamp: 1000,
1734 start_timestamp: 0,
1735 }],
1736 ))
1737 .unwrap();
1738
1739 let mut iter = ctx_req
1740 .samples
1741 .as_req_iter(Arc::new(QueryContext::with("greptime", "public")));
1742 let (ctx, reqs) = iter.next().unwrap();
1743 assert!(iter.next().is_none());
1744
1745 assert_eq!(ctx.current_schema(), "tenant_a");
1746 assert_eq!(
1747 ctx.extension(PHYSICAL_TABLE_PARAM),
1748 Some("metrics_physical")
1749 );
1750 assert_eq!(reqs.inserts.len(), 1);
1751
1752 let rows = reqs.inserts[0].rows.as_ref().unwrap();
1753 assert_eq!(
1754 rows.schema
1755 .iter()
1756 .map(|col| col.column_name.as_str())
1757 .collect::<Vec<_>>(),
1758 vec![greptime_timestamp(), greptime_value(), "job"]
1759 );
1760 }
1761
1762 #[tokio::test]
1763 async fn test_expanded_rows_are_charged_before_allocation_and_released() {
1764 use common_memory_manager::OnExhaustedPolicy;
1765 let label = "x".repeat(64 * 1024);
1766 let mut request = request_with_sample(vec![(METRIC_NAME_LABEL, "metric"), ("tag", &label)]);
1767 request.timeseries[0].samples = vec![Sample::default(); 10_000];
1768 let limiter = ServerMemoryLimiter::new(8 * 1024 * 1024, OnExhaustedPolicy::Fail);
1769 let body =
1770 Bytes::from(crate::prom_store::snappy_compress(&request.encode_to_vec()).unwrap());
1771 let error = match decode_remote_write_v2(false, body, &limiter, 0).await {
1772 Ok(_) => panic!("interned labels must not expand past the quota"),
1773 Err(error) => error,
1774 };
1775 assert!(matches!(error, error::Error::MemoryLimitExceeded { .. }));
1776 assert_eq!(limiter.used_bytes(), 0);
1777
1778 request.timeseries[0].samples.truncate(1);
1779 let body =
1780 Bytes::from(crate::prom_store::snappy_compress(&request.encode_to_vec()).unwrap());
1781 let decoded = decode_remote_write_v2(false, body, &limiter, 0)
1782 .await
1783 .unwrap();
1784 assert!(limiter.used_bytes() >= label.len() as u64);
1785 let progress =
1786 session::write_progress::WriteProgress::with_reservation(decoded.memory_guard.clone());
1787 drop(decoded);
1788 assert!(limiter.used_bytes() >= label.len() as u64);
1789 drop(progress);
1790 assert_eq!(limiter.used_bytes(), 0);
1791 let mut budget = ConversionBudget::new(&limiter, 0).unwrap();
1792 assert!(budget.reserve(usize::MAX.checked_mul(2)).is_err());
1793 }
1794
1795 #[tokio::test]
1796 async fn test_exemplar_conversion_reserves_repeated_labels() {
1797 let limiter = ServerMemoryLimiter::new(128 * 1024, OnExhaustedPolicy::Fail);
1798 let label = "x".repeat(4096);
1799 let mut request =
1800 request_with_sample(vec![(METRIC_NAME_LABEL, "metric"), ("host", &label)]);
1801 request.timeseries[0].samples.clear();
1802 request.timeseries[0].exemplars = vec![Exemplar::default(); 128];
1803 let body =
1804 Bytes::from(crate::prom_store::snappy_compress(&request.encode_to_vec()).unwrap());
1805 assert!(matches!(
1806 decode_remote_write_v2(false, body, &limiter, 0).await,
1807 Err(error::Error::MemoryLimitExceeded { .. })
1808 ));
1809 assert_eq!(limiter.used_bytes(), 0);
1810
1811 request.timeseries[0].exemplars.truncate(1);
1812 let body =
1813 Bytes::from(crate::prom_store::snappy_compress(&request.encode_to_vec()).unwrap());
1814 let decoded = decode_remote_write_v2(false, body, &limiter, 0)
1815 .await
1816 .unwrap();
1817 assert_eq!(decoded.exemplar_count, 1);
1818 assert!(limiter.used_bytes() >= label.len() as u64);
1819 let progress =
1820 session::write_progress::WriteProgress::with_reservation(decoded.memory_guard.clone());
1821 drop(decoded);
1822 assert!(limiter.used_bytes() >= label.len() as u64);
1823 drop(progress);
1824 assert_eq!(limiter.used_bytes(), 0);
1825 }
1826
1827 #[tokio::test]
1828 async fn test_large_symbols_do_not_reserve_body_multiples_or_retain_scratch() {
1829 let limiter = ServerMemoryLimiter::new(64 * 1024 * 1024, OnExhaustedPolicy::Fail);
1830 let mut request = request_with_sample(vec![(METRIC_NAME_LABEL, "metric")]);
1831 let body =
1832 Bytes::from(crate::prom_store::snappy_compress(&request.encode_to_vec()).unwrap());
1833 let decoded = decode_remote_write_v2(false, body, &limiter, 0)
1834 .await
1835 .unwrap();
1836 let row_bytes = limiter.used_bytes();
1837 drop(decoded);
1838
1839 request
1840 .symbols
1841 .extend((0..4096).map(|i| format!("unused_{i}")));
1842 let help_ref = request.symbols.len() as u32;
1843 request.symbols.push("x".repeat(4 * 1024 * 1024));
1844 request.timeseries[0].metadata = Some(Metadata {
1845 help_ref,
1846 ..Default::default()
1847 });
1848 let body =
1849 Bytes::from(crate::prom_store::snappy_compress(&request.encode_to_vec()).unwrap());
1850 let decoded = decode_remote_write_v2(false, body, &limiter, 0)
1851 .await
1852 .unwrap();
1853 assert_eq!(decoded.sample_count, 1);
1854 assert_eq!(limiter.used_bytes(), row_bytes);
1855 drop(decoded);
1856 assert_eq!(limiter.used_bytes(), 0);
1857 }
1858
1859 #[tokio::test(start_paused = true)]
1860 async fn test_conversion_wait_timeout_is_retryable_but_oversized_requests_do_not_wait() {
1861 use common_error::ext::{ErrorExt, RetryHint};
1862
1863 let limiter = ServerMemoryLimiter::new(64 * 1024, OnExhaustedPolicy::default());
1864 let occupied = limiter.try_acquire(60 * 1024).unwrap();
1865 let mut request = request_with_sample(vec![(METRIC_NAME_LABEL, "metric")]);
1866 let body =
1867 Bytes::from(crate::prom_store::snappy_compress(&request.encode_to_vec()).unwrap());
1868 let error = decode_remote_write_v2(false, body, &limiter, 0)
1869 .await
1870 .err()
1871 .unwrap();
1872 assert_eq!(error.retry_hint(), RetryHint::Retryable);
1873 assert!(crate::error::status_code_to_http_status(&error.status_code()).is_server_error());
1874 assert_eq!(limiter.used_bytes(), occupied.granted_bytes());
1875 drop(occupied);
1876
1877 request.timeseries[0].samples = vec![Sample::default(); 10_000];
1878 let body =
1879 Bytes::from(crate::prom_store::snappy_compress(&request.encode_to_vec()).unwrap());
1880 let started = tokio::time::Instant::now();
1881 let error = decode_remote_write_v2(false, body, &limiter, 0)
1882 .await
1883 .err()
1884 .unwrap();
1885 assert!(matches!(error, error::Error::MemoryLimitExceeded { .. }));
1886 assert_eq!(started.elapsed(), std::time::Duration::ZERO);
1887 assert_eq!(limiter.used_bytes(), 0);
1888 }
1889
1890 #[test]
1891 fn test_chunked_reservations_respect_small_and_shared_quotas() {
1892 let limiter =
1893 ServerMemoryLimiter::new(8192, common_memory_manager::OnExhaustedPolicy::Fail);
1894 let mut first = ConversionBudget::new(&limiter, 0).unwrap();
1895 let mut second = ConversionBudget::new(&limiter, 0).unwrap();
1896 first.reserve(Some(3000)).unwrap();
1897 second.reserve(Some(3000)).unwrap();
1898 assert!(limiter.used_bytes() <= 8192);
1899 assert!(first.reserve(Some(8192)).is_err());
1900 assert!(first.guard.granted_bytes() >= first.used_bytes);
1901 drop(second);
1902 first.reserve(Some(3000)).unwrap();
1903 assert!(first.guard.granted_bytes() >= first.used_bytes);
1904 first.used_bytes = u64::MAX;
1905 assert!(first.reserve(Some(1)).is_err());
1906 drop(first);
1907 assert_eq!(limiter.used_bytes(), 0);
1908 }
1909
1910 #[test]
1911 fn test_schema_widening_reserves_null_padding() {
1912 let mut request = request_with_sample(vec![(METRIC_NAME_LABEL, "metric")]);
1913 let series = request.timeseries.pop().unwrap();
1914 for index in 0..200 {
1915 let mut series = series.clone();
1916 series.labels_refs.extend([request.symbols.len() as u32, 0]);
1917 request.symbols.push(format!("label_{index}"));
1918 request.timeseries.push(series);
1919 }
1920 let body = request.encode_to_vec();
1921 let limiter = ServerMemoryLimiter::default();
1922 let budget = ConversionBudget::new(&limiter, 0).unwrap();
1923 let decoded =
1924 convert_remote_write_v2(BorrowedRequest::decode(&body).unwrap(), budget).unwrap();
1925 let minimum = 200 * 202 * std::mem::size_of::<Value>();
1926 assert!(limiter.used_bytes() >= minimum as u64);
1927 let rows = decoded.samples.all_req().next().unwrap().rows.unwrap();
1928 assert_eq!(rows.schema.len(), 202);
1929 assert!(rows.rows.iter().all(|row| row.values.len() == 202));
1930 drop(decoded.memory_guard);
1931 assert_eq!(limiter.used_bytes(), 0);
1932
1933 let limiter = ServerMemoryLimiter::new(
1934 minimum as u64 / 2,
1935 common_memory_manager::OnExhaustedPolicy::Fail,
1936 );
1937 let budget = ConversionBudget::new(&limiter, 0).unwrap();
1938 assert!(convert_remote_write_v2(BorrowedRequest::decode(&body).unwrap(), budget).is_err());
1939 assert_eq!(limiter.used_bytes(), 0);
1940 }
1941
1942 #[test]
1943 fn test_utf8_labels_and_histogram_row_templates() {
1944 let labels = vec![
1945 (METRIC_NAME_LABEL, "metric"),
1946 ("service.name", "api"),
1947 ("区域", "西"),
1948 ("has-dash", "value"),
1949 ];
1950 let mut sample = request_with_sample(labels.clone());
1951 let name = push_test_symbol(&mut sample.symbols, "trace.id");
1952 let value = push_test_symbol(&mut sample.symbols, "abc");
1953 sample.timeseries[0].exemplars = vec![Exemplar {
1954 labels_refs: vec![name, value],
1955 ..Default::default()
1956 }];
1957 let decoded = decode_test_request(sample.clone()).unwrap();
1958 assert_eq!(decoded.exemplar_count, 1);
1959 assert_eq!(decoded.exemplars[&None][0].series_labels["区域"], "西");
1960 assert_eq!(decoded.exemplars[&None][0].labels["trace.id"], "abc");
1961 let (requests, _, _, _) = test_util::write_requests(sample).unwrap();
1962 let sample_rows = requests[0].rows.as_ref().unwrap();
1963 assert_eq!(sample_rows.schema[3].column_name, "区域");
1964 assert_eq!(
1965 sample_rows.rows[0].values[3].value_data,
1966 Some(ValueData::StringValue("西".into()))
1967 );
1968
1969 let mut request = request_with_sample(labels);
1970 request.timeseries[0].samples.clear();
1971 request.timeseries[0].histograms = vec![
1972 Histogram {
1973 timestamp: i64::MIN,
1974 sum: 1.0,
1975 ..Default::default()
1976 },
1977 Histogram {
1978 timestamp: i64::MAX,
1979 sum: 2.0,
1980 ..Default::default()
1981 },
1982 ];
1983 let (_, requests, _, count) = test_util::write_requests(request.clone()).unwrap();
1984 assert_eq!(count, 2);
1985 let rows = requests[0].rows.as_ref().unwrap();
1986 for (index, timestamp) in [i64::MIN, i64::MAX].into_iter().enumerate() {
1987 assert_eq!(
1988 rows.rows[index].values[0].value_data,
1989 Some(ValueData::TimestampMillisecondValue(timestamp))
1990 );
1991 assert_eq!(
1992 rows.rows[index].values[1].value_data,
1993 Some(encode_native_histogram(&request.timeseries[0].histograms[index]).unwrap())
1994 );
1995 assert_eq!(
1996 rows.rows[index].values[3].value_data,
1997 Some(ValueData::StringValue("西".into()))
1998 );
1999 }
2000 request.timeseries[0].histograms[1].schema = 99;
2001 assert!(test_util::write_requests(request).is_err());
2002 }
2003
2004 #[test]
2005 fn test_into_context_req_rejects_invalid_requests() {
2006 let mut cases = Vec::new();
2007
2008 cases.push((
2009 "missing metric name",
2010 request_with_sample(vec![("job", "api")]),
2011 "missing '__name__'",
2012 ));
2013
2014 let mut request = request_with_sample(vec![(METRIC_NAME_LABEL, "metric")]);
2015 request.timeseries[0].labels_refs.push(1);
2016 cases.push((
2017 "odd label refs",
2018 request,
2019 "labels_refs must contain name/value pairs",
2020 ));
2021
2022 let mut request = request_with_sample(vec![(METRIC_NAME_LABEL, "metric")]);
2023 request.timeseries[0].labels_refs[1] = 99;
2024 cases.push((
2025 "out of range symbol ref",
2026 request,
2027 "symbol reference 99 is out of range",
2028 ));
2029
2030 let mut request = request_with_sample(vec![(METRIC_NAME_LABEL, "metric")]);
2031 request.symbols[0] = "not-empty".to_string();
2032 cases.push((
2033 "non-empty first symbol",
2034 request,
2035 "symbols must start with an empty string",
2036 ));
2037
2038 cases.push((
2039 "repeated label name",
2040 request_with_sample(vec![
2041 (METRIC_NAME_LABEL, "metric"),
2042 ("job", "api"),
2043 ("job", "worker"),
2044 ]),
2045 "label name `job` is repeated",
2046 ));
2047
2048 cases.push((
2049 "empty label name",
2050 request_with_sample(vec![(METRIC_NAME_LABEL, "metric"), ("", "api")]),
2051 "invalid label name",
2052 ));
2053
2054 cases.push((
2055 "empty metric name",
2056 request_with_sample(vec![(METRIC_NAME_LABEL, "")]),
2057 "label `__name__` value must not be empty",
2058 ));
2059
2060 cases.push((
2061 "internal histogram label on samples",
2062 request_with_sample(vec![
2063 (METRIC_NAME_LABEL, "metric"),
2064 (greptime_native_histogram(), "user_value"),
2065 ]),
2066 "conflicts with an internal native histogram label",
2067 ));
2068
2069 cases.push((
2070 "int count with float zero count",
2071 request_with_histogram(Histogram {
2072 count: Some(Count::CountInt(1)),
2073 zero_count: Some(ZeroCount::ZeroCountFloat(0.5)),
2074 ..Default::default()
2075 }),
2076 "count and zero_count must use the same integer or float family",
2077 ));
2078
2079 cases.push((
2080 "float count with int zero count",
2081 request_with_histogram(Histogram {
2082 count: Some(Count::CountFloat(1.0)),
2083 zero_count: Some(ZeroCount::ZeroCountInt(1)),
2084 ..Default::default()
2085 }),
2086 "count and zero_count must use the same integer or float family",
2087 ));
2088
2089 cases.push((
2090 "reducible schema",
2091 request_with_histogram(Histogram {
2092 schema: 9,
2093 ..Default::default()
2094 }),
2095 "schema 9 must be reduced before ingestion",
2096 ));
2097
2098 cases.push((
2099 "unsupported schema",
2100 request_with_histogram(Histogram {
2101 schema: 53,
2102 ..Default::default()
2103 }),
2104 "schema 53 is unsupported",
2105 ));
2106
2107 cases.push((
2108 "standard schema with custom values",
2109 request_with_histogram(Histogram {
2110 schema: 1,
2111 custom_values: vec![1.0],
2112 ..Default::default()
2113 }),
2114 "standard native histogram must not use custom_values",
2115 ));
2116
2117 cases.push((
2118 "custom values with inf",
2119 request_with_histogram(Histogram {
2120 schema: CUSTOM_BUCKETS_SCHEMA,
2121 custom_values: vec![f64::INFINITY],
2122 ..Default::default()
2123 }),
2124 "custom_values must not contain +Inf or NaN",
2125 ));
2126
2127 cases.push((
2128 "custom values not sorted",
2129 request_with_histogram(Histogram {
2130 schema: CUSTOM_BUCKETS_SCHEMA,
2131 custom_values: vec![2.0, 1.0],
2132 ..Default::default()
2133 }),
2134 "custom_values must be sorted",
2135 ));
2136
2137 cases.push((
2138 "custom schema with zero bucket",
2139 request_with_histogram(Histogram {
2140 schema: CUSTOM_BUCKETS_SCHEMA,
2141 zero_count: Some(ZeroCount::ZeroCountInt(1)),
2142 ..Default::default()
2143 }),
2144 "custom native histogram must not use a zero bucket",
2145 ));
2146
2147 cases.push((
2148 "custom schema with negative buckets",
2149 request_with_histogram(Histogram {
2150 schema: CUSTOM_BUCKETS_SCHEMA,
2151 negative_spans: vec![BucketSpan {
2152 offset: -1,
2153 length: 1,
2154 }],
2155 negative_deltas: vec![1],
2156 ..Default::default()
2157 }),
2158 "custom native histogram must not use negative buckets",
2159 ));
2160
2161 cases.push((
2162 "span count mismatch",
2163 request_with_histogram(Histogram {
2164 positive_spans: vec![BucketSpan {
2165 offset: 0,
2166 length: 2,
2167 }],
2168 positive_deltas: vec![1],
2169 ..Default::default()
2170 }),
2171 "positive spans describe 2 buckets, found 1",
2172 ));
2173
2174 cases.push((
2175 "negative offset after first span",
2176 request_with_histogram(Histogram {
2177 count: Some(Count::CountInt(2)),
2178 positive_spans: vec![
2179 BucketSpan {
2180 offset: 0,
2181 length: 1,
2182 },
2183 BucketSpan {
2184 offset: -1,
2185 length: 1,
2186 },
2187 ],
2188 positive_deltas: vec![1, 0],
2189 ..Default::default()
2190 }),
2191 "positive span 2 has negative offset -1",
2192 ));
2193
2194 cases.push((
2195 "negative custom span offset",
2196 request_with_histogram(Histogram {
2197 count: Some(Count::CountInt(1)),
2198 schema: CUSTOM_BUCKETS_SCHEMA,
2199 custom_values: vec![1.0],
2200 positive_spans: vec![BucketSpan {
2201 offset: -1,
2202 length: 1,
2203 }],
2204 positive_deltas: vec![1],
2205 ..Default::default()
2206 }),
2207 "positive span 1 has negative offset -1",
2208 ));
2209
2210 cases.push((
2211 "integer bucket total mismatch",
2212 request_with_histogram(Histogram {
2213 count: Some(Count::CountInt(0)),
2214 positive_spans: vec![BucketSpan {
2215 offset: 0,
2216 length: 1,
2217 }],
2218 positive_deltas: vec![1],
2219 ..Default::default()
2220 }),
2221 "has 1 observations in buckets, count is 0",
2222 ));
2223
2224 cases.push((
2225 "negative float count",
2226 request_with_histogram(Histogram {
2227 count: Some(Count::CountFloat(-1.0)),
2228 ..Default::default()
2229 }),
2230 "float count must not be negative",
2231 ));
2232
2233 cases.push((
2234 "negative float zero count",
2235 request_with_histogram(Histogram {
2236 count: Some(Count::CountFloat(0.0)),
2237 zero_count: Some(ZeroCount::ZeroCountFloat(-1.0)),
2238 ..Default::default()
2239 }),
2240 "float zero_count must not be negative",
2241 ));
2242
2243 cases.push((
2244 "negative float bucket count",
2245 request_with_histogram(Histogram {
2246 count: Some(Count::CountFloat(0.0)),
2247 positive_spans: vec![BucketSpan {
2248 offset: 0,
2249 length: 1,
2250 }],
2251 positive_counts: vec![-1.0],
2252 ..Default::default()
2253 }),
2254 "positive bucket 1 count must not be negative",
2255 ));
2256
2257 cases.push((
2258 "custom span index out of range",
2259 request_with_histogram(Histogram {
2260 schema: CUSTOM_BUCKETS_SCHEMA,
2261 custom_values: vec![1.0],
2262 positive_spans: vec![BucketSpan {
2263 offset: 2,
2264 length: 1,
2265 }],
2266 positive_deltas: vec![1],
2267 ..Default::default()
2268 }),
2269 "positive bucket index 2 is out of range",
2270 ));
2271
2272 for (name, request, expected) in cases {
2273 assert_invalid(name, request, expected);
2274 }
2275 }
2276
2277 #[test]
2278 fn test_into_context_req_allows_nan_observations_outside_buckets() {
2279 decode_test_request(request_with_histogram(Histogram {
2280 count: Some(Count::CountInt(2)),
2281 sum: f64::NAN,
2282 positive_spans: vec![BucketSpan {
2283 offset: 0,
2284 length: 1,
2285 }],
2286 positive_deltas: vec![1],
2287 ..Default::default()
2288 }))
2289 .unwrap();
2290 }
2291
2292 #[test]
2293 fn test_into_context_req_allows_empty_label_values() {
2294 let ctx_req = decode_test_request(test_util::request_with_labels_and_samples(
2295 vec![(METRIC_NAME_LABEL, "metric"), ("job", "")],
2296 vec![Sample {
2297 value: 1.0,
2298 timestamp: 1000,
2299 start_timestamp: 0,
2300 }],
2301 ))
2302 .unwrap();
2303
2304 let rows = ctx_req.samples.all_req().next().unwrap().rows.unwrap();
2305 let job_idx = column_index(&rows.schema, "job");
2306 assert_eq!(
2307 rows.rows[0].values[job_idx].value_data,
2308 Some(ValueData::StringValue(String::new()))
2309 );
2310 }
2311
2312 #[test]
2313 fn test_into_context_req_rejects_same_metric_samples_and_histograms() {
2314 let mut request = test_util::request_with_labels_and_samples(
2315 vec![(METRIC_NAME_LABEL, "metric")],
2316 vec![Sample {
2317 value: 1.0,
2318 timestamp: 1000,
2319 start_timestamp: 0,
2320 }],
2321 );
2322 request.timeseries[0].histograms.push(Histogram::default());
2323
2324 assert_invalid(
2325 "same metric samples and histograms",
2326 request,
2327 "contains both samples and native histograms",
2328 );
2329
2330 let mut request = test_util::request_with_labels_and_samples(
2331 vec![(METRIC_NAME_LABEL, "metric")],
2332 vec![Sample {
2333 value: 1.0,
2334 timestamp: 1000,
2335 start_timestamp: 0,
2336 }],
2337 );
2338 request.timeseries.push(TimeSeries {
2339 labels_refs: request.timeseries[0].labels_refs.clone(),
2340 histograms: vec![Histogram::default()],
2341 ..Default::default()
2342 });
2343
2344 assert_invalid(
2345 "same metric samples and histograms across series",
2346 request,
2347 "contains both samples and native histograms",
2348 );
2349 }
2350
2351 #[test]
2352 fn test_into_context_req_rejects_metric_kind_conflict_across_label_sets() {
2353 let request = Request {
2354 symbols: vec![
2355 "".to_string(),
2356 METRIC_NAME_LABEL.to_string(),
2357 "metric".to_string(),
2358 "job".to_string(),
2359 "api".to_string(),
2360 "worker".to_string(),
2361 ],
2362 timeseries: vec![
2363 TimeSeries {
2364 labels_refs: vec![1, 2, 3, 4],
2365 samples: vec![Sample {
2366 value: 1.0,
2367 timestamp: 1000,
2368 start_timestamp: 0,
2369 }],
2370 ..Default::default()
2371 },
2372 TimeSeries {
2373 labels_refs: vec![1, 2, 3, 5],
2374 histograms: vec![Histogram::default()],
2375 ..Default::default()
2376 },
2377 ],
2378 };
2379
2380 assert_invalid(
2381 "same metric kind conflict across label sets",
2382 request,
2383 "contains both samples and native histograms",
2384 );
2385 }
2386
2387 #[test]
2388 fn test_into_context_req_validates_exponential_overflow_bucket_index() {
2389 for schema in [-4, 0, 8] {
2390 let max_index = exponential_overflow_bucket_index(schema).unwrap();
2391 for positive in [true, false] {
2392 let mut histogram = Histogram {
2393 schema,
2394 count: Some(Count::CountInt(1)),
2395 ..Default::default()
2396 };
2397 if positive {
2398 histogram.positive_spans = vec![BucketSpan {
2399 offset: max_index,
2400 length: 1,
2401 }];
2402 histogram.positive_deltas = vec![1];
2403 } else {
2404 histogram.negative_spans = vec![BucketSpan {
2405 offset: max_index,
2406 length: 1,
2407 }];
2408 histogram.negative_deltas = vec![1];
2409 }
2410 decode_test_request(request_with_histogram(histogram.clone())).unwrap();
2411
2412 let beyond = max_index + 1;
2413 if positive {
2414 histogram.positive_spans[0].offset = beyond;
2415 } else {
2416 histogram.negative_spans[0].offset = beyond;
2417 }
2418 assert_invalid(
2419 "exponential overflow bucket index",
2420 request_with_histogram(histogram),
2421 &format!("bucket index {beyond} is out of range"),
2422 );
2423 }
2424 }
2425 }
2426
2427 #[test]
2428 fn test_into_context_req_preserves_histogram_exemplars() {
2429 let request = Request {
2430 symbols: vec![
2431 "".to_string(),
2432 METRIC_NAME_LABEL.to_string(),
2433 "sample_metric".to_string(),
2434 "histogram_metric".to_string(),
2435 ],
2436 timeseries: vec![
2437 TimeSeries {
2438 labels_refs: vec![1, 2],
2439 samples: vec![Sample {
2440 value: 1.0,
2441 timestamp: 1000,
2442 start_timestamp: 0,
2443 }],
2444 ..Default::default()
2445 },
2446 TimeSeries {
2447 labels_refs: vec![1, 3],
2448 histograms: vec![Histogram::default()],
2449 exemplars: vec![Exemplar::default()],
2450 ..Default::default()
2451 },
2452 ],
2453 };
2454
2455 let ctx_req = decode_test_request(request).unwrap();
2456
2457 assert_eq!(ctx_req.sample_count, 1);
2458 assert_eq!(ctx_req.histogram_count, 1);
2459 assert_eq!(ctx_req.exemplar_count, 1);
2460 assert_eq!(
2461 ctx_req.exemplars[&None][0].metric.as_ref(),
2462 "histogram_metric"
2463 );
2464 assert_eq!(ctx_req.samples.all_req().count(), 1);
2465 assert_eq!(ctx_req.histograms.all_req().count(), 1);
2466 }
2467
2468 #[test]
2469 fn test_into_context_req_converts_histogram_only_series() {
2470 let mut request =
2471 test_util::request_with_labels_and_samples(vec![(METRIC_NAME_LABEL, "metric")], vec![]);
2472 request.timeseries[0].histograms.push(Histogram::default());
2473
2474 let ctx_req = decode_test_request(request).unwrap();
2475
2476 assert_eq!(ctx_req.sample_count, 0);
2477 assert_eq!(ctx_req.histogram_count, 1);
2478 assert_eq!(ctx_req.samples.all_req().count(), 0);
2479 let mut inserts = ctx_req.histograms.all_req().collect::<Vec<_>>();
2480 assert_eq!(inserts.len(), 1);
2481
2482 let request = inserts.pop().unwrap();
2483 assert_eq!(request.table_name, "metric");
2484 let rows = request.rows.unwrap();
2485 assert_eq!(rows.rows.len(), 1);
2486 assert_eq!(
2487 rows.schema
2488 .iter()
2489 .map(|col| col.column_name.as_str())
2490 .collect::<Vec<_>>(),
2491 vec![greptime_timestamp(), greptime_native_histogram()]
2492 );
2493 assert_eq!(
2494 rows.rows[0].values[0].value_data,
2495 Some(ValueData::TimestampMillisecondValue(0))
2496 );
2497 assert_eq!(
2498 histogram_field_value(&rows, 0, SCHEMA_FIELD),
2499 Some(ValueData::I32Value(0))
2500 );
2501 assert_eq!(
2502 histogram_field_value(&rows, 0, COUNT_I64_FIELD),
2503 Some(ValueData::I64Value(0))
2504 );
2505 assert_eq!(histogram_field_value(&rows, 0, COUNT_F64_FIELD), None);
2506 }
2507
2508 #[test]
2509 fn test_into_context_req_preserves_histogram_start_timestamp() {
2510 let ctx_req = decode_test_request(test_util::request_with_labels_and_histograms(
2511 vec![(METRIC_NAME_LABEL, "metric")],
2512 vec![Histogram {
2513 timestamp: 2000,
2514 start_timestamp: 1000,
2515 ..Default::default()
2516 }],
2517 ))
2518 .unwrap();
2519
2520 let mut inserts = ctx_req.histograms.all_req().collect::<Vec<_>>();
2521 let rows = inserts.pop().unwrap().rows.unwrap();
2522
2523 assert_eq!(
2524 histogram_field_value(&rows, 0, START_TIMESTAMP_FIELD),
2525 Some(ValueData::TimestampMillisecondValue(1000))
2526 );
2527 }
2528
2529 #[test]
2530 fn test_into_context_req_preserves_exponential_zero_threshold() {
2531 for zero_threshold in [-1.0, f64::NAN] {
2532 let ctx_req = decode_test_request(request_with_histogram(Histogram {
2533 zero_threshold,
2534 ..Default::default()
2535 }))
2536 .unwrap();
2537 let rows = ctx_req.histograms.all_req().next().unwrap().rows.unwrap();
2538 let Some(ValueData::F64Value(actual)) =
2539 histogram_field_value(&rows, 0, ZERO_THRESHOLD_FIELD)
2540 else {
2541 panic!("expected zero threshold");
2542 };
2543
2544 assert_eq!(zero_threshold.to_bits(), actual.to_bits());
2545 }
2546 }
2547
2548 #[test]
2549 fn test_into_context_req_rejects_internal_histogram_labels() {
2550 let mut request = test_util::request_with_labels_and_samples(
2551 vec![
2552 (METRIC_NAME_LABEL, "metric"),
2553 (greptime_native_histogram(), "user_value"),
2554 ],
2555 vec![],
2556 );
2557 request.timeseries[0].histograms.push(Histogram::default());
2558
2559 let err = match decode_test_request(request) {
2560 Ok(_) => panic!("expected invalid request error"),
2561 Err(err) => err,
2562 };
2563 assert_eq!(
2564 err.to_string(),
2565 "Invalid prometheus remote request, msg: remote write v2 label `greptime_native_histogram` conflicts with an internal native histogram label"
2566 );
2567 }
2568
2569 #[test]
2570 fn test_rejects_legacy_histogram_label_after_prefix_change() {
2571 set_default_prefix(Some("custom")).unwrap();
2572 assert_eq!(greptime_native_histogram(), "custom_native_histogram");
2573
2574 let err = ensure_no_internal_histogram_labels(&vec![(
2575 NATIVE_HISTOGRAM_FIELD,
2576 "user_value".to_string(),
2577 )])
2578 .unwrap_err();
2579 assert!(
2580 err.to_string()
2581 .contains("conflicts with an internal native histogram label")
2582 );
2583 }
2584
2585 #[test]
2586 fn test_into_context_req_converts_int_and_float_histograms_to_one_schema() {
2587 let float_histogram = Histogram {
2588 count: Some(api::greptime_proto::io::prometheus::write::v2::histogram::Count::CountFloat(6.0)),
2589 zero_count: Some(
2590 api::greptime_proto::io::prometheus::write::v2::histogram::ZeroCount::ZeroCountFloat(
2591 0.5,
2592 ),
2593 ),
2594 positive_counts: vec![2.0, 3.5],
2595 positive_spans: vec![api::greptime_proto::io::prometheus::write::v2::BucketSpan {
2596 offset: 3,
2597 length: 2,
2598 }],
2599 timestamp: 2000,
2600 ..Default::default()
2601 };
2602 let request = Request {
2603 symbols: vec![
2604 "".to_string(),
2605 METRIC_NAME_LABEL.to_string(),
2606 "metric".to_string(),
2607 ],
2608 timeseries: vec![
2609 TimeSeries {
2610 labels_refs: vec![1, 2],
2611 histograms: vec![test_util::histogram(1000)],
2612 ..Default::default()
2613 },
2614 TimeSeries {
2615 labels_refs: vec![1, 2],
2616 histograms: vec![float_histogram],
2617 ..Default::default()
2618 },
2619 ],
2620 };
2621
2622 let ctx_req = decode_test_request(request).unwrap();
2623
2624 assert_eq!(ctx_req.histogram_count, 2);
2625 let mut inserts = ctx_req.histograms.all_req().collect::<Vec<_>>();
2626 assert_eq!(inserts.len(), 1);
2627 let rows = inserts.pop().unwrap().rows.unwrap();
2628 assert_eq!(rows.rows.len(), 2);
2629 assert_eq!(
2630 rows.schema
2631 .iter()
2632 .map(|col| col.column_name.as_str())
2633 .collect::<Vec<_>>(),
2634 vec![greptime_timestamp(), greptime_native_histogram()]
2635 );
2636
2637 assert_eq!(
2638 histogram_field_value(&rows, 0, COUNT_I64_FIELD),
2639 Some(ValueData::I64Value(0))
2640 );
2641 assert_eq!(histogram_field_value(&rows, 0, COUNT_F64_FIELD), None);
2642 assert!(matches!(
2643 histogram_field_value(&rows, 0, POSITIVE_BUCKETS_I64_FIELD),
2644 Some(ValueData::ListValue(_))
2645 ));
2646 assert!(is_empty_list(histogram_field_value(
2647 &rows,
2648 0,
2649 POSITIVE_BUCKETS_F64_FIELD
2650 )));
2651
2652 assert_eq!(histogram_field_value(&rows, 1, COUNT_I64_FIELD), None);
2653 assert_eq!(
2654 histogram_field_value(&rows, 1, COUNT_F64_FIELD),
2655 Some(ValueData::F64Value(6.0))
2656 );
2657 assert!(is_empty_list(histogram_field_value(
2658 &rows,
2659 1,
2660 POSITIVE_BUCKETS_I64_FIELD
2661 )));
2662 assert!(matches!(
2663 histogram_field_value(&rows, 1, POSITIVE_BUCKETS_F64_FIELD),
2664 Some(ValueData::ListValue(_))
2665 ));
2666 }
2667
2668 fn decode_wire(wire: &[u8]) -> Result<RemoteWriteV2WriteRequests> {
2669 let body = Bytes::from(crate::prom_store::snappy_compress(wire).unwrap());
2670 decode_v2_on_test_runtime(false, body)
2671 }
2672
2673 fn decode_v2_on_test_runtime(is_zstd: bool, body: Bytes) -> Result<RemoteWriteV2WriteRequests> {
2676 tokio::runtime::Builder::new_current_thread()
2677 .enable_all()
2678 .build()
2679 .unwrap()
2680 .block_on(decode_remote_write_v2(
2681 is_zstd,
2682 body,
2683 &ServerMemoryLimiter::default(),
2684 0,
2685 ))
2686 }
2687
2688 fn decode_wire_error(wire: &[u8], name: &str) -> error::Error {
2689 match decode_wire(wire) {
2690 Ok(_) => panic!("{name}: expected decoder error"),
2691 Err(error) => error,
2692 }
2693 }
2694
2695 fn request_wire(symbols: &[&str], series: &[Vec<u8>]) -> Vec<u8> {
2696 let mut wire = Vec::new();
2697 for symbol in symbols {
2698 wire.extend(string_field(4, symbol.as_bytes()));
2699 }
2700 for series in series {
2701 wire.extend(encoded_message_field(5, series));
2702 }
2703 wire
2704 }
2705
2706 fn series_wire(labels_refs: &[u32], leaf_tag: u32, leaf: &[u8]) -> Vec<u8> {
2707 let mut series = packed_u32_field(1, labels_refs);
2708 series.extend(encoded_message_field(leaf_tag, leaf));
2709 series
2710 }
2711
2712 fn string_field(tag: u32, value: &[u8]) -> Vec<u8> {
2713 encoded_message_field(tag, value)
2714 }
2715
2716 fn encoded_message_field(tag: u32, value: &[u8]) -> Vec<u8> {
2717 let mut field = Vec::new();
2718 prost::encoding::encode_key(tag, WireType::LengthDelimited, &mut field);
2719 prost::encoding::encode_varint(u64::try_from(value.len()).unwrap(), &mut field);
2720 field.extend_from_slice(value);
2721 field
2722 }
2723
2724 fn varint_field(tag: u32, value: u64) -> Vec<u8> {
2725 let mut field = Vec::new();
2726 prost::encoding::encode_key(tag, WireType::Varint, &mut field);
2727 prost::encoding::encode_varint(value, &mut field);
2728 field
2729 }
2730
2731 fn packed_u32_field(tag: u32, values: &[u32]) -> Vec<u8> {
2732 let mut packed = Vec::new();
2733 for value in values {
2734 prost::encoding::encode_varint(u64::from(*value), &mut packed);
2735 }
2736 encoded_message_field(tag, &packed)
2737 }
2738
2739 fn request_with_sample(labels: Vec<(&str, &str)>) -> Request {
2740 test_util::request_with_labels_and_samples(
2741 labels,
2742 vec![Sample {
2743 value: 1.0,
2744 timestamp: 1000,
2745 start_timestamp: 0,
2746 }],
2747 )
2748 }
2749
2750 fn request_with_histogram(histogram: Histogram) -> Request {
2751 test_util::request_with_labels_and_histograms(
2752 vec![(METRIC_NAME_LABEL, "metric")],
2753 vec![histogram],
2754 )
2755 }
2756
2757 fn decode_test_request(request: Request) -> Result<RemoteWriteV2WriteRequests> {
2758 let body =
2759 Bytes::from(crate::prom_store::snappy_compress(&request.encode_to_vec()).unwrap());
2760 decode_v2_on_test_runtime(false, body)
2761 }
2762
2763 fn assert_invalid(name: &str, request: Request, expected: &str) {
2764 let err = match decode_test_request(request) {
2765 Ok(_) => panic!("{name}: expected invalid request error"),
2766 Err(err) => err,
2767 };
2768 assert!(
2769 matches!(err, error::Error::InvalidPromRemoteRequest { .. }),
2770 "{name}: expected invalid request error, got {err}"
2771 );
2772 assert!(
2773 err.to_string().contains(expected),
2774 "{name}: expected error containing {expected:?}, got {err}"
2775 );
2776 }
2777
2778 fn column_index(schema: &[ColumnSchema], column_name: &str) -> usize {
2779 schema
2780 .iter()
2781 .position(|column| column.column_name == column_name)
2782 .unwrap()
2783 }
2784
2785 fn histogram_field_value(rows: &Rows, row_idx: usize, field_name: &str) -> Option<ValueData> {
2786 let histogram_idx = column_index(&rows.schema, greptime_native_histogram());
2787 let Some(ValueData::StructValue(histogram)) =
2788 &rows.rows[row_idx].values[histogram_idx].value_data
2789 else {
2790 panic!("expected native histogram struct value");
2791 };
2792 let field_idx = NATIVE_HISTOGRAM_FIELD_NAMES
2793 .iter()
2794 .position(|name| *name == field_name)
2795 .unwrap();
2796 histogram.items[field_idx].value_data.clone()
2797 }
2798
2799 fn is_empty_list(value: Option<ValueData>) -> bool {
2800 matches!(value, Some(ValueData::ListValue(list)) if list.items.is_empty())
2801 }
2802
2803 fn push_test_symbol(symbols: &mut Vec<String>, symbol: &str) -> u32 {
2804 if let Some(idx) = symbols.iter().position(|s| s == symbol) {
2805 return idx as u32;
2806 }
2807 let idx = symbols.len();
2808 symbols.push(symbol.to_string());
2809 idx as u32
2810 }
2811
2812 type MetadataSeries<'a> = (Vec<(&'a str, &'a str)>, i32, Option<&'a str>);
2814
2815 fn metadata_request(series: Vec<MetadataSeries<'_>>) -> Request {
2816 let mut symbols = vec!["".to_string()];
2817 let timeseries = series
2818 .into_iter()
2819 .map(|(labels, metric_type, unit)| {
2820 let mut labels_refs = Vec::with_capacity(labels.len() * 2);
2821 for (name, value) in labels {
2822 labels_refs.push(push_test_symbol(&mut symbols, name));
2823 labels_refs.push(push_test_symbol(&mut symbols, value));
2824 }
2825 let unit_ref = unit.map_or(0, |unit| push_test_symbol(&mut symbols, unit));
2826 TimeSeries {
2827 labels_refs,
2828 samples: vec![Sample {
2829 value: 1.0,
2830 timestamp: 1000,
2831 start_timestamp: 0,
2832 }],
2833 histograms: Vec::new(),
2834 exemplars: Vec::new(),
2835 metadata: Some(Metadata {
2836 r#type: metric_type,
2837 help_ref: 0,
2838 unit_ref,
2839 }),
2840 }
2841 })
2842 .collect();
2843 Request {
2844 symbols,
2845 timeseries,
2846 }
2847 }
2848
2849 type DecodedIndex = std::collections::BTreeMap<
2850 String,
2851 std::collections::BTreeMap<String, std::collections::BTreeMap<String, String>>,
2852 >;
2853
2854 fn decoded_index(request: Request) -> DecodedIndex {
2855 let encoded = decode_test_request(request)
2856 .unwrap()
2857 .semantic_index
2858 .encode("public")
2859 .expect("non-empty semantic index");
2860 serde_json::from_str(&encoded).unwrap()
2861 }
2862
2863 #[test]
2864 fn test_metadata_stamps_semantic_index() {
2865 use table::requests::METADATA_QUALITY_DECLARED;
2866
2867 let index = decoded_index(metadata_request(vec![
2868 (
2869 vec![(METRIC_NAME_LABEL, "http_requests_total")],
2870 metadata::MetricType::Counter as i32,
2871 Some("seconds"),
2872 ),
2873 (
2874 vec![(METRIC_NAME_LABEL, "queue_depth")],
2875 metadata::MetricType::Gauge as i32,
2876 Some("requests"),
2878 ),
2879 ]));
2880
2881 let tables = &index["public"];
2882 let typed = &tables["http_requests_total"];
2883 assert_eq!(typed[SEMANTIC_METRIC_TYPE], "counter");
2884 assert_eq!(
2885 typed[SEMANTIC_METRIC_METADATA_QUALITY],
2886 METADATA_QUALITY_DECLARED
2887 );
2888 assert_eq!(typed[SEMANTIC_METRIC_UNIT], "s");
2889
2890 let unitless = &tables["queue_depth"];
2891 assert_eq!(unitless[SEMANTIC_METRIC_TYPE], "gauge");
2892 assert!(!unitless.contains_key(SEMANTIC_METRIC_UNIT));
2893 }
2894
2895 #[test]
2896 fn test_metadata_unspecified_stamps_unit_but_not_type() {
2897 let index = decoded_index(metadata_request(vec![(
2898 vec![(METRIC_NAME_LABEL, "untyped_total")],
2899 metadata::MetricType::Unspecified as i32,
2900 Some("seconds"),
2901 )]));
2902 let untyped = &index["public"]["untyped_total"];
2903 assert_eq!(untyped[SEMANTIC_METRIC_UNIT], "s");
2904 assert!(!untyped.contains_key(SEMANTIC_METRIC_TYPE));
2905 assert!(!untyped.contains_key(SEMANTIC_METRIC_METADATA_QUALITY));
2906
2907 let requests = decode_test_request(metadata_request(vec![(
2908 vec![(METRIC_NAME_LABEL, "untyped_unitless_total")],
2909 metadata::MetricType::Unspecified as i32,
2910 None,
2911 )]))
2912 .unwrap();
2913 assert!(requests.semantic_index.is_empty());
2914
2915 let requests = decode_test_request(test_util::request_with_labels_and_samples(
2917 vec![(METRIC_NAME_LABEL, "bare_total")],
2918 vec![Sample {
2919 value: 1.0,
2920 timestamp: 1000,
2921 start_timestamp: 0,
2922 }],
2923 ))
2924 .unwrap();
2925 assert!(requests.semantic_index.is_empty());
2926 }
2927
2928 #[test]
2929 fn test_metadata_type_conflict_collapses_to_mixed() {
2930 let index = decoded_index(metadata_request(vec![
2931 (
2932 vec![(METRIC_NAME_LABEL, "flappy_metric"), ("job", "a")],
2933 metadata::MetricType::Counter as i32,
2934 None,
2935 ),
2936 (
2937 vec![(METRIC_NAME_LABEL, "flappy_metric"), ("job", "b")],
2938 metadata::MetricType::Gauge as i32,
2939 None,
2940 ),
2941 ]));
2942 assert_eq!(
2943 index["public"]["flappy_metric"][SEMANTIC_METRIC_TYPE],
2944 "mixed"
2945 );
2946 }
2947
2948 #[test]
2949 fn test_metadata_schema_overrides_stay_apart() {
2950 let index = decoded_index(metadata_request(vec![
2953 (
2954 vec![(METRIC_NAME_LABEL, "cpu_usage")],
2955 metadata::MetricType::Counter as i32,
2956 None,
2957 ),
2958 (
2959 vec![
2960 (METRIC_NAME_LABEL, "cpu_usage"),
2961 (DATABASE_LABEL, "tenant_b"),
2962 ],
2963 metadata::MetricType::Gauge as i32,
2964 None,
2965 ),
2966 ]));
2967 assert_eq!(
2968 index["public"]["cpu_usage"][SEMANTIC_METRIC_TYPE],
2969 "counter"
2970 );
2971 assert_eq!(
2972 index["tenant_b"]["cpu_usage"][SEMANTIC_METRIC_TYPE],
2973 "gauge"
2974 );
2975 }
2976
2977 #[test]
2978 fn test_metadata_out_of_range_refs_are_rejected() {
2979 let mut request = metadata_request(vec![(
2980 vec![(METRIC_NAME_LABEL, "broken_total")],
2981 metadata::MetricType::Counter as i32,
2982 None,
2983 )]);
2984 request.timeseries[0].metadata.as_mut().unwrap().unit_ref = 999;
2985 let err = decode_test_request(request).err().unwrap();
2986 assert!(err.to_string().contains("out of range"), "{err}");
2987
2988 let mut request = metadata_request(vec![(
2991 vec![(METRIC_NAME_LABEL, "broken_total")],
2992 metadata::MetricType::Unspecified as i32,
2993 None,
2994 )]);
2995 request.timeseries[0].metadata.as_mut().unwrap().unit_ref = 999;
2996 let err = decode_test_request(request).err().unwrap();
2997 assert!(err.to_string().contains("out of range"), "{err}");
2998
2999 let mut request = metadata_request(vec![(
3001 vec![(METRIC_NAME_LABEL, "broken_total")],
3002 metadata::MetricType::Counter as i32,
3003 None,
3004 )]);
3005 request.timeseries[0].metadata.as_mut().unwrap().help_ref = 999;
3006 let err = decode_test_request(request).err().unwrap();
3007 assert!(err.to_string().contains("out of range"), "{err}");
3008 }
3009}