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, 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::prelude::{greptime_timestamp, greptime_value};
37use common_recordbatch::{RecordBatch, RecordBatches};
38use common_telemetry::{tracing, warn};
39use datafusion::dataframe::DataFrame;
40use datafusion::prelude::{Expr, ident, lit, regexp_match};
41use datafusion_common::ScalarValue;
42use datafusion_expr::LogicalPlan;
43use snafu::{OptionExt, ResultExt, ensure};
44use snap::raw::{Decoder, Encoder};
45
46use crate::error::{self, Result};
47use crate::prom_remote_write::REMOTE_WRITE_V1_VERSION;
48use crate::request_memory_limiter::ServerMemoryLimiter;
49use crate::request_memory_metrics::RequestMemoryMetrics;
50use crate::row_writer::{self, MultiTableData};
51
52pub const METRIC_NAME_LABEL: &str = "__name__";
53pub const METRIC_NAME_LABEL_BYTES: &[u8] = b"__name__";
54
55pub const DATABASE_LABEL: &str = "x_greptime_database";
57pub const DATABASE_LABEL_BYTES: &[u8] = b"x_greptime_database";
58pub const DATABASE_LABEL_ALT: &str = "__database__";
59pub const DATABASE_LABEL_ALT_BYTES: &[u8] = b"__database__";
60
61#[deprecated(note = "use DATABASE_LABEL instead")]
63pub const SCHEMA_LABEL: &str = "__schema__";
64#[deprecated(note = "use DATABASE_LABEL_BYTES instead")]
65pub const SCHEMA_LABEL_BYTES: &[u8] = b"__schema__";
66
67pub const PHYSICAL_TABLE_LABEL: &str = "x_greptime_physical_table";
69pub const PHYSICAL_TABLE_LABEL_BYTES: &[u8] = b"x_greptime_physical_table";
70pub const PHYSICAL_TABLE_LABEL_ALT: &str = "__physical_table__";
71pub const PHYSICAL_TABLE_LABEL_ALT_BYTES: &[u8] = b"__physical_table__";
72
73pub const FIELD_NAME_LABEL: &str = "__field__";
75
76#[allow(deprecated)]
78pub fn is_remote_write_special_label(label: &str) -> bool {
79 label == DATABASE_LABEL
80 || label == DATABASE_LABEL_ALT
81 || label == PHYSICAL_TABLE_LABEL
82 || label == PHYSICAL_TABLE_LABEL_ALT
83 || label == SCHEMA_LABEL
84}
85
86#[allow(deprecated)]
87pub fn is_remote_read_special_label(label: &str) -> bool {
88 label == METRIC_NAME_LABEL
89 || label == DATABASE_LABEL
90 || label == DATABASE_LABEL_ALT
91 || label == SCHEMA_LABEL
92}
93
94#[allow(deprecated)]
96pub fn is_database_selection_label(label: &str) -> bool {
97 label == DATABASE_LABEL || label == DATABASE_LABEL_ALT || label == SCHEMA_LABEL
98}
99
100pub fn is_physical_table_selection_label(label: &str) -> bool {
102 label == PHYSICAL_TABLE_LABEL || label == PHYSICAL_TABLE_LABEL_ALT
103}
104
105pub fn table_name(q: &Query) -> Result<String> {
107 let mut matchers = q
108 .matchers
109 .iter()
110 .filter(|matcher| matcher.name == METRIC_NAME_LABEL);
111 let matcher = matchers
112 .next()
113 .context(error::InvalidPromRemoteRequestSnafu {
114 msg: "missing '__name__' label in timeseries",
115 })?;
116
117 if matcher.r#type != MatcherType::Eq as i32
118 || matcher.value.is_empty()
119 || matchers.next().is_some()
120 {
121 return Err(error::InvalidPromRemoteRequestSnafu {
122 msg: "expected exactly one non-empty equality matcher for '__name__'".to_string(),
123 }
124 .build());
125 }
126
127 Ok(matcher.value.clone())
128}
129
130pub fn extract_schema_from_query(query: &Query) -> Option<String> {
132 query
133 .matchers
134 .iter()
135 .find(|matcher| {
136 is_database_selection_label(&matcher.name) && matcher.r#type == MatcherType::Eq as i32
137 })
138 .map(|matcher| matcher.value.clone())
139}
140
141#[tracing::instrument(skip_all)]
143pub fn query_to_plan(
144 dataframe: DataFrame,
145 q: &Query,
146 timestamp_column_name: &str,
147) -> Result<LogicalPlan> {
148 let start_timestamp_ms = q.start_timestamp_ms;
149 let end_timestamp_ms = q.end_timestamp_ms;
150
151 let label_matches = &q.matchers;
152
153 let mut conditions = Vec::with_capacity(label_matches.len() + 1);
154
155 conditions
156 .push(ident(timestamp_column_name).gt_eq(lit_timestamp_millisecond(start_timestamp_ms)));
157 conditions
158 .push(ident(timestamp_column_name).lt_eq(lit_timestamp_millisecond(end_timestamp_ms)));
159
160 for m in label_matches {
161 let name = &m.name;
162
163 if is_remote_read_special_label(name) {
164 continue;
165 }
166
167 let value = &m.value;
168 let m_type = MatcherType::try_from(m.r#type).map_err(|e| {
169 error::InvalidPromRemoteRequestSnafu {
170 msg: format!("invalid LabelMatcher type, decode error: {e}",),
171 }
172 .build()
173 })?;
174
175 match m_type {
176 MatcherType::Eq => {
177 conditions.push(ident(name).eq(lit(value)));
178 }
179 MatcherType::Neq => {
180 conditions.push(ident(name).not_eq(lit(value)));
181 }
182 MatcherType::Re => {
184 conditions.push(regexp_match(ident(name), lit(value), None).is_not_null());
185 }
186 MatcherType::Nre => {
188 conditions.push(regexp_match(ident(name), lit(value), None).is_null());
189 }
190 }
191 }
192
193 let conditions = conditions.into_iter().reduce(Expr::and).unwrap();
195
196 let dataframe = dataframe
197 .filter(conditions)
198 .context(error::DataFrameSnafu)?;
199
200 Ok(dataframe.into_parts().1)
201}
202
203#[inline]
204fn new_label(name: String, value: String) -> Label {
205 Label { name, value }
206}
207
208fn lit_timestamp_millisecond(ts: i64) -> Expr {
209 Expr::Literal(ScalarValue::TimestampMillisecond(Some(ts), None), None)
210}
211
212fn compare_timeseries_labels(left: &[Label], right: &[Label]) -> Ordering {
214 let ordering = left.len().cmp(&right.len());
215 if ordering != Ordering::Equal {
216 return ordering;
217 }
218
219 for (left, right) in left.iter().zip(right) {
220 let ordering = left.name.cmp(&right.name);
221 if ordering != Ordering::Equal {
222 return ordering;
223 }
224
225 let ordering = left.value.cmp(&right.value);
226 if ordering != Ordering::Equal {
227 return ordering;
228 }
229 }
230
231 Ordering::Equal
232}
233
234enum LabelValues<'a> {
235 Utf8(&'a StringArray),
236 LargeUtf8(&'a LargeStringArray),
237 Utf8View(&'a StringViewArray),
238 DictionaryUtf8 {
239 dictionary: &'a DictionaryArray<UInt32Type>,
240 values: &'a StringArray,
241 },
242 Other(Vec<Option<String>>),
243}
244
245impl LabelValues<'_> {
246 fn value(&self, row: usize) -> Option<&str> {
247 match self {
248 Self::Utf8(values) => values.is_valid(row).then(|| values.value(row)),
249 Self::LargeUtf8(values) => values.is_valid(row).then(|| values.value(row)),
250 Self::Utf8View(values) => values.is_valid(row).then(|| values.value(row)),
251 Self::DictionaryUtf8 { dictionary, values } => dictionary
252 .key(row)
253 .and_then(|key| values.is_valid(key).then(|| values.value(key))),
254 Self::Other(values) => values.get(row).and_then(Option::as_deref),
255 }
256 }
257}
258
259fn row_labels<'a>(
260 columns: &'a [LabelColumn<'a>],
261 row: usize,
262) -> impl Iterator<Item = (&'a str, &'a str)> {
263 columns
264 .iter()
265 .filter_map(move |column| column.values.value(row).map(|value| (column.name, value)))
266}
267
268struct LabelColumn<'a> {
269 name: &'a str,
270 values: LabelValues<'a>,
271}
272
273fn label_columns<'a>(
274 recordbatch: &'a RecordBatch,
275 timestamp_column_name: &str,
276 value_column_name: &str,
277) -> Result<Vec<LabelColumn<'a>>> {
278 recordbatch
279 .schema
280 .column_schemas()
281 .iter()
282 .enumerate()
283 .filter(|(_, column_schema)| {
284 column_schema.name != timestamp_column_name && column_schema.name != value_column_name
285 })
286 .map(|(index, column_schema)| {
287 let array = recordbatch.column(index);
288 let values = match array.data_type() {
289 arrow::datatypes::DataType::Utf8 => LabelValues::Utf8(array.as_string::<i32>()),
290 arrow::datatypes::DataType::LargeUtf8 => {
291 LabelValues::LargeUtf8(array.as_string::<i64>())
292 }
293 arrow::datatypes::DataType::Utf8View => {
294 LabelValues::Utf8View(array.as_string_view())
295 }
296 arrow::datatypes::DataType::Dictionary(key, value)
297 if key.as_ref() == &arrow::datatypes::DataType::UInt32
298 && value.as_ref() == &arrow::datatypes::DataType::Utf8 =>
299 {
300 let dictionary = array.as_dictionary::<UInt32Type>();
301 LabelValues::DictionaryUtf8 {
302 dictionary,
303 values: dictionary.values().as_string::<i32>(),
304 }
305 }
306 _ => {
307 let values = recordbatch.iter_column_as_string(index).collect::<Vec<_>>();
308 ensure!(
309 values.len() == recordbatch.num_rows(),
310 error::InvalidPromRemoteReadQueryResultSnafu {
311 msg: format!(
312 "Cannot convert label column '{}' of datatype {:?} to string",
313 column_schema.name,
314 array.data_type()
315 ),
316 }
317 );
318 LabelValues::Other(values)
319 }
320 };
321 Ok(LabelColumn {
322 name: &column_schema.name,
323 values,
324 })
325 })
326 .collect()
327}
328
329fn hash_timeseries(columns: &[LabelColumn<'_>], row: usize) -> u64 {
330 let mut hasher = DefaultHasher::new();
331
332 for (name, value) in row_labels(columns, row) {
333 name.hash(&mut hasher);
334 value.hash(&mut hasher);
335 }
336
337 hasher.finish()
338}
339
340fn matches_timeseries(labels: &[Label], columns: &[LabelColumn<'_>], row: usize) -> bool {
341 let mut labels = labels.iter().skip(1);
342 for (name, value) in row_labels(columns, row) {
343 let Some(label) = labels.next() else {
344 return false;
345 };
346 if label.name != name || label.value != value {
347 return false;
348 }
349 }
350
351 labels.next().is_none()
352}
353
354fn new_timeseries(table: &str, columns: &[LabelColumn<'_>], row: usize) -> TimeSeries {
355 let mut labels = Vec::with_capacity(columns.len() + 1);
356 labels.push(new_label(METRIC_NAME_LABEL.to_string(), table.to_string()));
357
358 for (name, value) in row_labels(columns, row) {
359 labels.push(new_label(name.to_string(), value.to_string()));
360 }
361
362 TimeSeries {
363 labels,
364 ..Default::default()
365 }
366}
367
368pub fn recordbatches_to_timeseries(
369 table_name: &str,
370 timestamp_column_name: &str,
371 value_column_name: &str,
372 recordbatches: RecordBatches,
373) -> Result<Vec<TimeSeries>> {
374 let mut timeseries: Vec<TimeSeries> = Vec::new();
375 let mut timeseries_by_hash: HashMap<u64, Vec<usize>> = HashMap::new();
376
377 for recordbatch in recordbatches.take() {
378 append_recordbatch_to_timeseries(
379 table_name,
380 timestamp_column_name,
381 value_column_name,
382 recordbatch,
383 &mut timeseries,
384 &mut timeseries_by_hash,
385 )?;
386 }
387
388 for ts in &mut timeseries {
389 ts.samples.sort_unstable_by_key(|s| s.timestamp);
390 }
391
392 timeseries
393 .sort_unstable_by(|left, right| compare_timeseries_labels(&left.labels, &right.labels));
394 Ok(timeseries)
395}
396
397fn append_recordbatch_to_timeseries(
398 table: &str,
399 timestamp_column_name: &str,
400 value_column_name: &str,
401 recordbatch: RecordBatch,
402 timeseries: &mut Vec<TimeSeries>,
403 timeseries_by_hash: &mut HashMap<u64, Vec<usize>>,
404) -> Result<()> {
405 let ts_column = recordbatch
406 .column_by_name(timestamp_column_name)
407 .with_context(|| error::InvalidPromRemoteReadQueryResultSnafu {
408 msg: format!("missing timestamp column '{timestamp_column_name}' in query result"),
409 })?;
410 let ts_column: ArrayRef = match ts_column.data_type() {
419 DataType::Timestamp(TimeUnit::Millisecond, _) => ts_column.clone(),
420 DataType::Timestamp(TimeUnit::Second, _) => casts::cast(
422 ts_column,
423 &DataType::Timestamp(TimeUnit::Millisecond, None),
424 )
425 .map_err(|e| {
426 error::InvalidPromRemoteReadQueryResultSnafu {
427 msg: format!(
428 "failed to cast timestamp column '{timestamp_column_name}' of datatype {:?} to millisecond: {e}",
429 ts_column.data_type()
430 ),
431 }
432 .build()
433 })?,
434 DataType::Timestamp(TimeUnit::Microsecond, _) => Arc::new(
435 ts_column
436 .as_primitive::<TimestampMicrosecondType>()
437 .unary::<_, TimestampMillisecondType>(|v| v.div_euclid(1_000)),
438 ),
439 DataType::Timestamp(TimeUnit::Nanosecond, _) => Arc::new(
440 ts_column
441 .as_primitive::<TimestampNanosecondType>()
442 .unary::<_, TimestampMillisecondType>(|v| v.div_euclid(1_000_000)),
443 ),
444 _ => {
445 return error::InvalidPromRemoteReadQueryResultSnafu {
446 msg: format!(
447 "Expect timestamp column of datatype Timestamp(Millisecond), actual {:?}",
448 ts_column.data_type()
449 ),
450 }
451 .fail();
452 }
453 };
454 let ts_column = ts_column
455 .as_primitive_opt::<TimestampMillisecondType>()
456 .with_context(|| error::InvalidPromRemoteReadQueryResultSnafu {
457 msg: format!(
458 "Expect timestamp column of datatype Timestamp(Millisecond), actual {:?}",
459 ts_column.data_type()
460 ),
461 })?;
462
463 let field_column = recordbatch
466 .column_by_name(value_column_name)
467 .with_context(|| error::InvalidPromRemoteReadQueryResultSnafu {
468 msg: format!("missing value column '{value_column_name}' in query result"),
469 })?;
470 let field_column = field_column
471 .as_primitive_opt::<Float64Type>()
472 .with_context(|| error::InvalidPromRemoteReadQueryResultSnafu {
473 msg: format!(
474 "Expect value column of datatype Float64, actual {:?}",
475 field_column.data_type()
476 ),
477 })?;
478
479 let columns = label_columns(&recordbatch, timestamp_column_name, value_column_name)?;
480 let mut previous_timeseries: Option<usize> = None;
481
482 for row in 0..recordbatch.num_rows() {
483 let timeseries_index = match previous_timeseries {
484 Some(index) if matches_timeseries(×eries[index].labels, &columns, row) => index,
485 _ => {
486 let hash = hash_timeseries(&columns, row);
487 let candidates = timeseries_by_hash.entry(hash).or_default();
488 match candidates
489 .iter()
490 .copied()
491 .find(|index| matches_timeseries(×eries[*index].labels, &columns, row))
492 {
493 Some(index) => index,
494 None => {
495 let index = timeseries.len();
496 timeseries.push(new_timeseries(table, &columns, row));
497 candidates.push(index);
498 index
499 }
500 }
501 }
502 };
503 previous_timeseries = Some(timeseries_index);
504
505 if ts_column.is_null(row) || field_column.is_null(row) {
506 continue;
507 }
508
509 let value = field_column.value(row);
510 let timestamp = ts_column.value(row);
511 let sample = Sample { value, timestamp };
512
513 timeseries[timeseries_index].samples.push(sample);
514 }
515
516 Ok(())
517}
518
519pub fn to_grpc_row_insert_requests(request: &WriteRequest) -> Result<(RowInsertRequests, usize)> {
520 let _timer = crate::metrics::METRIC_HTTP_PROM_STORE_CODEC_ELAPSED
521 .with_label_values(&["convert", REMOTE_WRITE_V1_VERSION])
522 .start_timer();
523
524 let mut multi_table_data = MultiTableData::new();
525
526 for series in &request.timeseries {
527 let table_name = &series
528 .labels
529 .iter()
530 .find(|label| {
531 label.name == METRIC_NAME_LABEL
533 })
534 .context(error::InvalidPromRemoteRequestSnafu {
535 msg: "missing '__name__' label in time-series",
536 })?
537 .value;
538
539 let num_columns = series.labels.len() + 1;
542
543 let table_data = multi_table_data.get_or_default_table_data(
544 table_name,
545 num_columns,
546 series.samples.len(),
547 );
548
549 let kvs = series.labels.iter().filter_map(|label| {
551 if label.name == METRIC_NAME_LABEL {
552 None
553 } else {
554 Some((label.name.clone(), label.value.clone()))
555 }
556 });
557
558 if series.samples.len() == 1 {
559 let mut one_row = table_data.alloc_one_row();
560
561 row_writer::write_tags(table_data, kvs, &mut one_row)?;
562 row_writer::write_f64(
564 table_data,
565 greptime_value(),
566 series.samples[0].value,
567 &mut one_row,
568 )?;
569 row_writer::write_ts_to_millis(
571 table_data,
572 greptime_timestamp(),
573 Some(series.samples[0].timestamp),
574 Precision::Millisecond,
575 &mut one_row,
576 )?;
577
578 table_data.add_row(one_row);
579 } else {
580 for Sample { value, timestamp } in &series.samples {
581 let mut one_row = table_data.alloc_one_row();
582
583 let kvs = kvs.clone();
585 row_writer::write_tags(table_data, kvs, &mut one_row)?;
586 row_writer::write_f64(table_data, greptime_value(), *value, &mut one_row)?;
588 row_writer::write_ts_to_millis(
590 table_data,
591 greptime_timestamp(),
592 Some(*timestamp),
593 Precision::Millisecond,
594 &mut one_row,
595 )?;
596
597 table_data.add_row(one_row);
598 }
599 }
600
601 if !series.histograms.is_empty() {
602 warn!("Native histograms are not supported yet, data ignored");
603 }
604 }
605
606 Ok(multi_table_data.into_row_insert_requests())
607}
608
609pub const MAX_DECOMPRESSED_REQUEST_SIZE: usize = 512 * 1024 * 1024;
618
619const ZSTD_DECOMPRESS_CHUNK: usize = 256 * 1024;
621
622pub(crate) struct ChargedBuffer {
626 pub data: Vec<u8>,
627 guards: Vec<MemoryGuard<RequestMemoryMetrics>>,
628}
629
630impl std::ops::Deref for ChargedBuffer {
631 type Target = [u8];
632
633 fn deref(&self) -> &Self::Target {
634 &self.data
635 }
636}
637
638impl std::fmt::Debug for ChargedBuffer {
639 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
640 f.debug_struct("ChargedBuffer")
641 .field("data_len", &self.data.len())
642 .field("guards", &self.guards.len())
643 .finish()
644 }
645}
646
647impl ChargedBuffer {
648 pub(crate) fn new(data: Vec<u8>, guards: Vec<MemoryGuard<RequestMemoryMetrics>>) -> Self {
650 Self { data, guards }
651 }
652
653 pub(crate) fn into_parts(self) -> (Vec<u8>, Vec<MemoryGuard<RequestMemoryMetrics>>) {
656 (self.data, self.guards)
657 }
658}
659
660pub(crate) async fn snappy_decompress_limited(
667 buf: &[u8],
668 max_decompressed: usize,
669 limiter: &ServerMemoryLimiter,
670) -> Result<ChargedBuffer> {
671 let decoded_len =
672 snap::raw::decompress_len(buf).context(error::DecompressSnappyPromRemoteRequestSnafu)?;
673 ensure!(
674 decoded_len <= max_decompressed,
675 error::DecompressedBodyTooLargeSnafu {
676 size: decoded_len as u64,
677 limit: max_decompressed as u64,
678 }
679 );
680 let guard = limiter.acquire(decoded_len as u64).await?;
683 let mut decoder = Decoder::new();
684 let data = decoder
685 .decompress_vec(buf)
686 .context(error::DecompressSnappyPromRemoteRequestSnafu)?;
687 Ok(ChargedBuffer {
688 data,
689 guards: vec![guard],
690 })
691}
692
693pub(crate) async fn zstd_decompress_limited(
697 buf: &[u8],
698 max_decompressed: usize,
699 limiter: &ServerMemoryLimiter,
700) -> Result<ChargedBuffer> {
701 use std::io::Read;
702
703 let decoder = zstd::stream::read::Decoder::new(buf)
704 .context(error::DecompressZstdPromRemoteRequestSnafu)?;
705 let mut limited = decoder.take(max_decompressed as u64 + 1);
708 let mut data = Vec::new();
709 let mut guards = Vec::new();
710 let mut chunk = vec![0u8; ZSTD_DECOMPRESS_CHUNK];
711 loop {
712 let n = limited
713 .read(&mut chunk)
714 .context(error::DecompressZstdPromRemoteRequestSnafu)?;
715 if n == 0 {
716 break;
717 }
718 ensure!(
719 data.len() + n <= max_decompressed,
720 error::DecompressedBodyTooLargeSnafu {
721 size: (data.len() + n) as u64,
722 limit: max_decompressed as u64,
723 }
724 );
725 guards.push(limiter.acquire(n as u64).await?);
728 data.extend_from_slice(&chunk[..n]);
729 }
730 Ok(ChargedBuffer { data, guards })
731}
732
733#[inline]
740pub fn snappy_decompress(buf: &[u8]) -> Result<Vec<u8>> {
741 let decoded_len =
742 snap::raw::decompress_len(buf).context(error::DecompressSnappyPromRemoteRequestSnafu)?;
743 ensure!(
744 decoded_len <= MAX_DECOMPRESSED_REQUEST_SIZE,
745 error::DecompressedBodyTooLargeSnafu {
746 size: decoded_len as u64,
747 limit: MAX_DECOMPRESSED_REQUEST_SIZE as u64,
748 }
749 );
750 let mut decoder = Decoder::new();
751 decoder
752 .decompress_vec(buf)
753 .context(error::DecompressSnappyPromRemoteRequestSnafu)
754}
755
756#[inline]
757pub fn snappy_compress(buf: &[u8]) -> Result<Vec<u8>> {
758 let mut encoder = Encoder::new();
759 encoder
760 .compress_vec(buf)
761 .context(error::CompressPromRemoteRequestSnafu)
762}
763
764pub fn mock_timeseries() -> Vec<TimeSeries> {
767 vec![
768 TimeSeries {
769 labels: vec![
770 new_label(METRIC_NAME_LABEL.to_string(), "metric1".to_string()),
771 new_label("job".to_string(), "spark".to_string()),
772 ],
773 samples: vec![
774 Sample {
775 value: 1.0f64,
776 timestamp: 1000,
777 },
778 Sample {
779 value: 2.0f64,
780 timestamp: 2000,
781 },
782 ],
783 ..Default::default()
784 },
785 TimeSeries {
786 labels: vec![
787 new_label(METRIC_NAME_LABEL.to_string(), "metric2".to_string()),
788 new_label("instance".to_string(), "test_host1".to_string()),
789 new_label("idc".to_string(), "z001".to_string()),
790 ],
791 samples: vec![
792 Sample {
793 value: 3.0f64,
794 timestamp: 1000,
795 },
796 Sample {
797 value: 4.0f64,
798 timestamp: 2000,
799 },
800 ],
801 ..Default::default()
802 },
803 TimeSeries {
804 labels: vec![
805 new_label(METRIC_NAME_LABEL.to_string(), "metric3".to_string()),
806 new_label("idc".to_string(), "z002".to_string()),
807 new_label("app".to_string(), "biz".to_string()),
808 ],
809 samples: vec![
810 Sample {
811 value: 5.0f64,
812 timestamp: 1000,
813 },
814 Sample {
815 value: 6.0f64,
816 timestamp: 2000,
817 },
818 Sample {
819 value: 7.0f64,
820 timestamp: 3000,
821 },
822 ],
823 ..Default::default()
824 },
825 ]
826}
827
828pub fn mock_timeseries_new_label() -> Vec<TimeSeries> {
830 let ts_demo_metrics = TimeSeries {
831 labels: vec![
832 new_label(METRIC_NAME_LABEL.to_string(), "demo_metrics".to_string()),
833 new_label("idc".to_string(), "idc3".to_string()),
834 new_label("new_label1".to_string(), "foo".to_string()),
835 ],
836 samples: vec![Sample {
837 value: 42.0,
838 timestamp: 3000,
839 }],
840 ..Default::default()
841 };
842 let ts_multi_labels = TimeSeries {
843 labels: vec![
844 new_label(METRIC_NAME_LABEL.to_string(), "metric1".to_string()),
845 new_label("idc".to_string(), "idc4".to_string()),
846 new_label("env".to_string(), "prod".to_string()),
847 new_label("host".to_string(), "host9".to_string()),
848 new_label("new_label2".to_string(), "bar".to_string()),
849 ],
850 samples: vec![Sample {
851 value: 99.0,
852 timestamp: 4000,
853 }],
854 ..Default::default()
855 };
856
857 vec![ts_demo_metrics, ts_multi_labels]
858}
859
860pub fn mock_timeseries_special_labels() -> Vec<TimeSeries> {
862 let idc3_schema = TimeSeries {
863 labels: vec![
864 new_label(METRIC_NAME_LABEL.to_string(), "idc3_lo_table".to_string()),
865 new_label(DATABASE_LABEL.to_string(), "idc3".to_string()),
866 new_label(PHYSICAL_TABLE_LABEL.to_string(), "f1".to_string()),
867 ],
868 samples: vec![Sample {
869 value: 42.0,
870 timestamp: 3000,
871 }],
872 ..Default::default()
873 };
874 let idc4_schema = TimeSeries {
875 labels: vec![
876 new_label(
877 METRIC_NAME_LABEL.to_string(),
878 "idc4_local_table".to_string(),
879 ),
880 new_label(DATABASE_LABEL.to_string(), "idc4".to_string()),
881 new_label(PHYSICAL_TABLE_LABEL.to_string(), "f2".to_string()),
882 ],
883 samples: vec![Sample {
884 value: 99.0,
885 timestamp: 4000,
886 }],
887 ..Default::default()
888 };
889
890 vec![idc3_schema, idc4_schema]
891}
892
893#[cfg(test)]
894mod tests {
895 use std::sync::Arc;
896
897 use api::prom_store::remote::LabelMatcher;
898 use api::v1::{ColumnDataType, Row, SemanticType};
899 use arrow::array::{
900 DictionaryArray, Float64Array, StringArray, TimestampMillisecondArray, UInt32Array,
901 };
902 use arrow::datatypes::{DataType as ArrowDataType, Field, Schema as ArrowSchema, UInt32Type};
903 use common_memory_manager::OnExhaustedPolicy;
904 use common_recordbatch::DfRecordBatch;
905 use datafusion::prelude::SessionContext;
906 use datatypes::data_type::ConcreteDataType;
907 use datatypes::schema::{ColumnSchema, Schema};
908 use datatypes::vectors::{
909 Float64Vector, Int32Vector, StringVector, TimestampMillisecondVector,
910 };
911 use table::table::adapter::DfTableProviderAdapter;
912 use table::test_util::MemTable;
913
914 use super::*;
915
916 const EQ_TYPE: i32 = MatcherType::Eq as i32;
917 const NEQ_TYPE: i32 = MatcherType::Neq as i32;
918 const RE_TYPE: i32 = MatcherType::Re as i32;
919
920 #[test]
921 fn test_table_name() {
922 let q = Query {
923 start_timestamp_ms: 1000,
924 end_timestamp_ms: 2000,
925 matchers: vec![],
926 ..Default::default()
927 };
928 let err = table_name(&q).unwrap_err();
929 assert!(matches!(err, error::Error::InvalidPromRemoteRequest { .. }));
930
931 let q = Query {
932 start_timestamp_ms: 1000,
933 end_timestamp_ms: 2000,
934 matchers: vec![LabelMatcher {
935 name: METRIC_NAME_LABEL.to_string(),
936 value: "test".to_string(),
937 r#type: EQ_TYPE,
938 }],
939 ..Default::default()
940 };
941 assert_eq!("test", table_name(&q).unwrap());
942
943 for matchers in [
944 vec![LabelMatcher {
945 name: METRIC_NAME_LABEL.to_string(),
946 value: "test.*".to_string(),
947 r#type: RE_TYPE,
948 }],
949 vec![LabelMatcher {
950 name: METRIC_NAME_LABEL.to_string(),
951 value: String::new(),
952 r#type: EQ_TYPE,
953 }],
954 vec![
955 LabelMatcher {
956 name: METRIC_NAME_LABEL.to_string(),
957 value: "test".to_string(),
958 r#type: EQ_TYPE,
959 },
960 LabelMatcher {
961 name: METRIC_NAME_LABEL.to_string(),
962 value: "other".to_string(),
963 r#type: EQ_TYPE,
964 },
965 ],
966 ] {
967 let q = Query {
968 matchers,
969 ..Default::default()
970 };
971 assert!(matches!(
972 table_name(&q),
973 Err(error::Error::InvalidPromRemoteRequest { .. })
974 ));
975 }
976 }
977
978 #[test]
979 #[allow(deprecated)]
980 fn test_extract_schema_from_query() {
981 let query = Query::default();
982 assert_eq!(None, extract_schema_from_query(&query));
983
984 for label in [DATABASE_LABEL, DATABASE_LABEL_ALT, SCHEMA_LABEL] {
985 let query = Query {
986 matchers: vec![LabelMatcher {
987 name: label.to_string(),
988 value: "selected_schema".to_string(),
989 r#type: EQ_TYPE,
990 }],
991 ..Default::default()
992 };
993 assert_eq!(
994 Some("selected_schema".to_string()),
995 extract_schema_from_query(&query)
996 );
997 }
998
999 let query = Query {
1000 matchers: vec![LabelMatcher {
1001 name: DATABASE_LABEL.to_string(),
1002 value: "selected_schema".to_string(),
1003 r#type: NEQ_TYPE,
1004 }],
1005 ..Default::default()
1006 };
1007 assert_eq!(None, extract_schema_from_query(&query));
1008 }
1009
1010 #[test]
1011 fn test_query_to_plan() {
1012 let q = Query {
1013 start_timestamp_ms: 1000,
1014 end_timestamp_ms: 2000,
1015 matchers: vec![LabelMatcher {
1016 name: METRIC_NAME_LABEL.to_string(),
1017 value: "test".to_string(),
1018 r#type: EQ_TYPE,
1019 }],
1020 ..Default::default()
1021 };
1022
1023 let schema = Arc::new(Schema::new(vec![
1024 ColumnSchema::new(
1025 greptime_timestamp(),
1026 ConcreteDataType::timestamp_millisecond_datatype(),
1027 true,
1028 ),
1029 ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true),
1030 ColumnSchema::new("instance", ConcreteDataType::string_datatype(), true),
1031 ColumnSchema::new("job", ConcreteDataType::string_datatype(), true),
1032 ColumnSchema::new("service.name", ConcreteDataType::string_datatype(), true),
1033 ColumnSchema::new("Region", ConcreteDataType::string_datatype(), true),
1034 ]));
1035 let recordbatch = RecordBatch::new(
1036 schema,
1037 vec![
1038 Arc::new(TimestampMillisecondVector::from_vec(vec![1000])) as _,
1039 Arc::new(Float64Vector::from_vec(vec![3.0])) as _,
1040 Arc::new(StringVector::from(vec!["host1"])) as _,
1041 Arc::new(StringVector::from(vec!["job"])) as _,
1042 Arc::new(StringVector::from(vec!["api"])) as _,
1043 Arc::new(StringVector::from(vec!["us"])) as _,
1044 ],
1045 )
1046 .unwrap();
1047
1048 let ctx = SessionContext::new();
1049 let table = MemTable::table("test", recordbatch);
1050 let table_provider = Arc::new(DfTableProviderAdapter::new(table));
1051
1052 let dataframe = ctx.read_table(table_provider.clone()).unwrap();
1053 let plan = query_to_plan(dataframe, &q, greptime_timestamp()).unwrap();
1054 let display_string = format!("{}", plan.display_indent());
1055
1056 let ts_col = greptime_timestamp();
1057 let expected = format!(
1058 "Filter: ?table?.{} >= TimestampMillisecond(1000, None) AND ?table?.{} <= TimestampMillisecond(2000, None)\n TableScan: ?table?",
1059 ts_col, ts_col
1060 );
1061 assert_eq!(expected, display_string);
1062
1063 let q = Query {
1064 start_timestamp_ms: 1000,
1065 end_timestamp_ms: 2000,
1066 matchers: vec![
1067 LabelMatcher {
1068 name: METRIC_NAME_LABEL.to_string(),
1069 value: "test".to_string(),
1070 r#type: EQ_TYPE,
1071 },
1072 LabelMatcher {
1073 name: "job".to_string(),
1074 value: "*prom*".to_string(),
1075 r#type: RE_TYPE,
1076 },
1077 LabelMatcher {
1078 name: "instance".to_string(),
1079 value: "localhost".to_string(),
1080 r#type: NEQ_TYPE,
1081 },
1082 LabelMatcher {
1083 name: "service.name".to_string(),
1084 value: "api".to_string(),
1085 r#type: EQ_TYPE,
1086 },
1087 LabelMatcher {
1088 name: "Region".to_string(),
1089 value: "us".to_string(),
1090 r#type: EQ_TYPE,
1091 },
1092 ],
1093 ..Default::default()
1094 };
1095
1096 let dataframe = ctx.read_table(table_provider).unwrap();
1097 let plan = query_to_plan(dataframe, &q, greptime_timestamp()).unwrap();
1098 let display_string = format!("{}", plan.display_indent());
1099
1100 let ts_col = greptime_timestamp();
1101 let expected = format!(
1102 "Filter: ?table?.{} >= TimestampMillisecond(1000, None) AND ?table?.{} <= TimestampMillisecond(2000, None) AND regexp_match(?table?.job, Utf8(\"*prom*\")) IS NOT NULL AND ?table?.instance != Utf8(\"localhost\") AND ?table?.service.name = Utf8(\"api\") AND ?table?.Region = Utf8(\"us\")\n TableScan: ?table?",
1103 ts_col, ts_col
1104 );
1105 assert_eq!(expected, display_string);
1106 }
1107
1108 fn column_schemas_with(
1109 mut kts_iter: Vec<(&str, ColumnDataType, SemanticType)>,
1110 ) -> Vec<api::v1::ColumnSchema> {
1111 kts_iter.push((
1112 greptime_value(),
1113 ColumnDataType::Float64,
1114 SemanticType::Field,
1115 ));
1116 kts_iter.push((
1117 greptime_timestamp(),
1118 ColumnDataType::TimestampMillisecond,
1119 SemanticType::Timestamp,
1120 ));
1121
1122 kts_iter
1123 .into_iter()
1124 .map(|(k, t, s)| api::v1::ColumnSchema {
1125 column_name: k.to_string(),
1126 datatype: t as i32,
1127 semantic_type: s as i32,
1128 ..Default::default()
1129 })
1130 .collect()
1131 }
1132
1133 fn make_row_with_label(l1: &str, value: f64, timestamp: i64) -> Row {
1134 Row {
1135 values: vec![
1136 api::v1::Value {
1137 value_data: Some(api::v1::value::ValueData::StringValue(l1.to_string())),
1138 },
1139 api::v1::Value {
1140 value_data: Some(api::v1::value::ValueData::F64Value(value)),
1141 },
1142 api::v1::Value {
1143 value_data: Some(api::v1::value::ValueData::TimestampMillisecondValue(
1144 timestamp,
1145 )),
1146 },
1147 ],
1148 }
1149 }
1150
1151 fn make_row_with_2_labels(l1: &str, l2: &str, value: f64, timestamp: i64) -> Row {
1152 Row {
1153 values: vec![
1154 api::v1::Value {
1155 value_data: Some(api::v1::value::ValueData::StringValue(l1.to_string())),
1156 },
1157 api::v1::Value {
1158 value_data: Some(api::v1::value::ValueData::StringValue(l2.to_string())),
1159 },
1160 api::v1::Value {
1161 value_data: Some(api::v1::value::ValueData::F64Value(value)),
1162 },
1163 api::v1::Value {
1164 value_data: Some(api::v1::value::ValueData::TimestampMillisecondValue(
1165 timestamp,
1166 )),
1167 },
1168 ],
1169 }
1170 }
1171
1172 #[test]
1173 fn test_write_request_to_row_insert_exprs() {
1174 let write_request = WriteRequest {
1175 timeseries: mock_timeseries(),
1176 ..Default::default()
1177 };
1178
1179 let mut exprs = to_grpc_row_insert_requests(&write_request)
1180 .unwrap()
1181 .0
1182 .inserts;
1183 exprs.sort_unstable_by(|l, r| l.table_name.cmp(&r.table_name));
1184 assert_eq!(3, exprs.len());
1185 assert_eq!("metric1", exprs[0].table_name);
1186 assert_eq!("metric2", exprs[1].table_name);
1187 assert_eq!("metric3", exprs[2].table_name);
1188
1189 let rows = exprs[0].rows.as_ref().unwrap();
1190 let schema = &rows.schema;
1191 let rows = &rows.rows;
1192 assert_eq!(2, rows.len());
1193 assert_eq!(3, schema.len());
1194 assert_eq!(
1195 column_schemas_with(vec![("job", ColumnDataType::String, SemanticType::Tag)]),
1196 *schema
1197 );
1198 assert_eq!(
1199 &vec![
1200 make_row_with_label("spark", 1.0, 1000),
1201 make_row_with_label("spark", 2.0, 2000),
1202 ],
1203 rows
1204 );
1205
1206 let rows = exprs[1].rows.as_ref().unwrap();
1207 let schema = &rows.schema;
1208 let rows = &rows.rows;
1209 assert_eq!(2, rows.len());
1210 assert_eq!(4, schema.len());
1211 assert_eq!(
1212 column_schemas_with(vec![
1213 ("instance", ColumnDataType::String, SemanticType::Tag),
1214 ("idc", ColumnDataType::String, SemanticType::Tag)
1215 ]),
1216 *schema
1217 );
1218 assert_eq!(
1219 &vec![
1220 make_row_with_2_labels("test_host1", "z001", 3.0, 1000),
1221 make_row_with_2_labels("test_host1", "z001", 4.0, 2000),
1222 ],
1223 rows
1224 );
1225
1226 let rows = exprs[2].rows.as_ref().unwrap();
1227 let schema = &rows.schema;
1228 let rows = &rows.rows;
1229 assert_eq!(3, rows.len());
1230 assert_eq!(4, schema.len());
1231 assert_eq!(
1232 column_schemas_with(vec![
1233 ("idc", ColumnDataType::String, SemanticType::Tag),
1234 ("app", ColumnDataType::String, SemanticType::Tag)
1235 ]),
1236 *schema
1237 );
1238 assert_eq!(
1239 &vec![
1240 make_row_with_2_labels("z002", "biz", 5.0, 1000),
1241 make_row_with_2_labels("z002", "biz", 6.0, 2000),
1242 make_row_with_2_labels("z002", "biz", 7.0, 3000),
1243 ],
1244 rows
1245 );
1246 }
1247
1248 #[test]
1249 fn test_recordbatches_to_timeseries() {
1250 let schema = Arc::new(Schema::new(vec![
1251 ColumnSchema::new(
1252 greptime_timestamp(),
1253 ConcreteDataType::timestamp_millisecond_datatype(),
1254 true,
1255 ),
1256 ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true),
1257 ColumnSchema::new("instance", ConcreteDataType::string_datatype(), true),
1258 ]));
1259
1260 let recordbatches = RecordBatches::try_new(
1261 schema.clone(),
1262 vec![
1263 RecordBatch::new(
1264 schema.clone(),
1265 vec![
1266 Arc::new(TimestampMillisecondVector::from_vec(vec![1000])) as _,
1267 Arc::new(Float64Vector::from_vec(vec![3.0])) as _,
1268 Arc::new(StringVector::from(vec!["host1"])) as _,
1269 ],
1270 )
1271 .unwrap(),
1272 RecordBatch::new(
1273 schema,
1274 vec![
1275 Arc::new(TimestampMillisecondVector::from_vec(vec![2000])) as _,
1276 Arc::new(Float64Vector::from_vec(vec![7.0])) as _,
1277 Arc::new(StringVector::from(vec!["host2"])) as _,
1278 ],
1279 )
1280 .unwrap(),
1281 ],
1282 )
1283 .unwrap();
1284
1285 let timeseries = recordbatches_to_timeseries(
1286 "metric1",
1287 greptime_timestamp(),
1288 greptime_value(),
1289 recordbatches,
1290 )
1291 .unwrap();
1292 assert_eq!(2, timeseries.len());
1293
1294 assert_eq!(
1295 vec![
1296 Label {
1297 name: METRIC_NAME_LABEL.to_string(),
1298 value: "metric1".to_string(),
1299 },
1300 Label {
1301 name: "instance".to_string(),
1302 value: "host1".to_string(),
1303 },
1304 ],
1305 timeseries[0].labels
1306 );
1307
1308 assert_eq!(
1309 timeseries[0].samples,
1310 vec![Sample {
1311 value: 3.0,
1312 timestamp: 1000,
1313 }]
1314 );
1315
1316 assert_eq!(
1317 vec![
1318 Label {
1319 name: METRIC_NAME_LABEL.to_string(),
1320 value: "metric1".to_string(),
1321 },
1322 Label {
1323 name: "instance".to_string(),
1324 value: "host2".to_string(),
1325 },
1326 ],
1327 timeseries[1].labels
1328 );
1329 assert_eq!(
1330 timeseries[1].samples,
1331 vec![Sample {
1332 value: 7.0,
1333 timestamp: 2000,
1334 }]
1335 );
1336 }
1337
1338 #[test]
1339 fn test_recordbatches_to_timeseries_merges_across_batches() {
1340 let schema = Arc::new(Schema::new(vec![
1341 ColumnSchema::new(
1342 greptime_timestamp(),
1343 ConcreteDataType::timestamp_millisecond_datatype(),
1344 true,
1345 ),
1346 ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true),
1347 ColumnSchema::new("instance", ConcreteDataType::string_datatype(), true),
1348 ]));
1349
1350 let recordbatches = RecordBatches::try_new(
1351 schema.clone(),
1352 vec![
1353 RecordBatch::new(
1354 schema.clone(),
1355 vec![
1356 Arc::new(TimestampMillisecondVector::from_vec(vec![3000, 1500])) as _,
1357 Arc::new(Float64Vector::from_vec(vec![30.0, 15.0])) as _,
1358 Arc::new(StringVector::from(vec!["host1", "host2"])) as _,
1359 ],
1360 )
1361 .unwrap(),
1362 RecordBatch::new(
1363 schema.clone(),
1364 vec![
1365 Arc::new(TimestampMillisecondVector::from_vec(vec![1000])) as _,
1366 Arc::new(Float64Vector::from_vec(vec![10.0])) as _,
1367 Arc::new(StringVector::from(vec!["host1"])) as _,
1368 ],
1369 )
1370 .unwrap(),
1371 RecordBatch::new(
1372 schema,
1373 vec![
1374 Arc::new(TimestampMillisecondVector::from_vec(vec![2000, 2500])) as _,
1375 Arc::new(Float64Vector::from_vec(vec![20.0, 25.0])) as _,
1376 Arc::new(StringVector::from(vec!["host1", "host2"])) as _,
1377 ],
1378 )
1379 .unwrap(),
1380 ],
1381 )
1382 .unwrap();
1383
1384 let timeseries = recordbatches_to_timeseries(
1385 "cpu_usage",
1386 greptime_timestamp(),
1387 greptime_value(),
1388 recordbatches,
1389 )
1390 .unwrap();
1391
1392 assert_eq!(2, timeseries.len());
1393
1394 assert_eq!(
1395 vec![
1396 Label {
1397 name: METRIC_NAME_LABEL.to_string(),
1398 value: "cpu_usage".to_string(),
1399 },
1400 Label {
1401 name: "instance".to_string(),
1402 value: "host1".to_string(),
1403 },
1404 ],
1405 timeseries[0].labels
1406 );
1407 assert_eq!(
1408 vec![
1409 Sample {
1410 value: 10.0,
1411 timestamp: 1000,
1412 },
1413 Sample {
1414 value: 20.0,
1415 timestamp: 2000,
1416 },
1417 Sample {
1418 value: 30.0,
1419 timestamp: 3000,
1420 },
1421 ],
1422 timeseries[0].samples
1423 );
1424
1425 assert_eq!(
1426 vec![
1427 Label {
1428 name: METRIC_NAME_LABEL.to_string(),
1429 value: "cpu_usage".to_string(),
1430 },
1431 Label {
1432 name: "instance".to_string(),
1433 value: "host2".to_string(),
1434 },
1435 ],
1436 timeseries[1].labels
1437 );
1438 assert_eq!(
1439 vec![
1440 Sample {
1441 value: 15.0,
1442 timestamp: 1500,
1443 },
1444 Sample {
1445 value: 25.0,
1446 timestamp: 2500,
1447 },
1448 ],
1449 timeseries[1].samples
1450 );
1451 }
1452
1453 #[test]
1454 fn test_recordbatches_to_timeseries_borrows_and_groups_dictionary_labels() {
1455 let arrow_schema = Arc::new(ArrowSchema::new(vec![
1456 Field::new(
1457 greptime_timestamp(),
1458 ArrowDataType::Timestamp(arrow::datatypes::TimeUnit::Millisecond, None),
1459 false,
1460 ),
1461 Field::new(greptime_value(), ArrowDataType::Float64, false),
1462 Field::new_dictionary("instance", ArrowDataType::UInt32, ArrowDataType::Utf8, true),
1463 ]));
1464 let schema = Arc::new(Schema::try_from(arrow_schema.clone()).unwrap());
1465 let instance = DictionaryArray::<UInt32Type>::new(
1466 UInt32Array::from(vec![Some(0), None, Some(1), Some(0), Some(2)]),
1467 Arc::new(StringArray::from(vec![Some("host2"), Some("host1"), None])),
1468 );
1469 let batch = DfRecordBatch::try_new(
1470 arrow_schema,
1471 vec![
1472 Arc::new(TimestampMillisecondArray::from(vec![
1473 1000, 2000, 3000, 4000, 5000,
1474 ])),
1475 Arc::new(Float64Array::from(vec![1.0, 2.0, 3.0, 4.0, 5.0])),
1476 Arc::new(instance),
1477 ],
1478 )
1479 .unwrap();
1480 let recordbatch = RecordBatch::from_df_record_batch(schema.clone(), batch);
1481 let columns = label_columns(&recordbatch, greptime_timestamp(), greptime_value()).unwrap();
1482 assert!(matches!(
1483 columns[0].values,
1484 LabelValues::DictionaryUtf8 { .. }
1485 ));
1486 drop(columns);
1487 let recordbatches = RecordBatches::try_new(schema, vec![recordbatch]).unwrap();
1488
1489 let timeseries = recordbatches_to_timeseries(
1490 "metric1",
1491 greptime_timestamp(),
1492 greptime_value(),
1493 recordbatches,
1494 )
1495 .unwrap();
1496
1497 assert_eq!(3, timeseries.len());
1498 assert_eq!(
1499 vec![Label {
1500 name: METRIC_NAME_LABEL.to_string(),
1501 value: "metric1".to_string(),
1502 }],
1503 timeseries[0].labels
1504 );
1505 assert_eq!(
1506 vec![
1507 Sample {
1508 value: 2.0,
1509 timestamp: 2000,
1510 },
1511 Sample {
1512 value: 5.0,
1513 timestamp: 5000,
1514 },
1515 ],
1516 timeseries[0].samples
1517 );
1518 assert_eq!("host1", timeseries[1].labels[1].value);
1519 assert_eq!(
1520 vec![Sample {
1521 value: 3.0,
1522 timestamp: 3000,
1523 }],
1524 timeseries[1].samples
1525 );
1526 assert_eq!("host2", timeseries[2].labels[1].value);
1527 assert_eq!(
1528 vec![
1529 Sample {
1530 value: 1.0,
1531 timestamp: 1000,
1532 },
1533 Sample {
1534 value: 4.0,
1535 timestamp: 4000,
1536 },
1537 ],
1538 timeseries[2].samples
1539 );
1540 }
1541
1542 #[test]
1543 fn test_recordbatches_to_timeseries_groups_non_contiguous_series() {
1544 let schema = Arc::new(Schema::new(vec![
1545 ColumnSchema::new(
1546 greptime_timestamp(),
1547 ConcreteDataType::timestamp_millisecond_datatype(),
1548 true,
1549 ),
1550 ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true),
1551 ColumnSchema::new("instance", ConcreteDataType::string_datatype(), true),
1552 ]));
1553 let recordbatch = RecordBatch::new(
1554 schema.clone(),
1555 vec![
1556 Arc::new(TimestampMillisecondVector::from_vec(vec![1000, 2000, 3000])) as _,
1557 Arc::new(Float64Vector::from_vec(vec![1.0, 2.0, 3.0])) as _,
1558 Arc::new(StringVector::from(vec!["host2", "host1", "host2"])) as _,
1559 ],
1560 )
1561 .unwrap();
1562
1563 let recordbatches = RecordBatches::try_new(schema, vec![recordbatch]).unwrap();
1564 let timeseries = recordbatches_to_timeseries(
1565 "metric1",
1566 greptime_timestamp(),
1567 greptime_value(),
1568 recordbatches,
1569 )
1570 .unwrap();
1571
1572 assert_eq!("host1", timeseries[0].labels[1].value);
1574 assert_eq!("host2", timeseries[1].labels[1].value);
1575 assert_eq!(
1576 vec![
1577 Sample {
1578 value: 1.0,
1579 timestamp: 1000,
1580 },
1581 Sample {
1582 value: 3.0,
1583 timestamp: 3000,
1584 },
1585 ],
1586 timeseries[1].samples
1587 );
1588 }
1589
1590 #[test]
1591 fn test_recordbatches_to_timeseries_arrow_label_types_and_nulls() {
1592 let schema = Arc::new(Schema::new(vec![
1593 ColumnSchema::new(
1594 greptime_timestamp(),
1595 ConcreteDataType::timestamp_millisecond_datatype(),
1596 true,
1597 ),
1598 ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true),
1599 ColumnSchema::new("instance", ConcreteDataType::large_string_datatype(), true),
1600 ColumnSchema::new("zone", ConcreteDataType::utf8_view_datatype(), true),
1601 ColumnSchema::new("shard", ConcreteDataType::int32_datatype(), true),
1602 ]));
1603 let recordbatch = RecordBatch::new(
1604 schema.clone(),
1605 vec![
1606 Arc::new(TimestampMillisecondVector::from_vec(vec![1000, 2000, 3000])) as _,
1607 Arc::new(Float64Vector::from_vec(vec![1.0, 2.0, 3.0])) as _,
1608 Arc::new(StringVector::from(LargeStringArray::from(vec![
1609 "host2", "host1", "host2",
1610 ]))) as _,
1611 Arc::new(StringVector::from(StringViewArray::from(vec![
1612 Some("west"),
1613 None,
1614 Some("west"),
1615 ]))) as _,
1616 Arc::new(Int32Vector::from_vec(vec![2, 1, 2])) as _,
1617 ],
1618 )
1619 .unwrap();
1620
1621 let recordbatches = RecordBatches::try_new(schema, vec![recordbatch]).unwrap();
1622 let timeseries = recordbatches_to_timeseries(
1623 "metric1",
1624 greptime_timestamp(),
1625 greptime_value(),
1626 recordbatches,
1627 )
1628 .unwrap();
1629
1630 assert_eq!(2, timeseries.len());
1631 assert_eq!(
1632 vec![
1633 new_label(METRIC_NAME_LABEL.to_string(), "metric1".to_string()),
1634 new_label("instance".to_string(), "host1".to_string()),
1635 new_label("shard".to_string(), "1".to_string()),
1636 ],
1637 timeseries[0].labels
1638 );
1639 assert_eq!(
1640 vec![
1641 new_label(METRIC_NAME_LABEL.to_string(), "metric1".to_string()),
1642 new_label("instance".to_string(), "host2".to_string()),
1643 new_label("zone".to_string(), "west".to_string()),
1644 new_label("shard".to_string(), "2".to_string()),
1645 ],
1646 timeseries[1].labels
1647 );
1648 assert_eq!(
1649 vec![
1650 Sample {
1651 value: 1.0,
1652 timestamp: 1000,
1653 },
1654 Sample {
1655 value: 3.0,
1656 timestamp: 3000,
1657 },
1658 ],
1659 timeseries[1].samples
1660 );
1661 }
1662
1663 #[tokio::test]
1664 async fn test_snappy_varint_bomb_is_rejected_before_allocation() {
1665 let bomb = [0xffu8, 0xff, 0xff, 0xff, 0x0f];
1669 let limiter = ServerMemoryLimiter::default();
1670
1671 let err = snappy_decompress_limited(&bomb, MAX_DECOMPRESSED_REQUEST_SIZE, &limiter)
1672 .await
1673 .unwrap_err();
1674 assert!(matches!(
1675 err,
1676 crate::error::Error::DecompressedBodyTooLarge { .. }
1677 ));
1678 assert_eq!(0, limiter.used_bytes());
1679 }
1680
1681 #[tokio::test]
1682 async fn test_snappy_decompress_limited_roundtrip() {
1683 let raw = vec![b'a'; 1024 * 1024];
1684 let mut encoder = snap::raw::Encoder::new();
1685 let compressed = encoder.compress_vec(&raw).unwrap();
1686 let limiter = ServerMemoryLimiter::default();
1687
1688 let decoded =
1689 snappy_decompress_limited(&compressed, MAX_DECOMPRESSED_REQUEST_SIZE, &limiter)
1690 .await
1691 .unwrap();
1692 assert_eq!(&decoded[..], &raw[..]);
1693 }
1694
1695 #[tokio::test]
1696 async fn test_snappy_charges_and_releases_quota() {
1697 let raw = vec![b'a'; 64 * 1024];
1698 let mut encoder = snap::raw::Encoder::new();
1699 let compressed = encoder.compress_vec(&raw).unwrap();
1700 let limiter = ServerMemoryLimiter::new(128 * 1024, OnExhaustedPolicy::Fail);
1701
1702 let decoded =
1703 snappy_decompress_limited(&compressed, MAX_DECOMPRESSED_REQUEST_SIZE, &limiter)
1704 .await
1705 .unwrap();
1706 assert!(limiter.used_bytes() >= 64 * 1024);
1708 assert!(limiter.used_bytes() <= 65 * 1024);
1709 drop(decoded);
1710 assert_eq!(0, limiter.used_bytes());
1711 }
1712
1713 #[tokio::test]
1714 async fn test_snappy_rejects_when_quota_exhausted() {
1715 let raw = vec![b'a'; 64 * 1024];
1716 let mut encoder = snap::raw::Encoder::new();
1717 let compressed = encoder.compress_vec(&raw).unwrap();
1718 let limiter = ServerMemoryLimiter::new(1024, OnExhaustedPolicy::Fail);
1720
1721 let err = snappy_decompress_limited(&compressed, MAX_DECOMPRESSED_REQUEST_SIZE, &limiter)
1722 .await
1723 .unwrap_err();
1724 assert!(matches!(
1725 err,
1726 crate::error::Error::MemoryLimitExceeded { .. }
1727 ));
1728 assert_eq!(0, limiter.used_bytes(), "failed charge must be released");
1729 }
1730
1731 #[tokio::test]
1732 async fn test_zstd_at_and_over_the_cap() {
1733 let limiter = ServerMemoryLimiter::default();
1734 let small_cap = 8 * 1024;
1735
1736 let raw = vec![0u8; small_cap];
1738 let compressed = zstd::stream::encode_all(&raw[..], 3).unwrap();
1739 let decoded = zstd_decompress_limited(&compressed, small_cap, &limiter)
1740 .await
1741 .unwrap();
1742 assert_eq!(decoded.len(), small_cap);
1743
1744 let raw = vec![0u8; small_cap + 1];
1746 let compressed = zstd::stream::encode_all(&raw[..], 3).unwrap();
1747 let err = zstd_decompress_limited(&compressed, small_cap, &limiter)
1748 .await
1749 .unwrap_err();
1750 assert!(matches!(
1751 err,
1752 crate::error::Error::DecompressedBodyTooLarge { .. }
1753 ));
1754 }
1755
1756 #[tokio::test]
1757 async fn test_zstd_charges_incrementally() {
1758 let raw = vec![0u8; 2 * 1024 * 1024];
1761 let compressed = zstd::stream::encode_all(&raw[..], 19).unwrap();
1762 assert!(compressed.len() < 16 * 1024);
1763 let limiter = ServerMemoryLimiter::new(4 * 1024 * 1024, OnExhaustedPolicy::Fail);
1764
1765 let decoded = zstd_decompress_limited(&compressed, MAX_DECOMPRESSED_REQUEST_SIZE, &limiter)
1766 .await
1767 .unwrap();
1768 assert_eq!(decoded.len(), raw.len());
1769 assert!(limiter.used_bytes() >= raw.len() as u64);
1770 drop(decoded);
1771 assert_eq!(0, limiter.used_bytes());
1772 }
1773
1774 #[test]
1775 fn test_snappy_response_decompress_is_capped() {
1776 let bomb = [0xffu8, 0xff, 0xff, 0xff, 0x0f];
1778 let err = snappy_decompress(&bomb).unwrap_err();
1779 assert!(matches!(
1780 err,
1781 crate::error::Error::DecompressedBodyTooLarge { .. }
1782 ));
1783 }
1784}