Adds holt-winters query function
This commit is contained in:
parent
583b1f3753
commit
1543ef92b2
45
promql/bench.go
Normal file
45
promql/bench.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// 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, softwar
|
||||||
|
// 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 promql
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// A Benchmark holds context for running a unit test as a benchmark.
|
||||||
|
type Benchmark struct {
|
||||||
|
b *testing.B
|
||||||
|
t *Test
|
||||||
|
iterCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBenchmark returns an initialized empty Benchmark.
|
||||||
|
func NewBenchmark(b *testing.B, input string) *Benchmark {
|
||||||
|
t, err := NewTest(b, input)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("Unable to run benchmark: %s", err)
|
||||||
|
}
|
||||||
|
return &Benchmark{
|
||||||
|
b: b,
|
||||||
|
t: t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs the benchmark.
|
||||||
|
func (b *Benchmark) Run() {
|
||||||
|
b.b.ReportAllocs()
|
||||||
|
b.b.ResetTimer()
|
||||||
|
for i := 0; i < b.b.N; i++ {
|
||||||
|
b.t.RunAsBenchmark(b)
|
||||||
|
b.iterCount++
|
||||||
|
}
|
||||||
|
}
|
@ -184,6 +184,102 @@ func funcIrate(ev *evaluator, args Expressions) model.Value {
|
|||||||
return resultVector
|
return resultVector
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate the trend value at the given index i in raw data d.
|
||||||
|
// This is somewhat analogous to the slope of the trend at the given index.
|
||||||
|
// The argument "s" is the set of computed smoothed values.
|
||||||
|
// The argument "b" is the set of computed trend factors.
|
||||||
|
// The argument "d" is the set of raw input values.
|
||||||
|
func calcTrendValue(i int, sf, tf float64, s, b, d []float64) float64 {
|
||||||
|
if i == 0 {
|
||||||
|
return b[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
x := tf * (s[i] - s[i-1])
|
||||||
|
y := (1 - tf) * b[i-1]
|
||||||
|
|
||||||
|
// Cache the computed value.
|
||||||
|
b[i] = x + y
|
||||||
|
|
||||||
|
return b[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Holt-Winters is similar to a weighted moving average, where historical data has exponentially less influence on the current data.
|
||||||
|
// Holt-Winter also accounts for trends in data. The smoothing factor (0 < sf < 1) effects how historical data will effect the current
|
||||||
|
// data. A lower smoothing factor increases the influence of historical data. The trend factor (0 < tf < 1) effects
|
||||||
|
// how trends in historical data will effect the current data. A higher trend factor increases the influence.
|
||||||
|
// of trends. Algorithm taken from https://en.wikipedia.org/wiki/Exponential_smoothing titled: "Double exponential smoothing".
|
||||||
|
func funcHoltWinters(ev *evaluator, args Expressions) model.Value {
|
||||||
|
mat := ev.evalMatrix(args[0])
|
||||||
|
|
||||||
|
// The smoothing factor argument.
|
||||||
|
sf := ev.evalFloat(args[1])
|
||||||
|
|
||||||
|
// The trend factor argument.
|
||||||
|
tf := ev.evalFloat(args[2])
|
||||||
|
|
||||||
|
// Sanity check the input.
|
||||||
|
if sf <= 0 || sf >= 1 {
|
||||||
|
ev.errorf("invalid smoothing factor. Expected: 0 < sf < 1 got: %f", sf)
|
||||||
|
}
|
||||||
|
if tf <= 0 || tf >= 1 {
|
||||||
|
ev.errorf("invalid trend factor. Expected: 0 < tf < 1 got: %f", sf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an output vector large enough to hold the entire result.
|
||||||
|
resultVector := make(vector, 0, len(mat))
|
||||||
|
|
||||||
|
// Create scratch values.
|
||||||
|
var s, b, d []float64
|
||||||
|
|
||||||
|
var l int
|
||||||
|
for _, samples := range mat {
|
||||||
|
l = len(samples.Values)
|
||||||
|
|
||||||
|
// Can't do the smoothing operation with less than two points.
|
||||||
|
if l < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize scratch values.
|
||||||
|
if l != len(s) {
|
||||||
|
s = make([]float64, l)
|
||||||
|
b = make([]float64, l)
|
||||||
|
d = make([]float64, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in the d values with the raw values from the input.
|
||||||
|
for i, v := range samples.Values {
|
||||||
|
d[i] = float64(v.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set initial values.
|
||||||
|
s[0] = d[0]
|
||||||
|
b[0] = d[1] - d[0]
|
||||||
|
|
||||||
|
// Run the smoothing operation.
|
||||||
|
var x, y float64
|
||||||
|
for i := 1; i < len(d); i++ {
|
||||||
|
|
||||||
|
// Scale the raw value against the smoothing factor.
|
||||||
|
x = sf * d[i]
|
||||||
|
|
||||||
|
// Scale the last smoothed value with the trend at this point.
|
||||||
|
y = (1 - sf) * (s[i-1] + calcTrendValue(i-1, sf, tf, s, b, d))
|
||||||
|
|
||||||
|
s[i] = x + y
|
||||||
|
}
|
||||||
|
|
||||||
|
samples.Metric.Del(model.MetricNameLabel)
|
||||||
|
resultVector = append(resultVector, &sample{
|
||||||
|
Metric: samples.Metric,
|
||||||
|
Value: model.SampleValue(s[len(s)-1]), // The last value in the vector is the smoothed result.
|
||||||
|
Timestamp: ev.Timestamp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultVector
|
||||||
|
}
|
||||||
|
|
||||||
// === sort(node model.ValVector) Vector ===
|
// === sort(node model.ValVector) Vector ===
|
||||||
func funcSort(ev *evaluator, args Expressions) model.Value {
|
func funcSort(ev *evaluator, args Expressions) model.Value {
|
||||||
// NaN should sort to the bottom, so take descending sort with NaN first and
|
// NaN should sort to the bottom, so take descending sort with NaN first and
|
||||||
@ -848,6 +944,12 @@ var functions = map[string]*Function{
|
|||||||
ReturnType: model.ValVector,
|
ReturnType: model.ValVector,
|
||||||
Call: funcHistogramQuantile,
|
Call: funcHistogramQuantile,
|
||||||
},
|
},
|
||||||
|
"holt_winters": {
|
||||||
|
Name: "holt_winters",
|
||||||
|
ArgTypes: []model.ValueType{model.ValMatrix, model.ValScalar, model.ValScalar},
|
||||||
|
ReturnType: model.ValVector,
|
||||||
|
Call: funcHoltWinters,
|
||||||
|
},
|
||||||
"irate": {
|
"irate": {
|
||||||
Name: "irate",
|
Name: "irate",
|
||||||
ArgTypes: []model.ValueType{model.ValMatrix},
|
ArgTypes: []model.ValueType{model.ValMatrix},
|
||||||
|
73
promql/functions_test.go
Normal file
73
promql/functions_test.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// 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 promql
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func BenchmarkHoltWinters4Week5Min(b *testing.B) {
|
||||||
|
input := `
|
||||||
|
clear
|
||||||
|
load 5m
|
||||||
|
http_requests{path="/foo"} 0+10x8064
|
||||||
|
|
||||||
|
eval instant at 4w holt_winters(http_requests[4w], 0.3, 0.3)
|
||||||
|
{path="/foo"} 20160
|
||||||
|
`
|
||||||
|
|
||||||
|
bench := NewBenchmark(b, input)
|
||||||
|
bench.Run()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkHoltWinters1Week5Min(b *testing.B) {
|
||||||
|
input := `
|
||||||
|
clear
|
||||||
|
load 5m
|
||||||
|
http_requests{path="/foo"} 0+10x2016
|
||||||
|
|
||||||
|
eval instant at 1w holt_winters(http_requests[1w], 0.3, 0.3)
|
||||||
|
{path="/foo"} 20160
|
||||||
|
`
|
||||||
|
|
||||||
|
bench := NewBenchmark(b, input)
|
||||||
|
bench.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkHoltWinters1Day1Min(b *testing.B) {
|
||||||
|
input := `
|
||||||
|
clear
|
||||||
|
load 1m
|
||||||
|
http_requests{path="/foo"} 0+10x1440
|
||||||
|
|
||||||
|
eval instant at 1d holt_winters(http_requests[1d], 0.3, 0.3)
|
||||||
|
{path="/foo"} 20160
|
||||||
|
`
|
||||||
|
|
||||||
|
bench := NewBenchmark(b, input)
|
||||||
|
bench.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkChanges1Day1Min(b *testing.B) {
|
||||||
|
input := `
|
||||||
|
clear
|
||||||
|
load 1m
|
||||||
|
http_requests{path="/foo"} 0+10x1440
|
||||||
|
|
||||||
|
eval instant at 1d changes(http_requests[1d])
|
||||||
|
{path="/foo"} 20160
|
||||||
|
`
|
||||||
|
|
||||||
|
bench := NewBenchmark(b, input)
|
||||||
|
bench.Run()
|
||||||
|
}
|
@ -411,6 +411,32 @@ func (cmd clearCmd) String() string {
|
|||||||
return "clear"
|
return "clear"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunAsBenchmark runs the test in benchmark mode.
|
||||||
|
// This will not count any loads or non eval functions.
|
||||||
|
func (t *Test) RunAsBenchmark(b *Benchmark) error {
|
||||||
|
for _, cmd := range t.cmds {
|
||||||
|
|
||||||
|
switch cmd.(type) {
|
||||||
|
// Only time the "eval" command.
|
||||||
|
case *evalCmd:
|
||||||
|
err := t.exec(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if b.iterCount == 0 {
|
||||||
|
b.b.StopTimer()
|
||||||
|
err := t.exec(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.b.StartTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Run executes the command sequence of the test. Until the maximum error number
|
// Run executes the command sequence of the test. Until the maximum error number
|
||||||
// is reached, evaluation errors do not terminate execution.
|
// is reached, evaluation errors do not terminate execution.
|
||||||
func (t *Test) Run() error {
|
func (t *Test) Run() error {
|
||||||
|
30
promql/testdata/functions.test
vendored
30
promql/testdata/functions.test
vendored
@ -288,3 +288,33 @@ eval_ordered instant at 50m sort_desc(http_requests)
|
|||||||
http_requests{group="production", instance="1", job="api-server"} 200
|
http_requests{group="production", instance="1", job="api-server"} 200
|
||||||
http_requests{group="production", instance="0", job="api-server"} 100
|
http_requests{group="production", instance="0", job="api-server"} 100
|
||||||
http_requests{group="canary", instance="2", job="api-server"} NaN
|
http_requests{group="canary", instance="2", job="api-server"} NaN
|
||||||
|
|
||||||
|
# Tests for holt_winters
|
||||||
|
clear
|
||||||
|
|
||||||
|
# positive trends
|
||||||
|
load 10s
|
||||||
|
http_requests{job="api-server", instance="0", group="production"} 0+10x1000 100+30x1000
|
||||||
|
http_requests{job="api-server", instance="1", group="production"} 0+20x1000 200+30x1000
|
||||||
|
http_requests{job="api-server", instance="0", group="canary"} 0+30x1000 300+80x1000
|
||||||
|
http_requests{job="api-server", instance="1", group="canary"} 0+40x2000
|
||||||
|
|
||||||
|
eval instant at 8000s holt_winters(http_requests[1m], 0.01, 0.1)
|
||||||
|
{job="api-server", instance="0", group="production"} 8000
|
||||||
|
{job="api-server", instance="1", group="production"} 16000
|
||||||
|
{job="api-server", instance="0", group="canary"} 24000
|
||||||
|
{job="api-server", instance="1", group="canary"} 32000
|
||||||
|
|
||||||
|
# negative trends
|
||||||
|
clear
|
||||||
|
load 10s
|
||||||
|
http_requests{job="api-server", instance="0", group="production"} 8000-10x1000
|
||||||
|
http_requests{job="api-server", instance="1", group="production"} 0-20x1000
|
||||||
|
http_requests{job="api-server", instance="0", group="canary"} 0+30x1000 300-80x1000
|
||||||
|
http_requests{job="api-server", instance="1", group="canary"} 0-40x1000 0+40x1000
|
||||||
|
|
||||||
|
eval instant at 8000s holt_winters(http_requests[1m], 0.01, 0.1)
|
||||||
|
{job="api-server", instance="0", group="production"} 0
|
||||||
|
{job="api-server", instance="1", group="production"} -16000
|
||||||
|
{job="api-server", instance="0", group="canary"} 24000
|
||||||
|
{job="api-server", instance="1", group="canary"} -32000
|
||||||
|
Loading…
x
Reference in New Issue
Block a user