app/vmselect/promql: add share_le_over_time and share_gt_over_time functions for SLI and SLO calculations

This commit is contained in:
Aliaksandr Valialkin 2020-01-03 00:41:09 +02:00
parent 0038365206
commit cb3a342882
5 changed files with 126 additions and 0 deletions

View file

@ -3083,6 +3083,28 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r} resultExpected := []netstorage.Result{r}
f(q, resultExpected) f(q, resultExpected)
}) })
t.Run(`share_gt_over_time`, func(t *testing.T) {
t.Parallel()
q := `share_gt_over_time(rand(0)[200s:10s], 0.7)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{0.35, 0.3, 0.5, 0.3, 0.3, 0.25},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`share_le_over_time`, func(t *testing.T) {
t.Parallel()
q := `share_le_over_time(rand(0)[200s:10s], 0.7)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{0.65, 0.7, 0.5, 0.7, 0.7, 0.75},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`increases_over_time`, func(t *testing.T) { t.Run(`increases_over_time`, func(t *testing.T) {
t.Parallel() t.Parallel()
q := `increases_over_time(rand(0)[200s:10s])` q := `increases_over_time(rand(0)[200s:10s])`

View file

@ -49,6 +49,8 @@ var rollupFuncs = map[string]newRollupFunc{
"lifetime": newRollupFuncOneArg(rollupLifetime), "lifetime": newRollupFuncOneArg(rollupLifetime),
"lag": newRollupFuncOneArg(rollupLag), "lag": newRollupFuncOneArg(rollupLag),
"scrape_interval": newRollupFuncOneArg(rollupScrapeInterval), "scrape_interval": newRollupFuncOneArg(rollupScrapeInterval),
"share_le_over_time": newRollupShareLE,
"share_gt_over_time": newRollupShareGT,
"rollup": newRollupFuncOneArg(rollupFake), "rollup": newRollupFuncOneArg(rollupFake),
"rollup_rate": newRollupFuncOneArg(rollupFake), // + rollupFuncsRemoveCounterResets "rollup_rate": newRollupFuncOneArg(rollupFake), // + rollupFuncsRemoveCounterResets
"rollup_deriv": newRollupFuncOneArg(rollupFake), "rollup_deriv": newRollupFuncOneArg(rollupFake),
@ -529,6 +531,56 @@ func linearRegression(rfa *rollupFuncArg) (float64, float64) {
return v, k return v, k
} }
func newRollupShareLE(args []interface{}) (rollupFunc, error) {
return newRollupShareFilter(args, countFilterLE)
}
func countFilterLE(values []float64, le float64) int {
n := 0
for _, v := range values {
if v <= le {
n++
}
}
return n
}
func newRollupShareGT(args []interface{}) (rollupFunc, error) {
return newRollupShareFilter(args, countFilterGT)
}
func countFilterGT(values []float64, gt float64) int {
n := 0
for _, v := range values {
if v > gt {
n++
}
}
return n
}
func newRollupShareFilter(args []interface{}, countFilter func(values []float64, limit float64) int) (rollupFunc, error) {
if err := expectRollupArgsNum(args, 2); err != nil {
return nil, err
}
limits, err := getScalar(args[1], 1)
if err != nil {
return nil, err
}
rf := func(rfa *rollupFuncArg) float64 {
// There is no need in handling NaNs here, since they must be cleaned up
// before calling rollup funcs.
values := rfa.values
if len(values) == 0 {
return nan
}
limit := limits[rfa.idx]
n := countFilter(values, limit)
return float64(n) / float64(len(values))
}
return rf, nil
}
func newRollupQuantile(args []interface{}) (rollupFunc, error) { func newRollupQuantile(args []interface{}) (rollupFunc, error) {
if err := expectRollupArgsNum(args, 2); err != nil { if err := expectRollupArgsNum(args, 2); err != nil {
return nil, err return nil, err

View file

@ -192,6 +192,52 @@ func testRollupFunc(t *testing.T, funcName string, args []interface{}, meExpecte
} }
} }
func TestRollupShareLEOverTime(t *testing.T) {
f := func(le, vExpected float64) {
t.Helper()
les := []*timeseries{{
Values: []float64{le},
Timestamps: []int64{123},
}}
var me metricsql.MetricExpr
args := []interface{}{&metricsql.RollupExpr{Expr: &me}, les}
testRollupFunc(t, "share_le_over_time", args, &me, vExpected)
}
f(-123, 0)
f(0, 0)
f(10, 0)
f(12, 0.08333333333333333)
f(30, 0.16666666666666666)
f(50, 0.75)
f(100, 0.9166666666666666)
f(123, 1)
f(1000, 1)
}
func TestRollupShareGTOverTime(t *testing.T) {
f := func(gt, vExpected float64) {
t.Helper()
gts := []*timeseries{{
Values: []float64{gt},
Timestamps: []int64{123},
}}
var me metricsql.MetricExpr
args := []interface{}{&metricsql.RollupExpr{Expr: &me}, gts}
testRollupFunc(t, "share_gt_over_time", args, &me, vExpected)
}
f(-123, 1)
f(0, 1)
f(10, 1)
f(12, 0.9166666666666666)
f(30, 0.8333333333333334)
f(50, 0.25)
f(100, 0.08333333333333333)
f(123, 0)
f(1000, 0)
}
func TestRollupQuantileOverTime(t *testing.T) { func TestRollupQuantileOverTime(t *testing.T) {
f := func(phi, vExpected float64) { f := func(phi, vExpected float64) {
t.Helper() t.Helper()

View file

@ -89,3 +89,7 @@ This functionality can be tried at [an editable Grafana dashboard](http://play-g
- `bottomk_max(k, q)` - returns bottom K time series with the min maximums on the given time range - `bottomk_max(k, q)` - returns bottom K time series with the min maximums on the given time range
- `bottomk_avg(k, q)` - returns bottom K time series with the min averages on the given time range - `bottomk_avg(k, q)` - returns bottom K time series with the min averages on the given time range
- `bottomk_median(k, q)` - returns bottom K time series with the min medians on the given time range - `bottomk_median(k, q)` - returns bottom K time series with the min medians on the given time range
- `share_le_over_time(m[d], le)` - returns share (in the range 0..1) of values in `m` over `d`, which are smaller or equal to `le`. Useful for calculating SLI and SLO.
Example: `share_le_over_time(memory_usage_bytes[24h], 100*1024*1024)` returns the share of time series values for the last 24 hours when memory usage was below or equal to 100MB.
- `share_gt_over_time(m[d], gt)` - returns share (in the range 0..1) of values in `m` over `d`, which are bigger than `gt`. Useful for calculating SLI and SLO.
Example: `share_gt_over_time(up[24h], 0)` - returns service availability for the last 24 hours.

View file

@ -41,6 +41,8 @@ var rollupFuncs = map[string]bool{
"lifetime": true, "lifetime": true,
"lag": true, "lag": true,
"scrape_interval": true, "scrape_interval": true,
"share_le_over_time": true,
"share_gt_over_time": true,
"rollup": true, "rollup": true,
"rollup_rate": true, "rollup_rate": true,
"rollup_deriv": true, "rollup_deriv": true,