github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/prometheus/native/common_test.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package native 22 23 import ( 24 "bytes" 25 "math" 26 "net/http" 27 "net/http/httptest" 28 "net/url" 29 "strings" 30 "testing" 31 "time" 32 33 "github.com/m3db/m3/src/query/api/v1/handler/prometheus/handleroptions" 34 "github.com/m3db/m3/src/query/block" 35 "github.com/m3db/m3/src/query/executor" 36 "github.com/m3db/m3/src/query/models" 37 "github.com/m3db/m3/src/query/test" 38 "github.com/m3db/m3/src/query/ts" 39 "github.com/m3db/m3/src/query/util/json" 40 xerrors "github.com/m3db/m3/src/x/errors" 41 xjson "github.com/m3db/m3/src/x/json" 42 xhttp "github.com/m3db/m3/src/x/net/http" 43 xtest "github.com/m3db/m3/src/x/test" 44 xtime "github.com/m3db/m3/src/x/time" 45 46 "github.com/golang/mock/gomock" 47 "github.com/stretchr/testify/assert" 48 "github.com/stretchr/testify/require" 49 ) 50 51 const ( 52 promQuery = `http_requests_total{job="prometheus",group="canary"}` 53 ) 54 55 func defaultParams() url.Values { 56 vals := url.Values{} 57 now := time.Now() 58 vals.Add(QueryParam, promQuery) 59 vals.Add(startParam, now.Format(time.RFC3339)) 60 vals.Add(endParam, string(now.Add(time.Hour).Format(time.RFC3339))) 61 vals.Add(handleroptions.StepParam, (time.Duration(10) * time.Second).String()) 62 return vals 63 } 64 65 func testParseParams(req *http.Request) (models.RequestParams, error) { 66 fetchOptsBuilder, err := handleroptions.NewFetchOptionsBuilder( 67 handleroptions.FetchOptionsBuilderOptions{ 68 Timeout: 15 * time.Second, 69 }) 70 if err != nil { 71 return models.RequestParams{}, err 72 } 73 74 _, fetchOpts, err := fetchOptsBuilder.NewFetchOptions(req.Context(), req) 75 if err != nil { 76 return models.RequestParams{}, err 77 } 78 79 return parseParams(req, executor.NewEngineOptions(), fetchOpts) 80 } 81 82 func TestParamParsing(t *testing.T) { 83 req := httptest.NewRequest("GET", PromReadURL, nil) 84 req.URL.RawQuery = defaultParams().Encode() 85 86 r, err := testParseParams(req) 87 require.Nil(t, err, "unable to parse request") 88 require.Equal(t, promQuery, r.Query) 89 } 90 91 func TestParamParsing_POST(t *testing.T) { 92 params := defaultParams().Encode() 93 req := httptest.NewRequest("POST", PromReadURL, strings.NewReader(params)) 94 req.Header.Add(xhttp.HeaderContentType, xhttp.ContentTypeFormURLEncoded) 95 96 r, err := testParseParams(req) 97 require.NoError(t, err, "unable to parse request") 98 require.Equal(t, promQuery, r.Query) 99 } 100 101 func TestInstantaneousParamParsing(t *testing.T) { 102 req := httptest.NewRequest("GET", PromReadURL, nil) 103 params := url.Values{} 104 now := time.Now() 105 params.Add(QueryParam, promQuery) 106 params.Add(timeParam, now.Format(time.RFC3339)) 107 req.URL.RawQuery = params.Encode() 108 fetchOptsBuilder, err := handleroptions.NewFetchOptionsBuilder( 109 handleroptions.FetchOptionsBuilderOptions{ 110 Timeout: 10 * time.Second, 111 }) 112 require.NoError(t, err) 113 _, fetchOpts, err := fetchOptsBuilder.NewFetchOptions(req.Context(), req) 114 require.NoError(t, err) 115 116 r, err := parseInstantaneousParams(req, executor.NewEngineOptions(), 117 fetchOpts) 118 require.NoError(t, err, "unable to parse request") 119 require.Equal(t, promQuery, r.Query) 120 } 121 122 func TestInvalidStart(t *testing.T) { 123 req := httptest.NewRequest("GET", PromReadURL, nil) 124 vals := defaultParams() 125 vals.Del(startParam) 126 req.URL.RawQuery = vals.Encode() 127 _, err := testParseParams(req) 128 require.NotNil(t, err, "unable to parse request") 129 require.True(t, xerrors.IsInvalidParams(err)) 130 } 131 132 func TestInvalidTarget(t *testing.T) { 133 req := httptest.NewRequest("GET", PromReadURL, nil) 134 vals := defaultParams() 135 vals.Del(QueryParam) 136 req.URL.RawQuery = vals.Encode() 137 138 p, err := testParseParams(req) 139 require.NotNil(t, err, "unable to parse request") 140 assert.NotNil(t, p.Start) 141 require.True(t, xerrors.IsInvalidParams(err)) 142 } 143 144 func TestParseBlockType(t *testing.T) { 145 for _, test := range []struct { 146 input string 147 expected models.FetchedBlockType 148 err bool 149 }{ 150 { 151 input: "0", 152 expected: models.TypeSingleBlock, 153 }, 154 { 155 input: "1", 156 err: true, 157 }, 158 { 159 input: "2", 160 err: true, 161 }, 162 { 163 input: "foo", 164 err: true, 165 }, 166 } { 167 t.Run(test.input, func(t *testing.T) { 168 req := httptest.NewRequest("GET", PromReadURL, nil) 169 p := defaultParams() 170 p.Set("block-type", test.input) 171 req.URL.RawQuery = p.Encode() 172 173 r, err := testParseParams(req) 174 if !test.err { 175 require.NoError(t, err, "should be no block error") 176 require.Equal(t, test.expected, r.BlockType) 177 } else { 178 require.Error(t, err, "should be block error") 179 } 180 }) 181 } 182 } 183 184 func TestRenderResultsJSON(t *testing.T) { 185 buffer := bytes.NewBuffer(nil) 186 jw := json.NewWriter(buffer) 187 series := testSeries(2) 188 189 start := series[0].Values().DatapointAt(0).Timestamp 190 params := models.RequestParams{ 191 Start: start, 192 End: start.Add(time.Hour * 1), 193 } 194 195 readResult := ReadResult{Series: series} 196 rr := RenderResultsJSON(json.NewNoopWriter(), readResult, RenderResultsOptions{ 197 Start: params.Start, 198 End: params.End, 199 KeepNaNs: true, 200 }) 201 require.Equal(t, false, rr.LimitedMaxReturnedData) 202 require.Equal(t, 6, rr.Datapoints) 203 require.Equal(t, 3, rr.Series) 204 require.Equal(t, 3, rr.TotalSeries) 205 206 rr = RenderResultsJSON(jw, readResult, RenderResultsOptions{ 207 Start: params.Start, 208 End: params.End, 209 KeepNaNs: true, 210 }) 211 require.Equal(t, false, rr.LimitedMaxReturnedData) 212 require.Equal(t, 6, rr.Datapoints) 213 require.Equal(t, 3, rr.Series) 214 require.Equal(t, 3, rr.TotalSeries) 215 require.NoError(t, jw.Close()) 216 217 expected := xtest.MustPrettyJSONMap(t, xjson.Map{ 218 "status": "success", 219 "warnings": xjson.Array{ 220 "m3db exceeded query limit: results not exhaustive", 221 }, 222 "data": xjson.Map{ 223 "resultType": "matrix", 224 "result": xjson.Array{ 225 xjson.Map{ 226 "metric": xjson.Map{ 227 "bar": "baz", 228 "qux": "qaz", 229 }, 230 "values": xjson.Array{ 231 xjson.Array{ 232 1535948880, 233 "1", 234 }, 235 xjson.Array{ 236 1535948890, 237 "NaN", 238 }, 239 }, 240 "step_size_ms": 10000, 241 }, 242 xjson.Map{ 243 "metric": xjson.Map{ 244 "baz": "bar", 245 "qaz": "qux", 246 }, 247 "values": xjson.Array{ 248 xjson.Array{ 249 1535948880, 250 "2", 251 }, 252 xjson.Array{ 253 1535948890, 254 "2", 255 }, 256 }, 257 "step_size_ms": 10000, 258 }, 259 xjson.Map{ 260 "metric": xjson.Map{ 261 "biz": "baz", 262 "qux": "qaz", 263 }, 264 "values": xjson.Array{ 265 xjson.Array{ 266 1535948880, 267 "NaN", 268 }, 269 xjson.Array{ 270 1535948890, 271 "NaN", 272 }, 273 }, 274 "step_size_ms": 10000, 275 }, 276 }, 277 }, 278 }) 279 280 actual := xtest.MustPrettyJSONString(t, buffer.String()) 281 assert.Equal(t, expected, actual, xtest.Diff(expected, actual)) 282 } 283 284 func TestRenderResultsJSONWithDroppedNaNs(t *testing.T) { 285 var ( 286 start = xtime.FromSeconds(1535948880) 287 buffer = bytes.NewBuffer(nil) 288 step = 10 * time.Second 289 valsWithNaN = ts.NewFixedStepValues(step, 2, 1, start) 290 params = models.RequestParams{ 291 Start: start, 292 End: start.Add(2 * step), 293 } 294 ) 295 296 jw := json.NewWriter(buffer) 297 298 valsWithNaN.SetValueAt(1, math.NaN()) 299 series := []*ts.Series{ 300 ts.NewSeries([]byte("foo"), 301 valsWithNaN, test.TagSliceToTags([]models.Tag{ 302 {Name: []byte("bar"), Value: []byte("baz")}, 303 {Name: []byte("qux"), Value: []byte("qaz")}, 304 })), 305 ts.NewSeries([]byte("foobar"), 306 ts.NewFixedStepValues(step, 2, math.NaN(), start), 307 test.TagSliceToTags([]models.Tag{ 308 {Name: []byte("biz"), Value: []byte("baz")}, 309 {Name: []byte("qux"), Value: []byte("qaz")}, 310 })), 311 ts.NewSeries([]byte("bar"), 312 ts.NewFixedStepValues(step, 2, 2, start), 313 test.TagSliceToTags([]models.Tag{ 314 {Name: []byte("baz"), Value: []byte("bar")}, 315 {Name: []byte("qaz"), Value: []byte("qux")}, 316 })), 317 } 318 319 meta := block.NewResultMetadata() 320 meta.AddWarning("foo", "bar") 321 meta.AddWarning("baz", "qux") 322 readResult := ReadResult{ 323 Series: series, 324 Meta: meta, 325 } 326 327 // Ensure idempotent by running first once with noop render. 328 rr := RenderResultsJSON(json.NewNoopWriter(), readResult, RenderResultsOptions{ 329 Start: params.Start, 330 End: params.End, 331 KeepNaNs: false, 332 }) 333 require.Equal(t, false, rr.LimitedMaxReturnedData) 334 require.Equal(t, 3, rr.Datapoints) 335 require.Equal(t, 2, rr.Series) 336 require.Equal(t, 3, rr.TotalSeries) 337 338 rr = RenderResultsJSON(jw, readResult, RenderResultsOptions{ 339 Start: params.Start, 340 End: params.End, 341 KeepNaNs: false, 342 }) 343 require.Equal(t, false, rr.LimitedMaxReturnedData) 344 require.Equal(t, 3, rr.Datapoints) 345 require.Equal(t, 2, rr.Series) 346 require.Equal(t, 3, rr.TotalSeries) 347 require.NoError(t, jw.Close()) 348 349 expected := xtest.MustPrettyJSONMap(t, xjson.Map{ 350 "status": "success", 351 "warnings": xjson.Array{ 352 "foo_bar", 353 "baz_qux", 354 }, 355 "data": xjson.Map{ 356 "resultType": "matrix", 357 "result": xjson.Array{ 358 xjson.Map{ 359 "metric": xjson.Map{ 360 "bar": "baz", 361 "qux": "qaz", 362 }, 363 "values": xjson.Array{ 364 xjson.Array{ 365 1535948880, 366 "1", 367 }, 368 }, 369 "step_size_ms": 10000, 370 }, 371 xjson.Map{ 372 "metric": xjson.Map{ 373 "baz": "bar", 374 "qaz": "qux", 375 }, 376 "values": xjson.Array{ 377 xjson.Array{ 378 1535948880, 379 "2", 380 }, 381 xjson.Array{ 382 1535948890, 383 "2", 384 }, 385 }, 386 "step_size_ms": 10000, 387 }, 388 }, 389 }, 390 }) 391 392 actual := xtest.MustPrettyJSONString(t, buffer.String()) 393 assert.Equal(t, expected, actual, xtest.Diff(expected, actual)) 394 } 395 396 func TestRenderInstantaneousResultsJSONVector(t *testing.T) { 397 start := xtime.FromSeconds(1535948880) 398 399 series := []*ts.Series{ 400 ts.NewSeries([]byte("foo"), 401 ts.NewFixedStepValues(10*time.Second, 1, 1, start), 402 test.TagSliceToTags([]models.Tag{ 403 {Name: []byte("bar"), Value: []byte("baz")}, 404 {Name: []byte("qux"), Value: []byte("qaz")}, 405 })), 406 ts.NewSeries([]byte("nan"), 407 ts.NewFixedStepValues(10*time.Second, 1, math.NaN(), start), 408 test.TagSliceToTags([]models.Tag{ 409 {Name: []byte("baz"), Value: []byte("bar")}, 410 })), 411 ts.NewSeries([]byte("bar"), 412 ts.NewFixedStepValues(10*time.Second, 1, 2, start), 413 test.TagSliceToTags([]models.Tag{ 414 {Name: []byte("baz"), Value: []byte("bar")}, 415 {Name: []byte("qaz"), Value: []byte("qux")}, 416 })), 417 } 418 419 readResult := ReadResult{ 420 Series: series, 421 Meta: block.NewResultMetadata(), 422 } 423 424 foo := xjson.Map{ 425 "metric": xjson.Map{ 426 "bar": "baz", 427 "qux": "qaz", 428 }, 429 "value": xjson.Array{ 430 1535948880, 431 "1", 432 }, 433 } 434 435 bar := xjson.Map{ 436 "metric": xjson.Map{ 437 "baz": "bar", 438 "qaz": "qux", 439 }, 440 "value": xjson.Array{ 441 1535948880, 442 "2", 443 }, 444 } 445 446 nan := xjson.Map{ 447 "metric": xjson.Map{ 448 "baz": "bar", 449 }, 450 "value": xjson.Array{ 451 1535948880, 452 "NaN", 453 }, 454 } 455 456 // Ensure idempotent by running first once with noop render. 457 r := renderResultsInstantaneousJSON(json.NewNoopWriter(), readResult, RenderResultsOptions{KeepNaNs: true}) 458 require.Equal(t, false, r.LimitedMaxReturnedData) 459 require.Equal(t, 3, r.Datapoints) 460 require.Equal(t, 3, r.Series) 461 require.Equal(t, 3, r.TotalSeries) 462 463 buffer := bytes.NewBuffer(nil) 464 jw := json.NewWriter(buffer) 465 r = renderResultsInstantaneousJSON(jw, readResult, RenderResultsOptions{KeepNaNs: true}) 466 require.NoError(t, jw.Close()) 467 require.Equal(t, false, r.LimitedMaxReturnedData) 468 require.Equal(t, 3, r.Datapoints) 469 require.Equal(t, 3, r.Series) 470 require.Equal(t, 3, r.TotalSeries) 471 472 expectedWithNaN := xtest.MustPrettyJSONMap(t, xjson.Map{ 473 "status": "success", 474 "data": xjson.Map{ 475 "resultType": "vector", 476 "result": xjson.Array{foo, nan, bar}, 477 }, 478 }) 479 actualWithNaN := xtest.MustPrettyJSONString(t, buffer.String()) 480 assert.Equal(t, expectedWithNaN, actualWithNaN, xtest.Diff(expectedWithNaN, actualWithNaN)) 481 482 // Ensure idempotent by running first once with noop render. 483 r = renderResultsInstantaneousJSON(json.NewNoopWriter(), 484 readResult, 485 RenderResultsOptions{KeepNaNs: false}) 486 require.NoError(t, jw.Close()) 487 require.Equal(t, false, r.LimitedMaxReturnedData) 488 require.Equal(t, 2, r.Datapoints) 489 require.Equal(t, 2, r.Series) 490 require.Equal(t, 3, r.TotalSeries) // This is > rendered series due to keepNaN: false. 491 492 buffer = bytes.NewBuffer(nil) 493 jw = json.NewWriter(buffer) 494 r = renderResultsInstantaneousJSON(jw, readResult, RenderResultsOptions{KeepNaNs: false}) 495 require.NoError(t, jw.Close()) 496 require.Equal(t, false, r.LimitedMaxReturnedData) 497 require.Equal(t, 2, r.Datapoints) 498 require.Equal(t, 2, r.Series) 499 require.Equal(t, 3, r.TotalSeries) // This is > rendered series due to keepNaN: false. 500 expectedWithoutNaN := xtest.MustPrettyJSONMap(t, xjson.Map{ 501 "status": "success", 502 "data": xjson.Map{ 503 "resultType": "vector", 504 "result": xjson.Array{foo, bar}, 505 }, 506 }) 507 actualWithoutNaN := xtest.MustPrettyJSONString(t, buffer.String()) 508 assert.Equal(t, expectedWithoutNaN, actualWithoutNaN, xtest.Diff(expectedWithoutNaN, actualWithoutNaN)) 509 } 510 511 func TestRenderInstantaneousResultsNansOnlyJSON(t *testing.T) { 512 start := xtime.FromSeconds(1535948880) 513 514 series := []*ts.Series{ 515 ts.NewSeries([]byte("nan"), 516 ts.NewFixedStepValues(10*time.Second, 1, math.NaN(), start), 517 test.TagSliceToTags([]models.Tag{ 518 {Name: []byte("qux"), Value: []byte("qaz")}, 519 })), 520 ts.NewSeries([]byte("nan"), 521 ts.NewFixedStepValues(10*time.Second, 1, math.NaN(), start), 522 test.TagSliceToTags([]models.Tag{ 523 {Name: []byte("baz"), Value: []byte("bar")}, 524 })), 525 } 526 527 readResult := ReadResult{ 528 Series: series, 529 Meta: block.NewResultMetadata(), 530 } 531 532 nan1 := xjson.Map{ 533 "metric": xjson.Map{ 534 "qux": "qaz", 535 }, 536 "value": xjson.Array{ 537 1535948880, 538 "NaN", 539 }, 540 } 541 542 nan2 := xjson.Map{ 543 "metric": xjson.Map{ 544 "baz": "bar", 545 }, 546 "value": xjson.Array{ 547 1535948880, 548 "NaN", 549 }, 550 } 551 552 // Ensure idempotent by running first once with noop render. 553 r := renderResultsInstantaneousJSON(json.NewNoopWriter(), readResult, RenderResultsOptions{KeepNaNs: true}) 554 require.Equal(t, false, r.LimitedMaxReturnedData) 555 require.Equal(t, 2, r.Datapoints) 556 require.Equal(t, 2, r.Series) 557 require.Equal(t, 2, r.TotalSeries) 558 559 buffer := bytes.NewBuffer(nil) 560 jw := json.NewWriter(buffer) 561 r = renderResultsInstantaneousJSON(jw, readResult, RenderResultsOptions{KeepNaNs: true}) 562 require.NoError(t, jw.Close()) 563 require.Equal(t, false, r.LimitedMaxReturnedData) 564 require.Equal(t, 2, r.Datapoints) 565 require.Equal(t, 2, r.Series) 566 require.Equal(t, 2, r.TotalSeries) 567 568 expectedWithNaN := xtest.MustPrettyJSONMap(t, xjson.Map{ 569 "status": "success", 570 "data": xjson.Map{ 571 "resultType": "vector", 572 "result": xjson.Array{nan1, nan2}, 573 }, 574 }) 575 actualWithNaN := xtest.MustPrettyJSONString(t, buffer.String()) 576 assert.Equal(t, expectedWithNaN, actualWithNaN, xtest.Diff(expectedWithNaN, actualWithNaN)) 577 578 // Ensure idempotent by running first once with noop render. 579 r = renderResultsInstantaneousJSON(json.NewNoopWriter(), readResult, RenderResultsOptions{KeepNaNs: false}) 580 require.Equal(t, false, r.LimitedMaxReturnedData) 581 require.Equal(t, 0, r.Datapoints) 582 require.Equal(t, 0, r.Series) 583 require.Equal(t, 2, r.TotalSeries) // This is > rendered series due to keepNaN: false. 584 585 buffer = bytes.NewBuffer(nil) 586 jw = json.NewWriter(buffer) 587 r = renderResultsInstantaneousJSON(jw, readResult, RenderResultsOptions{KeepNaNs: false}) 588 require.NoError(t, jw.Close()) 589 require.Equal(t, false, r.LimitedMaxReturnedData) 590 require.Equal(t, 0, r.Datapoints) 591 require.Equal(t, 0, r.Series) 592 require.Equal(t, 2, r.TotalSeries) // This is > rendered series due to keepNaN: false. 593 expectedWithoutNaN := xtest.MustPrettyJSONMap(t, xjson.Map{ 594 "status": "success", 595 "data": xjson.Map{ 596 "resultType": "vector", 597 "result": xjson.Array{}, 598 }, 599 }) 600 actualWithoutNaN := xtest.MustPrettyJSONString(t, buffer.String()) 601 assert.Equal(t, expectedWithoutNaN, actualWithoutNaN, xtest.Diff(expectedWithoutNaN, actualWithoutNaN)) 602 } 603 604 func TestRenderInstantaneousResultsJSONScalar(t *testing.T) { 605 start := xtime.FromSeconds(1535948880) 606 607 series := []*ts.Series{ 608 ts.NewSeries( 609 []byte("foo"), 610 ts.NewFixedStepValues(10*time.Second, 1, 5, start), 611 test.TagSliceToTags([]models.Tag{})), 612 } 613 614 readResult := ReadResult{ 615 Series: series, 616 Meta: block.NewResultMetadata(), 617 BlockType: block.BlockScalar, 618 } 619 620 // Ensure idempotent by running first once with noop render. 621 r := renderResultsInstantaneousJSON(json.NewNoopWriter(), readResult, RenderResultsOptions{KeepNaNs: false}) 622 require.Equal(t, false, r.LimitedMaxReturnedData) 623 require.Equal(t, 1, r.Datapoints) 624 require.Equal(t, 1, r.Series) 625 require.Equal(t, 1, r.TotalSeries) 626 627 buffer := bytes.NewBuffer(nil) 628 jw := json.NewWriter(buffer) 629 r = renderResultsInstantaneousJSON(jw, readResult, RenderResultsOptions{KeepNaNs: false}) 630 require.NoError(t, jw.Close()) 631 require.Equal(t, false, r.LimitedMaxReturnedData) 632 require.Equal(t, 1, r.Datapoints) 633 require.Equal(t, 1, r.Series) 634 require.Equal(t, 1, r.TotalSeries) 635 636 expected := xtest.MustPrettyJSONMap(t, xjson.Map{ 637 "status": "success", 638 "data": xjson.Map{ 639 "resultType": "scalar", 640 "result": xjson.Array{ 641 1535948880, 642 "5", 643 }, 644 }, 645 }) 646 647 actual := xtest.MustPrettyJSONString(t, buffer.String()) 648 assert.Equal(t, expected, actual, xtest.Diff(expected, actual)) 649 } 650 651 func TestSanitizeSeries(t *testing.T) { 652 ctrl := gomock.NewController(t) 653 defer ctrl.Finish() 654 655 nan := math.NaN() 656 testData := []struct { 657 name string 658 data []float64 659 }{ 660 {"1", []float64{nan, nan, nan, nan}}, 661 {"2", []float64{nan, nan, nan, 1}}, 662 {"3", []float64{nan, nan, nan, nan}}, 663 {"4", []float64{nan, nan, 1, nan}}, 664 {"5", []float64{1, 1, 1, 1}}, 665 {"6", []float64{nan, nan, nan, nan}}, 666 {"no values", []float64{}}, 667 {"non-nan point is too early", []float64{1, nan, nan, nan, nan}}, 668 {"non-nan point is too late ", []float64{nan, nan, nan, nan, 1}}, 669 } 670 671 var ( 672 series = make([]*ts.Series, 0, len(testData)) 673 tags = models.NewTags(0, models.NewTagOptions()) 674 now = xtime.FromSeconds(1535948880) 675 step = time.Minute 676 start = now.Add(step) 677 end = now.Add(step * 3) 678 ) 679 680 for _, d := range testData { 681 vals := ts.NewMockValues(ctrl) 682 dps := make(ts.Datapoints, 0, len(d.data)) 683 for i, p := range d.data { 684 timestamp := now.Add(time.Duration(i) * step) 685 dps = append(dps, ts.Datapoint{Value: p, Timestamp: timestamp}) 686 vals.EXPECT().DatapointAt(i).Return(dps[i]) 687 } 688 689 vals.EXPECT().Len().Return(len(dps)) 690 series = append(series, ts.NewSeries([]byte(d.name), vals, tags)) 691 } 692 693 buffer := bytes.NewBuffer(nil) 694 jw := json.NewWriter(buffer) 695 r := RenderResultsJSON(jw, 696 ReadResult{Series: series}, 697 RenderResultsOptions{Start: start, End: end}) 698 require.NoError(t, jw.Close()) 699 700 require.Equal(t, false, r.LimitedMaxReturnedData) 701 require.Equal(t, 3, r.Series) 702 require.Equal(t, 9, r.TotalSeries) 703 require.Equal(t, 5, r.Datapoints) 704 705 expected := xtest.MustPrettyJSONMap(t, xjson.Map{ 706 "status": "success", 707 "data": xjson.Map{ 708 "resultType": "matrix", 709 "result": xjson.Array{ 710 xjson.Map{ 711 "metric": xjson.Map{}, 712 "values": xjson.Array{ 713 xjson.Array{ 714 1535949060, 715 "1", 716 }, 717 }, 718 }, 719 xjson.Map{ 720 "metric": xjson.Map{}, 721 "values": xjson.Array{ 722 xjson.Array{ 723 1535949000, 724 "1", 725 }, 726 }, 727 }, 728 xjson.Map{ 729 "metric": xjson.Map{}, 730 "values": xjson.Array{ 731 xjson.Array{ 732 1535948940, 733 "1", 734 }, 735 xjson.Array{ 736 1535949000, 737 "1", 738 }, 739 xjson.Array{ 740 1535949060, 741 "1", 742 }, 743 }, 744 }, 745 }, 746 }, 747 "warnings": xjson.Array{ 748 "m3db exceeded query limit: results not exhaustive", 749 }, 750 }) 751 actual := xtest.MustPrettyJSONString(t, buffer.String()) 752 assert.Equal(t, expected, actual, xtest.Diff(expected, actual)) 753 } 754 755 func TestRenderResultsJSONWithLimits(t *testing.T) { 756 buffer := bytes.NewBuffer(nil) 757 jw := json.NewWriter(buffer) 758 defer require.NoError(t, jw.Close()) 759 series := testSeries(5) 760 761 start := series[0].Values().DatapointAt(0).Timestamp 762 params := models.RequestParams{ 763 Start: start, 764 End: start.Add(time.Hour * 24), 765 } 766 767 intPrt := func(v int) *int { 768 return &v 769 } 770 771 tests := []struct { 772 name string 773 limit *int 774 expectedDatapoints int 775 expectedSeries int 776 expectedLimited bool 777 }{ 778 { 779 name: "Omit limit", 780 expectedDatapoints: 15, 781 expectedSeries: 3, 782 expectedLimited: false, 783 }, 784 { 785 name: "Below limit", 786 limit: intPrt(16), 787 expectedDatapoints: 15, 788 expectedSeries: 3, 789 expectedLimited: false, 790 }, 791 { 792 name: "At limit", 793 limit: intPrt(15), 794 expectedDatapoints: 15, 795 expectedSeries: 3, 796 expectedLimited: false, 797 }, 798 { 799 name: "Above limit - skip 1 series high", 800 limit: intPrt(14), 801 expectedDatapoints: 10, 802 expectedSeries: 2, 803 expectedLimited: true, 804 }, 805 { 806 name: "Above limit - skip 1 series low", 807 limit: intPrt(11), 808 expectedDatapoints: 10, 809 expectedSeries: 2, 810 expectedLimited: true, 811 }, 812 { 813 name: "Above limit - skip 1 series equal", 814 limit: intPrt(10), 815 expectedDatapoints: 10, 816 expectedSeries: 2, 817 expectedLimited: true, 818 }, 819 { 820 name: "Above limit - skip 2 series", 821 limit: intPrt(9), 822 expectedDatapoints: 5, 823 expectedSeries: 1, 824 expectedLimited: true, 825 }, 826 { 827 name: "Above limit - skip 3 series", 828 limit: intPrt(4), 829 expectedDatapoints: 0, 830 expectedSeries: 0, 831 expectedLimited: true, 832 }, 833 { 834 name: "Zero enforces no limit", 835 limit: intPrt(0), 836 expectedDatapoints: 15, 837 expectedSeries: 3, 838 expectedLimited: false, 839 }, 840 } 841 842 for _, test := range tests { 843 t.Run(test.name, func(t *testing.T) { 844 readResult := ReadResult{Series: series} 845 o := RenderResultsOptions{ 846 Start: params.Start, 847 End: params.End, 848 KeepNaNs: true, 849 } 850 if test.limit != nil { 851 o.ReturnedDatapointsLimit = *test.limit 852 } 853 r := RenderResultsJSON(jw, readResult, o) 854 require.Equal(t, 3, r.TotalSeries) 855 require.Equal(t, test.expectedSeries, r.Series) 856 require.Equal(t, test.expectedDatapoints, r.Datapoints) 857 require.Equal(t, test.expectedLimited, r.LimitedMaxReturnedData) 858 }) 859 } 860 } 861 862 func testSeries(datapointsPerSeries int) []*ts.Series { 863 start := xtime.FromSeconds(1535948880) 864 valsWithNaN := ts.NewFixedStepValues(10*time.Second, datapointsPerSeries, 1, start) 865 valsWithNaN.SetValueAt(1, math.NaN()) 866 return []*ts.Series{ 867 ts.NewSeries([]byte("foo"), 868 valsWithNaN, test.TagSliceToTags([]models.Tag{ 869 {Name: []byte("bar"), Value: []byte("baz")}, 870 {Name: []byte("qux"), Value: []byte("qaz")}, 871 })), 872 ts.NewSeries([]byte("bar"), 873 ts.NewFixedStepValues(10*time.Second, datapointsPerSeries, 2, start), 874 test.TagSliceToTags([]models.Tag{ 875 {Name: []byte("baz"), Value: []byte("bar")}, 876 {Name: []byte("qaz"), Value: []byte("qux")}, 877 })), 878 ts.NewSeries([]byte("foobar"), 879 ts.NewFixedStepValues(10*time.Second, datapointsPerSeries, math.NaN(), start), 880 test.TagSliceToTags([]models.Tag{ 881 {Name: []byte("biz"), Value: []byte("baz")}, 882 {Name: []byte("qux"), Value: []byte("qaz")}, 883 })), 884 } 885 }