mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-11-14 06:46:56 +00:00
feat(promql): add evaluated series math kernels (#9672)
* feat(promql): add evaluated series math kernels Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> * fix(promql): drop metric name in evaluated series math like Prometheus Prometheus' simpleFloatFunc drops __name__ together with __type__ and __unit__. Check abs, ceil, floor, sqrt and round against fixtures generated by the Prometheus v3.15.0 engine. Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> * fix(promql): drop empty labels with the metric name in series math Prometheus has no empty-valued labels, so a kept empty value would hide a same-labelset collision from the caller. Round shares its calculation with the UDF, so check it only against the Prometheus fixtures. Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> * chore: exclude promql golden fixtures from typos Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> * docs(promql): point series round at Prometheus funcRound Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> * chore: allow the Unicode category Nd in typos Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> * perf(promql): drop reserved and empty labels in one rebuild drop_metric_name removed empty-valued labels one at a time, and each MetricName::remove copies the whole label array, so the cost grew with the square of the number of empty labels. Filter the labels once and rebuild the name with MetricName::try_new. Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> --------- Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
This commit is contained in:
@@ -66,7 +66,7 @@ impl Round {
|
||||
let null_array = Float64Array::new_null(value_array.len());
|
||||
return Ok(ColumnarValue::Array(Arc::new(null_array)));
|
||||
};
|
||||
let op = |a: f64| round_to_nearest(a, nearest);
|
||||
let op = |a: f64| Self::round_to_nearest(a, nearest);
|
||||
let result: PrimitiveArray<Float64Type> =
|
||||
value_array.as_primitive::<Float64Type>().unary(op);
|
||||
Ok(ColumnarValue::Array(Arc::new(result) as _))
|
||||
@@ -83,23 +83,25 @@ impl Round {
|
||||
)),
|
||||
)?;
|
||||
|
||||
let result: PrimitiveArray<Float64Type> =
|
||||
datatypes::arrow::compute::binary(value_array, nearest_array, round_to_nearest)
|
||||
.map_err(|err: ArrowError| {
|
||||
DataFusionError::ArrowError(Box::new(err), None)
|
||||
})?;
|
||||
let result: PrimitiveArray<Float64Type> = datatypes::arrow::compute::binary(
|
||||
value_array,
|
||||
nearest_array,
|
||||
Self::round_to_nearest,
|
||||
)
|
||||
.map_err(|err: ArrowError| DataFusionError::ArrowError(Box::new(err), None))?;
|
||||
|
||||
Ok(ColumnarValue::Array(Arc::new(result) as _))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Rounds like Prometheus: ties round up (towards +Inf), and multiplying by the inverse
|
||||
/// of `nearest` follows its float behavior, including NaN for `nearest == 0`.
|
||||
fn round_to_nearest(value: f64, nearest: f64) -> f64 {
|
||||
let inverse = 1.0 / nearest;
|
||||
(value * inverse + 0.5).floor() / inverse
|
||||
/// Follows Prometheus `funcRound`: multiplying by the inverse of `nearest` and dividing by it
|
||||
/// again (not `/ nearest` then `* nearest`) can move a `floor` result by a whole step.
|
||||
/// `nearest == 0` gives NaN; with a positive `nearest`, ties round towards +Inf.
|
||||
pub(crate) fn round_to_nearest(value: f64, nearest: f64) -> f64 {
|
||||
let inverse = 1.0 / nearest;
|
||||
(value * inverse + 0.5).floor() / inverse
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -19,6 +19,7 @@ mod go_sort;
|
||||
pub mod grid_series;
|
||||
pub mod histogram_eval;
|
||||
pub mod instant_eval;
|
||||
pub mod map;
|
||||
pub mod map_label;
|
||||
pub mod map_time;
|
||||
mod metrics;
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Pointwise kernels for evaluated series, independent of planning and execution nodes.
|
||||
//!
|
||||
//! Expression column naming and omission of missing points belong to the planner/output adapter;
|
||||
//! these kernels retain the shared field schema and represent missing grid positions by validity.
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use datafusion::error::{DataFusionError, Result};
|
||||
use datatypes::arrow::array::Float64Array;
|
||||
use datatypes::series::MetricName;
|
||||
use datatypes::series::samples::{DenseSamplesRef, Samples, Series, SeriesFields, SeriesSchema};
|
||||
use datatypes::series::time::TimeAxis;
|
||||
|
||||
use crate::functions::Round;
|
||||
|
||||
/// Float math supported by the evaluated-series kernel.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum MathOp {
|
||||
/// Absolute value.
|
||||
Abs,
|
||||
/// Rounds towards positive infinity.
|
||||
Ceil,
|
||||
/// Rounds towards negative infinity.
|
||||
Floor,
|
||||
/// IEEE square root, returning NaN for negative input.
|
||||
Sqrt,
|
||||
/// Rounds to a constant multiple; use `1.0` for PromQL's default argument.
|
||||
Round { nearest: f64 },
|
||||
}
|
||||
|
||||
/// Applies float math to every field of one evaluated series and drops the metric name, like
|
||||
/// Prometheus' `simpleFloatFunc`.
|
||||
///
|
||||
/// The explicit schema and actual grid range are preserved. Fields are processed in their own
|
||||
/// chunk boundaries, without expanding to the full grid or densifying chunked samples. Missing
|
||||
/// samples stay missing; ordinary NaNs remain values. An empty or all-missing input is valid.
|
||||
///
|
||||
/// Eval must resolve raw staleness before calling this kernel. Evaluated samples use validity,
|
||||
/// not NaN payloads, for missingness: arithmetic can produce the reserved stale bit pattern too.
|
||||
/// Raw time axes and histogram/mixed segments are rejected. Unsupported query types must be
|
||||
/// admitted to the ordinary path before execution; this function neither falls back nor returns
|
||||
/// partial results.
|
||||
///
|
||||
/// Distinct inputs can share a label set once the name is dropped. Merging them, or failing
|
||||
/// when both have a value at one step, is left to the caller (Prometheus
|
||||
/// `mergeSeriesWithSameLabelset`).
|
||||
pub fn map_math(schema: &SeriesSchema, series: &Series, op: MathOp) -> Result<Series> {
|
||||
if !matches!(series.time, TimeAxis::Grid(_)) {
|
||||
return Err(DataFusionError::Execution(
|
||||
"Map math requires an evaluated grid time axis".into(),
|
||||
));
|
||||
}
|
||||
let fields = series
|
||||
.fields
|
||||
.iter()
|
||||
.map(|samples| map_samples(samples, op))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
let fields = SeriesFields::try_from_vec(fields).map_err(series_error)?;
|
||||
let metric = drop_metric_name(&series.metric)?;
|
||||
Series::try_new(schema, metric, series.time.clone(), fields).map_err(series_error)
|
||||
}
|
||||
|
||||
/// Prometheus `DropReserved(schema.IsMetadataLabel)` drops the type and unit labels with the
|
||||
/// name. Empty values are dropped too: Prometheus has no empty labels, and keeping them would
|
||||
/// hide a same-labelset collision from the caller.
|
||||
pub(crate) fn drop_metric_name(metric: &MetricName) -> Result<MetricName> {
|
||||
// One rebuild: removing labels one by one copies the label array each time.
|
||||
let tags = metric
|
||||
.tags()
|
||||
.iter()
|
||||
.filter(|label| {
|
||||
!label.value.is_empty() && !matches!(label.name.as_ref(), "__type__" | "__unit__")
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
MetricName::try_new(None, tags).map_err(series_error)
|
||||
}
|
||||
|
||||
impl MathOp {
|
||||
fn apply(self, floats: &Float64Array) -> Float64Array {
|
||||
match self {
|
||||
Self::Abs => floats.unary(f64::abs),
|
||||
Self::Ceil => floats.unary(f64::ceil),
|
||||
Self::Floor => floats.unary(f64::floor),
|
||||
Self::Sqrt => floats.unary(f64::sqrt),
|
||||
Self::Round { nearest } => {
|
||||
floats.unary(|sample| Round::round_to_nearest(sample, nearest))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn map_samples(samples: &Samples, op: MathOp) -> Result<Samples> {
|
||||
let chunks = samples
|
||||
.segments()
|
||||
.iter()
|
||||
.map(|segment| map_float_segment(segment, op))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
Samples::try_from_chunks(chunks).map_err(series_error)
|
||||
}
|
||||
|
||||
fn map_float_segment(segment: &Samples, op: MathOp) -> Result<Samples> {
|
||||
let Some(DenseSamplesRef::Float(floats)) = segment.dense() else {
|
||||
return Err(DataFusionError::NotImplemented(
|
||||
"Map math supports only float samples; histogram/mixed samples are not supported"
|
||||
.into(),
|
||||
));
|
||||
};
|
||||
Ok(Samples::from_float(op.apply(floats)))
|
||||
}
|
||||
|
||||
fn series_error(error: datatypes::error::Error) -> DataFusionError {
|
||||
DataFusionError::External(Box::new(error))
|
||||
}
|
||||
@@ -0,0 +1,398 @@
|
||||
# Generated by promql-compat cmd/narthex-golden-map built for amd64 (Prometheus v3.15.0
|
||||
# engine, go1.26.5, seed 1): 48 math. A case lists its operation, the PromQL
|
||||
# expression, the grid, the inputs read back from the engine and the result series.
|
||||
# Points are `grid index:hex value bits`.
|
||||
case math abs
|
||||
expr abs(a)
|
||||
grid 219680840871 1 5
|
||||
series __name__=a,job=j1,zone=z1 ; 0:c0210e147ae147ae 1:c083700000000000 2:c0e0acc000000000 3:800000000000001d 4:c07a4a0000000000
|
||||
result job=j1,zone=z1 ; 0:40210e147ae147ae 1:4083700000000000 2:40e0acc000000000 3:000000000000001d 4:407a4a0000000000
|
||||
end
|
||||
case math abs
|
||||
expr abs(a)
|
||||
grid -1083181880376 60000 9
|
||||
series __name__=a,host=h1,job=j1 ; 1:c020d00000000000 2:8000000000000001 3:7c55a9b1e1c0627c 4:403af33333333334 5:7ff0000000000000 6:fdcc30f03c00b384 7:3fe677ced916872b 8:fa539f7693acc750
|
||||
result host=h1,job=j1 ; 1:4020d00000000000 2:0000000000000001 3:7c55a9b1e1c0627c 4:403af33333333334 5:7ff0000000000000 6:7dcc30f03c00b384 7:3fe677ced916872b 8:7a539f7693acc750
|
||||
end
|
||||
case math abs
|
||||
expr abs(a)
|
||||
grid 2320841668853 1 3
|
||||
series __name__=a,host=h1,zone=z2 ; 1:c014d9999999999a 2:4008000000000000
|
||||
series __name__=a,host=h2 ; 0:4013d1eb851eb852 2:4014000000000000
|
||||
series __name__=a,host=h2,job=j2 ; 0:c04f700000000000 1:c01ce8f5c28f5c29 2:fff0000000000000
|
||||
result host=h1,zone=z2 ; 1:4014d9999999999a 2:4008000000000000
|
||||
result host=h2 ; 0:4013d1eb851eb852 2:4014000000000000
|
||||
result host=h2,job=j2 ; 0:404f700000000000 1:401ce8f5c28f5c29 2:7ff0000000000000
|
||||
end
|
||||
case math abs
|
||||
expr abs(a)
|
||||
grid -2210646068282 60000 3
|
||||
series __name__=a,host=h1,zone=z2 ; 0:408e680000000000 1:c118a49800000000 2:4000000000000000
|
||||
result host=h1,zone=z2 ; 0:408e680000000000 1:4118a49800000000 2:4000000000000000
|
||||
end
|
||||
case math abs
|
||||
expr abs(a)
|
||||
grid 1248740558131 1 6
|
||||
series __name__=a,host=h2,job=j1,zone=z1 ; 0:3fe0000000000000 2:40e1a6c000000000 3:40f1a21000000000 4:4110b55800000000 5:40e6391000000000
|
||||
result host=h2,job=j1,zone=z1 ; 0:3fe0000000000000 2:40e1a6c000000000 3:40f1a21000000000 4:4110b55800000000 5:40e6391000000000
|
||||
end
|
||||
case math abs
|
||||
expr abs(a)
|
||||
grid 1493340200482 15000 5
|
||||
series __name__=a,host=h2,job=j2,zone=z1 ; 0:c081cf0000000000 1:408acb0000000000 2:c00f7ae147ae147b 3:000000165354657a 4:c0824e0000000000
|
||||
series __name__=a,job=j2,zone=z2 ; 0:40c005e000000000 1:c022b00000000000 2:bfe1be76c8b43958 3:4058073333333334
|
||||
series __name__=a,zone=z2 ; 0:4015e7ae147ae148 1:bfe0000000000000 2:40f579a800000000 3:c08eca0000000000 4:8000000003b6a28c
|
||||
result host=h2,job=j2,zone=z1 ; 0:4081cf0000000000 1:408acb0000000000 2:400f7ae147ae147b 3:000000165354657a 4:40824e0000000000
|
||||
result job=j2,zone=z2 ; 0:40c005e000000000 1:4022b00000000000 2:3fe1be76c8b43958 3:4058073333333334
|
||||
result zone=z2 ; 0:4015e7ae147ae148 1:3fe0000000000000 2:40f579a800000000 3:408eca0000000000 4:0000000003b6a28c
|
||||
end
|
||||
case math ceil
|
||||
expr ceil(a)
|
||||
grid 2030383452465 15000 7
|
||||
series __name__=a,host=h1,job=j2 ; 0:401b2147ae147ae1 1:ffe1ccf385ebc8a0 2:4024000000000000 3:c0f7589000000000 4:c01c000000000000 5:0000000000000000 6:bfe3b74bc6a7ef9e
|
||||
series __name__=a,job=j2,zone=z2 ; 0:c0a0068000000000 1:40c2412000000000 2:c11fd08800000000 3:40f14f4000000000 4:c0f648b000000000 6:ffe1ccf385ebc8a0
|
||||
result host=h1,job=j2 ; 0:401c000000000000 1:ffe1ccf385ebc8a0 2:4024000000000000 3:c0f7589000000000 4:c01c000000000000 5:0000000000000000 6:8000000000000000
|
||||
result job=j2,zone=z2 ; 0:c0a0060000000000 1:40c2418000000000 2:c11fd08800000000 3:40f14f4000000000 4:c0f648b000000000 6:ffe1ccf385ebc8a0
|
||||
end
|
||||
case math ceil
|
||||
expr ceil(a)
|
||||
grid -2295928214828 1 7
|
||||
series __name__=a,host=h1 ; 0:7fefffffffffffff 1:80001a78509b389c 3:7acb7cd9a5ea2f80 6:800000000000001c
|
||||
series __name__=a,host=h1,job=j1 ; 2:c0d6922000000000 4:000000000000000f 5:bff0000000000000
|
||||
series __name__=a,host=h1,job=j2,zone=z1 ; 2:4000000000000000 3:40f19bd000000000 4:c0568ccccccccccd 5:c0bc9d0000000000
|
||||
result host=h1 ; 0:7fefffffffffffff 1:8000000000000000 3:7acb7cd9a5ea2f80 6:8000000000000000
|
||||
result host=h1,job=j1 ; 2:c0d6920000000000 4:3ff0000000000000 5:bff0000000000000
|
||||
result host=h1,job=j2,zone=z1 ; 2:4000000000000000 3:40f19bd000000000 4:c056800000000000 5:c0bc9d0000000000
|
||||
end
|
||||
case math ceil
|
||||
expr ceil(a)
|
||||
grid 288693432093 1 10
|
||||
series __name__=a,host=h2 ; 0:3fea666666666667 1:40858f0000000000 2:fd20e89049e21476 3:f9f391b611275f20 4:c0116147ae147ae1 5:7ff8000000000001 6:4020000000000000 9:3ff0000000000000
|
||||
series __name__=a,host=h2,job=j1 ; 1:40cd90c000000000 2:0000000000000001 3:bfb2e147ae147ae1 4:3fe41fbe76c8b439 5:7e2ee5ddbbc90e98 6:8002b0ee2e12172a 7:401f428f5c28f5c3 8:c01c000000000000 9:0000000000000003
|
||||
result host=h2 ; 0:3ff0000000000000 1:4085900000000000 2:fd20e89049e21476 3:f9f391b611275f20 4:c010000000000000 5:7ff8000000000001 6:4020000000000000 9:3ff0000000000000
|
||||
result host=h2,job=j1 ; 1:40cd910000000000 2:3ff0000000000000 3:8000000000000000 4:3ff0000000000000 5:7e2ee5ddbbc90e98 6:8000000000000000 7:4020000000000000 8:c01c000000000000 9:3ff0000000000000
|
||||
end
|
||||
case math ceil
|
||||
expr ceil(a)
|
||||
grid 2052594281964 1000 4
|
||||
series __name__=a,host=h1,job=j2,zone=z1 ; 0:c0e73f9000000000 2:c048100000000000 3:40be190000000000
|
||||
series __name__=a,host=h1,zone=z2 ; 1:c123459800000000 3:c0282ccccccccccd
|
||||
series __name__=a,job=j2,zone=z1 ; 0:bfd7333333333333 1:f896e9a156d2b750 3:fcefe8af01db2170
|
||||
result host=h1,job=j2,zone=z1 ; 0:c0e73f8000000000 2:c048000000000000 3:40be190000000000
|
||||
result host=h1,zone=z2 ; 1:c123459800000000 3:c028000000000000
|
||||
result job=j2,zone=z1 ; 0:8000000000000000 1:f896e9a156d2b750 3:fcefe8af01db2170
|
||||
end
|
||||
case math ceil
|
||||
expr ceil(a)
|
||||
grid 305490743228 60000 9
|
||||
series __name__=a,host=h1,job=j2,zone=z2 ; 0:4011c00000000000 1:4008000000000000 2:c0fdb19000000000 3:c0c13c8000000000 4:4122a28200000000 6:c10dada800000000 7:4000000000000000 8:4120d49800000000
|
||||
series __name__=a,host=h2,job=j1,zone=z2 ; 0:401c27ae147ae148 1:fa6899d641714788 2:ffefffffffffffff 3:c05f400000000000 4:bfedbc6a7ef9db23 5:0000000000000000 6:3fdae147ae147ae1 8:802c7cc387a67ec0
|
||||
series __name__=a,host=h2,zone=z1 ; 2:ffe1ccf385ebc8a0 3:0000000000000001 6:7c0a489f44473ee4 7:782a67fa6c1344b4
|
||||
result host=h1,job=j2,zone=z2 ; 0:4014000000000000 1:4008000000000000 2:c0fdb19000000000 3:c0c13c8000000000 4:4122a28200000000 6:c10dada800000000 7:4000000000000000 8:4120d49800000000
|
||||
result host=h2,job=j1,zone=z2 ; 0:4020000000000000 1:fa6899d641714788 2:ffefffffffffffff 3:c05f400000000000 4:8000000000000000 5:0000000000000000 6:3ff0000000000000 8:8000000000000000
|
||||
result host=h2,zone=z1 ; 2:ffe1ccf385ebc8a0 3:3ff0000000000000 6:7c0a489f44473ee4 7:782a67fa6c1344b4
|
||||
end
|
||||
case math ceil
|
||||
expr ceil(a)
|
||||
grid 768183472652 15000 4
|
||||
series __name__=a,host=h2,job=j1,zone=z2 ; 0:ffefffffffffffff 1:40ebee0000000000 3:4022200000000000
|
||||
result host=h2,job=j1,zone=z2 ; 0:ffefffffffffffff 1:40ebee0000000000 3:4024000000000000
|
||||
end
|
||||
case math floor
|
||||
expr floor(a)
|
||||
grid -3544116501466 60000 8
|
||||
series __name__=a,host=h2,job=j1,zone=z1 ; 0:802dc0445669d422 1:0000000000000002 2:c0e8e0c000000000 3:c01c000000000000
|
||||
series __name__=a,host=h2,job=j1,zone=z2 ; 1:78d6bfa04792fb40 2:c050780000000000 3:0000000059b707f6 4:c0b7818000000000 5:4127eef400000000 6:c0f1733000000000 7:c10dd8a000000000
|
||||
series __name__=a,host=h2,zone=z1 ; 1:7b162d38e8c8ffd4 3:c01c000000000000 4:7a9956047b631df8 7:c0af8d8000000000
|
||||
result host=h2,job=j1,zone=z1 ; 0:bff0000000000000 1:0000000000000000 2:c0e8e0c000000000 3:c01c000000000000
|
||||
result host=h2,job=j1,zone=z2 ; 1:78d6bfa04792fb40 2:c050800000000000 3:0000000000000000 4:c0b7820000000000 5:4127eef400000000 6:c0f1733000000000 7:c10dd8a000000000
|
||||
result host=h2,zone=z1 ; 1:7b162d38e8c8ffd4 3:c01c000000000000 4:7a9956047b631df8 7:c0af8e0000000000
|
||||
end
|
||||
case math floor
|
||||
expr floor(a)
|
||||
grid 3110963353051 15000 4
|
||||
series __name__=a,host=h1,job=j1,zone=z1 ; 0:bfd6f7ced916872b 1:3fd49ba5e353f7cf 2:7cf8e2149a0c0ea0 3:c079f00000000000
|
||||
result host=h1,job=j1,zone=z1 ; 0:bff0000000000000 1:0000000000000000 2:7cf8e2149a0c0ea0 3:c079f00000000000
|
||||
end
|
||||
case math floor
|
||||
expr floor(a)
|
||||
grid -2988969278171 1 3
|
||||
series __name__=a,job=j1,zone=z2 ; 0:4341c37937e08000 1:c127a6b000000000 2:8000000000000000
|
||||
result job=j1,zone=z2 ; 0:4341c37937e08000 1:c127a6b000000000 2:8000000000000000
|
||||
end
|
||||
case math floor
|
||||
expr floor(a)
|
||||
grid -2632120620786 15000 8
|
||||
series __name__=a,zone=z1 ; 0:7ff8000000000001 1:4121335200000000 2:f872592494d05e60 4:c086330000000000 6:7fe1ccf385ebc8a0 7:bff0000000000000
|
||||
result zone=z1 ; 0:7ff8000000000001 1:4121335200000000 2:f872592494d05e60 4:c086380000000000 6:7fe1ccf385ebc8a0 7:bff0000000000000
|
||||
end
|
||||
case math floor
|
||||
expr floor(a)
|
||||
grid -725694921407 15000 9
|
||||
series __name__=a,host=h1,job=j1 ; 0:800000006e8b16cd 1:fa2ebb4882039496 2:40f7b26800000000 3:40b9a00000000000 4:3fe0000000000000 5:40b9ce4000000000 7:bfc370a3d70a3d71
|
||||
result host=h1,job=j1 ; 0:bff0000000000000 1:fa2ebb4882039496 2:40f7b26000000000 3:40b9a00000000000 4:0000000000000000 5:40b9ce0000000000 7:bff0000000000000
|
||||
end
|
||||
case math floor
|
||||
expr floor(a)
|
||||
grid -951820580376 1000 9
|
||||
series __name__=a ; 0:bfec872b020c49bb 1:c0c0d88000000000 2:3ff0000000000000 3:000fffffffffffff 4:c04771999999999a 7:c0a3178000000000 8:406b140000000000
|
||||
series __name__=a,host=h1,job=j2,zone=z2 ; 0:8000000000000001 1:401c000000000000 2:fea65e065a8a2a94 5:c08f110000000000 6:3ff0000000000000 7:3fd6353f7ced9169
|
||||
result - ; 0:bff0000000000000 1:c0c0d88000000000 2:3ff0000000000000 3:0000000000000000 4:c047800000000000 7:c0a3180000000000 8:406b000000000000
|
||||
result host=h1,job=j2,zone=z2 ; 0:bff0000000000000 1:401c000000000000 2:fea65e065a8a2a94 5:c08f180000000000 6:3ff0000000000000 7:0000000000000000
|
||||
end
|
||||
case math sqrt
|
||||
expr sqrt(a)
|
||||
grid 360879133452 1 4
|
||||
series __name__=a,host=h2,job=j1 ; 1:4020000000000000 2:7ff0000000000000
|
||||
series __name__=a,zone=z2 ; 1:4008000000000000 3:000fffffffffffff
|
||||
result host=h2,job=j1 ; 1:4006a09e667f3bcd 2:7ff0000000000000
|
||||
result zone=z2 ; 1:3ffbb67ae8584caa 3:1fffffffffffffff
|
||||
end
|
||||
case math sqrt
|
||||
expr sqrt(a)
|
||||
grid 3408288750287 60000 7
|
||||
series __name__=a,host=h1,job=j2,zone=z2 ; 1:8000000000016c4a 4:c0b304c000000000 5:bfe122d0e5604189 6:c010000000000000
|
||||
series __name__=a,zone=z2 ; 0:c018000000000000 1:c0801c0000000000 3:7fefffffffffffff 4:40f319d800000000
|
||||
result host=h1,job=j2,zone=z2 ; 1:fff8000000000000 4:fff8000000000000 5:fff8000000000000 6:fff8000000000000
|
||||
result zone=z2 ; 0:fff8000000000000 1:fff8000000000000 3:5fefffffffffffff 4:40717b5acfe20ea7
|
||||
end
|
||||
case math sqrt
|
||||
expr sqrt(a)
|
||||
grid 1581145206376 15000 6
|
||||
series __name__=a,host=h1,job=j2,zone=z2 ; 0:4111d85c00000000 1:4008000000000000 2:80000000005f05dc 4:0000000000000002 5:40a99b0000000000
|
||||
series __name__=a,zone=z1 ; 0:4341c37937e08000 1:3fe347ae147ae148 2:c021db851eb851ec 4:80000000000257c7 5:bfc849ba5e353f7d
|
||||
series __name__=a,zone=z2 ; 1:404e666666666667 2:c0d14c2000000000 3:3ff0000000000000 4:402193d70a3d70a4 5:bfd56e978d4fdf3c
|
||||
result host=h1,job=j2,zone=z2 ; 0:4080e5bca789e64e 1:3ffbb67ae8584caa 2:fff8000000000000 4:1e66a09e667f3bcd 5:404c9fee1d0b2e27
|
||||
result zone=z1 ; 0:4197d78400000000 1:3fe8d6b3b35c8629 2:fff8000000000000 4:fff8000000000000 5:fff8000000000000
|
||||
result zone=z2 ; 1:401f3092ece5bc36 2:fff8000000000000 3:3ff0000000000000 4:4007b77715376505 5:fff8000000000000
|
||||
end
|
||||
case math sqrt
|
||||
expr sqrt(a)
|
||||
grid -1962160767282 60000 5
|
||||
series __name__=a,host=h1,job=j2,zone=z1 ; 0:bfe0000000000000 1:ffefffffffffffff 3:40c368c000000000 4:c01e8ccccccccccd
|
||||
series __name__=a,host=h2,job=j2,zone=z2 ; 0:c00ca147ae147ae1 1:bff0000000000000 3:c1214fa400000000 4:c023770a3d70a3d7
|
||||
result host=h1,job=j2,zone=z1 ; 0:fff8000000000000 1:fff8000000000000 3:4058ebf7f9932c00 4:fff8000000000000
|
||||
result host=h2,job=j2,zone=z2 ; 0:fff8000000000000 1:fff8000000000000 3:fff8000000000000 4:fff8000000000000
|
||||
end
|
||||
case math sqrt
|
||||
expr sqrt(a)
|
||||
grid 1348888560782 60000 5
|
||||
series __name__=a,host=h1 ; 0:c0f78ce800000000 1:7dd845edd8013610 2:4008000000000000 3:40bffe0000000000 4:40b38bc000000000
|
||||
series __name__=a,host=h2,job=j2,zone=z2 ; 0:8000000000000015 2:4008000000000000 3:c0bb990000000000 4:800000000000304d
|
||||
series __name__=a,job=j2,zone=z1 ; 0:f8d7b7fd453652b6 1:fc0c5487d15de900 2:412195f400000000 3:3fe0000000000000 4:c010000000000000
|
||||
result host=h1 ; 0:fff8000000000000 1:5ee3b505cc2390b1 2:3ffbb67ae8584caa 3:40569fe95eb7dd64 4:4051af2e1d829cb0
|
||||
result host=h2,job=j2,zone=z2 ; 0:fff8000000000000 2:3ffbb67ae8584caa 3:fff8000000000000 4:fff8000000000000
|
||||
result job=j2,zone=z1 ; 0:fff8000000000000 1:fff8000000000000 2:4087b8e3fd11a620 3:3fe6a09e667f3bcd 4:fff8000000000000
|
||||
end
|
||||
case math sqrt
|
||||
expr sqrt(a)
|
||||
grid 471619062567 1 4
|
||||
series __name__=a,host=h1,job=j2,zone=z2 ; 0:fb5cce50d24c3e94 1:412a265600000000 2:40520a6666666667 3:402e6ccccccccccd
|
||||
series __name__=a,zone=z1 ; 0:4008000000000000 1:0000000000000000 3:fa74d01a2a81ef9c
|
||||
result host=h1,job=j2,zone=z2 ; 0:fff8000000000000 1:408ced67475c5d9d 2:4020fd5d290afe95 3:400f33db3bef5d6f
|
||||
result zone=z1 ; 0:3ffbb67ae8584caa 1:0000000000000000 3:fff8000000000000
|
||||
end
|
||||
case math round 3ff0000000000000
|
||||
expr round(a)
|
||||
grid -3022986188931 1000 4
|
||||
series __name__=a,host=h1,job=j1,zone=z1 ; 1:c10d0d8000000000
|
||||
series __name__=a,host=h2,job=j2,zone=z2 ; 0:c020000000000000 1:c0b23e0000000000 3:c0847c0000000000
|
||||
series __name__=a,job=j1 ; 0:c12c176800000000 1:7cc952a9e608b874 3:7df02c2e3b40b0c0
|
||||
result host=h1,job=j1,zone=z1 ; 1:c10d0d8000000000
|
||||
result host=h2,job=j2,zone=z2 ; 0:c020000000000000 1:c0b23e0000000000 3:c084780000000000
|
||||
result job=j1 ; 0:c12c176800000000 1:7cc952a9e608b874 3:7df02c2e3b40b0c0
|
||||
end
|
||||
case math round 3ff0000000000000
|
||||
expr round(a)
|
||||
grid 2091185698579 15000 4
|
||||
series __name__=a,host=h1,job=j1,zone=z2 ; 0:c014dc28f5c28f5c 2:0000000004bc4ca6 3:c022666666666667
|
||||
series __name__=a,job=j1,zone=z1 ; 0:8000001d70ebc600 1:c11391c400000000 2:405291999999999a
|
||||
result host=h1,job=j1,zone=z2 ; 0:c014000000000000 2:0000000000000000 3:c022000000000000
|
||||
result job=j1,zone=z1 ; 0:0000000000000000 1:c11391c400000000 2:4052800000000000
|
||||
end
|
||||
case math round 3ff0000000000000
|
||||
expr round(a)
|
||||
grid 1147230677471 15000 5
|
||||
series __name__=a,host=h1,job=j1 ; 0:bfe25e353f7ced92 1:80000007caacec44 4:bfe0000000000000
|
||||
series __name__=a,host=h1,zone=z1 ; 0:4000000000000000 1:4024000000000000 2:c024000000000000 3:fc09bd4e2758d562 4:00000000000fe1be
|
||||
series __name__=a,host=h2,zone=z2 ; 0:3fec99999999999a 1:c01f000000000000 3:41154d2000000000 4:40863c0000000000
|
||||
result host=h1,job=j1 ; 0:bff0000000000000 1:0000000000000000 4:0000000000000000
|
||||
result host=h1,zone=z1 ; 0:4000000000000000 1:4024000000000000 2:c024000000000000 3:fc09bd4e2758d562 4:0000000000000000
|
||||
result host=h2,zone=z2 ; 0:3ff0000000000000 1:c020000000000000 3:41154d2000000000 4:4086400000000000
|
||||
end
|
||||
case math round 3ff0000000000000
|
||||
expr round(a)
|
||||
grid -2682458523828 1 4
|
||||
series __name__=a,host=h1,job=j1 ; 0:bfe0f0a3d70a3d71 1:c056926666666667 2:c008c7ae147ae148 3:bfd1fdf3b645a1cb
|
||||
series __name__=a,host=h2,job=j1,zone=z1 ; 0:4010000000000000 2:4341c37937e08000 3:4008000000000000
|
||||
series __name__=a,host=h2,job=j2,zone=z1 ; 1:40068ccccccccccd 2:3ff0000000000000 3:40af9a0000000000
|
||||
result host=h1,job=j1 ; 0:bff0000000000000 1:c056800000000000 2:c008000000000000 3:0000000000000000
|
||||
result host=h2,job=j1,zone=z1 ; 0:4010000000000000 2:4341c37937e08000 3:4008000000000000
|
||||
result host=h2,job=j2,zone=z1 ; 1:4008000000000000 2:3ff0000000000000 3:40af9a0000000000
|
||||
end
|
||||
case math round 3ff0000000000000
|
||||
expr round(a, 1)
|
||||
grid -2013284349647 15000 5
|
||||
series __name__=a,host=h1,zone=z2 ; 0:800000000000ab55 3:800000118741c447 4:c0e0cd9000000000
|
||||
series __name__=a,zone=z1 ; 0:40f22d2000000000 1:c0555ccccccccccd 3:c122287000000000 4:c072480000000000
|
||||
result host=h1,zone=z2 ; 0:0000000000000000 3:0000000000000000 4:c0e0cd8000000000
|
||||
result zone=z1 ; 0:40f22d2000000000 1:c055400000000000 3:c122287000000000 4:c072400000000000
|
||||
end
|
||||
case math round 3ff0000000000000
|
||||
expr round(a, 1)
|
||||
grid -1934277777347 60000 9
|
||||
series __name__=a,job=j2,zone=z1 ; 0:79498be2a0084080 1:40ea932000000000 3:4115aae000000000 5:ffe1ccf385ebc8a0 6:40b77dc000000000 7:40fae2d000000000 8:0000000000000000
|
||||
result job=j2,zone=z1 ; 0:79498be2a0084080 1:40ea932000000000 3:4115aae000000000 5:ffe1ccf385ebc8a0 6:40b77e0000000000 7:40fae2d000000000 8:0000000000000000
|
||||
end
|
||||
case math round 3fe0000000000000
|
||||
expr round(a, 0.5)
|
||||
grid -1733790336954 15000 7
|
||||
series __name__=a,host=h1,zone=z2 ; 1:4008000000000000 2:c0b8948000000000 3:4123524a00000000 4:40152f5c28f5c28f 6:fea794b1b4e30050
|
||||
result host=h1,zone=z2 ; 1:4008000000000000 2:c0b8948000000000 3:4123524a00000000 4:4016000000000000 6:fea794b1b4e30050
|
||||
end
|
||||
case math round 3fe0000000000000
|
||||
expr round(a, 0.5)
|
||||
grid 1861395254097 1 10
|
||||
series __name__=a,host=h1,job=j1,zone=z2 ; 1:c341c37937e08000 2:411b8ac800000000 4:8000007c3d9a1780 5:80000001c0d0aa17 7:0000000000066342 8:4014000000000000 9:405249999999999a
|
||||
series __name__=a,host=h2,job=j2,zone=z1 ; 2:c010000000000000 5:40ddd26000000000 6:405cf00000000000 7:40b246c000000000 8:78f187976c2eea08 9:c0f4677000000000
|
||||
result host=h1,job=j1,zone=z2 ; 1:c341c37937e08000 2:411b8ac800000000 4:0000000000000000 5:0000000000000000 7:0000000000000000 8:4014000000000000 9:4052400000000000
|
||||
result host=h2,job=j2,zone=z1 ; 2:c010000000000000 5:40ddd26000000000 6:405d000000000000 7:40b2470000000000 8:78f187976c2eea08 9:c0f4677000000000
|
||||
end
|
||||
case math round 3fb999999999999a
|
||||
expr round(a, 0.1)
|
||||
grid 892799548686 15000 4
|
||||
series __name__=a,host=h2,job=j1 ; 0:3fd77ced916872b0 1:c0f571d800000000 2:000fffffffffffff
|
||||
series __name__=a,host=h2,zone=z2 ; 1:7d2967c5469db91e 3:7fe1ccf385ebc8a0
|
||||
series __name__=a,job=j1,zone=z2 ; 0:7fe1ccf385ebc8a0 1:3ff0000000000000 2:40f0e69000000000 3:c089a40000000000
|
||||
result host=h2,job=j1 ; 0:3fd999999999999a 1:c0f571d800000000 2:0000000000000000
|
||||
result host=h2,zone=z2 ; 1:7d2967c5469db91e 3:7ff0000000000000
|
||||
result job=j1,zone=z2 ; 0:7ff0000000000000 1:3ff0000000000000 2:40f0e69000000000 3:c089a40000000000
|
||||
end
|
||||
case math round 3fb999999999999a
|
||||
expr round(a, 0.1)
|
||||
grid 1935929279437 1 6
|
||||
series __name__=a,host=h2,job=j1 ; 0:4121076000000000 1:40eff54000000000 2:4081b30000000000 4:40e0059000000000
|
||||
series __name__=a,host=h2,zone=z2 ; 0:4008000000000000 2:c08e3d0000000000 3:80000000000761f1 4:3fe1428f5c28f5c3
|
||||
series __name__=a,job=j2,zone=z2 ; 1:4086120000000000 3:4120f0ea00000000 5:8000000000000000
|
||||
result host=h2,job=j1 ; 0:4121076000000000 1:40eff54000000000 2:4081b33333333333 4:40e0059000000000
|
||||
result host=h2,zone=z2 ; 0:4008000000000000 2:c08e3ccccccccccd 3:0000000000000000 4:3fe0000000000000
|
||||
result job=j2,zone=z2 ; 1:4086126666666666 3:4120f0ea00000000 5:0000000000000000
|
||||
end
|
||||
case math round 4008000000000000
|
||||
expr round(a, 3)
|
||||
grid -1472300150492 1 5
|
||||
series __name__=a,host=h2,job=j2,zone=z2 ; 1:bfe0000000000000 2:8000000000000074 3:40c1d8c000000000 4:4341c37937e08000
|
||||
result host=h2,job=j2,zone=z2 ; 1:0000000000000000 2:0000000000000000 3:40c1d90000000000 4:4341c37937e08000
|
||||
end
|
||||
case math round 4008000000000000
|
||||
expr round(a, 3)
|
||||
grid -553570169856 60000 3
|
||||
series __name__=a,host=h1,zone=z2 ; 1:f90da661f15293b8 2:ffe1ccf385ebc8a0
|
||||
series __name__=a,zone=z1 ; 0:40236ccccccccccd 1:4095590000000000 2:4008828f5c28f5c3
|
||||
result host=h1,zone=z2 ; 1:f90da661f15293b7 2:ffe1ccf385ebc8a0
|
||||
result zone=z1 ; 0:4022000000000000 1:4095540000000000 2:4008000000000000
|
||||
end
|
||||
case math round c000000000000000
|
||||
expr round(a, (-2))
|
||||
grid 793533970766 60000 4
|
||||
series __name__=a,host=h1,job=j2,zone=z1 ; 1:c0763c0000000000 2:40e8399000000000 3:4125f51800000000
|
||||
series __name__=a,host=h1,job=j2,zone=z2 ; 1:3ff0000000000000 2:bfebd604189374bd 3:bfee570a3d70a3d7
|
||||
series __name__=a,job=j2,zone=z1 ; 0:4004fd70a3d70a3e 1:4000000000000000 2:c341c37937e08000
|
||||
result host=h1,job=j2,zone=z1 ; 1:c076400000000000 2:40e8398000000000 3:4125f51800000000
|
||||
result host=h1,job=j2,zone=z2 ; 1:8000000000000000 2:8000000000000000 3:8000000000000000
|
||||
result job=j2,zone=z1 ; 0:4000000000000000 1:4000000000000000 2:c341c37937e08000
|
||||
end
|
||||
case math round c000000000000000
|
||||
expr round(a, (-2))
|
||||
grid 1566718578387 15000 5
|
||||
series __name__=a,host=h2,zone=z2 ; 0:3fe6851eb851eb85 1:c0f3948000000000 2:4056eccccccccccd 3:4020000000000000 4:c089650000000000
|
||||
series __name__=a,job=j2,zone=z2 ; 0:c0f0c5c000000000 1:ffefffffffffffff 2:c018e66666666667 3:c0ef4fa000000000 4:3fe0000000000000
|
||||
series __name__=a,zone=z2 ; 0:c07f820000000000 2:8000000000000089 3:3fe91fbe76c8b439 4:3fe0000000000000
|
||||
result host=h2,zone=z2 ; 0:8000000000000000 1:c0f3948000000000 2:4057000000000000 3:4020000000000000 4:c089600000000000
|
||||
result job=j2,zone=z2 ; 0:c0f0c5c000000000 1:ffefffffffffffff 2:c018000000000000 3:c0ef4fc000000000 4:8000000000000000
|
||||
result zone=z2 ; 0:c07f800000000000 2:8000000000000000 3:8000000000000000 4:8000000000000000
|
||||
end
|
||||
case math round 0000000000000000
|
||||
expr round(a, 0)
|
||||
grid -1026082157982 60000 7
|
||||
series __name__=a,host=h1,zone=z2 ; 0:7ff0000000000000 1:c341c37937e08000 2:c018000000000000 3:7ff8000000000001 4:c024000000000000 5:4056533333333334 6:7ff8000000000001
|
||||
series __name__=a,job=j2,zone=z2 ; 0:40abb48000000000 4:3ff0000000000000 5:4052580000000000 6:4082180000000000
|
||||
series __name__=a,zone=z1 ; 0:8000000000000001 2:c024000000000000 4:c0f2694800000000 5:3fe0000000000000 6:bfe0000000000000
|
||||
result host=h1,zone=z2 ; 0:fff8000000000000 1:fff8000000000000 2:fff8000000000000 3:7ff8000000000001 4:fff8000000000000 5:fff8000000000000 6:7ff8000000000001
|
||||
result job=j2,zone=z2 ; 0:fff8000000000000 4:fff8000000000000 5:fff8000000000000 6:fff8000000000000
|
||||
result zone=z1 ; 0:fff8000000000000 2:fff8000000000000 4:fff8000000000000 5:fff8000000000000 6:fff8000000000000
|
||||
end
|
||||
case math round 0000000000000000
|
||||
expr round(a, 0)
|
||||
grid -2669275454038 60000 5
|
||||
series __name__=a,host=h1,job=j2 ; 0:407aae0000000000 1:4022e1eb851eb852 2:ffe1ccf385ebc8a0 3:7fe1ccf385ebc8a0 4:c0be4c4000000000
|
||||
series __name__=a,host=h2,job=j2,zone=z2 ; 0:c0f03ab000000000 1:c00a51eb851eb852 2:bfe8353f7ced9169 3:c101ce9800000000 4:4023e28f5c28f5c3
|
||||
result host=h1,job=j2 ; 0:fff8000000000000 1:fff8000000000000 2:fff8000000000000 3:fff8000000000000 4:fff8000000000000
|
||||
result host=h2,job=j2,zone=z2 ; 0:fff8000000000000 1:fff8000000000000 2:fff8000000000000 3:fff8000000000000 4:fff8000000000000
|
||||
end
|
||||
case math round 7ff8000000000001
|
||||
expr round(a, NaN)
|
||||
grid -119798354174 1000 5
|
||||
series __name__=a,job=j2 ; 0:40aff18000000000 1:c020000000000000 2:40f39ac000000000 4:4341c37937e08000
|
||||
result job=j2 ; 0:7ff8000000000001 1:7ff8000000000001 2:7ff8000000000001 4:7ff8000000000001
|
||||
end
|
||||
case math round 7ff8000000000001
|
||||
expr round(a, NaN)
|
||||
grid -2544211762022 60000 5
|
||||
series __name__=a,host=h1,job=j2 ; 1:80000000000000d7 2:bfe2a1cac083126f 3:c039b33333333334
|
||||
series __name__=a,host=h2,zone=z1 ; 0:4008000000000000 2:ffe1ccf385ebc8a0 3:4064b80000000000
|
||||
series __name__=a,host=h2,zone=z2 ; 0:c02189999999999a 2:bfd34189374bc6a8 3:fc2814243a77b35e
|
||||
result host=h1,job=j2 ; 1:7ff8000000000001 2:7ff8000000000001 3:7ff8000000000001
|
||||
result host=h2,zone=z1 ; 0:7ff8000000000001 2:7ff8000000000001 3:7ff8000000000001
|
||||
result host=h2,zone=z2 ; 0:7ff8000000000001 2:7ff8000000000001 3:7ff8000000000001
|
||||
end
|
||||
case math round 7ff0000000000000
|
||||
expr round(a, Inf)
|
||||
grid 877674442098 1 10
|
||||
series __name__=a ; 2:c0b4438000000000 3:c117450800000000 5:bfc4f5c28f5c28f6 8:401671eb851eb852
|
||||
series __name__=a,host=h1,job=j1,zone=z1 ; 1:0000000000000000 3:7fefffffffffffff 5:bfed916872b020c5 6:4000000000000000 8:c12ab1fc00000000 9:f8483959c1011e02
|
||||
result - ; 2:fff8000000000000 3:fff8000000000000 5:fff8000000000000 8:fff8000000000000
|
||||
result host=h1,job=j1,zone=z1 ; 1:fff8000000000000 3:fff8000000000000 5:fff8000000000000 6:fff8000000000000 8:fff8000000000000 9:fff8000000000000
|
||||
end
|
||||
case math round 7ff0000000000000
|
||||
expr round(a, Inf)
|
||||
grid 2715442887313 15000 8
|
||||
series __name__=a,host=h1 ; 0:c341c37937e08000 1:8000000352c83617 2:c01bc7ae147ae148 3:4083920000000000 4:401e6147ae147ae1 5:401c000000000000 6:7ff0000000000000 7:c0d01d0000000000
|
||||
series __name__=a,host=h2,job=j1,zone=z2 ; 0:f85d39a07811acb8 1:4039a66666666667 2:8029d1b066bfc15c 3:405264cccccccccd 4:41154b2c00000000 5:c0f4363800000000 6:7fefffffffffffff
|
||||
series __name__=a,job=j1,zone=z2 ; 0:0000000000000001 1:410a13c800000000 4:fb2862529867c73a 7:c020000000000000
|
||||
result host=h1 ; 0:fff8000000000000 1:fff8000000000000 2:fff8000000000000 3:fff8000000000000 4:fff8000000000000 5:fff8000000000000 6:fff8000000000000 7:fff8000000000000
|
||||
result host=h2,job=j1,zone=z2 ; 0:fff8000000000000 1:fff8000000000000 2:fff8000000000000 3:fff8000000000000 4:fff8000000000000 5:fff8000000000000 6:fff8000000000000
|
||||
result job=j1,zone=z2 ; 0:fff8000000000000 1:fff8000000000000 4:fff8000000000000 7:fff8000000000000
|
||||
end
|
||||
case math round 01a56e1fc2f8f359
|
||||
expr round(a, 1e-300)
|
||||
grid -3966454058935 15000 4
|
||||
series __name__=a,host=h1,zone=z2 ; 1:7fe1ccf385ebc8a0 2:c0c34b6000000000 3:c10a1f8000000000
|
||||
series __name__=a,host=h2 ; 1:c0408ccccccccccd 2:4014000000000000 3:4020000000000000
|
||||
result host=h1,zone=z2 ; 1:7ff0000000000000 2:c0c34b6000000000 3:c10a1f8000000000
|
||||
result host=h2 ; 1:c0408ccccccccccd 2:4014000000000000 3:4020000000000000
|
||||
end
|
||||
case math round 01a56e1fc2f8f359
|
||||
expr round(a, 1e-300)
|
||||
grid -716571138391 15000 7
|
||||
series __name__=a,job=j2 ; 0:40534d999999999a 1:7fe1ccf385ebc8a0 2:c0774c0000000000 3:c008000000000000 4:3fe8ced916872b02 5:c020b70a3d70a3d7 6:7fe1ccf385ebc8a0
|
||||
result job=j2 ; 0:40534d999999999a 1:7ff0000000000000 2:c0774c0000000000 3:c008000000000000 4:3fe8ced916872b03 5:c020b70a3d70a3d7 6:7ff0000000000000
|
||||
end
|
||||
case math round 3fd0000000000000
|
||||
expr round(a, 0.25)
|
||||
grid 571617009559 1 10
|
||||
series __name__=a,host=h1,job=j1,zone=z1 ; 0:40e753e000000000 1:ffefffffffffffff 2:408a8a0000000000 4:8000000000ca7e01 5:7e4c0821a7721ad8 6:4022000000000000 7:c0f255c000000000 8:c080840000000000 9:c041cccccccccccd
|
||||
series __name__=a,job=j2,zone=z1 ; 0:bfee4fdf3b645a1d 2:00190396fed4d716 5:3ff4147ae147ae15 6:c0d10a8000000000 8:0000000000f835bd 9:c0737e0000000000
|
||||
series __name__=a,zone=z1 ; 0:4008000000000000 1:bfe16a7ef9db22d1 2:7c981b9fc7dc7ca0 3:00000000000024d9 4:0000000000000003 5:0000000000000008 6:bff0000000000000 7:4088e30000000000 8:40c1aa8000000000 9:4052aa6666666667
|
||||
result host=h1,job=j1,zone=z1 ; 0:40e753e000000000 1:fff0000000000000 2:408a8a0000000000 4:0000000000000000 5:7e4c0821a7721ad8 6:4022000000000000 7:c0f255c000000000 8:c080840000000000 9:c041c00000000000
|
||||
result job=j2,zone=z1 ; 0:bff0000000000000 2:0000000000000000 5:3ff4000000000000 6:c0d10a8000000000 8:0000000000000000 9:c0737c0000000000
|
||||
result zone=z1 ; 0:4008000000000000 1:bfe0000000000000 2:7c981b9fc7dc7ca0 3:0000000000000000 4:0000000000000000 5:0000000000000000 6:bff0000000000000 7:4088e40000000000 8:40c1aa8000000000 9:4052b00000000000
|
||||
end
|
||||
case math round 3fd0000000000000
|
||||
expr round(a, 0.25)
|
||||
grid 24248430186 1 5
|
||||
series __name__=a,host=h1,zone=z1 ; 0:c010000000000000 1:4082150000000000 2:7b27f6e2e1474872 3:c11bb1d800000000 4:800000068c10face
|
||||
series __name__=a,job=j1,zone=z2 ; 0:40adec0000000000 1:7e12ad13ecfb769e 3:40163851eb851eb8 4:f9d77d15cf193c48
|
||||
result host=h1,zone=z1 ; 0:c010000000000000 1:4082160000000000 2:7b27f6e2e1474872 3:c11bb1d800000000 4:0000000000000000
|
||||
result job=j1,zone=z2 ; 0:40adec0000000000 1:7e12ad13ecfb769e 3:4016000000000000 4:f9d77d15cf193c48
|
||||
end
|
||||
@@ -0,0 +1,504 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use common_query::prometheus::PROMETHEUS_STALE_NAN_BITS;
|
||||
use datafusion::functions::math;
|
||||
use datafusion_common::config::ConfigOptions;
|
||||
use datafusion_expr::{ColumnarValue, ScalarFunctionArgs};
|
||||
use datatypes::arrow::array::{AsArray, UInt32Array};
|
||||
use datatypes::arrow::buffer::NullBuffer;
|
||||
use datatypes::arrow::datatypes::{DataType, Field, Float64Type};
|
||||
use datatypes::series::histogram::{
|
||||
BucketSideView, CounterResetHint, HistogramBatch, HistogramBatchBuilder, HistogramView,
|
||||
};
|
||||
use datatypes::series::samples::{FieldSpec, HistogramSamples, SampleRef};
|
||||
use datatypes::series::time::{EvalGrid, GridRange, GridSpec};
|
||||
use datatypes::series::{Label, MetricName};
|
||||
|
||||
use super::*;
|
||||
use crate::functions::IeeeSqrt;
|
||||
|
||||
mod prometheus;
|
||||
|
||||
fn schema(names: &[&str]) -> SeriesSchema {
|
||||
SeriesSchema {
|
||||
fields: names
|
||||
.iter()
|
||||
.map(|name| FieldSpec {
|
||||
name: (*name).into(),
|
||||
})
|
||||
.collect(),
|
||||
eval_grid: Some(Arc::new(
|
||||
EvalGrid::try_new(GridSpec::try_new(1000, 500, 64).unwrap()).unwrap(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn series(schema: &SeriesSchema, start: usize, fields: Vec<Samples>) -> Series {
|
||||
let time = TimeAxis::Grid(
|
||||
GridRange::try_new(schema.eval_grid.clone().unwrap(), start, fields[0].len()).unwrap(),
|
||||
);
|
||||
let metric = MetricName::try_new(
|
||||
Some("requests".into()),
|
||||
vec![
|
||||
Label {
|
||||
name: "host".into(),
|
||||
value: "a".into(),
|
||||
},
|
||||
Label {
|
||||
name: "empty".into(),
|
||||
value: "".into(),
|
||||
},
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
Series::try_new(
|
||||
schema,
|
||||
metric,
|
||||
time,
|
||||
SeriesFields::try_from_vec(fields).unwrap(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn floats(samples: &[Option<f64>]) -> Samples {
|
||||
Samples::from_float(Float64Array::from(samples.to_vec()))
|
||||
}
|
||||
|
||||
fn assert_floats(actual: &Samples, expected: &[Option<f64>]) {
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for (index, (actual, expected)) in actual.iter().zip(expected).enumerate() {
|
||||
match (actual, expected) {
|
||||
(SampleRef::Missing, None) => {}
|
||||
(SampleRef::Float(actual), Some(expected)) if expected.is_nan() => {
|
||||
assert!(actual.is_nan(), "sample {index}: {actual} is not NaN");
|
||||
}
|
||||
(SampleRef::Float(actual), Some(expected)) => {
|
||||
assert_eq!(actual.to_bits(), expected.to_bits(), "sample {index}");
|
||||
}
|
||||
_ => panic!("unexpected sample kind at {index}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_preserves_missing_and_uses_ieee_float_semantics() {
|
||||
// Boundaries: negative values, signed zero, infinities, ordinary NaN and Missing.
|
||||
let schema = schema(&["value"]);
|
||||
let input = series(
|
||||
&schema,
|
||||
3,
|
||||
vec![floats(&[
|
||||
Some(-2.5),
|
||||
Some(-0.5),
|
||||
Some(-0.0),
|
||||
Some(0.0),
|
||||
Some(4.0),
|
||||
Some(f64::INFINITY),
|
||||
Some(f64::NEG_INFINITY),
|
||||
Some(f64::NAN),
|
||||
None,
|
||||
])],
|
||||
);
|
||||
let cases = [
|
||||
(
|
||||
MathOp::Abs,
|
||||
[
|
||||
2.5,
|
||||
0.5,
|
||||
0.0,
|
||||
0.0,
|
||||
4.0,
|
||||
f64::INFINITY,
|
||||
f64::INFINITY,
|
||||
f64::NAN,
|
||||
],
|
||||
),
|
||||
(
|
||||
MathOp::Ceil,
|
||||
[
|
||||
-2.0,
|
||||
-0.0,
|
||||
-0.0,
|
||||
0.0,
|
||||
4.0,
|
||||
f64::INFINITY,
|
||||
f64::NEG_INFINITY,
|
||||
f64::NAN,
|
||||
],
|
||||
),
|
||||
(
|
||||
MathOp::Floor,
|
||||
[
|
||||
-3.0,
|
||||
-1.0,
|
||||
-0.0,
|
||||
0.0,
|
||||
4.0,
|
||||
f64::INFINITY,
|
||||
f64::NEG_INFINITY,
|
||||
f64::NAN,
|
||||
],
|
||||
),
|
||||
(
|
||||
MathOp::Sqrt,
|
||||
[
|
||||
f64::NAN,
|
||||
f64::NAN,
|
||||
-0.0,
|
||||
0.0,
|
||||
2.0,
|
||||
f64::INFINITY,
|
||||
f64::NAN,
|
||||
f64::NAN,
|
||||
],
|
||||
),
|
||||
(
|
||||
MathOp::Round { nearest: 1.0 },
|
||||
[
|
||||
-2.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
4.0,
|
||||
f64::INFINITY,
|
||||
f64::NEG_INFINITY,
|
||||
f64::NAN,
|
||||
],
|
||||
),
|
||||
];
|
||||
for (op, expected) in cases {
|
||||
let output = map_math(&schema, &input, op).unwrap();
|
||||
let expected = expected
|
||||
.into_iter()
|
||||
.map(Some)
|
||||
.chain([None])
|
||||
.collect::<Vec<_>>();
|
||||
assert_floats(output.fields.get(0).unwrap(), &expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_multifield_slices_keep_absolute_grid_and_labels() {
|
||||
let schema = schema(&["left", "right"]);
|
||||
let first = floats(&[Some(99.0), Some(-1.2), None, Some(-3.2), Some(99.0)]);
|
||||
let second = floats(&[Some(99.0), Some(4.8), Some(-0.0), None, Some(99.0)]);
|
||||
let fields = vec![
|
||||
Samples::try_from_chunks(vec![first.slice(1, 2).unwrap(), first.slice(3, 1).unwrap()])
|
||||
.unwrap(),
|
||||
Samples::try_from_chunks(vec![
|
||||
second.slice(1, 1).unwrap(),
|
||||
second.slice(2, 2).unwrap(),
|
||||
])
|
||||
.unwrap(),
|
||||
];
|
||||
let input = series(&schema, 7, fields);
|
||||
let output = map_math(&schema, &input, MathOp::Floor).unwrap();
|
||||
let TimeAxis::Grid(range) = &output.time else {
|
||||
panic!("lost grid axis")
|
||||
};
|
||||
assert_eq!((range.start(), range.len()), (7, 3));
|
||||
assert!(Arc::ptr_eq(
|
||||
range.grid(),
|
||||
schema.eval_grid.as_ref().unwrap()
|
||||
));
|
||||
assert_eq!(output.time.timestamp(0), Some(4500));
|
||||
assert_eq!(output.time.timestamp(2), Some(5500));
|
||||
// Prometheus drops the name; main keeps it because its HTTP adapter follows Call arguments.
|
||||
assert_eq!(output.metric.name(), None);
|
||||
assert_eq!(output.metric.tags(), &input.metric.tags()[1..]);
|
||||
assert_eq!(input.metric.name(), Some("requests"));
|
||||
assert_floats(
|
||||
input.fields.get(0).unwrap(),
|
||||
&[Some(-1.2), None, Some(-3.2)],
|
||||
);
|
||||
for (field, lengths) in output.fields.iter().zip([[2, 1], [1, 2]]) {
|
||||
assert_eq!(
|
||||
field
|
||||
.segments()
|
||||
.iter()
|
||||
.map(Samples::len)
|
||||
.collect::<Vec<_>>(),
|
||||
lengths
|
||||
);
|
||||
}
|
||||
drop(input);
|
||||
drop(schema);
|
||||
drop(first);
|
||||
drop(second);
|
||||
assert_floats(
|
||||
output.fields.get(0).unwrap(),
|
||||
&[Some(-2.0), None, Some(-4.0)],
|
||||
);
|
||||
assert_floats(
|
||||
output.fields.get(1).unwrap(),
|
||||
&[Some(4.0), Some(-0.0), None],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_and_all_missing_ranges_are_not_expanded() {
|
||||
let schema = schema(&["value"]);
|
||||
// Empty at grid end, one missing point, and a longer all-missing range.
|
||||
for (start, len) in [(64, 0), (9, 1), (2, 4)] {
|
||||
let input = series(&schema, start, vec![floats(&vec![None; len])]);
|
||||
for op in [
|
||||
MathOp::Abs,
|
||||
MathOp::Ceil,
|
||||
MathOp::Floor,
|
||||
MathOp::Sqrt,
|
||||
MathOp::Round { nearest: 0.0 },
|
||||
] {
|
||||
let output = map_math(&schema, &input, op).unwrap();
|
||||
let TimeAxis::Grid(range) = &output.time else {
|
||||
panic!("lost grid axis")
|
||||
};
|
||||
assert_eq!((range.start(), range.len()), (start, len));
|
||||
assert_floats(output.fields.get(0).unwrap(), &vec![None; len]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluated_missingness_depends_on_validity_not_nan_payloads() {
|
||||
let schema = schema(&["value"]);
|
||||
let stale = f64::from_bits(PROMETHEUS_STALE_NAN_BITS);
|
||||
let ordinary_nan = f64::from_bits(0x7ff8_0000_0000_0042);
|
||||
let input = series(
|
||||
&schema,
|
||||
1,
|
||||
vec![Samples::from_float(Float64Array::new(
|
||||
vec![stale, ordinary_nan, -1.0].into(),
|
||||
Some(NullBuffer::from(vec![false, true, true])),
|
||||
))],
|
||||
);
|
||||
let output = map_math(&schema, &input, MathOp::Abs).unwrap();
|
||||
assert_floats(
|
||||
output.fields.get(0).unwrap(),
|
||||
&[None, Some(ordinary_nan), Some(1.0)],
|
||||
);
|
||||
let SampleRef::Float(actual_nan) = output.fields.get(0).unwrap().sample(1) else {
|
||||
panic!("lost NaN")
|
||||
};
|
||||
assert_eq!(actual_nan.to_bits(), ordinary_nan.to_bits());
|
||||
|
||||
// Removing the sign of this ordinary NaN produces the reserved raw stale bits. A later Map
|
||||
// must not reinterpret that computed value as a missing sample or reject the evaluated input.
|
||||
let negative_nan = f64::from_bits(PROMETHEUS_STALE_NAN_BITS | (1_u64 << 63));
|
||||
let input = series(&schema, 1, vec![floats(&[Some(negative_nan)])]);
|
||||
let absolute = map_math(&schema, &input, MathOp::Abs).unwrap();
|
||||
let SampleRef::Float(computed_nan) = absolute.fields.get(0).unwrap().sample(0) else {
|
||||
panic!("lost computed NaN")
|
||||
};
|
||||
assert_eq!(computed_nan.to_bits(), PROMETHEUS_STALE_NAN_BITS);
|
||||
let rounded = map_math(&schema, &absolute, MathOp::Ceil).unwrap();
|
||||
assert_floats(rounded.fields.get(0).unwrap(), &[Some(f64::NAN)]);
|
||||
}
|
||||
|
||||
fn histogram_batch() -> Arc<HistogramBatch> {
|
||||
let empty_side = BucketSideView {
|
||||
span_offsets: &[],
|
||||
span_lengths: &[],
|
||||
buckets: &[],
|
||||
};
|
||||
let mut builder = HistogramBatchBuilder::new();
|
||||
builder
|
||||
.push(HistogramView {
|
||||
schema: 1,
|
||||
zero_threshold: 0.01,
|
||||
sum: 0.0,
|
||||
count: 1.0,
|
||||
zero_count: 1.0,
|
||||
reset_hint: CounterResetHint::Unknown,
|
||||
start_timestamp: None,
|
||||
custom_values: &[],
|
||||
positive: empty_side,
|
||||
negative: empty_side,
|
||||
})
|
||||
.unwrap();
|
||||
builder.finish().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn histogram_and_mixed_fields_are_rejected_without_partial_success() {
|
||||
let batch = histogram_batch();
|
||||
let histogram = Samples::try_from_histogram(UInt32Array::from(vec![0]), batch.clone()).unwrap();
|
||||
let mixed = Samples::try_from_mixed(
|
||||
Float64Array::from(vec![Some(1.0), None]),
|
||||
HistogramSamples::try_new(UInt32Array::from(vec![None, Some(0)]), batch.clone()).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let null_histogram = Samples::try_from_histogram(UInt32Array::from(vec![None]), batch).unwrap();
|
||||
let chunks = Samples::try_from_chunks(vec![floats(&[Some(-1.0)]), histogram.clone()]).unwrap();
|
||||
let schema = schema(&["float", "unsupported"]);
|
||||
for unsupported in [histogram, mixed, null_histogram, chunks] {
|
||||
let prefix = floats(&vec![Some(-2.0); unsupported.len()]);
|
||||
let input = series(&schema, 2, vec![prefix, unsupported]);
|
||||
assert!(matches!(
|
||||
map_math(&schema, &input, MathOp::Abs),
|
||||
Err(DataFusionError::NotImplemented(_))
|
||||
));
|
||||
assert_floats(
|
||||
input.fields.get(0).unwrap(),
|
||||
&vec![Some(-2.0); input.time.len()],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raw_time_axes_are_not_treated_as_evaluated_grids() {
|
||||
let mut schema = schema(&["value"]);
|
||||
let input = series(&schema, 2, vec![floats(&[Some(-1.0), Some(2.0)])]);
|
||||
schema.eval_grid = None;
|
||||
for time in [
|
||||
TimeAxis::try_new(vec![2000, 2500].into()).unwrap(),
|
||||
TimeAxis::try_from_chunks(vec![vec![2000].into(), vec![2500].into()]).unwrap(),
|
||||
] {
|
||||
let raw =
|
||||
Series::try_new(&schema, input.metric.clone(), time, input.fields.clone()).unwrap();
|
||||
assert!(
|
||||
matches!(map_math(&schema, &raw, MathOp::Abs), Err(DataFusionError::Execution(reason)) if reason.contains("grid"))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mismatched_schema_and_mutated_field_lengths_return_typed_errors() {
|
||||
let schema = schema(&["value"]);
|
||||
let mut input = series(&schema, 2, vec![floats(&[Some(-1.0), Some(2.0)])]);
|
||||
let mut other_grid = schema.clone();
|
||||
other_grid.eval_grid = Some(Arc::new(
|
||||
EvalGrid::try_new(GridSpec::try_new(0, 500, 64).unwrap()).unwrap(),
|
||||
));
|
||||
assert!(
|
||||
matches!(map_math(&other_grid, &input, MathOp::Abs), Err(DataFusionError::External(error)) if error.is::<datatypes::error::Error>())
|
||||
);
|
||||
input.fields = SeriesFields::one(floats(&[Some(-1.0)]));
|
||||
assert!(
|
||||
matches!(map_math(&schema, &input, MathOp::Abs), Err(DataFusionError::External(error)) if error.is::<datatypes::error::Error>())
|
||||
);
|
||||
}
|
||||
|
||||
fn ordinary_math(op: MathOp, input: Float64Array) -> Float64Array {
|
||||
// Calibration against main's UDFs, whose values agree with Prometheus for these functions;
|
||||
// the Prometheus engine's own results are checked in `prometheus`.
|
||||
let number_rows = input.len();
|
||||
let args = vec![ColumnarValue::Array(Arc::new(input))];
|
||||
let udf = match op {
|
||||
MathOp::Abs => math::abs(),
|
||||
MathOp::Ceil => math::ceil(),
|
||||
MathOp::Floor => math::floor(),
|
||||
MathOp::Sqrt => Arc::new(IeeeSqrt::scalar_udf()),
|
||||
MathOp::Round { .. } => unreachable!("round is checked against Prometheus fixtures"),
|
||||
};
|
||||
let arg_fields = args
|
||||
.iter()
|
||||
.map(|arg| Arc::new(Field::new("value", arg.data_type(), true)))
|
||||
.collect();
|
||||
let output = udf
|
||||
.invoke_with_args(ScalarFunctionArgs {
|
||||
args,
|
||||
arg_fields,
|
||||
number_rows,
|
||||
return_field: Arc::new(Field::new("output", DataType::Float64, true)),
|
||||
config_options: Arc::new(ConfigOptions::default()),
|
||||
})
|
||||
.unwrap();
|
||||
let ColumnarValue::Array(output) = output else {
|
||||
panic!("expected array")
|
||||
};
|
||||
output.as_primitive::<Float64Type>().clone()
|
||||
}
|
||||
|
||||
fn float_boundary_samples() -> Float64Array {
|
||||
let mut samples = vec![
|
||||
Some(-2.5),
|
||||
Some((-0.5_f64).next_down()),
|
||||
Some(-0.5),
|
||||
Some((-0.5_f64).next_up()),
|
||||
Some(0.5_f64.next_down()),
|
||||
Some(0.5),
|
||||
Some(0.5_f64.next_up()),
|
||||
Some(2.5),
|
||||
Some(123.456),
|
||||
];
|
||||
// Both signs, every exponent, and mantissa boundaries cover zeros, subnormals, normal
|
||||
// magnitudes, infinities, and signaling/quiet NaNs without a random seed or new dependency.
|
||||
for exponent in 0..=0x7ff_u64 {
|
||||
for mantissa in [0, 1, 1 << 51, (1 << 52) - 1] {
|
||||
for sign in [0, 1_u64 << 63] {
|
||||
samples.push(Some(f64::from_bits(sign | (exponent << 52) | mantissa)));
|
||||
}
|
||||
}
|
||||
samples.push(None);
|
||||
}
|
||||
Float64Array::from(samples)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_matches_main_udfs_across_float_boundaries() {
|
||||
let input = float_boundary_samples();
|
||||
let mut schema = schema(&["dense", "chunked"]);
|
||||
let grid_len = u64::try_from(input.len() + 6).unwrap();
|
||||
schema.eval_grid = Some(Arc::new(
|
||||
EvalGrid::try_new(GridSpec::try_new(1000, 500, grid_len).unwrap()).unwrap(),
|
||||
));
|
||||
let chunks = (0..input.len())
|
||||
.step_by(61)
|
||||
.map(|offset| Samples::from_float(input.slice(offset, 61.min(input.len() - offset))))
|
||||
.collect();
|
||||
let series = series(
|
||||
&schema,
|
||||
5,
|
||||
vec![
|
||||
Samples::from_float(input.clone()),
|
||||
Samples::try_from_chunks(chunks).unwrap(),
|
||||
],
|
||||
);
|
||||
// Round shares its calculation with the UDF; the Prometheus fixtures check it instead.
|
||||
let cases = [MathOp::Abs, MathOp::Ceil, MathOp::Floor, MathOp::Sqrt];
|
||||
for op in cases {
|
||||
let output = map_math(&schema, &series, op).unwrap();
|
||||
let expected = ordinary_math(op, input.clone()).iter().collect::<Vec<_>>();
|
||||
for field in output.fields.iter() {
|
||||
assert_floats(field, &expected);
|
||||
}
|
||||
assert_eq!(output.metric.name(), None);
|
||||
assert_eq!(output.metric.tags(), &series.metric.tags()[1..]);
|
||||
let TimeAxis::Grid(range) = output.time else {
|
||||
panic!("lost grid axis")
|
||||
};
|
||||
assert_eq!((range.start(), range.len()), (5, input.len()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_drops_reserved_metadata_and_empty_labels_with_the_name() {
|
||||
let schema = schema(&["value"]);
|
||||
let mut input = series(&schema, 0, vec![floats(&[Some(-1.0)])]);
|
||||
for label in ["__type__", "__unit__", "__other__"] {
|
||||
input.metric.set(label, "x".into()).unwrap();
|
||||
}
|
||||
let output = map_math(&schema, &input, MathOp::Abs).unwrap();
|
||||
let names = output
|
||||
.metric
|
||||
.tags()
|
||||
.iter()
|
||||
.map(|label| label.name.as_ref())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(output.metric.name(), None);
|
||||
assert_eq!(names, ["__other__", "host"]);
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Golden cases evaluated by the Prometheus engine; see the header of `prometheus_cases.txt`.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use datatypes::arrow::array::Float64Array;
|
||||
use datatypes::series::samples::{
|
||||
FieldSpec, SampleRef, Samples, Series, SeriesFields, SeriesSchema,
|
||||
};
|
||||
use datatypes::series::time::{EvalGrid, GridRange, GridSpec, TimeAxis};
|
||||
use datatypes::series::{Label, MetricName};
|
||||
|
||||
use crate::map::{MathOp, map_math};
|
||||
|
||||
const CASES: &str = include_str!("../prometheus_cases.txt");
|
||||
|
||||
type Points = Vec<(usize, f64)>;
|
||||
|
||||
pub(super) struct Case {
|
||||
pub(super) header: Vec<String>,
|
||||
pub(super) expr: String,
|
||||
pub(super) schema: SeriesSchema,
|
||||
pub(super) series: Vec<Series>,
|
||||
pub(super) scalar: Option<Series>,
|
||||
pub(super) result: Vec<(MetricName, Points)>,
|
||||
}
|
||||
|
||||
pub(super) fn cases() -> Vec<Case> {
|
||||
let mut cases = Vec::new();
|
||||
let mut lines = CASES.lines().filter(|line| !line.starts_with('#'));
|
||||
while let Some(line) = lines.next() {
|
||||
let header = line.strip_prefix("case ").expect("case line");
|
||||
let expr = lines
|
||||
.next()
|
||||
.unwrap()
|
||||
.strip_prefix("expr ")
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let grid: Vec<i64> = lines.next().unwrap()["grid ".len()..]
|
||||
.split(' ')
|
||||
.map(|v| v.parse().unwrap())
|
||||
.collect();
|
||||
let schema = SeriesSchema {
|
||||
fields: vec![FieldSpec {
|
||||
name: "value".into(),
|
||||
}]
|
||||
.into(),
|
||||
eval_grid: Some(Arc::new(
|
||||
EvalGrid::try_new(GridSpec::try_new(grid[0], grid[1], grid[2] as u64).unwrap())
|
||||
.unwrap(),
|
||||
)),
|
||||
};
|
||||
let mut case = Case {
|
||||
header: header.split(' ').map(String::from).collect(),
|
||||
expr,
|
||||
series: Vec::new(),
|
||||
scalar: None,
|
||||
result: Vec::new(),
|
||||
schema,
|
||||
};
|
||||
for line in lines.by_ref() {
|
||||
if line == "end" {
|
||||
break;
|
||||
}
|
||||
let (kind, rest) = line.split_once(' ').unwrap();
|
||||
let (labels, points) = rest.split_once(" ;").unwrap();
|
||||
let (metric, points) = (parse_metric(labels), parse_points(points));
|
||||
match kind {
|
||||
"series" => {
|
||||
if let Some(series) = grid_series(&case.schema, metric, &points) {
|
||||
case.series.push(series);
|
||||
}
|
||||
}
|
||||
"scalar" => case.scalar = grid_series(&case.schema, metric, &points),
|
||||
"result" => case.result.push((metric, points)),
|
||||
_ => panic!("unknown line {line}"),
|
||||
}
|
||||
}
|
||||
cases.push(case);
|
||||
}
|
||||
cases
|
||||
}
|
||||
|
||||
fn parse_metric(labels: &str) -> MetricName {
|
||||
let mut name = None;
|
||||
let mut tags = Vec::new();
|
||||
for label in labels
|
||||
.split(',')
|
||||
.filter(|label| *label != "-" && !label.is_empty())
|
||||
{
|
||||
let (key, value) = label.split_once('=').unwrap();
|
||||
if key == "__name__" {
|
||||
name = Some(Arc::from(value));
|
||||
} else {
|
||||
tags.push(Label {
|
||||
name: key.into(),
|
||||
value: value.into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
MetricName::try_new(name, tags).unwrap()
|
||||
}
|
||||
|
||||
fn parse_points(points: &str) -> Points {
|
||||
points
|
||||
.split_whitespace()
|
||||
.map(|point| {
|
||||
let (index, bits) = point.split_once(':').unwrap();
|
||||
(
|
||||
index.parse().unwrap(),
|
||||
f64::from_bits(u64::from_str_radix(bits, 16).unwrap()),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(super) fn hex(bits: &str) -> f64 {
|
||||
f64::from_bits(u64::from_str_radix(bits, 16).unwrap())
|
||||
}
|
||||
|
||||
/// Builds the trimmed series Eval produces: from the first to the last point, nulls between.
|
||||
fn grid_series(schema: &SeriesSchema, metric: MetricName, points: &Points) -> Option<Series> {
|
||||
let (&(start, _), &(last, _)) = points.first().zip(points.last())?;
|
||||
let mut values = vec![None; last - start + 1];
|
||||
for &(index, value) in points {
|
||||
values[index - start] = Some(value);
|
||||
}
|
||||
let range = GridRange::try_new(schema.eval_grid.clone().unwrap(), start, values.len()).unwrap();
|
||||
Some(
|
||||
Series::try_new(
|
||||
schema,
|
||||
metric,
|
||||
TimeAxis::Grid(range),
|
||||
SeriesFields::one(Samples::from_float(Float64Array::from(values))),
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Compares labels, the trimmed absolute range and value bits; any NaN matches any NaN.
|
||||
pub(super) fn assert_result(case: &Case, mut actual: Vec<Series>) {
|
||||
actual.sort_by_key(|series| label_key(&series.metric));
|
||||
let mut expected = case.result.iter().collect::<Vec<_>>();
|
||||
expected.sort_by_key(|(metric, _)| label_key(metric));
|
||||
let context = || format!("{} ({})", case.expr, case.header.join(" "));
|
||||
assert_eq!(actual.len(), expected.len(), "{}", context());
|
||||
for (actual, (metric, points)) in actual.iter().zip(expected) {
|
||||
assert_eq!(&actual.metric, metric, "{}", context());
|
||||
let TimeAxis::Grid(range) = &actual.time else {
|
||||
panic!("{}: lost grid axis", context());
|
||||
};
|
||||
let mut present = Vec::new();
|
||||
for field in actual.fields.iter() {
|
||||
for (offset, sample) in field.iter().enumerate() {
|
||||
match sample {
|
||||
SampleRef::Float(value) => present.push((range.start() + offset, value)),
|
||||
SampleRef::Missing => {}
|
||||
_ => panic!("{}: non-float output", context()),
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
(range.start(), range.start() + range.len()),
|
||||
(points[0].0, points.last().unwrap().0 + 1),
|
||||
"{}: untrimmed range",
|
||||
context()
|
||||
);
|
||||
assert_eq!(present.len(), points.len(), "{}", context());
|
||||
for (&(index, actual), &(expected_index, expected)) in present.iter().zip(points) {
|
||||
assert_eq!(index, expected_index, "{}", context());
|
||||
assert!(
|
||||
actual.to_bits() == expected.to_bits() || (actual.is_nan() && expected.is_nan()),
|
||||
"{} at {index}: {actual:e} ({:016x}) != {expected:e} ({:016x})",
|
||||
context(),
|
||||
actual.to_bits(),
|
||||
expected.to_bits(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn label_key(metric: &MetricName) -> Vec<(String, String)> {
|
||||
let mut key = metric
|
||||
.tags()
|
||||
.iter()
|
||||
.map(|label| (label.name.to_string(), label.value.to_string()))
|
||||
.collect::<Vec<_>>();
|
||||
if let Some(name) = metric.name() {
|
||||
key.insert(0, ("__name__".into(), name.into()));
|
||||
}
|
||||
key
|
||||
}
|
||||
|
||||
fn math_op(header: &[String]) -> Option<MathOp> {
|
||||
Some(match header[1].as_str() {
|
||||
"abs" => MathOp::Abs,
|
||||
"ceil" => MathOp::Ceil,
|
||||
"floor" => MathOp::Floor,
|
||||
"sqrt" => MathOp::Sqrt,
|
||||
"round" => MathOp::Round {
|
||||
nearest: hex(&header[2]),
|
||||
},
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_matches_prometheus() {
|
||||
let mut matched = 0;
|
||||
for case in cases().iter().filter(|case| case.header[0] == "math") {
|
||||
let op = math_op(&case.header).unwrap_or_else(|| panic!("unmapped {}", case.expr));
|
||||
let output = case
|
||||
.series
|
||||
.iter()
|
||||
.map(|series| map_math(&case.schema, series, op).unwrap())
|
||||
.collect();
|
||||
assert_result(case, output);
|
||||
matched += 1;
|
||||
}
|
||||
assert!(matched >= 48, "only {matched} math cases");
|
||||
}
|
||||
Reference in New Issue
Block a user