package ast import ( "errors" "github.com/matttproud/prometheus/model" "log" "math" "strings" "time" ) // ---------------------------------------------------------------------------- // Raw data value types. type Vector []*model.Sample type Matrix []*model.SampleSet type groupedAggregation struct { labels model.Metric value model.SampleValue groupCount int } type labelValuePair struct { label model.LabelName value model.LabelValue } // ---------------------------------------------------------------------------- // Enums. // Rule language expression types. type ExprType int const ( SCALAR ExprType = iota VECTOR MATRIX STRING ) // Binary operator types. type BinOpType int const ( ADD BinOpType = iota SUB MUL DIV MOD NE EQ GT LT GE LE AND OR ) // Aggregation types. type AggrType int const ( SUM AggrType = iota AVG MIN MAX ) // ---------------------------------------------------------------------------- // Interfaces. // All node interfaces include the Node interface. type Node interface { Type() ExprType NodeTreeToDotGraph() string } // All node types implement one of the following interfaces. The name of the // interface represents the type returned to the parent node. type ScalarNode interface { Node Eval(timestamp *time.Time) model.SampleValue } type VectorNode interface { Node Eval(timestamp *time.Time) Vector } type MatrixNode interface { Node Eval(timestamp *time.Time) Matrix EvalBoundaries(timestamp *time.Time) Matrix } type StringNode interface { Node Eval(timestamp *time.Time) string } // ---------------------------------------------------------------------------- // ScalarNode types. type ( // A numeric literal. ScalarLiteral struct { value model.SampleValue } // A function of numeric return type. ScalarFunctionCall struct { function *Function args []Node } // An arithmetic expression of numeric type. ScalarArithExpr struct { opType BinOpType lhs ScalarNode rhs ScalarNode } ) // ---------------------------------------------------------------------------- // VectorNode types. type ( // Vector literal, i.e. metric name plus labelset. VectorLiteral struct { labels model.LabelSet } // A function of vector return type. VectorFunctionCall struct { function *Function args []Node } // A vector aggregation with vector return type. VectorAggregation struct { aggrType AggrType groupBy []model.LabelName vector VectorNode } // An arithmetic expression of vector type. VectorArithExpr struct { opType BinOpType lhs VectorNode rhs Node } ) // ---------------------------------------------------------------------------- // MatrixNode types. type ( // Matrix literal, i.e. metric name plus labelset and timerange. MatrixLiteral struct { labels model.LabelSet interval time.Duration } ) // ---------------------------------------------------------------------------- // StringNode types. type ( // String literal. StringLiteral struct { str string } // A function of string return type. StringFunctionCall struct { function *Function args []Node } ) // ---------------------------------------------------------------------------- // Implementations. func (node ScalarLiteral) Type() ExprType { return SCALAR } func (node ScalarFunctionCall) Type() ExprType { return SCALAR } func (node ScalarArithExpr) Type() ExprType { return SCALAR } func (node VectorLiteral) Type() ExprType { return VECTOR } func (node VectorFunctionCall) Type() ExprType { return VECTOR } func (node VectorAggregation) Type() ExprType { return VECTOR } func (node VectorArithExpr) Type() ExprType { return VECTOR } func (node MatrixLiteral) Type() ExprType { return MATRIX } func (node StringLiteral) Type() ExprType { return STRING } func (node StringFunctionCall) Type() ExprType { return STRING } func (node *ScalarLiteral) Eval(timestamp *time.Time) model.SampleValue { return node.value } func (node *ScalarArithExpr) Eval(timestamp *time.Time) model.SampleValue { lhs := node.lhs.Eval(timestamp) rhs := node.rhs.Eval(timestamp) return evalScalarBinop(node.opType, lhs, rhs) } func (node *ScalarFunctionCall) Eval(timestamp *time.Time) model.SampleValue { return node.function.callFn(timestamp, node.args).(model.SampleValue) } func (node *VectorAggregation) labelsToGroupingKey(labels model.Metric) string { keyParts := []string{} for _, keyLabel := range node.groupBy { keyParts = append(keyParts, string(labels[keyLabel])) } return strings.Join(keyParts, ",") // TODO not safe when label value contains comma. } func labelIntersection(metric1, metric2 model.Metric) model.Metric { intersection := model.Metric{} for label, value := range metric1 { if metric2[label] == value { intersection[label] = value } } return intersection } func (node *VectorAggregation) groupedAggregationsToVector(aggregations map[string]*groupedAggregation, timestamp *time.Time) Vector { vector := Vector{} for _, aggregation := range aggregations { if node.aggrType == AVG { aggregation.value = aggregation.value / model.SampleValue(aggregation.groupCount) } sample := &model.Sample{ Metric: aggregation.labels, Value: aggregation.value, Timestamp: *timestamp, } vector = append(vector, sample) } return vector } func (node *VectorAggregation) Eval(timestamp *time.Time) Vector { vector := node.vector.Eval(timestamp) result := map[string]*groupedAggregation{} for _, sample := range vector { groupingKey := node.labelsToGroupingKey(sample.Metric) if groupedResult, ok := result[groupingKey]; ok { groupedResult.labels = labelIntersection(groupedResult.labels, sample.Metric) switch node.aggrType { case SUM: groupedResult.value += sample.Value case AVG: groupedResult.value += sample.Value groupedResult.groupCount++ case MAX: if groupedResult.value < sample.Value { groupedResult.value = sample.Value } case MIN: if groupedResult.value > sample.Value { groupedResult.value = sample.Value } } } else { result[groupingKey] = &groupedAggregation{ labels: sample.Metric, value: sample.Value, groupCount: 1, } } } return node.groupedAggregationsToVector(result, timestamp) } func (node *VectorLiteral) Eval(timestamp *time.Time) Vector { values, err := persistence.GetValueAtTime(node.labels, timestamp, &stalenessPolicy) if err != nil { log.Printf("Unable to get vector values") return Vector{} } return values } func (node *VectorFunctionCall) Eval(timestamp *time.Time) Vector { return node.function.callFn(timestamp, node.args).(Vector) } func evalScalarBinop(opType BinOpType, lhs model.SampleValue, rhs model.SampleValue) model.SampleValue { switch opType { case ADD: return lhs + rhs case SUB: return lhs - rhs case MUL: return lhs * rhs case DIV: if rhs != 0 { return lhs / rhs } else { return model.SampleValue(math.Inf(int(rhs))) } case MOD: if rhs != 0 { return model.SampleValue(int(lhs) % int(rhs)) } else { return model.SampleValue(math.Inf(int(rhs))) } case EQ: if lhs == rhs { return 1 } else { return 0 } case NE: if lhs != rhs { return 1 } else { return 0 } case GT: if lhs > rhs { return 1 } else { return 0 } case LT: if lhs < rhs { return 1 } else { return 0 } case GE: if lhs >= rhs { return 1 } else { return 0 } case LE: if lhs <= rhs { return 1 } else { return 0 } } panic("Not all enum values enumerated in switch") } func evalVectorBinop(opType BinOpType, lhs model.SampleValue, rhs model.SampleValue) (model.SampleValue, bool) { switch opType { case ADD: return lhs + rhs, true case SUB: return lhs - rhs, true case MUL: return lhs * rhs, true case DIV: if rhs != 0 { return lhs / rhs, true } else { return model.SampleValue(math.Inf(int(rhs))), true } case MOD: if rhs != 0 { return model.SampleValue(int(lhs) % int(rhs)), true } else { return model.SampleValue(math.Inf(int(rhs))), true } case EQ: if lhs == rhs { return lhs, true } else { return 0, false } case NE: if lhs != rhs { return lhs, true } else { return 0, false } case GT: if lhs > rhs { return lhs, true } else { return 0, false } case LT: if lhs < rhs { return lhs, true } else { return 0, false } case GE: if lhs >= rhs { return lhs, true } else { return 0, false } case LE: if lhs <= rhs { return lhs, true } else { return 0, false } case AND: return lhs, true } panic("Not all enum values enumerated in switch") } func labelsEqual(labels1, labels2 model.Metric) bool { if len(labels1) != len(labels2) { return false } for label, value := range labels1 { if labels2[label] != value && label != "name" { return false } } return true } func (node *VectorArithExpr) Eval(timestamp *time.Time) Vector { lhs := node.lhs.Eval(timestamp) result := Vector{} if node.rhs.Type() == SCALAR { rhs := node.rhs.(ScalarNode).Eval(timestamp) for _, lhsSample := range lhs { value, keep := evalVectorBinop(node.opType, lhsSample.Value, rhs) if keep { lhsSample.Value = value result = append(result, lhsSample) } } return result } else if node.rhs.Type() == VECTOR { rhs := node.rhs.(VectorNode).Eval(timestamp) for _, lhsSample := range lhs { for _, rhsSample := range rhs { if labelsEqual(lhsSample.Metric, rhsSample.Metric) { value, keep := evalVectorBinop(node.opType, lhsSample.Value, rhsSample.Value) if keep { lhsSample.Value = value result = append(result, lhsSample) } } } } return result } panic("Invalid vector arithmetic expression operands") } func (node *MatrixLiteral) Eval(timestamp *time.Time) Matrix { values, err := persistence.GetRangeValues(node.labels, &model.Interval{}, &stalenessPolicy) if err != nil { log.Printf("Unable to get values for vector interval") return Matrix{} } return values } func (node *MatrixLiteral) EvalBoundaries(timestamp *time.Time) Matrix { interval := &model.Interval{ OldestInclusive: timestamp.Add(-node.interval), NewestInclusive: *timestamp, } values, err := persistence.GetBoundaryValues(node.labels, interval, &stalenessPolicy) if err != nil { log.Printf("Unable to get boundary values for vector interval") return Matrix{} } return values } func (node *StringLiteral) Eval(timestamp *time.Time) string { return node.str } func (node *StringFunctionCall) Eval(timestamp *time.Time) string { return node.function.callFn(timestamp, node.args).(string) } // ---------------------------------------------------------------------------- // Constructors. func NewScalarLiteral(value model.SampleValue) *ScalarLiteral { return &ScalarLiteral{ value: value, } } func NewVectorLiteral(labels model.LabelSet) *VectorLiteral { return &VectorLiteral{ labels: labels, } } func NewVectorAggregation(aggrType AggrType, vector VectorNode, groupBy []model.LabelName) *VectorAggregation { return &VectorAggregation{ aggrType: aggrType, groupBy: groupBy, vector: vector, } } func NewFunctionCall(function *Function, args []Node) (Node, error) { if err := function.CheckArgTypes(args); err != nil { return nil, err } switch function.returnType { case SCALAR: return &ScalarFunctionCall{ function: function, args: args, }, nil case VECTOR: return &VectorFunctionCall{ function: function, args: args, }, nil case STRING: return &StringFunctionCall{ function: function, args: args, }, nil } panic("Function with invalid return type") } func nodesHaveTypes(nodes []Node, exprTypes []ExprType) bool { for _, node := range nodes { for _, exprType := range exprTypes { if node.Type() == exprType { return true } } } return false } func NewArithExpr(opType BinOpType, lhs Node, rhs Node) (Node, error) { if !nodesHaveTypes([]Node{lhs, rhs}, []ExprType{SCALAR, VECTOR}) { return nil, errors.New("Binary operands must be of vector or scalar type") } if lhs.Type() == SCALAR && rhs.Type() == VECTOR { return nil, errors.New("Left side of vector binary operation must be of vector type") } if opType == AND || opType == OR { if lhs.Type() == SCALAR || rhs.Type() == SCALAR { return nil, errors.New("AND and OR operators may only be used between vectors") } } if lhs.Type() == VECTOR || rhs.Type() == VECTOR { return &VectorArithExpr{ opType: opType, lhs: lhs.(VectorNode), rhs: rhs, }, nil } return &ScalarArithExpr{ opType: opType, lhs: lhs.(ScalarNode), rhs: rhs.(ScalarNode), }, nil } func NewMatrixLiteral(vector *VectorLiteral, interval time.Duration) *MatrixLiteral { return &MatrixLiteral{ labels: vector.labels, interval: interval, } } func NewStringLiteral(str string) *StringLiteral { return &StringLiteral{ str: str, } }