mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
526dd93b32
* app/vmselect: `quantile` func compatiblity with Prometheus The `quantile` func was previously calculated by https://github.com/valyala/histogram package. The result of such calculation was always the closest real value to requested quantile. While in Prometheus implementation interpolation is used. Such difference may result into discrepancy in output between Prometheus and VictoriaMetrics. This commit adds a Prometheus-like `quantile` function. It also used by other functions which depend on it, such as `quantiles`, `quantile_over_time`, `median` etc. https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1625 Signed-off-by: hagen1778 <roman@victoriametrics.com> * app/vmselect: `quantile` review fixes * quantile functions were split into multiple to provide different API for already sorted data; * float64sPool is used for reducing allocations. Items in pool may have different sizes, but defining a new pool was complicates due to name collisions; Signed-off-by: hagen1778 <roman@victoriametrics.com>
2174 lines
52 KiB
Go
2174 lines
52 KiB
Go
package promql
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"math/rand"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
|
"github.com/VictoriaMetrics/metricsql"
|
|
)
|
|
|
|
var transformFuncsKeepMetricGroup = map[string]bool{
|
|
"ceil": true,
|
|
"clamp": true,
|
|
"clamp_max": true,
|
|
"clamp_min": true,
|
|
"floor": true,
|
|
"round": true,
|
|
"keep_last_value": true,
|
|
"keep_next_value": true,
|
|
"interpolate": true,
|
|
"running_min": true,
|
|
"running_max": true,
|
|
"running_avg": true,
|
|
"range_min": true,
|
|
"range_max": true,
|
|
"range_avg": true,
|
|
"range_first": true,
|
|
"range_last": true,
|
|
"range_quantile": true,
|
|
"smooth_exponential": true,
|
|
}
|
|
|
|
var transformFuncs = map[string]transformFunc{
|
|
// Standard promql funcs
|
|
// See funcs accepting instant-vector on https://prometheus.io/docs/prometheus/latest/querying/functions/ .
|
|
"abs": newTransformFuncOneArg(transformAbs),
|
|
"absent": transformAbsent,
|
|
"ceil": newTransformFuncOneArg(transformCeil),
|
|
"clamp": transformClamp,
|
|
"clamp_max": transformClampMax,
|
|
"clamp_min": transformClampMin,
|
|
"day_of_month": newTransformFuncDateTime(transformDayOfMonth),
|
|
"day_of_week": newTransformFuncDateTime(transformDayOfWeek),
|
|
"days_in_month": newTransformFuncDateTime(transformDaysInMonth),
|
|
"exp": newTransformFuncOneArg(transformExp),
|
|
"floor": newTransformFuncOneArg(transformFloor),
|
|
"histogram_quantile": transformHistogramQuantile,
|
|
"hour": newTransformFuncDateTime(transformHour),
|
|
"label_join": transformLabelJoin,
|
|
"label_replace": transformLabelReplace,
|
|
"ln": newTransformFuncOneArg(transformLn),
|
|
"log2": newTransformFuncOneArg(transformLog2),
|
|
"log10": newTransformFuncOneArg(transformLog10),
|
|
"minute": newTransformFuncDateTime(transformMinute),
|
|
"month": newTransformFuncDateTime(transformMonth),
|
|
"round": transformRound,
|
|
"sgn": transformSign,
|
|
"scalar": transformScalar,
|
|
"sort": newTransformFuncSort(false),
|
|
"sort_desc": newTransformFuncSort(true),
|
|
"sqrt": newTransformFuncOneArg(transformSqrt),
|
|
"time": transformTime,
|
|
// "timestamp" has been moved to rollup funcs. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/415
|
|
"vector": transformVector,
|
|
"year": newTransformFuncDateTime(transformYear),
|
|
|
|
// New funcs
|
|
"label_set": transformLabelSet,
|
|
"label_map": transformLabelMap,
|
|
"label_uppercase": transformLabelUppercase,
|
|
"label_lowercase": transformLabelLowercase,
|
|
"label_del": transformLabelDel,
|
|
"label_keep": transformLabelKeep,
|
|
"label_copy": transformLabelCopy,
|
|
"label_move": transformLabelMove,
|
|
"label_transform": transformLabelTransform,
|
|
"label_value": transformLabelValue,
|
|
"label_match": transformLabelMatch,
|
|
"label_mismatch": transformLabelMismatch,
|
|
"union": transformUnion,
|
|
"": transformUnion, // empty func is a synonym to union
|
|
"keep_last_value": transformKeepLastValue,
|
|
"keep_next_value": transformKeepNextValue,
|
|
"interpolate": transformInterpolate,
|
|
"start": newTransformFuncZeroArgs(transformStart),
|
|
"end": newTransformFuncZeroArgs(transformEnd),
|
|
"step": newTransformFuncZeroArgs(transformStep),
|
|
"running_sum": newTransformFuncRunning(runningSum),
|
|
"running_max": newTransformFuncRunning(runningMax),
|
|
"running_min": newTransformFuncRunning(runningMin),
|
|
"running_avg": newTransformFuncRunning(runningAvg),
|
|
"range_sum": newTransformFuncRange(runningSum),
|
|
"range_max": newTransformFuncRange(runningMax),
|
|
"range_min": newTransformFuncRange(runningMin),
|
|
"range_avg": newTransformFuncRange(runningAvg),
|
|
"range_first": transformRangeFirst,
|
|
"range_last": transformRangeLast,
|
|
"range_quantile": transformRangeQuantile,
|
|
"smooth_exponential": transformSmoothExponential,
|
|
"remove_resets": transformRemoveResets,
|
|
"rand": newTransformRand(newRandFloat64),
|
|
"rand_normal": newTransformRand(newRandNormFloat64),
|
|
"rand_exponential": newTransformRand(newRandExpFloat64),
|
|
"pi": transformPi,
|
|
"sin": newTransformFuncOneArg(transformSin),
|
|
"cos": newTransformFuncOneArg(transformCos),
|
|
"asin": newTransformFuncOneArg(transformAsin),
|
|
"acos": newTransformFuncOneArg(transformAcos),
|
|
"prometheus_buckets": transformPrometheusBuckets,
|
|
"buckets_limit": transformBucketsLimit,
|
|
"histogram_share": transformHistogramShare,
|
|
"histogram_avg": transformHistogramAvg,
|
|
"histogram_stdvar": transformHistogramStdvar,
|
|
"histogram_stddev": transformHistogramStddev,
|
|
"sort_by_label": newTransformFuncSortByLabel(false),
|
|
"sort_by_label_desc": newTransformFuncSortByLabel(true),
|
|
"timezone_offset": transformTimezoneOffset,
|
|
"bitmap_and": newTransformBitmap(bitmapAnd),
|
|
"bitmap_or": newTransformBitmap(bitmapOr),
|
|
"bitmap_xor": newTransformBitmap(bitmapXor),
|
|
"histogram_quantiles": transformHistogramQuantiles,
|
|
}
|
|
|
|
func getTransformFunc(s string) transformFunc {
|
|
s = strings.ToLower(s)
|
|
return transformFuncs[s]
|
|
}
|
|
|
|
type transformFuncArg struct {
|
|
ec *EvalConfig
|
|
fe *metricsql.FuncExpr
|
|
args [][]*timeseries
|
|
}
|
|
|
|
type transformFunc func(tfa *transformFuncArg) ([]*timeseries, error)
|
|
|
|
func newTransformFuncOneArg(tf func(v float64) float64) transformFunc {
|
|
tfe := func(values []float64) {
|
|
for i, v := range values {
|
|
values[i] = tf(v)
|
|
}
|
|
}
|
|
return func(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 1); err != nil {
|
|
return nil, err
|
|
}
|
|
return doTransformValues(args[0], tfe, tfa.fe)
|
|
}
|
|
}
|
|
|
|
func doTransformValues(arg []*timeseries, tf func(values []float64), fe *metricsql.FuncExpr) ([]*timeseries, error) {
|
|
name := strings.ToLower(fe.Name)
|
|
keepMetricGroup := transformFuncsKeepMetricGroup[name]
|
|
for _, ts := range arg {
|
|
if !keepMetricGroup {
|
|
ts.MetricName.ResetMetricGroup()
|
|
}
|
|
tf(ts.Values)
|
|
}
|
|
return arg, nil
|
|
}
|
|
|
|
func transformAbs(v float64) float64 {
|
|
return math.Abs(v)
|
|
}
|
|
|
|
func transformAbsent(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
|
|
if err := expectTransformArgsNum(args, 1); err != nil {
|
|
return nil, err
|
|
}
|
|
tss := args[0]
|
|
rvs := getAbsentTimeseries(tfa.ec, tfa.fe.Args[0])
|
|
if len(tss) == 0 {
|
|
return rvs, nil
|
|
}
|
|
for i := range tss[0].Values {
|
|
isAbsent := true
|
|
for _, ts := range tss {
|
|
if !math.IsNaN(ts.Values[i]) {
|
|
isAbsent = false
|
|
break
|
|
}
|
|
}
|
|
if !isAbsent {
|
|
rvs[0].Values[i] = nan
|
|
}
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func getAbsentTimeseries(ec *EvalConfig, arg metricsql.Expr) []*timeseries {
|
|
// Copy tags from arg
|
|
rvs := evalNumber(ec, 1)
|
|
rv := rvs[0]
|
|
me, ok := arg.(*metricsql.MetricExpr)
|
|
if !ok {
|
|
return rvs
|
|
}
|
|
tfs := toTagFilters(me.LabelFilters)
|
|
for i := range tfs {
|
|
tf := &tfs[i]
|
|
if len(tf.Key) == 0 {
|
|
continue
|
|
}
|
|
if tf.IsRegexp || tf.IsNegative {
|
|
continue
|
|
}
|
|
rv.MetricName.AddTagBytes(tf.Key, tf.Value)
|
|
}
|
|
return rvs
|
|
}
|
|
|
|
func transformCeil(v float64) float64 {
|
|
return math.Ceil(v)
|
|
}
|
|
|
|
func transformClamp(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 3); err != nil {
|
|
return nil, err
|
|
}
|
|
mins, err := getScalar(args[1], 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
maxs, err := getScalar(args[2], 2)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tf := func(values []float64) {
|
|
for i, v := range values {
|
|
if v > maxs[i] {
|
|
values[i] = maxs[i]
|
|
} else if v < mins[i] {
|
|
values[i] = mins[i]
|
|
}
|
|
}
|
|
}
|
|
return doTransformValues(args[0], tf, tfa.fe)
|
|
}
|
|
|
|
func transformClampMax(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 2); err != nil {
|
|
return nil, err
|
|
}
|
|
maxs, err := getScalar(args[1], 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tf := func(values []float64) {
|
|
for i, v := range values {
|
|
if v > maxs[i] {
|
|
values[i] = maxs[i]
|
|
}
|
|
}
|
|
}
|
|
return doTransformValues(args[0], tf, tfa.fe)
|
|
}
|
|
|
|
func transformClampMin(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 2); err != nil {
|
|
return nil, err
|
|
}
|
|
mins, err := getScalar(args[1], 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tf := func(values []float64) {
|
|
for i, v := range values {
|
|
if v < mins[i] {
|
|
values[i] = mins[i]
|
|
}
|
|
}
|
|
}
|
|
return doTransformValues(args[0], tf, tfa.fe)
|
|
}
|
|
|
|
func newTransformFuncDateTime(f func(t time.Time) int) transformFunc {
|
|
return func(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if len(args) > 1 {
|
|
return nil, fmt.Errorf(`too many args; got %d; want up to %d`, len(args), 1)
|
|
}
|
|
var arg []*timeseries
|
|
if len(args) == 0 {
|
|
arg = evalTime(tfa.ec)
|
|
} else {
|
|
arg = args[0]
|
|
}
|
|
tf := func(values []float64) {
|
|
for i, v := range values {
|
|
if math.IsNaN(v) {
|
|
continue
|
|
}
|
|
t := time.Unix(int64(v), 0).UTC()
|
|
values[i] = float64(f(t))
|
|
}
|
|
}
|
|
return doTransformValues(arg, tf, tfa.fe)
|
|
}
|
|
}
|
|
|
|
func transformDayOfMonth(t time.Time) int {
|
|
return t.Day()
|
|
}
|
|
|
|
func transformDayOfWeek(t time.Time) int {
|
|
return int(t.Weekday())
|
|
}
|
|
|
|
func transformDaysInMonth(t time.Time) int {
|
|
m := t.Month()
|
|
if m == 2 && isLeapYear(uint32(t.Year())) {
|
|
return 29
|
|
}
|
|
return daysInMonth[m]
|
|
}
|
|
|
|
func transformExp(v float64) float64 {
|
|
return math.Exp(v)
|
|
}
|
|
|
|
func transformFloor(v float64) float64 {
|
|
return math.Floor(v)
|
|
}
|
|
|
|
func transformBucketsLimit(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 2); err != nil {
|
|
return nil, err
|
|
}
|
|
limits, err := getScalar(args[0], 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
limit := int(limits[0])
|
|
if limit <= 0 {
|
|
return nil, nil
|
|
}
|
|
if limit < 3 {
|
|
// Preserve the first and the last bucket for better accuracy for min and max values.
|
|
limit = 3
|
|
}
|
|
tss := vmrangeBucketsToLE(args[1])
|
|
if len(tss) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
// Group timeseries by all MetricGroup+tags excluding `le` tag.
|
|
type x struct {
|
|
le float64
|
|
hits float64
|
|
ts *timeseries
|
|
}
|
|
m := make(map[string][]x)
|
|
var b []byte
|
|
var mn storage.MetricName
|
|
for _, ts := range tss {
|
|
leStr := ts.MetricName.GetTagValue("le")
|
|
if len(leStr) == 0 {
|
|
// Skip time series without `le` tag.
|
|
continue
|
|
}
|
|
le, err := strconv.ParseFloat(string(leStr), 64)
|
|
if err != nil {
|
|
// Skip time series with invalid `le` tag.
|
|
continue
|
|
}
|
|
mn.CopyFrom(&ts.MetricName)
|
|
mn.RemoveTag("le")
|
|
b = marshalMetricNameSorted(b[:0], &mn)
|
|
m[string(b)] = append(m[string(b)], x{
|
|
le: le,
|
|
ts: ts,
|
|
})
|
|
}
|
|
|
|
// Remove buckets with the smallest counters.
|
|
rvs := make([]*timeseries, 0, len(tss))
|
|
for _, leGroup := range m {
|
|
if len(leGroup) <= limit {
|
|
// Fast path - the number of buckets doesn't exceed the given limit.
|
|
// Keep all the buckets as is.
|
|
for _, xx := range leGroup {
|
|
rvs = append(rvs, xx.ts)
|
|
}
|
|
continue
|
|
}
|
|
// Slow path - remove buckets with the smallest number of hits until their count reaches the limit.
|
|
|
|
// Calculate per-bucket hits.
|
|
sort.Slice(leGroup, func(i, j int) bool {
|
|
return leGroup[i].le < leGroup[j].le
|
|
})
|
|
for n := range limits {
|
|
prevValue := float64(0)
|
|
for i := range leGroup {
|
|
xx := &leGroup[i]
|
|
value := xx.ts.Values[n]
|
|
xx.hits += value - prevValue
|
|
prevValue = value
|
|
}
|
|
}
|
|
for len(leGroup) > limit {
|
|
// Preserve the first and the last bucket for better accuracy for min and max values
|
|
xxMinIdx := 1
|
|
minMergeHits := leGroup[1].hits + leGroup[2].hits
|
|
for i := range leGroup[1 : len(leGroup)-2] {
|
|
mergeHits := leGroup[i+1].hits + leGroup[i+2].hits
|
|
if mergeHits < minMergeHits {
|
|
xxMinIdx = i + 1
|
|
minMergeHits = mergeHits
|
|
}
|
|
}
|
|
leGroup[xxMinIdx+1].hits += leGroup[xxMinIdx].hits
|
|
leGroup = append(leGroup[:xxMinIdx], leGroup[xxMinIdx+1:]...)
|
|
}
|
|
for _, xx := range leGroup {
|
|
rvs = append(rvs, xx.ts)
|
|
}
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformPrometheusBuckets(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 1); err != nil {
|
|
return nil, err
|
|
}
|
|
rvs := vmrangeBucketsToLE(args[0])
|
|
return rvs, nil
|
|
}
|
|
|
|
func vmrangeBucketsToLE(tss []*timeseries) []*timeseries {
|
|
rvs := make([]*timeseries, 0, len(tss))
|
|
|
|
// Group timeseries by MetricGroup+tags excluding `vmrange` tag.
|
|
type x struct {
|
|
startStr string
|
|
endStr string
|
|
start float64
|
|
end float64
|
|
ts *timeseries
|
|
}
|
|
m := make(map[string][]x)
|
|
bb := bbPool.Get()
|
|
defer bbPool.Put(bb)
|
|
for _, ts := range tss {
|
|
vmrange := ts.MetricName.GetTagValue("vmrange")
|
|
if len(vmrange) == 0 {
|
|
if le := ts.MetricName.GetTagValue("le"); len(le) > 0 {
|
|
// Keep Prometheus-compatible buckets.
|
|
rvs = append(rvs, ts)
|
|
}
|
|
continue
|
|
}
|
|
n := strings.Index(bytesutil.ToUnsafeString(vmrange), "...")
|
|
if n < 0 {
|
|
continue
|
|
}
|
|
startStr := string(vmrange[:n])
|
|
start, err := strconv.ParseFloat(startStr, 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
endStr := string(vmrange[n+len("..."):])
|
|
end, err := strconv.ParseFloat(endStr, 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
ts.MetricName.RemoveTag("le")
|
|
ts.MetricName.RemoveTag("vmrange")
|
|
bb.B = marshalMetricNameSorted(bb.B[:0], &ts.MetricName)
|
|
m[string(bb.B)] = append(m[string(bb.B)], x{
|
|
startStr: startStr,
|
|
endStr: endStr,
|
|
start: start,
|
|
end: end,
|
|
ts: ts,
|
|
})
|
|
}
|
|
|
|
// Convert `vmrange` label in each group of time series to `le` label.
|
|
copyTS := func(src *timeseries, leStr string) *timeseries {
|
|
var ts timeseries
|
|
ts.CopyFromShallowTimestamps(src)
|
|
values := ts.Values
|
|
for i := range values {
|
|
values[i] = 0
|
|
}
|
|
ts.MetricName.RemoveTag("le")
|
|
ts.MetricName.AddTag("le", leStr)
|
|
return &ts
|
|
}
|
|
isZeroTS := func(ts *timeseries) bool {
|
|
for _, v := range ts.Values {
|
|
if v > 0 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
for _, xss := range m {
|
|
sort.Slice(xss, func(i, j int) bool { return xss[i].end < xss[j].end })
|
|
xssNew := make([]x, 0, len(xss)+2)
|
|
var xsPrev x
|
|
uniqTs := make(map[string]*timeseries, len(xss))
|
|
for _, xs := range xss {
|
|
ts := xs.ts
|
|
if isZeroTS(ts) {
|
|
// Skip time series with zeros. They are substituted by xssNew below.
|
|
xsPrev = xs
|
|
continue
|
|
}
|
|
if xs.start != xsPrev.end && uniqTs[xs.startStr] == nil {
|
|
uniqTs[xs.startStr] = xs.ts
|
|
xssNew = append(xssNew, x{
|
|
endStr: xs.startStr,
|
|
end: xs.start,
|
|
ts: copyTS(ts, xs.startStr),
|
|
})
|
|
}
|
|
ts.MetricName.AddTag("le", xs.endStr)
|
|
prevTs := uniqTs[xs.endStr]
|
|
if prevTs != nil {
|
|
// the end of the current bucket is not unique, need to merge it with the existing bucket.
|
|
mergeNonOverlappingTimeseries(prevTs, xs.ts)
|
|
} else {
|
|
xssNew = append(xssNew, xs)
|
|
uniqTs[xs.endStr] = xs.ts
|
|
}
|
|
xsPrev = xs
|
|
}
|
|
if !math.IsInf(xsPrev.end, 1) {
|
|
xssNew = append(xssNew, x{
|
|
endStr: "+Inf",
|
|
end: math.Inf(1),
|
|
ts: copyTS(xsPrev.ts, "+Inf"),
|
|
})
|
|
}
|
|
xss = xssNew
|
|
for i := range xss[0].ts.Values {
|
|
count := float64(0)
|
|
for _, xs := range xss {
|
|
ts := xs.ts
|
|
v := ts.Values[i]
|
|
if !math.IsNaN(v) && v > 0 {
|
|
count += v
|
|
}
|
|
ts.Values[i] = count
|
|
}
|
|
}
|
|
for _, xs := range xss {
|
|
rvs = append(rvs, xs.ts)
|
|
}
|
|
}
|
|
return rvs
|
|
}
|
|
|
|
func transformHistogramShare(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if len(args) < 2 || len(args) > 3 {
|
|
return nil, fmt.Errorf("unexpected number of args; got %d; want 2...3", len(args))
|
|
}
|
|
les, err := getScalar(args[0], 0)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot parse le: %w", err)
|
|
}
|
|
|
|
// Convert buckets with `vmrange` labels to buckets with `le` labels.
|
|
tss := vmrangeBucketsToLE(args[1])
|
|
|
|
// Parse boundsLabel. See https://github.com/prometheus/prometheus/issues/5706 for details.
|
|
var boundsLabel string
|
|
if len(args) > 2 {
|
|
s, err := getString(args[2], 2)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot parse boundsLabel (arg #3): %w", err)
|
|
}
|
|
boundsLabel = s
|
|
}
|
|
|
|
// Group metrics by all tags excluding "le"
|
|
m := groupLeTimeseries(tss)
|
|
|
|
// Calculate share for les
|
|
share := func(i int, les []float64, xss []leTimeseries) (q, lower, upper float64) {
|
|
leReq := les[i]
|
|
if math.IsNaN(leReq) || len(xss) == 0 {
|
|
return nan, nan, nan
|
|
}
|
|
fixBrokenBuckets(i, xss)
|
|
if leReq < 0 {
|
|
return 0, 0, 0
|
|
}
|
|
if math.IsInf(leReq, 1) {
|
|
return 1, 1, 1
|
|
}
|
|
var vPrev, lePrev float64
|
|
for _, xs := range xss {
|
|
v := xs.ts.Values[i]
|
|
le := xs.le
|
|
if leReq >= le {
|
|
vPrev = v
|
|
lePrev = le
|
|
continue
|
|
}
|
|
// precondition: lePrev <= leReq < le
|
|
vLast := xss[len(xss)-1].ts.Values[i]
|
|
lower = vPrev / vLast
|
|
if math.IsInf(le, 1) {
|
|
return lower, lower, 1
|
|
}
|
|
if lePrev == leReq {
|
|
return lower, lower, lower
|
|
}
|
|
upper = v / vLast
|
|
q = lower + (v-vPrev)/vLast*(leReq-lePrev)/(le-lePrev)
|
|
return q, lower, upper
|
|
}
|
|
// precondition: leReq > leLast
|
|
return 1, 1, 1
|
|
}
|
|
rvs := make([]*timeseries, 0, len(m))
|
|
for _, xss := range m {
|
|
sort.Slice(xss, func(i, j int) bool {
|
|
return xss[i].le < xss[j].le
|
|
})
|
|
dst := xss[0].ts
|
|
var tsLower, tsUpper *timeseries
|
|
if len(boundsLabel) > 0 {
|
|
tsLower = ×eries{}
|
|
tsLower.CopyFromShallowTimestamps(dst)
|
|
tsLower.MetricName.RemoveTag(boundsLabel)
|
|
tsLower.MetricName.AddTag(boundsLabel, "lower")
|
|
tsUpper = ×eries{}
|
|
tsUpper.CopyFromShallowTimestamps(dst)
|
|
tsUpper.MetricName.RemoveTag(boundsLabel)
|
|
tsUpper.MetricName.AddTag(boundsLabel, "upper")
|
|
}
|
|
for i := range dst.Values {
|
|
q, lower, upper := share(i, les, xss)
|
|
dst.Values[i] = q
|
|
if len(boundsLabel) > 0 {
|
|
tsLower.Values[i] = lower
|
|
tsUpper.Values[i] = upper
|
|
}
|
|
}
|
|
rvs = append(rvs, dst)
|
|
if len(boundsLabel) > 0 {
|
|
rvs = append(rvs, tsLower)
|
|
rvs = append(rvs, tsUpper)
|
|
}
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformHistogramAvg(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 1); err != nil {
|
|
return nil, err
|
|
}
|
|
tss := vmrangeBucketsToLE(args[0])
|
|
m := groupLeTimeseries(tss)
|
|
rvs := make([]*timeseries, 0, len(m))
|
|
for _, xss := range m {
|
|
sort.Slice(xss, func(i, j int) bool {
|
|
return xss[i].le < xss[j].le
|
|
})
|
|
dst := xss[0].ts
|
|
for i := range dst.Values {
|
|
dst.Values[i] = avgForLeTimeseries(i, xss)
|
|
}
|
|
rvs = append(rvs, dst)
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformHistogramStddev(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 1); err != nil {
|
|
return nil, err
|
|
}
|
|
tss := vmrangeBucketsToLE(args[0])
|
|
m := groupLeTimeseries(tss)
|
|
rvs := make([]*timeseries, 0, len(m))
|
|
for _, xss := range m {
|
|
sort.Slice(xss, func(i, j int) bool {
|
|
return xss[i].le < xss[j].le
|
|
})
|
|
dst := xss[0].ts
|
|
for i := range dst.Values {
|
|
v := stdvarForLeTimeseries(i, xss)
|
|
dst.Values[i] = math.Sqrt(v)
|
|
}
|
|
rvs = append(rvs, dst)
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformHistogramStdvar(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 1); err != nil {
|
|
return nil, err
|
|
}
|
|
tss := vmrangeBucketsToLE(args[0])
|
|
m := groupLeTimeseries(tss)
|
|
rvs := make([]*timeseries, 0, len(m))
|
|
for _, xss := range m {
|
|
sort.Slice(xss, func(i, j int) bool {
|
|
return xss[i].le < xss[j].le
|
|
})
|
|
dst := xss[0].ts
|
|
for i := range dst.Values {
|
|
dst.Values[i] = stdvarForLeTimeseries(i, xss)
|
|
}
|
|
rvs = append(rvs, dst)
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func avgForLeTimeseries(i int, xss []leTimeseries) float64 {
|
|
lePrev := float64(0)
|
|
vPrev := float64(0)
|
|
sum := float64(0)
|
|
weightTotal := float64(0)
|
|
for _, xs := range xss {
|
|
if math.IsInf(xs.le, 0) {
|
|
continue
|
|
}
|
|
le := xs.le
|
|
n := (le + lePrev) / 2
|
|
v := xs.ts.Values[i]
|
|
weight := v - vPrev
|
|
sum += n * weight
|
|
weightTotal += weight
|
|
lePrev = le
|
|
vPrev = v
|
|
}
|
|
if weightTotal == 0 {
|
|
return nan
|
|
}
|
|
return sum / weightTotal
|
|
}
|
|
|
|
func stdvarForLeTimeseries(i int, xss []leTimeseries) float64 {
|
|
lePrev := float64(0)
|
|
vPrev := float64(0)
|
|
sum := float64(0)
|
|
sum2 := float64(0)
|
|
weightTotal := float64(0)
|
|
for _, xs := range xss {
|
|
if math.IsInf(xs.le, 0) {
|
|
continue
|
|
}
|
|
le := xs.le
|
|
n := (le + lePrev) / 2
|
|
v := xs.ts.Values[i]
|
|
weight := v - vPrev
|
|
sum += n * weight
|
|
sum2 += n * n * weight
|
|
weightTotal += weight
|
|
lePrev = le
|
|
vPrev = v
|
|
}
|
|
if weightTotal == 0 {
|
|
return nan
|
|
}
|
|
avg := sum / weightTotal
|
|
avg2 := sum2 / weightTotal
|
|
stdvar := avg2 - avg*avg
|
|
if stdvar < 0 {
|
|
// Correct possible calculation error.
|
|
stdvar = 0
|
|
}
|
|
return stdvar
|
|
}
|
|
|
|
func transformHistogramQuantiles(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if len(args) < 3 {
|
|
return nil, fmt.Errorf("unexpected number of args: %d; expecting at least 3 args", len(args))
|
|
}
|
|
dstLabel, err := getString(args[0], 0)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot obtain dstLabel: %w", err)
|
|
}
|
|
phiArgs := args[1 : len(args)-1]
|
|
tssOrig := args[len(args)-1]
|
|
// Calculate quantile individually per each phi.
|
|
var rvs []*timeseries
|
|
for _, phiArg := range phiArgs {
|
|
phiStr := fmt.Sprintf("%g", phiArg[0].Values[0])
|
|
tss := copyTimeseries(tssOrig)
|
|
tfaTmp := &transformFuncArg{
|
|
ec: tfa.ec,
|
|
fe: tfa.fe,
|
|
args: [][]*timeseries{
|
|
phiArg,
|
|
tss,
|
|
},
|
|
}
|
|
tssTmp, err := transformHistogramQuantile(tfaTmp)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot calculate quantile %s: %w", phiStr, err)
|
|
}
|
|
for _, ts := range tssTmp {
|
|
ts.MetricName.RemoveTag(dstLabel)
|
|
ts.MetricName.AddTag(dstLabel, phiStr)
|
|
}
|
|
rvs = append(rvs, tssTmp...)
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformHistogramQuantile(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if len(args) < 2 || len(args) > 3 {
|
|
return nil, fmt.Errorf("unexpected number of args; got %d; want 2...3", len(args))
|
|
}
|
|
phis, err := getScalar(args[0], 0)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot parse phi: %w", err)
|
|
}
|
|
|
|
// Convert buckets with `vmrange` labels to buckets with `le` labels.
|
|
tss := vmrangeBucketsToLE(args[1])
|
|
|
|
// Parse boundsLabel. See https://github.com/prometheus/prometheus/issues/5706 for details.
|
|
var boundsLabel string
|
|
if len(args) > 2 {
|
|
s, err := getString(args[2], 2)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot parse boundsLabel (arg #3): %w", err)
|
|
}
|
|
boundsLabel = s
|
|
}
|
|
|
|
// Group metrics by all tags excluding "le"
|
|
m := groupLeTimeseries(tss)
|
|
|
|
// Calculate quantile for each group in m
|
|
lastNonInf := func(i int, xss []leTimeseries) float64 {
|
|
for len(xss) > 0 {
|
|
xsLast := xss[len(xss)-1]
|
|
if !math.IsInf(xsLast.le, 0) {
|
|
return xsLast.le
|
|
}
|
|
xss = xss[:len(xss)-1]
|
|
}
|
|
return nan
|
|
}
|
|
quantile := func(i int, phis []float64, xss []leTimeseries) (q, lower, upper float64) {
|
|
phi := phis[i]
|
|
if math.IsNaN(phi) {
|
|
return nan, nan, nan
|
|
}
|
|
fixBrokenBuckets(i, xss)
|
|
vLast := float64(0)
|
|
if len(xss) > 0 {
|
|
vLast = xss[len(xss)-1].ts.Values[i]
|
|
}
|
|
if vLast == 0 {
|
|
return nan, nan, nan
|
|
}
|
|
if phi < 0 {
|
|
return -inf, -inf, xss[0].ts.Values[i]
|
|
}
|
|
if phi > 1 {
|
|
return inf, vLast, inf
|
|
}
|
|
vReq := vLast * phi
|
|
vPrev := float64(0)
|
|
lePrev := float64(0)
|
|
for _, xs := range xss {
|
|
v := xs.ts.Values[i]
|
|
le := xs.le
|
|
if v <= 0 {
|
|
// Skip zero buckets.
|
|
lePrev = le
|
|
continue
|
|
}
|
|
if v < vReq {
|
|
vPrev = v
|
|
lePrev = le
|
|
continue
|
|
}
|
|
if math.IsInf(le, 0) {
|
|
break
|
|
}
|
|
if v == vPrev {
|
|
return lePrev, lePrev, v
|
|
}
|
|
vv := lePrev + (le-lePrev)*(vReq-vPrev)/(v-vPrev)
|
|
return vv, lePrev, le
|
|
}
|
|
vv := lastNonInf(i, xss)
|
|
return vv, vv, inf
|
|
}
|
|
rvs := make([]*timeseries, 0, len(m))
|
|
for _, xss := range m {
|
|
sort.Slice(xss, func(i, j int) bool {
|
|
return xss[i].le < xss[j].le
|
|
})
|
|
dst := xss[0].ts
|
|
var tsLower, tsUpper *timeseries
|
|
if len(boundsLabel) > 0 {
|
|
tsLower = ×eries{}
|
|
tsLower.CopyFromShallowTimestamps(dst)
|
|
tsLower.MetricName.RemoveTag(boundsLabel)
|
|
tsLower.MetricName.AddTag(boundsLabel, "lower")
|
|
tsUpper = ×eries{}
|
|
tsUpper.CopyFromShallowTimestamps(dst)
|
|
tsUpper.MetricName.RemoveTag(boundsLabel)
|
|
tsUpper.MetricName.AddTag(boundsLabel, "upper")
|
|
}
|
|
for i := range dst.Values {
|
|
v, lower, upper := quantile(i, phis, xss)
|
|
dst.Values[i] = v
|
|
if len(boundsLabel) > 0 {
|
|
tsLower.Values[i] = lower
|
|
tsUpper.Values[i] = upper
|
|
}
|
|
}
|
|
rvs = append(rvs, dst)
|
|
if len(boundsLabel) > 0 {
|
|
rvs = append(rvs, tsLower)
|
|
rvs = append(rvs, tsUpper)
|
|
}
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
type leTimeseries struct {
|
|
le float64
|
|
ts *timeseries
|
|
}
|
|
|
|
func groupLeTimeseries(tss []*timeseries) map[string][]leTimeseries {
|
|
m := make(map[string][]leTimeseries)
|
|
bb := bbPool.Get()
|
|
for _, ts := range tss {
|
|
tagValue := ts.MetricName.GetTagValue("le")
|
|
if len(tagValue) == 0 {
|
|
continue
|
|
}
|
|
le, err := strconv.ParseFloat(bytesutil.ToUnsafeString(tagValue), 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
ts.MetricName.ResetMetricGroup()
|
|
ts.MetricName.RemoveTag("le")
|
|
bb.B = marshalMetricTagsSorted(bb.B[:0], &ts.MetricName)
|
|
m[string(bb.B)] = append(m[string(bb.B)], leTimeseries{
|
|
le: le,
|
|
ts: ts,
|
|
})
|
|
}
|
|
bbPool.Put(bb)
|
|
return m
|
|
}
|
|
|
|
func fixBrokenBuckets(i int, xss []leTimeseries) {
|
|
// Fix broken buckets.
|
|
// They are already sorted by le, so their values must be in ascending order,
|
|
// since the next bucket includes all the previous buckets.
|
|
vPrev := float64(0)
|
|
for _, xs := range xss {
|
|
v := xs.ts.Values[i]
|
|
if v < vPrev || math.IsNaN(v) {
|
|
xs.ts.Values[i] = vPrev
|
|
} else {
|
|
vPrev = v
|
|
}
|
|
}
|
|
}
|
|
|
|
func transformHour(t time.Time) int {
|
|
return t.Hour()
|
|
}
|
|
|
|
func runningSum(a, b float64, idx int) float64 {
|
|
return a + b
|
|
}
|
|
|
|
func runningMax(a, b float64, idx int) float64 {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
func runningMin(a, b float64, idx int) float64 {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
func runningAvg(a, b float64, idx int) float64 {
|
|
// See `Rapid calculation methods` at https://en.wikipedia.org/wiki/Standard_deviation
|
|
return a + (b-a)/float64(idx+1)
|
|
}
|
|
|
|
func skipLeadingNaNs(values []float64) []float64 {
|
|
i := 0
|
|
for i < len(values) && math.IsNaN(values[i]) {
|
|
i++
|
|
}
|
|
return values[i:]
|
|
}
|
|
|
|
func skipTrailingNaNs(values []float64) []float64 {
|
|
i := len(values) - 1
|
|
for i >= 0 && math.IsNaN(values[i]) {
|
|
i--
|
|
}
|
|
return values[:i+1]
|
|
}
|
|
|
|
func transformKeepLastValue(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 1); err != nil {
|
|
return nil, err
|
|
}
|
|
rvs := args[0]
|
|
for _, ts := range rvs {
|
|
values := ts.Values
|
|
if len(values) == 0 {
|
|
continue
|
|
}
|
|
lastValue := values[0]
|
|
for i, v := range values {
|
|
if !math.IsNaN(v) {
|
|
lastValue = v
|
|
continue
|
|
}
|
|
values[i] = lastValue
|
|
}
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformKeepNextValue(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 1); err != nil {
|
|
return nil, err
|
|
}
|
|
rvs := args[0]
|
|
for _, ts := range rvs {
|
|
values := ts.Values
|
|
if len(values) == 0 {
|
|
continue
|
|
}
|
|
nextValue := values[len(values)-1]
|
|
for i := len(values) - 1; i >= 0; i-- {
|
|
v := values[i]
|
|
if !math.IsNaN(v) {
|
|
nextValue = v
|
|
continue
|
|
}
|
|
values[i] = nextValue
|
|
}
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformInterpolate(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 1); err != nil {
|
|
return nil, err
|
|
}
|
|
rvs := args[0]
|
|
for _, ts := range rvs {
|
|
values := ts.Values
|
|
if len(values) == 0 {
|
|
continue
|
|
}
|
|
prevValue := nan
|
|
var nextValue float64
|
|
for i := 0; i < len(values); i++ {
|
|
if !math.IsNaN(values[i]) {
|
|
continue
|
|
}
|
|
if i > 0 {
|
|
prevValue = values[i-1]
|
|
}
|
|
j := i + 1
|
|
for j < len(values) {
|
|
if !math.IsNaN(values[j]) {
|
|
break
|
|
}
|
|
j++
|
|
}
|
|
if j >= len(values) {
|
|
nextValue = prevValue
|
|
} else {
|
|
nextValue = values[j]
|
|
}
|
|
if math.IsNaN(prevValue) {
|
|
prevValue = nextValue
|
|
}
|
|
delta := (nextValue - prevValue) / float64(j-i+1)
|
|
for i < j {
|
|
prevValue += delta
|
|
values[i] = prevValue
|
|
i++
|
|
}
|
|
}
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func newTransformFuncRunning(rf func(a, b float64, idx int) float64) transformFunc {
|
|
return func(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 1); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rvs := args[0]
|
|
for _, ts := range rvs {
|
|
ts.MetricName.ResetMetricGroup()
|
|
values := skipLeadingNaNs(ts.Values)
|
|
if len(values) == 0 {
|
|
continue
|
|
}
|
|
prevValue := values[0]
|
|
values = values[1:]
|
|
for i, v := range values {
|
|
if !math.IsNaN(v) {
|
|
prevValue = rf(prevValue, v, i+1)
|
|
}
|
|
values[i] = prevValue
|
|
}
|
|
}
|
|
return rvs, nil
|
|
}
|
|
}
|
|
|
|
func newTransformFuncRange(rf func(a, b float64, idx int) float64) transformFunc {
|
|
tfr := newTransformFuncRunning(rf)
|
|
return func(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
rvs, err := tfr(tfa)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
setLastValues(rvs)
|
|
return rvs, nil
|
|
}
|
|
}
|
|
|
|
func transformRangeQuantile(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 2); err != nil {
|
|
return nil, err
|
|
}
|
|
phis, err := getScalar(args[0], 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(phis) == 0 {
|
|
return nil, nil
|
|
}
|
|
phi := phis[0]
|
|
rvs := args[1]
|
|
var values []float64
|
|
for _, ts := range rvs {
|
|
lastIdx := -1
|
|
originValues := ts.Values
|
|
values = values[:0]
|
|
for i, v := range originValues {
|
|
if math.IsNaN(v) {
|
|
continue
|
|
}
|
|
values = append(values, v)
|
|
lastIdx = i
|
|
}
|
|
if lastIdx >= 0 {
|
|
sort.Float64s(values)
|
|
originValues[lastIdx] = quantileSorted(phi, values)
|
|
}
|
|
}
|
|
setLastValues(rvs)
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformRangeFirst(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 1); err != nil {
|
|
return nil, err
|
|
}
|
|
rvs := args[0]
|
|
for _, ts := range rvs {
|
|
values := skipLeadingNaNs(ts.Values)
|
|
if len(values) == 0 {
|
|
continue
|
|
}
|
|
vFirst := values[0]
|
|
for i, v := range values {
|
|
if math.IsNaN(v) {
|
|
continue
|
|
}
|
|
values[i] = vFirst
|
|
}
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformRangeLast(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 1); err != nil {
|
|
return nil, err
|
|
}
|
|
rvs := args[0]
|
|
setLastValues(rvs)
|
|
return rvs, nil
|
|
}
|
|
|
|
func setLastValues(tss []*timeseries) {
|
|
for _, ts := range tss {
|
|
values := skipTrailingNaNs(ts.Values)
|
|
if len(values) == 0 {
|
|
continue
|
|
}
|
|
vLast := values[len(values)-1]
|
|
for i, v := range values {
|
|
if math.IsNaN(v) {
|
|
continue
|
|
}
|
|
values[i] = vLast
|
|
}
|
|
}
|
|
}
|
|
|
|
func transformSmoothExponential(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 2); err != nil {
|
|
return nil, err
|
|
}
|
|
sfs, err := getScalar(args[1], 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rvs := args[0]
|
|
for _, ts := range rvs {
|
|
values := skipLeadingNaNs(ts.Values)
|
|
for i, v := range values {
|
|
if !math.IsInf(v, 0) {
|
|
values = values[i:]
|
|
break
|
|
}
|
|
}
|
|
if len(values) == 0 {
|
|
continue
|
|
}
|
|
avg := values[0]
|
|
values = values[1:]
|
|
sfsX := sfs[len(ts.Values)-len(values):]
|
|
for i, v := range values {
|
|
if math.IsNaN(v) {
|
|
continue
|
|
}
|
|
if math.IsInf(v, 0) {
|
|
values[i] = avg
|
|
continue
|
|
}
|
|
sf := sfsX[i]
|
|
if math.IsNaN(sf) {
|
|
sf = 1
|
|
}
|
|
if sf < 0 {
|
|
sf = 0
|
|
}
|
|
if sf > 1 {
|
|
sf = 1
|
|
}
|
|
avg = avg*(1-sf) + v*sf
|
|
values[i] = avg
|
|
}
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformRemoveResets(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 1); err != nil {
|
|
return nil, err
|
|
}
|
|
rvs := args[0]
|
|
for _, ts := range rvs {
|
|
removeCounterResetsMaybeNaNs(ts.Values)
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformUnion(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if len(args) < 1 {
|
|
return evalNumber(tfa.ec, nan), nil
|
|
}
|
|
|
|
rvs := make([]*timeseries, 0, len(args[0]))
|
|
m := make(map[string]bool, len(args[0]))
|
|
bb := bbPool.Get()
|
|
for _, arg := range args {
|
|
for _, ts := range arg {
|
|
bb.B = marshalMetricNameSorted(bb.B[:0], &ts.MetricName)
|
|
if m[string(bb.B)] {
|
|
continue
|
|
}
|
|
m[string(bb.B)] = true
|
|
rvs = append(rvs, ts)
|
|
}
|
|
}
|
|
bbPool.Put(bb)
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformLabelKeep(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if len(args) < 1 {
|
|
return nil, fmt.Errorf(`not enough args; got %d; want at least %d`, len(args), 1)
|
|
}
|
|
var keepLabels []string
|
|
for i := 1; i < len(args); i++ {
|
|
keepLabel, err := getString(args[i], i)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
keepLabels = append(keepLabels, keepLabel)
|
|
}
|
|
|
|
rvs := args[0]
|
|
for _, ts := range rvs {
|
|
ts.MetricName.RemoveTagsOn(keepLabels)
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformLabelDel(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if len(args) < 1 {
|
|
return nil, fmt.Errorf(`not enough args; got %d; want at least %d`, len(args), 1)
|
|
}
|
|
var delLabels []string
|
|
for i := 1; i < len(args); i++ {
|
|
delLabel, err := getString(args[i], i)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
delLabels = append(delLabels, delLabel)
|
|
}
|
|
|
|
rvs := args[0]
|
|
for _, ts := range rvs {
|
|
ts.MetricName.RemoveTagsIgnoring(delLabels)
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformLabelSet(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if len(args) < 1 {
|
|
return nil, fmt.Errorf(`not enough args; got %d; want at least %d`, len(args), 1)
|
|
}
|
|
dstLabels, dstValues, err := getStringPairs(args[1:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rvs := args[0]
|
|
for _, ts := range rvs {
|
|
mn := &ts.MetricName
|
|
for i, dstLabel := range dstLabels {
|
|
value := dstValues[i]
|
|
dstValue := getDstValue(mn, dstLabel)
|
|
*dstValue = append((*dstValue)[:0], value...)
|
|
if len(value) == 0 {
|
|
mn.RemoveTag(dstLabel)
|
|
}
|
|
}
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformLabelUppercase(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
return transformLabelValueFunc(tfa, strings.ToUpper)
|
|
}
|
|
|
|
func transformLabelLowercase(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
return transformLabelValueFunc(tfa, strings.ToLower)
|
|
}
|
|
|
|
func transformLabelValueFunc(tfa *transformFuncArg, f func(string) string) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if len(args) < 2 {
|
|
return nil, fmt.Errorf(`not enough args; got %d; want at least %d`, len(args), 2)
|
|
}
|
|
labels := make([]string, 0, len(args)-1)
|
|
for i := 1; i < len(args); i++ {
|
|
label, err := getString(args[i], i)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
labels = append(labels, label)
|
|
}
|
|
|
|
rvs := args[0]
|
|
for _, ts := range rvs {
|
|
mn := &ts.MetricName
|
|
for _, label := range labels {
|
|
dstValue := getDstValue(mn, label)
|
|
*dstValue = append((*dstValue)[:0], f(string(*dstValue))...)
|
|
if len(*dstValue) == 0 {
|
|
mn.RemoveTag(label)
|
|
}
|
|
}
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformLabelMap(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if len(args) < 2 {
|
|
return nil, fmt.Errorf(`not enough args; got %d; want at least %d`, len(args), 2)
|
|
}
|
|
label, err := getString(args[1], 1)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot read label name: %w", err)
|
|
}
|
|
srcValues, dstValues, err := getStringPairs(args[2:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
m := make(map[string]string, len(srcValues))
|
|
for i, srcValue := range srcValues {
|
|
m[srcValue] = dstValues[i]
|
|
}
|
|
rvs := args[0]
|
|
for _, ts := range rvs {
|
|
mn := &ts.MetricName
|
|
dstValue := getDstValue(mn, label)
|
|
value, ok := m[string(*dstValue)]
|
|
if ok {
|
|
*dstValue = append((*dstValue)[:0], value...)
|
|
}
|
|
if len(*dstValue) == 0 {
|
|
mn.RemoveTag(label)
|
|
}
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformLabelCopy(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
return transformLabelCopyExt(tfa, false)
|
|
}
|
|
|
|
func transformLabelMove(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
return transformLabelCopyExt(tfa, true)
|
|
}
|
|
|
|
func transformLabelCopyExt(tfa *transformFuncArg, removeSrcLabels bool) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if len(args) < 1 {
|
|
return nil, fmt.Errorf(`not enough args; got %d; want at least %d`, len(args), 1)
|
|
}
|
|
srcLabels, dstLabels, err := getStringPairs(args[1:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rvs := args[0]
|
|
for _, ts := range rvs {
|
|
mn := &ts.MetricName
|
|
for i, srcLabel := range srcLabels {
|
|
dstLabel := dstLabels[i]
|
|
value := mn.GetTagValue(srcLabel)
|
|
if len(value) == 0 {
|
|
// Do not remove destination label if the source label doesn't exist.
|
|
continue
|
|
}
|
|
dstValue := getDstValue(mn, dstLabel)
|
|
*dstValue = append((*dstValue)[:0], value...)
|
|
if removeSrcLabels && srcLabel != dstLabel {
|
|
mn.RemoveTag(srcLabel)
|
|
}
|
|
}
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func getStringPairs(args [][]*timeseries) ([]string, []string, error) {
|
|
if len(args)%2 != 0 {
|
|
return nil, nil, fmt.Errorf(`the number of string args must be even; got %d`, len(args))
|
|
}
|
|
var ks, vs []string
|
|
for i := 0; i < len(args); i += 2 {
|
|
k, err := getString(args[i], i)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
ks = append(ks, k)
|
|
|
|
v, err := getString(args[i+1], i+1)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
vs = append(vs, v)
|
|
}
|
|
return ks, vs, nil
|
|
}
|
|
|
|
func transformLabelJoin(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if len(args) < 3 {
|
|
return nil, fmt.Errorf(`not enough args; got %d; want at least %d`, len(args), 3)
|
|
}
|
|
dstLabel, err := getString(args[1], 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
separator, err := getString(args[2], 2)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var srcLabels []string
|
|
for i := 3; i < len(args); i++ {
|
|
srcLabel, err := getString(args[i], i)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
srcLabels = append(srcLabels, srcLabel)
|
|
}
|
|
|
|
rvs := args[0]
|
|
for _, ts := range rvs {
|
|
mn := &ts.MetricName
|
|
dstValue := getDstValue(mn, dstLabel)
|
|
b := *dstValue
|
|
b = b[:0]
|
|
for j, srcLabel := range srcLabels {
|
|
srcValue := mn.GetTagValue(srcLabel)
|
|
b = append(b, srcValue...)
|
|
if j+1 < len(srcLabels) {
|
|
b = append(b, separator...)
|
|
}
|
|
}
|
|
*dstValue = b
|
|
if len(b) == 0 {
|
|
mn.RemoveTag(dstLabel)
|
|
}
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformLabelTransform(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 4); err != nil {
|
|
return nil, err
|
|
}
|
|
label, err := getString(args[1], 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
regex, err := getString(args[2], 2)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
replacement, err := getString(args[3], 3)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
r, err := metricsql.CompileRegexp(regex)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`cannot compile regex %q: %w`, regex, err)
|
|
}
|
|
return labelReplace(args[0], label, r, label, replacement)
|
|
}
|
|
|
|
func transformLabelReplace(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 5); err != nil {
|
|
return nil, err
|
|
}
|
|
dstLabel, err := getString(args[1], 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
replacement, err := getString(args[2], 2)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
srcLabel, err := getString(args[3], 3)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
regex, err := getString(args[4], 4)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
r, err := metricsql.CompileRegexpAnchored(regex)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`cannot compile regex %q: %w`, regex, err)
|
|
}
|
|
return labelReplace(args[0], srcLabel, r, dstLabel, replacement)
|
|
}
|
|
|
|
func labelReplace(tss []*timeseries, srcLabel string, r *regexp.Regexp, dstLabel, replacement string) ([]*timeseries, error) {
|
|
replacementBytes := []byte(replacement)
|
|
for _, ts := range tss {
|
|
mn := &ts.MetricName
|
|
dstValue := getDstValue(mn, dstLabel)
|
|
srcValue := mn.GetTagValue(srcLabel)
|
|
if !r.Match(srcValue) {
|
|
continue
|
|
}
|
|
b := r.ReplaceAll(srcValue, replacementBytes)
|
|
*dstValue = append((*dstValue)[:0], b...)
|
|
if len(b) == 0 {
|
|
mn.RemoveTag(dstLabel)
|
|
}
|
|
}
|
|
return tss, nil
|
|
}
|
|
|
|
func transformLabelValue(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 2); err != nil {
|
|
return nil, err
|
|
}
|
|
labelName, err := getString(args[1], 1)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot get label name: %w", err)
|
|
}
|
|
rvs := args[0]
|
|
for _, ts := range rvs {
|
|
ts.MetricName.ResetMetricGroup()
|
|
labelValue := ts.MetricName.GetTagValue(labelName)
|
|
v, err := strconv.ParseFloat(string(labelValue), 64)
|
|
if err != nil {
|
|
v = nan
|
|
}
|
|
values := ts.Values
|
|
for i := range values {
|
|
values[i] = v
|
|
}
|
|
}
|
|
// Do not remove timeseries with only NaN values, so `default` could be applied to them:
|
|
// label_value(q, "label") default 123
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformLabelMatch(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 3); err != nil {
|
|
return nil, err
|
|
}
|
|
labelName, err := getString(args[1], 1)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot get label name: %w", err)
|
|
}
|
|
labelRe, err := getString(args[2], 2)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot get regexp: %w", err)
|
|
}
|
|
r, err := metricsql.CompileRegexpAnchored(labelRe)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`cannot compile regexp %q: %w`, labelRe, err)
|
|
}
|
|
tss := args[0]
|
|
rvs := tss[:0]
|
|
for _, ts := range tss {
|
|
labelValue := ts.MetricName.GetTagValue(labelName)
|
|
if r.Match(labelValue) {
|
|
rvs = append(rvs, ts)
|
|
}
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformLabelMismatch(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 3); err != nil {
|
|
return nil, err
|
|
}
|
|
labelName, err := getString(args[1], 1)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot get label name: %w", err)
|
|
}
|
|
labelRe, err := getString(args[2], 2)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot get regexp: %w", err)
|
|
}
|
|
r, err := metricsql.CompileRegexpAnchored(labelRe)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(`cannot compile regexp %q: %w`, labelRe, err)
|
|
}
|
|
tss := args[0]
|
|
rvs := tss[:0]
|
|
for _, ts := range tss {
|
|
labelValue := ts.MetricName.GetTagValue(labelName)
|
|
if !r.Match(labelValue) {
|
|
rvs = append(rvs, ts)
|
|
}
|
|
}
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformLn(v float64) float64 {
|
|
return math.Log(v)
|
|
}
|
|
|
|
func transformLog2(v float64) float64 {
|
|
return math.Log2(v)
|
|
}
|
|
|
|
func transformLog10(v float64) float64 {
|
|
return math.Log10(v)
|
|
}
|
|
|
|
func transformMinute(t time.Time) int {
|
|
return t.Minute()
|
|
}
|
|
|
|
func transformMonth(t time.Time) int {
|
|
return int(t.Month())
|
|
}
|
|
|
|
func transformRound(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if len(args) != 1 && len(args) != 2 {
|
|
return nil, fmt.Errorf(`unexpected number of args: %d; want 1 or 2`, len(args))
|
|
}
|
|
var nearestArg []*timeseries
|
|
if len(args) == 1 {
|
|
nearestArg = evalNumber(tfa.ec, 1)
|
|
} else {
|
|
nearestArg = args[1]
|
|
}
|
|
nearest, err := getScalar(nearestArg, 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tf := func(values []float64) {
|
|
var nPrev float64
|
|
var p10 float64
|
|
for i, v := range values {
|
|
n := nearest[i]
|
|
if n != nPrev {
|
|
nPrev = n
|
|
_, e := decimal.FromFloat(n)
|
|
p10 = math.Pow10(int(-e))
|
|
}
|
|
v += 0.5 * math.Copysign(n, v)
|
|
v -= math.Mod(v, n)
|
|
v, _ = math.Modf(v * p10)
|
|
values[i] = v / p10
|
|
}
|
|
}
|
|
return doTransformValues(args[0], tf, tfa.fe)
|
|
}
|
|
|
|
func transformSign(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 1); err != nil {
|
|
return nil, err
|
|
}
|
|
tf := func(values []float64) {
|
|
for i, v := range values {
|
|
sign := float64(0)
|
|
if v < 0 {
|
|
sign = -1
|
|
} else if v > 0 {
|
|
sign = 1
|
|
}
|
|
values[i] = sign
|
|
}
|
|
}
|
|
return doTransformValues(args[0], tf, tfa.fe)
|
|
}
|
|
|
|
func transformScalar(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 1); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Verify whether the arg is a string.
|
|
// Then try converting the string to number.
|
|
if se, ok := tfa.fe.Args[0].(*metricsql.StringExpr); ok {
|
|
n, err := strconv.ParseFloat(se.S, 64)
|
|
if err != nil {
|
|
n = nan
|
|
}
|
|
return evalNumber(tfa.ec, n), nil
|
|
}
|
|
|
|
// The arg isn't a string. Extract scalar from it.
|
|
arg := args[0]
|
|
if len(arg) != 1 {
|
|
return evalNumber(tfa.ec, nan), nil
|
|
}
|
|
arg[0].MetricName.Reset()
|
|
return arg, nil
|
|
}
|
|
|
|
func newTransformFuncSortByLabel(isDesc bool) transformFunc {
|
|
return func(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if len(args) < 2 {
|
|
return nil, fmt.Errorf("expecting at least 2 args; got %d args", len(args))
|
|
}
|
|
var labels []string
|
|
for i, arg := range args[1:] {
|
|
label, err := getString(arg, 1)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot parse label #%d for sorting: %w", i+1, err)
|
|
}
|
|
labels = append(labels, label)
|
|
}
|
|
rvs := args[0]
|
|
sort.SliceStable(rvs, func(i, j int) bool {
|
|
for _, label := range labels {
|
|
a := rvs[i].MetricName.GetTagValue(label)
|
|
b := rvs[j].MetricName.GetTagValue(label)
|
|
if string(a) == string(b) {
|
|
continue
|
|
}
|
|
if isDesc {
|
|
return string(b) < string(a)
|
|
}
|
|
return string(a) < string(b)
|
|
}
|
|
return false
|
|
})
|
|
return rvs, nil
|
|
}
|
|
}
|
|
|
|
func newTransformFuncSort(isDesc bool) transformFunc {
|
|
return func(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 1); err != nil {
|
|
return nil, err
|
|
}
|
|
rvs := args[0]
|
|
sort.Slice(rvs, func(i, j int) bool {
|
|
a := rvs[i].Values
|
|
b := rvs[j].Values
|
|
n := len(a) - 1
|
|
for n >= 0 {
|
|
if !math.IsNaN(a[n]) {
|
|
if math.IsNaN(b[n]) {
|
|
return false
|
|
}
|
|
if a[n] != b[n] {
|
|
break
|
|
}
|
|
} else if !math.IsNaN(b[n]) {
|
|
return true
|
|
}
|
|
n--
|
|
}
|
|
if n < 0 {
|
|
return false
|
|
}
|
|
if isDesc {
|
|
return b[n] < a[n]
|
|
}
|
|
return a[n] < b[n]
|
|
})
|
|
return rvs, nil
|
|
}
|
|
}
|
|
|
|
func transformSqrt(v float64) float64 {
|
|
return math.Sqrt(v)
|
|
}
|
|
|
|
func transformSin(v float64) float64 {
|
|
return math.Sin(v)
|
|
}
|
|
|
|
func transformCos(v float64) float64 {
|
|
return math.Cos(v)
|
|
}
|
|
|
|
func transformAsin(v float64) float64 {
|
|
return math.Asin(v)
|
|
}
|
|
|
|
func transformAcos(v float64) float64 {
|
|
return math.Acos(v)
|
|
}
|
|
|
|
func newTransformRand(newRandFunc func(r *rand.Rand) func() float64) transformFunc {
|
|
return func(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if len(args) > 1 {
|
|
return nil, fmt.Errorf(`unexpected number of args; got %d; want 0 or 1`, len(args))
|
|
}
|
|
var seed int64
|
|
if len(args) == 1 {
|
|
tmp, err := getScalar(args[0], 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
seed = int64(tmp[0])
|
|
} else {
|
|
seed = time.Now().UnixNano()
|
|
}
|
|
source := rand.NewSource(seed)
|
|
r := rand.New(source)
|
|
randFunc := newRandFunc(r)
|
|
tss := evalNumber(tfa.ec, 0)
|
|
values := tss[0].Values
|
|
for i := range values {
|
|
values[i] = randFunc()
|
|
}
|
|
return tss, nil
|
|
}
|
|
}
|
|
|
|
func newRandFloat64(r *rand.Rand) func() float64 {
|
|
return r.Float64
|
|
}
|
|
|
|
func newRandNormFloat64(r *rand.Rand) func() float64 {
|
|
return r.NormFloat64
|
|
}
|
|
|
|
func newRandExpFloat64(r *rand.Rand) func() float64 {
|
|
return r.ExpFloat64
|
|
}
|
|
|
|
func transformPi(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
if err := expectTransformArgsNum(tfa.args, 0); err != nil {
|
|
return nil, err
|
|
}
|
|
return evalNumber(tfa.ec, math.Pi), nil
|
|
}
|
|
|
|
func bitmapAnd(a, b uint64) uint64 {
|
|
return a & b
|
|
}
|
|
|
|
func bitmapOr(a, b uint64) uint64 {
|
|
return a | b
|
|
}
|
|
|
|
func bitmapXor(a, b uint64) uint64 {
|
|
return a ^ b
|
|
}
|
|
|
|
func newTransformBitmap(bitmapFunc func(a, b uint64) uint64) func(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
return func(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 2); err != nil {
|
|
return nil, err
|
|
}
|
|
ns, err := getScalar(args[1], 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tf := func(values []float64) {
|
|
for i, v := range values {
|
|
values[i] = float64(bitmapFunc(uint64(v), uint64(ns[i])))
|
|
}
|
|
}
|
|
return doTransformValues(args[0], tf, tfa.fe)
|
|
}
|
|
}
|
|
|
|
func transformTimezoneOffset(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 1); err != nil {
|
|
return nil, err
|
|
}
|
|
tzString, err := getString(args[0], 0)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot get timezone name: %w", err)
|
|
}
|
|
tzOffset, err := getTimezoneOffset(tzString)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot get timezone offset for %q: %w", tzString, err)
|
|
}
|
|
rv := evalNumber(tfa.ec, float64(tzOffset))
|
|
return rv, nil
|
|
}
|
|
|
|
func getTimezoneOffset(tzString string) (int, error) {
|
|
loc, err := time.LoadLocation(tzString)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("cannot load timezone %q: %w", tzString, err)
|
|
}
|
|
_, tzOffset := time.Now().In(loc).Zone()
|
|
return tzOffset, nil
|
|
}
|
|
|
|
func transformTime(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
if err := expectTransformArgsNum(tfa.args, 0); err != nil {
|
|
return nil, err
|
|
}
|
|
return evalTime(tfa.ec), nil
|
|
}
|
|
|
|
func transformVector(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
args := tfa.args
|
|
if err := expectTransformArgsNum(args, 1); err != nil {
|
|
return nil, err
|
|
}
|
|
rvs := args[0]
|
|
return rvs, nil
|
|
}
|
|
|
|
func transformYear(t time.Time) int {
|
|
return t.Year()
|
|
}
|
|
|
|
func newTransformFuncZeroArgs(f func(tfa *transformFuncArg) float64) transformFunc {
|
|
return func(tfa *transformFuncArg) ([]*timeseries, error) {
|
|
if err := expectTransformArgsNum(tfa.args, 0); err != nil {
|
|
return nil, err
|
|
}
|
|
v := f(tfa)
|
|
return evalNumber(tfa.ec, v), nil
|
|
}
|
|
}
|
|
|
|
func transformStep(tfa *transformFuncArg) float64 {
|
|
return float64(tfa.ec.Step) / 1e3
|
|
}
|
|
|
|
func transformStart(tfa *transformFuncArg) float64 {
|
|
return float64(tfa.ec.Start) / 1e3
|
|
}
|
|
|
|
func transformEnd(tfa *transformFuncArg) float64 {
|
|
return float64(tfa.ec.End) / 1e3
|
|
}
|
|
|
|
// copyTimeseries returns a copy of tss.
|
|
func copyTimeseries(tss []*timeseries) []*timeseries {
|
|
rvs := make([]*timeseries, len(tss))
|
|
for i, src := range tss {
|
|
var dst timeseries
|
|
dst.CopyFromShallowTimestamps(src)
|
|
rvs[i] = &dst
|
|
}
|
|
return rvs
|
|
}
|
|
|
|
// copyTimeseriesMetricNames returns a copy of tss with real copy of MetricNames,
|
|
// but with shallow copy of Timestamps and Values if makeCopy is set.
|
|
//
|
|
// Otherwise tss is returned.
|
|
func copyTimeseriesMetricNames(tss []*timeseries, makeCopy bool) []*timeseries {
|
|
if !makeCopy {
|
|
return tss
|
|
}
|
|
rvs := make([]*timeseries, len(tss))
|
|
for i, src := range tss {
|
|
var dst timeseries
|
|
dst.CopyFromMetricNames(src)
|
|
rvs[i] = &dst
|
|
}
|
|
return rvs
|
|
}
|
|
|
|
// copyTimeseriesShallow returns a copy of arg with shallow copies of MetricNames,
|
|
// Timestamps and Values.
|
|
func copyTimeseriesShallow(arg []*timeseries) []*timeseries {
|
|
rvs := make([]*timeseries, len(arg))
|
|
for i, src := range arg {
|
|
var dst timeseries
|
|
dst.CopyShallow(src)
|
|
rvs[i] = &dst
|
|
}
|
|
return rvs
|
|
}
|
|
|
|
func getDstValue(mn *storage.MetricName, dstLabel string) *[]byte {
|
|
if dstLabel == "__name__" {
|
|
return &mn.MetricGroup
|
|
}
|
|
tags := mn.Tags
|
|
for i := range tags {
|
|
tag := &tags[i]
|
|
if string(tag.Key) == dstLabel {
|
|
return &tag.Value
|
|
}
|
|
}
|
|
if len(tags) < cap(tags) {
|
|
tags = tags[:len(tags)+1]
|
|
} else {
|
|
tags = append(tags, storage.Tag{})
|
|
}
|
|
mn.Tags = tags
|
|
tag := &tags[len(tags)-1]
|
|
tag.Key = append(tag.Key[:0], dstLabel...)
|
|
return &tag.Value
|
|
}
|
|
|
|
func isLeapYear(y uint32) bool {
|
|
if y%4 != 0 {
|
|
return false
|
|
}
|
|
if y%100 != 0 {
|
|
return true
|
|
}
|
|
return y%400 == 0
|
|
}
|
|
|
|
var daysInMonth = [...]int{
|
|
time.January: 31,
|
|
time.February: 28,
|
|
time.March: 31,
|
|
time.April: 30,
|
|
time.May: 31,
|
|
time.June: 30,
|
|
time.July: 31,
|
|
time.August: 31,
|
|
time.September: 30,
|
|
time.October: 31,
|
|
time.November: 30,
|
|
time.December: 31,
|
|
}
|
|
|
|
func expectTransformArgsNum(args [][]*timeseries, expectedNum int) error {
|
|
if len(args) == expectedNum {
|
|
return nil
|
|
}
|
|
return fmt.Errorf(`unexpected number of args; got %d; want %d`, len(args), expectedNum)
|
|
}
|
|
|
|
func removeCounterResetsMaybeNaNs(values []float64) {
|
|
values = skipLeadingNaNs(values)
|
|
if len(values) == 0 {
|
|
return
|
|
}
|
|
var correction float64
|
|
prevValue := values[0]
|
|
for i, v := range values {
|
|
if math.IsNaN(v) {
|
|
continue
|
|
}
|
|
d := v - prevValue
|
|
if d < 0 {
|
|
if (-d * 8) < prevValue {
|
|
// This is likely jitter from `Prometheus HA pairs`.
|
|
// Just substitute v with prevValue.
|
|
v = prevValue
|
|
} else {
|
|
correction += prevValue
|
|
}
|
|
}
|
|
prevValue = v
|
|
values[i] = v + correction
|
|
}
|
|
}
|