Merge pull request #1485 from eliothedeman/master
Adds holt-winters query function
This commit is contained in:
commit
24a3ad3d16
|
@ -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
|
||||
}
|
||||
|
||||
// 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 ===
|
||||
func funcSort(ev *evaluator, args Expressions) model.Value {
|
||||
// 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,
|
||||
Call: funcHistogramQuantile,
|
||||
},
|
||||
"holt_winters": {
|
||||
Name: "holt_winters",
|
||||
ArgTypes: []model.ValueType{model.ValMatrix, model.ValScalar, model.ValScalar},
|
||||
ReturnType: model.ValVector,
|
||||
Call: funcHoltWinters,
|
||||
},
|
||||
"irate": {
|
||||
Name: "irate",
|
||||
ArgTypes: []model.ValueType{model.ValMatrix},
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
||||
// 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
|
||||
// is reached, evaluation errors do not terminate execution.
|
||||
func (t *Test) Run() error {
|
||||
|
|
|
@ -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="0", job="api-server"} 100
|
||||
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…
Reference in New Issue