496 lines
9.8 KiB
Go
496 lines
9.8 KiB
Go
package promql
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/influxdata/flux"
|
|
"github.com/influxdata/flux/ast"
|
|
"github.com/influxdata/flux/interpreter"
|
|
"github.com/influxdata/flux/semantic"
|
|
"github.com/influxdata/flux/stdlib/universe"
|
|
"github.com/influxdata/influxdb/query/stdlib/influxdata/influxdb"
|
|
)
|
|
|
|
type ArgKind int
|
|
|
|
const (
|
|
IdentifierKind ArgKind = iota
|
|
DurationKind
|
|
ExprKind
|
|
NumberKind
|
|
StringKind
|
|
SelectorKind
|
|
)
|
|
|
|
type QueryBuilder interface {
|
|
QuerySpec() (*flux.Spec, error)
|
|
}
|
|
|
|
type Arg interface {
|
|
Type() ArgKind
|
|
Value() interface{}
|
|
}
|
|
|
|
type Identifier struct {
|
|
Name string `json:"name,omitempty"`
|
|
}
|
|
|
|
func (id *Identifier) Type() ArgKind {
|
|
return IdentifierKind
|
|
}
|
|
|
|
func (id *Identifier) Value() interface{} {
|
|
return id.Name
|
|
}
|
|
|
|
func NewIdentifierList(first *Identifier, rest interface{}) ([]*Identifier, error) {
|
|
ids := []*Identifier{first}
|
|
for _, l := range toIfaceSlice(rest) {
|
|
if id, ok := l.(*Identifier); ok {
|
|
ids = append(ids, id)
|
|
}
|
|
}
|
|
return ids, nil
|
|
}
|
|
|
|
type StringLiteral struct {
|
|
String string `json:"string,omitempty"`
|
|
}
|
|
|
|
func (s *StringLiteral) Type() ArgKind {
|
|
return StringKind
|
|
}
|
|
|
|
func (s *StringLiteral) Value() interface{} {
|
|
return s.String
|
|
}
|
|
|
|
type Duration struct {
|
|
Dur time.Duration `json:"dur,omitempty"`
|
|
}
|
|
|
|
func (d *Duration) Type() ArgKind {
|
|
return DurationKind
|
|
}
|
|
|
|
func (d *Duration) Value() interface{} {
|
|
return d.Dur
|
|
}
|
|
|
|
type Number struct {
|
|
Val float64 `json:"val,omitempty"`
|
|
}
|
|
|
|
func (n *Number) Type() ArgKind {
|
|
return NumberKind
|
|
}
|
|
|
|
func (n *Number) Value() interface{} {
|
|
return n.Val
|
|
}
|
|
|
|
func NewNumber(val string) (*Number, error) {
|
|
num, err := strconv.ParseFloat(val, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Number{num}, nil
|
|
}
|
|
|
|
// MatchKind is an enum for label matching types.
|
|
type MatchKind int
|
|
|
|
// Possible MatchKinds.
|
|
const (
|
|
Equal MatchKind = iota
|
|
NotEqual
|
|
RegexMatch
|
|
RegexNoMatch
|
|
)
|
|
|
|
type LabelMatcher struct {
|
|
Name string `json:"name,omitempty"`
|
|
Kind MatchKind `json:"kind,omitempty"`
|
|
Value Arg `json:"value,omitempty"`
|
|
}
|
|
|
|
func NewLabelMatcher(ident *Identifier, kind MatchKind, value Arg) (*LabelMatcher, error) {
|
|
return &LabelMatcher{
|
|
Name: ident.Name,
|
|
Kind: kind,
|
|
Value: value,
|
|
}, nil
|
|
}
|
|
|
|
func NewLabelMatches(first *LabelMatcher, rest interface{}) ([]*LabelMatcher, error) {
|
|
matches := []*LabelMatcher{first}
|
|
for _, m := range toIfaceSlice(rest) {
|
|
if match, ok := m.(*LabelMatcher); ok {
|
|
matches = append(matches, match)
|
|
}
|
|
}
|
|
return matches, nil
|
|
}
|
|
|
|
type Selector struct {
|
|
Name string `json:"name,omitempty"`
|
|
Range time.Duration `json:"range,omitempty"`
|
|
Offset time.Duration `json:"offset,omitempty"`
|
|
LabelMatchers []*LabelMatcher `json:"label_matchers,omitempty"`
|
|
}
|
|
|
|
func (s *Selector) QuerySpec() (*flux.Spec, error) {
|
|
parent := "from"
|
|
ops := []*flux.Operation{
|
|
{
|
|
ID: "from", // TODO: Change this to a UUID
|
|
Spec: &influxdb.FromOpSpec{
|
|
Bucket: "prometheus",
|
|
},
|
|
},
|
|
}
|
|
edges := []flux.Edge{}
|
|
|
|
rng, err := NewRangeOp(s.Range, s.Offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if rng != nil {
|
|
ops = append(ops, rng)
|
|
edge := flux.Edge{
|
|
Parent: flux.OperationID(parent),
|
|
Child: "range",
|
|
}
|
|
parent = "range"
|
|
edges = append(edges, edge)
|
|
}
|
|
|
|
where, err := NewWhereOperation(s.Name, s.LabelMatchers)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ops = append(ops, where)
|
|
edge := flux.Edge{
|
|
Parent: flux.OperationID(parent),
|
|
Child: "where",
|
|
}
|
|
parent = "where"
|
|
edges = append(edges, edge)
|
|
|
|
return &flux.Spec{
|
|
Operations: ops,
|
|
Edges: edges,
|
|
}, nil
|
|
}
|
|
|
|
func NewRangeOp(rng, offset time.Duration) (*flux.Operation, error) {
|
|
if rng == 0 && offset == 0 {
|
|
return nil, nil
|
|
}
|
|
return &flux.Operation{
|
|
ID: "range", // TODO: Change this to a UUID
|
|
Spec: &universe.RangeOpSpec{
|
|
Start: flux.Time{
|
|
Relative: -rng - offset,
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
var operatorLookup = map[MatchKind]ast.OperatorKind{
|
|
Equal: ast.EqualOperator,
|
|
NotEqual: ast.NotEqualOperator,
|
|
RegexMatch: ast.EqualOperator,
|
|
RegexNoMatch: ast.NotEqualOperator,
|
|
}
|
|
|
|
func NewWhereOperation(metricName string, labels []*LabelMatcher) (*flux.Operation, error) {
|
|
var node semantic.Expression = &semantic.BinaryExpression{
|
|
Operator: ast.EqualOperator,
|
|
Left: &semantic.MemberExpression{
|
|
Object: &semantic.IdentifierExpression{
|
|
Name: "r",
|
|
},
|
|
Property: "_metric",
|
|
},
|
|
Right: &semantic.StringLiteral{
|
|
Value: metricName,
|
|
},
|
|
}
|
|
for _, label := range labels {
|
|
op, ok := operatorLookup[label.Kind]
|
|
if !ok {
|
|
return nil, fmt.Errorf("unknown label match kind %d", label.Kind)
|
|
}
|
|
ref := &semantic.MemberExpression{
|
|
Object: &semantic.IdentifierExpression{
|
|
Name: "r",
|
|
},
|
|
Property: label.Name,
|
|
}
|
|
var value semantic.Expression
|
|
if label.Value.Type() == StringKind {
|
|
value = &semantic.StringLiteral{
|
|
Value: label.Value.Value().(string),
|
|
}
|
|
} else if label.Value.Type() == NumberKind {
|
|
value = &semantic.FloatLiteral{
|
|
Value: label.Value.Value().(float64),
|
|
}
|
|
}
|
|
node = &semantic.LogicalExpression{
|
|
Operator: ast.AndOperator,
|
|
Left: node,
|
|
Right: &semantic.BinaryExpression{
|
|
Operator: op,
|
|
Left: ref,
|
|
Right: value,
|
|
},
|
|
}
|
|
}
|
|
|
|
return &flux.Operation{
|
|
ID: "where", // TODO: Change this to a UUID
|
|
Spec: &universe.FilterOpSpec{
|
|
Fn: interpreter.ResolvedFunction{
|
|
Scope: nil,
|
|
Fn: &semantic.FunctionExpression{
|
|
Block: &semantic.FunctionBlock{
|
|
Parameters: &semantic.FunctionParameters{
|
|
List: []*semantic.FunctionParameter{{Key: &semantic.Identifier{Name: "r"}}},
|
|
},
|
|
Body: node,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (s *Selector) Type() ArgKind {
|
|
return SelectorKind
|
|
}
|
|
|
|
func (s *Selector) Value() interface{} {
|
|
return s.Name // TODO: Change to AST
|
|
}
|
|
|
|
func NewSelector(metric *Identifier, block, rng, offset interface{}) (*Selector, error) {
|
|
sel := &Selector{
|
|
Name: metric.Name,
|
|
}
|
|
|
|
if block != nil {
|
|
sel.LabelMatchers = block.([]*LabelMatcher)
|
|
}
|
|
|
|
if rng != nil {
|
|
sel.Range = rng.(time.Duration)
|
|
}
|
|
|
|
if offset != nil {
|
|
sel.Offset = offset.(time.Duration)
|
|
}
|
|
|
|
return sel, nil
|
|
}
|
|
|
|
type Aggregate struct {
|
|
Without bool `json:"without,omitempty"`
|
|
By bool `json:"by,omitempty"`
|
|
Labels []*Identifier `json:"labels,omitempty"`
|
|
}
|
|
|
|
func (a *Aggregate) QuerySpec() (*flux.Operation, error) {
|
|
if a.Without {
|
|
return nil, fmt.Errorf("unable to merge using `without`")
|
|
}
|
|
keys := make([]string, len(a.Labels))
|
|
for i := range a.Labels {
|
|
keys[i] = a.Labels[i].Name
|
|
}
|
|
return &flux.Operation{
|
|
ID: "merge",
|
|
Spec: &universe.GroupOpSpec{
|
|
Columns: keys,
|
|
Mode: "by",
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
type OperatorKind int
|
|
|
|
const (
|
|
UnknownOpKind OperatorKind = iota
|
|
CountValuesKind
|
|
TopKind
|
|
BottomKind
|
|
QuantileKind
|
|
SumKind
|
|
MinKind
|
|
MaxKind
|
|
AvgKind
|
|
StdevKind
|
|
StdVarKind
|
|
CountKind
|
|
)
|
|
|
|
func ToOperatorKind(op string) OperatorKind {
|
|
op = strings.ToLower(op)
|
|
switch op {
|
|
case "count_values":
|
|
return CountValuesKind
|
|
case "topk":
|
|
return TopKind
|
|
case "bottomk":
|
|
return BottomKind
|
|
case "quantile":
|
|
return QuantileKind
|
|
case "sum":
|
|
return SumKind
|
|
case "min":
|
|
return MinKind
|
|
case "max":
|
|
return MaxKind
|
|
case "avg":
|
|
return AvgKind
|
|
case "stddev":
|
|
return StdevKind
|
|
case "stdvar":
|
|
return StdVarKind
|
|
case "count":
|
|
return CountKind
|
|
default:
|
|
return UnknownOpKind
|
|
}
|
|
}
|
|
|
|
type Operator struct {
|
|
Kind OperatorKind `json:"kind,omitempty"`
|
|
Arg Arg `json:"arg,omitempty"`
|
|
}
|
|
|
|
func (o *Operator) QuerySpec() (*flux.Operation, error) {
|
|
switch o.Kind {
|
|
case CountValuesKind, BottomKind, QuantileKind, StdVarKind:
|
|
return nil, fmt.Errorf("unable to run %d yet", o.Kind)
|
|
case CountKind:
|
|
return &flux.Operation{
|
|
ID: "count",
|
|
Spec: &universe.CountOpSpec{},
|
|
}, nil
|
|
//case TopKind:
|
|
// return &flux.Operation{
|
|
// ID: "top",
|
|
// Spec: &universe.TopOpSpec{}, // TODO: Top doesn't have arg yet
|
|
// }, nil
|
|
case SumKind:
|
|
return &flux.Operation{
|
|
ID: "sum",
|
|
Spec: &universe.SumOpSpec{},
|
|
}, nil
|
|
//case MinKind:
|
|
// return &flux.Operation{
|
|
// ID: "min",
|
|
// Spec: &universe.MinOpSpec{},
|
|
// }, nil
|
|
//case MaxKind:
|
|
// return &flux.Operation{
|
|
// ID: "max",
|
|
// Spec: &universe.MaxOpSpec{},
|
|
// }, nil
|
|
//case AvgKind:
|
|
// return &flux.Operation{
|
|
// ID: "mean",
|
|
// Spec: &universe.MeanOpSpec{},
|
|
// }, nil
|
|
//case StdevKind:
|
|
// return &flux.Operation{
|
|
// ID: "stddev",
|
|
// Spec: &universe.StddevOpSpec{},
|
|
// }, nil
|
|
default:
|
|
return nil, fmt.Errorf("unknown Op kind %d", o.Kind)
|
|
}
|
|
}
|
|
|
|
type AggregateExpr struct {
|
|
Op *Operator `json:"op,omitempty"`
|
|
Selector *Selector `json:"selector,omitempty"`
|
|
Aggregate *Aggregate `json:"aggregate,omitempty"`
|
|
}
|
|
|
|
func (a *AggregateExpr) QuerySpec() (*flux.Spec, error) {
|
|
spec, err := a.Selector.QuerySpec()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if a.Aggregate != nil {
|
|
agg, err := a.Aggregate.QuerySpec()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
parent := flux.OperationID("from")
|
|
if len(spec.Edges) > 0 {
|
|
tail := spec.Edges[len(spec.Edges)-1]
|
|
parent = tail.Child
|
|
}
|
|
|
|
spec.Operations = append(spec.Operations, agg)
|
|
spec.Edges = append(spec.Edges, flux.Edge{
|
|
Parent: parent,
|
|
Child: agg.ID,
|
|
})
|
|
}
|
|
|
|
op, err := a.Op.QuerySpec()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
parent := flux.OperationID("from")
|
|
if len(spec.Edges) > 0 {
|
|
tail := spec.Edges[len(spec.Edges)-1]
|
|
parent = tail.Child
|
|
}
|
|
spec.Operations = append(spec.Operations, op)
|
|
spec.Edges = append(spec.Edges, flux.Edge{
|
|
Parent: parent,
|
|
Child: op.ID,
|
|
})
|
|
return spec, nil
|
|
}
|
|
|
|
func NewAggregateExpr(op *Operator, selector *Selector, group interface{}) (*AggregateExpr, error) {
|
|
expr := &AggregateExpr{
|
|
Op: op,
|
|
Selector: selector,
|
|
}
|
|
if group != nil {
|
|
expr.Aggregate = group.(*Aggregate)
|
|
}
|
|
return expr, nil
|
|
}
|
|
|
|
type Comment struct {
|
|
Source string `json:"source,omitempty"`
|
|
}
|
|
|
|
func (c *Comment) QuerySpec() (*flux.Spec, error) {
|
|
return nil, fmt.Errorf("unable to represent comments in the AST")
|
|
}
|
|
|
|
func toIfaceSlice(v interface{}) []interface{} {
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
return v.([]interface{})
|
|
}
|