bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/bosun/expr/funcs_test.go (about)

     1  package expr
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"testing"
     7  	"time"
     8  
     9  	"bosun.org/opentsdb"
    10  
    11  	"github.com/influxdata/influxdb/client/v2"
    12  )
    13  
    14  type exprInOut struct {
    15  	expr           string
    16  	out            Results
    17  	shouldParseErr bool
    18  }
    19  
    20  func testExpression(eio exprInOut, t *testing.T) error {
    21  	e, err := New(eio.expr, builtins)
    22  	if eio.shouldParseErr {
    23  		if err == nil {
    24  			return fmt.Errorf("no error when expected error on %v", eio.expr)
    25  		}
    26  		return nil
    27  	}
    28  	if err != nil {
    29  		return err
    30  	}
    31  	backends := &Backends{
    32  		InfluxConfig: client.HTTPConfig{},
    33  	}
    34  	providers := &BosunProviders{}
    35  	r, _, err := e.Execute(backends, providers, nil, queryTime, 0, false, t.Name())
    36  	if err != nil {
    37  		return err
    38  	}
    39  	if _, err := eio.out.Equal(r); err != nil {
    40  		return err
    41  	}
    42  	return nil
    43  }
    44  
    45  func TestDuration(t *testing.T) {
    46  	d := exprInOut{
    47  		`d("1h")`,
    48  		Results{
    49  			Results: ResultSlice{
    50  				&Result{
    51  					Value: Scalar(3600),
    52  				},
    53  			},
    54  		},
    55  		false,
    56  	}
    57  	err := testExpression(d, t)
    58  	if err != nil {
    59  		t.Error(err)
    60  	}
    61  }
    62  
    63  func TestToDuration(t *testing.T) {
    64  	inputs := []int{
    65  		0,
    66  		1,
    67  		60,
    68  		3600,
    69  		86400,
    70  		604800,
    71  		31536000,
    72  	}
    73  	outputs := []string{
    74  		"0ms",
    75  		"1s",
    76  		"1m",
    77  		"1h",
    78  		"1d",
    79  		"1w",
    80  		"1y",
    81  	}
    82  
    83  	for i := range inputs {
    84  		d := exprInOut{
    85  			fmt.Sprintf(`tod(%d)`, inputs[i]),
    86  			Results{
    87  				Results: ResultSlice{
    88  					&Result{
    89  						Value: String(outputs[i]),
    90  					},
    91  				},
    92  			},
    93  			false,
    94  		}
    95  		err := testExpression(d, t)
    96  		if err != nil {
    97  			t.Error(err)
    98  		}
    99  	}
   100  }
   101  
   102  func TestUngroup(t *testing.T) {
   103  	dictum := `series("foo=bar", 0, ungroup(last(series("foo=baz", 0, 1))))`
   104  	err := testExpression(exprInOut{
   105  		dictum,
   106  		Results{
   107  			Results: ResultSlice{
   108  				&Result{
   109  					Value: Series{
   110  						time.Unix(0, 0): 1,
   111  					},
   112  					Group: opentsdb.TagSet{"foo": "bar"},
   113  				},
   114  			},
   115  		},
   116  		false,
   117  	}, t)
   118  
   119  	if err != nil {
   120  		t.Error(err)
   121  	}
   122  }
   123  
   124  func TestMerge(t *testing.T) {
   125  	seriesA := `series("foo=bar", 0, 1)`
   126  	seriesB := `series("foo=baz", 0, 1)`
   127  	err := testExpression(exprInOut{
   128  		fmt.Sprintf("merge(%v, %v)", seriesA, seriesB),
   129  		Results{
   130  			Results: ResultSlice{
   131  				&Result{
   132  					Value: Series{
   133  						time.Unix(0, 0): 1,
   134  					},
   135  					Group: opentsdb.TagSet{"foo": "bar"},
   136  				},
   137  				&Result{
   138  					Value: Series{
   139  						time.Unix(0, 0): 1,
   140  					},
   141  					Group: opentsdb.TagSet{"foo": "baz"},
   142  				},
   143  			},
   144  		},
   145  		false,
   146  	}, t)
   147  	if err != nil {
   148  		t.Error(err)
   149  	}
   150  
   151  	//Should Error due to identical groups in merge
   152  	err = testExpression(exprInOut{
   153  		fmt.Sprintf("merge(%v, %v)", seriesA, seriesA),
   154  		Results{
   155  			Results: ResultSlice{
   156  				&Result{
   157  					Value: Series{
   158  						time.Unix(0, 0): 1,
   159  					},
   160  					Group: opentsdb.TagSet{"foo": "bar"},
   161  				},
   162  				&Result{
   163  					Value: Series{
   164  						time.Unix(0, 0): 1,
   165  					},
   166  					Group: opentsdb.TagSet{"foo": "bar"},
   167  				},
   168  			},
   169  		},
   170  		false,
   171  	}, t)
   172  	if err == nil {
   173  		t.Errorf("error expected due to identical groups in merge but did not get one")
   174  	}
   175  }
   176  
   177  func TestTimedelta(t *testing.T) {
   178  	for _, i := range []struct {
   179  		input    string
   180  		expected Series
   181  	}{
   182  		{
   183  			`timedelta(series("foo=bar", 1466133600, 1, 1466133610, 1, 1466133710, 1))`,
   184  			Series{
   185  				time.Unix(1466133610, 0): 10,
   186  				time.Unix(1466133710, 0): 100,
   187  			},
   188  		},
   189  		{
   190  			`timedelta(series("foo=bar", 1466133600, 1))`,
   191  			Series{
   192  				time.Unix(1466133600, 0): 0,
   193  			},
   194  		},
   195  		{
   196  			`timedelta(series("foo=bar"))`,
   197  			Series{},
   198  		},
   199  	} {
   200  
   201  		err := testExpression(exprInOut{
   202  			i.input,
   203  			Results{
   204  				Results: ResultSlice{
   205  					&Result{
   206  						Value: i.expected,
   207  						Group: opentsdb.TagSet{"foo": "bar"},
   208  					},
   209  				},
   210  			},
   211  			false,
   212  		}, t)
   213  
   214  		if err != nil {
   215  			t.Error(err)
   216  		}
   217  	}
   218  }
   219  
   220  func TestTail(t *testing.T) {
   221  	for _, i := range []struct {
   222  		input    string
   223  		expected Series
   224  	}{
   225  		{
   226  			`tail(series("foo=bar", 1466133600, 1, 1466133610, 1, 1466133710, 1), 2)`,
   227  			Series{
   228  				time.Unix(1466133610, 0): 1,
   229  				time.Unix(1466133710, 0): 1,
   230  			},
   231  		},
   232  		{
   233  			`tail(series("foo=bar", 1466133600, 1), 2)`,
   234  			Series{
   235  				time.Unix(1466133600, 0): 1,
   236  			},
   237  		},
   238  		{
   239  			`tail(series("foo=bar", 1466133600, 1, 1466133610, 1, 1466133710, 1), last(series("foo=bar", 1466133600, 2)))`,
   240  			Series{
   241  				time.Unix(1466133610, 0): 1,
   242  				time.Unix(1466133710, 0): 1,
   243  			},
   244  		},
   245  	} {
   246  
   247  		err := testExpression(exprInOut{
   248  			i.input,
   249  			Results{
   250  				Results: ResultSlice{
   251  					&Result{
   252  						Value: i.expected,
   253  						Group: opentsdb.TagSet{"foo": "bar"},
   254  					},
   255  				},
   256  			},
   257  			false,
   258  		}, t)
   259  
   260  		if err != nil {
   261  			t.Error(err)
   262  		}
   263  	}
   264  }
   265  
   266  func TestAggr(t *testing.T) {
   267  	seriesA := `series("foo=bar", 0, 1, 100, 2)`
   268  	seriesB := `series("foo=baz", 0, 3, 100, 4)`
   269  	seriesC := `series("foo=bat", 0, 5, 100, 6)`
   270  
   271  	seriesGroupsA := `series("color=blue,type=apple,name=bob", 0, 1)`
   272  	seriesGroupsB := `series("color=blue,type=apple", 1, 3)`
   273  	seriesGroupsC := `series("color=green,type=apple", 0, 5)`
   274  
   275  	seriesMathA := `series("color=blue,type=apple,name=bob", 0, 1)`
   276  	seriesMathB := `series("color=blue,type=apple", 1, 3)`
   277  	seriesMathC := `series("color=green,type=apple", 0, 5)`
   278  
   279  	aggrTestCases := []struct {
   280  		name      string
   281  		expr      string
   282  		want      Results
   283  		shouldErr bool
   284  	}{
   285  		{
   286  			name: "median aggregator",
   287  			expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"\", \"p.50\")", seriesA, seriesB, seriesC),
   288  			want: Results{
   289  				Results: ResultSlice{
   290  					&Result{
   291  						Value: Series{
   292  							time.Unix(0, 0):   3,
   293  							time.Unix(100, 0): 4,
   294  						},
   295  						Group: opentsdb.TagSet{},
   296  					},
   297  				},
   298  			},
   299  			shouldErr: false,
   300  		},
   301  		{
   302  			name: "average aggregator",
   303  			expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"\", \"avg\")", seriesA, seriesB, seriesC),
   304  			want: Results{
   305  				Results: ResultSlice{
   306  					&Result{
   307  						Value: Series{
   308  							time.Unix(0, 0):   3,
   309  							time.Unix(100, 0): 4,
   310  						},
   311  						Group: opentsdb.TagSet{},
   312  					},
   313  				},
   314  			},
   315  			shouldErr: false,
   316  		},
   317  		{
   318  			name: "min aggregator",
   319  			expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"\", \"min\")", seriesA, seriesB, seriesC),
   320  			want: Results{
   321  				Results: ResultSlice{
   322  					&Result{
   323  						Value: Series{
   324  							time.Unix(0, 0):   1,
   325  							time.Unix(100, 0): 2,
   326  						},
   327  						Group: opentsdb.TagSet{},
   328  					},
   329  				},
   330  			},
   331  			shouldErr: false,
   332  		},
   333  		{
   334  			name: "max aggregator",
   335  			expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"\", \"max\")", seriesA, seriesB, seriesC),
   336  			want: Results{
   337  				Results: ResultSlice{
   338  					&Result{
   339  						Value: Series{
   340  							time.Unix(0, 0):   5,
   341  							time.Unix(100, 0): 6,
   342  						},
   343  						Group: opentsdb.TagSet{},
   344  					},
   345  				},
   346  			},
   347  			shouldErr: false,
   348  		},
   349  		{
   350  			name: "check p0 == min",
   351  			expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"\", \"p0\")", seriesA, seriesB, seriesC),
   352  			want: Results{
   353  				Results: ResultSlice{
   354  					&Result{
   355  						Value: Series{
   356  							time.Unix(0, 0):   1,
   357  							time.Unix(100, 0): 2,
   358  						},
   359  						Group: opentsdb.TagSet{},
   360  					},
   361  				},
   362  			},
   363  			shouldErr: false,
   364  		},
   365  		{
   366  			name: "check that sum aggregator sums up the aligned points in the series",
   367  			expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"\", \"sum\")", seriesA, seriesB, seriesC),
   368  			want: Results{
   369  				Results: ResultSlice{
   370  					&Result{
   371  						Value: Series{
   372  							time.Unix(0, 0):   9,
   373  							time.Unix(100, 0): 12,
   374  						},
   375  						Group: opentsdb.TagSet{},
   376  					},
   377  				},
   378  			},
   379  			shouldErr: false,
   380  		},
   381  		{
   382  			name:      "check that unknown aggregator errors out",
   383  			expr:      fmt.Sprintf("aggr(merge(%v, %v, %v), \"\", \"unknown\")", seriesA, seriesB, seriesC),
   384  			want:      Results{},
   385  			shouldErr: true,
   386  		},
   387  		{
   388  			name: "single group",
   389  			expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"color\", \"p.50\")", seriesGroupsA, seriesGroupsB, seriesGroupsC),
   390  			want: Results{
   391  				Results: ResultSlice{
   392  					&Result{
   393  						Value: Series{
   394  							time.Unix(0, 0): 1,
   395  							time.Unix(1, 0): 3,
   396  						},
   397  						Group: opentsdb.TagSet{"color": "blue"},
   398  					},
   399  					&Result{
   400  						Value: Series{
   401  							time.Unix(0, 0): 5,
   402  						},
   403  						Group: opentsdb.TagSet{"color": "green"},
   404  					},
   405  				},
   406  			},
   407  			shouldErr: false,
   408  		},
   409  		{
   410  			name: "multiple groups",
   411  			expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"color,type\", \"p.50\")", seriesGroupsA, seriesGroupsB, seriesGroupsC),
   412  			want: Results{
   413  				Results: ResultSlice{
   414  					&Result{
   415  						Value: Series{
   416  							time.Unix(0, 0): 1,
   417  							time.Unix(1, 0): 3,
   418  						},
   419  						Group: opentsdb.TagSet{"color": "blue", "type": "apple"},
   420  					},
   421  					&Result{
   422  						Value: Series{
   423  							time.Unix(0, 0): 5,
   424  						},
   425  						Group: opentsdb.TagSet{"color": "green", "type": "apple"},
   426  					},
   427  				},
   428  			},
   429  			shouldErr: false,
   430  		},
   431  		{
   432  			name: "aggregator with no groups and math operation",
   433  			expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"\", \"p.50\") * 2", seriesMathA, seriesMathB, seriesMathC),
   434  			want: Results{
   435  				Results: ResultSlice{
   436  					&Result{
   437  						Value: Series{
   438  							time.Unix(0, 0): 10,
   439  							time.Unix(1, 0): 6,
   440  						},
   441  						Group: opentsdb.TagSet{},
   442  					},
   443  				},
   444  			},
   445  			shouldErr: false,
   446  		},
   447  		{
   448  			name: "aggregator with one group and math operation",
   449  			expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"color\", \"p.50\") * 2", seriesMathA, seriesMathB, seriesMathC),
   450  			want: Results{
   451  				Results: ResultSlice{
   452  					&Result{
   453  						Value: Series{
   454  							time.Unix(0, 0): 2,
   455  							time.Unix(1, 0): 6,
   456  						},
   457  						Group: opentsdb.TagSet{"color": "blue"},
   458  					},
   459  					&Result{
   460  						Value: Series{
   461  							time.Unix(0, 0): 10,
   462  						},
   463  						Group: opentsdb.TagSet{"color": "green"},
   464  					},
   465  				},
   466  			},
   467  			shouldErr: false,
   468  		},
   469  		{
   470  			name: "aggregator with multiple groups and math operation",
   471  			expr: fmt.Sprintf("aggr(merge(%v, %v, %v), \"color,type\", \"p.50\") * 2", seriesMathA, seriesMathB, seriesMathC),
   472  			want: Results{
   473  				Results: ResultSlice{
   474  					&Result{
   475  						Value: Series{
   476  							time.Unix(0, 0): 2,
   477  							time.Unix(1, 0): 6,
   478  						},
   479  						Group: opentsdb.TagSet{"color": "blue", "type": "apple"},
   480  					},
   481  					&Result{
   482  						Value: Series{
   483  							time.Unix(0, 0): 10,
   484  						},
   485  						Group: opentsdb.TagSet{"color": "green", "type": "apple"},
   486  					},
   487  				},
   488  			},
   489  			shouldErr: false,
   490  		},
   491  	}
   492  
   493  	for _, tc := range aggrTestCases {
   494  		err := testExpression(exprInOut{
   495  			expr:           tc.expr,
   496  			out:            tc.want,
   497  			shouldParseErr: false,
   498  		}, t)
   499  		if !tc.shouldErr && err != nil {
   500  			t.Errorf("Case %q: Got error: %v", tc.name, err)
   501  		} else if tc.shouldErr && err == nil {
   502  			t.Errorf("Case %q: Expected parse error, but got nil", tc.name)
   503  		}
   504  	}
   505  }
   506  
   507  func TestAggrNaNHandling(t *testing.T) {
   508  	// test behavior when NaN is encountered.
   509  	seriesD := `series("foo=bar", 0, 0 / 0, 100, 1)`
   510  	seriesE := `series("foo=baz", 0, 1, 100, 3)`
   511  
   512  	// expect NaN points to be dropped
   513  	eio := exprInOut{
   514  		fmt.Sprintf("aggr(merge(%v, %v), \"\", \"p.90\")", seriesD, seriesE),
   515  		Results{
   516  			Results: ResultSlice{
   517  				&Result{
   518  					Value: Series{
   519  						time.Unix(0, 0):   math.NaN(),
   520  						time.Unix(100, 0): 2,
   521  					},
   522  					Group: opentsdb.TagSet{},
   523  				},
   524  			},
   525  		},
   526  		false,
   527  	}
   528  	e, err := New(eio.expr, builtins)
   529  	if err != nil {
   530  		t.Fatal(err.Error())
   531  	}
   532  	backends := &Backends{
   533  		InfluxConfig: client.HTTPConfig{},
   534  	}
   535  	providers := &BosunProviders{}
   536  	_, _, err = e.Execute(backends, providers, nil, queryTime, 0, false, t.Name())
   537  	if err != nil {
   538  		t.Fatal(err.Error())
   539  	}
   540  
   541  	results := eio.out.Results
   542  	if len(results) != 1 {
   543  		t.Errorf("got len(results) == %d, want 1", len(results))
   544  	}
   545  	val0 := results[0].Value.(Series)[time.Unix(0, 0)]
   546  	if !math.IsNaN(val0) {
   547  		t.Errorf("got first point = %f, want NaN", val0)
   548  	}
   549  	val1 := results[0].Value.(Series)[time.Unix(100, 0)]
   550  	if val1 != 2.0 {
   551  		t.Errorf("got second point = %f, want %f", val1, 2.0)
   552  	}
   553  }