github.com/go-graphite/carbonapi@v0.17.0/expr/expr_test.go (about)

     1  package expr
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math"
     7  	"testing"
     8  	"time"
     9  	"unicode"
    10  
    11  	pb "github.com/go-graphite/protocol/carbonapi_v3_pb"
    12  
    13  	"github.com/go-graphite/carbonapi/expr/functions"
    14  	"github.com/go-graphite/carbonapi/expr/helper"
    15  	"github.com/go-graphite/carbonapi/expr/rewrite"
    16  	"github.com/go-graphite/carbonapi/expr/types"
    17  	"github.com/go-graphite/carbonapi/pkg/parser"
    18  	th "github.com/go-graphite/carbonapi/tests"
    19  	"github.com/go-graphite/carbonapi/tests/compare"
    20  )
    21  
    22  func init() {
    23  	rewrite.New(make(map[string]string))
    24  	functions.New(make(map[string]string))
    25  }
    26  
    27  func TestGetBuckets(t *testing.T) {
    28  	tests := []struct {
    29  		start       int64
    30  		stop        int64
    31  		bucketSize  int64
    32  		wantBuckets int64
    33  	}{
    34  		{13, 18, 5, 1},
    35  		{13, 17, 5, 1},
    36  		{13, 19, 5, 2},
    37  	}
    38  
    39  	for _, test := range tests {
    40  		buckets := helper.GetBuckets(test.start, test.stop, test.bucketSize)
    41  		if buckets != test.wantBuckets {
    42  			t.Errorf("TestGetBuckets failed!\n%v\ngot buckets %d",
    43  				test,
    44  				buckets,
    45  			)
    46  		}
    47  	}
    48  }
    49  
    50  func TestAlignToBucketSize(t *testing.T) {
    51  	tests := []struct {
    52  		inputStart int64
    53  		inputStop  int64
    54  		bucketSize int64
    55  		wantStart  int64
    56  		wantStop   int64
    57  	}{
    58  		{
    59  			13, 18, 5,
    60  			10, 20,
    61  		},
    62  		{
    63  			13, 17, 5,
    64  			10, 20,
    65  		},
    66  		{
    67  			13, 19, 5,
    68  			10, 20,
    69  		},
    70  	}
    71  
    72  	for _, test := range tests {
    73  		start, stop := helper.AlignToBucketSize(test.inputStart, test.inputStop, test.bucketSize)
    74  		if start != test.wantStart || stop != test.wantStop {
    75  			t.Errorf("TestAlignToBucketSize failed!\n%v\ngot start %d stop %d",
    76  				test,
    77  				start,
    78  				stop,
    79  			)
    80  		}
    81  	}
    82  }
    83  
    84  func TestAlignToInterval(t *testing.T) {
    85  	tests := []struct {
    86  		inputStart int64
    87  		inputStop  int64
    88  		bucketSize int64
    89  		wantStart  int64
    90  	}{
    91  		{
    92  			91111, 92222, 5,
    93  			91111,
    94  		},
    95  		{
    96  			91111, 92222, 60,
    97  			91080,
    98  		},
    99  		{
   100  			91111, 92222, 3600,
   101  			90000,
   102  		},
   103  		{
   104  			91111, 92222, 86400,
   105  			86400,
   106  		},
   107  	}
   108  
   109  	for _, test := range tests {
   110  		start := helper.AlignStartToInterval(test.inputStart, test.inputStop, test.bucketSize)
   111  		if start != test.wantStart {
   112  			t.Errorf("TestAlignToInterval failed!\n%v\ngot start %d",
   113  				test,
   114  				start,
   115  			)
   116  		}
   117  	}
   118  }
   119  
   120  type evalExprTestCase struct {
   121  	metric        string
   122  	request       string
   123  	metricRequest parser.MetricRequest
   124  	values        []float64
   125  	isAbsent      []bool
   126  	stepTime      int64
   127  	from          int64
   128  	until         int64
   129  }
   130  
   131  func TestEvalExpr(t *testing.T) {
   132  	tests := map[string]evalExprTestCase{
   133  		"EvalExp with summarize": {
   134  			metric:  "metric1",
   135  			request: "summarize(metric1,'1min')",
   136  			metricRequest: parser.MetricRequest{
   137  				Metric: "metric1",
   138  				From:   1437127020,
   139  				Until:  1437127140,
   140  			},
   141  			values:   []float64{343, 407, 385},
   142  			isAbsent: []bool{false, false, false},
   143  			stepTime: 60,
   144  			from:     1437127020,
   145  			until:    1437127140,
   146  		},
   147  		"metric name starts with digit": {
   148  			metric:  "1metric",
   149  			request: "1metric",
   150  			metricRequest: parser.MetricRequest{
   151  				Metric: "1metric",
   152  				From:   1437127020,
   153  				Until:  1437127140,
   154  			},
   155  			values:   []float64{343, 407, 385},
   156  			isAbsent: []bool{false, false, false},
   157  			stepTime: 60,
   158  			from:     1437127020,
   159  			until:    1437127140,
   160  		},
   161  		"metric unicode name starts with digit": {
   162  			metric:  "1Метрика",
   163  			request: "1Метрика",
   164  			metricRequest: parser.MetricRequest{
   165  				Metric: "1Метрика",
   166  				From:   1437127020,
   167  				Until:  1437127140,
   168  			},
   169  			values:   []float64{343, 407, 385},
   170  			isAbsent: []bool{false, false, false},
   171  			stepTime: 60,
   172  			from:     1437127020,
   173  			until:    1437127140,
   174  		},
   175  	}
   176  
   177  	parser.RangeTables = append(parser.RangeTables, unicode.Cyrillic)
   178  	for name, test := range tests {
   179  		t.Run(fmt.Sprintf("%s: %s", "TestEvalExpr", name), func(t *testing.T) {
   180  			exp, e, err := parser.ParseExpr(test.request)
   181  			if err != nil || e != "" {
   182  				t.Errorf("error='%v', leftovers='%v'", err, e)
   183  			}
   184  
   185  			metricMap := make(map[parser.MetricRequest][]*types.MetricData)
   186  			request := parser.MetricRequest{
   187  				Metric: test.metric,
   188  				From:   test.from,
   189  				Until:  test.until,
   190  			}
   191  
   192  			data := types.MetricData{
   193  				FetchResponse: pb.FetchResponse{
   194  					Name:              request.Metric,
   195  					StartTime:         request.From,
   196  					StopTime:          request.Until,
   197  					StepTime:          test.stepTime,
   198  					Values:            test.values,
   199  					ConsolidationFunc: "average",
   200  					PathExpression:    request.Metric,
   201  				},
   202  				Tags: map[string]string{"name": request.Metric},
   203  			}
   204  
   205  			metricMap[request] = []*types.MetricData{
   206  				&data,
   207  			}
   208  
   209  			eval, err := NewEvaluator(nil, th.NewTestZipper(nil), false)
   210  			if err == nil {
   211  				_, err = EvalExpr(context.Background(), eval, exp, request.From, request.Until, metricMap)
   212  			}
   213  			if err != nil {
   214  				t.Errorf("error='%v'", err)
   215  			}
   216  		})
   217  	}
   218  }
   219  
   220  func TestEvalExpression(t *testing.T) {
   221  
   222  	now32 := time.Now().Unix()
   223  
   224  	tests := []th.EvalTestItem{
   225  		{
   226  			"metric",
   227  			map[parser.MetricRequest][]*types.MetricData{
   228  				{Metric: "metric", From: 0, Until: 1}: {types.MakeMetricData("metric", []float64{1, 2, 3, 4, 5}, 1, now32)},
   229  			},
   230  			[]*types.MetricData{types.MakeMetricData("metric", []float64{1, 2, 3, 4, 5}, 1, now32)},
   231  		},
   232  		{
   233  			"42",
   234  			map[parser.MetricRequest][]*types.MetricData{},
   235  			[]*types.MetricData{types.MakeMetricData("42", []float64{42}, 1, 0)},
   236  		},
   237  		{
   238  			"metric*",
   239  			map[parser.MetricRequest][]*types.MetricData{
   240  				{"metric*", "", 0, 1}: {
   241  					types.MakeMetricData("metric1", []float64{1, 2, 3, 4, 5}, 1, now32),
   242  					types.MakeMetricData("metric2", []float64{2, 3, 4, 5, 6}, 1, now32),
   243  				},
   244  			},
   245  			[]*types.MetricData{
   246  				types.MakeMetricData("metric1", []float64{1, 2, 3, 4, 5}, 1, now32),
   247  				types.MakeMetricData("metric2", []float64{2, 3, 4, 5, 6}, 1, now32),
   248  			},
   249  		},
   250  		{
   251  			"reduceSeries(mapSeries(devops.service.*.filter.received.*.count,2), \"asPercent\", 5,\"valid\",\"total\")",
   252  			map[parser.MetricRequest][]*types.MetricData{
   253  				{Metric: "devops.service.*.filter.received.*.count", From: 0, Until: 1}: {
   254  					types.MakeMetricData("devops.service.server1.filter.received.valid.count", []float64{2, 4, 8}, 1, now32),
   255  					types.MakeMetricData("devops.service.server1.filter.received.total.count", []float64{8, 2, 4}, 1, now32),
   256  					types.MakeMetricData("devops.service.server2.filter.received.valid.count", []float64{3, 9, 12}, 1, now32),
   257  					types.MakeMetricData("devops.service.server2.filter.received.total.count", []float64{12, 9, 3}, 1, now32),
   258  				},
   259  			},
   260  			[]*types.MetricData{
   261  				types.MakeMetricData("devops.service.server1.filter.received.reduce.asPercent.count", []float64{25, 200, 200}, 1, now32).SetNameTag("devops.service.server1.filter.received.valid.count"),
   262  				types.MakeMetricData("devops.service.server2.filter.received.reduce.asPercent.count", []float64{25, 100, 400}, 1, now32).SetNameTag("devops.service.server2.filter.received.valid.count"),
   263  			},
   264  		},
   265  		{
   266  			"reduceSeries(mapSeries(devops.service.*.filter.received.*.count,2), \"asPercent\", 5,\"valid\",\"total\")",
   267  			map[parser.MetricRequest][]*types.MetricData{
   268  				{Metric: "devops.service.*.filter.received.*.count", From: 0, Until: 1}: {
   269  					types.MakeMetricData("devops.service.server1.filter.received.total.count", []float64{8, 2, 4}, 1, now32),
   270  					types.MakeMetricData("devops.service.server2.filter.received.valid.count", []float64{3, 9, 12}, 1, now32),
   271  					types.MakeMetricData("devops.service.server2.filter.received.total.count", []float64{12, 9, 3}, 1, now32),
   272  				},
   273  			},
   274  			[]*types.MetricData{
   275  				types.MakeMetricData("devops.service.server2.filter.received.reduce.asPercent.count", []float64{25, 100, 400}, 1, now32).SetNameTag("devops.service.server2.filter.received.valid.count"),
   276  			},
   277  		},
   278  		{
   279  			"sumSeries(pow(devops.service.*.filter.received.*.count, 0))",
   280  			map[parser.MetricRequest][]*types.MetricData{
   281  				{Metric: "devops.service.*.filter.received.*.count", From: 0, Until: 1}: {
   282  					types.MakeMetricData("devops.service.server1.filter.received.total.count", []float64{8, 2, 4}, 1, now32),
   283  					types.MakeMetricData("devops.service.server2.filter.received.valid.count", []float64{3, 9, 12}, 1, now32),
   284  					types.MakeMetricData("devops.service.server2.filter.received.total.count", []float64{math.NaN(), math.NaN(), math.NaN()}, 1, now32),
   285  				},
   286  			},
   287  			[]*types.MetricData{types.MakeMetricData("sumSeries(pow(devops.service.*.filter.received.*.count, 0))", []float64{2, 2, 2}, 1, now32).SetTag("aggregatedBy", "sum")},
   288  		},
   289  		{
   290  			"multiplySeriesWithWildcards(metric1.foo.*.*,1,2)",
   291  			map[parser.MetricRequest][]*types.MetricData{
   292  				{Metric: "metric1.foo.*.*", From: 0, Until: 1}: {
   293  					types.MakeMetricData("metric1.foo.bar1.baz", []float64{1, 2, 3, 4, 5}, 1, now32),
   294  					types.MakeMetricData("metric1.foo.bar2.baz", []float64{11, 12, 13, 14, 15}, 1, now32),
   295  					types.MakeMetricData("metric1.foo.bar3.baz", []float64{2, 2, 2, 2, 2}, 1, now32),
   296  				},
   297  			},
   298  			[]*types.MetricData{types.MakeMetricData("metric1.baz", []float64{22, 48, 78, 112, 150}, 1, now32).SetTag("aggregatedBy", "multiply").SetNameTag("metric1.foo.*.*")},
   299  		},
   300  		{
   301  			"groupByNode(metric1foo.*,0,\"asPercent\")",
   302  			map[parser.MetricRequest][]*types.MetricData{
   303  				{Metric: "metric1foo.*", From: 0, Until: 1}: {
   304  					types.MakeMetricData("metric1foo.bar1.baz", []float64{1, 2, 3, 4, 5}, 1, now32),
   305  					types.MakeMetricData("metric1foo.bar1.qux", []float64{6, 7, 8, 9, 10}, 1, now32),
   306  					types.MakeMetricData("metric1foo.bar2.baz", []float64{11, 12, 13, 14, 15}, 1, now32),
   307  					types.MakeMetricData("metric1foo.bar2.qux", []float64{7, 8, 9, 10, 11}, 1, now32),
   308  				},
   309  			},
   310  			[]*types.MetricData{types.MakeMetricData("metric1foo", []float64{4, 6.896551724137931, 9.09090909090909, 10.81081081081081, 12.195121951219512}, 1, now32)},
   311  		},
   312  		{
   313  			"groupByNodes(test.metric*.foo*,\"keepLastValue\",1,0)",
   314  			map[parser.MetricRequest][]*types.MetricData{
   315  				{Metric: "test.metric*.foo*", From: 0, Until: 1}: {
   316  					types.MakeMetricData("test.metric1.foo1", []float64{0}, 1, now32),
   317  					types.MakeMetricData("test.metric1.foo2", []float64{0}, 1, now32),
   318  					types.MakeMetricData("test.metric2.foo1", []float64{0}, 1, now32),
   319  					types.MakeMetricData("test.metric2.foo2", []float64{0}, 1, now32),
   320  				},
   321  			},
   322  			[]*types.MetricData{
   323  				types.MakeMetricData("metric1.test", []float64{0}, 1, now32),
   324  				types.MakeMetricData("metric2.test", []float64{0}, 1, now32),
   325  			},
   326  		},
   327  		{
   328  			"groupByNodes(test.metric*.foo*,\"keepLastValue\",1,2)",
   329  			map[parser.MetricRequest][]*types.MetricData{
   330  				{Metric: "test.metric*.foo*", From: 0, Until: 1}: {
   331  					types.MakeMetricData("test.metric1.foo1", []float64{0}, 1, now32),
   332  					types.MakeMetricData("test.metric1.foo2", []float64{0}, 1, now32),
   333  					types.MakeMetricData("test.metric2.foo1", []float64{0}, 1, now32),
   334  					types.MakeMetricData("test.metric2.foo2", []float64{0}, 1, now32),
   335  				},
   336  			},
   337  			[]*types.MetricData{
   338  				types.MakeMetricData("metric1.foo1", []float64{0}, 1, now32),
   339  				types.MakeMetricData("metric1.foo2", []float64{0}, 1, now32),
   340  				types.MakeMetricData("metric2.foo1", []float64{0}, 1, now32),
   341  				types.MakeMetricData("metric2.foo2", []float64{0}, 1, now32),
   342  			},
   343  		},
   344  		{
   345  			"groupByNodes(test.metric*.foo*,\"keepLastValue\",1)",
   346  			map[parser.MetricRequest][]*types.MetricData{
   347  				{Metric: "test.metric*.foo*", From: 0, Until: 1}: {
   348  					types.MakeMetricData("test.metric1.foo1", []float64{0}, 1, now32),
   349  					types.MakeMetricData("test.metric1.foo2", []float64{0}, 1, now32),
   350  					types.MakeMetricData("test.metric2.foo1", []float64{0}, 1, now32),
   351  					types.MakeMetricData("test.metric2.foo2", []float64{0}, 1, now32),
   352  				},
   353  			},
   354  			[]*types.MetricData{
   355  				types.MakeMetricData("metric1", []float64{0}, 1, now32),
   356  				types.MakeMetricData("metric2", []float64{0}, 1, now32),
   357  			},
   358  		},
   359  	}
   360  
   361  	for _, tt := range tests {
   362  		testName := tt.Target
   363  		t.Run(testName, func(t *testing.T) {
   364  			eval, err := NewEvaluator(nil, th.NewTestZipper(nil), false)
   365  			if err == nil {
   366  				th.TestEvalExpr(t, eval, &tt)
   367  			} else {
   368  				t.Errorf("error='%v'", err)
   369  			}
   370  		})
   371  	}
   372  }
   373  
   374  func TestRewriteExpr(t *testing.T) {
   375  	now32 := time.Now().Unix()
   376  
   377  	tests := []struct {
   378  		name       string
   379  		e          parser.Expr
   380  		m          map[parser.MetricRequest][]*types.MetricData
   381  		rewritten  bool
   382  		newTargets []string
   383  	}{
   384  		{
   385  			"ignore non-applyByNode",
   386  			parser.NewExpr("sumSeries",
   387  
   388  				"metric*",
   389  			),
   390  			map[parser.MetricRequest][]*types.MetricData{
   391  				{Metric: "metric*", From: 0, Until: 1}: {
   392  					types.MakeMetricData("metric1", []float64{1, 2, 3}, 1, now32),
   393  				},
   394  				{Metric: "metric1", From: 0, Until: 1}: {
   395  					types.MakeMetricData("metric1", []float64{1, 2, 3}, 1, now32),
   396  				},
   397  			},
   398  			false,
   399  			[]string{},
   400  		},
   401  		{
   402  			"applyByNode",
   403  			parser.NewExpr("applyByNode",
   404  
   405  				"metric*",
   406  				0,
   407  				parser.ArgValue("%.count"),
   408  			),
   409  			map[parser.MetricRequest][]*types.MetricData{
   410  				{Metric: "metric*", From: 0, Until: 1}: {
   411  					types.MakeMetricData("metric1", []float64{1, 2, 3}, 1, now32),
   412  				},
   413  				{Metric: "metric1", From: 0, Until: 1}: {
   414  					types.MakeMetricData("metric1", []float64{1, 2, 3}, 1, now32),
   415  				},
   416  			},
   417  			true,
   418  			[]string{"metric1.count"},
   419  		},
   420  		{
   421  			"applyByNode",
   422  			parser.NewExpr("applyByNode",
   423  
   424  				"metric*",
   425  				0,
   426  				parser.ArgValue("%.count"),
   427  				parser.ArgValue("% count"),
   428  			),
   429  			map[parser.MetricRequest][]*types.MetricData{
   430  				{Metric: "metric*", From: 0, Until: 1}: {
   431  					types.MakeMetricData("metric1", []float64{1, 2, 3}, 1, now32),
   432  				},
   433  				{Metric: "metric1", From: 0, Until: 1}: {
   434  					types.MakeMetricData("metric1", []float64{1, 2, 3}, 1, now32),
   435  				},
   436  			},
   437  			true,
   438  			[]string{"alias(metric1.count,\"metric1 count\")"},
   439  		},
   440  		{
   441  			"applyByNode",
   442  			parser.NewExpr("applyByNode",
   443  
   444  				"foo.metric*",
   445  				1,
   446  				parser.ArgValue("%.count"),
   447  			),
   448  			map[parser.MetricRequest][]*types.MetricData{
   449  				{Metric: "foo.metric*", From: 0, Until: 1}: {
   450  					types.MakeMetricData("foo.metric1", []float64{1, 2, 3}, 1, now32),
   451  					types.MakeMetricData("foo.metric2", []float64{1, 2, 3}, 1, now32),
   452  				},
   453  				{Metric: "foo.metric1", From: 0, Until: 1}: {
   454  					types.MakeMetricData("foo.metric1", []float64{1, 2, 3}, 1, now32),
   455  				},
   456  				{Metric: "foo.metric2", From: 0, Until: 1}: {
   457  					types.MakeMetricData("foo.metric2", []float64{1, 2, 3}, 1, now32),
   458  				},
   459  			},
   460  			true,
   461  			[]string{"foo.metric1.count", "foo.metric2.count"},
   462  		},
   463  	}
   464  
   465  	for _, tt := range tests {
   466  		t.Run(tt.name, func(t *testing.T) {
   467  			eval, err := NewEvaluator(nil, th.NewTestZipper(nil), false)
   468  			if err == nil {
   469  				rewritten, newTargets, err := RewriteExpr(context.Background(), eval, tt.e, 0, 1, tt.m)
   470  
   471  				if err != nil {
   472  					t.Errorf("failed to rewrite %v: %+v", tt.name, err)
   473  					return
   474  				}
   475  
   476  				if rewritten != tt.rewritten {
   477  					t.Errorf("failed to rewrite %v: expected rewritten=%v but was %v", tt.name, tt.rewritten, rewritten)
   478  					return
   479  				}
   480  
   481  				var targetsMatch = true
   482  				if len(tt.newTargets) != len(newTargets) {
   483  					targetsMatch = false
   484  				} else {
   485  					for i := range tt.newTargets {
   486  						targetsMatch = targetsMatch && tt.newTargets[i] == newTargets[i]
   487  					}
   488  				}
   489  
   490  				if !targetsMatch {
   491  					t.Errorf("failed to rewrite %v: expected newTargets=%v but was %v", tt.name, tt.newTargets, newTargets)
   492  					return
   493  				}
   494  			} else {
   495  				t.Errorf("error='%v'", err)
   496  			}
   497  		})
   498  	}
   499  }
   500  
   501  func TestEvalCustomFromUntil(t *testing.T) {
   502  	tests := []struct {
   503  		target string
   504  		m      map[parser.MetricRequest][]*types.MetricData
   505  		w      []float64
   506  		name   string
   507  		from   int64
   508  		until  int64
   509  	}{
   510  		{
   511  			"timeFunction(\"footime\")",
   512  			map[parser.MetricRequest][]*types.MetricData{},
   513  			[]float64{4200.0, 4260.0, 4320.0},
   514  			"footime",
   515  			4200,
   516  			4350,
   517  		},
   518  	}
   519  
   520  	for _, tt := range tests {
   521  		t.Run(tt.name, func(t *testing.T) {
   522  			originalMetrics := th.DeepClone(tt.m)
   523  			exp, _, _ := parser.ParseExpr(tt.target)
   524  			eval, err := NewEvaluator(nil, th.NewTestZipper(nil), false)
   525  			if err == nil {
   526  				g, err := EvalExpr(context.Background(), eval, exp, tt.from, tt.until, tt.m)
   527  				if err != nil {
   528  					t.Errorf("failed to eval %v: %s", tt.name, err)
   529  					return
   530  				}
   531  				if g[0] == nil {
   532  					t.Errorf("returned no value %v", tt.target)
   533  					return
   534  				}
   535  
   536  				th.DeepEqual(t, tt.target, originalMetrics, tt.m, false)
   537  
   538  				if g[0].StepTime == 0 {
   539  					t.Errorf("missing step for %+v", g)
   540  				}
   541  				if !compare.NearlyEqual(g[0].Values, tt.w) {
   542  					t.Errorf("failed: %s: got %+v, want %+v", g[0].Name, g[0].Values, tt.w)
   543  				}
   544  				if g[0].Name != tt.name {
   545  					t.Errorf("bad name for %+v: got %v, want %v", g, g[0].Name, tt.name)
   546  				}
   547  			} else {
   548  				t.Errorf("error='%v'", err)
   549  			}
   550  		})
   551  	}
   552  }