github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/graphite/render_test.go (about) 1 // Copyright (c) 2019 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 graphite 22 23 import ( 24 "fmt" 25 "io/ioutil" 26 "math" 27 "net/http" 28 "net/http/httptest" 29 "testing" 30 "time" 31 32 "github.com/m3db/m3/src/query/api/v1/handler/prometheus/handleroptions" 33 "github.com/m3db/m3/src/query/api/v1/options" 34 "github.com/m3db/m3/src/query/block" 35 "github.com/m3db/m3/src/query/graphite/graphite" 36 graphiteStorage "github.com/m3db/m3/src/query/graphite/storage" 37 "github.com/m3db/m3/src/query/models" 38 "github.com/m3db/m3/src/query/storage" 39 "github.com/m3db/m3/src/query/storage/mock" 40 "github.com/m3db/m3/src/query/ts" 41 "github.com/m3db/m3/src/x/headers" 42 xtest "github.com/m3db/m3/src/x/test" 43 xtime "github.com/m3db/m3/src/x/time" 44 45 "github.com/golang/mock/gomock" 46 "github.com/stretchr/testify/assert" 47 "github.com/stretchr/testify/require" 48 ) 49 50 func testHandlerOptions(t *testing.T) options.HandlerOptions { 51 fetchOptsBuilder, err := handleroptions.NewFetchOptionsBuilder( 52 handleroptions.FetchOptionsBuilderOptions{ 53 Timeout: 15 * time.Second, 54 }) 55 require.NoError(t, err) 56 57 return options.EmptyHandlerOptions(). 58 SetQueryContextOptions(models.QueryContextOptions{}). 59 SetGraphiteFindFetchOptionsBuilder(fetchOptsBuilder). 60 SetGraphiteRenderFetchOptionsBuilder(fetchOptsBuilder) 61 } 62 63 func makeBlockResult( 64 ctrl *gomock.Controller, 65 results *storage.FetchResult, 66 ) block.Result { 67 size := len(results.SeriesList) 68 unconsolidatedSeries := make([]block.UnconsolidatedSeries, 0, size) 69 metas := make([]block.SeriesMeta, 0, size) 70 for _, elem := range results.SeriesList { 71 meta := block.SeriesMeta{Name: elem.Name()} 72 series := block.NewUnconsolidatedSeries(elem.Values().Datapoints(), 73 meta, block.UnconsolidatedSeriesStats{}) 74 unconsolidatedSeries = append(unconsolidatedSeries, series) 75 metas = append(metas, meta) 76 } 77 78 bl := block.NewMockBlock(ctrl) 79 bl.EXPECT(). 80 SeriesIter(). 81 DoAndReturn(func() (block.SeriesIter, error) { 82 return block.NewUnconsolidatedSeriesIter(unconsolidatedSeries), nil 83 }). 84 AnyTimes() 85 bl.EXPECT().Close().Return(nil) 86 87 return block.Result{ 88 Blocks: []block.Block{bl}, 89 Metadata: results.Metadata, 90 } 91 } 92 93 func TestParseNoQuery(t *testing.T) { 94 mockStorage := mock.NewMockStorage() 95 96 opts := testHandlerOptions(t).SetStorage(mockStorage) 97 handler := NewRenderHandler(opts) 98 99 recorder := httptest.NewRecorder() 100 handler.ServeHTTP(recorder, newGraphiteReadHTTPRequest(t)) 101 102 res := recorder.Result() 103 require.Equal(t, 400, res.StatusCode) 104 } 105 106 func TestParseQueryNoResults(t *testing.T) { 107 ctrl := xtest.NewController(t) 108 defer ctrl.Finish() 109 110 store := storage.NewMockStorage(ctrl) 111 blockResult := makeBlockResult(ctrl, &storage.FetchResult{}) 112 store.EXPECT().FetchBlocks(gomock.Any(), gomock.Any(), gomock.Any()). 113 Return(blockResult, nil) 114 115 opts := testHandlerOptions(t).SetStorage(store) 116 handler := NewRenderHandler(opts) 117 118 req := newGraphiteReadHTTPRequest(t) 119 req.URL.RawQuery = "target=foo.bar&from=-2h&until=now" 120 recorder := httptest.NewRecorder() 121 handler.ServeHTTP(recorder, req) 122 123 res := recorder.Result() 124 require.Equal(t, 200, res.StatusCode) 125 126 buf, err := ioutil.ReadAll(res.Body) 127 require.NoError(t, err) 128 require.Equal(t, []byte("[]"), buf) 129 } 130 131 func TestParseQueryResults(t *testing.T) { 132 resolution := 10 * time.Second 133 truncateStart := xtime.Now().Add(-30 * time.Minute).Truncate(resolution) 134 start := truncateStart.Add(time.Second) 135 vals := ts.NewFixedStepValues(resolution, 3, 3, start) 136 tags := models.NewTags(0, nil) 137 tags = tags.AddTag(models.Tag{Name: graphite.TagName(0), Value: []byte("foo")}) 138 tags = tags.AddTag(models.Tag{Name: graphite.TagName(1), Value: []byte("bar")}) 139 seriesList := ts.SeriesList{ 140 ts.NewSeries([]byte("series_name"), vals, tags), 141 } 142 143 meta := block.NewResultMetadata() 144 meta.Resolutions = []time.Duration{resolution} 145 fr := &storage.FetchResult{ 146 SeriesList: seriesList, 147 Metadata: meta, 148 } 149 150 ctrl := xtest.NewController(t) 151 defer ctrl.Finish() 152 153 store := storage.NewMockStorage(ctrl) 154 blockResult := makeBlockResult(ctrl, fr) 155 store.EXPECT().FetchBlocks(gomock.Any(), gomock.Any(), gomock.Any()). 156 Return(blockResult, nil) 157 158 opts := testHandlerOptions(t).SetStorage(store) 159 handler := NewRenderHandler(opts) 160 161 req := newGraphiteReadHTTPRequest(t) 162 req.URL.RawQuery = fmt.Sprintf("target=foo.bar&from=%d&until=%d", 163 start.Seconds(), start.Seconds()+30) 164 recorder := httptest.NewRecorder() 165 handler.ServeHTTP(recorder, req) 166 167 res := recorder.Result() 168 assert.Equal(t, 200, res.StatusCode) 169 170 buf, err := ioutil.ReadAll(res.Body) 171 require.NoError(t, err) 172 exTimestamp := truncateStart.Seconds() + 10 173 expected := fmt.Sprintf( 174 `[{"target":"series_name","datapoints":[[3.000000,%d],`+ 175 `[3.000000,%d],[null,%d]],"step_size_ms":%d}]`, 176 exTimestamp, exTimestamp+10, exTimestamp+20, resolution/time.Millisecond) 177 178 require.Equal(t, expected, string(buf)) 179 } 180 181 func TestParseQueryResultsMaxDatapoints(t *testing.T) { 182 startStr := "03/07/14" 183 endStr := "03/07/15" 184 start, err := graphite.ParseTime(startStr, time.Now(), 0) 185 require.NoError(t, err) 186 end, err := graphite.ParseTime(endStr, time.Now(), 0) 187 require.NoError(t, err) 188 189 resolution := 10 * time.Second 190 vals := ts.NewFixedStepValues(resolution, 4, 4, xtime.ToUnixNano(start)) 191 seriesList := ts.SeriesList{ 192 ts.NewSeries([]byte("a"), vals, models.NewTags(0, nil)), 193 } 194 195 meta := block.NewResultMetadata() 196 meta.Resolutions = []time.Duration{resolution} 197 fr := &storage.FetchResult{ 198 SeriesList: seriesList, 199 Metadata: meta, 200 } 201 202 ctrl := xtest.NewController(t) 203 defer ctrl.Finish() 204 205 store := storage.NewMockStorage(ctrl) 206 blockResult := makeBlockResult(ctrl, fr) 207 store.EXPECT().FetchBlocks(gomock.Any(), gomock.Any(), gomock.Any()). 208 Return(blockResult, nil) 209 210 opts := testHandlerOptions(t).SetStorage(store) 211 handler := NewRenderHandler(opts) 212 213 req := newGraphiteReadHTTPRequest(t) 214 req.URL.RawQuery = fmt.Sprintf( 215 "target=foo.bar&from=%s&until=%s&maxDataPoints=1", 216 startStr, endStr, 217 ) 218 recorder := httptest.NewRecorder() 219 handler.ServeHTTP(recorder, req) 220 221 res := recorder.Result() 222 require.Equal(t, 200, res.StatusCode) 223 224 buf, err := ioutil.ReadAll(res.Body) 225 require.NoError(t, err) 226 227 // Expected resolution should be in milliseconds and subsume all datapoints. 228 exStep := end.Sub(start) / time.Millisecond 229 expected := fmt.Sprintf( 230 `[{"target":"a","datapoints":[[4.000000,%d]],"step_size_ms":%d}]`, 231 start.Unix(), exStep) 232 233 require.Equal(t, expected, string(buf)) 234 } 235 236 func TestParseQueryResultsMultiTarget(t *testing.T) { 237 minsAgo := 12 238 resolution := 10 * time.Second 239 start := time.Now(). 240 Add(-1 * time.Duration(minsAgo) * time.Minute). 241 Truncate(resolution) 242 243 vals := ts.NewFixedStepValues(resolution, 3, 3, xtime.ToUnixNano(start)) 244 seriesList := ts.SeriesList{ 245 ts.NewSeries([]byte("a"), vals, models.NewTags(0, nil)), 246 } 247 248 meta := block.NewResultMetadata() 249 meta.Resolutions = []time.Duration{resolution} 250 fr := &storage.FetchResult{ 251 SeriesList: seriesList, 252 Metadata: meta, 253 } 254 255 ctrl := xtest.NewController(t) 256 defer ctrl.Finish() 257 258 store := storage.NewMockStorage(ctrl) 259 store.EXPECT().FetchBlocks(gomock.Any(), gomock.Any(), gomock.Any()). 260 Return(makeBlockResult(ctrl, fr), nil) 261 store.EXPECT().FetchBlocks(gomock.Any(), gomock.Any(), gomock.Any()). 262 Return(makeBlockResult(ctrl, fr), nil) 263 264 opts := testHandlerOptions(t).SetStorage(store) 265 handler := NewRenderHandler(opts) 266 267 req := newGraphiteReadHTTPRequest(t) 268 req.URL.RawQuery = fmt.Sprintf( 269 "target=foo.bar&target=baz.qux&from=%d&until=%d", 270 start.Unix(), start.Unix()+30, 271 ) 272 recorder := httptest.NewRecorder() 273 handler.ServeHTTP(recorder, req) 274 275 res := recorder.Result() 276 require.Equal(t, 200, res.StatusCode) 277 278 buf, err := ioutil.ReadAll(res.Body) 279 require.NoError(t, err) 280 281 expected := fmt.Sprintf( 282 `[{"target":"a","datapoints":[[3.000000,%d],`+ 283 `[3.000000,%d],[3.000000,%d]],"step_size_ms":%d},`+ 284 `{"target":"a","datapoints":[[3.000000,%d],`+ 285 `[3.000000,%d],[3.000000,%d]],"step_size_ms":%d}]`, 286 start.Unix(), start.Unix()+10, start.Unix()+20, resolution/time.Millisecond, 287 start.Unix(), start.Unix()+10, start.Unix()+20, resolution/time.Millisecond) 288 289 require.Equal(t, expected, string(buf)) 290 } 291 292 func TestParseQueryResultsMultiTargetWithLimits(t *testing.T) { 293 for _, tt := range limitTests { 294 t.Run(tt.name, func(t *testing.T) { 295 minsAgo := 12 296 start := time.Now().Add(-1 * time.Duration(minsAgo) * time.Minute) 297 resolution := 10 * time.Second 298 vals := ts.NewFixedStepValues(resolution, 3, 3, xtime.ToUnixNano(start)) 299 seriesList := ts.SeriesList{ 300 ts.NewSeries([]byte("a"), vals, models.NewTags(0, nil)), 301 } 302 303 meta := block.NewResultMetadata() 304 meta.Resolutions = []time.Duration{resolution} 305 meta.Exhaustive = tt.ex 306 frOne := &storage.FetchResult{SeriesList: seriesList, Metadata: meta} 307 308 metaTwo := block.NewResultMetadata() 309 metaTwo.Resolutions = []time.Duration{resolution} 310 if !tt.ex2 { 311 metaTwo.AddWarning("foo", "bar") 312 } 313 314 frTwo := &storage.FetchResult{SeriesList: seriesList, Metadata: metaTwo} 315 316 ctrl := xtest.NewController(t) 317 defer ctrl.Finish() 318 319 store := storage.NewMockStorage(ctrl) 320 store.EXPECT().FetchBlocks(gomock.Any(), gomock.Any(), gomock.Any()). 321 Return(makeBlockResult(ctrl, frOne), nil) 322 store.EXPECT().FetchBlocks(gomock.Any(), gomock.Any(), gomock.Any()). 323 Return(makeBlockResult(ctrl, frTwo), nil) 324 325 opts := testHandlerOptions(t).SetStorage(store) 326 handler := NewRenderHandler(opts) 327 328 req := newGraphiteReadHTTPRequest(t) 329 req.URL.RawQuery = fmt.Sprintf( 330 "target=foo.bar&target=bar.baz&from=%d&until=%d", 331 start.Unix(), start.Unix()+30, 332 ) 333 recorder := httptest.NewRecorder() 334 handler.ServeHTTP(recorder, req) 335 336 actual := recorder.Header().Get(headers.LimitHeader) 337 assert.Equal(t, tt.header, actual) 338 }) 339 } 340 } 341 342 func TestParseQueryResultsAllNaN(t *testing.T) { 343 resolution := 10 * time.Second 344 truncateStart := time.Now().Add(-30 * time.Minute).Truncate(resolution) 345 start := truncateStart.Add(time.Second) 346 vals := ts.NewFixedStepValues(resolution, 3, math.NaN(), xtime.ToUnixNano(start)) 347 tags := models.NewTags(0, nil) 348 seriesList := ts.SeriesList{ 349 ts.NewSeries([]byte("series_name"), vals, tags), 350 } 351 352 meta := block.NewResultMetadata() 353 meta.Resolutions = []time.Duration{resolution} 354 fr := &storage.FetchResult{ 355 SeriesList: seriesList, 356 Metadata: meta, 357 } 358 359 ctrl := xtest.NewController(t) 360 defer ctrl.Finish() 361 362 store := storage.NewMockStorage(ctrl) 363 blockResult := makeBlockResult(ctrl, fr) 364 store.EXPECT().FetchBlocks(gomock.Any(), gomock.Any(), gomock.Any()). 365 Return(blockResult, nil) 366 367 graphiteStorageOpts := graphiteStorage.M3WrappedStorageOptions{ 368 RenderSeriesAllNaNs: true, 369 } 370 opts := testHandlerOptions(t). 371 SetStorage(store). 372 SetGraphiteStorageOptions(graphiteStorageOpts) 373 handler := NewRenderHandler(opts) 374 375 req := newGraphiteReadHTTPRequest(t) 376 req.URL.RawQuery = fmt.Sprintf("target=foo.bar&from=%d&until=%d", 377 start.Unix(), start.Unix()+30) 378 recorder := httptest.NewRecorder() 379 handler.ServeHTTP(recorder, req) 380 381 res := recorder.Result() 382 assert.Equal(t, 200, res.StatusCode) 383 384 buf, err := ioutil.ReadAll(res.Body) 385 require.NoError(t, err) 386 exTimestamp := truncateStart.Unix() + 10 387 expected := fmt.Sprintf( 388 `[{"target":"series_name","datapoints":[[null,%d],`+ 389 `[null,%d],[null,%d]],"step_size_ms":%d}]`, 390 exTimestamp, exTimestamp+10, exTimestamp+20, resolution/time.Millisecond) 391 392 require.Equal(t, expected, string(buf)) 393 } 394 395 func newGraphiteReadHTTPRequest(t *testing.T) *http.Request { 396 req, err := http.NewRequest(ReadHTTPMethods[0], ReadURL, nil) 397 require.NoError(t, err) 398 return req 399 }