Skip to main content

servers/
prom_store.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! prometheus protocol supportings
16//! handles prometheus remote_write, remote_read logic
17use 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
55/// special label for selecting database name on remote write
56pub 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, use DATABASE_LABEL instead
62#[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
67/// special label for selecting physical table name on remote write
68pub 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
73/// The same as `FIELD_COLUMN_MATCHER` in `promql` crate
74pub const FIELD_NAME_LABEL: &str = "__field__";
75
76/// Check if given label is a special label for remote write
77#[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/// Check if given label is a database selection label
95#[allow(deprecated)]
96pub fn is_database_selection_label(label: &str) -> bool {
97    label == DATABASE_LABEL || label == DATABASE_LABEL_ALT || label == SCHEMA_LABEL
98}
99
100/// Check if given label is a physical table selection label
101pub fn is_physical_table_selection_label(label: &str) -> bool {
102    label == PHYSICAL_TABLE_LABEL || label == PHYSICAL_TABLE_LABEL_ALT
103}
104
105/// Get table name from remote query
106pub 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
130/// Extract database selector from a remote read query.
131pub 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/// Create a DataFrame from a remote Query
142#[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            // Case sensitive regexp match
183            MatcherType::Re => {
184                conditions.push(regexp_match(ident(name), lit(value), None).is_not_null());
185            }
186            // Case sensitive regexp not match
187            MatcherType::Nre => {
188                conditions.push(regexp_match(ident(name), lit(value), None).is_null());
189            }
190        }
191    }
192
193    // Safety: conditions MUST not be empty, reduce always return Some(expr).
194    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
212/// Sort timeseries by labels, matching the former `BTreeMap` order.
213fn 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    // The Prometheus remote read wire format carries millisecond timestamps,
411    // while the table's time index can use any time unit (e.g. a metric
412    // physical table created with TIMESTAMP(6)). Narrowing floors towards
413    // negative infinity, consistent with `Timestamp::convert_to` on the
414    // ingestion path; arrow's cast would truncate towards zero, so a
415    // pre-epoch sample (e.g. -1001us) would round to -1ms instead of -2ms
416    // and disagree with what ingesting the same instant into a millisecond
417    // table would have stored.
418    let ts_column: ArrayRef = match ts_column.data_type() {
419        DataType::Timestamp(TimeUnit::Millisecond, _) => ts_column.clone(),
420        // Second -> millisecond is a widening (exact) conversion.
421        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    // TODO: Add native-histogram encoding when Prometheus Remote Read support is prioritized.
464    // The current path intentionally returns scalar samples only.
465    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(&timeseries[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(&timeseries[*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                // The metric name is a special label
532                label.name == METRIC_NAME_LABEL
533            })
534            .context(error::InvalidPromRemoteRequestSnafu {
535                msg: "missing '__name__' label in time-series",
536            })?
537            .value;
538
539        // The metric name is a special label,
540        // num_columns = labels.len() - 1 + 1 (value) + 1 (timestamp)
541        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        // labels
550        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            // value
563            row_writer::write_f64(
564                table_data,
565                greptime_value(),
566                series.samples[0].value,
567                &mut one_row,
568            )?;
569            // timestamp
570            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                // labels
584                let kvs = kvs.clone();
585                row_writer::write_tags(table_data, kvs, &mut one_row)?;
586                // value
587                row_writer::write_f64(table_data, greptime_value(), *value, &mut one_row)?;
588                // timestamp
589                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
609/// Hard cap on the decompressed size of a handler-decompressed request body
610/// (Prometheus remote read/write, Loki push).
611///
612/// The HTTP body limit only bounds the *encoded* bytes on the wire. Without
613/// this cap a tiny compressed payload (e.g. a five-byte raw-Snappy varint or a
614/// few KiB of zstd RLE blocks) could declare/expand to multiple GiB of heap
615/// before any protobuf validation runs, so the decompressed size must be
616/// bounded on its own.
617pub const MAX_DECOMPRESSED_REQUEST_SIZE: usize = 512 * 1024 * 1024;
618
619/// Size of the chunks a zstd body is decompressed and charged in.
620const ZSTD_DECOMPRESS_CHUNK: usize = 256 * 1024;
621
622/// Decompressed bytes together with the memory permits charged for them.
623/// The permits are released when the buffer is dropped, so the aggregate
624/// memory accounting covers the whole lifetime of the decompressed data.
625pub(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    /// Creates a buffer with the permits charged for it.
649    pub(crate) fn new(data: Vec<u8>, guards: Vec<MemoryGuard<RequestMemoryMetrics>>) -> Self {
650        Self { data, guards }
651    }
652
653    /// Splits the buffer into the raw bytes and the memory permits charged for
654    /// them. Hold the permits as long as the bytes are kept.
655    pub(crate) fn into_parts(self) -> (Vec<u8>, Vec<MemoryGuard<RequestMemoryMetrics>>) {
656        (self.data, self.guards)
657    }
658}
659
660/// Decompresses a raw Snappy body after enforcing a hard cap on the decoded
661/// size and charging the aggregate request-memory limiter.
662///
663/// The raw Snappy format prefixes the stream with a varint of the decoded
664/// length, so the output size is known (and validated) before the output
665/// buffer is allocated by `decompress_vec`.
666pub(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    // Reserve the declared size before the allocation happens so concurrent
681    // decompressions cannot exceed the aggregate quota.
682    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
693/// Decompresses a zstd body with a hard cap on the decoded size, charging the
694/// aggregate request-memory limiter incrementally while the output is
695/// materialized (zstd streams don't declare their decoded size up front).
696pub(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    // Allow reading one byte past the limit so oversized streams are detected
706    // instead of being silently truncated.
707    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        // Charge each chunk before it is appended so the aggregate quota
726        // bounds the memory while it is being materialized.
727        guards.push(limiter.acquire(n as u64).await?);
728        data.extend_from_slice(&chunk[..n]);
729    }
730    Ok(ChargedBuffer { data, guards })
731}
732
733/// Decompresses a raw Snappy body with the hard decoded-size cap enforced,
734/// without charging the aggregate request-memory limiter.
735///
736/// Used for response bodies (remote read replies) and tests; request bodies
737/// must use [`snappy_decompress_limited`] instead so their decoded size is
738/// also charged to the aggregate quota.
739#[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
764/// Mock timeseries for test, it is both used in servers and frontend crate
765/// So we present it here
766pub 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
828/// Add new labels to the mock timeseries.
829pub 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
860/// Add new labels to the mock timeseries.
861pub 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        // The result stays sorted by labels as it was with the previous BTreeMap.
1573        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        // A five-byte raw Snappy header declaring a ~4 GiB decoded length.
1666        // Regression test for the compression-bomb finding: the declared size
1667        // must be rejected by the cap check before any buffer is allocated.
1668        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        // Rounding to the 1 KiB permit granularity.
1707        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        // Quota smaller than the decoded size.
1719        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        // Exactly at the cap: accepted.
1737        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        // One byte past the cap: rejected.
1745        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        // 64 KiB wire that expands to 2 MiB must be charged chunk by chunk
1759        // against a quota that only fits the expanded size.
1760        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        // The response-side (uncharged) helper must enforce the cap too.
1777        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}