mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-25 13:44:05 -08:00
Merge pull request #655 from prometheus/fabxc/promql_3
Switch Prometheus to promql package.
This commit is contained in:
commit
95fbe51c50
36
main.go
36
main.go
|
@ -32,8 +32,9 @@ import (
|
|||
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/notification"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
"github.com/prometheus/prometheus/retrieval"
|
||||
"github.com/prometheus/prometheus/rules/manager"
|
||||
"github.com/prometheus/prometheus/rules"
|
||||
"github.com/prometheus/prometheus/storage"
|
||||
"github.com/prometheus/prometheus/storage/local"
|
||||
"github.com/prometheus/prometheus/storage/remote"
|
||||
|
@ -76,7 +77,8 @@ var (
|
|||
)
|
||||
|
||||
type prometheus struct {
|
||||
ruleManager manager.RuleManager
|
||||
queryEngine *promql.Engine
|
||||
ruleManager rules.RuleManager
|
||||
targetManager retrieval.TargetManager
|
||||
notificationHandler *notification.NotificationHandler
|
||||
storage local.Storage
|
||||
|
@ -155,17 +157,26 @@ func NewPrometheus() *prometheus {
|
|||
targetManager := retrieval.NewTargetManager(sampleAppender, conf.GlobalLabels())
|
||||
targetManager.AddTargetsFromConfig(conf)
|
||||
|
||||
ruleManager := manager.NewRuleManager(&manager.RuleManagerOptions{
|
||||
queryEngine := promql.NewEngine(memStorage)
|
||||
|
||||
ruleManager := rules.NewRuleManager(&rules.RuleManagerOptions{
|
||||
SampleAppender: sampleAppender,
|
||||
NotificationHandler: notificationHandler,
|
||||
EvaluationInterval: conf.EvaluationInterval(),
|
||||
Storage: memStorage,
|
||||
QueryEngine: queryEngine,
|
||||
PrometheusURL: web.MustBuildServerURL(*pathPrefix),
|
||||
PathPrefix: *pathPrefix,
|
||||
})
|
||||
if err := ruleManager.AddRulesFromConfig(conf); err != nil {
|
||||
glog.Error("Error loading rule files: ", err)
|
||||
os.Exit(1)
|
||||
for _, rf := range conf.Global.GetRuleFile() {
|
||||
query, err := queryEngine.NewQueryFromFile(rf)
|
||||
if err != nil {
|
||||
glog.Errorf("Error loading rule file %q: %s", rf, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if res := query.Exec(); res.Err != nil {
|
||||
glog.Errorf("Error initializing rules: %s", res.Err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
flags := map[string]string{}
|
||||
|
@ -188,8 +199,8 @@ func NewPrometheus() *prometheus {
|
|||
}
|
||||
|
||||
consolesHandler := &web.ConsolesHandler{
|
||||
Storage: memStorage,
|
||||
PathPrefix: *pathPrefix,
|
||||
QueryEngine: queryEngine,
|
||||
PathPrefix: *pathPrefix,
|
||||
}
|
||||
|
||||
graphsHandler := &web.GraphsHandler{
|
||||
|
@ -197,8 +208,9 @@ func NewPrometheus() *prometheus {
|
|||
}
|
||||
|
||||
metricsService := &api.MetricsService{
|
||||
Now: clientmodel.Now,
|
||||
Storage: memStorage,
|
||||
Now: clientmodel.Now,
|
||||
Storage: memStorage,
|
||||
QueryEngine: queryEngine,
|
||||
}
|
||||
|
||||
webService := &web.WebService{
|
||||
|
@ -210,6 +222,7 @@ func NewPrometheus() *prometheus {
|
|||
}
|
||||
|
||||
p := &prometheus{
|
||||
queryEngine: queryEngine,
|
||||
ruleManager: ruleManager,
|
||||
targetManager: targetManager,
|
||||
notificationHandler: notificationHandler,
|
||||
|
@ -252,6 +265,7 @@ func (p *prometheus) Serve() {
|
|||
|
||||
p.targetManager.Stop()
|
||||
p.ruleManager.Stop()
|
||||
p.queryEngine.Stop()
|
||||
|
||||
if err := p.storage.Stop(); err != nil {
|
||||
glog.Error("Error stopping local storage: ", err)
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
# Copyright 2013 The Prometheus Authors
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
all: parser.y.go lexer.l.go
|
||||
|
||||
include ../Makefile.INCLUDE
|
||||
|
||||
parser.y.go: parser.y
|
||||
$(GOCC) tool yacc -o parser.y.go -v "" parser.y
|
||||
|
||||
lexer.l.go: parser.y.go lexer.l
|
||||
# This is golex from https://github.com/cznic/golex.
|
||||
$(GO_GET) github.com/cznic/golex
|
||||
golex -o="lexer.l.go" lexer.l
|
||||
|
||||
clean:
|
||||
rm lexer.l.go parser.y.go
|
|
@ -22,9 +22,7 @@ import (
|
|||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
|
||||
"github.com/prometheus/prometheus/rules/ast"
|
||||
"github.com/prometheus/prometheus/stats"
|
||||
"github.com/prometheus/prometheus/storage/local"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
"github.com/prometheus/prometheus/utility"
|
||||
)
|
||||
|
||||
|
@ -80,7 +78,7 @@ type Alert struct {
|
|||
}
|
||||
|
||||
// sample returns a Sample suitable for recording the alert.
|
||||
func (a Alert) sample(timestamp clientmodel.Timestamp, value clientmodel.SampleValue) *ast.Sample {
|
||||
func (a Alert) sample(timestamp clientmodel.Timestamp, value clientmodel.SampleValue) *promql.Sample {
|
||||
recordedMetric := clientmodel.Metric{}
|
||||
for label, value := range a.Labels {
|
||||
recordedMetric[label] = value
|
||||
|
@ -90,7 +88,7 @@ func (a Alert) sample(timestamp clientmodel.Timestamp, value clientmodel.SampleV
|
|||
recordedMetric[AlertNameLabel] = clientmodel.LabelValue(a.Name)
|
||||
recordedMetric[AlertStateLabel] = clientmodel.LabelValue(a.State.String())
|
||||
|
||||
return &ast.Sample{
|
||||
return &promql.Sample{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: recordedMetric,
|
||||
Copied: true,
|
||||
|
@ -105,7 +103,7 @@ type AlertingRule struct {
|
|||
// The name of the alert.
|
||||
name string
|
||||
// The vector expression from which to generate alerts.
|
||||
Vector ast.VectorNode
|
||||
Vector promql.Expr
|
||||
// The duration for which a labelset needs to persist in the expression
|
||||
// output vector before an alert transitions from Pending to Firing state.
|
||||
holdDuration time.Duration
|
||||
|
@ -129,14 +127,18 @@ func (rule *AlertingRule) Name() string {
|
|||
}
|
||||
|
||||
// EvalRaw returns the raw value of the rule expression, without creating alerts.
|
||||
func (rule *AlertingRule) EvalRaw(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error) {
|
||||
return ast.EvalVectorInstant(rule.Vector, timestamp, storage, stats.NewTimerGroup())
|
||||
func (rule *AlertingRule) EvalRaw(timestamp clientmodel.Timestamp, engine *promql.Engine) (promql.Vector, error) {
|
||||
query, err := engine.NewInstantQuery(rule.Vector.String(), timestamp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return query.Exec().Vector()
|
||||
}
|
||||
|
||||
// Eval evaluates the rule expression and then creates pending alerts and fires
|
||||
// or removes previously pending alerts accordingly.
|
||||
func (rule *AlertingRule) Eval(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error) {
|
||||
exprResult, err := rule.EvalRaw(timestamp, storage)
|
||||
func (rule *AlertingRule) Eval(timestamp clientmodel.Timestamp, engine *promql.Engine) (promql.Vector, error) {
|
||||
exprResult, err := rule.EvalRaw(timestamp, engine)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -170,7 +172,7 @@ func (rule *AlertingRule) Eval(timestamp clientmodel.Timestamp, storage local.St
|
|||
}
|
||||
}
|
||||
|
||||
vector := ast.Vector{}
|
||||
vector := promql.Vector{}
|
||||
|
||||
// Check if any pending alerts should be removed or fire now. Write out alert timeseries.
|
||||
for fp, activeAlert := range rule.activeAlerts {
|
||||
|
@ -191,8 +193,8 @@ func (rule *AlertingRule) Eval(timestamp clientmodel.Timestamp, storage local.St
|
|||
return vector, nil
|
||||
}
|
||||
|
||||
// ToDotGraph returns the text representation of a dot graph.
|
||||
func (rule *AlertingRule) ToDotGraph() string {
|
||||
// DotGraph returns the text representation of a dot graph.
|
||||
func (rule *AlertingRule) DotGraph() string {
|
||||
graph := fmt.Sprintf(
|
||||
`digraph "Rules" {
|
||||
%#p[shape="box",label="ALERT %s IF FOR %s"];
|
||||
|
@ -201,7 +203,8 @@ func (rule *AlertingRule) ToDotGraph() string {
|
|||
}`,
|
||||
&rule, rule.name, utility.DurationToString(rule.holdDuration),
|
||||
&rule, reflect.ValueOf(rule.Vector).Pointer(),
|
||||
rule.Vector.NodeTreeToDotGraph())
|
||||
rule.Vector.DotGraph(),
|
||||
)
|
||||
return graph
|
||||
}
|
||||
|
||||
|
@ -217,9 +220,9 @@ func (rule *AlertingRule) HTMLSnippet() template.HTML {
|
|||
}
|
||||
return template.HTML(fmt.Sprintf(
|
||||
`ALERT <a href="%s">%s</a> IF <a href="%s">%s</a> FOR %s WITH %s`,
|
||||
GraphLinkForExpression(alertMetric.String()),
|
||||
utility.GraphLinkForExpression(alertMetric.String()),
|
||||
rule.name,
|
||||
GraphLinkForExpression(rule.Vector.String()),
|
||||
utility.GraphLinkForExpression(rule.Vector.String()),
|
||||
rule.Vector,
|
||||
utility.DurationToString(rule.holdDuration),
|
||||
rule.Labels))
|
||||
|
@ -252,7 +255,7 @@ func (rule *AlertingRule) ActiveAlerts() []Alert {
|
|||
}
|
||||
|
||||
// NewAlertingRule constructs a new AlertingRule.
|
||||
func NewAlertingRule(name string, vector ast.VectorNode, holdDuration time.Duration, labels clientmodel.LabelSet, summary string, description string) *AlertingRule {
|
||||
func NewAlertingRule(name string, vector promql.Expr, holdDuration time.Duration, labels clientmodel.LabelSet, summary string, description string) *AlertingRule {
|
||||
return &AlertingRule{
|
||||
name: name,
|
||||
Vector: vector,
|
||||
|
|
1175
rules/ast/ast.go
1175
rules/ast/ast.go
File diff suppressed because it is too large
Load diff
|
@ -1,752 +0,0 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ast
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
|
||||
"github.com/prometheus/prometheus/storage/metric"
|
||||
)
|
||||
|
||||
// Function represents a function of the expression language and is
|
||||
// used by function nodes.
|
||||
type Function struct {
|
||||
name string
|
||||
argTypes []ExprType
|
||||
optionalArgs int
|
||||
returnType ExprType
|
||||
callFn func(timestamp clientmodel.Timestamp, args []Node) interface{}
|
||||
}
|
||||
|
||||
// CheckArgTypes returns a non-nil error if the number or types of
|
||||
// passed in arg nodes do not match the function's expectations.
|
||||
func (function *Function) CheckArgTypes(args []Node) error {
|
||||
if len(function.argTypes) < len(args) {
|
||||
return fmt.Errorf(
|
||||
"too many arguments to function %v(): %v expected at most, %v given",
|
||||
function.name, len(function.argTypes), len(args),
|
||||
)
|
||||
}
|
||||
if len(function.argTypes)-function.optionalArgs > len(args) {
|
||||
return fmt.Errorf(
|
||||
"too few arguments to function %v(): %v expected at least, %v given",
|
||||
function.name, len(function.argTypes)-function.optionalArgs, len(args),
|
||||
)
|
||||
}
|
||||
for idx, arg := range args {
|
||||
invalidType := false
|
||||
var expectedType string
|
||||
if _, ok := arg.(ScalarNode); function.argTypes[idx] == ScalarType && !ok {
|
||||
invalidType = true
|
||||
expectedType = "scalar"
|
||||
}
|
||||
if _, ok := arg.(VectorNode); function.argTypes[idx] == VectorType && !ok {
|
||||
invalidType = true
|
||||
expectedType = "vector"
|
||||
}
|
||||
if _, ok := arg.(MatrixNode); function.argTypes[idx] == MatrixType && !ok {
|
||||
invalidType = true
|
||||
expectedType = "matrix"
|
||||
}
|
||||
if _, ok := arg.(StringNode); function.argTypes[idx] == StringType && !ok {
|
||||
invalidType = true
|
||||
expectedType = "string"
|
||||
}
|
||||
|
||||
if invalidType {
|
||||
return fmt.Errorf(
|
||||
"wrong type for argument %v in function %v(), expected %v",
|
||||
idx, function.name, expectedType,
|
||||
)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// === time() clientmodel.SampleValue ===
|
||||
func timeImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
return clientmodel.SampleValue(timestamp.Unix())
|
||||
}
|
||||
|
||||
// === delta(matrix MatrixNode, isCounter=0 ScalarNode) Vector ===
|
||||
func deltaImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
matrixNode := args[0].(MatrixNode)
|
||||
isCounter := len(args) >= 2 && args[1].(ScalarNode).Eval(timestamp) > 0
|
||||
resultVector := Vector{}
|
||||
|
||||
// If we treat these metrics as counters, we need to fetch all values
|
||||
// in the interval to find breaks in the timeseries' monotonicity.
|
||||
// I.e. if a counter resets, we want to ignore that reset.
|
||||
var matrixValue Matrix
|
||||
if isCounter {
|
||||
matrixValue = matrixNode.Eval(timestamp)
|
||||
} else {
|
||||
matrixValue = matrixNode.EvalBoundaries(timestamp)
|
||||
}
|
||||
for _, samples := range matrixValue {
|
||||
// No sense in trying to compute a delta without at least two points. Drop
|
||||
// this vector element.
|
||||
if len(samples.Values) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
counterCorrection := clientmodel.SampleValue(0)
|
||||
lastValue := clientmodel.SampleValue(0)
|
||||
for _, sample := range samples.Values {
|
||||
currentValue := sample.Value
|
||||
if isCounter && currentValue < lastValue {
|
||||
counterCorrection += lastValue - currentValue
|
||||
}
|
||||
lastValue = currentValue
|
||||
}
|
||||
resultValue := lastValue - samples.Values[0].Value + counterCorrection
|
||||
|
||||
targetInterval := args[0].(*MatrixSelector).interval
|
||||
sampledInterval := samples.Values[len(samples.Values)-1].Timestamp.Sub(samples.Values[0].Timestamp)
|
||||
if sampledInterval == 0 {
|
||||
// Only found one sample. Cannot compute a rate from this.
|
||||
continue
|
||||
}
|
||||
// Correct for differences in target vs. actual delta interval.
|
||||
//
|
||||
// Above, we didn't actually calculate the delta for the specified target
|
||||
// interval, but for an interval between the first and last found samples
|
||||
// under the target interval, which will usually have less time between
|
||||
// them. Depending on how many samples are found under a target interval,
|
||||
// the delta results are distorted and temporal aliasing occurs (ugly
|
||||
// bumps). This effect is corrected for below.
|
||||
intervalCorrection := clientmodel.SampleValue(targetInterval) / clientmodel.SampleValue(sampledInterval)
|
||||
resultValue *= intervalCorrection
|
||||
|
||||
resultSample := &Sample{
|
||||
Metric: samples.Metric,
|
||||
Value: resultValue,
|
||||
Timestamp: timestamp,
|
||||
}
|
||||
resultSample.Metric.Delete(clientmodel.MetricNameLabel)
|
||||
resultVector = append(resultVector, resultSample)
|
||||
}
|
||||
return resultVector
|
||||
}
|
||||
|
||||
// === rate(node MatrixNode) Vector ===
|
||||
func rateImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
args = append(args, &ScalarLiteral{value: 1})
|
||||
vector := deltaImpl(timestamp, args).(Vector)
|
||||
|
||||
// TODO: could be other type of MatrixNode in the future (right now, only
|
||||
// MatrixSelector exists). Find a better way of getting the duration of a
|
||||
// matrix, such as looking at the samples themselves.
|
||||
interval := args[0].(*MatrixSelector).interval
|
||||
for i := range vector {
|
||||
vector[i].Value /= clientmodel.SampleValue(interval / time.Second)
|
||||
}
|
||||
return vector
|
||||
}
|
||||
|
||||
type vectorByValueHeap Vector
|
||||
|
||||
func (s vectorByValueHeap) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s vectorByValueHeap) Less(i, j int) bool {
|
||||
return s[i].Value < s[j].Value
|
||||
}
|
||||
|
||||
func (s vectorByValueHeap) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s *vectorByValueHeap) Push(x interface{}) {
|
||||
*s = append(*s, x.(*Sample))
|
||||
}
|
||||
|
||||
func (s *vectorByValueHeap) Pop() interface{} {
|
||||
old := *s
|
||||
n := len(old)
|
||||
el := old[n-1]
|
||||
*s = old[0 : n-1]
|
||||
return el
|
||||
}
|
||||
|
||||
type reverseHeap struct {
|
||||
heap.Interface
|
||||
}
|
||||
|
||||
func (s reverseHeap) Less(i, j int) bool {
|
||||
return s.Interface.Less(j, i)
|
||||
}
|
||||
|
||||
// === sort(node VectorNode) Vector ===
|
||||
func sortImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
byValueSorter := vectorByValueHeap(args[0].(VectorNode).Eval(timestamp))
|
||||
sort.Sort(byValueSorter)
|
||||
return Vector(byValueSorter)
|
||||
}
|
||||
|
||||
// === sortDesc(node VectorNode) Vector ===
|
||||
func sortDescImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
byValueSorter := vectorByValueHeap(args[0].(VectorNode).Eval(timestamp))
|
||||
sort.Sort(sort.Reverse(byValueSorter))
|
||||
return Vector(byValueSorter)
|
||||
}
|
||||
|
||||
// === topk(k ScalarNode, node VectorNode) Vector ===
|
||||
func topkImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
k := int(args[0].(ScalarNode).Eval(timestamp))
|
||||
if k < 1 {
|
||||
return Vector{}
|
||||
}
|
||||
|
||||
topk := make(vectorByValueHeap, 0, k)
|
||||
vector := args[1].(VectorNode).Eval(timestamp)
|
||||
|
||||
for _, el := range vector {
|
||||
if len(topk) < k || topk[0].Value < el.Value {
|
||||
if len(topk) == k {
|
||||
heap.Pop(&topk)
|
||||
}
|
||||
heap.Push(&topk, el)
|
||||
}
|
||||
}
|
||||
sort.Sort(sort.Reverse(topk))
|
||||
return Vector(topk)
|
||||
}
|
||||
|
||||
// === bottomk(k ScalarNode, node VectorNode) Vector ===
|
||||
func bottomkImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
k := int(args[0].(ScalarNode).Eval(timestamp))
|
||||
if k < 1 {
|
||||
return Vector{}
|
||||
}
|
||||
|
||||
bottomk := make(vectorByValueHeap, 0, k)
|
||||
bkHeap := reverseHeap{Interface: &bottomk}
|
||||
vector := args[1].(VectorNode).Eval(timestamp)
|
||||
|
||||
for _, el := range vector {
|
||||
if len(bottomk) < k || bottomk[0].Value > el.Value {
|
||||
if len(bottomk) == k {
|
||||
heap.Pop(&bkHeap)
|
||||
}
|
||||
heap.Push(&bkHeap, el)
|
||||
}
|
||||
}
|
||||
sort.Sort(bottomk)
|
||||
return Vector(bottomk)
|
||||
}
|
||||
|
||||
// === drop_common_labels(node VectorNode) Vector ===
|
||||
func dropCommonLabelsImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
vector := args[0].(VectorNode).Eval(timestamp)
|
||||
if len(vector) < 1 {
|
||||
return Vector{}
|
||||
}
|
||||
common := clientmodel.LabelSet{}
|
||||
for k, v := range vector[0].Metric.Metric {
|
||||
// TODO(julius): Should we also drop common metric names?
|
||||
if k == clientmodel.MetricNameLabel {
|
||||
continue
|
||||
}
|
||||
common[k] = v
|
||||
}
|
||||
|
||||
for _, el := range vector[1:] {
|
||||
for k, v := range common {
|
||||
if el.Metric.Metric[k] != v {
|
||||
// Deletion of map entries while iterating over them is safe.
|
||||
// From http://golang.org/ref/spec#For_statements:
|
||||
// "If map entries that have not yet been reached are deleted during
|
||||
// iteration, the corresponding iteration values will not be produced."
|
||||
delete(common, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, el := range vector {
|
||||
for k := range el.Metric.Metric {
|
||||
if _, ok := common[k]; ok {
|
||||
el.Metric.Delete(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
return vector
|
||||
}
|
||||
|
||||
// === round(vector VectorNode, toNearest=1 Scalar) Vector ===
|
||||
func roundImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
// round returns a number rounded to toNearest.
|
||||
// Ties are solved by rounding up.
|
||||
toNearest := float64(1)
|
||||
if len(args) >= 2 {
|
||||
toNearest = float64(args[1].(ScalarNode).Eval(timestamp))
|
||||
}
|
||||
// Invert as it seems to cause fewer floating point accuracy issues.
|
||||
toNearestInverse := 1.0 / toNearest
|
||||
|
||||
n := args[0].(VectorNode)
|
||||
vector := n.Eval(timestamp)
|
||||
for _, el := range vector {
|
||||
el.Metric.Delete(clientmodel.MetricNameLabel)
|
||||
el.Value = clientmodel.SampleValue(math.Floor(float64(el.Value)*toNearestInverse+0.5) / toNearestInverse)
|
||||
}
|
||||
return vector
|
||||
}
|
||||
|
||||
// === scalar(node VectorNode) Scalar ===
|
||||
func scalarImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
v := args[0].(VectorNode).Eval(timestamp)
|
||||
if len(v) != 1 {
|
||||
return clientmodel.SampleValue(math.NaN())
|
||||
}
|
||||
return clientmodel.SampleValue(v[0].Value)
|
||||
}
|
||||
|
||||
// === count_scalar(vector VectorNode) model.SampleValue ===
|
||||
func countScalarImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
return clientmodel.SampleValue(len(args[0].(VectorNode).Eval(timestamp)))
|
||||
}
|
||||
|
||||
func aggrOverTime(timestamp clientmodel.Timestamp, args []Node, aggrFn func(metric.Values) clientmodel.SampleValue) interface{} {
|
||||
n := args[0].(MatrixNode)
|
||||
matrixVal := n.Eval(timestamp)
|
||||
resultVector := Vector{}
|
||||
|
||||
for _, el := range matrixVal {
|
||||
if len(el.Values) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
el.Metric.Delete(clientmodel.MetricNameLabel)
|
||||
resultVector = append(resultVector, &Sample{
|
||||
Metric: el.Metric,
|
||||
Value: aggrFn(el.Values),
|
||||
Timestamp: timestamp,
|
||||
})
|
||||
}
|
||||
return resultVector
|
||||
}
|
||||
|
||||
// === avg_over_time(matrix MatrixNode) Vector ===
|
||||
func avgOverTimeImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
return aggrOverTime(timestamp, args, func(values metric.Values) clientmodel.SampleValue {
|
||||
var sum clientmodel.SampleValue
|
||||
for _, v := range values {
|
||||
sum += v.Value
|
||||
}
|
||||
return sum / clientmodel.SampleValue(len(values))
|
||||
})
|
||||
}
|
||||
|
||||
// === count_over_time(matrix MatrixNode) Vector ===
|
||||
func countOverTimeImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
return aggrOverTime(timestamp, args, func(values metric.Values) clientmodel.SampleValue {
|
||||
return clientmodel.SampleValue(len(values))
|
||||
})
|
||||
}
|
||||
|
||||
// === floor(vector VectorNode) Vector ===
|
||||
func floorImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
n := args[0].(VectorNode)
|
||||
vector := n.Eval(timestamp)
|
||||
for _, el := range vector {
|
||||
el.Metric.Delete(clientmodel.MetricNameLabel)
|
||||
el.Value = clientmodel.SampleValue(math.Floor(float64(el.Value)))
|
||||
}
|
||||
return vector
|
||||
}
|
||||
|
||||
// === max_over_time(matrix MatrixNode) Vector ===
|
||||
func maxOverTimeImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
return aggrOverTime(timestamp, args, func(values metric.Values) clientmodel.SampleValue {
|
||||
max := math.Inf(-1)
|
||||
for _, v := range values {
|
||||
max = math.Max(max, float64(v.Value))
|
||||
}
|
||||
return clientmodel.SampleValue(max)
|
||||
})
|
||||
}
|
||||
|
||||
// === min_over_time(matrix MatrixNode) Vector ===
|
||||
func minOverTimeImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
return aggrOverTime(timestamp, args, func(values metric.Values) clientmodel.SampleValue {
|
||||
min := math.Inf(1)
|
||||
for _, v := range values {
|
||||
min = math.Min(min, float64(v.Value))
|
||||
}
|
||||
return clientmodel.SampleValue(min)
|
||||
})
|
||||
}
|
||||
|
||||
// === sum_over_time(matrix MatrixNode) Vector ===
|
||||
func sumOverTimeImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
return aggrOverTime(timestamp, args, func(values metric.Values) clientmodel.SampleValue {
|
||||
var sum clientmodel.SampleValue
|
||||
for _, v := range values {
|
||||
sum += v.Value
|
||||
}
|
||||
return sum
|
||||
})
|
||||
}
|
||||
|
||||
// === abs(vector VectorNode) Vector ===
|
||||
func absImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
n := args[0].(VectorNode)
|
||||
vector := n.Eval(timestamp)
|
||||
for _, el := range vector {
|
||||
el.Metric.Delete(clientmodel.MetricNameLabel)
|
||||
el.Value = clientmodel.SampleValue(math.Abs(float64(el.Value)))
|
||||
}
|
||||
return vector
|
||||
}
|
||||
|
||||
// === absent(vector VectorNode) Vector ===
|
||||
func absentImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
n := args[0].(VectorNode)
|
||||
if len(n.Eval(timestamp)) > 0 {
|
||||
return Vector{}
|
||||
}
|
||||
m := clientmodel.Metric{}
|
||||
if vs, ok := n.(*VectorSelector); ok {
|
||||
for _, matcher := range vs.labelMatchers {
|
||||
if matcher.Type == metric.Equal && matcher.Name != clientmodel.MetricNameLabel {
|
||||
m[matcher.Name] = matcher.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
return Vector{
|
||||
&Sample{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: m,
|
||||
Copied: true,
|
||||
},
|
||||
Value: 1,
|
||||
Timestamp: timestamp,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// === ceil(vector VectorNode) Vector ===
|
||||
func ceilImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
n := args[0].(VectorNode)
|
||||
vector := n.Eval(timestamp)
|
||||
for _, el := range vector {
|
||||
el.Metric.Delete(clientmodel.MetricNameLabel)
|
||||
el.Value = clientmodel.SampleValue(math.Ceil(float64(el.Value)))
|
||||
}
|
||||
return vector
|
||||
}
|
||||
|
||||
// === exp(vector VectorNode) Vector ===
|
||||
func expImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
n := args[0].(VectorNode)
|
||||
vector := n.Eval(timestamp)
|
||||
for _, el := range vector {
|
||||
el.Metric.Delete(clientmodel.MetricNameLabel)
|
||||
el.Value = clientmodel.SampleValue(math.Exp(float64(el.Value)))
|
||||
}
|
||||
return vector
|
||||
}
|
||||
|
||||
// === ln(vector VectorNode) Vector ===
|
||||
func lnImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
n := args[0].(VectorNode)
|
||||
vector := n.Eval(timestamp)
|
||||
for _, el := range vector {
|
||||
el.Metric.Delete(clientmodel.MetricNameLabel)
|
||||
el.Value = clientmodel.SampleValue(math.Log(float64(el.Value)))
|
||||
}
|
||||
return vector
|
||||
}
|
||||
|
||||
// === log2(vector VectorNode) Vector ===
|
||||
func log2Impl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
n := args[0].(VectorNode)
|
||||
vector := n.Eval(timestamp)
|
||||
for _, el := range vector {
|
||||
el.Metric.Delete(clientmodel.MetricNameLabel)
|
||||
el.Value = clientmodel.SampleValue(math.Log2(float64(el.Value)))
|
||||
}
|
||||
return vector
|
||||
}
|
||||
|
||||
// === log10(vector VectorNode) Vector ===
|
||||
func log10Impl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
n := args[0].(VectorNode)
|
||||
vector := n.Eval(timestamp)
|
||||
for _, el := range vector {
|
||||
el.Metric.Delete(clientmodel.MetricNameLabel)
|
||||
el.Value = clientmodel.SampleValue(math.Log10(float64(el.Value)))
|
||||
}
|
||||
return vector
|
||||
}
|
||||
|
||||
// === deriv(node MatrixNode) Vector ===
|
||||
func derivImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
matrixNode := args[0].(MatrixNode)
|
||||
resultVector := Vector{}
|
||||
|
||||
matrixValue := matrixNode.Eval(timestamp)
|
||||
for _, samples := range matrixValue {
|
||||
// No sense in trying to compute a derivative without at least two points.
|
||||
// Drop this vector element.
|
||||
if len(samples.Values) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Least squares.
|
||||
n := clientmodel.SampleValue(0)
|
||||
sumY := clientmodel.SampleValue(0)
|
||||
sumX := clientmodel.SampleValue(0)
|
||||
sumXY := clientmodel.SampleValue(0)
|
||||
sumX2 := clientmodel.SampleValue(0)
|
||||
for _, sample := range samples.Values {
|
||||
x := clientmodel.SampleValue(sample.Timestamp.UnixNano() / 1e9)
|
||||
n += 1.0
|
||||
sumY += sample.Value
|
||||
sumX += x
|
||||
sumXY += x * sample.Value
|
||||
sumX2 += x * x
|
||||
}
|
||||
numerator := sumXY - sumX*sumY/n
|
||||
denominator := sumX2 - (sumX*sumX)/n
|
||||
|
||||
resultValue := numerator / denominator
|
||||
|
||||
resultSample := &Sample{
|
||||
Metric: samples.Metric,
|
||||
Value: resultValue,
|
||||
Timestamp: timestamp,
|
||||
}
|
||||
resultSample.Metric.Delete(clientmodel.MetricNameLabel)
|
||||
resultVector = append(resultVector, resultSample)
|
||||
}
|
||||
return resultVector
|
||||
}
|
||||
|
||||
// === histogram_quantile(k ScalarNode, vector VectorNode) Vector ===
|
||||
func histogramQuantileImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
|
||||
q := args[0].(ScalarNode).Eval(timestamp)
|
||||
inVec := args[1].(VectorNode).Eval(timestamp)
|
||||
outVec := Vector{}
|
||||
signatureToMetricWithBuckets := map[uint64]*metricWithBuckets{}
|
||||
for _, el := range inVec {
|
||||
upperBound, err := strconv.ParseFloat(
|
||||
string(el.Metric.Metric[clientmodel.BucketLabel]), 64,
|
||||
)
|
||||
if err != nil {
|
||||
// Oops, no bucket label or malformed label value. Skip.
|
||||
// TODO(beorn7): Issue a warning somehow.
|
||||
continue
|
||||
}
|
||||
signature := clientmodel.SignatureWithoutLabels(el.Metric.Metric, excludedLabels)
|
||||
mb, ok := signatureToMetricWithBuckets[signature]
|
||||
if !ok {
|
||||
el.Metric.Delete(clientmodel.BucketLabel)
|
||||
el.Metric.Delete(clientmodel.MetricNameLabel)
|
||||
mb = &metricWithBuckets{el.Metric, nil}
|
||||
signatureToMetricWithBuckets[signature] = mb
|
||||
}
|
||||
mb.buckets = append(mb.buckets, bucket{upperBound, el.Value})
|
||||
}
|
||||
|
||||
for _, mb := range signatureToMetricWithBuckets {
|
||||
outVec = append(outVec, &Sample{
|
||||
Metric: mb.metric,
|
||||
Value: clientmodel.SampleValue(quantile(q, mb.buckets)),
|
||||
Timestamp: timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
return outVec
|
||||
}
|
||||
|
||||
var functions = map[string]*Function{
|
||||
"abs": {
|
||||
name: "abs",
|
||||
argTypes: []ExprType{VectorType},
|
||||
returnType: VectorType,
|
||||
callFn: absImpl,
|
||||
},
|
||||
"absent": {
|
||||
name: "absent",
|
||||
argTypes: []ExprType{VectorType},
|
||||
returnType: VectorType,
|
||||
callFn: absentImpl,
|
||||
},
|
||||
"avg_over_time": {
|
||||
name: "avg_over_time",
|
||||
argTypes: []ExprType{MatrixType},
|
||||
returnType: VectorType,
|
||||
callFn: avgOverTimeImpl,
|
||||
},
|
||||
"bottomk": {
|
||||
name: "bottomk",
|
||||
argTypes: []ExprType{ScalarType, VectorType},
|
||||
returnType: VectorType,
|
||||
callFn: bottomkImpl,
|
||||
},
|
||||
"ceil": {
|
||||
name: "ceil",
|
||||
argTypes: []ExprType{VectorType},
|
||||
returnType: VectorType,
|
||||
callFn: ceilImpl,
|
||||
},
|
||||
"count_over_time": {
|
||||
name: "count_over_time",
|
||||
argTypes: []ExprType{MatrixType},
|
||||
returnType: VectorType,
|
||||
callFn: countOverTimeImpl,
|
||||
},
|
||||
"count_scalar": {
|
||||
name: "count_scalar",
|
||||
argTypes: []ExprType{VectorType},
|
||||
returnType: ScalarType,
|
||||
callFn: countScalarImpl,
|
||||
},
|
||||
"delta": {
|
||||
name: "delta",
|
||||
argTypes: []ExprType{MatrixType, ScalarType},
|
||||
optionalArgs: 1, // The 2nd argument is deprecated.
|
||||
returnType: VectorType,
|
||||
callFn: deltaImpl,
|
||||
},
|
||||
"deriv": {
|
||||
name: "deriv",
|
||||
argTypes: []ExprType{MatrixType},
|
||||
returnType: VectorType,
|
||||
callFn: derivImpl,
|
||||
},
|
||||
"drop_common_labels": {
|
||||
name: "drop_common_labels",
|
||||
argTypes: []ExprType{VectorType},
|
||||
returnType: VectorType,
|
||||
callFn: dropCommonLabelsImpl,
|
||||
},
|
||||
"exp": {
|
||||
name: "exp",
|
||||
argTypes: []ExprType{VectorType},
|
||||
returnType: VectorType,
|
||||
callFn: expImpl,
|
||||
},
|
||||
"floor": {
|
||||
name: "floor",
|
||||
argTypes: []ExprType{VectorType},
|
||||
returnType: VectorType,
|
||||
callFn: floorImpl,
|
||||
},
|
||||
"histogram_quantile": {
|
||||
name: "histogram_quantile",
|
||||
argTypes: []ExprType{ScalarType, VectorType},
|
||||
returnType: VectorType,
|
||||
callFn: histogramQuantileImpl,
|
||||
},
|
||||
"ln": {
|
||||
name: "ln",
|
||||
argTypes: []ExprType{VectorType},
|
||||
returnType: VectorType,
|
||||
callFn: lnImpl,
|
||||
},
|
||||
"log10": {
|
||||
name: "log10",
|
||||
argTypes: []ExprType{VectorType},
|
||||
returnType: VectorType,
|
||||
callFn: log10Impl,
|
||||
},
|
||||
"log2": {
|
||||
name: "log2",
|
||||
argTypes: []ExprType{VectorType},
|
||||
returnType: VectorType,
|
||||
callFn: log2Impl,
|
||||
},
|
||||
"max_over_time": {
|
||||
name: "max_over_time",
|
||||
argTypes: []ExprType{MatrixType},
|
||||
returnType: VectorType,
|
||||
callFn: maxOverTimeImpl,
|
||||
},
|
||||
"min_over_time": {
|
||||
name: "min_over_time",
|
||||
argTypes: []ExprType{MatrixType},
|
||||
returnType: VectorType,
|
||||
callFn: minOverTimeImpl,
|
||||
},
|
||||
"rate": {
|
||||
name: "rate",
|
||||
argTypes: []ExprType{MatrixType},
|
||||
returnType: VectorType,
|
||||
callFn: rateImpl,
|
||||
},
|
||||
"round": {
|
||||
name: "round",
|
||||
argTypes: []ExprType{VectorType, ScalarType},
|
||||
optionalArgs: 1,
|
||||
returnType: VectorType,
|
||||
callFn: roundImpl,
|
||||
},
|
||||
"scalar": {
|
||||
name: "scalar",
|
||||
argTypes: []ExprType{VectorType},
|
||||
returnType: ScalarType,
|
||||
callFn: scalarImpl,
|
||||
},
|
||||
"sort": {
|
||||
name: "sort",
|
||||
argTypes: []ExprType{VectorType},
|
||||
returnType: VectorType,
|
||||
callFn: sortImpl,
|
||||
},
|
||||
"sort_desc": {
|
||||
name: "sort_desc",
|
||||
argTypes: []ExprType{VectorType},
|
||||
returnType: VectorType,
|
||||
callFn: sortDescImpl,
|
||||
},
|
||||
"sum_over_time": {
|
||||
name: "sum_over_time",
|
||||
argTypes: []ExprType{MatrixType},
|
||||
returnType: VectorType,
|
||||
callFn: sumOverTimeImpl,
|
||||
},
|
||||
"time": {
|
||||
name: "time",
|
||||
argTypes: []ExprType{},
|
||||
returnType: ScalarType,
|
||||
callFn: timeImpl,
|
||||
},
|
||||
"topk": {
|
||||
name: "topk",
|
||||
argTypes: []ExprType{ScalarType, VectorType},
|
||||
returnType: VectorType,
|
||||
callFn: topkImpl,
|
||||
},
|
||||
}
|
||||
|
||||
// GetFunction returns a predefined Function object for the given
|
||||
// name.
|
||||
func GetFunction(name string) (*Function, error) {
|
||||
function, ok := functions[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("couldn't find function %v()", name)
|
||||
}
|
||||
return function, nil
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ast
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
|
||||
"github.com/prometheus/prometheus/storage/metric"
|
||||
)
|
||||
|
||||
type emptyRangeNode struct{}
|
||||
|
||||
func (node emptyRangeNode) Type() ExprType { return MatrixType }
|
||||
func (node emptyRangeNode) NodeTreeToDotGraph() string { return "" }
|
||||
func (node emptyRangeNode) String() string { return "" }
|
||||
func (node emptyRangeNode) Children() Nodes { return Nodes{} }
|
||||
|
||||
func (node emptyRangeNode) Eval(timestamp clientmodel.Timestamp) Matrix {
|
||||
return Matrix{
|
||||
SampleStream{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{clientmodel.MetricNameLabel: "empty_metric"},
|
||||
},
|
||||
Values: metric.Values{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (node emptyRangeNode) EvalBoundaries(timestamp clientmodel.Timestamp) Matrix {
|
||||
return Matrix{
|
||||
SampleStream{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{clientmodel.MetricNameLabel: "empty_metric"},
|
||||
},
|
||||
Values: metric.Values{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeltaWithEmptyElementDoesNotCrash(t *testing.T) {
|
||||
now := clientmodel.Now()
|
||||
vector := deltaImpl(now, []Node{emptyRangeNode{}, &ScalarLiteral{value: 0}}).(Vector)
|
||||
if len(vector) != 0 {
|
||||
t.Fatalf("Expected empty result vector, got: %v", vector)
|
||||
}
|
||||
vector = deltaImpl(now, []Node{emptyRangeNode{}, &ScalarLiteral{value: 1}}).(Vector)
|
||||
if len(vector) != 0 {
|
||||
t.Fatalf("Expected empty result vector, got: %v", vector)
|
||||
}
|
||||
}
|
|
@ -1,441 +0,0 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ast
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
|
||||
"github.com/prometheus/prometheus/stats"
|
||||
"github.com/prometheus/prometheus/storage/local"
|
||||
"github.com/prometheus/prometheus/utility"
|
||||
)
|
||||
|
||||
// OutputFormat is an enum for the possible output formats.
|
||||
type OutputFormat int
|
||||
|
||||
// Possible output formats.
|
||||
const (
|
||||
Text OutputFormat = iota
|
||||
JSON
|
||||
)
|
||||
|
||||
const jsonFormatVersion = 1
|
||||
|
||||
func (opType BinOpType) String() string {
|
||||
opTypeMap := map[BinOpType]string{
|
||||
Add: "+",
|
||||
Sub: "-",
|
||||
Mul: "*",
|
||||
Div: "/",
|
||||
Mod: "%",
|
||||
GT: ">",
|
||||
LT: "<",
|
||||
EQ: "==",
|
||||
NE: "!=",
|
||||
GE: ">=",
|
||||
LE: "<=",
|
||||
And: "AND",
|
||||
Or: "OR",
|
||||
}
|
||||
return opTypeMap[opType]
|
||||
}
|
||||
|
||||
func (aggrType AggrType) String() string {
|
||||
aggrTypeMap := map[AggrType]string{
|
||||
Sum: "SUM",
|
||||
Avg: "AVG",
|
||||
Min: "MIN",
|
||||
Max: "MAX",
|
||||
Count: "COUNT",
|
||||
}
|
||||
return aggrTypeMap[aggrType]
|
||||
}
|
||||
|
||||
func (exprType ExprType) String() string {
|
||||
exprTypeMap := map[ExprType]string{
|
||||
ScalarType: "scalar",
|
||||
VectorType: "vector",
|
||||
MatrixType: "matrix",
|
||||
StringType: "string",
|
||||
}
|
||||
return exprTypeMap[exprType]
|
||||
}
|
||||
|
||||
func (vector Vector) String() string {
|
||||
metricStrings := make([]string, 0, len(vector))
|
||||
for _, sample := range vector {
|
||||
metricStrings = append(metricStrings,
|
||||
fmt.Sprintf("%s => %v @[%v]",
|
||||
sample.Metric,
|
||||
sample.Value, sample.Timestamp))
|
||||
}
|
||||
return strings.Join(metricStrings, "\n")
|
||||
}
|
||||
|
||||
func (matrix Matrix) String() string {
|
||||
metricStrings := make([]string, 0, len(matrix))
|
||||
for _, sampleStream := range matrix {
|
||||
metricName, hasName := sampleStream.Metric.Metric[clientmodel.MetricNameLabel]
|
||||
numLabels := len(sampleStream.Metric.Metric)
|
||||
if hasName {
|
||||
numLabels--
|
||||
}
|
||||
labelStrings := make([]string, 0, numLabels)
|
||||
for label, value := range sampleStream.Metric.Metric {
|
||||
if label != clientmodel.MetricNameLabel {
|
||||
labelStrings = append(labelStrings, fmt.Sprintf("%s=%q", label, value))
|
||||
}
|
||||
}
|
||||
sort.Strings(labelStrings)
|
||||
valueStrings := make([]string, 0, len(sampleStream.Values))
|
||||
for _, value := range sampleStream.Values {
|
||||
valueStrings = append(valueStrings,
|
||||
fmt.Sprintf("\n%v @[%v]", value.Value, value.Timestamp))
|
||||
}
|
||||
metricStrings = append(metricStrings,
|
||||
fmt.Sprintf("%s{%s} => %s",
|
||||
metricName,
|
||||
strings.Join(labelStrings, ", "),
|
||||
strings.Join(valueStrings, ", ")))
|
||||
}
|
||||
sort.Strings(metricStrings)
|
||||
return strings.Join(metricStrings, "\n")
|
||||
}
|
||||
|
||||
// ErrorToJSON converts the given error into JSON.
|
||||
func ErrorToJSON(err error) string {
|
||||
errorStruct := struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
Version int `json:"version"`
|
||||
}{
|
||||
Type: "error",
|
||||
Value: err.Error(),
|
||||
Version: jsonFormatVersion,
|
||||
}
|
||||
|
||||
errorJSON, err := json.Marshal(errorStruct)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(errorJSON)
|
||||
}
|
||||
|
||||
// TypedValueToJSON converts the given data of type 'scalar',
|
||||
// 'vector', or 'matrix' into its JSON representation.
|
||||
func TypedValueToJSON(data interface{}, typeStr string) string {
|
||||
dataStruct := struct {
|
||||
Type string `json:"type"`
|
||||
Value interface{} `json:"value"`
|
||||
Version int `json:"version"`
|
||||
}{
|
||||
Type: typeStr,
|
||||
Value: data,
|
||||
Version: jsonFormatVersion,
|
||||
}
|
||||
dataJSON, err := json.Marshal(dataStruct)
|
||||
if err != nil {
|
||||
return ErrorToJSON(err)
|
||||
}
|
||||
return string(dataJSON)
|
||||
}
|
||||
|
||||
// EvalToString evaluates the given node into a string of the given format.
|
||||
func EvalToString(node Node, timestamp clientmodel.Timestamp, format OutputFormat, storage local.Storage, queryStats *stats.TimerGroup) string {
|
||||
totalEvalTimer := queryStats.GetTimer(stats.TotalEvalTime).Start()
|
||||
defer totalEvalTimer.Stop()
|
||||
|
||||
prepareTimer := queryStats.GetTimer(stats.TotalQueryPreparationTime).Start()
|
||||
closer, err := prepareInstantQuery(node, timestamp, storage, queryStats)
|
||||
prepareTimer.Stop()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer closer.Close()
|
||||
|
||||
evalTimer := queryStats.GetTimer(stats.InnerEvalTime).Start()
|
||||
switch node.Type() {
|
||||
case ScalarType:
|
||||
scalar := node.(ScalarNode).Eval(timestamp)
|
||||
evalTimer.Stop()
|
||||
switch format {
|
||||
case Text:
|
||||
return fmt.Sprintf("scalar: %v @[%v]", scalar, timestamp)
|
||||
case JSON:
|
||||
return TypedValueToJSON(scalar, "scalar")
|
||||
}
|
||||
case VectorType:
|
||||
vector := node.(VectorNode).Eval(timestamp)
|
||||
evalTimer.Stop()
|
||||
switch format {
|
||||
case Text:
|
||||
return vector.String()
|
||||
case JSON:
|
||||
return TypedValueToJSON(vector, "vector")
|
||||
}
|
||||
case MatrixType:
|
||||
matrix := node.(MatrixNode).Eval(timestamp)
|
||||
evalTimer.Stop()
|
||||
switch format {
|
||||
case Text:
|
||||
return matrix.String()
|
||||
case JSON:
|
||||
return TypedValueToJSON(matrix, "matrix")
|
||||
}
|
||||
case StringType:
|
||||
str := node.(StringNode).Eval(timestamp)
|
||||
evalTimer.Stop()
|
||||
switch format {
|
||||
case Text:
|
||||
return str
|
||||
case JSON:
|
||||
return TypedValueToJSON(str, "string")
|
||||
}
|
||||
}
|
||||
panic("Switch didn't cover all node types")
|
||||
}
|
||||
|
||||
// EvalToVector evaluates the given node into a Vector. Matrices aren't supported.
|
||||
func EvalToVector(node Node, timestamp clientmodel.Timestamp, storage local.Storage, queryStats *stats.TimerGroup) (Vector, error) {
|
||||
totalEvalTimer := queryStats.GetTimer(stats.TotalEvalTime).Start()
|
||||
defer totalEvalTimer.Stop()
|
||||
|
||||
prepareTimer := queryStats.GetTimer(stats.TotalQueryPreparationTime).Start()
|
||||
closer, err := prepareInstantQuery(node, timestamp, storage, queryStats)
|
||||
prepareTimer.Stop()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer closer.Close()
|
||||
|
||||
evalTimer := queryStats.GetTimer(stats.InnerEvalTime).Start()
|
||||
switch node.Type() {
|
||||
case ScalarType:
|
||||
scalar := node.(ScalarNode).Eval(timestamp)
|
||||
evalTimer.Stop()
|
||||
return Vector{&Sample{Value: scalar}}, nil
|
||||
case VectorType:
|
||||
vector := node.(VectorNode).Eval(timestamp)
|
||||
evalTimer.Stop()
|
||||
return vector, nil
|
||||
case MatrixType:
|
||||
return nil, errors.New("matrices not supported by EvalToVector")
|
||||
case StringType:
|
||||
str := node.(StringNode).Eval(timestamp)
|
||||
evalTimer.Stop()
|
||||
return Vector{
|
||||
&Sample{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
"__value__": clientmodel.LabelValue(str),
|
||||
},
|
||||
Copied: true,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
panic("Switch didn't cover all node types")
|
||||
}
|
||||
|
||||
// NodeTreeToDotGraph returns a DOT representation of the scalar
|
||||
// literal.
|
||||
func (node *ScalarLiteral) NodeTreeToDotGraph() string {
|
||||
return fmt.Sprintf("%#p[label=\"%v\"];\n", node, node.value)
|
||||
}
|
||||
|
||||
func functionArgsToDotGraph(node Node, args []Node) string {
|
||||
graph := ""
|
||||
for _, arg := range args {
|
||||
graph += fmt.Sprintf("%x -> %x;\n", reflect.ValueOf(node).Pointer(), reflect.ValueOf(arg).Pointer())
|
||||
}
|
||||
for _, arg := range args {
|
||||
graph += arg.NodeTreeToDotGraph()
|
||||
}
|
||||
return graph
|
||||
}
|
||||
|
||||
// NodeTreeToDotGraph returns a DOT representation of the function
|
||||
// call.
|
||||
func (node *ScalarFunctionCall) NodeTreeToDotGraph() string {
|
||||
graph := fmt.Sprintf("%#p[label=\"%s\"];\n", node, node.function.name)
|
||||
graph += functionArgsToDotGraph(node, node.args)
|
||||
return graph
|
||||
}
|
||||
|
||||
// NodeTreeToDotGraph returns a DOT representation of the expression.
|
||||
func (node *ScalarArithExpr) NodeTreeToDotGraph() string {
|
||||
nodeAddr := reflect.ValueOf(node).Pointer()
|
||||
graph := fmt.Sprintf(
|
||||
`
|
||||
%x[label="%s"];
|
||||
%x -> %x;
|
||||
%x -> %x;
|
||||
%s
|
||||
%s
|
||||
}`,
|
||||
nodeAddr, node.opType,
|
||||
nodeAddr, reflect.ValueOf(node.lhs).Pointer(),
|
||||
nodeAddr, reflect.ValueOf(node.rhs).Pointer(),
|
||||
node.lhs.NodeTreeToDotGraph(),
|
||||
node.rhs.NodeTreeToDotGraph(),
|
||||
)
|
||||
return graph
|
||||
}
|
||||
|
||||
// NodeTreeToDotGraph returns a DOT representation of the vector selector.
|
||||
func (node *VectorSelector) NodeTreeToDotGraph() string {
|
||||
return fmt.Sprintf("%#p[label=\"%s\"];\n", node, node)
|
||||
}
|
||||
|
||||
// NodeTreeToDotGraph returns a DOT representation of the function
|
||||
// call.
|
||||
func (node *VectorFunctionCall) NodeTreeToDotGraph() string {
|
||||
graph := fmt.Sprintf("%#p[label=\"%s\"];\n", node, node.function.name)
|
||||
graph += functionArgsToDotGraph(node, node.args)
|
||||
return graph
|
||||
}
|
||||
|
||||
// NodeTreeToDotGraph returns a DOT representation of the vector
|
||||
// aggregation.
|
||||
func (node *VectorAggregation) NodeTreeToDotGraph() string {
|
||||
groupByStrings := make([]string, 0, len(node.groupBy))
|
||||
for _, label := range node.groupBy {
|
||||
groupByStrings = append(groupByStrings, string(label))
|
||||
}
|
||||
|
||||
graph := fmt.Sprintf("%#p[label=\"%s BY (%s)\"]\n",
|
||||
node,
|
||||
node.aggrType,
|
||||
strings.Join(groupByStrings, ", "))
|
||||
graph += fmt.Sprintf("%#p -> %x;\n", node, reflect.ValueOf(node.vector).Pointer())
|
||||
graph += node.vector.NodeTreeToDotGraph()
|
||||
return graph
|
||||
}
|
||||
|
||||
// NodeTreeToDotGraph returns a DOT representation of the expression.
|
||||
func (node *VectorArithExpr) NodeTreeToDotGraph() string {
|
||||
nodeAddr := reflect.ValueOf(node).Pointer()
|
||||
graph := fmt.Sprintf(
|
||||
`
|
||||
%x[label="%s"];
|
||||
%x -> %x;
|
||||
%x -> %x;
|
||||
%s
|
||||
%s
|
||||
}`,
|
||||
nodeAddr, node.opType,
|
||||
nodeAddr, reflect.ValueOf(node.lhs).Pointer(),
|
||||
nodeAddr, reflect.ValueOf(node.rhs).Pointer(),
|
||||
node.lhs.NodeTreeToDotGraph(),
|
||||
node.rhs.NodeTreeToDotGraph(),
|
||||
)
|
||||
return graph
|
||||
}
|
||||
|
||||
// NodeTreeToDotGraph returns a DOT representation of the matrix
|
||||
// selector.
|
||||
func (node *MatrixSelector) NodeTreeToDotGraph() string {
|
||||
return fmt.Sprintf("%#p[label=\"%s\"];\n", node, node)
|
||||
}
|
||||
|
||||
// NodeTreeToDotGraph returns a DOT representation of the string
|
||||
// literal.
|
||||
func (node *StringLiteral) NodeTreeToDotGraph() string {
|
||||
return fmt.Sprintf("%#p[label=\"'%q'\"];\n", node, node.str)
|
||||
}
|
||||
|
||||
// NodeTreeToDotGraph returns a DOT representation of the function
|
||||
// call.
|
||||
func (node *StringFunctionCall) NodeTreeToDotGraph() string {
|
||||
graph := fmt.Sprintf("%#p[label=\"%s\"];\n", node, node.function.name)
|
||||
graph += functionArgsToDotGraph(node, node.args)
|
||||
return graph
|
||||
}
|
||||
|
||||
func (nodes Nodes) String() string {
|
||||
nodeStrings := make([]string, 0, len(nodes))
|
||||
for _, node := range nodes {
|
||||
nodeStrings = append(nodeStrings, node.String())
|
||||
}
|
||||
return strings.Join(nodeStrings, ", ")
|
||||
}
|
||||
|
||||
func (node *ScalarLiteral) String() string {
|
||||
return fmt.Sprint(node.value)
|
||||
}
|
||||
|
||||
func (node *ScalarFunctionCall) String() string {
|
||||
return fmt.Sprintf("%s(%s)", node.function.name, node.args)
|
||||
}
|
||||
|
||||
func (node *ScalarArithExpr) String() string {
|
||||
return fmt.Sprintf("(%s %s %s)", node.lhs, node.opType, node.rhs)
|
||||
}
|
||||
|
||||
func (node *VectorSelector) String() string {
|
||||
labelStrings := make([]string, 0, len(node.labelMatchers)-1)
|
||||
var metricName clientmodel.LabelValue
|
||||
for _, matcher := range node.labelMatchers {
|
||||
if matcher.Name != clientmodel.MetricNameLabel {
|
||||
labelStrings = append(labelStrings, fmt.Sprintf("%s%s%q", matcher.Name, matcher.Type, matcher.Value))
|
||||
} else {
|
||||
metricName = matcher.Value
|
||||
}
|
||||
}
|
||||
|
||||
switch len(labelStrings) {
|
||||
case 0:
|
||||
return string(metricName)
|
||||
default:
|
||||
sort.Strings(labelStrings)
|
||||
return fmt.Sprintf("%s{%s}", metricName, strings.Join(labelStrings, ","))
|
||||
}
|
||||
}
|
||||
|
||||
func (node *VectorFunctionCall) String() string {
|
||||
return fmt.Sprintf("%s(%s)", node.function.name, node.args)
|
||||
}
|
||||
|
||||
func (node *VectorAggregation) String() string {
|
||||
aggrString := fmt.Sprintf("%s(%s)", node.aggrType, node.vector)
|
||||
if len(node.groupBy) > 0 {
|
||||
return fmt.Sprintf("%s BY (%s)", aggrString, node.groupBy)
|
||||
}
|
||||
return aggrString
|
||||
}
|
||||
|
||||
func (node *VectorArithExpr) String() string {
|
||||
return fmt.Sprintf("(%s %s %s)", node.lhs, node.opType, node.rhs)
|
||||
}
|
||||
|
||||
func (node *MatrixSelector) String() string {
|
||||
vectorString := (&VectorSelector{labelMatchers: node.labelMatchers}).String()
|
||||
intervalString := fmt.Sprintf("[%s]", utility.DurationToString(node.interval))
|
||||
return vectorString + intervalString
|
||||
}
|
||||
|
||||
func (node *StringLiteral) String() string {
|
||||
return fmt.Sprintf("%q", node.str)
|
||||
}
|
||||
|
||||
func (node *StringFunctionCall) String() string {
|
||||
return fmt.Sprintf("%s(%s)", node.function.name, node.args)
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
// Copyright 2015 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ast
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
)
|
||||
|
||||
// Helpers to calculate quantiles.
|
||||
|
||||
// excludedLabels are the labels to exclude from signature calculation for
|
||||
// quantiles.
|
||||
var excludedLabels = map[clientmodel.LabelName]struct{}{
|
||||
clientmodel.MetricNameLabel: {},
|
||||
clientmodel.BucketLabel: {},
|
||||
}
|
||||
|
||||
type bucket struct {
|
||||
upperBound float64
|
||||
count clientmodel.SampleValue
|
||||
}
|
||||
|
||||
// buckets implements sort.Interface.
|
||||
type buckets []bucket
|
||||
|
||||
func (b buckets) Len() int { return len(b) }
|
||||
func (b buckets) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
||||
func (b buckets) Less(i, j int) bool { return b[i].upperBound < b[j].upperBound }
|
||||
|
||||
type metricWithBuckets struct {
|
||||
metric clientmodel.COWMetric
|
||||
buckets buckets
|
||||
}
|
||||
|
||||
// quantile calculates the quantile 'q' based on the given buckets. The buckets
|
||||
// will be sorted by upperBound by this function (i.e. no sorting needed before
|
||||
// calling this function). The quantile value is interpolated assuming a linear
|
||||
// distribution within a bucket. However, if the quantile falls into the highest
|
||||
// bucket, the upper bound of the 2nd highest bucket is returned. A natural
|
||||
// lower bound of 0 is assumed if the upper bound of the lowest bucket is
|
||||
// greater 0. In that case, interpolation in the lowest bucket happens linearly
|
||||
// between 0 and the upper bound of the lowest bucket. However, if the lowest
|
||||
// bucket has an upper bound less or equal 0, this upper bound is returned if
|
||||
// the quantile falls into the lowest bucket.
|
||||
//
|
||||
// There are a number of special cases (once we have a way to report errors
|
||||
// happening during evaluations of AST functions, we should report those
|
||||
// explicitly):
|
||||
//
|
||||
// If 'buckets' has fewer than 2 elements, NaN is returned.
|
||||
//
|
||||
// If the highest bucket is not +Inf, NaN is returned.
|
||||
//
|
||||
// If q<0, -Inf is returned.
|
||||
//
|
||||
// If q>1, +Inf is returned.
|
||||
func quantile(q clientmodel.SampleValue, buckets buckets) float64 {
|
||||
if q < 0 {
|
||||
return math.Inf(-1)
|
||||
}
|
||||
if q > 1 {
|
||||
return math.Inf(+1)
|
||||
}
|
||||
if len(buckets) < 2 {
|
||||
return math.NaN()
|
||||
}
|
||||
sort.Sort(buckets)
|
||||
if !math.IsInf(buckets[len(buckets)-1].upperBound, +1) {
|
||||
return math.NaN()
|
||||
}
|
||||
|
||||
rank := q * buckets[len(buckets)-1].count
|
||||
b := sort.Search(len(buckets)-1, func(i int) bool { return buckets[i].count >= rank })
|
||||
|
||||
if b == len(buckets)-1 {
|
||||
return buckets[len(buckets)-2].upperBound
|
||||
}
|
||||
if b == 0 && buckets[0].upperBound <= 0 {
|
||||
return buckets[0].upperBound
|
||||
}
|
||||
var (
|
||||
bucketStart float64
|
||||
bucketEnd = buckets[b].upperBound
|
||||
count = buckets[b].count
|
||||
)
|
||||
if b > 0 {
|
||||
bucketStart = buckets[b-1].upperBound
|
||||
count -= buckets[b-1].count
|
||||
rank -= buckets[b-1].count
|
||||
}
|
||||
return bucketStart + (bucketEnd-bucketStart)*float64(rank/count)
|
||||
}
|
|
@ -1,229 +0,0 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ast
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
|
||||
"github.com/prometheus/prometheus/stats"
|
||||
"github.com/prometheus/prometheus/storage/local"
|
||||
)
|
||||
|
||||
// preloadTimes tracks which instants or ranges to preload for a set of
|
||||
// fingerprints. One of these structs is collected for each offset by the query
|
||||
// analyzer.
|
||||
type preloadTimes struct {
|
||||
// Instants require single samples to be loaded along the entire query
|
||||
// range, with intervals between the samples corresponding to the query
|
||||
// resolution.
|
||||
instants map[clientmodel.Fingerprint]struct{}
|
||||
// Ranges require loading a range of samples at each resolution step,
|
||||
// stretching backwards from the current evaluation timestamp. The length of
|
||||
// the range into the past is given by the duration, as in "foo[5m]".
|
||||
ranges map[clientmodel.Fingerprint]time.Duration
|
||||
}
|
||||
|
||||
// A queryAnalyzer recursively traverses the AST to look for any nodes
|
||||
// which will need data from the datastore. Instantiate with
|
||||
// newQueryAnalyzer.
|
||||
type queryAnalyzer struct {
|
||||
// Tracks one set of times to preload per offset that occurs in the query
|
||||
// expression.
|
||||
offsetPreloadTimes map[time.Duration]preloadTimes
|
||||
// The underlying storage to which the query will be applied. Needed for
|
||||
// extracting timeseries fingerprint information during query analysis.
|
||||
storage local.Storage
|
||||
}
|
||||
|
||||
// newQueryAnalyzer returns a pointer to a newly instantiated
|
||||
// queryAnalyzer. The storage is needed to extract timeseries
|
||||
// fingerprint information during query analysis.
|
||||
func newQueryAnalyzer(storage local.Storage) *queryAnalyzer {
|
||||
return &queryAnalyzer{
|
||||
offsetPreloadTimes: map[time.Duration]preloadTimes{},
|
||||
storage: storage,
|
||||
}
|
||||
}
|
||||
|
||||
func (analyzer *queryAnalyzer) getPreloadTimes(offset time.Duration) preloadTimes {
|
||||
if _, ok := analyzer.offsetPreloadTimes[offset]; !ok {
|
||||
analyzer.offsetPreloadTimes[offset] = preloadTimes{
|
||||
instants: map[clientmodel.Fingerprint]struct{}{},
|
||||
ranges: map[clientmodel.Fingerprint]time.Duration{},
|
||||
}
|
||||
}
|
||||
return analyzer.offsetPreloadTimes[offset]
|
||||
}
|
||||
|
||||
// visit implements the visitor interface.
|
||||
func (analyzer *queryAnalyzer) visit(node Node) {
|
||||
switch n := node.(type) {
|
||||
case *VectorSelector:
|
||||
pt := analyzer.getPreloadTimes(n.offset)
|
||||
fingerprints := analyzer.storage.GetFingerprintsForLabelMatchers(n.labelMatchers)
|
||||
n.fingerprints = fingerprints
|
||||
for _, fp := range fingerprints {
|
||||
// Only add the fingerprint to the instants if not yet present in the
|
||||
// ranges. Ranges always contain more points and span more time than
|
||||
// instants for the same offset.
|
||||
if _, alreadyInRanges := pt.ranges[fp]; !alreadyInRanges {
|
||||
pt.instants[fp] = struct{}{}
|
||||
}
|
||||
|
||||
n.metrics[fp] = analyzer.storage.GetMetricForFingerprint(fp)
|
||||
}
|
||||
case *MatrixSelector:
|
||||
pt := analyzer.getPreloadTimes(n.offset)
|
||||
fingerprints := analyzer.storage.GetFingerprintsForLabelMatchers(n.labelMatchers)
|
||||
n.fingerprints = fingerprints
|
||||
for _, fp := range fingerprints {
|
||||
if pt.ranges[fp] < n.interval {
|
||||
pt.ranges[fp] = n.interval
|
||||
// Delete the fingerprint from the instants. Ranges always contain more
|
||||
// points and span more time than instants, so we don't need to track
|
||||
// an instant for the same fingerprint, should we have one.
|
||||
delete(pt.instants, fp)
|
||||
}
|
||||
|
||||
n.metrics[fp] = analyzer.storage.GetMetricForFingerprint(fp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type iteratorInitializer struct {
|
||||
storage local.Storage
|
||||
}
|
||||
|
||||
func (i *iteratorInitializer) visit(node Node) {
|
||||
switch n := node.(type) {
|
||||
case *VectorSelector:
|
||||
for _, fp := range n.fingerprints {
|
||||
n.iterators[fp] = i.storage.NewIterator(fp)
|
||||
}
|
||||
case *MatrixSelector:
|
||||
for _, fp := range n.fingerprints {
|
||||
n.iterators[fp] = i.storage.NewIterator(fp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func prepareInstantQuery(node Node, timestamp clientmodel.Timestamp, storage local.Storage, queryStats *stats.TimerGroup) (local.Preloader, error) {
|
||||
totalTimer := queryStats.GetTimer(stats.TotalEvalTime)
|
||||
|
||||
analyzeTimer := queryStats.GetTimer(stats.QueryAnalysisTime).Start()
|
||||
analyzer := newQueryAnalyzer(storage)
|
||||
Walk(analyzer, node)
|
||||
analyzeTimer.Stop()
|
||||
|
||||
preloadTimer := queryStats.GetTimer(stats.PreloadTime).Start()
|
||||
p := storage.NewPreloader()
|
||||
for offset, pt := range analyzer.offsetPreloadTimes {
|
||||
ts := timestamp.Add(-offset)
|
||||
for fp, rangeDuration := range pt.ranges {
|
||||
if et := totalTimer.ElapsedTime(); et > *queryTimeout {
|
||||
preloadTimer.Stop()
|
||||
p.Close()
|
||||
return nil, queryTimeoutError{et}
|
||||
}
|
||||
if err := p.PreloadRange(fp, ts.Add(-rangeDuration), ts, *stalenessDelta); err != nil {
|
||||
preloadTimer.Stop()
|
||||
p.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for fp := range pt.instants {
|
||||
if et := totalTimer.ElapsedTime(); et > *queryTimeout {
|
||||
preloadTimer.Stop()
|
||||
p.Close()
|
||||
return nil, queryTimeoutError{et}
|
||||
}
|
||||
if err := p.PreloadRange(fp, ts, ts, *stalenessDelta); err != nil {
|
||||
preloadTimer.Stop()
|
||||
p.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
preloadTimer.Stop()
|
||||
|
||||
ii := &iteratorInitializer{
|
||||
storage: storage,
|
||||
}
|
||||
Walk(ii, node)
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func prepareRangeQuery(node Node, start clientmodel.Timestamp, end clientmodel.Timestamp, interval time.Duration, storage local.Storage, queryStats *stats.TimerGroup) (local.Preloader, error) {
|
||||
totalTimer := queryStats.GetTimer(stats.TotalEvalTime)
|
||||
|
||||
analyzeTimer := queryStats.GetTimer(stats.QueryAnalysisTime).Start()
|
||||
analyzer := newQueryAnalyzer(storage)
|
||||
Walk(analyzer, node)
|
||||
analyzeTimer.Stop()
|
||||
|
||||
preloadTimer := queryStats.GetTimer(stats.PreloadTime).Start()
|
||||
p := storage.NewPreloader()
|
||||
for offset, pt := range analyzer.offsetPreloadTimes {
|
||||
offsetStart := start.Add(-offset)
|
||||
offsetEnd := end.Add(-offset)
|
||||
for fp, rangeDuration := range pt.ranges {
|
||||
if et := totalTimer.ElapsedTime(); et > *queryTimeout {
|
||||
preloadTimer.Stop()
|
||||
p.Close()
|
||||
return nil, queryTimeoutError{et}
|
||||
}
|
||||
if err := p.PreloadRange(fp, offsetStart.Add(-rangeDuration), offsetEnd, *stalenessDelta); err != nil {
|
||||
preloadTimer.Stop()
|
||||
p.Close()
|
||||
return nil, err
|
||||
}
|
||||
/*
|
||||
if interval < rangeDuration {
|
||||
if err := p.GetMetricRange(fp, offsetEnd, offsetEnd.Sub(offsetStart)+rangeDuration); err != nil {
|
||||
p.Close()
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := p.GetMetricRangeAtInterval(fp, offsetStart, offsetEnd, interval, rangeDuration); err != nil {
|
||||
p.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
for fp := range pt.instants {
|
||||
if et := totalTimer.ElapsedTime(); et > *queryTimeout {
|
||||
preloadTimer.Stop()
|
||||
p.Close()
|
||||
return nil, queryTimeoutError{et}
|
||||
}
|
||||
if err := p.PreloadRange(fp, offsetStart, offsetEnd, *stalenessDelta); err != nil {
|
||||
preloadTimer.Stop()
|
||||
p.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
preloadTimer.Stop()
|
||||
|
||||
ii := &iteratorInitializer{
|
||||
storage: storage,
|
||||
}
|
||||
Walk(ii, node)
|
||||
|
||||
return p, nil
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ast
|
||||
|
||||
// visitor is the interface for a Node visitor.
|
||||
type visitor interface {
|
||||
visit(node Node)
|
||||
}
|
||||
|
||||
// Walk does a depth-first traversal of the AST, starting at node,
|
||||
// calling visitor.visit for each encountered Node in the tree.
|
||||
func Walk(v visitor, node Node) {
|
||||
v.visit(node)
|
||||
for _, childNode := range node.Children() {
|
||||
Walk(v, childNode)
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
// A simple test recording rule.
|
||||
dc_http_request_rate5m = sum(rate(http_request_count[5m])) by (dc)
|
||||
|
||||
// A simple test alerting rule.
|
||||
ALERT GlobalRequestRateLow IF(dc_http_request_rate5m < 10000) FOR 5m WITH {
|
||||
service = "testservice"
|
||||
/* ... more fields here ... */
|
||||
}
|
||||
SUMMARY "Global request rate low"
|
||||
DESCRIPTION "The global request rate is low"
|
||||
|
||||
foo = bar{label1="value1"}
|
||||
|
||||
ALERT BazAlert IF(foo > 10) WITH {}
|
||||
SUMMARY "Baz"
|
||||
DESCRIPTION "BazAlert"
|
|
@ -1 +0,0 @@
|
|||
now = time()
|
|
@ -1,15 +0,0 @@
|
|||
// A simple test recording rule.
|
||||
dc_http_request_rate5m = sum(rate(http_request_count[5m])) by (dc)
|
||||
|
||||
// A simple test alerting rule with a syntax error (invalid duration string "5").
|
||||
ALERT GlobalRequestRateLow IF(dc_http_request_rate5m < 10000) FOR 5 WITH {
|
||||
description = "Global HTTP request rate low!",
|
||||
summary = "Request rate low"
|
||||
/* ... more fields here ... */
|
||||
}
|
||||
SUMMARY "summary"
|
||||
DESCRIPTION "description"
|
||||
|
||||
foo = bar{label1="value1"}
|
||||
|
||||
ALERT BazAlert IF(foo > 10) WITH {} SUMMARY "summary" DESCRIPTION "description"
|
221
rules/helpers.go
221
rules/helpers.go
|
@ -1,221 +0,0 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
|
||||
"github.com/prometheus/prometheus/rules/ast"
|
||||
"github.com/prometheus/prometheus/storage/metric"
|
||||
"github.com/prometheus/prometheus/utility"
|
||||
)
|
||||
|
||||
// CreateRecordingRule is a convenience function to create a recording rule.
|
||||
func CreateRecordingRule(name string, labels clientmodel.LabelSet, expr ast.Node, permanent bool) (*RecordingRule, error) {
|
||||
if _, ok := expr.(ast.VectorNode); !ok {
|
||||
return nil, fmt.Errorf("recording rule expression %v does not evaluate to vector type", expr)
|
||||
}
|
||||
return &RecordingRule{
|
||||
name: name,
|
||||
labels: labels,
|
||||
vector: expr.(ast.VectorNode),
|
||||
permanent: permanent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateAlertingRule is a convenience function to create a new alerting rule.
|
||||
func CreateAlertingRule(name string, expr ast.Node, holdDurationStr string, labels clientmodel.LabelSet, summary string, description string) (*AlertingRule, error) {
|
||||
if _, ok := expr.(ast.VectorNode); !ok {
|
||||
return nil, fmt.Errorf("alert rule expression %v does not evaluate to vector type", expr)
|
||||
}
|
||||
holdDuration, err := utility.StringToDuration(holdDurationStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewAlertingRule(name, expr.(ast.VectorNode), holdDuration, labels, summary, description), nil
|
||||
}
|
||||
|
||||
// NewScalarLiteral returns a ScalarLiteral with the given value. If sign is "-"
|
||||
// the value is negated.
|
||||
func NewScalarLiteral(value clientmodel.SampleValue, sign string) *ast.ScalarLiteral {
|
||||
if sign == "-" {
|
||||
value = -value
|
||||
}
|
||||
return ast.NewScalarLiteral(value)
|
||||
}
|
||||
|
||||
// NewFunctionCall is a convenience function to create a new AST function-call node.
|
||||
func NewFunctionCall(name string, args []ast.Node) (ast.Node, error) {
|
||||
function, err := ast.GetFunction(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unknown function %q", name)
|
||||
}
|
||||
functionCall, err := ast.NewFunctionCall(function, args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(err.Error())
|
||||
}
|
||||
return functionCall, nil
|
||||
}
|
||||
|
||||
// NewVectorAggregation is a convenience function to create a new AST vector aggregation.
|
||||
func NewVectorAggregation(aggrTypeStr string, vector ast.Node, groupBy clientmodel.LabelNames, keepExtraLabels bool) (*ast.VectorAggregation, error) {
|
||||
if _, ok := vector.(ast.VectorNode); !ok {
|
||||
return nil, fmt.Errorf("operand of %v aggregation must be of vector type", aggrTypeStr)
|
||||
}
|
||||
var aggrTypes = map[string]ast.AggrType{
|
||||
"SUM": ast.Sum,
|
||||
"MAX": ast.Max,
|
||||
"MIN": ast.Min,
|
||||
"AVG": ast.Avg,
|
||||
"COUNT": ast.Count,
|
||||
}
|
||||
aggrType, ok := aggrTypes[aggrTypeStr]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown aggregation type %q", aggrTypeStr)
|
||||
}
|
||||
return ast.NewVectorAggregation(aggrType, vector.(ast.VectorNode), groupBy, keepExtraLabels), nil
|
||||
}
|
||||
|
||||
// vectorMatching combines data used to match samples between vectors.
|
||||
type vectorMatching struct {
|
||||
matchCardinality ast.VectorMatchCardinality
|
||||
matchOn clientmodel.LabelNames
|
||||
includeLabels clientmodel.LabelNames
|
||||
}
|
||||
|
||||
// newVectorMatching is a convenience function to create a new vectorMatching.
|
||||
func newVectorMatching(card string, matchOn, include clientmodel.LabelNames) (*vectorMatching, error) {
|
||||
var matchCardinalities = map[string]ast.VectorMatchCardinality{
|
||||
"": ast.MatchOneToOne,
|
||||
"GROUP_LEFT": ast.MatchManyToOne,
|
||||
"GROUP_RIGHT": ast.MatchOneToMany,
|
||||
}
|
||||
matchCard, ok := matchCardinalities[card]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid vector match cardinality %q", card)
|
||||
}
|
||||
if matchCard != ast.MatchOneToOne && len(include) == 0 {
|
||||
return nil, fmt.Errorf("grouped vector matching must provide labels")
|
||||
}
|
||||
// There must be no overlap between both labelname lists.
|
||||
for _, matchLabel := range matchOn {
|
||||
for _, incLabel := range include {
|
||||
if matchLabel == incLabel {
|
||||
return nil, fmt.Errorf("use of label %s in ON and %s clauses not allowed", incLabel, card)
|
||||
}
|
||||
}
|
||||
}
|
||||
return &vectorMatching{matchCard, matchOn, include}, nil
|
||||
}
|
||||
|
||||
// NewArithExpr is a convenience function to create a new AST arithmetic expression.
|
||||
func NewArithExpr(opTypeStr string, lhs ast.Node, rhs ast.Node, vecMatching *vectorMatching) (ast.Node, error) {
|
||||
var opTypes = map[string]ast.BinOpType{
|
||||
"+": ast.Add,
|
||||
"-": ast.Sub,
|
||||
"*": ast.Mul,
|
||||
"/": ast.Div,
|
||||
"%": ast.Mod,
|
||||
">": ast.GT,
|
||||
"<": ast.LT,
|
||||
"==": ast.EQ,
|
||||
"!=": ast.NE,
|
||||
">=": ast.GE,
|
||||
"<=": ast.LE,
|
||||
"AND": ast.And,
|
||||
"OR": ast.Or,
|
||||
}
|
||||
opType, ok := opTypes[opTypeStr]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid binary operator %q", opTypeStr)
|
||||
}
|
||||
var vm vectorMatching
|
||||
if vecMatching != nil {
|
||||
vm = *vecMatching
|
||||
// And/or always do many-to-many matching.
|
||||
if opType == ast.And || opType == ast.Or {
|
||||
vm.matchCardinality = ast.MatchManyToMany
|
||||
}
|
||||
}
|
||||
expr, err := ast.NewArithExpr(opType, lhs, rhs, vm.matchCardinality, vm.matchOn, vm.includeLabels)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(err.Error())
|
||||
}
|
||||
return expr, nil
|
||||
}
|
||||
|
||||
// NewVectorSelector is a convenience function to create a new AST vector selector.
|
||||
func NewVectorSelector(m metric.LabelMatchers, offsetStr string) (ast.VectorNode, error) {
|
||||
offset, err := utility.StringToDuration(offsetStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ast.NewVectorSelector(m, offset), nil
|
||||
}
|
||||
|
||||
// NewMatrixSelector is a convenience function to create a new AST matrix selector.
|
||||
func NewMatrixSelector(vector ast.Node, intervalStr string, offsetStr string) (ast.MatrixNode, error) {
|
||||
interval, err := utility.StringToDuration(intervalStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
offset, err := utility.StringToDuration(offsetStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vectorSelector, ok := vector.(*ast.VectorSelector)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("intervals are currently only supported for vector selectors")
|
||||
}
|
||||
return ast.NewMatrixSelector(vectorSelector, interval, offset), nil
|
||||
}
|
||||
|
||||
func newLabelMatcher(matchTypeStr string, name clientmodel.LabelName, value clientmodel.LabelValue) (*metric.LabelMatcher, error) {
|
||||
matchTypes := map[string]metric.MatchType{
|
||||
"=": metric.Equal,
|
||||
"!=": metric.NotEqual,
|
||||
"=~": metric.RegexMatch,
|
||||
"!~": metric.RegexNoMatch,
|
||||
}
|
||||
matchType, ok := matchTypes[matchTypeStr]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid label matching operator %q", matchTypeStr)
|
||||
}
|
||||
return metric.NewLabelMatcher(matchType, name, value)
|
||||
}
|
||||
|
||||
// TableLinkForExpression creates an escaped relative link to the table view of
|
||||
// the provided expression.
|
||||
func TableLinkForExpression(expr string) string {
|
||||
// url.QueryEscape percent-escapes everything except spaces, for which it
|
||||
// uses "+". However, in the non-query part of a URI, only percent-escaped
|
||||
// spaces are legal, so we need to manually replace "+" with "%20" after
|
||||
// query-escaping the string.
|
||||
//
|
||||
// See also:
|
||||
// http://stackoverflow.com/questions/1634271/url-encoding-the-space-character-or-20.
|
||||
urlData := url.QueryEscape(fmt.Sprintf(`[{"expr":%q,"tab":1}]`, expr))
|
||||
return fmt.Sprintf("/graph#%s", strings.Replace(urlData, "+", "%20", -1))
|
||||
}
|
||||
|
||||
// GraphLinkForExpression creates an escaped relative link to the graph view of
|
||||
// the provided expression.
|
||||
func GraphLinkForExpression(expr string) string {
|
||||
urlData := url.QueryEscape(fmt.Sprintf(`[{"expr":%q,"tab":0}]`, expr))
|
||||
return fmt.Sprintf("/graph#%s", strings.Replace(urlData, "+", "%20", -1))
|
||||
}
|
|
@ -1,487 +0,0 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
|
||||
"github.com/prometheus/prometheus/rules/ast"
|
||||
"github.com/prometheus/prometheus/storage/local"
|
||||
"github.com/prometheus/prometheus/storage/metric"
|
||||
)
|
||||
|
||||
var testSampleInterval = time.Duration(5) * time.Minute
|
||||
var testStartTime = clientmodel.Timestamp(0)
|
||||
|
||||
func getTestValueStream(startVal clientmodel.SampleValue, endVal clientmodel.SampleValue, stepVal clientmodel.SampleValue, startTime clientmodel.Timestamp) (resultValues metric.Values) {
|
||||
currentTime := startTime
|
||||
for currentVal := startVal; currentVal <= endVal; currentVal += stepVal {
|
||||
sample := metric.SamplePair{
|
||||
Value: currentVal,
|
||||
Timestamp: currentTime,
|
||||
}
|
||||
resultValues = append(resultValues, sample)
|
||||
currentTime = currentTime.Add(testSampleInterval)
|
||||
}
|
||||
return resultValues
|
||||
}
|
||||
|
||||
func getTestVectorFromTestMatrix(matrix ast.Matrix) ast.Vector {
|
||||
vector := ast.Vector{}
|
||||
for _, sampleStream := range matrix {
|
||||
lastSample := sampleStream.Values[len(sampleStream.Values)-1]
|
||||
vector = append(vector, &ast.Sample{
|
||||
Metric: sampleStream.Metric,
|
||||
Value: lastSample.Value,
|
||||
Timestamp: lastSample.Timestamp,
|
||||
})
|
||||
}
|
||||
return vector
|
||||
}
|
||||
|
||||
func storeMatrix(storage local.Storage, matrix ast.Matrix) {
|
||||
pendingSamples := clientmodel.Samples{}
|
||||
for _, sampleStream := range matrix {
|
||||
for _, sample := range sampleStream.Values {
|
||||
pendingSamples = append(pendingSamples, &clientmodel.Sample{
|
||||
Metric: sampleStream.Metric.Metric,
|
||||
Value: sample.Value,
|
||||
Timestamp: sample.Timestamp,
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, s := range pendingSamples {
|
||||
storage.Append(s)
|
||||
}
|
||||
storage.WaitForIndexing()
|
||||
}
|
||||
|
||||
var testMatrix = ast.Matrix{
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "http_requests",
|
||||
clientmodel.JobLabel: "api-server",
|
||||
"instance": "0",
|
||||
"group": "production",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 100, 10, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "http_requests",
|
||||
clientmodel.JobLabel: "api-server",
|
||||
"instance": "1",
|
||||
"group": "production",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 200, 20, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "http_requests",
|
||||
clientmodel.JobLabel: "api-server",
|
||||
"instance": "0",
|
||||
"group": "canary",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 300, 30, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "http_requests",
|
||||
clientmodel.JobLabel: "api-server",
|
||||
"instance": "1",
|
||||
"group": "canary",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 400, 40, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "http_requests",
|
||||
clientmodel.JobLabel: "app-server",
|
||||
"instance": "0",
|
||||
"group": "production",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 500, 50, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "http_requests",
|
||||
clientmodel.JobLabel: "app-server",
|
||||
"instance": "1",
|
||||
"group": "production",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 600, 60, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "http_requests",
|
||||
clientmodel.JobLabel: "app-server",
|
||||
"instance": "0",
|
||||
"group": "canary",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 700, 70, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "http_requests",
|
||||
clientmodel.JobLabel: "app-server",
|
||||
"instance": "1",
|
||||
"group": "canary",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 800, 80, testStartTime),
|
||||
},
|
||||
// Single-letter metric and label names.
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "x",
|
||||
"y": "testvalue",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 100, 10, testStartTime),
|
||||
},
|
||||
// Counter reset in the middle of range.
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "testcounter_reset_middle",
|
||||
},
|
||||
},
|
||||
Values: append(getTestValueStream(0, 40, 10, testStartTime), getTestValueStream(0, 50, 10, testStartTime.Add(testSampleInterval*5))...),
|
||||
},
|
||||
// Counter reset at the end of range.
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "testcounter_reset_end",
|
||||
},
|
||||
},
|
||||
Values: append(getTestValueStream(0, 90, 10, testStartTime), getTestValueStream(0, 0, 10, testStartTime.Add(testSampleInterval*10))...),
|
||||
},
|
||||
// For label-key grouping regression test.
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "label_grouping_test",
|
||||
"a": "aa",
|
||||
"b": "bb",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 100, 10, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "label_grouping_test",
|
||||
"a": "a",
|
||||
"b": "abb",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 200, 20, testStartTime),
|
||||
},
|
||||
// Two histograms with 4 buckets each (*_sum and *_count not included,
|
||||
// only buckets). Lowest bucket for one histogram < 0, for the other >
|
||||
// 0. They have the same name, just separated by label. Not useful in
|
||||
// practice, but can happen (if clients change bucketing), and the
|
||||
// server has to cope with it.
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "testhistogram_bucket",
|
||||
"le": "0.1",
|
||||
"start": "positive",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 50, 5, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "testhistogram_bucket",
|
||||
"le": ".2",
|
||||
"start": "positive",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 70, 7, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "testhistogram_bucket",
|
||||
"le": "1e0",
|
||||
"start": "positive",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 110, 11, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "testhistogram_bucket",
|
||||
"le": "+Inf",
|
||||
"start": "positive",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 120, 12, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "testhistogram_bucket",
|
||||
"le": "-.2",
|
||||
"start": "negative",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 10, 1, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "testhistogram_bucket",
|
||||
"le": "-0.1",
|
||||
"start": "negative",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 20, 2, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "testhistogram_bucket",
|
||||
"le": "0.3",
|
||||
"start": "negative",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 20, 2, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "testhistogram_bucket",
|
||||
"le": "+Inf",
|
||||
"start": "negative",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 30, 3, testStartTime),
|
||||
},
|
||||
// Now a more realistic histogram per job and instance to test aggregation.
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "request_duration_seconds_bucket",
|
||||
clientmodel.JobLabel: "job1",
|
||||
"instance": "ins1",
|
||||
"le": "0.1",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 10, 1, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "request_duration_seconds_bucket",
|
||||
clientmodel.JobLabel: "job1",
|
||||
"instance": "ins1",
|
||||
"le": "0.2",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 30, 3, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "request_duration_seconds_bucket",
|
||||
clientmodel.JobLabel: "job1",
|
||||
"instance": "ins1",
|
||||
"le": "+Inf",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 40, 4, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "request_duration_seconds_bucket",
|
||||
clientmodel.JobLabel: "job1",
|
||||
"instance": "ins2",
|
||||
"le": "0.1",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 20, 2, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "request_duration_seconds_bucket",
|
||||
clientmodel.JobLabel: "job1",
|
||||
"instance": "ins2",
|
||||
"le": "0.2",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 50, 5, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "request_duration_seconds_bucket",
|
||||
clientmodel.JobLabel: "job1",
|
||||
"instance": "ins2",
|
||||
"le": "+Inf",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 60, 6, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "request_duration_seconds_bucket",
|
||||
clientmodel.JobLabel: "job2",
|
||||
"instance": "ins1",
|
||||
"le": "0.1",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 30, 3, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "request_duration_seconds_bucket",
|
||||
clientmodel.JobLabel: "job2",
|
||||
"instance": "ins1",
|
||||
"le": "0.2",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 40, 4, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "request_duration_seconds_bucket",
|
||||
clientmodel.JobLabel: "job2",
|
||||
"instance": "ins1",
|
||||
"le": "+Inf",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 60, 6, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "request_duration_seconds_bucket",
|
||||
clientmodel.JobLabel: "job2",
|
||||
"instance": "ins2",
|
||||
"le": "0.1",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 40, 4, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "request_duration_seconds_bucket",
|
||||
clientmodel.JobLabel: "job2",
|
||||
"instance": "ins2",
|
||||
"le": "0.2",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 70, 7, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "request_duration_seconds_bucket",
|
||||
clientmodel.JobLabel: "job2",
|
||||
"instance": "ins2",
|
||||
"le": "+Inf",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 90, 9, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "vector_matching_a",
|
||||
"l": "x",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 100, 1, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "vector_matching_a",
|
||||
"l": "y",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 100, 2, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "vector_matching_b",
|
||||
"l": "x",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 100, 4, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "cpu_count",
|
||||
"instance": "0",
|
||||
"type": "numa",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 500, 30, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "cpu_count",
|
||||
"instance": "0",
|
||||
"type": "smp",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 200, 10, testStartTime),
|
||||
},
|
||||
{
|
||||
Metric: clientmodel.COWMetric{
|
||||
Metric: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "cpu_count",
|
||||
"instance": "1",
|
||||
"type": "smp",
|
||||
},
|
||||
},
|
||||
Values: getTestValueStream(0, 200, 20, testStartTime),
|
||||
},
|
||||
}
|
||||
|
||||
var testVector = getTestVectorFromTestMatrix(testMatrix)
|
118
rules/lexer.l
118
rules/lexer.l
|
@ -1,118 +0,0 @@
|
|||
/* Copyright 2013 The Prometheus Authors
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License. */
|
||||
|
||||
%{
|
||||
package rules
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
)
|
||||
|
||||
// Lex is called by the parser generated by "go tool yacc" to obtain each
|
||||
// token. The method is opened before the matching rules block and closed at
|
||||
// the end of the file.
|
||||
func (lexer *RulesLexer) Lex(lval *yySymType) int {
|
||||
// Internal lexer states.
|
||||
const (
|
||||
S_INITIAL = iota
|
||||
S_COMMENTS
|
||||
)
|
||||
|
||||
// We simulate multiple start symbols for closely-related grammars via dummy tokens. See
|
||||
// http://www.gnu.org/software/bison/manual/html_node/Multiple-start_002dsymbols.html
|
||||
// Reason: we want to be able to parse lists of named rules as well as single expressions.
|
||||
if lexer.startToken != 0 {
|
||||
startToken := lexer.startToken
|
||||
lexer.startToken = 0
|
||||
return startToken
|
||||
}
|
||||
|
||||
c := lexer.current
|
||||
currentState := 0
|
||||
|
||||
if lexer.empty {
|
||||
c, lexer.empty = lexer.getChar(), false
|
||||
}
|
||||
|
||||
%}
|
||||
|
||||
D [0-9]
|
||||
L [a-zA-Z_]
|
||||
M [a-zA-Z_:]
|
||||
U [smhdwy]
|
||||
|
||||
FLOAT ({D}*\.?{D}+|{D}+\.?{D}*){EXPONENT}?|[+-]?[iI][nN][fF]|[nN][aA][nN]
|
||||
EXPONENT [eE][-+]?[0-9]+
|
||||
|
||||
STR \"(\\.|[^\\"])*\"|\'(\\.|[^\\'])*\'
|
||||
|
||||
%x S_COMMENTS
|
||||
|
||||
%yyc c
|
||||
%yyn c = lexer.getChar()
|
||||
%yyt currentState
|
||||
|
||||
%%
|
||||
lexer.buf = lexer.buf[:0] // The code before the first rule executed before every scan cycle (rule #0 / state 0 action)
|
||||
|
||||
"/*" currentState = S_COMMENTS
|
||||
<S_COMMENTS>"*/" currentState = S_INITIAL
|
||||
<S_COMMENTS>.|\n /* ignore chars within multi-line comments */
|
||||
|
||||
\/\/[^\r\n]*\n /* gobble up one-line comments */
|
||||
|
||||
ALERT|alert return ALERT
|
||||
IF|if return IF
|
||||
FOR|for return FOR
|
||||
WITH|with return WITH
|
||||
SUMMARY|summary return SUMMARY
|
||||
DESCRIPTION|description return DESCRIPTION
|
||||
|
||||
PERMANENT|permanent return PERMANENT
|
||||
BY|by return GROUP_OP
|
||||
ON|on return MATCH_OP
|
||||
GROUP_LEFT|GROUP_RIGHT lval.str = lexer.token(); return MATCH_MOD
|
||||
group_left|group_right lval.str = strings.ToUpper(lexer.token()); return MATCH_MOD
|
||||
KEEPING_EXTRA|keeping_extra return KEEPING_EXTRA
|
||||
OFFSET|offset return OFFSET
|
||||
AVG|SUM|MAX|MIN|COUNT lval.str = lexer.token(); return AGGR_OP
|
||||
avg|sum|max|min|count lval.str = strings.ToUpper(lexer.token()); return AGGR_OP
|
||||
\<|>|AND|OR|and|or lval.str = strings.ToUpper(lexer.token()); return CMP_OP
|
||||
==|!=|>=|<=|=~|!~ lval.str = lexer.token(); return CMP_OP
|
||||
[+\-] lval.str = lexer.token(); return ADDITIVE_OP
|
||||
[*/%] lval.str = lexer.token(); return MULT_OP
|
||||
|
||||
{FLOAT} num, err := strconv.ParseFloat(lexer.token(), 64);
|
||||
if (err != nil && err.(*strconv.NumError).Err == strconv.ErrSyntax) {
|
||||
panic("Invalid float")
|
||||
}
|
||||
lval.num = clientmodel.SampleValue(num)
|
||||
return NUMBER
|
||||
|
||||
{D}+{U} lval.str = lexer.token(); return DURATION
|
||||
{L}({L}|{D})* lval.str = lexer.token(); return IDENTIFIER
|
||||
{M}({M}|{D})* lval.str = lexer.token(); return METRICNAME
|
||||
|
||||
{STR} lval.str = lexer.token()[1:len(lexer.token()) - 1]; return STRING
|
||||
|
||||
[{}\[\]()=,] return int(lexer.buf[0])
|
||||
[\t\n\r ] /* gobble up any whitespace */
|
||||
%%
|
||||
|
||||
lexer.empty = true
|
||||
return int(c)
|
||||
}
|
2738
rules/lexer.l.go
2738
rules/lexer.l.go
File diff suppressed because it is too large
Load diff
164
rules/load.go
164
rules/load.go
|
@ -1,164 +0,0 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"github.com/prometheus/prometheus/rules/ast"
|
||||
)
|
||||
|
||||
// RulesLexer is the lexer for rule expressions.
|
||||
type RulesLexer struct {
|
||||
// Errors encountered during parsing.
|
||||
errors []string
|
||||
// Dummy token to simulate multiple start symbols (see below).
|
||||
startToken int
|
||||
// Parsed full rules.
|
||||
parsedRules []Rule
|
||||
// Parsed single expression.
|
||||
parsedExpr ast.Node
|
||||
|
||||
// Current character.
|
||||
current byte
|
||||
// Current token buffer.
|
||||
buf []byte
|
||||
// Input text.
|
||||
src *bufio.Reader
|
||||
// Whether we have a current char.
|
||||
empty bool
|
||||
|
||||
// Current input line.
|
||||
line int
|
||||
// Current character position within the current input line.
|
||||
pos int
|
||||
}
|
||||
|
||||
func (lexer *RulesLexer) Error(errorStr string) {
|
||||
err := fmt.Sprintf("Error parsing rules at line %v, char %v: %v", lexer.line, lexer.pos, errorStr)
|
||||
lexer.errors = append(lexer.errors, err)
|
||||
}
|
||||
|
||||
func (lexer *RulesLexer) getChar() byte {
|
||||
if lexer.current != 0 {
|
||||
lexer.buf = append(lexer.buf, lexer.current)
|
||||
}
|
||||
lexer.current = 0
|
||||
if b, err := lexer.src.ReadByte(); err == nil {
|
||||
if b == '\n' {
|
||||
lexer.line++
|
||||
lexer.pos = 0
|
||||
} else {
|
||||
lexer.pos++
|
||||
}
|
||||
lexer.current = b
|
||||
} else if err != io.EOF {
|
||||
glog.Fatal(err)
|
||||
}
|
||||
return lexer.current
|
||||
}
|
||||
|
||||
func (lexer *RulesLexer) token() string {
|
||||
return string(lexer.buf)
|
||||
}
|
||||
|
||||
func newRulesLexer(src io.Reader, singleExpr bool) *RulesLexer {
|
||||
lexer := &RulesLexer{
|
||||
startToken: START_RULES,
|
||||
src: bufio.NewReader(src),
|
||||
pos: 1,
|
||||
line: 1,
|
||||
}
|
||||
|
||||
if singleExpr {
|
||||
lexer.startToken = START_EXPRESSION
|
||||
}
|
||||
lexer.getChar()
|
||||
return lexer
|
||||
}
|
||||
|
||||
func lexAndParse(rulesReader io.Reader, singleExpr bool) (*RulesLexer, error) {
|
||||
lexer := newRulesLexer(rulesReader, singleExpr)
|
||||
ret := yyParse(lexer)
|
||||
if ret != 0 && len(lexer.errors) == 0 {
|
||||
lexer.Error("unknown parser error")
|
||||
}
|
||||
|
||||
if len(lexer.errors) > 0 {
|
||||
err := errors.New(strings.Join(lexer.errors, "\n"))
|
||||
return nil, err
|
||||
}
|
||||
return lexer, nil
|
||||
}
|
||||
|
||||
// LoadRulesFromReader parses rules from the provided reader and returns them.
|
||||
func LoadRulesFromReader(rulesReader io.Reader) ([]Rule, error) {
|
||||
lexer, err := lexAndParse(rulesReader, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return lexer.parsedRules, err
|
||||
}
|
||||
|
||||
// LoadRulesFromString parses rules from the provided string returns them.
|
||||
func LoadRulesFromString(rulesString string) ([]Rule, error) {
|
||||
rulesReader := strings.NewReader(rulesString)
|
||||
return LoadRulesFromReader(rulesReader)
|
||||
}
|
||||
|
||||
// LoadRulesFromFile parses rules from the file of the provided name and returns
|
||||
// them.
|
||||
func LoadRulesFromFile(fileName string) ([]Rule, error) {
|
||||
rulesReader, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return []Rule{}, err
|
||||
}
|
||||
defer rulesReader.Close()
|
||||
return LoadRulesFromReader(rulesReader)
|
||||
}
|
||||
|
||||
// LoadExprFromReader parses a single expression from the provided reader and
|
||||
// returns it as an AST node.
|
||||
func LoadExprFromReader(exprReader io.Reader) (ast.Node, error) {
|
||||
lexer, err := lexAndParse(exprReader, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return lexer.parsedExpr, err
|
||||
}
|
||||
|
||||
// LoadExprFromString parses a single expression from the provided string and
|
||||
// returns it as an AST node.
|
||||
func LoadExprFromString(exprString string) (ast.Node, error) {
|
||||
exprReader := strings.NewReader(exprString)
|
||||
return LoadExprFromReader(exprReader)
|
||||
}
|
||||
|
||||
// LoadExprFromFile parses a single expression from the file of the provided
|
||||
// name and returns it as an AST node.
|
||||
func LoadExprFromFile(fileName string) (ast.Node, error) {
|
||||
exprReader, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer exprReader.Close()
|
||||
return LoadExprFromReader(exprReader)
|
||||
}
|
|
@ -11,7 +11,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package manager
|
||||
package rules
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -20,15 +20,15 @@ import (
|
|||
|
||||
"github.com/golang/glog"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/notification"
|
||||
"github.com/prometheus/prometheus/rules"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
"github.com/prometheus/prometheus/storage"
|
||||
"github.com/prometheus/prometheus/storage/local"
|
||||
"github.com/prometheus/prometheus/templates"
|
||||
"github.com/prometheus/prometheus/utility"
|
||||
)
|
||||
|
||||
// Constants for instrumentation.
|
||||
|
@ -73,60 +73,61 @@ func init() {
|
|||
// A RuleManager manages recording and alerting rules. Create instances with
|
||||
// NewRuleManager.
|
||||
type RuleManager interface {
|
||||
// Load and add rules from rule files specified in the configuration.
|
||||
AddRulesFromConfig(config config.Config) error
|
||||
// Start the rule manager's periodic rule evaluation.
|
||||
Run()
|
||||
// Stop the rule manager's rule evaluation cycles.
|
||||
Stop()
|
||||
// Return all rules.
|
||||
Rules() []rules.Rule
|
||||
Rules() []Rule
|
||||
// Return all alerting rules.
|
||||
AlertingRules() []*rules.AlertingRule
|
||||
AlertingRules() []*AlertingRule
|
||||
}
|
||||
|
||||
type ruleManager struct {
|
||||
// Protects the rules list.
|
||||
sync.Mutex
|
||||
rules []rules.Rule
|
||||
rules []Rule
|
||||
|
||||
done chan bool
|
||||
|
||||
interval time.Duration
|
||||
storage local.Storage
|
||||
interval time.Duration
|
||||
queryEngine *promql.Engine
|
||||
|
||||
sampleAppender storage.SampleAppender
|
||||
notificationHandler *notification.NotificationHandler
|
||||
|
||||
prometheusURL string
|
||||
pathPrefix string
|
||||
pathPrefix string
|
||||
}
|
||||
|
||||
// RuleManagerOptions bundles options for the RuleManager.
|
||||
type RuleManagerOptions struct {
|
||||
EvaluationInterval time.Duration
|
||||
Storage local.Storage
|
||||
QueryEngine *promql.Engine
|
||||
|
||||
NotificationHandler *notification.NotificationHandler
|
||||
SampleAppender storage.SampleAppender
|
||||
|
||||
PrometheusURL string
|
||||
PathPrefix string
|
||||
PathPrefix string
|
||||
}
|
||||
|
||||
// NewRuleManager returns an implementation of RuleManager, ready to be started
|
||||
// by calling the Run method.
|
||||
func NewRuleManager(o *RuleManagerOptions) RuleManager {
|
||||
manager := &ruleManager{
|
||||
rules: []rules.Rule{},
|
||||
rules: []Rule{},
|
||||
done: make(chan bool),
|
||||
|
||||
interval: o.EvaluationInterval,
|
||||
storage: o.Storage,
|
||||
sampleAppender: o.SampleAppender,
|
||||
queryEngine: o.QueryEngine,
|
||||
notificationHandler: o.NotificationHandler,
|
||||
prometheusURL: o.PrometheusURL,
|
||||
}
|
||||
manager.queryEngine.RegisterAlertHandler("rule_manager", manager.AddAlertingRule)
|
||||
manager.queryEngine.RegisterRecordHandler("rule_manager", manager.AddRecordingRule)
|
||||
|
||||
return manager
|
||||
}
|
||||
|
||||
|
@ -162,7 +163,7 @@ func (m *ruleManager) Stop() {
|
|||
m.done <- true
|
||||
}
|
||||
|
||||
func (m *ruleManager) queueAlertNotifications(rule *rules.AlertingRule, timestamp clientmodel.Timestamp) {
|
||||
func (m *ruleManager) queueAlertNotifications(rule *AlertingRule, timestamp clientmodel.Timestamp) {
|
||||
activeAlerts := rule.ActiveAlerts()
|
||||
if len(activeAlerts) == 0 {
|
||||
return
|
||||
|
@ -170,7 +171,7 @@ func (m *ruleManager) queueAlertNotifications(rule *rules.AlertingRule, timestam
|
|||
|
||||
notifications := make(notification.NotificationReqs, 0, len(activeAlerts))
|
||||
for _, aa := range activeAlerts {
|
||||
if aa.State != rules.Firing {
|
||||
if aa.State != Firing {
|
||||
// BUG: In the future, make AlertManager support pending alerts?
|
||||
continue
|
||||
}
|
||||
|
@ -192,7 +193,7 @@ func (m *ruleManager) queueAlertNotifications(rule *rules.AlertingRule, timestam
|
|||
defs := "{{$labels := .Labels}}{{$value := .Value}}"
|
||||
|
||||
expand := func(text string) string {
|
||||
template := templates.NewTemplateExpander(defs+text, "__alert_"+rule.Name(), tmplData, timestamp, m.storage, m.pathPrefix)
|
||||
template := templates.NewTemplateExpander(defs+text, "__alert_"+rule.Name(), tmplData, timestamp, m.queryEngine, m.pathPrefix)
|
||||
result, err := template.Expand()
|
||||
if err != nil {
|
||||
result = err.Error()
|
||||
|
@ -205,12 +206,12 @@ func (m *ruleManager) queueAlertNotifications(rule *rules.AlertingRule, timestam
|
|||
Summary: expand(rule.Summary),
|
||||
Description: expand(rule.Description),
|
||||
Labels: aa.Labels.Merge(clientmodel.LabelSet{
|
||||
rules.AlertNameLabel: clientmodel.LabelValue(rule.Name()),
|
||||
AlertNameLabel: clientmodel.LabelValue(rule.Name()),
|
||||
}),
|
||||
Value: aa.Value,
|
||||
ActiveSince: aa.ActiveSince.Time(),
|
||||
RuleString: rule.String(),
|
||||
GeneratorURL: m.prometheusURL + rules.GraphLinkForExpression(rule.Vector.String()),
|
||||
GeneratorURL: m.prometheusURL + utility.GraphLinkForExpression(rule.Vector.String()),
|
||||
})
|
||||
}
|
||||
m.notificationHandler.SubmitReqs(notifications)
|
||||
|
@ -221,18 +222,18 @@ func (m *ruleManager) runIteration() {
|
|||
wg := sync.WaitGroup{}
|
||||
|
||||
m.Lock()
|
||||
rulesSnapshot := make([]rules.Rule, len(m.rules))
|
||||
rulesSnapshot := make([]Rule, len(m.rules))
|
||||
copy(rulesSnapshot, m.rules)
|
||||
m.Unlock()
|
||||
|
||||
for _, rule := range rulesSnapshot {
|
||||
wg.Add(1)
|
||||
// BUG(julius): Look at fixing thundering herd.
|
||||
go func(rule rules.Rule) {
|
||||
go func(rule Rule) {
|
||||
defer wg.Done()
|
||||
|
||||
start := time.Now()
|
||||
vector, err := rule.Eval(now, m.storage)
|
||||
vector, err := rule.Eval(now, m.queryEngine)
|
||||
duration := time.Since(start)
|
||||
|
||||
if err != nil {
|
||||
|
@ -242,17 +243,17 @@ func (m *ruleManager) runIteration() {
|
|||
}
|
||||
|
||||
switch r := rule.(type) {
|
||||
case *rules.AlertingRule:
|
||||
case *AlertingRule:
|
||||
m.queueAlertNotifications(r, now)
|
||||
evalDuration.WithLabelValues(alertingRuleType).Observe(
|
||||
float64(duration / time.Millisecond),
|
||||
)
|
||||
case *rules.RecordingRule:
|
||||
case *RecordingRule:
|
||||
evalDuration.WithLabelValues(recordingRuleType).Observe(
|
||||
float64(duration / time.Millisecond),
|
||||
)
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown rule type: %T", rule))
|
||||
panic(fmt.Errorf("Unknown rule type: %T", rule))
|
||||
}
|
||||
|
||||
for _, s := range vector {
|
||||
|
@ -267,35 +268,40 @@ func (m *ruleManager) runIteration() {
|
|||
wg.Wait()
|
||||
}
|
||||
|
||||
func (m *ruleManager) AddRulesFromConfig(config config.Config) error {
|
||||
for _, ruleFile := range config.Global.RuleFile {
|
||||
newRules, err := rules.LoadRulesFromFile(ruleFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s", ruleFile, err)
|
||||
}
|
||||
m.Lock()
|
||||
m.rules = append(m.rules, newRules...)
|
||||
m.Unlock()
|
||||
}
|
||||
func (m *ruleManager) AddAlertingRule(ctx context.Context, r *promql.AlertStmt) error {
|
||||
rule := NewAlertingRule(r.Name, r.Expr, r.Duration, r.Labels, r.Summary, r.Description)
|
||||
|
||||
m.Lock()
|
||||
m.rules = append(m.rules, rule)
|
||||
m.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ruleManager) Rules() []rules.Rule {
|
||||
func (m *ruleManager) AddRecordingRule(ctx context.Context, r *promql.RecordStmt) error {
|
||||
rule := &RecordingRule{r.Name, r.Expr, r.Labels}
|
||||
|
||||
m.Lock()
|
||||
m.rules = append(m.rules, rule)
|
||||
m.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ruleManager) Rules() []Rule {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
rules := make([]rules.Rule, len(m.rules))
|
||||
rules := make([]Rule, len(m.rules))
|
||||
copy(rules, m.rules)
|
||||
return rules
|
||||
}
|
||||
|
||||
func (m *ruleManager) AlertingRules() []*rules.AlertingRule {
|
||||
func (m *ruleManager) AlertingRules() []*AlertingRule {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
alerts := []*rules.AlertingRule{}
|
||||
alerts := []*AlertingRule{}
|
||||
for _, rule := range m.rules {
|
||||
if alertingRule, ok := rule.(*rules.AlertingRule); ok {
|
||||
if alertingRule, ok := rule.(*AlertingRule); ok {
|
||||
alerts = append(alerts, alertingRule)
|
||||
}
|
||||
}
|
281
rules/parser.y
281
rules/parser.y
|
@ -1,281 +0,0 @@
|
|||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
%{
|
||||
package rules
|
||||
|
||||
import (
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
|
||||
"github.com/prometheus/prometheus/rules/ast"
|
||||
"github.com/prometheus/prometheus/storage/metric"
|
||||
)
|
||||
%}
|
||||
|
||||
%union {
|
||||
num clientmodel.SampleValue
|
||||
str string
|
||||
ruleNode ast.Node
|
||||
ruleNodeSlice []ast.Node
|
||||
boolean bool
|
||||
labelNameSlice clientmodel.LabelNames
|
||||
labelSet clientmodel.LabelSet
|
||||
labelMatcher *metric.LabelMatcher
|
||||
labelMatchers metric.LabelMatchers
|
||||
vectorMatching *vectorMatching
|
||||
}
|
||||
|
||||
/* We simulate multiple start symbols for closely-related grammars via dummy tokens. See
|
||||
http://www.gnu.org/software/bison/manual/html_node/Multiple-start_002dsymbols.html
|
||||
Reason: we want to be able to parse lists of named rules as well as single expressions.
|
||||
*/
|
||||
%token START_RULES START_EXPRESSION
|
||||
|
||||
%token <str> IDENTIFIER STRING DURATION METRICNAME
|
||||
%token <num> NUMBER
|
||||
%token PERMANENT GROUP_OP KEEPING_EXTRA OFFSET MATCH_OP
|
||||
%token <str> AGGR_OP CMP_OP ADDITIVE_OP MULT_OP MATCH_MOD
|
||||
%token ALERT IF FOR WITH SUMMARY DESCRIPTION
|
||||
|
||||
%type <ruleNodeSlice> func_arg_list
|
||||
%type <labelNameSlice> label_list grouping_opts
|
||||
%type <labelSet> label_assign label_assign_list rule_labels
|
||||
%type <labelMatcher> label_match
|
||||
%type <labelMatchers> label_match_list label_matches
|
||||
%type <vectorMatching> vector_matching
|
||||
%type <ruleNode> rule_expr func_arg
|
||||
%type <boolean> qualifier extra_labels_opts
|
||||
%type <str> for_duration metric_name label_match_type offset_opts
|
||||
|
||||
%right '='
|
||||
%left CMP_OP
|
||||
%left ADDITIVE_OP
|
||||
%left MULT_OP
|
||||
%start start
|
||||
|
||||
%%
|
||||
start : START_RULES rules_stat_list
|
||||
| START_EXPRESSION saved_rule_expr
|
||||
;
|
||||
|
||||
rules_stat_list : /* empty */
|
||||
| rules_stat_list rules_stat
|
||||
;
|
||||
|
||||
saved_rule_expr : rule_expr
|
||||
{ yylex.(*RulesLexer).parsedExpr = $1 }
|
||||
;
|
||||
|
||||
|
||||
rules_stat : qualifier metric_name rule_labels '=' rule_expr
|
||||
{
|
||||
rule, err := CreateRecordingRule($2, $3, $5, $1)
|
||||
if err != nil { yylex.Error(err.Error()); return 1 }
|
||||
yylex.(*RulesLexer).parsedRules = append(yylex.(*RulesLexer).parsedRules, rule)
|
||||
}
|
||||
| ALERT IDENTIFIER IF rule_expr for_duration WITH rule_labels SUMMARY STRING DESCRIPTION STRING
|
||||
{
|
||||
rule, err := CreateAlertingRule($2, $4, $5, $7, $9, $11)
|
||||
if err != nil { yylex.Error(err.Error()); return 1 }
|
||||
yylex.(*RulesLexer).parsedRules = append(yylex.(*RulesLexer).parsedRules, rule)
|
||||
}
|
||||
;
|
||||
|
||||
for_duration : /* empty */
|
||||
{ $$ = "0s" }
|
||||
| FOR DURATION
|
||||
{ $$ = $2 }
|
||||
;
|
||||
|
||||
qualifier : /* empty */
|
||||
{ $$ = false }
|
||||
| PERMANENT
|
||||
{ $$ = true }
|
||||
;
|
||||
|
||||
metric_name : METRICNAME
|
||||
{ $$ = $1 }
|
||||
| IDENTIFIER
|
||||
{ $$ = $1 }
|
||||
;
|
||||
|
||||
rule_labels : /* empty */
|
||||
{ $$ = clientmodel.LabelSet{} }
|
||||
| '{' label_assign_list '}'
|
||||
{ $$ = $2 }
|
||||
| '{' '}'
|
||||
{ $$ = clientmodel.LabelSet{} }
|
||||
|
||||
label_assign_list : label_assign
|
||||
{ $$ = $1 }
|
||||
| label_assign_list ',' label_assign
|
||||
{ for k, v := range $3 { $$[k] = v } }
|
||||
;
|
||||
|
||||
label_assign : IDENTIFIER '=' STRING
|
||||
{ $$ = clientmodel.LabelSet{ clientmodel.LabelName($1): clientmodel.LabelValue($3) } }
|
||||
;
|
||||
|
||||
label_matches : /* empty */
|
||||
{ $$ = metric.LabelMatchers{} }
|
||||
| '{' '}'
|
||||
{ $$ = metric.LabelMatchers{} }
|
||||
| '{' label_match_list '}'
|
||||
{ $$ = $2 }
|
||||
;
|
||||
|
||||
label_match_list : label_match
|
||||
{ $$ = metric.LabelMatchers{$1} }
|
||||
| label_match_list ',' label_match
|
||||
{ $$ = append($$, $3) }
|
||||
;
|
||||
|
||||
label_match : IDENTIFIER label_match_type STRING
|
||||
{
|
||||
var err error
|
||||
$$, err = newLabelMatcher($2, clientmodel.LabelName($1), clientmodel.LabelValue($3))
|
||||
if err != nil { yylex.Error(err.Error()); return 1 }
|
||||
}
|
||||
;
|
||||
|
||||
label_match_type : '='
|
||||
{ $$ = "=" }
|
||||
| CMP_OP
|
||||
{ $$ = $1 }
|
||||
;
|
||||
|
||||
offset_opts : /* empty */
|
||||
{ $$ = "0s" }
|
||||
| OFFSET DURATION
|
||||
{ $$ = $2 }
|
||||
;
|
||||
|
||||
rule_expr : '(' rule_expr ')'
|
||||
{ $$ = $2 }
|
||||
| '{' label_match_list '}' offset_opts
|
||||
{
|
||||
var err error
|
||||
$$, err = NewVectorSelector($2, $4)
|
||||
if err != nil { yylex.Error(err.Error()); return 1 }
|
||||
}
|
||||
| metric_name label_matches offset_opts
|
||||
{
|
||||
var err error
|
||||
m, err := metric.NewLabelMatcher(metric.Equal, clientmodel.MetricNameLabel, clientmodel.LabelValue($1))
|
||||
if err != nil { yylex.Error(err.Error()); return 1 }
|
||||
$2 = append($2, m)
|
||||
$$, err = NewVectorSelector($2, $3)
|
||||
if err != nil { yylex.Error(err.Error()); return 1 }
|
||||
}
|
||||
| IDENTIFIER '(' func_arg_list ')'
|
||||
{
|
||||
var err error
|
||||
$$, err = NewFunctionCall($1, $3)
|
||||
if err != nil { yylex.Error(err.Error()); return 1 }
|
||||
}
|
||||
| IDENTIFIER '(' ')'
|
||||
{
|
||||
var err error
|
||||
$$, err = NewFunctionCall($1, []ast.Node{})
|
||||
if err != nil { yylex.Error(err.Error()); return 1 }
|
||||
}
|
||||
| rule_expr '[' DURATION ']' offset_opts
|
||||
{
|
||||
var err error
|
||||
$$, err = NewMatrixSelector($1, $3, $5)
|
||||
if err != nil { yylex.Error(err.Error()); return 1 }
|
||||
}
|
||||
| AGGR_OP '(' rule_expr ')' grouping_opts extra_labels_opts
|
||||
{
|
||||
var err error
|
||||
$$, err = NewVectorAggregation($1, $3, $5, $6)
|
||||
if err != nil { yylex.Error(err.Error()); return 1 }
|
||||
}
|
||||
| AGGR_OP grouping_opts extra_labels_opts '(' rule_expr ')'
|
||||
{
|
||||
var err error
|
||||
$$, err = NewVectorAggregation($1, $5, $2, $3)
|
||||
if err != nil { yylex.Error(err.Error()); return 1 }
|
||||
}
|
||||
/* Yacc can only attach associativity to terminals, so we
|
||||
* have to list all operators here. */
|
||||
| rule_expr ADDITIVE_OP vector_matching rule_expr
|
||||
{
|
||||
var err error
|
||||
$$, err = NewArithExpr($2, $1, $4, $3)
|
||||
if err != nil { yylex.Error(err.Error()); return 1 }
|
||||
}
|
||||
| rule_expr MULT_OP vector_matching rule_expr
|
||||
{
|
||||
var err error
|
||||
$$, err = NewArithExpr($2, $1, $4, $3)
|
||||
if err != nil { yylex.Error(err.Error()); return 1 }
|
||||
}
|
||||
| rule_expr CMP_OP vector_matching rule_expr
|
||||
{
|
||||
var err error
|
||||
$$, err = NewArithExpr($2, $1, $4, $3)
|
||||
if err != nil { yylex.Error(err.Error()); return 1 }
|
||||
}
|
||||
| NUMBER
|
||||
{ $$ = NewScalarLiteral($1, "+")}
|
||||
| ADDITIVE_OP NUMBER
|
||||
{ $$ = NewScalarLiteral($2, $1)}
|
||||
;
|
||||
|
||||
extra_labels_opts : /* empty */
|
||||
{ $$ = false }
|
||||
| KEEPING_EXTRA
|
||||
{ $$ = true }
|
||||
;
|
||||
|
||||
vector_matching : /* empty */
|
||||
{ $$ = nil }
|
||||
| MATCH_OP '(' label_list ')'
|
||||
{
|
||||
var err error
|
||||
$$, err = newVectorMatching("", $3, nil)
|
||||
if err != nil { yylex.Error(err.Error()); return 1 }
|
||||
}
|
||||
| MATCH_OP '(' label_list ')' MATCH_MOD '(' label_list ')'
|
||||
{
|
||||
var err error
|
||||
$$, err = newVectorMatching($5, $3, $7)
|
||||
if err != nil { yylex.Error(err.Error()); return 1 }
|
||||
}
|
||||
;
|
||||
|
||||
grouping_opts :
|
||||
{ $$ = clientmodel.LabelNames{} }
|
||||
| GROUP_OP '(' label_list ')'
|
||||
{ $$ = $3 }
|
||||
;
|
||||
|
||||
label_list : IDENTIFIER
|
||||
{ $$ = clientmodel.LabelNames{clientmodel.LabelName($1)} }
|
||||
| label_list ',' IDENTIFIER
|
||||
{ $$ = append($$, clientmodel.LabelName($3)) }
|
||||
;
|
||||
|
||||
func_arg_list : func_arg
|
||||
{ $$ = []ast.Node{$1} }
|
||||
| func_arg_list ',' func_arg
|
||||
{ $$ = append($$, $3) }
|
||||
;
|
||||
|
||||
func_arg : rule_expr
|
||||
{ $$ = $1 }
|
||||
| STRING
|
||||
{ $$ = ast.NewStringLiteral($1) }
|
||||
;
|
||||
%%
|
|
@ -1,784 +0,0 @@
|
|||
//line parser.y:15
|
||||
package rules
|
||||
|
||||
import __yyfmt__ "fmt"
|
||||
|
||||
//line parser.y:15
|
||||
import (
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
|
||||
"github.com/prometheus/prometheus/rules/ast"
|
||||
"github.com/prometheus/prometheus/storage/metric"
|
||||
)
|
||||
|
||||
//line parser.y:25
|
||||
type yySymType struct {
|
||||
yys int
|
||||
num clientmodel.SampleValue
|
||||
str string
|
||||
ruleNode ast.Node
|
||||
ruleNodeSlice []ast.Node
|
||||
boolean bool
|
||||
labelNameSlice clientmodel.LabelNames
|
||||
labelSet clientmodel.LabelSet
|
||||
labelMatcher *metric.LabelMatcher
|
||||
labelMatchers metric.LabelMatchers
|
||||
vectorMatching *vectorMatching
|
||||
}
|
||||
|
||||
const START_RULES = 57346
|
||||
const START_EXPRESSION = 57347
|
||||
const IDENTIFIER = 57348
|
||||
const STRING = 57349
|
||||
const DURATION = 57350
|
||||
const METRICNAME = 57351
|
||||
const NUMBER = 57352
|
||||
const PERMANENT = 57353
|
||||
const GROUP_OP = 57354
|
||||
const KEEPING_EXTRA = 57355
|
||||
const OFFSET = 57356
|
||||
const MATCH_OP = 57357
|
||||
const AGGR_OP = 57358
|
||||
const CMP_OP = 57359
|
||||
const ADDITIVE_OP = 57360
|
||||
const MULT_OP = 57361
|
||||
const MATCH_MOD = 57362
|
||||
const ALERT = 57363
|
||||
const IF = 57364
|
||||
const FOR = 57365
|
||||
const WITH = 57366
|
||||
const SUMMARY = 57367
|
||||
const DESCRIPTION = 57368
|
||||
|
||||
var yyToknames = []string{
|
||||
"START_RULES",
|
||||
"START_EXPRESSION",
|
||||
"IDENTIFIER",
|
||||
"STRING",
|
||||
"DURATION",
|
||||
"METRICNAME",
|
||||
"NUMBER",
|
||||
"PERMANENT",
|
||||
"GROUP_OP",
|
||||
"KEEPING_EXTRA",
|
||||
"OFFSET",
|
||||
"MATCH_OP",
|
||||
"AGGR_OP",
|
||||
"CMP_OP",
|
||||
"ADDITIVE_OP",
|
||||
"MULT_OP",
|
||||
"MATCH_MOD",
|
||||
"ALERT",
|
||||
"IF",
|
||||
"FOR",
|
||||
"WITH",
|
||||
"SUMMARY",
|
||||
"DESCRIPTION",
|
||||
"'='",
|
||||
}
|
||||
var yyStatenames = []string{}
|
||||
|
||||
const yyEofCode = 1
|
||||
const yyErrCode = 2
|
||||
const yyMaxDepth = 200
|
||||
|
||||
//line parser.y:281
|
||||
|
||||
//line yacctab:1
|
||||
var yyExca = []int{
|
||||
-1, 1,
|
||||
1, -1,
|
||||
-2, 0,
|
||||
-1, 4,
|
||||
1, 1,
|
||||
-2, 10,
|
||||
}
|
||||
|
||||
const yyNprod = 56
|
||||
const yyPrivate = 57344
|
||||
|
||||
var yyTokenNames []string
|
||||
var yyStates []string
|
||||
|
||||
const yyLast = 159
|
||||
|
||||
var yyAct = []int{
|
||||
|
||||
78, 61, 83, 58, 55, 54, 31, 48, 6, 25,
|
||||
20, 21, 23, 21, 10, 56, 64, 14, 12, 10,
|
||||
56, 19, 14, 12, 11, 19, 13, 19, 92, 11,
|
||||
113, 13, 22, 20, 21, 57, 8, 32, 109, 7,
|
||||
53, 8, 77, 65, 7, 67, 68, 101, 19, 22,
|
||||
20, 21, 70, 69, 10, 98, 30, 14, 12, 22,
|
||||
20, 21, 94, 95, 11, 19, 13, 87, 85, 92,
|
||||
96, 99, 86, 84, 76, 19, 8, 66, 60, 7,
|
||||
29, 88, 90, 89, 24, 93, 22, 20, 21, 22,
|
||||
20, 21, 92, 100, 91, 75, 82, 74, 103, 73,
|
||||
43, 42, 19, 44, 43, 19, 26, 108, 62, 47,
|
||||
111, 28, 80, 51, 114, 110, 38, 105, 63, 46,
|
||||
18, 107, 39, 9, 49, 59, 32, 33, 35, 50,
|
||||
17, 14, 106, 72, 37, 115, 112, 104, 40, 41,
|
||||
34, 71, 79, 84, 102, 26, 36, 2, 3, 15,
|
||||
5, 4, 1, 45, 97, 16, 27, 81, 52,
|
||||
}
|
||||
var yyPact = []int{
|
||||
|
||||
143, -1000, -1000, 48, 109, -1000, 72, 48, 139, 83,
|
||||
49, 25, -1000, 117, -1000, -1000, 122, 140, -1000, 126,
|
||||
107, 107, 107, 69, 74, -1000, 92, 110, 100, 8,
|
||||
48, 112, 47, -1000, 80, -1000, 96, -18, 48, 46,
|
||||
48, 48, -1000, 139, 110, 134, -1000, -1000, -1000, 125,
|
||||
-1000, 70, 65, -1000, -1000, 72, -1000, 42, 11, -1000,
|
||||
136, 85, 67, 48, 110, -6, 136, -12, -8, -1000,
|
||||
-1000, -1000, -1000, -1000, -1000, 13, 114, 48, 62, -1000,
|
||||
48, 33, -1000, -1000, 43, 32, -1000, 39, -1000, 112,
|
||||
15, -1000, 138, 72, -1000, 137, 130, 93, 124, 101,
|
||||
-1000, -1000, -1000, -1000, -1000, 80, -1000, 7, 90, 136,
|
||||
129, -2, 88, -1000, 128, -1000,
|
||||
}
|
||||
var yyPgo = []int{
|
||||
|
||||
0, 158, 0, 6, 2, 157, 1, 9, 84, 156,
|
||||
116, 4, 5, 155, 3, 154, 123, 153, 7, 152,
|
||||
151, 150, 149,
|
||||
}
|
||||
var yyR1 = []int{
|
||||
|
||||
0, 19, 19, 20, 20, 21, 22, 22, 15, 15,
|
||||
13, 13, 16, 16, 6, 6, 6, 5, 5, 4,
|
||||
9, 9, 9, 8, 8, 7, 17, 17, 18, 18,
|
||||
11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
|
||||
11, 11, 11, 14, 14, 10, 10, 10, 3, 3,
|
||||
2, 2, 1, 1, 12, 12,
|
||||
}
|
||||
var yyR2 = []int{
|
||||
|
||||
0, 2, 2, 0, 2, 1, 5, 11, 0, 2,
|
||||
0, 1, 1, 1, 0, 3, 2, 1, 3, 3,
|
||||
0, 2, 3, 1, 3, 3, 1, 1, 0, 2,
|
||||
3, 4, 3, 4, 3, 5, 6, 6, 4, 4,
|
||||
4, 1, 2, 0, 1, 0, 4, 8, 0, 4,
|
||||
1, 3, 1, 3, 1, 1,
|
||||
}
|
||||
var yyChk = []int{
|
||||
|
||||
-1000, -19, 4, 5, -20, -21, -11, 31, 28, -16,
|
||||
6, 16, 10, 18, 9, -22, -13, 21, 11, 33,
|
||||
18, 19, 17, -11, -8, -7, 6, -9, 28, 31,
|
||||
31, -3, 12, 10, -16, 6, 6, 8, -10, 15,
|
||||
-10, -10, 32, 30, 29, -17, 27, 17, -18, 14,
|
||||
29, -8, -1, 32, -12, -11, 7, -11, -14, 13,
|
||||
31, -6, 28, 22, 34, -11, 31, -11, -11, -7,
|
||||
-18, 7, 8, 29, 32, 30, 32, 31, -2, 6,
|
||||
27, -5, 29, -4, 6, -11, -18, -2, -12, -3,
|
||||
-11, 32, 30, -11, 29, 30, 27, -15, 23, 32,
|
||||
-14, 32, 6, -4, 7, 24, 8, 20, -6, 31,
|
||||
25, -2, 7, 32, 26, 7,
|
||||
}
|
||||
var yyDef = []int{
|
||||
|
||||
0, -2, 3, 0, -2, 2, 5, 0, 0, 20,
|
||||
13, 48, 41, 0, 12, 4, 0, 0, 11, 0,
|
||||
45, 45, 45, 0, 0, 23, 0, 28, 0, 0,
|
||||
0, 43, 0, 42, 14, 13, 0, 0, 0, 0,
|
||||
0, 0, 30, 0, 28, 0, 26, 27, 32, 0,
|
||||
21, 0, 0, 34, 52, 54, 55, 0, 0, 44,
|
||||
0, 0, 0, 0, 28, 38, 0, 39, 40, 24,
|
||||
31, 25, 29, 22, 33, 0, 48, 0, 0, 50,
|
||||
0, 0, 16, 17, 0, 8, 35, 0, 53, 43,
|
||||
0, 49, 0, 6, 15, 0, 0, 0, 0, 46,
|
||||
36, 37, 51, 18, 19, 14, 9, 0, 0, 0,
|
||||
0, 0, 0, 47, 0, 7,
|
||||
}
|
||||
var yyTok1 = []int{
|
||||
|
||||
1, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
31, 32, 3, 3, 30, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 27, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 33, 3, 34, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 28, 3, 29,
|
||||
}
|
||||
var yyTok2 = []int{
|
||||
|
||||
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,
|
||||
}
|
||||
var yyTok3 = []int{
|
||||
0,
|
||||
}
|
||||
|
||||
//line yaccpar:1
|
||||
|
||||
/* parser for yacc output */
|
||||
|
||||
var yyDebug = 0
|
||||
|
||||
type yyLexer interface {
|
||||
Lex(lval *yySymType) int
|
||||
Error(s string)
|
||||
}
|
||||
|
||||
const yyFlag = -1000
|
||||
|
||||
func yyTokname(c int) string {
|
||||
// 4 is TOKSTART above
|
||||
if c >= 4 && c-4 < len(yyToknames) {
|
||||
if yyToknames[c-4] != "" {
|
||||
return yyToknames[c-4]
|
||||
}
|
||||
}
|
||||
return __yyfmt__.Sprintf("tok-%v", c)
|
||||
}
|
||||
|
||||
func yyStatname(s int) string {
|
||||
if s >= 0 && s < len(yyStatenames) {
|
||||
if yyStatenames[s] != "" {
|
||||
return yyStatenames[s]
|
||||
}
|
||||
}
|
||||
return __yyfmt__.Sprintf("state-%v", s)
|
||||
}
|
||||
|
||||
func yylex1(lex yyLexer, lval *yySymType) int {
|
||||
c := 0
|
||||
char := lex.Lex(lval)
|
||||
if char <= 0 {
|
||||
c = yyTok1[0]
|
||||
goto out
|
||||
}
|
||||
if char < len(yyTok1) {
|
||||
c = yyTok1[char]
|
||||
goto out
|
||||
}
|
||||
if char >= yyPrivate {
|
||||
if char < yyPrivate+len(yyTok2) {
|
||||
c = yyTok2[char-yyPrivate]
|
||||
goto out
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(yyTok3); i += 2 {
|
||||
c = yyTok3[i+0]
|
||||
if c == char {
|
||||
c = yyTok3[i+1]
|
||||
goto out
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
if c == 0 {
|
||||
c = yyTok2[1] /* unknown char */
|
||||
}
|
||||
if yyDebug >= 3 {
|
||||
__yyfmt__.Printf("lex %s(%d)\n", yyTokname(c), uint(char))
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func yyParse(yylex yyLexer) int {
|
||||
var yyn int
|
||||
var yylval yySymType
|
||||
var yyVAL yySymType
|
||||
yyS := make([]yySymType, yyMaxDepth)
|
||||
|
||||
Nerrs := 0 /* number of errors */
|
||||
Errflag := 0 /* error recovery flag */
|
||||
yystate := 0
|
||||
yychar := -1
|
||||
yyp := -1
|
||||
goto yystack
|
||||
|
||||
ret0:
|
||||
return 0
|
||||
|
||||
ret1:
|
||||
return 1
|
||||
|
||||
yystack:
|
||||
/* put a state and value onto the stack */
|
||||
if yyDebug >= 4 {
|
||||
__yyfmt__.Printf("char %v in %v\n", yyTokname(yychar), yyStatname(yystate))
|
||||
}
|
||||
|
||||
yyp++
|
||||
if yyp >= len(yyS) {
|
||||
nyys := make([]yySymType, len(yyS)*2)
|
||||
copy(nyys, yyS)
|
||||
yyS = nyys
|
||||
}
|
||||
yyS[yyp] = yyVAL
|
||||
yyS[yyp].yys = yystate
|
||||
|
||||
yynewstate:
|
||||
yyn = yyPact[yystate]
|
||||
if yyn <= yyFlag {
|
||||
goto yydefault /* simple state */
|
||||
}
|
||||
if yychar < 0 {
|
||||
yychar = yylex1(yylex, &yylval)
|
||||
}
|
||||
yyn += yychar
|
||||
if yyn < 0 || yyn >= yyLast {
|
||||
goto yydefault
|
||||
}
|
||||
yyn = yyAct[yyn]
|
||||
if yyChk[yyn] == yychar { /* valid shift */
|
||||
yychar = -1
|
||||
yyVAL = yylval
|
||||
yystate = yyn
|
||||
if Errflag > 0 {
|
||||
Errflag--
|
||||
}
|
||||
goto yystack
|
||||
}
|
||||
|
||||
yydefault:
|
||||
/* default state action */
|
||||
yyn = yyDef[yystate]
|
||||
if yyn == -2 {
|
||||
if yychar < 0 {
|
||||
yychar = yylex1(yylex, &yylval)
|
||||
}
|
||||
|
||||
/* look through exception table */
|
||||
xi := 0
|
||||
for {
|
||||
if yyExca[xi+0] == -1 && yyExca[xi+1] == yystate {
|
||||
break
|
||||
}
|
||||
xi += 2
|
||||
}
|
||||
for xi += 2; ; xi += 2 {
|
||||
yyn = yyExca[xi+0]
|
||||
if yyn < 0 || yyn == yychar {
|
||||
break
|
||||
}
|
||||
}
|
||||
yyn = yyExca[xi+1]
|
||||
if yyn < 0 {
|
||||
goto ret0
|
||||
}
|
||||
}
|
||||
if yyn == 0 {
|
||||
/* error ... attempt to resume parsing */
|
||||
switch Errflag {
|
||||
case 0: /* brand new error */
|
||||
yylex.Error("syntax error")
|
||||
Nerrs++
|
||||
if yyDebug >= 1 {
|
||||
__yyfmt__.Printf("%s", yyStatname(yystate))
|
||||
__yyfmt__.Printf(" saw %s\n", yyTokname(yychar))
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case 1, 2: /* incompletely recovered error ... try again */
|
||||
Errflag = 3
|
||||
|
||||
/* find a state where "error" is a legal shift action */
|
||||
for yyp >= 0 {
|
||||
yyn = yyPact[yyS[yyp].yys] + yyErrCode
|
||||
if yyn >= 0 && yyn < yyLast {
|
||||
yystate = yyAct[yyn] /* simulate a shift of "error" */
|
||||
if yyChk[yystate] == yyErrCode {
|
||||
goto yystack
|
||||
}
|
||||
}
|
||||
|
||||
/* the current p has no shift on "error", pop stack */
|
||||
if yyDebug >= 2 {
|
||||
__yyfmt__.Printf("error recovery pops state %d\n", yyS[yyp].yys)
|
||||
}
|
||||
yyp--
|
||||
}
|
||||
/* there is no state on the stack with an error shift ... abort */
|
||||
goto ret1
|
||||
|
||||
case 3: /* no shift yet; clobber input char */
|
||||
if yyDebug >= 2 {
|
||||
__yyfmt__.Printf("error recovery discards %s\n", yyTokname(yychar))
|
||||
}
|
||||
if yychar == yyEofCode {
|
||||
goto ret1
|
||||
}
|
||||
yychar = -1
|
||||
goto yynewstate /* try again in the same state */
|
||||
}
|
||||
}
|
||||
|
||||
/* reduction by production yyn */
|
||||
if yyDebug >= 2 {
|
||||
__yyfmt__.Printf("reduce %v in:\n\t%v\n", yyn, yyStatname(yystate))
|
||||
}
|
||||
|
||||
yynt := yyn
|
||||
yypt := yyp
|
||||
_ = yypt // guard against "declared and not used"
|
||||
|
||||
yyp -= yyR2[yyn]
|
||||
yyVAL = yyS[yyp+1]
|
||||
|
||||
/* consult goto table to find next state */
|
||||
yyn = yyR1[yyn]
|
||||
yyg := yyPgo[yyn]
|
||||
yyj := yyg + yyS[yyp].yys + 1
|
||||
|
||||
if yyj >= yyLast {
|
||||
yystate = yyAct[yyg]
|
||||
} else {
|
||||
yystate = yyAct[yyj]
|
||||
if yyChk[yystate] != -yyn {
|
||||
yystate = yyAct[yyg]
|
||||
}
|
||||
}
|
||||
// dummy call; replaced with literal code
|
||||
switch yynt {
|
||||
|
||||
case 5:
|
||||
//line parser.y:76
|
||||
{
|
||||
yylex.(*RulesLexer).parsedExpr = yyS[yypt-0].ruleNode
|
||||
}
|
||||
case 6:
|
||||
//line parser.y:81
|
||||
{
|
||||
rule, err := CreateRecordingRule(yyS[yypt-3].str, yyS[yypt-2].labelSet, yyS[yypt-0].ruleNode, yyS[yypt-4].boolean)
|
||||
if err != nil {
|
||||
yylex.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
yylex.(*RulesLexer).parsedRules = append(yylex.(*RulesLexer).parsedRules, rule)
|
||||
}
|
||||
case 7:
|
||||
//line parser.y:87
|
||||
{
|
||||
rule, err := CreateAlertingRule(yyS[yypt-9].str, yyS[yypt-7].ruleNode, yyS[yypt-6].str, yyS[yypt-4].labelSet, yyS[yypt-2].str, yyS[yypt-0].str)
|
||||
if err != nil {
|
||||
yylex.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
yylex.(*RulesLexer).parsedRules = append(yylex.(*RulesLexer).parsedRules, rule)
|
||||
}
|
||||
case 8:
|
||||
//line parser.y:95
|
||||
{
|
||||
yyVAL.str = "0s"
|
||||
}
|
||||
case 9:
|
||||
//line parser.y:97
|
||||
{
|
||||
yyVAL.str = yyS[yypt-0].str
|
||||
}
|
||||
case 10:
|
||||
//line parser.y:101
|
||||
{
|
||||
yyVAL.boolean = false
|
||||
}
|
||||
case 11:
|
||||
//line parser.y:103
|
||||
{
|
||||
yyVAL.boolean = true
|
||||
}
|
||||
case 12:
|
||||
//line parser.y:107
|
||||
{
|
||||
yyVAL.str = yyS[yypt-0].str
|
||||
}
|
||||
case 13:
|
||||
//line parser.y:109
|
||||
{
|
||||
yyVAL.str = yyS[yypt-0].str
|
||||
}
|
||||
case 14:
|
||||
//line parser.y:113
|
||||
{
|
||||
yyVAL.labelSet = clientmodel.LabelSet{}
|
||||
}
|
||||
case 15:
|
||||
//line parser.y:115
|
||||
{
|
||||
yyVAL.labelSet = yyS[yypt-1].labelSet
|
||||
}
|
||||
case 16:
|
||||
//line parser.y:117
|
||||
{
|
||||
yyVAL.labelSet = clientmodel.LabelSet{}
|
||||
}
|
||||
case 17:
|
||||
//line parser.y:120
|
||||
{
|
||||
yyVAL.labelSet = yyS[yypt-0].labelSet
|
||||
}
|
||||
case 18:
|
||||
//line parser.y:122
|
||||
{
|
||||
for k, v := range yyS[yypt-0].labelSet {
|
||||
yyVAL.labelSet[k] = v
|
||||
}
|
||||
}
|
||||
case 19:
|
||||
//line parser.y:126
|
||||
{
|
||||
yyVAL.labelSet = clientmodel.LabelSet{clientmodel.LabelName(yyS[yypt-2].str): clientmodel.LabelValue(yyS[yypt-0].str)}
|
||||
}
|
||||
case 20:
|
||||
//line parser.y:130
|
||||
{
|
||||
yyVAL.labelMatchers = metric.LabelMatchers{}
|
||||
}
|
||||
case 21:
|
||||
//line parser.y:132
|
||||
{
|
||||
yyVAL.labelMatchers = metric.LabelMatchers{}
|
||||
}
|
||||
case 22:
|
||||
//line parser.y:134
|
||||
{
|
||||
yyVAL.labelMatchers = yyS[yypt-1].labelMatchers
|
||||
}
|
||||
case 23:
|
||||
//line parser.y:138
|
||||
{
|
||||
yyVAL.labelMatchers = metric.LabelMatchers{yyS[yypt-0].labelMatcher}
|
||||
}
|
||||
case 24:
|
||||
//line parser.y:140
|
||||
{
|
||||
yyVAL.labelMatchers = append(yyVAL.labelMatchers, yyS[yypt-0].labelMatcher)
|
||||
}
|
||||
case 25:
|
||||
//line parser.y:144
|
||||
{
|
||||
var err error
|
||||
yyVAL.labelMatcher, err = newLabelMatcher(yyS[yypt-1].str, clientmodel.LabelName(yyS[yypt-2].str), clientmodel.LabelValue(yyS[yypt-0].str))
|
||||
if err != nil {
|
||||
yylex.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
}
|
||||
case 26:
|
||||
//line parser.y:152
|
||||
{
|
||||
yyVAL.str = "="
|
||||
}
|
||||
case 27:
|
||||
//line parser.y:154
|
||||
{
|
||||
yyVAL.str = yyS[yypt-0].str
|
||||
}
|
||||
case 28:
|
||||
//line parser.y:158
|
||||
{
|
||||
yyVAL.str = "0s"
|
||||
}
|
||||
case 29:
|
||||
//line parser.y:160
|
||||
{
|
||||
yyVAL.str = yyS[yypt-0].str
|
||||
}
|
||||
case 30:
|
||||
//line parser.y:164
|
||||
{
|
||||
yyVAL.ruleNode = yyS[yypt-1].ruleNode
|
||||
}
|
||||
case 31:
|
||||
//line parser.y:166
|
||||
{
|
||||
var err error
|
||||
yyVAL.ruleNode, err = NewVectorSelector(yyS[yypt-2].labelMatchers, yyS[yypt-0].str)
|
||||
if err != nil {
|
||||
yylex.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
}
|
||||
case 32:
|
||||
//line parser.y:172
|
||||
{
|
||||
var err error
|
||||
m, err := metric.NewLabelMatcher(metric.Equal, clientmodel.MetricNameLabel, clientmodel.LabelValue(yyS[yypt-2].str))
|
||||
if err != nil {
|
||||
yylex.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
yyS[yypt-1].labelMatchers = append(yyS[yypt-1].labelMatchers, m)
|
||||
yyVAL.ruleNode, err = NewVectorSelector(yyS[yypt-1].labelMatchers, yyS[yypt-0].str)
|
||||
if err != nil {
|
||||
yylex.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
}
|
||||
case 33:
|
||||
//line parser.y:181
|
||||
{
|
||||
var err error
|
||||
yyVAL.ruleNode, err = NewFunctionCall(yyS[yypt-3].str, yyS[yypt-1].ruleNodeSlice)
|
||||
if err != nil {
|
||||
yylex.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
}
|
||||
case 34:
|
||||
//line parser.y:187
|
||||
{
|
||||
var err error
|
||||
yyVAL.ruleNode, err = NewFunctionCall(yyS[yypt-2].str, []ast.Node{})
|
||||
if err != nil {
|
||||
yylex.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
}
|
||||
case 35:
|
||||
//line parser.y:193
|
||||
{
|
||||
var err error
|
||||
yyVAL.ruleNode, err = NewMatrixSelector(yyS[yypt-4].ruleNode, yyS[yypt-2].str, yyS[yypt-0].str)
|
||||
if err != nil {
|
||||
yylex.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
}
|
||||
case 36:
|
||||
//line parser.y:199
|
||||
{
|
||||
var err error
|
||||
yyVAL.ruleNode, err = NewVectorAggregation(yyS[yypt-5].str, yyS[yypt-3].ruleNode, yyS[yypt-1].labelNameSlice, yyS[yypt-0].boolean)
|
||||
if err != nil {
|
||||
yylex.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
}
|
||||
case 37:
|
||||
//line parser.y:205
|
||||
{
|
||||
var err error
|
||||
yyVAL.ruleNode, err = NewVectorAggregation(yyS[yypt-5].str, yyS[yypt-1].ruleNode, yyS[yypt-4].labelNameSlice, yyS[yypt-3].boolean)
|
||||
if err != nil {
|
||||
yylex.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
}
|
||||
case 38:
|
||||
//line parser.y:213
|
||||
{
|
||||
var err error
|
||||
yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-2].str, yyS[yypt-3].ruleNode, yyS[yypt-0].ruleNode, yyS[yypt-1].vectorMatching)
|
||||
if err != nil {
|
||||
yylex.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
}
|
||||
case 39:
|
||||
//line parser.y:219
|
||||
{
|
||||
var err error
|
||||
yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-2].str, yyS[yypt-3].ruleNode, yyS[yypt-0].ruleNode, yyS[yypt-1].vectorMatching)
|
||||
if err != nil {
|
||||
yylex.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
}
|
||||
case 40:
|
||||
//line parser.y:225
|
||||
{
|
||||
var err error
|
||||
yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-2].str, yyS[yypt-3].ruleNode, yyS[yypt-0].ruleNode, yyS[yypt-1].vectorMatching)
|
||||
if err != nil {
|
||||
yylex.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
}
|
||||
case 41:
|
||||
//line parser.y:231
|
||||
{
|
||||
yyVAL.ruleNode = NewScalarLiteral(yyS[yypt-0].num, "+")
|
||||
}
|
||||
case 42:
|
||||
//line parser.y:233
|
||||
{
|
||||
yyVAL.ruleNode = NewScalarLiteral(yyS[yypt-0].num, yyS[yypt-1].str)
|
||||
}
|
||||
case 43:
|
||||
//line parser.y:237
|
||||
{
|
||||
yyVAL.boolean = false
|
||||
}
|
||||
case 44:
|
||||
//line parser.y:239
|
||||
{
|
||||
yyVAL.boolean = true
|
||||
}
|
||||
case 45:
|
||||
//line parser.y:243
|
||||
{
|
||||
yyVAL.vectorMatching = nil
|
||||
}
|
||||
case 46:
|
||||
//line parser.y:245
|
||||
{
|
||||
var err error
|
||||
yyVAL.vectorMatching, err = newVectorMatching("", yyS[yypt-1].labelNameSlice, nil)
|
||||
if err != nil {
|
||||
yylex.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
}
|
||||
case 47:
|
||||
//line parser.y:251
|
||||
{
|
||||
var err error
|
||||
yyVAL.vectorMatching, err = newVectorMatching(yyS[yypt-3].str, yyS[yypt-5].labelNameSlice, yyS[yypt-1].labelNameSlice)
|
||||
if err != nil {
|
||||
yylex.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
}
|
||||
case 48:
|
||||
//line parser.y:259
|
||||
{
|
||||
yyVAL.labelNameSlice = clientmodel.LabelNames{}
|
||||
}
|
||||
case 49:
|
||||
//line parser.y:261
|
||||
{
|
||||
yyVAL.labelNameSlice = yyS[yypt-1].labelNameSlice
|
||||
}
|
||||
case 50:
|
||||
//line parser.y:265
|
||||
{
|
||||
yyVAL.labelNameSlice = clientmodel.LabelNames{clientmodel.LabelName(yyS[yypt-0].str)}
|
||||
}
|
||||
case 51:
|
||||
//line parser.y:267
|
||||
{
|
||||
yyVAL.labelNameSlice = append(yyVAL.labelNameSlice, clientmodel.LabelName(yyS[yypt-0].str))
|
||||
}
|
||||
case 52:
|
||||
//line parser.y:271
|
||||
{
|
||||
yyVAL.ruleNodeSlice = []ast.Node{yyS[yypt-0].ruleNode}
|
||||
}
|
||||
case 53:
|
||||
//line parser.y:273
|
||||
{
|
||||
yyVAL.ruleNodeSlice = append(yyVAL.ruleNodeSlice, yyS[yypt-0].ruleNode)
|
||||
}
|
||||
case 54:
|
||||
//line parser.y:277
|
||||
{
|
||||
yyVAL.ruleNode = yyS[yypt-0].ruleNode
|
||||
}
|
||||
case 55:
|
||||
//line parser.y:279
|
||||
{
|
||||
yyVAL.ruleNode = ast.NewStringLiteral(yyS[yypt-0].str)
|
||||
}
|
||||
}
|
||||
goto yystack /* stack new state and value */
|
||||
}
|
|
@ -20,30 +20,32 @@ import (
|
|||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
|
||||
"github.com/prometheus/prometheus/rules/ast"
|
||||
"github.com/prometheus/prometheus/stats"
|
||||
"github.com/prometheus/prometheus/storage/local"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
"github.com/prometheus/prometheus/utility"
|
||||
)
|
||||
|
||||
// A RecordingRule records its vector expression into new timeseries.
|
||||
type RecordingRule struct {
|
||||
name string
|
||||
vector ast.VectorNode
|
||||
labels clientmodel.LabelSet
|
||||
permanent bool
|
||||
name string
|
||||
vector promql.Expr
|
||||
labels clientmodel.LabelSet
|
||||
}
|
||||
|
||||
// Name returns the rule name.
|
||||
func (rule RecordingRule) Name() string { return rule.name }
|
||||
|
||||
// EvalRaw returns the raw value of the rule expression.
|
||||
func (rule RecordingRule) EvalRaw(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error) {
|
||||
return ast.EvalVectorInstant(rule.vector, timestamp, storage, stats.NewTimerGroup())
|
||||
func (rule RecordingRule) EvalRaw(timestamp clientmodel.Timestamp, engine *promql.Engine) (promql.Vector, error) {
|
||||
query, err := engine.NewInstantQuery(rule.vector.String(), timestamp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return query.Exec().Vector()
|
||||
}
|
||||
|
||||
// Eval evaluates the rule and then overrides the metric names and labels accordingly.
|
||||
func (rule RecordingRule) Eval(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error) {
|
||||
vector, err := rule.EvalRaw(timestamp, storage)
|
||||
func (rule RecordingRule) Eval(timestamp clientmodel.Timestamp, engine *promql.Engine) (promql.Vector, error) {
|
||||
vector, err := rule.EvalRaw(timestamp, engine)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -63,8 +65,8 @@ func (rule RecordingRule) Eval(timestamp clientmodel.Timestamp, storage local.St
|
|||
return vector, nil
|
||||
}
|
||||
|
||||
// ToDotGraph returns the text representation of a dot graph.
|
||||
func (rule RecordingRule) ToDotGraph() string {
|
||||
// DotGraph returns the text representation of a dot graph.
|
||||
func (rule RecordingRule) DotGraph() string {
|
||||
graph := fmt.Sprintf(
|
||||
`digraph "Rules" {
|
||||
%#p[shape="box",label="%s = "];
|
||||
|
@ -73,7 +75,7 @@ func (rule RecordingRule) ToDotGraph() string {
|
|||
}`,
|
||||
&rule, rule.name,
|
||||
&rule, reflect.ValueOf(rule.vector).Pointer(),
|
||||
rule.vector.NodeTreeToDotGraph(),
|
||||
rule.vector.DotGraph(),
|
||||
)
|
||||
return graph
|
||||
}
|
||||
|
@ -87,9 +89,9 @@ func (rule RecordingRule) HTMLSnippet() template.HTML {
|
|||
ruleExpr := rule.vector.String()
|
||||
return template.HTML(fmt.Sprintf(
|
||||
`<a href="%s">%s</a>%s = <a href="%s">%s</a>`,
|
||||
GraphLinkForExpression(rule.name),
|
||||
utility.GraphLinkForExpression(rule.name),
|
||||
rule.name,
|
||||
rule.labels,
|
||||
GraphLinkForExpression(ruleExpr),
|
||||
utility.GraphLinkForExpression(ruleExpr),
|
||||
ruleExpr))
|
||||
}
|
||||
|
|
|
@ -18,8 +18,7 @@ import (
|
|||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
|
||||
"github.com/prometheus/prometheus/rules/ast"
|
||||
"github.com/prometheus/prometheus/storage/local"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
)
|
||||
|
||||
// A Rule encapsulates a vector expression which is evaluated at a specified
|
||||
|
@ -29,11 +28,11 @@ type Rule interface {
|
|||
Name() string
|
||||
// EvalRaw evaluates the rule's vector expression without triggering any
|
||||
// other actions, like recording or alerting.
|
||||
EvalRaw(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error)
|
||||
EvalRaw(clientmodel.Timestamp, *promql.Engine) (promql.Vector, error)
|
||||
// Eval evaluates the rule, including any associated recording or alerting actions.
|
||||
Eval(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error)
|
||||
// ToDotGraph returns a Graphviz dot graph of the rule.
|
||||
ToDotGraph() string
|
||||
Eval(clientmodel.Timestamp, *promql.Engine) (promql.Vector, error)
|
||||
// DotGraph returns a Graphviz dot graph of the rule.
|
||||
DotGraph() string
|
||||
// String returns a human-readable string representation of the rule.
|
||||
String() string
|
||||
// HTMLSnippet returns a human-readable string representation of the rule,
|
||||
|
|
1720
rules/rules_test.go
1720
rules/rules_test.go
File diff suppressed because it is too large
Load diff
|
@ -27,10 +27,8 @@ import (
|
|||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
|
||||
"github.com/prometheus/prometheus/rules"
|
||||
"github.com/prometheus/prometheus/rules/ast"
|
||||
"github.com/prometheus/prometheus/stats"
|
||||
"github.com/prometheus/prometheus/storage/local"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
"github.com/prometheus/prometheus/utility"
|
||||
)
|
||||
|
||||
// A version of vector that's easier to use from templates.
|
||||
|
@ -57,18 +55,17 @@ func (q queryResultByLabelSorter) Swap(i, j int) {
|
|||
q.results[i], q.results[j] = q.results[j], q.results[i]
|
||||
}
|
||||
|
||||
func query(q string, timestamp clientmodel.Timestamp, storage local.Storage) (queryResult, error) {
|
||||
exprNode, err := rules.LoadExprFromString(q)
|
||||
func query(q string, timestamp clientmodel.Timestamp, queryEngine *promql.Engine) (queryResult, error) {
|
||||
query, err := queryEngine.NewInstantQuery(q, timestamp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queryStats := stats.NewTimerGroup()
|
||||
vector, err := ast.EvalToVector(exprNode, timestamp, storage, queryStats)
|
||||
vector, err := query.Exec().Vector()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ast.Vector is hard to work with in templates, so convert to
|
||||
// promql.Vector is hard to work with in templates, so convert to
|
||||
// base data types.
|
||||
var result = make(queryResult, len(vector))
|
||||
for n, v := range vector {
|
||||
|
@ -92,14 +89,14 @@ type templateExpander struct {
|
|||
}
|
||||
|
||||
// NewTemplateExpander returns a template expander ready to use.
|
||||
func NewTemplateExpander(text string, name string, data interface{}, timestamp clientmodel.Timestamp, storage local.Storage, pathPrefix string) *templateExpander {
|
||||
func NewTemplateExpander(text string, name string, data interface{}, timestamp clientmodel.Timestamp, queryEngine *promql.Engine, pathPrefix string) *templateExpander {
|
||||
return &templateExpander{
|
||||
text: text,
|
||||
name: name,
|
||||
data: data,
|
||||
funcMap: text_template.FuncMap{
|
||||
"query": func(q string) (queryResult, error) {
|
||||
return query(q, timestamp, storage)
|
||||
return query(q, timestamp, queryEngine)
|
||||
},
|
||||
"first": func(v queryResult) (*sample, error) {
|
||||
if len(v) > 0 {
|
||||
|
@ -132,8 +129,8 @@ func NewTemplateExpander(text string, name string, data interface{}, timestamp c
|
|||
},
|
||||
"match": regexp.MatchString,
|
||||
"title": strings.Title,
|
||||
"graphLink": rules.GraphLinkForExpression,
|
||||
"tableLink": rules.TableLinkForExpression,
|
||||
"graphLink": utility.GraphLinkForExpression,
|
||||
"tableLink": utility.TableLinkForExpression,
|
||||
"sortByLabel": func(label string, v queryResult) queryResult {
|
||||
sorter := queryResultByLabelSorter{v[:], label}
|
||||
sort.Stable(sorter)
|
||||
|
@ -219,7 +216,7 @@ func NewTemplateExpander(text string, name string, data interface{}, timestamp c
|
|||
return fmt.Sprintf("%.4g%ss", v, prefix)
|
||||
},
|
||||
"pathPrefix": func() string {
|
||||
return pathPrefix;
|
||||
return pathPrefix
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
"github.com/prometheus/prometheus/storage/local"
|
||||
)
|
||||
|
||||
|
@ -175,10 +176,12 @@ func TestTemplateExpansion(t *testing.T) {
|
|||
})
|
||||
storage.WaitForIndexing()
|
||||
|
||||
engine := promql.NewEngine(storage)
|
||||
|
||||
for i, s := range scenarios {
|
||||
var result string
|
||||
var err error
|
||||
expander := NewTemplateExpander(s.text, "test", s.input, time, storage, "/")
|
||||
expander := NewTemplateExpander(s.text, "test", s.input, time, engine, "/")
|
||||
if s.html {
|
||||
result, err = expander.ExpandHTML(nil)
|
||||
} else {
|
||||
|
|
|
@ -20,9 +20,10 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/prometheus/prometheus/rules"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -33,7 +34,12 @@ var (
|
|||
// checkRules reads rules from in. Sucessfully read rules
|
||||
// are printed to out.
|
||||
func checkRules(filename string, in io.Reader, out io.Writer) error {
|
||||
rules, err := rules.LoadRulesFromReader(in)
|
||||
content, err := ioutil.ReadAll(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rules, err := promql.ParseStmts(filename, string(content))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -15,8 +15,10 @@ package utility
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -80,3 +82,24 @@ func StringToDuration(durationStr string) (duration time.Duration, err error) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TableLinkForExpression creates an escaped relative link to the table view of
|
||||
// the provided expression.
|
||||
func TableLinkForExpression(expr string) string {
|
||||
// url.QueryEscape percent-escapes everything except spaces, for which it
|
||||
// uses "+". However, in the non-query part of a URI, only percent-escaped
|
||||
// spaces are legal, so we need to manually replace "+" with "%20" after
|
||||
// query-escaping the string.
|
||||
//
|
||||
// See also:
|
||||
// http://stackoverflow.com/questions/1634271/url-encoding-the-space-character-or-20.
|
||||
urlData := url.QueryEscape(fmt.Sprintf(`[{"expr":%q,"tab":1}]`, expr))
|
||||
return fmt.Sprintf("/graph#%s", strings.Replace(urlData, "+", "%20", -1))
|
||||
}
|
||||
|
||||
// GraphLinkForExpression creates an escaped relative link to the graph view of
|
||||
// the provided expression.
|
||||
func GraphLinkForExpression(expr string) string {
|
||||
urlData := url.QueryEscape(fmt.Sprintf(`[{"expr":%q,"tab":0}]`, expr))
|
||||
return fmt.Sprintf("/graph#%s", strings.Replace(urlData, "+", "%20", -1))
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/prometheus/prometheus/rules"
|
||||
"github.com/prometheus/prometheus/rules/manager"
|
||||
)
|
||||
|
||||
// AlertStatus bundles alerting rules and the mapping of alert states to row
|
||||
|
@ -47,9 +46,10 @@ func (s byAlertStateSorter) Swap(i, j int) {
|
|||
|
||||
// AlertsHandler implements http.Handler.
|
||||
type AlertsHandler struct {
|
||||
mutex sync.Mutex
|
||||
RuleManager manager.RuleManager
|
||||
RuleManager rules.RuleManager
|
||||
PathPrefix string
|
||||
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (h *AlertsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -20,14 +20,16 @@ import (
|
|||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
"github.com/prometheus/prometheus/storage/local"
|
||||
"github.com/prometheus/prometheus/web/httputils"
|
||||
)
|
||||
|
||||
// MetricsService manages the /api HTTP endpoint.
|
||||
type MetricsService struct {
|
||||
Now func() clientmodel.Timestamp
|
||||
Storage local.Storage
|
||||
Now func() clientmodel.Timestamp
|
||||
Storage local.Storage
|
||||
QueryEngine *promql.Engine
|
||||
}
|
||||
|
||||
// RegisterHandler registers the handler for the various endpoints below /api.
|
||||
|
@ -37,13 +39,13 @@ func (msrv *MetricsService) RegisterHandler(pathPrefix string) {
|
|||
Handler: http.HandlerFunc(h),
|
||||
}
|
||||
}
|
||||
http.Handle(pathPrefix + "api/query", prometheus.InstrumentHandler(
|
||||
pathPrefix + "api/query", handler(msrv.Query),
|
||||
http.Handle(pathPrefix+"api/query", prometheus.InstrumentHandler(
|
||||
pathPrefix+"api/query", handler(msrv.Query),
|
||||
))
|
||||
http.Handle(pathPrefix + "api/query_range", prometheus.InstrumentHandler(
|
||||
pathPrefix + "api/query_range", handler(msrv.QueryRange),
|
||||
http.Handle(pathPrefix+"api/query_range", prometheus.InstrumentHandler(
|
||||
pathPrefix+"api/query_range", handler(msrv.QueryRange),
|
||||
))
|
||||
http.Handle(pathPrefix + "api/metrics", prometheus.InstrumentHandler(
|
||||
pathPrefix + "api/metrics", handler(msrv.Metrics),
|
||||
http.Handle(pathPrefix+"api/metrics", prometheus.InstrumentHandler(
|
||||
pathPrefix+"api/metrics", handler(msrv.Metrics),
|
||||
))
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
"github.com/prometheus/prometheus/storage/local"
|
||||
)
|
||||
|
||||
|
@ -51,7 +52,7 @@ func TestQuery(t *testing.T) {
|
|||
{
|
||||
queryStr: "",
|
||||
status: http.StatusOK,
|
||||
bodyRe: "syntax error",
|
||||
bodyRe: `{"type":"error","value":"query:1,1 no expression found in input","version":1}`,
|
||||
},
|
||||
{
|
||||
queryStr: "expr=testmetric",
|
||||
|
@ -76,7 +77,7 @@ func TestQuery(t *testing.T) {
|
|||
{
|
||||
queryStr: "expr=(badexpression",
|
||||
status: http.StatusOK,
|
||||
bodyRe: "syntax error",
|
||||
bodyRe: `{"type":"error","value":"query:1,15 unexpected unclosed left parenthesis in paren expression","version":1}`,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -92,8 +93,9 @@ func TestQuery(t *testing.T) {
|
|||
storage.WaitForIndexing()
|
||||
|
||||
api := MetricsService{
|
||||
Now: testNow,
|
||||
Storage: storage,
|
||||
Now: testNow,
|
||||
Storage: storage,
|
||||
QueryEngine: promql.NewEngine(storage),
|
||||
}
|
||||
api.RegisterHandler("/")
|
||||
|
||||
|
|
|
@ -26,9 +26,6 @@ import (
|
|||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
|
||||
"github.com/prometheus/prometheus/rules"
|
||||
"github.com/prometheus/prometheus/rules/ast"
|
||||
"github.com/prometheus/prometheus/stats"
|
||||
"github.com/prometheus/prometheus/web/httputils"
|
||||
)
|
||||
|
||||
|
@ -41,9 +38,8 @@ func setAccessControlHeaders(w http.ResponseWriter) {
|
|||
}
|
||||
|
||||
func httpJSONError(w http.ResponseWriter, err error, code int) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
fmt.Fprintln(w, ast.ErrorToJSON(err))
|
||||
httputils.ErrorJSON(w, err)
|
||||
}
|
||||
|
||||
func parseTimestampOrNow(t string, now clientmodel.Timestamp) (clientmodel.Timestamp, error) {
|
||||
|
@ -80,16 +76,19 @@ func (serv MetricsService) Query(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
exprNode, err := rules.LoadExprFromString(expr)
|
||||
query, err := serv.QueryEngine.NewInstantQuery(expr, timestamp)
|
||||
if err != nil {
|
||||
fmt.Fprint(w, ast.ErrorToJSON(err))
|
||||
httpJSONError(w, err, http.StatusOK)
|
||||
return
|
||||
}
|
||||
res := query.Exec()
|
||||
if res.Err != nil {
|
||||
httpJSONError(w, res.Err, http.StatusOK)
|
||||
return
|
||||
}
|
||||
glog.V(1).Infof("Instant query: %s\nQuery stats:\n%s\n", expr, query.Stats())
|
||||
|
||||
queryStats := stats.NewTimerGroup()
|
||||
result := ast.EvalToString(exprNode, timestamp, ast.JSON, serv.Storage, queryStats)
|
||||
glog.V(1).Infof("Instant query: %s\nQuery stats:\n%s\n", expr, queryStats)
|
||||
fmt.Fprint(w, result)
|
||||
httputils.RespondJSON(w, res.Value)
|
||||
}
|
||||
|
||||
// QueryRange handles the /api/query_range endpoint.
|
||||
|
@ -125,50 +124,31 @@ func (serv MetricsService) QueryRange(w http.ResponseWriter, r *http.Request) {
|
|||
end = serv.Now()
|
||||
}
|
||||
|
||||
exprNode, err := rules.LoadExprFromString(expr)
|
||||
if err != nil {
|
||||
fmt.Fprint(w, ast.ErrorToJSON(err))
|
||||
return
|
||||
}
|
||||
if exprNode.Type() != ast.VectorType {
|
||||
fmt.Fprint(w, ast.ErrorToJSON(errors.New("expression does not evaluate to vector type")))
|
||||
return
|
||||
}
|
||||
|
||||
// For safety, limit the number of returned points per timeseries.
|
||||
// This is sufficient for 60s resolution for a week or 1h resolution for a year.
|
||||
if duration/step > 11000 {
|
||||
fmt.Fprint(w, ast.ErrorToJSON(errors.New("exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)")))
|
||||
err := errors.New("exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)")
|
||||
httpJSONError(w, err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Align the start to step "tick" boundary.
|
||||
end = end.Add(-time.Duration(end.UnixNano() % int64(step)))
|
||||
start := end.Add(-duration)
|
||||
|
||||
queryStats := stats.NewTimerGroup()
|
||||
|
||||
matrix, err := ast.EvalVectorRange(
|
||||
exprNode.(ast.VectorNode),
|
||||
end.Add(-duration),
|
||||
end,
|
||||
step,
|
||||
serv.Storage,
|
||||
queryStats)
|
||||
query, err := serv.QueryEngine.NewRangeQuery(expr, start, end, step)
|
||||
if err != nil {
|
||||
fmt.Fprint(w, ast.ErrorToJSON(err))
|
||||
httpJSONError(w, err, http.StatusOK)
|
||||
return
|
||||
}
|
||||
matrix, err := query.Exec().Matrix()
|
||||
if err != nil {
|
||||
httpJSONError(w, err, http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
sortTimer := queryStats.GetTimer(stats.ResultSortTime).Start()
|
||||
sort.Sort(matrix)
|
||||
sortTimer.Stop()
|
||||
|
||||
jsonTimer := queryStats.GetTimer(stats.JSONEncodeTime).Start()
|
||||
result := ast.TypedValueToJSON(matrix, "matrix")
|
||||
jsonTimer.Stop()
|
||||
|
||||
glog.V(1).Infof("Range query: %s\nQuery stats:\n%s\n", expr, queryStats)
|
||||
fmt.Fprint(w, result)
|
||||
glog.V(1).Infof("Range query: %s\nQuery stats:\n%s\n", expr, query.Stats())
|
||||
httputils.RespondJSON(w, matrix)
|
||||
}
|
||||
|
||||
// Metrics handles the /api/metrics endpoint.
|
||||
|
|
|
@ -22,7 +22,7 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
"github.com/prometheus/prometheus/storage/local"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
"github.com/prometheus/prometheus/templates"
|
||||
)
|
||||
|
||||
|
@ -33,8 +33,8 @@ var (
|
|||
|
||||
// ConsolesHandler implements http.Handler.
|
||||
type ConsolesHandler struct {
|
||||
Storage local.Storage
|
||||
PathPrefix string
|
||||
QueryEngine *promql.Engine
|
||||
PathPrefix string
|
||||
}
|
||||
|
||||
func (h *ConsolesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -70,7 +70,7 @@ func (h *ConsolesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
Path: r.URL.Path,
|
||||
}
|
||||
|
||||
template := templates.NewTemplateExpander(string(text), "__console_"+r.URL.Path, data, clientmodel.Now(), h.Storage, h.PathPrefix)
|
||||
template := templates.NewTemplateExpander(string(text), "__console_"+r.URL.Path, data, clientmodel.Now(), h.QueryEngine, h.PathPrefix)
|
||||
filenames, err := filepath.Glob(*consoleLibrariesPath + "/*.lib")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
|
|
@ -14,8 +14,12 @@
|
|||
package httputils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
)
|
||||
|
||||
// GetQueryParams calls r.ParseForm and returns r.Form.
|
||||
|
@ -23,3 +27,39 @@ func GetQueryParams(r *http.Request) url.Values {
|
|||
r.ParseForm()
|
||||
return r.Form
|
||||
}
|
||||
|
||||
var jsonFormatVersion = 1
|
||||
|
||||
// ErrorJSON writes the given error JSON-formatted to w.
|
||||
func ErrorJSON(w io.Writer, err error) error {
|
||||
data := struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
Version int `json:"version"`
|
||||
}{
|
||||
Type: "error",
|
||||
Value: err.Error(),
|
||||
Version: jsonFormatVersion,
|
||||
}
|
||||
enc := json.NewEncoder(w)
|
||||
return enc.Encode(data)
|
||||
}
|
||||
|
||||
// RespondJSON converts the given data value to JSON and writes it to w.
|
||||
func RespondJSON(w io.Writer, val promql.Value) error {
|
||||
data := struct {
|
||||
Type string `json:"type"`
|
||||
Value interface{} `json:"value"`
|
||||
Version int `json:"version"`
|
||||
}{
|
||||
Type: val.Type().String(),
|
||||
Value: val,
|
||||
Version: jsonFormatVersion,
|
||||
}
|
||||
// TODO(fabxc): Adding MarshalJSON to promql.Values might be a good idea.
|
||||
if sc, ok := val.(*promql.Scalar); ok {
|
||||
data.Value = sc.Value
|
||||
}
|
||||
enc := json.NewEncoder(w)
|
||||
return enc.Encode(data)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/prometheus/prometheus/retrieval"
|
||||
"github.com/prometheus/prometheus/rules/manager"
|
||||
"github.com/prometheus/prometheus/rules"
|
||||
)
|
||||
|
||||
// PrometheusStatusHandler implements http.Handler.
|
||||
|
@ -29,7 +29,7 @@ type PrometheusStatusHandler struct {
|
|||
BuildInfo map[string]string
|
||||
Config string
|
||||
Flags map[string]string
|
||||
RuleManager manager.RuleManager
|
||||
RuleManager rules.RuleManager
|
||||
TargetPools map[string]*retrieval.TargetPool
|
||||
|
||||
Birth time.Time
|
||||
|
|
Loading…
Reference in a new issue