Skip to content

Commit

Permalink
[expr] add countValues func
Browse files Browse the repository at this point in the history
function draws line for each unique value in the seriesList
  • Loading branch information
Egor Redozubov committed Nov 18, 2024
1 parent 6e55637 commit a139486
Show file tree
Hide file tree
Showing 3 changed files with 245 additions and 0 deletions.
118 changes: 118 additions & 0 deletions expr/functions/countValues/function.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package countValues

import (
"context"
"math"
"strconv"

"github.com/go-graphite/carbonapi/expr/helper"
"github.com/go-graphite/carbonapi/expr/interfaces"
"github.com/go-graphite/carbonapi/expr/types"
"github.com/go-graphite/carbonapi/pkg/parser"
)

type countValues struct {
interfaces.Function
}

func GetOrder() interfaces.Order {
return interfaces.Any
}

func New(_ string) []interfaces.FunctionMetadata {
res := make([]interfaces.FunctionMetadata, 0)
f := &countValues{}
functions := []string{"countValues"}
for _, n := range functions {
res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
}
return res
}

const (
defaultValuesLimit = 32
LimitExceededMetricName = "error.too.many.values.limit.reached"
)

// countValues(seriesList)
func (f *countValues) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
// TODO(civil): Check that series have equal length
args, err := helper.GetSeriesArg(ctx, eval, e.Args()[0], from, until, values)
if err != nil {
return nil, err
}

valuesLimit, err := e.GetIntNamedOrPosArgDefault("valuesLimit", 1, defaultValuesLimit)
if err != nil {
return nil, err
}

var results []*types.MetricData

data := map[int][]float64{}

for _, arg := range args {
for bucket, value := range arg.Values {

if math.IsNaN(value) {
continue
}

key := int(value)
v, ok := data[key]
if !ok {
if len(data) >= valuesLimit {
m := *args[0]
m.Name = LimitExceededMetricName
m.Values = make([]float64, len(m.Values))
return []*types.MetricData{&m}, nil
}

v = make([]float64, len(arg.Values))
data[key] = v
}
v[bucket]++

}
}

for key, value := range data {
mName := strconv.FormatInt(int64(key), 10)
m := *args[0]
m.Name = mName
m.Values = value
results = append(results, &m)
}

return results, nil
}

const functionDescription = `Draws line for each unique value in the seriesList. Each line displays count of the value in current bucket.
.. code-block:: none
&target=countValues(carbon.agents.*.*)`

func (f *countValues) Description() map[string]types.FunctionDescription {
return map[string]types.FunctionDescription{
"countValues": {
Description: functionDescription,
Function: "countValues(*seriesLists)",
Group: "Combine",
Module: "graphite.render.functions",
Name: "countValues",
Params: []types.FunctionParam{
{
Name: "seriesList",
Required: true,
Type: types.SeriesList,
},
{
Default: types.NewSuggestion(defaultValuesLimit),
Name: "valuesLimit",
Type: types.Integer,
},
},
},
}
}
125 changes: 125 additions & 0 deletions expr/functions/countValues/function_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package countValues

import (
"math"
"testing"
"time"

"github.com/go-graphite/carbonapi/expr/helper"
"github.com/go-graphite/carbonapi/expr/metadata"
"github.com/go-graphite/carbonapi/expr/types"
"github.com/go-graphite/carbonapi/pkg/parser"
th "github.com/go-graphite/carbonapi/tests"
)

func init() {
md := New("")
for _, m := range md {
metadata.RegisterFunction(m.Name, m.F)
}

evaluator := th.EvaluatorFromFuncWithMetadata(metadata.FunctionMD.Functions)
metadata.SetEvaluator(evaluator)
helper.SetEvaluator(evaluator)
}

