1use std::cmp::Ordering;
18use std::collections::HashMap;
19use std::collections::hash_map::DefaultHasher;
20use std::hash::{Hash, Hasher};
21use std::sync::Arc;
22
23use api::prom_store::remote::label_matcher::Type as MatcherType;
24use api::prom_store::remote::{Label, LabelMatcher, Query, Sample, TimeSeries, WriteRequest};
25use api::v1::RowInsertRequests;
26use arrow::array::{
27 Array, ArrayRef, AsArray, DictionaryArray, LargeStringArray, StringArray, StringViewArray,
28};
29use arrow::compute::kernels::cast as casts;
30use arrow::datatypes::{
31 DataType, Float64Type, TimeUnit, TimestampMicrosecondType, TimestampMillisecondType,
32 TimestampNanosecondType, UInt32Type,
33};
34use common_grpc::precision::Precision;
35use common_memory_manager::MemoryGuard;
36use common_query::native_histogram;
37use common_query::prelude::{greptime_timestamp, greptime_value};
38use common_recordbatch::{RecordBatch, RecordBatches};
39use common_telemetry::{tracing, warn};
40use datafusion::dataframe::DataFrame;
41use datafusion::prelude::{Expr, ident, lit, regexp_match};
42use datafusion_common::ScalarValue;
43use datafusion_expr::LogicalPlan;
44use snafu::{OptionExt, ResultExt, ensure};
45use snap::raw::{Decoder, Encoder};
46
47use crate::error::{self, Result};
48use crate::prom_remote_write::REMOTE_WRITE_V1_VERSION;
49use crate::request_memory_limiter::ServerMemoryLimiter;
50use crate::request_memory_metrics::RequestMemoryMetrics;
51use crate::row_writer::{self, MultiTableData};
52
53pub const METRIC_NAME_LABEL: &str = "__name__";
54pub const METRIC_NAME_LABEL_BYTES: &[u8] = b"__name__";
55
56pub const DATABASE_LABEL: &str = "x_greptime_database";
58pub const DATABASE_LABEL_BYTES: &[u8] = b"x_greptime_database";
59pub const DATABASE_LABEL_ALT: &str = "__database__";
60pub const DATABASE_LABEL_ALT_BYTES: &[u8] = b"__database__";
61
62#[deprecated(note = "use DATABASE_LABEL instead")]
64pub const SCHEMA_LABEL: &str = "__schema__";
65#[deprecated(note = "use DATABASE_LABEL_BYTES instead")]
66pub const SCHEMA_LABEL_BYTES: &[u8] = b"__schema__";
67
68pub const PHYSICAL_TABLE_LABEL: &str = "x_greptime_physical_table";
70pub const PHYSICAL_TABLE_LABEL_BYTES: &[u8] = b"x_greptime_physical_table";
71pub const PHYSICAL_TABLE_LABEL_ALT: &str = "__physical_table__";
72pub const PHYSICAL_TABLE_LABEL_ALT_BYTES: &[u8] = b"__physical_table__";
73
74pub const FIELD_NAME_LABEL: &str = "__field__";
76
77#[allow(deprecated)]
79pub fn is_remote_write_special_label(label: &str) -> bool {
80 label == DATABASE_LABEL
81 || label == DATABASE_LABEL_ALT
82 || label == PHYSICAL_TABLE_LABEL
83 || label == PHYSICAL_TABLE_LABEL_ALT
84 || label == SCHEMA_LABEL
85}
86
87#[allow(deprecated)]
88pub fn is_remote_read_special_label(label: &str) -> bool {
89 label == METRIC_NAME_LABEL
90 || label == DATABASE_LABEL
91 || label == DATABASE_LABEL_ALT
92 || label == SCHEMA_LABEL
93}
94
95#[allow(deprecated)]
97pub fn is_database_selection_label(label: &str) -> bool {
98 label == DATABASE_LABEL || label == DATABASE_LABEL_ALT || label == SCHEMA_LABEL
99}
100
101pub fn is_physical_table_selection_label(label: &str) -> bool {
103 label == PHYSICAL_TABLE_LABEL || label == PHYSICAL_TABLE_LABEL_ALT
104}
105
106pub fn table_name(q: &Query) -> Result<String> {
108 let mut matchers = q
109 .matchers
110 .iter()
111 .filter(|matcher| matcher.name == METRIC_NAME_LABEL);
112 let matcher = matchers
113 .next()
114 .context(error::InvalidPromRemoteRequestSnafu {
115 msg: "missing '__name__' label in timeseries",
116 })?;
117
118 if matcher.r#type != MatcherType::Eq as i32
119 || matcher.value.is_empty()
120 || matchers.next().is_some()
121 {
122 return Err(error::InvalidPromRemoteRequestSnafu {
123 msg: "expected exactly one non-empty equality matcher for '__name__'".to_string(),
124 }
125 .build());
126 }
127
128 Ok(matcher.value.clone())
129}
130
131pub fn extract_schema_from_query(query: &Query) -> Option<String> {
133 query
134 .matchers
135 .iter()
136 .find(|matcher| {
137 is_database_selection_label(&matcher.name) && matcher.r#type == MatcherType::Eq as i32
138 })
139 .map(|matcher| matcher.value.clone())
140}
141
142pub fn validate_matchers(matchers: &[LabelMatcher]) -> Result<()> {
144 for matcher in matchers {
145 let matcher_type = MatcherType::try_from(matcher.r#type).map_err(|e| {
146 error::InvalidPromRemoteRequestSnafu {
147 msg: format!("invalid LabelMatcher type, decode error: {e}"),
148 }
149 .build()
150 })?;
151 if matches!(matcher_type, MatcherType::Re | MatcherType::Nre) {
152 regex::Regex::new(&matcher.value)
156 .and_then(|_| regex::Regex::new(&anchored_label_regex(&matcher.value)))
157 .map_err(|e| {
158 error::InvalidPromRemoteRequestSnafu {
159 msg: format!("invalid regex for label '{}': {e}", matcher.name),
160 }
161 .build()
162 })?;
163 }
164 }
165 Ok(())
166}
167
168#[tracing::instrument(skip_all)]
170pub fn query_to_plan(
171 dataframe: DataFrame,
172 q: &Query,
173 timestamp_column_name: &str,
174) -> Result<LogicalPlan> {
175 let (start_timestamp_ms, end_timestamp_ms) = remote_read_time_range(q);
176
177 let label_matches = &q.matchers;
178
179 let mut conditions = Vec::with_capacity(label_matches.len() + 1);
180
181 conditions
182 .push(ident(timestamp_column_name).gt_eq(lit_timestamp_millisecond(start_timestamp_ms)));
183 conditions
184 .push(ident(timestamp_column_name).lt_eq(lit_timestamp_millisecond(end_timestamp_ms)));
185
186 for m in label_matches {
187 let name = &m.name;
188
189 if is_remote_read_special_label(name) {
190 continue;
191 }
192
193 let value = &m.value;
194 let m_type = MatcherType::try_from(m.r#type).map_err(|e| {
195 error::InvalidPromRemoteRequestSnafu {
196 msg: format!("invalid LabelMatcher type, decode error: {e}",),
197 }
198 .build()
199 })?;
200
201 let label = if dataframe.schema().has_column_with_unqualified_name(name) {
203 ident(name)
204 } else {
205 lit("")
206 };
207
208 let condition = match m_type {
211 MatcherType::Eq => {
212 if value.is_empty() {
213 label.clone().eq(lit(value)).or(label.is_null())
214 } else {
215 label.eq(lit(value))
216 }
217 }
218 MatcherType::Neq => {
219 if value.is_empty() {
220 label.not_eq(lit(value))
221 } else {
222 label.clone().not_eq(lit(value)).or(label.is_null())
223 }
224 }
225 MatcherType::Re | MatcherType::Nre => {
226 let pattern = anchored_label_regex(value);
227 let matches_empty = regex::Regex::new(&pattern)
228 .map_err(|e| {
229 error::InvalidPromRemoteRequestSnafu {
230 msg: format!("invalid regex for label '{name}': {e}"),
231 }
232 .build()
233 })?
234 .is_match("");
235 let matched = regexp_match(label.clone(), lit(pattern), None);
236 if m_type == MatcherType::Re {
237 let condition = matched.is_not_null();
238 if matches_empty {
239 condition.or(label.is_null())
240 } else {
241 condition
242 }
243 } else {
244 let condition = matched.is_null();
245 if matches_empty {
248 condition.and(label.is_not_null())
249 } else {
250 condition
251 }
252 }
253 }
254 };
255 conditions.push(condition);
256 }
257
258 let conditions = conditions.into_iter().reduce(Expr::and).unwrap();
260
261 let dataframe = dataframe
262 .filter(conditions)
263 .context(error::DataFrameSnafu)?;
264
265 Ok(dataframe.into_parts().1)
266}
267
268fn remote_read_time_range(query: &Query) -> (i64, i64) {
273 let outer = (query.start_timestamp_ms, query.end_timestamp_ms);
274 let Some(hints) = &query.hints else {
275 return outer;
276 };
277 if hints.start_ms <= 0 || hints.end_ms <= 0 || hints.start_ms > hints.end_ms {
278 return outer;
279 }
280
281 (outer.0.max(hints.start_ms), outer.1.min(hints.end_ms))
282}
283
284fn anchored_label_regex(value: &str) -> String {
287 format!("^(?s:{value})$")
288}
289
290#[inline]
291fn new_label(name: String, value: String) -> Label {
292 Label { name, value }
293}
294
295fn lit_timestamp_millisecond(ts: i64) -> Expr {
296 Expr::Literal(ScalarValue::TimestampMillisecond(Some(ts), None), None)
297}
298
299fn compare_timeseries_labels(left: &[Label], right: &[Label]) -> Ordering {
301 let ordering = left.len().cmp(&right.len());
302 if ordering != Ordering::Equal {
303 return ordering;
304 }
305
306 for (left, right) in left.iter().zip(right) {
307 let ordering = left.name.cmp(&right.name);
308 if ordering != Ordering::Equal {
309 return ordering;
310 }
311
312 let ordering = left.value.cmp(&right.value);
313 if ordering != Ordering::Equal {
314 return ordering;
315 }
316 }
317
318 Ordering::Equal
319}
320
321enum LabelValues<'a> {
322 Utf8(&'a StringArray),
323 LargeUtf8(&'a LargeStringArray),
324 Utf8View(&'a StringViewArray),
325 DictionaryUtf8 {
326 dictionary: &'a DictionaryArray<UInt32Type>,
327 values: &'a StringArray,
328 },
329 Other(Vec<Option<String>>),
330}
331
332impl LabelValues<'_> {
333 fn value(&self, row: usize) -> Option<&str> {
334 match self {
335 Self::Utf8(values) => values.is_valid(row).then(|| values.value(row)),
336 Self::LargeUtf8(values) => values.is_valid(row).then(|| values.value(row)),
337 Self::Utf8View(values) => values.is_valid(row).then(|| values.value(row)),
338 Self::DictionaryUtf8 { dictionary, values } => dictionary
339 .key(row)
340 .and_then(|key| values.is_valid(key).then(|| values.value(key))),
341 Self::Other(values) => values.get(row).and_then(Option::as_deref),
342 }
343 }
344}
345
346fn row_labels<'a>(
347 columns: &'a [LabelColumn<'a>],
348 row: usize,
349) -> impl Iterator<Item = (&'a str, &'a str)> {
350 columns
351 .iter()
352 .filter_map(move |column| column.values.value(row).map(|value| (column.name, value)))
353}
354
355struct LabelColumn<'a> {
356 name: &'a str,
357 values: LabelValues<'a>,
358}
359
360fn label_columns<'a>(
361 recordbatch: &'a RecordBatch,
362 timestamp_column_name: &str,
363 value_column_name: &str,
364) -> Result<Vec<LabelColumn<'a>>> {
365 recordbatch
366 .schema
367 .column_schemas()
368 .iter()
369 .enumerate()
370 .filter(|(_, column_schema)| {
371 column_schema.name != timestamp_column_name && column_schema.name != value_column_name
372 })
373 .map(|(index, column_schema)| {
374 let array = recordbatch.column(index);
375 let values = match array.data_type() {
376 arrow::datatypes::DataType::Utf8 => LabelValues::Utf8(array.as_string::<i32>()),
377 arrow::datatypes::DataType::LargeUtf8 => {
378 LabelValues::LargeUtf8(array.as_string::<i64>())
379 }
380 arrow::datatypes::DataType::Utf8View => {
381 LabelValues::Utf8View(array.as_string_view())
382 }
383 arrow::datatypes::DataType::Dictionary(key, value)
384 if key.as_ref() == &arrow::datatypes::DataType::UInt32
385 && value.as_ref() == &arrow::datatypes::DataType::Utf8 =>
386 {
387 let dictionary = array.as_dictionary::<UInt32Type>();
388 LabelValues::DictionaryUtf8 {
389 dictionary,
390 values: dictionary.values().as_string::<i32>(),
391 }
392 }
393 _ => {
394 let values = recordbatch.iter_column_as_string(index).collect::<Vec<_>>();
395 ensure!(
396 values.len() == recordbatch.num_rows(),
397 error::InvalidPromRemoteReadQueryResultSnafu {
398 msg: format!(
399 "Cannot convert label column '{}' of datatype {:?} to string",
400 column_schema.name,
401 array.data_type()
402 ),
403 }
404 );
405 LabelValues::Other(values)
406 }
407 };
408 Ok(LabelColumn {
409 name: &column_schema.name,
410 values,
411 })
412 })
413 .collect()
414}
415
416fn hash_timeseries(columns: &[LabelColumn<'_>], row: usize) -> u64 {
417 let mut hasher = DefaultHasher::new();
418
419 for (name, value) in row_labels(columns, row) {
420 name.hash(&mut hasher);
421 value.hash(&mut hasher);
422 }
423
424 hasher.finish()
425}
426
427fn matches_timeseries(labels: &[Label], columns: &[LabelColumn<'_>], row: usize) -> bool {
428 let mut labels = labels.iter().skip(1);
429 for (name, value) in row_labels(columns, row) {
430 let Some(label) = labels.next() else {
431 return false;
432 };
433 if label.name != name || label.value != value {
434 return false;
435 }
436 }
437
438 labels.next().is_none()
439}
440
441fn new_timeseries(table: &str, columns: &[LabelColumn<'_>], row: usize) -> TimeSeries {
442 let mut labels = Vec::with_capacity(columns.len() + 1);
443 labels.push(new_label(METRIC_NAME_LABEL.to_string(), table.to_string()));
444
445 for (name, value) in row_labels(columns, row) {
446 labels.push(new_label(name.to_string(), value.to_string()));
447 }
448
449 TimeSeries {
450 labels,
451 ..Default::default()
452 }
453}
454
455pub fn recordbatches_to_timeseries(
456 table_name: &str,
457 timestamp_column_name: &str,
458 value_column_name: &str,
459 recordbatches: RecordBatches,
460) -> Result<Vec<TimeSeries>> {
461 let mut timeseries: Vec<TimeSeries> = Vec::new();
462 let mut timeseries_by_hash: HashMap<u64, Vec<usize>> = HashMap::new();
463
464 for recordbatch in recordbatches.take() {
465 append_recordbatch_to_timeseries(
466 table_name,
467 timestamp_column_name,
468 value_column_name,
469 recordbatch,
470 &mut timeseries,
471 &mut timeseries_by_hash,
472 )?;
473 }
474
475 for ts in &mut timeseries {
476 ts.samples.sort_unstable_by_key(|s| s.timestamp);
477 ts.histograms.sort_unstable_by_key(|h| h.timestamp);
478 }
479
480 timeseries
481 .sort_unstable_by(|left, right| compare_timeseries_labels(&left.labels, &right.labels));
482 Ok(timeseries)
483}
484
485fn append_recordbatch_to_timeseries(
486 table: &str,
487 timestamp_column_name: &str,
488 value_column_name: &str,
489 recordbatch: RecordBatch,
490 timeseries: &mut Vec<TimeSeries>,
491 timeseries_by_hash: &mut HashMap<u64, Vec<usize>>,
492) -> Result<()> {
493 let ts_column = recordbatch
494 .column_by_name(timestamp_column_name)
495 .with_context(|| error::InvalidPromRemoteReadQueryResultSnafu {
496 msg: format!("missing timestamp column '{timestamp_column_name}' in query result"),
497 })?;
498 let ts_column: ArrayRef = match ts_column.data_type() {
507 DataType::Timestamp(TimeUnit::Millisecond, _) => ts_column.clone(),
508 DataType::Timestamp(TimeUnit::Second, _) => casts::cast(
510 ts_column,
511 &DataType::Timestamp(TimeUnit::Millisecond, None),
512 )
513 .map_err(|e| {
514 error::InvalidPromRemoteReadQueryResultSnafu {
515 msg: format!(
516 "failed to cast timestamp column '{timestamp_column_name}' of datatype {:?} to millisecond: {e}",
517 ts_column.data_type()
518 ),
519 }
520 .build()
521 })?,
522 DataType::Timestamp(TimeUnit::Microsecond, _) => Arc::new(
523 ts_column
524 .as_primitive::<TimestampMicrosecondType>()
525 .unary::<_, TimestampMillisecondType>(|v| v.div_euclid(1_000)),
526 ),
527 DataType::Timestamp(TimeUnit::Nanosecond, _) => Arc::new(
528 ts_column
529 .as_primitive::<TimestampNanosecondType>()
530 .unary::<_, TimestampMillisecondType>(|v| v.div_euclid(1_000_000)),
531 ),
532 _ => {
533 return error::InvalidPromRemoteReadQueryResultSnafu {
534 msg: format!(
535 "Expect timestamp column of datatype Timestamp(Millisecond), actual {:?}",
536 ts_column.data_type()
537 ),
538 }
539 .fail();
540 }
541 };
542 let ts_column = ts_column
543 .as_primitive_opt::<TimestampMillisecondType>()
544 .with_context(|| error::InvalidPromRemoteReadQueryResultSnafu {
545 msg: format!(
546 "Expect timestamp column of datatype Timestamp(Millisecond), actual {:?}",
547 ts_column.data_type()
548 ),
549 })?;
550
551 let field_column = recordbatch
552 .column_by_name(value_column_name)
553 .with_context(|| error::InvalidPromRemoteReadQueryResultSnafu {
554 msg: format!("missing value column '{value_column_name}' in query result"),
555 })?;
556 let scalar_column = field_column.as_primitive_opt::<Float64Type>();
557 let histogram_column = recordbatch
558 .schema
559 .column_schema_by_name(value_column_name)
560 .filter(|column| native_histogram::is_native_histogram_value_type(&column.data_type))
561 .and_then(|_| field_column.as_struct_opt());
562 ensure!(
563 scalar_column.is_some() || histogram_column.is_some(),
564 error::InvalidPromRemoteReadQueryResultSnafu {
565 msg: format!(
566 "Expect value column of datatype Float64 or canonical native histogram Struct, actual {:?}",
567 field_column.data_type()
568 ),
569 }
570 );
571
572 let columns = label_columns(&recordbatch, timestamp_column_name, value_column_name)?;
573 let mut previous_timeseries: Option<usize> = None;
574
575 for row in 0..recordbatch.num_rows() {
576 let timeseries_index = match previous_timeseries {
577 Some(index) if matches_timeseries(×eries[index].labels, &columns, row) => index,
578 _ => {
579 let hash = hash_timeseries(&columns, row);
580 let candidates = timeseries_by_hash.entry(hash).or_default();
581 match candidates
582 .iter()
583 .copied()
584 .find(|index| matches_timeseries(×eries[*index].labels, &columns, row))
585 {
586 Some(index) => index,
587 None => {
588 let index = timeseries.len();
589 timeseries.push(new_timeseries(table, &columns, row));
590 candidates.push(index);
591 index
592 }
593 }
594 }
595 };
596 previous_timeseries = Some(timeseries_index);
597
598 if ts_column.is_null(row) || field_column.is_null(row) {
599 continue;
600 }
601
602 let timestamp = ts_column.value(row);
603 if let Some(column) = scalar_column {
604 timeseries[timeseries_index].samples.push(Sample {
605 value: column.value(row),
606 timestamp,
607 });
608 } else if let Some(column) = histogram_column {
609 let histogram =
610 native_histogram::decode_native_histogram_for_remote_read(column, row, timestamp)
611 .map_err(|error| {
612 error::InvalidPromRemoteReadQueryResultSnafu {
613 msg: error.to_string(),
614 }
615 .build()
616 })?;
617 if let Some(histogram) = histogram {
618 timeseries[timeseries_index].histograms.push(histogram);
619 }
620 }
621 }
622
623 Ok(())
624}
625
626pub fn to_grpc_row_insert_requests(request: &WriteRequest) -> Result<(RowInsertRequests, usize)> {
627 let _timer = crate::metrics::METRIC_HTTP_PROM_STORE_CODEC_ELAPSED
628 .with_label_values(&["convert", REMOTE_WRITE_V1_VERSION])
629 .start_timer();
630
631 let mut multi_table_data = MultiTableData::new();
632
633 for series in &request.timeseries {
634 let table_name = &series
635 .labels
636 .iter()
637 .find(|label| {
638 label.name == METRIC_NAME_LABEL
640 })
641 .context(error::InvalidPromRemoteRequestSnafu {
642 msg: "missing '__name__' label in time-series",
643 })?
644 .value;
645
646 let num_columns = series.labels.len() + 1;
649
650 let table_data = multi_table_data.get_or_default_table_data(
651 table_name,
652 num_columns,
653 series.samples.len(),
654 );
655
656 let kvs = series.labels.iter().filter_map(|label| {
658 if label.name == METRIC_NAME_LABEL {
659 None
660 } else {
661 Some((label.name.clone(), label.value.clone()))
662 }
663 });
664
665 if series.samples.len() == 1 {
666 let mut one_row = table_data.alloc_one_row();
667
668 row_writer::write_tags(table_data, kvs, &mut one_row)?;
669 row_writer::write_f64(
671 table_data,
672 greptime_value(),
673 series.samples[0].value,
674 &mut one_row,
675 )?;
676 row_writer::write_ts_to_millis(
678 table_data,
679 greptime_timestamp(),
680 Some(series.samples[0].timestamp),
681 Precision::Millisecond,
682 &mut one_row,
683 )?;
684
685 table_data.add_row(one_row);
686 } else {
687 for Sample { value, timestamp } in &series.samples {
688 let mut one_row = table_data.alloc_one_row();
689
690 let kvs = kvs.clone();
692 row_writer::write_tags(table_data, kvs, &mut one_row)?;
693 row_writer::write_f64(table_data, greptime_value(), *value, &mut one_row)?;
695 row_writer::write_ts_to_millis(
697 table_data,
698 greptime_timestamp(),
699 Some(*timestamp),
700 Precision::Millisecond,
701 &mut one_row,
702 )?;
703
704 table_data.add_row(one_row);
705 }
706 }
707
708 if !series.histograms.is_empty() {
709 warn!("Native histograms are not supported yet, data ignored");
710 }
711 }
712
713 Ok(multi_table_data.into_row_insert_requests())
714}
715
716pub const MAX_DECOMPRESSED_REQUEST_SIZE: usize = 512 * 1024 * 1024;
725
726const ZSTD_DECOMPRESS_CHUNK: usize = 256 * 1024;
728
729pub(crate) struct ChargedBuffer {
733 pub data: Vec<u8>,
734 guards: Vec<MemoryGuard<RequestMemoryMetrics>>,
735}
736
737impl std::ops::Deref for ChargedBuffer {
738 type Target = [u8];
739
740 fn deref(&self) -> &Self::Target {
741 &self.data
742 }
743}
744
745impl std::fmt::Debug for ChargedBuffer {
746 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
747 f.debug_struct("ChargedBuffer")
748 .field("data_len", &self.data.len())
749 .field("guards", &self.guards.len())
750 .finish()
751 }
752}
753
754impl ChargedBuffer {
755 pub(crate) fn reserved_bytes(&self) -> u64 {
756 self.guards.iter().map(MemoryGuard::granted_bytes).sum()
757 }
758
759 pub(crate) fn new(data: Vec<u8>, guards: Vec<MemoryGuard<RequestMemoryMetrics>>) -> Self {
761 Self { data, guards }
762 }
763
764 pub(crate) fn into_parts(self) -> (Vec<u8>, Vec<MemoryGuard<RequestMemoryMetrics>>) {
767 (self.data, self.guards)
768 }
769}
770
771pub(crate) async fn snappy_decompress_limited(
778 buf: &[u8],
779 max_decompressed: usize,
780 limiter: &ServerMemoryLimiter,
781 held_bytes: u64,
782) -> Result<ChargedBuffer> {
783 let decoded_len =
784 snap::raw::decompress_len(buf).context(error::DecompressSnappyPromRemoteRequestSnafu)?;
785 ensure!(
786 decoded_len <= max_decompressed,
787 error::DecompressedBodyTooLargeSnafu {
788 size: decoded_len as u64,
789 limit: max_decompressed as u64,
790 }
791 );
792 limiter.check_request_fits(decoded_len as u64, held_bytes)?;
795 let guard = limiter.acquire(decoded_len as u64).await?;
796 let mut decoder = Decoder::new();
797 let data = decoder
798 .decompress_vec(buf)
799 .context(error::DecompressSnappyPromRemoteRequestSnafu)?;
800 Ok(ChargedBuffer {
801 data,
802 guards: vec![guard],
803 })
804}
805
806pub(crate) async fn zstd_decompress_limited(
810 buf: &[u8],
811 max_decompressed: usize,
812 limiter: &ServerMemoryLimiter,
813 mut held_bytes: u64,
814) -> Result<ChargedBuffer> {
815 use std::io::Read;
816
817 let decoder = zstd::stream::read::Decoder::new(buf)
818 .context(error::DecompressZstdPromRemoteRequestSnafu)?;
819 let mut limited = decoder.take(max_decompressed as u64 + 1);
822 let mut data = Vec::new();
823 let mut guards = Vec::new();
824 let mut chunk = vec![0u8; ZSTD_DECOMPRESS_CHUNK];
825 loop {
826 let n = limited
827 .read(&mut chunk)
828 .context(error::DecompressZstdPromRemoteRequestSnafu)?;
829 if n == 0 {
830 break;
831 }
832 ensure!(
833 data.len() + n <= max_decompressed,
834 error::DecompressedBodyTooLargeSnafu {
835 size: (data.len() + n) as u64,
836 limit: max_decompressed as u64,
837 }
838 );
839 limiter.check_request_fits(n as u64, held_bytes)?;
842 let guard = limiter.acquire(n as u64).await?;
843 held_bytes += guard.granted_bytes();
844 guards.push(guard);
845 data.extend_from_slice(&chunk[..n]);
846 }
847 Ok(ChargedBuffer { data, guards })
848}
849
850#[inline]
857pub fn snappy_decompress(buf: &[u8]) -> Result<Vec<u8>> {
858 let decoded_len =
859 snap::raw::decompress_len(buf).context(error::DecompressSnappyPromRemoteRequestSnafu)?;
860 ensure!(
861 decoded_len <= MAX_DECOMPRESSED_REQUEST_SIZE,
862 error::DecompressedBodyTooLargeSnafu {
863 size: decoded_len as u64,
864 limit: MAX_DECOMPRESSED_REQUEST_SIZE as u64,
865 }
866 );
867 let mut decoder = Decoder::new();
868 decoder
869 .decompress_vec(buf)
870 .context(error::DecompressSnappyPromRemoteRequestSnafu)
871}
872
873#[inline]
874pub fn snappy_compress(buf: &[u8]) -> Result<Vec<u8>> {
875 let mut encoder = Encoder::new();
876 encoder
877 .compress_vec(buf)
878 .context(error::CompressPromRemoteRequestSnafu)
879}
880
881pub fn mock_timeseries() -> Vec<TimeSeries> {
884 vec![
885 TimeSeries {
886 labels: vec![
887 new_label(METRIC_NAME_LABEL.to_string(), "metric1".to_string()),
888 new_label("job".to_string(), "spark".to_string()),
889 ],
890 samples: vec![
891 Sample {
892 value: 1.0f64,
893 timestamp: 1000,
894 },
895 Sample {
896 value: 2.0f64,
897 timestamp: 2000,
898 },
899 ],
900 ..Default::default()
901 },
902 TimeSeries {
903 labels: vec![
904 new_label(METRIC_NAME_LABEL.to_string(), "metric2".to_string()),
905 new_label("instance".to_string(), "test_host1".to_string()),
906 new_label("idc".to_string(), "z001".to_string()),
907 ],
908 samples: vec![
909 Sample {
910 value: 3.0f64,
911 timestamp: 1000,
912 },
913 Sample {
914 value: 4.0f64,
915 timestamp: 2000,
916 },
917 ],
918 ..Default::default()
919 },
920 TimeSeries {
921 labels: vec![
922 new_label(METRIC_NAME_LABEL.to_string(), "metric3".to_string()),
923 new_label("idc".to_string(), "z002".to_string()),
924 new_label("app".to_string(), "biz".to_string()),
925 ],
926 samples: vec![
927 Sample {
928 value: 5.0f64,
929 timestamp: 1000,
930 },
931 Sample {
932 value: 6.0f64,
933 timestamp: 2000,
934 },
935 Sample {
936 value: 7.0f64,
937 timestamp: 3000,
938 },
939 ],
940 ..Default::default()
941 },
942 ]
943}
944
945pub fn mock_timeseries_new_label() -> Vec<TimeSeries> {
947 let ts_demo_metrics = TimeSeries {
948 labels: vec![
949 new_label(METRIC_NAME_LABEL.to_string(), "demo_metrics".to_string()),
950 new_label("idc".to_string(), "idc3".to_string()),
951 new_label("new_label1".to_string(), "foo".to_string()),
952 ],
953 samples: vec![Sample {
954 value: 42.0,
955 timestamp: 3000,
956 }],
957 ..Default::default()
958 };
959 let ts_multi_labels = TimeSeries {
960 labels: vec![
961 new_label(METRIC_NAME_LABEL.to_string(), "metric1".to_string()),
962 new_label("idc".to_string(), "idc4".to_string()),
963 new_label("env".to_string(), "prod".to_string()),
964 new_label("host".to_string(), "host9".to_string()),
965 new_label("new_label2".to_string(), "bar".to_string()),
966 ],
967 samples: vec![Sample {
968 value: 99.0,
969 timestamp: 4000,
970 }],
971 ..Default::default()
972 };
973
974 vec![ts_demo_metrics, ts_multi_labels]
975}
976
977pub fn mock_timeseries_special_labels() -> Vec<TimeSeries> {
979 let idc3_schema = TimeSeries {
980 labels: vec![
981 new_label(METRIC_NAME_LABEL.to_string(), "idc3_lo_table".to_string()),
982 new_label(DATABASE_LABEL.to_string(), "idc3".to_string()),
983 new_label(PHYSICAL_TABLE_LABEL.to_string(), "f1".to_string()),
984 ],
985 samples: vec![Sample {
986 value: 42.0,
987 timestamp: 3000,
988 }],
989 ..Default::default()
990 };
991 let idc4_schema = TimeSeries {
992 labels: vec![
993 new_label(
994 METRIC_NAME_LABEL.to_string(),
995 "idc4_local_table".to_string(),
996 ),
997 new_label(DATABASE_LABEL.to_string(), "idc4".to_string()),
998 new_label(PHYSICAL_TABLE_LABEL.to_string(), "f2".to_string()),
999 ],
1000 samples: vec![Sample {
1001 value: 99.0,
1002 timestamp: 4000,
1003 }],
1004 ..Default::default()
1005 };
1006
1007 vec![idc3_schema, idc4_schema]
1008}
1009
1010#[cfg(test)]
1011mod tests {
1012 use std::sync::Arc;
1013
1014 use api::prom_store::remote::LabelMatcher;
1015 use api::v1::{ColumnDataType, Row, SemanticType};
1016 use arrow::array::{
1017 DictionaryArray, Float64Array, StringArray, TimestampMillisecondArray, UInt32Array,
1018 };
1019 use arrow::datatypes::{DataType as ArrowDataType, Field, Schema as ArrowSchema, UInt32Type};
1020 use common_memory_manager::OnExhaustedPolicy;
1021 use common_recordbatch::DfRecordBatch;
1022 use datafusion::prelude::SessionContext;
1023 use datatypes::data_type::ConcreteDataType;
1024 use datatypes::schema::{ColumnSchema, Schema};
1025 use datatypes::vectors::{
1026 Float64Vector, Int32Vector, StringVector, TimestampMillisecondVector,
1027 };
1028 use table::table::adapter::DfTableProviderAdapter;
1029 use table::test_util::MemTable;
1030
1031 use super::*;
1032
1033 const EQ_TYPE: i32 = MatcherType::Eq as i32;
1034 const NEQ_TYPE: i32 = MatcherType::Neq as i32;
1035 const RE_TYPE: i32 = MatcherType::Re as i32;
1036
1037 #[test]
1038 fn test_validate_matchers_regex_syntax() {
1039 for op in [MatcherType::Re, MatcherType::Nre] {
1040 for (value, valid) in [
1041 ("api|web", true),
1042 ("(?s)api.web", true),
1043 ("(?x) api", true),
1044 ("(?x) api # comment", false),
1045 ("[", false),
1046 ("a)|(?:b", false),
1048 ] {
1049 let matcher = LabelMatcher {
1050 name: "job".into(),
1051 value: value.into(),
1052 r#type: op as i32,
1053 };
1054 assert_eq!(
1055 valid,
1056 validate_matchers(&[matcher]).is_ok(),
1057 "{op:?}: {value}"
1058 );
1059 }
1060 }
1061 }
1062
1063 #[test]
1064 fn test_table_name() {
1065 let q = Query {
1066 start_timestamp_ms: 1000,
1067 end_timestamp_ms: 2000,
1068 matchers: vec![],
1069 ..Default::default()
1070 };
1071 let err = table_name(&q).unwrap_err();
1072 assert!(matches!(err, error::Error::InvalidPromRemoteRequest { .. }));
1073
1074 let q = Query {
1075 start_timestamp_ms: 1000,
1076 end_timestamp_ms: 2000,
1077 matchers: vec![LabelMatcher {
1078 name: METRIC_NAME_LABEL.to_string(),
1079 value: "test".to_string(),
1080 r#type: EQ_TYPE,
1081 }],
1082 ..Default::default()
1083 };
1084 assert_eq!("test", table_name(&q).unwrap());
1085
1086 for matchers in [
1087 vec![LabelMatcher {
1088 name: METRIC_NAME_LABEL.to_string(),
1089 value: "test.*".to_string(),
1090 r#type: RE_TYPE,
1091 }],
1092 vec![LabelMatcher {
1093 name: METRIC_NAME_LABEL.to_string(),
1094 value: String::new(),
1095 r#type: EQ_TYPE,
1096 }],
1097 vec![
1098 LabelMatcher {
1099 name: METRIC_NAME_LABEL.to_string(),
1100 value: "test".to_string(),
1101 r#type: EQ_TYPE,
1102 },
1103 LabelMatcher {
1104 name: METRIC_NAME_LABEL.to_string(),
1105 value: "other".to_string(),
1106 r#type: EQ_TYPE,
1107 },
1108 ],
1109 ] {
1110 let q = Query {
1111 matchers,
1112 ..Default::default()
1113 };
1114 assert!(matches!(
1115 table_name(&q),
1116 Err(error::Error::InvalidPromRemoteRequest { .. })
1117 ));
1118 }
1119 }
1120
1121 #[test]
1122 #[allow(deprecated)]
1123 fn test_extract_schema_from_query() {
1124 let query = Query::default();
1125 assert_eq!(None, extract_schema_from_query(&query));
1126
1127 for label in [DATABASE_LABEL, DATABASE_LABEL_ALT, SCHEMA_LABEL] {
1128 let query = Query {
1129 matchers: vec![LabelMatcher {
1130 name: label.to_string(),
1131 value: "selected_schema".to_string(),
1132 r#type: EQ_TYPE,
1133 }],
1134 ..Default::default()
1135 };
1136 assert_eq!(
1137 Some("selected_schema".to_string()),
1138 extract_schema_from_query(&query)
1139 );
1140 }
1141
1142 let query = Query {
1143 matchers: vec![LabelMatcher {
1144 name: DATABASE_LABEL.to_string(),
1145 value: "selected_schema".to_string(),
1146 r#type: NEQ_TYPE,
1147 }],
1148 ..Default::default()
1149 };
1150 assert_eq!(None, extract_schema_from_query(&query));
1151 }
1152
1153 #[test]
1154 fn test_query_to_plan() {
1155 let q = Query {
1156 start_timestamp_ms: 1000,
1157 end_timestamp_ms: 2000,
1158 matchers: vec![LabelMatcher {
1159 name: METRIC_NAME_LABEL.to_string(),
1160 value: "test".to_string(),
1161 r#type: EQ_TYPE,
1162 }],
1163 ..Default::default()
1164 };
1165
1166 let schema = Arc::new(Schema::new(vec![
1167 ColumnSchema::new(
1168 greptime_timestamp(),
1169 ConcreteDataType::timestamp_millisecond_datatype(),
1170 true,
1171 ),
1172 ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true),
1173 ColumnSchema::new("instance", ConcreteDataType::string_datatype(), true),
1174 ColumnSchema::new("job", ConcreteDataType::string_datatype(), true),
1175 ColumnSchema::new("service.name", ConcreteDataType::string_datatype(), true),
1176 ColumnSchema::new("Region", ConcreteDataType::string_datatype(), true),
1177 ]));
1178 let recordbatch = RecordBatch::new(
1179 schema,
1180 vec![
1181 Arc::new(TimestampMillisecondVector::from_vec(vec![1000])) as _,
1182 Arc::new(Float64Vector::from_vec(vec![3.0])) as _,
1183 Arc::new(StringVector::from(vec!["host1"])) as _,
1184 Arc::new(StringVector::from(vec!["job"])) as _,
1185 Arc::new(StringVector::from(vec!["api"])) as _,
1186 Arc::new(StringVector::from(vec!["us"])) as _,
1187 ],
1188 )
1189 .unwrap();
1190
1191 let ctx = SessionContext::new();
1192 let table = MemTable::table("test", recordbatch);
1193 let table_provider = Arc::new(DfTableProviderAdapter::new(table));
1194
1195 let dataframe = ctx.read_table(table_provider.clone()).unwrap();
1196 let plan = query_to_plan(dataframe, &q, greptime_timestamp()).unwrap();
1197 let display_string = format!("{}", plan.display_indent());
1198
1199 let ts_col = greptime_timestamp();
1200 let expected = format!(
1201 "Filter: ?table?.{} >= TimestampMillisecond(1000, None) AND ?table?.{} <= TimestampMillisecond(2000, None)\n TableScan: ?table?",
1202 ts_col, ts_col
1203 );
1204 assert_eq!(expected, display_string);
1205
1206 let q = Query {
1207 start_timestamp_ms: 1000,
1208 end_timestamp_ms: 2000,
1209 matchers: vec![
1210 LabelMatcher {
1211 name: METRIC_NAME_LABEL.to_string(),
1212 value: "test".to_string(),
1213 r#type: EQ_TYPE,
1214 },
1215 LabelMatcher {
1216 name: "job".to_string(),
1217 value: ".*prom.*".to_string(),
1218 r#type: RE_TYPE,
1219 },
1220 LabelMatcher {
1221 name: "instance".to_string(),
1222 value: "localhost".to_string(),
1223 r#type: NEQ_TYPE,
1224 },
1225 LabelMatcher {
1226 name: "service.name".to_string(),
1227 value: "api".to_string(),
1228 r#type: EQ_TYPE,
1229 },
1230 LabelMatcher {
1231 name: "Region".to_string(),
1232 value: "us".to_string(),
1233 r#type: EQ_TYPE,
1234 },
1235 ],
1236 ..Default::default()
1237 };
1238
1239 let dataframe = ctx.read_table(table_provider).unwrap();
1240 let plan = query_to_plan(dataframe, &q, greptime_timestamp()).unwrap();
1241 let display_string = format!("{}", plan.display_indent());
1242
1243 let ts_col = greptime_timestamp();
1244 let expected = format!(
1245 "Filter: ?table?.{} >= TimestampMillisecond(1000, None) AND ?table?.{} <= TimestampMillisecond(2000, None) AND regexp_match(?table?.job, Utf8(\"^(?s:.*prom.*)$\")) IS NOT NULL AND (?table?.instance != Utf8(\"localhost\") OR ?table?.instance IS NULL) AND ?table?.service.name = Utf8(\"api\") AND ?table?.Region = Utf8(\"us\")\n TableScan: ?table?",
1246 ts_col, ts_col
1247 );
1248 assert_eq!(expected, display_string);
1249 }
1250
1251 #[tokio::test]
1252 async fn test_remote_read_time_hints() {
1253 use api::prom_store::remote::ReadHints;
1254
1255 let ctx = SessionContext::new();
1256 let timestamps = vec![-1, 0, 999, 1000, 1500, 2000, 2001, 3000];
1257 let batch = DfRecordBatch::try_from_iter(vec![(
1258 greptime_timestamp(),
1259 Arc::new(TimestampMillisecondArray::from(timestamps)) as ArrayRef,
1260 )])
1261 .unwrap();
1262 ctx.register_batch("metrics", batch).unwrap();
1263
1264 for (outer, hinted, expected) in [
1265 ((1000, 2000), None, vec![1000, 1500, 2000]),
1266 ((1000, 2000), Some((0, 0)), vec![1000, 1500, 2000]),
1267 ((1000, 2000), Some((0, 1500)), vec![1000, 1500, 2000]),
1268 ((1000, 2000), Some((1500, 0)), vec![1000, 1500, 2000]),
1269 ((1000, 2000), Some((-1, 1500)), vec![1000, 1500, 2000]),
1270 ((1000, 2000), Some((1500, -1)), vec![1000, 1500, 2000]),
1271 ((1000, 2000), Some((2000, 1000)), vec![1000, 1500, 2000]),
1272 ((1000, 2000), Some((1000, 2000)), vec![1000, 1500, 2000]),
1273 ((1000, 2000), Some((1, 3000)), vec![1000, 1500, 2000]),
1274 ((1000, 2000), Some((1001, 1999)), vec![1500]),
1275 ((1000, 2000), Some((1500, 3000)), vec![1500, 2000]),
1276 ((1000, 2000), Some((1, 1500)), vec![1000, 1500]),
1277 ((1000, 2000), Some((1500, 1500)), vec![1500]),
1278 ((1000, 2000), Some((2000, 3000)), vec![2000]),
1279 ((1000, 2000), Some((2001, 3000)), vec![]),
1280 ((1000, 2000), Some((1, 999)), vec![]),
1281 ((2000, 1000), Some((1, 3000)), vec![]),
1282 ((-1, 1000), Some((0, 1000)), vec![-1, 0, 999, 1000]),
1283 ] {
1284 let query = Query {
1285 start_timestamp_ms: outer.0,
1286 end_timestamp_ms: outer.1,
1287 hints: hinted.map(|(start_ms, end_ms)| ReadHints {
1288 start_ms,
1289 end_ms,
1290 step_ms: 10000,
1292 range_ms: 300000,
1293 func: "rate".into(),
1294 grouping: vec!["job".into()],
1295 by: true,
1296 }),
1297 ..Default::default()
1298 };
1299 let plan = query_to_plan(
1300 ctx.table("metrics").await.unwrap(),
1301 &query,
1302 greptime_timestamp(),
1303 )
1304 .unwrap();
1305 let batches = ctx
1306 .execute_logical_plan(plan)
1307 .await
1308 .unwrap()
1309 .collect()
1310 .await
1311 .unwrap();
1312 let actual: Vec<i64> = batches
1313 .iter()
1314 .flat_map(|batch| {
1315 batch
1316 .column(0)
1317 .as_primitive::<TimestampMillisecondType>()
1318 .values()
1319 .to_vec()
1320 })
1321 .collect();
1322 assert_eq!(expected, actual, "outer={outer:?}, hints={hinted:?}");
1323 }
1324 }
1325
1326 #[tokio::test]
1327 async fn test_remote_read_label_matcher_semantics() {
1328 let ctx = SessionContext::new();
1329 let batch = DfRecordBatch::try_from_iter(vec![
1330 (
1331 greptime_timestamp(),
1332 Arc::new(TimestampMillisecondArray::from(vec![1000; 6])) as ArrayRef,
1333 ),
1334 (
1335 "job",
1336 Arc::new(StringArray::from(vec![
1337 Some("api"),
1338 Some("my-api-server"),
1339 Some(""),
1340 None,
1341 Some("web"),
1342 Some("api\nweb"),
1343 ])) as ArrayRef,
1344 ),
1345 (
1346 "id",
1347 Arc::new(UInt32Array::from(vec![0, 1, 2, 3, 4, 5])) as ArrayRef,
1348 ),
1349 ])
1350 .unwrap();
1351 ctx.register_batch("metrics", batch).unwrap();
1352 for (name, op, value, expected) in [
1353 ("job", MatcherType::Eq, "api", vec![0]),
1354 ("job", MatcherType::Neq, "api", vec![1, 2, 3, 4, 5]),
1355 ("job", MatcherType::Eq, "", vec![2, 3]),
1356 ("job", MatcherType::Neq, "", vec![0, 1, 4, 5]),
1357 ("job", MatcherType::Re, "api", vec![0]),
1358 ("job", MatcherType::Nre, "api", vec![1, 2, 3, 4, 5]),
1359 ("job", MatcherType::Re, "api|web", vec![0, 4]),
1360 ("job", MatcherType::Nre, "api|web", vec![1, 2, 3, 5]),
1361 ("job", MatcherType::Re, "", vec![2, 3]),
1362 ("job", MatcherType::Nre, "", vec![0, 1, 4, 5]),
1363 ("job", MatcherType::Re, ".*", vec![0, 1, 2, 3, 4, 5]),
1364 ("job", MatcherType::Nre, ".*", vec![]),
1365 ("job", MatcherType::Re, ".+", vec![0, 1, 4, 5]),
1366 ("job", MatcherType::Nre, ".+", vec![2, 3]),
1367 ("job", MatcherType::Re, "(?m)api", vec![0]),
1368 ("job", MatcherType::Re, "api.web", vec![5]),
1369 ("job", MatcherType::Nre, "api.web", vec![0, 1, 2, 3, 4]),
1370 ("job", MatcherType::Re, "(?-s:api.web)", vec![]),
1371 ("job", MatcherType::Re, "(api)?", vec![0, 2, 3]),
1372 ("missing", MatcherType::Eq, "", vec![0, 1, 2, 3, 4, 5]),
1373 ("missing", MatcherType::Eq, "us", vec![]),
1374 ("missing", MatcherType::Neq, "us", vec![0, 1, 2, 3, 4, 5]),
1375 ("missing", MatcherType::Neq, "", vec![]),
1376 ("missing", MatcherType::Re, ".*", vec![0, 1, 2, 3, 4, 5]),
1377 ("missing", MatcherType::Re, ".+", vec![]),
1378 ("missing", MatcherType::Nre, ".*", vec![]),
1379 ("missing", MatcherType::Nre, ".+", vec![0, 1, 2, 3, 4, 5]),
1380 ("missing", MatcherType::Re, "", vec![0, 1, 2, 3, 4, 5]),
1381 ("missing", MatcherType::Nre, "", vec![]),
1382 ] {
1383 let query = Query {
1384 start_timestamp_ms: 1000,
1385 end_timestamp_ms: 1000,
1386 matchers: vec![LabelMatcher {
1387 name: name.into(),
1388 value: value.into(),
1389 r#type: op as i32,
1390 }],
1391 ..Default::default()
1392 };
1393 let plan = query_to_plan(
1394 ctx.table("metrics").await.unwrap(),
1395 &query,
1396 greptime_timestamp(),
1397 )
1398 .unwrap_or_else(|err| panic!("{name} {op:?} {value:?}: {err}"));
1399 let batches = ctx
1400 .execute_logical_plan(plan)
1401 .await
1402 .unwrap()
1403 .collect()
1404 .await
1405 .unwrap();
1406 let actual: Vec<u32> = batches
1407 .iter()
1408 .flat_map(|batch| {
1409 batch
1410 .column(2)
1411 .as_primitive::<UInt32Type>()
1412 .values()
1413 .to_vec()
1414 })
1415 .collect();
1416 assert_eq!(expected, actual, "{name} {op:?} {value:?}");
1417 }
1418 }
1419
1420 #[tokio::test]
1421 async fn test_remote_read_matcher_combinations_and_label_types() {
1422 let labels = StringArray::from(vec![Some("api"), Some("api"), None, Some("api")]);
1423 for label_type in [
1424 DataType::Utf8,
1425 DataType::LargeUtf8,
1426 DataType::Utf8View,
1427 DataType::Dictionary(Box::new(DataType::UInt32), Box::new(DataType::Utf8)),
1428 ] {
1429 let ctx = SessionContext::new();
1430 let batch = DfRecordBatch::try_from_iter(vec![
1431 (
1432 greptime_timestamp(),
1433 Arc::new(TimestampMillisecondArray::from(vec![999, 1000, 2000, 2001]))
1434 as ArrayRef,
1435 ),
1436 ("Job.Name", casts::cast(&labels, &label_type).unwrap()),
1437 ])
1438 .unwrap();
1439 ctx.register_batch("metrics", batch).unwrap();
1440 let mut cases = vec![
1441 (vec![("Job.Name", MatcherType::Re, ".*")], vec![1000, 2000]),
1442 (vec![("Job.Name", MatcherType::Nre, ".+")], vec![2000]),
1443 ];
1444 if label_type == DataType::Utf8 {
1446 cases.extend([
1447 (
1448 vec![
1449 ("Job.Name", MatcherType::Re, "api|web"),
1450 ("Job.Name", MatcherType::Neq, "web"),
1451 ],
1452 vec![1000],
1453 ),
1454 (
1455 vec![
1456 ("Job.Name", MatcherType::Eq, ""),
1457 ("Job.Name", MatcherType::Neq, "api"),
1458 ],
1459 vec![2000],
1460 ),
1461 (
1462 vec![
1463 ("Job.Name", MatcherType::Eq, "api"),
1464 ("Job.Name", MatcherType::Eq, "web"),
1465 ],
1466 vec![],
1467 ),
1468 (
1469 vec![
1470 ("job.name", MatcherType::Eq, ""),
1471 ("Job.Name", MatcherType::Re, ".*"),
1472 ],
1473 vec![1000, 2000],
1474 ),
1475 ]);
1476 }
1477 for (matchers, expected) in cases {
1478 let mut query = Query {
1479 start_timestamp_ms: 1000,
1480 end_timestamp_ms: 2000,
1481 matchers: matchers
1482 .iter()
1483 .map(|(name, op, value)| LabelMatcher {
1484 name: (*name).into(),
1485 value: (*value).into(),
1486 r#type: *op as i32,
1487 })
1488 .collect(),
1489 ..Default::default()
1490 };
1491 query.matchers.push(LabelMatcher {
1492 name: METRIC_NAME_LABEL.into(),
1493 value: "metrics".into(),
1494 r#type: EQ_TYPE,
1495 });
1496 let plan = query_to_plan(
1497 ctx.table("metrics").await.unwrap(),
1498 &query,
1499 greptime_timestamp(),
1500 )
1501 .unwrap();
1502 let batches = ctx
1503 .execute_logical_plan(plan)
1504 .await
1505 .unwrap()
1506 .collect()
1507 .await
1508 .unwrap();
1509 let actual: Vec<i64> = batches
1510 .iter()
1511 .flat_map(|batch| {
1512 batch
1513 .column(0)
1514 .as_primitive::<TimestampMillisecondType>()
1515 .values()
1516 .to_vec()
1517 })
1518 .collect();
1519 assert_eq!(expected, actual, "{label_type:?}: {matchers:?}");
1520 }
1521 }
1522 }
1523
1524 fn column_schemas_with(
1525 mut kts_iter: Vec<(&str, ColumnDataType, SemanticType)>,
1526 ) -> Vec<api::v1::ColumnSchema> {
1527 kts_iter.push((
1528 greptime_value(),
1529 ColumnDataType::Float64,
1530 SemanticType::Field,
1531 ));
1532 kts_iter.push((
1533 greptime_timestamp(),
1534 ColumnDataType::TimestampMillisecond,
1535 SemanticType::Timestamp,
1536 ));
1537
1538 kts_iter
1539 .into_iter()
1540 .map(|(k, t, s)| api::v1::ColumnSchema {
1541 column_name: k.to_string(),
1542 datatype: t as i32,
1543 semantic_type: s as i32,
1544 ..Default::default()
1545 })
1546 .collect()
1547 }
1548
1549 fn make_row_with_label(l1: &str, value: f64, timestamp: i64) -> Row {
1550 Row {
1551 values: vec![
1552 api::v1::Value {
1553 value_data: Some(api::v1::value::ValueData::StringValue(l1.to_string())),
1554 },
1555 api::v1::Value {
1556 value_data: Some(api::v1::value::ValueData::F64Value(value)),
1557 },
1558 api::v1::Value {
1559 value_data: Some(api::v1::value::ValueData::TimestampMillisecondValue(
1560 timestamp,
1561 )),
1562 },
1563 ],
1564 }
1565 }
1566
1567 fn make_row_with_2_labels(l1: &str, l2: &str, value: f64, timestamp: i64) -> Row {
1568 Row {
1569 values: vec![
1570 api::v1::Value {
1571 value_data: Some(api::v1::value::ValueData::StringValue(l1.to_string())),
1572 },
1573 api::v1::Value {
1574 value_data: Some(api::v1::value::ValueData::StringValue(l2.to_string())),
1575 },
1576 api::v1::Value {
1577 value_data: Some(api::v1::value::ValueData::F64Value(value)),
1578 },
1579 api::v1::Value {
1580 value_data: Some(api::v1::value::ValueData::TimestampMillisecondValue(
1581 timestamp,
1582 )),
1583 },
1584 ],
1585 }
1586 }
1587
1588 #[test]
1589 fn test_write_request_to_row_insert_exprs() {
1590 let write_request = WriteRequest {
1591 timeseries: mock_timeseries(),
1592 ..Default::default()
1593 };
1594
1595 let mut exprs = to_grpc_row_insert_requests(&write_request)
1596 .unwrap()
1597 .0
1598 .inserts;
1599 exprs.sort_unstable_by(|l, r| l.table_name.cmp(&r.table_name));
1600 assert_eq!(3, exprs.len());
1601 assert_eq!("metric1", exprs[0].table_name);
1602 assert_eq!("metric2", exprs[1].table_name);
1603 assert_eq!("metric3", exprs[2].table_name);
1604
1605 let rows = exprs[0].rows.as_ref().unwrap();
1606 let schema = &rows.schema;
1607 let rows = &rows.rows;
1608 assert_eq!(2, rows.len());
1609 assert_eq!(3, schema.len());
1610 assert_eq!(
1611 column_schemas_with(vec![("job", ColumnDataType::String, SemanticType::Tag)]),
1612 *schema
1613 );
1614 assert_eq!(
1615 &vec![
1616 make_row_with_label("spark", 1.0, 1000),
1617 make_row_with_label("spark", 2.0, 2000),
1618 ],
1619 rows
1620 );
1621
1622 let rows = exprs[1].rows.as_ref().unwrap();
1623 let schema = &rows.schema;
1624 let rows = &rows.rows;
1625 assert_eq!(2, rows.len());
1626 assert_eq!(4, schema.len());
1627 assert_eq!(
1628 column_schemas_with(vec![
1629 ("instance", ColumnDataType::String, SemanticType::Tag),
1630 ("idc", ColumnDataType::String, SemanticType::Tag)
1631 ]),
1632 *schema
1633 );
1634 assert_eq!(
1635 &vec![
1636 make_row_with_2_labels("test_host1", "z001", 3.0, 1000),
1637 make_row_with_2_labels("test_host1", "z001", 4.0, 2000),
1638 ],
1639 rows
1640 );
1641
1642 let rows = exprs[2].rows.as_ref().unwrap();
1643 let schema = &rows.schema;
1644 let rows = &rows.rows;
1645 assert_eq!(3, rows.len());
1646 assert_eq!(4, schema.len());
1647 assert_eq!(
1648 column_schemas_with(vec![
1649 ("idc", ColumnDataType::String, SemanticType::Tag),
1650 ("app", ColumnDataType::String, SemanticType::Tag)
1651 ]),
1652 *schema
1653 );
1654 assert_eq!(
1655 &vec![
1656 make_row_with_2_labels("z002", "biz", 5.0, 1000),
1657 make_row_with_2_labels("z002", "biz", 6.0, 2000),
1658 make_row_with_2_labels("z002", "biz", 7.0, 3000),
1659 ],
1660 rows
1661 );
1662 }
1663
1664 #[test]
1665 fn test_native_histogram_remote_read_wire_fixture() {
1666 use api::greptime_proto::io::prometheus::write::v2::{
1667 BucketSpan, Histogram,
1668 histogram::{Count, ZeroCount},
1669 };
1670 use api::prom_store::remote::{QueryResult, ReadResponse};
1671 use common_query::native_histogram::testing::histogram_array;
1672 use prost::Message;
1673
1674 let histograms = vec![
1675 Histogram {
1676 count: Some(Count::CountInt((1 << 53) + 13)),
1677 zero_count: Some(ZeroCount::ZeroCountInt((1 << 53) + 1)),
1678 sum: 10.0,
1679 schema: 1,
1680 zero_threshold: 0.001,
1681 reset_hint: 2,
1682 start_timestamp: 123,
1683 positive_spans: vec![
1684 BucketSpan {
1685 offset: -2,
1686 length: 2,
1687 },
1688 BucketSpan {
1689 offset: 3,
1690 length: 1,
1691 },
1692 ],
1693 positive_deltas: vec![1, 2, -1],
1694 negative_spans: vec![
1695 BucketSpan {
1696 offset: -3,
1697 length: 1,
1698 },
1699 BucketSpan {
1700 offset: 2,
1701 length: 2,
1702 },
1703 ],
1704 negative_deltas: vec![1, 2, -1],
1705 ..Default::default()
1706 },
1707 Histogram {
1708 count: Some(Count::CountFloat(6.0)),
1709 zero_count: Some(ZeroCount::ZeroCountFloat(0.5)),
1710 sum: 20.0,
1711 schema: 2,
1712 zero_threshold: 0.002,
1713 reset_hint: 3,
1714 positive_spans: vec![BucketSpan {
1715 offset: 3,
1716 length: 2,
1717 }],
1718 positive_counts: vec![2.0, 3.5],
1719 ..Default::default()
1720 },
1721 Histogram {
1722 count: Some(Count::CountInt(1)),
1723 sum: 1.0,
1724 schema: -53,
1725 custom_values: vec![1.0, 2.0],
1726 positive_spans: vec![BucketSpan {
1727 offset: 0,
1728 length: 1,
1729 }],
1730 positive_deltas: vec![1],
1731 ..Default::default()
1732 },
1733 Histogram {
1734 count: Some(Count::CountFloat(3.5)),
1735 sum: 4.0,
1736 schema: -53,
1737 custom_values: vec![1.0, 2.0],
1738 reset_hint: 3,
1739 positive_spans: vec![BucketSpan {
1740 offset: 0,
1741 length: 2,
1742 }],
1743 positive_counts: vec![1.0, 2.5],
1744 ..Default::default()
1745 },
1746 Histogram {
1747 count: Some(Count::CountInt(0)),
1748 ..Default::default()
1749 },
1750 Histogram {
1751 count: Some(Count::CountFloat(0.0)),
1752 ..Default::default()
1753 },
1754 Histogram {
1755 count: Some(Count::CountInt(8)),
1756 sum: f64::from_bits(0x7ff8_0000_0000_0123),
1757 positive_spans: vec![BucketSpan {
1758 offset: 0,
1759 length: 2,
1760 }],
1761 positive_deltas: vec![3, -1],
1762 ..Default::default()
1763 },
1764 Histogram {
1765 count: Some(Count::CountInt(0)),
1766 sum: f64::from_bits(0x7ff0_0000_0000_0002),
1767 ..Default::default()
1768 },
1769 Histogram {
1770 count: Some(Count::CountInt(i64::MAX as u64)),
1771 zero_count: Some(ZeroCount::ZeroCountInt(i64::MAX as u64)),
1772 ..Default::default()
1773 },
1774 ];
1775 let values = histograms
1776 .iter()
1777 .map(|h| Some(native_histogram::encode_native_histogram(h).unwrap()))
1778 .collect::<Vec<_>>();
1779 let array = histogram_array(&values);
1780 let buckets = array
1781 .column_by_name("positive_buckets_i64")
1782 .unwrap()
1783 .as_list::<i32>()
1784 .value(0);
1785 assert_eq!(
1786 buckets
1787 .as_primitive::<arrow::datatypes::Int64Type>()
1788 .values()
1789 .as_ref(),
1790 &[1, 3, 2]
1791 );
1792 let arrow_schema = Arc::new(ArrowSchema::new(vec![
1793 Field::new(
1794 "ts",
1795 ArrowDataType::Timestamp(TimeUnit::Millisecond, None),
1796 false,
1797 ),
1798 Field::new("histogram", array.data_type().clone(), true),
1799 ]));
1800 let schema = Arc::new(Schema::try_from(arrow_schema.clone()).unwrap());
1801 let batch = DfRecordBatch::try_new(
1802 arrow_schema,
1803 vec![
1804 Arc::new(TimestampMillisecondArray::from(
1805 (1..=9).map(|i| i * 1000).collect::<Vec<_>>(),
1806 )),
1807 Arc::new(array),
1808 ],
1809 )
1810 .unwrap();
1811 let batches = RecordBatches::try_new(
1812 schema.clone(),
1813 vec![RecordBatch::from_df_record_batch(schema, batch)],
1814 )
1815 .unwrap();
1816 let response = ReadResponse {
1817 results: vec![QueryResult {
1818 timeseries: recordbatches_to_timeseries(
1819 "native_histogram_remote_read",
1820 "ts",
1821 "histogram",
1822 batches,
1823 )
1824 .unwrap(),
1825 }],
1826 };
1827 let body = snappy_compress(&response.encode_to_vec()).unwrap();
1828 let mut decoded =
1829 ReadResponse::decode(snappy_decompress(&body).unwrap().as_slice()).unwrap();
1830 let mut expected = ReadResponse::decode(
1831 include_bytes!(
1832 "../../../tests/compatibility/wire/native_histogram_remote_read/response.pb"
1833 )
1834 .as_slice(),
1835 )
1836 .unwrap();
1837 let actual_histograms = &mut decoded.results[0].timeseries[0].histograms;
1838 assert_eq!(actual_histograms[0].positive_deltas, vec![1, 2, -1]);
1839 assert_eq!(actual_histograms[0].negative_deltas, vec![1, 2, -1]);
1840 assert_eq!(actual_histograms[6].positive_deltas, vec![3, -1]);
1841 for (actual, expected) in actual_histograms
1842 .iter_mut()
1843 .zip(&mut expected.results[0].timeseries[0].histograms)
1844 {
1845 assert_eq!(actual.sum.to_bits(), expected.sum.to_bits());
1846 actual.sum = 0.0;
1847 expected.sum = 0.0;
1848 }
1849 assert_eq!(decoded, expected);
1850 if let Some(path) = std::env::var_os("GREPTIME_NATIVE_HISTOGRAM_REMOTE_READ_OUTPUT") {
1852 std::fs::write(path, body).unwrap();
1853 }
1854 }
1855
1856 #[test]
1857 fn test_native_histogram_remote_read_groups_and_sorts_batches() {
1858 use api::greptime_proto::io::prometheus::write::v2::{Histogram, histogram::Count};
1859 use common_query::native_histogram::testing::histogram_array;
1860
1861 let integer = native_histogram::encode_native_histogram(&Histogram {
1862 count: Some(Count::CountInt(0)),
1863 sum: f64::from_bits(0x7ff0_0000_0000_0002),
1864 ..Default::default()
1865 })
1866 .unwrap();
1867 let float = native_histogram::encode_native_histogram(&Histogram {
1868 count: Some(Count::CountFloat(0.0)),
1869 ..Default::default()
1870 })
1871 .unwrap();
1872 let histograms = histogram_array(&[Some(integer), Some(float.clone()), None, Some(float)]);
1873 let arrow_schema = Arc::new(ArrowSchema::new(vec![
1874 Field::new(
1875 "event_time",
1876 ArrowDataType::Timestamp(TimeUnit::Microsecond, None),
1877 true,
1878 ),
1879 Field::new("legacy_histogram", histograms.data_type().clone(), true),
1880 Field::new_dictionary("instance", ArrowDataType::UInt32, ArrowDataType::Utf8, true),
1881 ]));
1882 let schema = Arc::new(Schema::try_from(arrow_schema.clone()).unwrap());
1883 let batch = |timestamps: Vec<Option<i64>>, labels: Vec<Option<u32>>| {
1884 RecordBatch::from_df_record_batch(
1885 schema.clone(),
1886 DfRecordBatch::try_new(
1887 arrow_schema.clone(),
1888 vec![
1889 Arc::new(arrow::array::TimestampMicrosecondArray::from(timestamps)),
1890 Arc::new(histograms.clone()),
1891 Arc::new(DictionaryArray::<UInt32Type>::new(
1892 UInt32Array::from(labels),
1893 Arc::new(StringArray::from(vec![Some("b"), Some("a"), None])),
1894 )),
1895 ],
1896 )
1897 .unwrap(),
1898 )
1899 };
1900 let batches = vec![
1901 batch(
1902 vec![Some(4000), Some(3000), Some(5000), Some(-1001)],
1903 vec![Some(0), None, Some(0), Some(0)],
1904 ),
1905 batch(
1906 vec![Some(2000), Some(1000), Some(6000), None],
1907 vec![Some(0), Some(2), Some(1), Some(0)],
1908 ),
1909 ];
1910 let series = recordbatches_to_timeseries(
1911 "latency",
1912 "event_time",
1913 "legacy_histogram",
1914 RecordBatches::try_new(schema.clone(), batches).unwrap(),
1915 )
1916 .unwrap();
1917 assert_eq!(series.len(), 3);
1918 assert_eq!(
1919 series[0].labels,
1920 vec![new_label(
1921 METRIC_NAME_LABEL.to_string(),
1922 "latency".to_string()
1923 )]
1924 );
1925 assert_eq!(
1926 series[0]
1927 .histograms
1928 .iter()
1929 .map(|h| h.timestamp)
1930 .collect::<Vec<_>>(),
1931 vec![1, 3]
1932 );
1933 assert_eq!(series[1].labels[1].value, "a");
1934 assert!(series[1].histograms.is_empty());
1935 assert_eq!(series[2].labels[1].value, "b");
1936 assert_eq!(
1937 series[2]
1938 .histograms
1939 .iter()
1940 .map(|h| h.timestamp)
1941 .collect::<Vec<_>>(),
1942 vec![-2, 2, 4]
1943 );
1944 assert!(matches!(
1945 series[2].histograms[0].count,
1946 Some(api::prom_store::remote::histogram::Count::CountFloat(0.0))
1947 ));
1948 for histogram in &series[2].histograms[1..] {
1949 assert!(matches!(
1950 histogram.count,
1951 Some(api::prom_store::remote::histogram::Count::CountInt(0))
1952 ));
1953 assert_eq!(histogram.sum.to_bits(), 0x7ff0_0000_0000_0002);
1954 }
1955 assert!(
1956 series
1957 .iter()
1958 .all(|s| s.samples.is_empty()
1959 && s.labels.iter().all(|l| l.name != "legacy_histogram"))
1960 );
1961 }
1962
1963 #[test]
1964 fn test_recordbatches_to_timeseries() {
1965 let schema = Arc::new(Schema::new(vec![
1966 ColumnSchema::new(
1967 greptime_timestamp(),
1968 ConcreteDataType::timestamp_millisecond_datatype(),
1969 true,
1970 ),
1971 ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true),
1972 ColumnSchema::new("instance", ConcreteDataType::string_datatype(), true),
1973 ]));
1974
1975 let recordbatches = RecordBatches::try_new(
1976 schema.clone(),
1977 vec![
1978 RecordBatch::new(
1979 schema.clone(),
1980 vec![
1981 Arc::new(TimestampMillisecondVector::from_vec(vec![1000])) as _,
1982 Arc::new(Float64Vector::from_vec(vec![3.0])) as _,
1983 Arc::new(StringVector::from(vec!["host1"])) as _,
1984 ],
1985 )
1986 .unwrap(),
1987 RecordBatch::new(
1988 schema,
1989 vec![
1990 Arc::new(TimestampMillisecondVector::from_vec(vec![2000])) as _,
1991 Arc::new(Float64Vector::from_vec(vec![7.0])) as _,
1992 Arc::new(StringVector::from(vec!["host2"])) as _,
1993 ],
1994 )
1995 .unwrap(),
1996 ],
1997 )
1998 .unwrap();
1999
2000 let timeseries = recordbatches_to_timeseries(
2001 "metric1",
2002 greptime_timestamp(),
2003 greptime_value(),
2004 recordbatches,
2005 )
2006 .unwrap();
2007 assert_eq!(2, timeseries.len());
2008
2009 assert_eq!(
2010 vec![
2011 Label {
2012 name: METRIC_NAME_LABEL.to_string(),
2013 value: "metric1".to_string(),
2014 },
2015 Label {
2016 name: "instance".to_string(),
2017 value: "host1".to_string(),
2018 },
2019 ],
2020 timeseries[0].labels
2021 );
2022
2023 assert_eq!(
2024 timeseries[0].samples,
2025 vec![Sample {
2026 value: 3.0,
2027 timestamp: 1000,
2028 }]
2029 );
2030
2031 assert_eq!(
2032 vec![
2033 Label {
2034 name: METRIC_NAME_LABEL.to_string(),
2035 value: "metric1".to_string(),
2036 },
2037 Label {
2038 name: "instance".to_string(),
2039 value: "host2".to_string(),
2040 },
2041 ],
2042 timeseries[1].labels
2043 );
2044 assert_eq!(
2045 timeseries[1].samples,
2046 vec![Sample {
2047 value: 7.0,
2048 timestamp: 2000,
2049 }]
2050 );
2051 }
2052
2053 #[test]
2054 fn test_recordbatches_to_timeseries_merges_across_batches() {
2055 let schema = Arc::new(Schema::new(vec![
2056 ColumnSchema::new(
2057 greptime_timestamp(),
2058 ConcreteDataType::timestamp_millisecond_datatype(),
2059 true,
2060 ),
2061 ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true),
2062 ColumnSchema::new("instance", ConcreteDataType::string_datatype(), true),
2063 ]));
2064
2065 let recordbatches = RecordBatches::try_new(
2066 schema.clone(),
2067 vec![
2068 RecordBatch::new(
2069 schema.clone(),
2070 vec![
2071 Arc::new(TimestampMillisecondVector::from_vec(vec![3000, 1500])) as _,
2072 Arc::new(Float64Vector::from_vec(vec![30.0, 15.0])) as _,
2073 Arc::new(StringVector::from(vec!["host1", "host2"])) as _,
2074 ],
2075 )
2076 .unwrap(),
2077 RecordBatch::new(
2078 schema.clone(),
2079 vec![
2080 Arc::new(TimestampMillisecondVector::from_vec(vec![1000])) as _,
2081 Arc::new(Float64Vector::from_vec(vec![10.0])) as _,
2082 Arc::new(StringVector::from(vec!["host1"])) as _,
2083 ],
2084 )
2085 .unwrap(),
2086 RecordBatch::new(
2087 schema,
2088 vec![
2089 Arc::new(TimestampMillisecondVector::from_vec(vec![2000, 2500])) as _,
2090 Arc::new(Float64Vector::from_vec(vec![20.0, 25.0])) as _,
2091 Arc::new(StringVector::from(vec!["host1", "host2"])) as _,
2092 ],
2093 )
2094 .unwrap(),
2095 ],
2096 )
2097 .unwrap();
2098
2099 let timeseries = recordbatches_to_timeseries(
2100 "cpu_usage",
2101 greptime_timestamp(),
2102 greptime_value(),
2103 recordbatches,
2104 )
2105 .unwrap();
2106
2107 assert_eq!(2, timeseries.len());
2108
2109 assert_eq!(
2110 vec![
2111 Label {
2112 name: METRIC_NAME_LABEL.to_string(),
2113 value: "cpu_usage".to_string(),
2114 },
2115 Label {
2116 name: "instance".to_string(),
2117 value: "host1".to_string(),
2118 },
2119 ],
2120 timeseries[0].labels
2121 );
2122 assert_eq!(
2123 vec![
2124 Sample {
2125 value: 10.0,
2126 timestamp: 1000,
2127 },
2128 Sample {
2129 value: 20.0,
2130 timestamp: 2000,
2131 },
2132 Sample {
2133 value: 30.0,
2134 timestamp: 3000,
2135 },
2136 ],
2137 timeseries[0].samples
2138 );
2139
2140 assert_eq!(
2141 vec![
2142 Label {
2143 name: METRIC_NAME_LABEL.to_string(),
2144 value: "cpu_usage".to_string(),
2145 },
2146 Label {
2147 name: "instance".to_string(),
2148 value: "host2".to_string(),
2149 },
2150 ],
2151 timeseries[1].labels
2152 );
2153 assert_eq!(
2154 vec![
2155 Sample {
2156 value: 15.0,
2157 timestamp: 1500,
2158 },
2159 Sample {
2160 value: 25.0,
2161 timestamp: 2500,
2162 },
2163 ],
2164 timeseries[1].samples
2165 );
2166 }
2167
2168 #[test]
2169 fn test_recordbatches_to_timeseries_borrows_and_groups_dictionary_labels() {
2170 let arrow_schema = Arc::new(ArrowSchema::new(vec![
2171 Field::new(
2172 greptime_timestamp(),
2173 ArrowDataType::Timestamp(arrow::datatypes::TimeUnit::Millisecond, None),
2174 false,
2175 ),
2176 Field::new(greptime_value(), ArrowDataType::Float64, false),
2177 Field::new_dictionary("instance", ArrowDataType::UInt32, ArrowDataType::Utf8, true),
2178 ]));
2179 let schema = Arc::new(Schema::try_from(arrow_schema.clone()).unwrap());
2180 let instance = DictionaryArray::<UInt32Type>::new(
2181 UInt32Array::from(vec![Some(0), None, Some(1), Some(0), Some(2)]),
2182 Arc::new(StringArray::from(vec![Some("host2"), Some("host1"), None])),
2183 );
2184 let batch = DfRecordBatch::try_new(
2185 arrow_schema,
2186 vec![
2187 Arc::new(TimestampMillisecondArray::from(vec![
2188 1000, 2000, 3000, 4000, 5000,
2189 ])),
2190 Arc::new(Float64Array::from(vec![1.0, 2.0, 3.0, 4.0, 5.0])),
2191 Arc::new(instance),
2192 ],
2193 )
2194 .unwrap();
2195 let recordbatch = RecordBatch::from_df_record_batch(schema.clone(), batch);
2196 let columns = label_columns(&recordbatch, greptime_timestamp(), greptime_value()).unwrap();
2197 assert!(matches!(
2198 columns[0].values,
2199 LabelValues::DictionaryUtf8 { .. }
2200 ));
2201 drop(columns);
2202 let recordbatches = RecordBatches::try_new(schema, vec![recordbatch]).unwrap();
2203
2204 let timeseries = recordbatches_to_timeseries(
2205 "metric1",
2206 greptime_timestamp(),
2207 greptime_value(),
2208 recordbatches,
2209 )
2210 .unwrap();
2211
2212 assert_eq!(3, timeseries.len());
2213 assert_eq!(
2214 vec![Label {
2215 name: METRIC_NAME_LABEL.to_string(),
2216 value: "metric1".to_string(),
2217 }],
2218 timeseries[0].labels
2219 );
2220 assert_eq!(
2221 vec![
2222 Sample {
2223 value: 2.0,
2224 timestamp: 2000,
2225 },
2226 Sample {
2227 value: 5.0,
2228 timestamp: 5000,
2229 },
2230 ],
2231 timeseries[0].samples
2232 );
2233 assert_eq!("host1", timeseries[1].labels[1].value);
2234 assert_eq!(
2235 vec![Sample {
2236 value: 3.0,
2237 timestamp: 3000,
2238 }],
2239 timeseries[1].samples
2240 );
2241 assert_eq!("host2", timeseries[2].labels[1].value);
2242 assert_eq!(
2243 vec![
2244 Sample {
2245 value: 1.0,
2246 timestamp: 1000,
2247 },
2248 Sample {
2249 value: 4.0,
2250 timestamp: 4000,
2251 },
2252 ],
2253 timeseries[2].samples
2254 );
2255 }
2256
2257 #[test]
2258 fn test_recordbatches_to_timeseries_groups_non_contiguous_series() {
2259 let schema = Arc::new(Schema::new(vec![
2260 ColumnSchema::new(
2261 greptime_timestamp(),
2262 ConcreteDataType::timestamp_millisecond_datatype(),
2263 true,
2264 ),
2265 ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true),
2266 ColumnSchema::new("instance", ConcreteDataType::string_datatype(), true),
2267 ]));
2268 let recordbatch = RecordBatch::new(
2269 schema.clone(),
2270 vec![
2271 Arc::new(TimestampMillisecondVector::from_vec(vec![1000, 2000, 3000])) as _,
2272 Arc::new(Float64Vector::from_vec(vec![1.0, 2.0, 3.0])) as _,
2273 Arc::new(StringVector::from(vec!["host2", "host1", "host2"])) as _,
2274 ],
2275 )
2276 .unwrap();
2277
2278 let recordbatches = RecordBatches::try_new(schema, vec![recordbatch]).unwrap();
2279 let timeseries = recordbatches_to_timeseries(
2280 "metric1",
2281 greptime_timestamp(),
2282 greptime_value(),
2283 recordbatches,
2284 )
2285 .unwrap();
2286
2287 assert_eq!("host1", timeseries[0].labels[1].value);
2289 assert_eq!("host2", timeseries[1].labels[1].value);
2290 assert_eq!(
2291 vec![
2292 Sample {
2293 value: 1.0,
2294 timestamp: 1000,
2295 },
2296 Sample {
2297 value: 3.0,
2298 timestamp: 3000,
2299 },
2300 ],
2301 timeseries[1].samples
2302 );
2303 }
2304
2305 #[test]
2306 fn test_recordbatches_to_timeseries_arrow_label_types_and_nulls() {
2307 let schema = Arc::new(Schema::new(vec![
2308 ColumnSchema::new(
2309 greptime_timestamp(),
2310 ConcreteDataType::timestamp_millisecond_datatype(),
2311 true,
2312 ),
2313 ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true),
2314 ColumnSchema::new("instance", ConcreteDataType::large_string_datatype(), true),
2315 ColumnSchema::new("zone", ConcreteDataType::utf8_view_datatype(), true),
2316 ColumnSchema::new("shard", ConcreteDataType::int32_datatype(), true),
2317 ]));
2318 let recordbatch = RecordBatch::new(
2319 schema.clone(),
2320 vec![
2321 Arc::new(TimestampMillisecondVector::from_vec(vec![1000, 2000, 3000])) as _,
2322 Arc::new(Float64Vector::from_vec(vec![1.0, 2.0, 3.0])) as _,
2323 Arc::new(StringVector::from(LargeStringArray::from(vec![
2324 "host2", "host1", "host2",
2325 ]))) as _,
2326 Arc::new(StringVector::from(StringViewArray::from(vec![
2327 Some("west"),
2328 None,
2329 Some("west"),
2330 ]))) as _,
2331 Arc::new(Int32Vector::from_vec(vec![2, 1, 2])) as _,
2332 ],
2333 )
2334 .unwrap();
2335
2336 let recordbatches = RecordBatches::try_new(schema, vec![recordbatch]).unwrap();
2337 let timeseries = recordbatches_to_timeseries(
2338 "metric1",
2339 greptime_timestamp(),
2340 greptime_value(),
2341 recordbatches,
2342 )
2343 .unwrap();
2344
2345 assert_eq!(2, timeseries.len());
2346 assert_eq!(
2347 vec![
2348 new_label(METRIC_NAME_LABEL.to_string(), "metric1".to_string()),
2349 new_label("instance".to_string(), "host1".to_string()),
2350 new_label("shard".to_string(), "1".to_string()),
2351 ],
2352 timeseries[0].labels
2353 );
2354 assert_eq!(
2355 vec![
2356 new_label(METRIC_NAME_LABEL.to_string(), "metric1".to_string()),
2357 new_label("instance".to_string(), "host2".to_string()),
2358 new_label("zone".to_string(), "west".to_string()),
2359 new_label("shard".to_string(), "2".to_string()),
2360 ],
2361 timeseries[1].labels
2362 );
2363 assert_eq!(
2364 vec![
2365 Sample {
2366 value: 1.0,
2367 timestamp: 1000,
2368 },
2369 Sample {
2370 value: 3.0,
2371 timestamp: 3000,
2372 },
2373 ],
2374 timeseries[1].samples
2375 );
2376 }
2377
2378 #[tokio::test]
2379 async fn test_snappy_varint_bomb_is_rejected_before_allocation() {
2380 let bomb = [0xffu8, 0xff, 0xff, 0xff, 0x0f];
2384 let limiter = ServerMemoryLimiter::default();
2385
2386 let err = snappy_decompress_limited(&bomb, MAX_DECOMPRESSED_REQUEST_SIZE, &limiter, 0)
2387 .await
2388 .unwrap_err();
2389 assert!(matches!(
2390 err,
2391 crate::error::Error::DecompressedBodyTooLarge { .. }
2392 ));
2393 assert_eq!(0, limiter.used_bytes());
2394 }
2395
2396 #[tokio::test]
2397 async fn test_snappy_decompress_limited_roundtrip() {
2398 let raw = vec![b'a'; 1024 * 1024];
2399 let mut encoder = snap::raw::Encoder::new();
2400 let compressed = encoder.compress_vec(&raw).unwrap();
2401 let limiter = ServerMemoryLimiter::default();
2402
2403 let decoded =
2404 snappy_decompress_limited(&compressed, MAX_DECOMPRESSED_REQUEST_SIZE, &limiter, 0)
2405 .await
2406 .unwrap();
2407 assert_eq!(&decoded[..], &raw[..]);
2408 }
2409
2410 #[tokio::test]
2411 async fn test_snappy_charges_and_releases_quota() {
2412 let raw = vec![b'a'; 64 * 1024];
2413 let mut encoder = snap::raw::Encoder::new();
2414 let compressed = encoder.compress_vec(&raw).unwrap();
2415 let limiter = ServerMemoryLimiter::new(128 * 1024, OnExhaustedPolicy::Fail);
2416
2417 let decoded =
2418 snappy_decompress_limited(&compressed, MAX_DECOMPRESSED_REQUEST_SIZE, &limiter, 0)
2419 .await
2420 .unwrap();
2421 assert!(limiter.used_bytes() >= 64 * 1024);
2423 assert!(limiter.used_bytes() <= 65 * 1024);
2424 drop(decoded);
2425 assert_eq!(0, limiter.used_bytes());
2426 }
2427
2428 #[tokio::test]
2429 async fn test_snappy_rejects_when_quota_exhausted() {
2430 let raw = vec![b'a'; 64 * 1024];
2431 let mut encoder = snap::raw::Encoder::new();
2432 let compressed = encoder.compress_vec(&raw).unwrap();
2433 let limiter = ServerMemoryLimiter::new(1024, OnExhaustedPolicy::Fail);
2435
2436 let err =
2437 snappy_decompress_limited(&compressed, MAX_DECOMPRESSED_REQUEST_SIZE, &limiter, 0)
2438 .await
2439 .unwrap_err();
2440 assert!(matches!(
2441 err,
2442 crate::error::Error::MemoryLimitExceeded { .. }
2443 ));
2444 assert_eq!(0, limiter.used_bytes(), "failed charge must be released");
2445 }
2446
2447 #[tokio::test]
2448 async fn test_zstd_at_and_over_the_cap() {
2449 let limiter = ServerMemoryLimiter::default();
2450 let small_cap = 8 * 1024;
2451
2452 let raw = vec![0u8; small_cap];
2454 let compressed = zstd::stream::encode_all(&raw[..], 3).unwrap();
2455 let decoded = zstd_decompress_limited(&compressed, small_cap, &limiter, 0)
2456 .await
2457 .unwrap();
2458 assert_eq!(decoded.len(), small_cap);
2459
2460 let raw = vec![0u8; small_cap + 1];
2462 let compressed = zstd::stream::encode_all(&raw[..], 3).unwrap();
2463 let err = zstd_decompress_limited(&compressed, small_cap, &limiter, 0)
2464 .await
2465 .unwrap_err();
2466 assert!(matches!(
2467 err,
2468 crate::error::Error::DecompressedBodyTooLarge { .. }
2469 ));
2470 }
2471
2472 #[tokio::test(start_paused = true)]
2473 async fn test_zstd_charges_incrementally() {
2474 let raw = vec![0u8; 2 * 1024 * 1024];
2477 let compressed = zstd::stream::encode_all(&raw[..], 19).unwrap();
2478 assert!(compressed.len() < 16 * 1024);
2479 let limiter = ServerMemoryLimiter::new(4 * 1024 * 1024, OnExhaustedPolicy::Fail);
2480
2481 let decoded =
2482 zstd_decompress_limited(&compressed, MAX_DECOMPRESSED_REQUEST_SIZE, &limiter, 0)
2483 .await
2484 .unwrap();
2485 assert_eq!(decoded.len(), raw.len());
2486 assert!(limiter.used_bytes() >= raw.len() as u64);
2487 drop(decoded);
2488 assert_eq!(0, limiter.used_bytes());
2489 let limiter = ServerMemoryLimiter::new(
2491 (2 * ZSTD_DECOMPRESS_CHUNK) as u64,
2492 OnExhaustedPolicy::default(),
2493 );
2494 let started = tokio::time::Instant::now();
2495 let error =
2496 zstd_decompress_limited(&compressed, MAX_DECOMPRESSED_REQUEST_SIZE, &limiter, 0)
2497 .await
2498 .unwrap_err();
2499 assert!(matches!(error, error::Error::MemoryLimitExceeded { .. }));
2500 assert_eq!(started.elapsed(), std::time::Duration::ZERO);
2501 assert_eq!(limiter.used_bytes(), 0);
2502 }
2503
2504 #[test]
2505 fn test_snappy_response_decompress_is_capped() {
2506 let bomb = [0xffu8, 0xff, 0xff, 0xff, 0x0f];
2508 let err = snappy_decompress(&bomb).unwrap_err();
2509 assert!(matches!(
2510 err,
2511 crate::error::Error::DecompressedBodyTooLarge { .. }
2512 ));
2513 }
2514}