mirror of
https://github.com/hanzoai/metrics.git
synced 2026-10-30 20:03:39 +00:00
714 lines
19 KiB
Go
714 lines
19 KiB
Go
package metricsql
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// Optimize optimizes e in order to improve its performance.
|
|
//
|
|
// It performs the following optimizations:
|
|
//
|
|
// - Adds missing filters to `foo{filters1} op bar{filters2}`
|
|
// according to https://utcc.utoronto.ca/~cks/space/blog/sysadmin/PrometheusLabelNonOptimization
|
|
// I.e. such query is converted to `foo{filters1, filters2} op bar{filters1, filters2}`
|
|
func Optimize(e Expr) Expr {
|
|
if !canOptimize(e) {
|
|
return e
|
|
}
|
|
eCopy := Clone(e)
|
|
optimizeInplace(eCopy)
|
|
return eCopy
|
|
}
|
|
|
|
func canOptimize(e Expr) bool {
|
|
switch t := e.(type) {
|
|
case *RollupExpr:
|
|
return canOptimize(t.Expr) || canOptimize(t.At)
|
|
case *FuncExpr:
|
|
for _, arg := range t.Args {
|
|
if canOptimize(arg) {
|
|
return true
|
|
}
|
|
}
|
|
case *AggrFuncExpr:
|
|
for _, arg := range t.Args {
|
|
if canOptimize(arg) {
|
|
return true
|
|
}
|
|
}
|
|
case *BinaryOpExpr:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Clone clones the given expression e and returns the cloned copy.
|
|
func Clone(e Expr) Expr {
|
|
s := e.AppendString(nil)
|
|
eCopy, err := Parse(string(s))
|
|
if err != nil {
|
|
panic(fmt.Errorf("BUG: cannot parse the expression %q: %w", s, err))
|
|
}
|
|
return eCopy
|
|
}
|
|
|
|
func optimizeInplace(e Expr) {
|
|
switch t := e.(type) {
|
|
case *RollupExpr:
|
|
optimizeInplace(t.Expr)
|
|
optimizeInplace(t.At)
|
|
case *FuncExpr:
|
|
optimizeArgsInplace(t.Args)
|
|
case *AggrFuncExpr:
|
|
optimizeArgsInplace(t.Args)
|
|
case *BinaryOpExpr:
|
|
optimizeInplace(t.Left)
|
|
optimizeInplace(t.Right)
|
|
lfs := getCommonLabelFilters(t)
|
|
pushdownBinaryOpFiltersInplace(lfs, t)
|
|
}
|
|
}
|
|
|
|
func optimizeArgsInplace(args []Expr) {
|
|
for _, arg := range args {
|
|
optimizeInplace(arg)
|
|
}
|
|
}
|
|
|
|
func getCommonLabelFilters(e Expr) []LabelFilter {
|
|
switch t := e.(type) {
|
|
case *MetricExpr:
|
|
return getCommonLabelFiltersWithoutMetricName(t.LabelFilterss)
|
|
case *RollupExpr:
|
|
return getCommonLabelFilters(t.Expr)
|
|
case *FuncExpr:
|
|
args := t.Args
|
|
switch strings.ToLower(t.Name) {
|
|
case "label_set":
|
|
return getCommonLabelFiltersForLabelSet(args)
|
|
case "label_replace", "label_join", "label_map", "label_match", "label_mismatch", "label_transform":
|
|
return getCommonLabelFiltersForLabelReplace(args)
|
|
case "label_copy", "label_move":
|
|
return getCommonLabelFiltersForLabelCopy(args)
|
|
case "label_del", "label_uppercase", "label_lowercase", "labels_equal":
|
|
return getCommonLabelFiltersForLabelDel(args)
|
|
case "label_keep":
|
|
return getCommonLabelFiltersForLabelKeep(args)
|
|
case "count_values_over_time":
|
|
return getCommonLabelFiltersForCountValuesOverTime(args)
|
|
case "range_normalize", "union", "":
|
|
return intersectLabelFiltersForAllArgs(args)
|
|
default:
|
|
arg := getFuncArgForOptimization(t.Name, args)
|
|
if arg == nil {
|
|
return nil
|
|
}
|
|
return getCommonLabelFilters(arg)
|
|
}
|
|
case *AggrFuncExpr:
|
|
args := t.Args
|
|
if strings.ToLower(t.Name) == "count_values" {
|
|
if len(args) != 2 {
|
|
return nil
|
|
}
|
|
lfs := getCommonLabelFilters(args[1])
|
|
lfs = dropLabelFiltersForLabelName(lfs, args[0])
|
|
return trimFiltersByAggrModifier(lfs, t)
|
|
}
|
|
if canAcceptMultipleArgsForAggrFunc(t.Name) {
|
|
lfs := intersectLabelFiltersForAllArgs(args)
|
|
return trimFiltersByAggrModifier(lfs, t)
|
|
}
|
|
arg := getFuncArgForOptimization(t.Name, args)
|
|
if arg == nil {
|
|
return nil
|
|
}
|
|
lfs := getCommonLabelFilters(arg)
|
|
return trimFiltersByAggrModifier(lfs, t)
|
|
case *BinaryOpExpr:
|
|
lfsLeft := getCommonLabelFilters(t.Left)
|
|
lfsRight := getCommonLabelFilters(t.Right)
|
|
var lfs []LabelFilter
|
|
switch strings.ToLower(t.Op) {
|
|
case "or":
|
|
// {fCommon, f1} or {fCommon, f2} -> {fCommon}
|
|
// {fCommon, f1} or on() {fCommon, f2} -> {}
|
|
// {fCommon, f1} or on(fCommon) {fCommon, f2} -> {fCommon}
|
|
// {fCommon, f1} or on(f1) {fCommon, f2} -> {}
|
|
// {fCommon, f1} or on(f2) {fCommon, f2} -> {}
|
|
// {fCommon, f1} or on(f3) {fCommon, f2} -> {}
|
|
lfs = intersectLabelFilters(lfsLeft, lfsRight)
|
|
return TrimFiltersByGroupModifier(lfs, t)
|
|
case "unless":
|
|
// {f1} unless {f2} -> {f1}
|
|
// {f1} unless on() {f2} -> {}
|
|
// {f1} unless on(f1) {f2} -> {f1}
|
|
// {f1} unless on(f2) {f2} -> {}
|
|
// {f1} unless on(f1, f2) {f2} -> {f1}
|
|
// {f1} unless on(f3) {f2} -> {}
|
|
return TrimFiltersByGroupModifier(lfsLeft, t)
|
|
case "ifnot":
|
|
// remove right from left, so filter in left can be pushed down to right.
|
|
// {f1} ifnot `any` -> {f1}
|
|
// see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8435
|
|
return TrimFiltersByGroupModifier(lfsLeft, t)
|
|
default:
|
|
switch strings.ToLower(t.JoinModifier.Op) {
|
|
case "group_left":
|
|
// {f1} * group_left() {f2} -> {f1, f2}
|
|
// {f1} * on() group_left() {f2} -> {f1}
|
|
// {f1} * on(f1) group_left() {f2} -> {f1}
|
|
// {f1} * on(f2) group_left() {f2} -> {f1, f2}
|
|
// {f1} * on(f1, f2) group_left() {f2} -> {f1, f2}
|
|
// {f1} * on(f3) group_left() {f2} -> {f1}
|
|
lfsRight = TrimFiltersByGroupModifier(lfsRight, t)
|
|
return unionLabelFilters(lfsLeft, lfsRight)
|
|
case "group_right":
|
|
// {f1} * group_right() {f2} -> {f1, f2}
|
|
// {f1} * on() group_right() {f2} -> {f2}
|
|
// {f1} * on(f1) group_right() {f2} -> {f1, f2}
|
|
// {f1} * on(f2) group_right() {f2} -> {f2}
|
|
// {f1} * on(f1, f2) group_right() {f2} -> {f1, f2}
|
|
// {f1} * on(f3) group_right() {f2} -> {f2}
|
|
lfsLeft = TrimFiltersByGroupModifier(lfsLeft, t)
|
|
return unionLabelFilters(lfsLeft, lfsRight)
|
|
default:
|
|
// {f1} * {f2} -> {f1, f2}
|
|
// {f1} * on() {f2} -> {}
|
|
// {f1} * on(f1) {f2} -> {f1}
|
|
// {f1} * on(f2) {f2} -> {f2}
|
|
// {f1} * on(f1, f2) {f2} -> {f2}
|
|
// {f1} * on(f3} {f2} -> {}
|
|
lfs = unionLabelFilters(lfsLeft, lfsRight)
|
|
return TrimFiltersByGroupModifier(lfs, t)
|
|
}
|
|
}
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func intersectLabelFiltersForAllArgs(args []Expr) []LabelFilter {
|
|
if len(args) == 0 {
|
|
return nil
|
|
}
|
|
lfs := getCommonLabelFilters(args[0])
|
|
for _, arg := range args[1:] {
|
|
lfsNext := getCommonLabelFilters(arg)
|
|
lfs = intersectLabelFilters(lfs, lfsNext)
|
|
}
|
|
return lfs
|
|
}
|
|
|
|
func getCommonLabelFiltersForCountValuesOverTime(args []Expr) []LabelFilter {
|
|
if len(args) != 2 {
|
|
return nil
|
|
}
|
|
lfs := getCommonLabelFilters(args[1])
|
|
return dropLabelFiltersForLabelName(lfs, args[0])
|
|
}
|
|
|
|
func getCommonLabelFiltersForLabelKeep(args []Expr) []LabelFilter {
|
|
if len(args) == 0 {
|
|
return nil
|
|
}
|
|
lfs := getCommonLabelFilters(args[0])
|
|
lfs = keepLabelFiltersForLabelNames(lfs, args[1:])
|
|
return lfs
|
|
}
|
|
|
|
func getCommonLabelFiltersForLabelDel(args []Expr) []LabelFilter {
|
|
if len(args) == 0 {
|
|
return nil
|
|
}
|
|
lfs := getCommonLabelFilters(args[0])
|
|
lfs = dropLabelFiltersForLabelNames(lfs, args[1:])
|
|
return lfs
|
|
}
|
|
|
|
func getCommonLabelFiltersForLabelCopy(args []Expr) []LabelFilter {
|
|
if len(args) == 0 {
|
|
return nil
|
|
}
|
|
lfs := getCommonLabelFilters(args[0])
|
|
args = args[1:]
|
|
var labelNames []Expr
|
|
for i := 0; i < len(args); i += 2 {
|
|
if i+1 >= len(args) {
|
|
return nil
|
|
}
|
|
labelNames = append(labelNames, args[i+1])
|
|
}
|
|
lfs = dropLabelFiltersForLabelNames(lfs, labelNames)
|
|
return lfs
|
|
}
|
|
|
|
func getCommonLabelFiltersForLabelReplace(args []Expr) []LabelFilter {
|
|
if len(args) < 2 {
|
|
return nil
|
|
}
|
|
lfs := getCommonLabelFilters(args[0])
|
|
return dropLabelFiltersForLabelName(lfs, args[1])
|
|
}
|
|
|
|
func getCommonLabelFiltersForLabelSet(args []Expr) []LabelFilter {
|
|
if len(args) == 0 {
|
|
return nil
|
|
}
|
|
lfs := getCommonLabelFilters(args[0])
|
|
args = args[1:]
|
|
for i := 0; i < len(args); i += 2 {
|
|
labelName := args[i]
|
|
if i+1 >= len(args) {
|
|
return nil
|
|
}
|
|
labelValue := args[i+1]
|
|
|
|
seLabelName, ok := labelName.(*StringExpr)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
seLabelValue, ok := labelValue.(*StringExpr)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
if seLabelName.S == "__name__" {
|
|
continue
|
|
}
|
|
|
|
lfs = dropLabelFiltersForLabelName(lfs, labelName)
|
|
lfs = append(lfs, LabelFilter{
|
|
Label: seLabelName.S,
|
|
Value: seLabelValue.S,
|
|
})
|
|
}
|
|
return lfs
|
|
}
|
|
|
|
func trimFiltersByAggrModifier(lfs []LabelFilter, afe *AggrFuncExpr) []LabelFilter {
|
|
switch strings.ToLower(afe.Modifier.Op) {
|
|
case "by":
|
|
return filterLabelFiltersOn(lfs, afe.Modifier.Args)
|
|
case "without":
|
|
return filterLabelFiltersIgnoring(lfs, afe.Modifier.Args)
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// TrimFiltersByGroupModifier trims lfs by the specified be.GroupModifier.Op (e.g. on() or ignoring()).
|
|
//
|
|
// The following cases are possible:
|
|
// - It returns lfs as is if be doesn't contain any group modifier
|
|
// - It returns only filters specified in on()
|
|
// - It drops filters specified inside ignoring()
|
|
func TrimFiltersByGroupModifier(lfs []LabelFilter, be *BinaryOpExpr) []LabelFilter {
|
|
switch strings.ToLower(be.GroupModifier.Op) {
|
|
case "on":
|
|
return filterLabelFiltersOn(lfs, be.GroupModifier.Args)
|
|
case "ignoring":
|
|
return filterLabelFiltersIgnoring(lfs, be.GroupModifier.Args)
|
|
default:
|
|
return lfs
|
|
}
|
|
}
|
|
|
|
func getCommonLabelFiltersWithoutMetricName(lfss [][]LabelFilter) []LabelFilter {
|
|
if len(lfss) == 0 {
|
|
return nil
|
|
}
|
|
lfsA := getLabelFiltersWithoutMetricName(lfss[0])
|
|
for _, lfs := range lfss[1:] {
|
|
if len(lfsA) == 0 {
|
|
return nil
|
|
}
|
|
lfsB := getLabelFiltersWithoutMetricName(lfs)
|
|
lfsA = intersectLabelFilters(lfsA, lfsB)
|
|
}
|
|
return lfsA
|
|
}
|
|
|
|
func getLabelFiltersWithoutMetricName(lfs []LabelFilter) []LabelFilter {
|
|
lfsNew := make([]LabelFilter, 0, len(lfs))
|
|
for _, lf := range lfs {
|
|
if lf.Label != "__name__" {
|
|
lfsNew = append(lfsNew, lf)
|
|
}
|
|
}
|
|
return lfsNew
|
|
}
|
|
|
|
// PushdownBinaryOpFilters pushes down the given commonFilters to e if possible.
|
|
//
|
|
// e must be a part of binary operation - either left or right.
|
|
//
|
|
// For example, if e contains `foo + sum(bar)` and commonFilters={x="y"},
|
|
// then the returned expression will contain `foo{x="y"} + sum(bar)`.
|
|
// The `{x="y"}` cannot be pusehd down to `sum(bar)`, since this may change binary operation results.
|
|
func PushdownBinaryOpFilters(e Expr, commonFilters []LabelFilter) Expr {
|
|
if len(commonFilters) == 0 {
|
|
// Fast path - nothing to push down.
|
|
return e
|
|
}
|
|
eCopy := Clone(e)
|
|
pushdownBinaryOpFiltersInplace(commonFilters, eCopy)
|
|
return eCopy
|
|
}
|
|
|
|
func pushdownBinaryOpFiltersInplace(lfs []LabelFilter, e Expr) {
|
|
if len(lfs) == 0 {
|
|
return
|
|
}
|
|
switch t := e.(type) {
|
|
case *MetricExpr:
|
|
for i, lfsLocal := range t.LabelFilterss {
|
|
lfsLocal = unionLabelFilters(lfsLocal, lfs)
|
|
sortLabelFilters(lfsLocal)
|
|
t.LabelFilterss[i] = lfsLocal
|
|
}
|
|
case *RollupExpr:
|
|
pushdownBinaryOpFiltersInplace(lfs, t.Expr)
|
|
case *FuncExpr:
|
|
args := t.Args
|
|
switch strings.ToLower(t.Name) {
|
|
case "label_set":
|
|
pushdownLabelFiltersForLabelSet(lfs, args)
|
|
case "label_replace", "label_join", "label_map", "label_match", "label_mismatch", "label_transform":
|
|
pushdownLabelFiltersForLabelReplace(lfs, args)
|
|
case "label_copy", "label_move":
|
|
pushdownLabelFiltersForLabelCopy(lfs, args)
|
|
case "label_del", "label_uppercase", "label_lowercase", "labels_equal":
|
|
pushdownLabelFiltersForLabelDel(lfs, args)
|
|
case "label_keep":
|
|
pushdownLabelFiltersForLabelKeep(lfs, args)
|
|
case "count_values_over_time":
|
|
pushdownLabelFiltersForCountValuesOverTime(lfs, args)
|
|
case "range_normalize", "union", "":
|
|
pushdownLabelFiltersForAllArgs(lfs, args)
|
|
default:
|
|
arg := getFuncArgForOptimization(t.Name, args)
|
|
if arg != nil {
|
|
pushdownBinaryOpFiltersInplace(lfs, arg)
|
|
}
|
|
}
|
|
case *AggrFuncExpr:
|
|
lfs = trimFiltersByAggrModifier(lfs, t)
|
|
args := t.Args
|
|
if strings.ToLower(t.Name) == "count_values" {
|
|
if len(args) == 2 {
|
|
lfs = dropLabelFiltersForLabelName(lfs, args[0])
|
|
pushdownBinaryOpFiltersInplace(lfs, args[1])
|
|
}
|
|
} else if canAcceptMultipleArgsForAggrFunc(t.Name) {
|
|
pushdownLabelFiltersForAllArgs(lfs, args)
|
|
} else {
|
|
arg := getFuncArgForOptimization(t.Name, args)
|
|
if arg != nil {
|
|
pushdownBinaryOpFiltersInplace(lfs, arg)
|
|
}
|
|
}
|
|
case *BinaryOpExpr:
|
|
lfs = TrimFiltersByGroupModifier(lfs, t)
|
|
pushdownBinaryOpFiltersInplace(lfs, t.Left)
|
|
pushdownBinaryOpFiltersInplace(lfs, t.Right)
|
|
}
|
|
}
|
|
|
|
func pushdownLabelFiltersForAllArgs(lfs []LabelFilter, args []Expr) {
|
|
for _, arg := range args {
|
|
pushdownBinaryOpFiltersInplace(lfs, arg)
|
|
}
|
|
}
|
|
|
|
func pushdownLabelFiltersForCountValuesOverTime(lfs []LabelFilter, args []Expr) {
|
|
if len(args) != 2 {
|
|
return
|
|
}
|
|
lfs = dropLabelFiltersForLabelName(lfs, args[0])
|
|
pushdownBinaryOpFiltersInplace(lfs, args[1])
|
|
}
|
|
|
|
func pushdownLabelFiltersForLabelKeep(lfs []LabelFilter, args []Expr) {
|
|
if len(args) == 0 {
|
|
return
|
|
}
|
|
lfs = keepLabelFiltersForLabelNames(lfs, args[1:])
|
|
pushdownBinaryOpFiltersInplace(lfs, args[0])
|
|
}
|
|
|
|
func pushdownLabelFiltersForLabelDel(lfs []LabelFilter, args []Expr) {
|
|
if len(args) == 0 {
|
|
return
|
|
}
|
|
lfs = dropLabelFiltersForLabelNames(lfs, args[1:])
|
|
pushdownBinaryOpFiltersInplace(lfs, args[0])
|
|
}
|
|
|
|
func pushdownLabelFiltersForLabelCopy(lfs []LabelFilter, args []Expr) {
|
|
if len(args) == 0 {
|
|
return
|
|
}
|
|
arg := args[0]
|
|
args = args[1:]
|
|
var labelNames []Expr
|
|
for i := 0; i < len(args); i += 2 {
|
|
if i+1 >= len(args) {
|
|
return
|
|
}
|
|
labelNames = append(labelNames, args[i+1])
|
|
}
|
|
lfs = dropLabelFiltersForLabelNames(lfs, labelNames)
|
|
pushdownBinaryOpFiltersInplace(lfs, arg)
|
|
}
|
|
|
|
func pushdownLabelFiltersForLabelReplace(lfs []LabelFilter, args []Expr) {
|
|
if len(args) < 2 {
|
|
return
|
|
}
|
|
lfs = dropLabelFiltersForLabelName(lfs, args[1])
|
|
pushdownBinaryOpFiltersInplace(lfs, args[0])
|
|
}
|
|
|
|
func pushdownLabelFiltersForLabelSet(lfs []LabelFilter, args []Expr) {
|
|
if len(args) == 0 {
|
|
return
|
|
}
|
|
arg := args[0]
|
|
args = args[1:]
|
|
var labelNames []Expr
|
|
for i := 0; i < len(args); i += 2 {
|
|
labelNames = append(labelNames, args[i])
|
|
}
|
|
lfs = dropLabelFiltersForLabelNames(lfs, labelNames)
|
|
pushdownBinaryOpFiltersInplace(lfs, arg)
|
|
}
|
|
|
|
func intersectLabelFilters(lfsA, lfsB []LabelFilter) []LabelFilter {
|
|
if len(lfsA) == 0 || len(lfsB) == 0 {
|
|
return nil
|
|
}
|
|
m := getLabelFiltersMap(lfsA)
|
|
var b []byte
|
|
var lfs []LabelFilter
|
|
for _, lf := range lfsB {
|
|
b = lf.AppendString(b[:0])
|
|
if _, ok := m[string(b)]; ok {
|
|
lfs = append(lfs, lf)
|
|
}
|
|
}
|
|
return lfs
|
|
}
|
|
|
|
func keepLabelFiltersForLabelNames(lfs []LabelFilter, labelNames []Expr) []LabelFilter {
|
|
m := make(map[string]struct{}, len(labelNames))
|
|
for _, labelName := range labelNames {
|
|
seLabelName, ok := labelName.(*StringExpr)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
m[seLabelName.S] = struct{}{}
|
|
}
|
|
|
|
var lfsDst []LabelFilter
|
|
for _, lf := range lfs {
|
|
if _, ok := m[lf.Label]; ok {
|
|
lfsDst = append(lfsDst, lf)
|
|
}
|
|
}
|
|
|
|
return lfsDst
|
|
}
|
|
|
|
func dropLabelFiltersForLabelNames(lfs []LabelFilter, labelNames []Expr) []LabelFilter {
|
|
for _, labelName := range labelNames {
|
|
lfs = dropLabelFiltersForLabelName(lfs, labelName)
|
|
}
|
|
return lfs
|
|
}
|
|
|
|
func dropLabelFiltersForLabelName(lfs []LabelFilter, labelName Expr) []LabelFilter {
|
|
seLabelName, ok := labelName.(*StringExpr)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
lfsDst := make([]LabelFilter, 0, len(lfs))
|
|
for _, lf := range lfs {
|
|
if lf.Label != seLabelName.S {
|
|
lfsDst = append(lfsDst, lf)
|
|
}
|
|
}
|
|
return lfsDst
|
|
}
|
|
|
|
func unionLabelFilters(lfsA, lfsB []LabelFilter) []LabelFilter {
|
|
if len(lfsA) == 0 {
|
|
return lfsB
|
|
}
|
|
if len(lfsB) == 0 {
|
|
return lfsA
|
|
}
|
|
m := getLabelFiltersMap(lfsA)
|
|
var b []byte
|
|
lfs := append([]LabelFilter{}, lfsA...)
|
|
for _, lf := range lfsB {
|
|
b = lf.AppendString(b[:0])
|
|
if _, ok := m[string(b)]; !ok {
|
|
lfs = append(lfs, lf)
|
|
}
|
|
}
|
|
return lfs
|
|
}
|
|
|
|
func getLabelFiltersMap(lfs []LabelFilter) map[string]struct{} {
|
|
m := make(map[string]struct{}, len(lfs))
|
|
var b []byte
|
|
for _, lf := range lfs {
|
|
b = lf.AppendString(b[:0])
|
|
m[string(b)] = struct{}{}
|
|
}
|
|
return m
|
|
}
|
|
|
|
func sortLabelFilters(lfs []LabelFilter) {
|
|
// Make sure the first label filter is __name__ (if any)
|
|
if len(lfs) > 0 && lfs[0].isMetricNameFilter() {
|
|
lfs = lfs[1:]
|
|
}
|
|
sort.Slice(lfs, func(i, j int) bool {
|
|
a, b := lfs[i], lfs[j]
|
|
if a.Label != b.Label {
|
|
return a.Label < b.Label
|
|
}
|
|
return a.Value < b.Value
|
|
})
|
|
}
|
|
|
|
func filterLabelFiltersOn(lfs []LabelFilter, args []string) []LabelFilter {
|
|
if len(args) == 0 {
|
|
return nil
|
|
}
|
|
m := make(map[string]struct{}, len(args))
|
|
for _, arg := range args {
|
|
m[arg] = struct{}{}
|
|
}
|
|
var lfsNew []LabelFilter
|
|
for _, lf := range lfs {
|
|
if _, ok := m[lf.Label]; ok {
|
|
lfsNew = append(lfsNew, lf)
|
|
}
|
|
}
|
|
return lfsNew
|
|
}
|
|
|
|
func filterLabelFiltersIgnoring(lfs []LabelFilter, args []string) []LabelFilter {
|
|
if len(args) == 0 {
|
|
return lfs
|
|
}
|
|
m := make(map[string]struct{}, len(args))
|
|
for _, arg := range args {
|
|
m[arg] = struct{}{}
|
|
}
|
|
var lfsNew []LabelFilter
|
|
for _, lf := range lfs {
|
|
if _, ok := m[lf.Label]; !ok {
|
|
lfsNew = append(lfsNew, lf)
|
|
}
|
|
}
|
|
return lfsNew
|
|
}
|
|
|
|
func getFuncArgForOptimization(funcName string, args []Expr) Expr {
|
|
idx := getFuncArgIdxForOptimization(funcName, args)
|
|
if idx < 0 || idx >= len(args) {
|
|
return nil
|
|
}
|
|
return args[idx]
|
|
}
|
|
|
|
func getFuncArgIdxForOptimization(funcName string, args []Expr) int {
|
|
funcName = strings.ToLower(funcName)
|
|
if IsRollupFunc(funcName) {
|
|
return getRollupArgIdxForOptimization(funcName, args)
|
|
}
|
|
if IsTransformFunc(funcName) {
|
|
return getTransformArgIdxForOptimization(funcName, args)
|
|
}
|
|
if IsAggrFunc(funcName) {
|
|
return getAggrArgIdxForOptimization(funcName, args)
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func getAggrArgIdxForOptimization(funcName string, args []Expr) int {
|
|
switch strings.ToLower(funcName) {
|
|
case "bottomk", "bottomk_avg", "bottomk_max", "bottomk_median", "bottomk_last", "bottomk_min",
|
|
"limitk", "outliers_mad", "outliersk", "quantile",
|
|
"topk", "topk_avg", "topk_max", "topk_median", "topk_last", "topk_min":
|
|
return 1
|
|
case "quantiles":
|
|
return len(args) - 1
|
|
case "count_values":
|
|
panic(fmt.Errorf("BUG: count_values must be already handled"))
|
|
default:
|
|
if canAcceptMultipleArgsForAggrFunc(funcName) {
|
|
panic(fmt.Errorf("BUG: %s must be already handled", funcName))
|
|
}
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func canAcceptMultipleArgsForAggrFunc(funcName string) bool {
|
|
switch strings.ToLower(funcName) {
|
|
case "any", "avg", "count", "distinct", "geomean", "group", "histogram", "mad", "max",
|
|
"median", "min", "mode", "share", "stddev", "stdvar", "sum", "sum2", "zscore":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func getRollupArgIdxForOptimization(funcName string, args []Expr) int {
|
|
// This must be kept in sync with GetRollupArgIdx()
|
|
switch strings.ToLower(funcName) {
|
|
case "count_values_over_time":
|
|
panic(fmt.Errorf("BUG: count_values_over_time must be already handled"))
|
|
case "absent_over_time":
|
|
return -1
|
|
case "quantile_over_time", "aggr_over_time",
|
|
"hoeffding_bound_lower", "hoeffding_bound_upper":
|
|
return 1
|
|
case "quantiles_over_time":
|
|
return len(args) - 1
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func getTransformArgIdxForOptimization(funcName string, args []Expr) int {
|
|
switch strings.ToLower(funcName) {
|
|
case "label_copy", "label_del", "label_join", "label_keep", "label_lowercase", "label_map",
|
|
"label_match", "label_mismatch", "label_move", "label_replace", "label_set", "label_transform",
|
|
"label_uppercase", "labels_equal", "range_normalize", "", "union":
|
|
panic(fmt.Errorf("BUG: %s must be already handled", funcName))
|
|
case "drop_common_labels":
|
|
return -1
|
|
case "absent", "scalar":
|
|
return -1
|
|
case "end", "now", "pi", "ru", "start", "step", "time":
|
|
return -1
|
|
case "limit_offset", "histogram_fraction":
|
|
return 2
|
|
case "buckets_limit", "histogram_quantile", "histogram_share", "range_quantile",
|
|
"range_trim_outliers", "range_trim_spikes", "range_trim_zscore":
|
|
return 1
|
|
case "histogram_quantiles":
|
|
return len(args) - 1
|
|
default:
|
|
return 0
|
|
}
|
|
}
|