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 }