-
Notifications
You must be signed in to change notification settings - Fork 5
/
promqlsmith.go
144 lines (125 loc) · 4.22 KB
/
promqlsmith.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package promqlsmith
import (
"math/rand"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/promql/parser"
)
type ExprType int
const (
VectorSelector ExprType = iota
MatrixSelector
AggregateExpr
BinaryExpr
SubQueryExpr
CallExpr
NumberLiteral
UnaryExpr
)
var (
valueTypeToExprsMap = map[parser.ValueType][]ExprType{
parser.ValueTypeVector: {VectorSelector, BinaryExpr, AggregateExpr, CallExpr, UnaryExpr},
parser.ValueTypeMatrix: {MatrixSelector, SubQueryExpr},
parser.ValueTypeScalar: {NumberLiteral, BinaryExpr, CallExpr, UnaryExpr},
}
vectorAndScalarValueTypes = []parser.ValueType{parser.ValueTypeVector, parser.ValueTypeScalar}
allValueTypes = []parser.ValueType{
parser.ValueTypeVector,
parser.ValueTypeScalar,
parser.ValueTypeMatrix,
parser.ValueTypeString,
}
)
type PromQLSmith struct {
rnd *rand.Rand
enableOffset bool
enableAtModifier bool
enableVectorMatching bool
enableExperimentalPromQL bool
atModifierMaxTimestamp int64
seriesSet []labels.Labels
labelNames []string
labelValues map[string][]string
enforceMatchers []*labels.Matcher
supportedExprs []ExprType
supportedAggrs []parser.ItemType
supportedFuncs []*parser.Function
supportedBinops []parser.ItemType
}
// New creates a PromQLsmith instance.
func New(rnd *rand.Rand, seriesSet []labels.Labels, opts ...Option) *PromQLSmith {
options := options{}
for _, o := range opts {
o.apply(&options)
}
options.applyDefaults()
ps := &PromQLSmith{
rnd: rnd,
seriesSet: filterEmptySeries(seriesSet),
supportedExprs: options.enabledExprs,
supportedAggrs: options.enabledAggrs,
supportedBinops: options.enabledBinops,
supportedFuncs: options.enabledFuncs,
enableOffset: options.enableOffset,
enableAtModifier: options.enableAtModifier,
atModifierMaxTimestamp: options.atModifierMaxTimestamp,
enableVectorMatching: options.enableVectorMatching,
enableExperimentalPromQL: options.enableExperimentalPromQLFunctions,
enforceMatchers: options.enforceLabelMatchers,
}
ps.labelNames, ps.labelValues = labelNameAndValuesFromLabelSet(seriesSet)
return ps
}
// WalkInstantQuery walks the ast and generate an expression that can be used in
// instant query. Instant query also supports string literal, but we skip it here.
func (s *PromQLSmith) WalkInstantQuery() parser.Expr {
return s.Walk(parser.ValueTypeVector, parser.ValueTypeScalar, parser.ValueTypeMatrix)
}
// WalkRangeQuery walks the ast and generate an expression that can be used in range query.
func (s *PromQLSmith) WalkRangeQuery() parser.Expr {
return s.Walk(vectorAndScalarValueTypes...)
}
// WalkSelectors generates random label matchers based on the input series labels.
func (s *PromQLSmith) WalkSelectors() []*labels.Matcher {
return s.walkSelectors()
}
// Walk will walk the ast tree using one of the randomly generated expr type.
func (s *PromQLSmith) Walk(valueTypes ...parser.ValueType) parser.Expr {
supportedExprs := s.supportedExprs
if len(valueTypes) > 0 {
supportedExprs = exprsFromValueTypes(valueTypes)
}
e := supportedExprs[s.rnd.Intn(len(supportedExprs))]
expr, _ := s.walkExpr(e, valueTypes...)
return expr
}
func filterEmptySeries(seriesSet []labels.Labels) []labels.Labels {
output := make([]labels.Labels, 0, len(seriesSet))
for _, lbls := range seriesSet {
if lbls.IsEmpty() {
continue
}
output = append(output, lbls)
}
return output
}
func labelNameAndValuesFromLabelSet(labelSet []labels.Labels) ([]string, map[string][]string) {
labelValueSet := make(map[string]map[string]struct{})
for _, lbls := range labelSet {
lbls.Range(func(lbl labels.Label) {
if _, ok := labelValueSet[lbl.Name]; !ok {
labelValueSet[lbl.Name] = make(map[string]struct{})
}
labelValueSet[lbl.Name][lbl.Value] = struct{}{}
})
}
labelNames := make([]string, 0, len(labelValueSet))
labelValues := make(map[string][]string)
for name, values := range labelValueSet {
labelNames = append(labelNames, name)
labelValues[name] = make([]string, 0, len(values))
for val := range values {
labelValues[name] = append(labelValues[name], val)
}
}
return labelNames, labelValues
}