github.com/go-graphite/carbonapi@v0.17.0/expr/functions/timeShiftByMetric/function_test.go (about)

     1  package timeShiftByMetric
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/go-graphite/carbonapi/expr/interfaces"
    10  	"github.com/go-graphite/carbonapi/expr/metadata"
    11  	"github.com/go-graphite/carbonapi/expr/types"
    12  	"github.com/go-graphite/carbonapi/pkg/parser"
    13  	th "github.com/go-graphite/carbonapi/tests"
    14  )
    15  
    16  var (
    17  	md []interfaces.FunctionMetadata = New("")
    18  )
    19  
    20  func init() {
    21  	for _, m := range md {
    22  		metadata.RegisterFunction(m.Name, m.F)
    23  	}
    24  }
    25  
    26  func TestTimeShift(t *testing.T) {
    27  	nan := math.NaN()
    28  	now32 := time.Now().Unix()
    29  
    30  	testCases := []th.EvalTestItem{
    31  		// 1. Versions: 1_0, 1_1, 1_2, 1_3, 2_0, 2_1, 2_2, 3_0, 3_1. Each two consequential versions are have 1 time unit between them.
    32  		// 2. Leading versions: 1_3, 2_2, 3_1.
    33  		// 3. Version 2_2 is 2 time units behind version 3_1. Version 1_3 is 3 time units behind version 2_2 therefore 5 units behind version 3_1.
    34  		{
    35  			"timeShiftByMetric(apps.*.metric, apps.mark.*, 1)",
    36  			map[parser.MetricRequest][]*types.MetricData{
    37  				parser.MetricRequest{Metric: "apps.*.metric", From: 0, Until: 1}: {
    38  					types.MakeMetricData("apps.1_3.metric", []float64{1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, nan, nan}, 1, now32),
    39  					types.MakeMetricData("apps.2.metric", []float64{nan, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, nan}, 1, now32),
    40  					types.MakeMetricData("apps.3.metric", []float64{nan, nan, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9}, 1, now32),
    41  				},
    42  				parser.MetricRequest{Metric: "apps.mark.*", From: 0, Until: 1}: {
    43  					// leading versions
    44  					types.MakeMetricData("apps.mark.1_3", []float64{nan, nan, nan, 1, nan, nan, nan, nan, nan, nan, nan}, 1, now32),
    45  					types.MakeMetricData("apps.mark.2_2", []float64{nan, nan, nan, nan, nan, nan, 1, nan, nan, nan, nan}, 1, now32),
    46  					types.MakeMetricData("apps.mark.3_1", []float64{nan, nan, nan, nan, nan, nan, nan, nan, 1, nan, nan}, 1, now32),
    47  					// rest
    48  					types.MakeMetricData("apps.mark.1_0", []float64{1, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan}, 1, now32),
    49  					types.MakeMetricData("apps.mark.1_1", []float64{nan, 1, nan, nan, nan, nan, nan, nan, nan, nan, nan}, 1, now32),
    50  					types.MakeMetricData("apps.mark.1_2", []float64{nan, nan, 1, nan, nan, nan, nan, nan, nan, nan, nan}, 1, now32),
    51  					types.MakeMetricData("apps.mark.2_0", []float64{nan, nan, nan, nan, 1, nan, nan, nan, nan, nan, nan}, 1, now32),
    52  					types.MakeMetricData("apps.mark.2_1", []float64{nan, nan, nan, nan, nan, 1, nan, nan, nan, nan, nan}, 1, now32),
    53  					types.MakeMetricData("apps.mark.3_0", []float64{nan, nan, nan, nan, nan, nan, nan, 1, nan, nan, nan}, 1, now32),
    54  				},
    55  			},
    56  			[]*types.MetricData{
    57  				types.MakeMetricData("timeShiftByMetric(apps.1_3.metric)", []float64{1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, nan, nan}, 1, now32+5),
    58  				types.MakeMetricData("timeShiftByMetric(apps.2.metric)", []float64{nan, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, nan}, 1, now32+2),
    59  				types.MakeMetricData("timeShiftByMetric(apps.3.metric)", []float64{nan, nan, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9}, 1, now32),
    60  			},
    61  		},
    62  		// 1. Versions: 1_0, 1_1, 2_0.
    63  		// 2. Leading versions: 1_1, 2_0.
    64  		// 3. Version 1_1 is 4 time units behind versions 2_0.
    65  		{
    66  			"timeShiftByMetric(*.metric, apps.mark.*, 0)",
    67  			map[parser.MetricRequest][]*types.MetricData{
    68  				parser.MetricRequest{Metric: "*.metric", From: 0, Until: 1}: {
    69  					types.MakeMetricData("1_1.metric", []float64{1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7}, 1, now32),
    70  					types.MakeMetricData("2_0.metric", []float64{2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7}, 1, now32),
    71  				},
    72  				parser.MetricRequest{Metric: "apps.mark.*", From: 0, Until: 1}: {
    73  					// leading versions
    74  					types.MakeMetricData("apps.mark.1_1", []float64{nan, nan, 1, nan, nan, nan, nan}, 1, now32),
    75  					types.MakeMetricData("apps.mark.2_0", []float64{nan, nan, nan, nan, nan, nan, 1}, 1, now32),
    76  					// rest
    77  					types.MakeMetricData("apps.mark.1_0", []float64{1, nan, nan, nan, nan, nan, nan}, 1, now32),
    78  				},
    79  			},
    80  			[]*types.MetricData{
    81  				types.MakeMetricData("timeShiftByMetric(1_1.metric)", []float64{1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7}, 1, now32+4),
    82  				types.MakeMetricData("timeShiftByMetric(2_0.metric)", []float64{2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7}, 1, now32),
    83  			},
    84  		},
    85  	}
    86  
    87  	for _, testCase := range testCases {
    88  		testName := testCase.Target
    89  		t.Run(testName, func(t *testing.T) {
    90  			eval := th.EvaluatorFromFunc(md[0].F)
    91  			th.TestEvalExpr(t, eval, &testCase)
    92  		})
    93  	}
    94  }
    95  
    96  func TestBadMarks(t *testing.T) {
    97  	nan := math.NaN()
    98  	now32 := time.Now().Unix()
    99  
   100  	testCases := []th.EvalTestItemWithError{
   101  		// we have only one major version here, it's not right
   102  		{
   103  			"timeShiftByMetric(apps.*.metric, apps.mark.*, 1)",
   104  			map[parser.MetricRequest][]*types.MetricData{
   105  				parser.MetricRequest{Metric: "apps.*.metric", From: 0, Until: 1}: {
   106  					types.MakeMetricData("apps.1.metric", []float64{1, 2, 3}, 1, now32),
   107  					types.MakeMetricData("apps.2.metric", []float64{1, 2, 3}, 1, now32),
   108  					types.MakeMetricData("apps.3.metric", []float64{1, 2, 3}, 1, now32),
   109  				},
   110  				parser.MetricRequest{Metric: "apps.mark.*", From: 0, Until: 1}: {
   111  					types.MakeMetricData("apps.mark.1_0", []float64{1, nan, nan}, 1, now32),
   112  					types.MakeMetricData("apps.mark.1_1", []float64{nan, 1, nan}, 1, now32),
   113  					types.MakeMetricData("apps.mark.1_2", []float64{nan, nan, 1}, 1, now32),
   114  				},
   115  			},
   116  			nil,
   117  			errLessThan2Marks,
   118  		},
   119  	}
   120  
   121  	for _, testCase := range testCases {
   122  		testName := testCase.Target
   123  		t.Run(testName, func(t *testing.T) {
   124  			eval := th.EvaluatorFromFunc(md[0].F)
   125  			th.TestEvalExprWithError(t, eval, &testCase)
   126  		})
   127  	}
   128  }
   129  
   130  func TestNotEnoughSeries(t *testing.T) {
   131  	nan := math.NaN()
   132  	now32 := time.Now().Unix()
   133  	testCases := make([]th.EvalTestItemWithError, 0, 4)
   134  
   135  	// enough metrics but not enough marks
   136  	for i := 0; i < 2; i++ {
   137  		marksData := make([]*types.MetricData, 0, 1)
   138  		for j := 0; j < i; j++ {
   139  			marksData = append(marksData, types.MakeMetricData("apps.mark.1_0", []float64{1, nan, nan, nan, nan}, 1, now32))
   140  		}
   141  
   142  		metricsData := []*types.MetricData{
   143  			types.MakeMetricData("apps.1.metric", []float64{1, 2, 3, 4, 5}, 1, now32),
   144  			types.MakeMetricData("apps.2.metric", []float64{1, 2, 3, 4, 5}, 1, now32),
   145  		}
   146  
   147  		testCases = append(testCases, th.EvalTestItemWithError{
   148  			"timeShiftByMetric(apps.*.metric, apps.mark.*, 1)",
   149  			map[parser.MetricRequest][]*types.MetricData{
   150  				parser.MetricRequest{Metric: "apps.*.metric", From: 0, Until: 1}: metricsData,
   151  				parser.MetricRequest{Metric: "apps.mark.*", From: 0, Until: 1}:   marksData,
   152  			},
   153  			nil,
   154  			errTooFewDatasets,
   155  		})
   156  	}
   157  
   158  	// enough marks but not enough metrics
   159  	for i := 0; i < 2; i++ {
   160  		metricsData := make([]*types.MetricData, 0, 1)
   161  		for j := 0; j < i; j++ {
   162  			metricsData = append(metricsData, types.MakeMetricData("apps.1.metric", []float64{1, 2, 3, 4, 5}, 1, now32))
   163  		}
   164  
   165  		marksData := []*types.MetricData{
   166  			types.MakeMetricData("apps.mark.1_0", []float64{1, nan}, 1, now32),
   167  			types.MakeMetricData("apps.mark.2_0", []float64{nan, 2}, 1, now32),
   168  		}
   169  
   170  		testCases = append(testCases, th.EvalTestItemWithError{
   171  			"timeShiftByMetric(apps.*.metric, apps.mark.*, 1)",
   172  			map[parser.MetricRequest][]*types.MetricData{
   173  				parser.MetricRequest{Metric: "apps.*.metric", From: 0, Until: 1}: metricsData,
   174  				parser.MetricRequest{Metric: "apps.mark.*", From: 0, Until: 1}:   marksData,
   175  			},
   176  			nil,
   177  			errTooFewDatasets,
   178  		})
   179  	}
   180  
   181  	for _, testCase := range testCases {
   182  		testName := testCase.Target
   183  		t.Run(testName, func(t *testing.T) {
   184  			eval := th.EvaluatorFromFunc(md[0].F)
   185  			th.TestEvalExprWithError(t, eval, &testCase)
   186  		})
   187  	}
   188  }
   189  
   190  func BenchmarkTimeShift(b *testing.B) {
   191  	nan := math.NaN()
   192  	now32 := time.Now().Unix()
   193  
   194  	benchmarks := []struct {
   195  		target string
   196  		M      map[parser.MetricRequest][]*types.MetricData
   197  	}{
   198  		// 1. Versions: 1_0, 1_1, 1_2, 1_3, 2_0, 2_1, 2_2, 3_0, 3_1. Each two consequential versions are have 1 time unit between them.
   199  		// 2. Leading versions: 1_3, 2_2, 3_1.
   200  		// 3. Version 2_2 is 2 time units behind version 3_1. Version 1_3 is 3 time units behind version 2_2 therefore 5 units behind version 3_1.
   201  		{
   202  			target: "timeShiftByMetric(apps.*.metric, apps.mark.*, 1)",
   203  			M: map[parser.MetricRequest][]*types.MetricData{
   204  				parser.MetricRequest{Metric: "apps.*.metric", From: 0, Until: 1}: {
   205  					types.MakeMetricData("apps.1_3.metric", []float64{1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, nan, nan}, 1, now32),
   206  					types.MakeMetricData("apps.2.metric", []float64{nan, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, nan}, 1, now32),
   207  					types.MakeMetricData("apps.3.metric", []float64{nan, nan, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9}, 1, now32),
   208  				},
   209  				parser.MetricRequest{Metric: "apps.mark.*", From: 0, Until: 1}: {
   210  					// leading versions
   211  					types.MakeMetricData("apps.mark.1_3", []float64{nan, nan, nan, 1, nan, nan, nan, nan, nan, nan, nan}, 1, now32),
   212  					types.MakeMetricData("apps.mark.2_2", []float64{nan, nan, nan, nan, nan, nan, 1, nan, nan, nan, nan}, 1, now32),
   213  					types.MakeMetricData("apps.mark.3_1", []float64{nan, nan, nan, nan, nan, nan, nan, nan, 1, nan, nan}, 1, now32),
   214  					// rest
   215  					types.MakeMetricData("apps.mark.1_0", []float64{1, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan}, 1, now32),
   216  					types.MakeMetricData("apps.mark.1_1", []float64{nan, 1, nan, nan, nan, nan, nan, nan, nan, nan, nan}, 1, now32),
   217  					types.MakeMetricData("apps.mark.1_2", []float64{nan, nan, 1, nan, nan, nan, nan, nan, nan, nan, nan}, 1, now32),
   218  					types.MakeMetricData("apps.mark.2_0", []float64{nan, nan, nan, nan, 1, nan, nan, nan, nan, nan, nan}, 1, now32),
   219  					types.MakeMetricData("apps.mark.2_1", []float64{nan, nan, nan, nan, nan, 1, nan, nan, nan, nan, nan}, 1, now32),
   220  					types.MakeMetricData("apps.mark.3_0", []float64{nan, nan, nan, nan, nan, nan, nan, 1, nan, nan, nan}, 1, now32),
   221  				},
   222  			},
   223  		},
   224  		// 1. Versions: 1_0, 1_1, 2_0.
   225  		// 2. Leading versions: 1_1, 2_0.
   226  		// 3. Version 1_1 is 4 time units behind versions 2_0.
   227  		{
   228  			target: "timeShiftByMetric(*.metric, apps.mark.*, 0)",
   229  			M: map[parser.MetricRequest][]*types.MetricData{
   230  				{Metric: "*.metric", From: 0, Until: 1}: {
   231  					types.MakeMetricData("1_1.metric", []float64{1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7}, 1, now32),
   232  					types.MakeMetricData("2_0.metric", []float64{2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7}, 1, now32),
   233  				},
   234  				{Metric: "apps.mark.*", From: 0, Until: 1}: {
   235  					// leading versions
   236  					types.MakeMetricData("apps.mark.1_1", []float64{nan, nan, 1, nan, nan, nan, nan}, 1, now32),
   237  					types.MakeMetricData("apps.mark.2_0", []float64{nan, nan, nan, nan, nan, nan, 1}, 1, now32),
   238  					// rest
   239  					types.MakeMetricData("apps.mark.1_0", []float64{1, nan, nan, nan, nan, nan, nan}, 1, now32),
   240  				},
   241  			},
   242  		},
   243  	}
   244  
   245  	eval := th.EvaluatorFromFunc(md[0].F)
   246  
   247  	for _, bm := range benchmarks {
   248  		b.Run(bm.target, func(b *testing.B) {
   249  			exp, _, err := parser.ParseExpr(bm.target)
   250  			if err != nil {
   251  				b.Fatalf("failed to parse %s: %+v", bm.target, err)
   252  			}
   253  
   254  			b.ResetTimer()
   255  
   256  			for i := 0; i < b.N; i++ {
   257  				g, err := eval.Eval(context.Background(), exp, 0, 1, bm.M)
   258  				if err != nil {
   259  					b.Fatalf("failed to eval %s: %+v", bm.target, err)
   260  				}
   261  				_ = g
   262  			}
   263  		})
   264  	}
   265  }