func TestCountValues(t *testing.T) {
now32 := int64(time.Now().Unix())

tests := []th.MultiReturnEvalTestItem{
{
"countValues(metric1.foo.*.*)",
map[parser.MetricRequest][]*types.MetricData{
{"metric1.foo.*.*", 0, 1}: {
types.MakeMetricData("metric1.foo.bar1.baz", []float64{1, 2, 3, 4, 5}, 1, now32),
types.MakeMetricData("metric1.foo.bar1.qux", []float64{2, 2, 4, 5, 6}, 1, now32),
types.MakeMetricData("metric1.foo.bar2.baz", []float64{math.NaN(), 1, 1, 1, 1}, 1, now32),
},
},
"countValues",
map[string][]*types.MetricData{
"1": {types.MakeMetricData("1", []float64{1, 1, 1, 1, 1}, 1, now32)},
"2": {types.MakeMetricData("2", []float64{1, 2, 0, 0, 0}, 1, now32)},
"3": {types.MakeMetricData("3", []float64{0, 0, 1, 0, 0}, 1, now32)},
"4": {types.MakeMetricData("4", []float64{0, 0, 1, 1, 0}, 1, now32)},
"5": {types.MakeMetricData("5", []float64{0, 0, 0, 1, 1}, 1, now32)},
"6": {types.MakeMetricData("6", []float64{0, 0, 0, 0, 1}, 1, now32)},
},
},
{
"countValues(metric1.foo.*.*, 7)",
map[parser.MetricRequest][]*types.MetricData{
{"metric1.foo.*.*", 0, 1}: {
types.MakeMetricData("metric1.foo.bar1.baz", []float64{1, 2, 3, 4, 5}, 1, now32),
types.MakeMetricData("metric1.foo.bar1.qux", []float64{2, 2, 4, 5, 6}, 1, now32),
types.MakeMetricData("metric1.foo.bar2.baz", []float64{math.NaN(), 1, 1, 1, 1}, 1, now32),
},
},
"countValues",
map[string][]*types.MetricData{
"1": {types.MakeMetricData("1", []float64{1, 1, 1, 1, 1}, 1, now32)},
"2": {types.MakeMetricData("2", []float64{1, 2, 0, 0, 0}, 1, now32)},
"3": {types.MakeMetricData("3", []float64{0, 0, 1, 0, 0}, 1, now32)},
"4": {types.MakeMetricData("4", []float64{0, 0, 1, 1, 0}, 1, now32)},
"5": {types.MakeMetricData("5", []float64{0, 0, 0, 1, 1}, 1, now32)},
"6": {types.MakeMetricData("6", []float64{0, 0, 0, 0, 1}, 1, now32)},
},
},
{
"countValues(metric1.foo.*.*,valuesLimit=6)",
map[parser.MetricRequest][]*types.MetricData{
{"metric1.foo.*.*", 0, 1}: {
types.MakeMetricData("metric1.foo.bar1.baz", []float64{1, 2, 3, 4, 5}, 1, now32),
types.MakeMetricData("metric1.foo.bar1.qux", []float64{2, 2, 4, 5, 6}, 1, now32),
types.MakeMetricData("metric1.foo.bar2.baz", []float64{math.NaN(), 1, 1, 1, 1}, 1, now32),
},
},
"countValues",
map[string][]*types.MetricData{
"1": {types.MakeMetricData("1", []float64{1, 1, 1, 1, 1}, 1, now32)},
"2": {types.MakeMetricData("2", []float64{1, 2, 0, 0, 0}, 1, now32)},
"3": {types.MakeMetricData("3", []float64{0, 0, 1, 0, 0}, 1, now32)},
"4": {types.MakeMetricData("4", []float64{0, 0, 1, 1, 0}, 1, now32)},
"5": {types.MakeMetricData("5", []float64{0, 0, 0, 1, 1}, 1, now32)},
"6": {types.MakeMetricData("6", []float64{0, 0, 0, 0, 1}, 1, now32)},
},
},
{
"countValues(metric1.foo.*.*, 5)"

Check failure on line 88 in expr/functions/countValues/function_test.go

View workflow job for this annotation

GitHub Actions / Test code (^1.21)

missing ',' before newline in composite literal

Check failure on line 88 in expr/functions/countValues/function_test.go

View workflow job for this annotation

GitHub Actions / Test code (^1.22)

missing ',' before newline in composite literal

Check failure on line 88 in expr/functions/countValues/function_test.go

View workflow job for this annotation

GitHub Actions / Test code (^1)

missing ',' before newline in composite literal
map[parser.MetricRequest][]*types.MetricData{
{"metric1.foo.*.*", 0, 1}: {
types.MakeMetricData("metric1.foo.bar1.baz", []float64{1, 2, 3, 4, 5}, 1, now32),
types.MakeMetricData("metric1.foo.bar1.qux", []float64{2, 2, 4, 5, 6}, 1, now32),
types.MakeMetricData("metric1.foo.bar2.baz", []float64{math.NaN(), 1, 1, 1, 1}, 1, now32),
},
},
"countValues",
map[string][]*types.MetricData{
LimitExceededMetricName: {types.MakeMetricData(LimitExceededMetricName, []float64{0, 0, 0, 0, 0}, 1, now32)},
},
},
{
"countValues(metric1.doo.*.*,valuesLimit=32)",
map[parser.MetricRequest][]*types.MetricData{
{"metric1.doo.*.*", 0, 1}: {
types.MakeMetricData("metric1.doo.bar1.baz", []float64{11, 21, 31, 41, 51, 61, 71, 81, 91, 101}, 1, now32),
types.MakeMetricData("metric1.doo.bar2.baz", []float64{12, 22, 32, 42, 52, 62, 72, 82, 92, 102}, 1, now32),
types.MakeMetricData("metric1.doo.bar3.baz", []float64{13, 23, 33, 43, 53, 63, 73, 83, 93, 103}, 1, now32),
types.MakeMetricData("metric1.doo.bar4.baz", []float64{14, 24, 34, 44, 54, 64, 74, 84, 94, 104}, 1, now32),
},
},
"countValues",
map[string][]*types.MetricData{
LimitExceededMetricName: {types.MakeMetricData(LimitExceededMetricName, []float64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 1, now32)},
},
},
}

for _, tt := range tests {
testName := tt.E.Target() + "(" + tt.E.RawArgs() + ")"
t.Run(testName, func(t *testing.T) {
th.TestMultiReturnEvalExpr(t, &tt)
})
}

}
2 changes: 2 additions & 0 deletions expr/functions/glue.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/go-graphite/carbonapi/expr/functions/compressPeriodicGaps"
"github.com/go-graphite/carbonapi/expr/functions/consolidateBy"
"github.com/go-graphite/carbonapi/expr/functions/constantLine"
"github.com/go-graphite/carbonapi/expr/functions/countValues"
"github.com/go-graphite/carbonapi/expr/functions/cumulative"
"github.com/go-graphite/carbonapi/expr/functions/delay"
"github.com/go-graphite/carbonapi/expr/functions/derivative"
Expand Down Expand Up @@ -153,6 +154,7 @@ func New(configs map[string]string) {
{name: "compressPeriodicGaps", filename: "compressPeriodicGaps", order: compressPeriodicGaps.GetOrder(), f: compressPeriodicGaps.New},
{name: "consolidateBy", filename: "consolidateBy", order: consolidateBy.GetOrder(), f: consolidateBy.New},
{name: "constantLine", filename: "constantLine", order: constantLine.GetOrder(), f: constantLine.New},
{name: "countValues", filename: "countValues", order: countValues.GetOrder(), f: countValues.New},
{name: "cumulative", filename: "cumulative", order: cumulative.GetOrder(), f: cumulative.New},
{name: "delay", filename: "delay", order: delay.GetOrder(), f: delay.New},
{name: "derivative", filename: "derivative", order: derivative.GetOrder(), f: derivative.New},
Expand Down

0 comments on commit a139486

Please sign in to comment.