Skip to main content

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