github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/prometheus/native/common.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 "fmt" 25 "math" 26 "net/http" 27 "strconv" 28 "time" 29 30 "github.com/m3db/m3/src/query/api/v1/handler/prometheus" 31 "github.com/m3db/m3/src/query/api/v1/handler/prometheus/handleroptions" 32 "github.com/m3db/m3/src/query/block" 33 "github.com/m3db/m3/src/query/errors" 34 "github.com/m3db/m3/src/query/executor" 35 "github.com/m3db/m3/src/query/functions/utils" 36 "github.com/m3db/m3/src/query/models" 37 "github.com/m3db/m3/src/query/storage" 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 xtime "github.com/m3db/m3/src/x/time" 42 ) 43 44 const ( 45 // QueryParam is the name of the query form/url parameter 46 QueryParam = "query" 47 48 endParam = "end" 49 startParam = "start" 50 timeParam = "time" 51 debugParam = "debug" 52 endExclusiveParam = "end-exclusive" 53 blockTypeParam = "block-type" 54 55 formatErrStr = "error parsing param: %s, error: %v" 56 ) 57 58 // parseParams parses all params from the GET request 59 func parseParams( 60 r *http.Request, 61 engineOpts executor.EngineOptions, 62 fetchOpts *storage.FetchOptions, 63 ) (models.RequestParams, error) { 64 var params models.RequestParams 65 66 if err := r.ParseForm(); err != nil { 67 err = fmt.Errorf(formatErrStr, timeParam, err) 68 return params, xerrors.NewInvalidParamsError(err) 69 } 70 71 timeParams, err := prometheus.ParseTimeParams(r) 72 if err != nil { 73 return params, err 74 } 75 76 params.Now = timeParams.Now 77 params.Start = xtime.ToUnixNano(timeParams.Start) 78 params.End = xtime.ToUnixNano(timeParams.End) 79 80 timeout := fetchOpts.Timeout 81 if timeout <= 0 { 82 err := fmt.Errorf("expected positive timeout, instead got: %d", timeout) 83 return params, xerrors.NewInvalidParamsError( 84 fmt.Errorf(formatErrStr, handleroptions.TimeoutParam, err)) 85 } 86 params.Timeout = timeout 87 88 step := fetchOpts.Step 89 if step <= 0 { 90 err := fmt.Errorf("expected positive step size, instead got: %d", step) 91 return params, xerrors.NewInvalidParamsError( 92 fmt.Errorf(formatErrStr, handleroptions.StepParam, err)) 93 } 94 params.Step = fetchOpts.Step 95 96 query, err := ParseQuery(r) 97 if err != nil { 98 return params, xerrors.NewInvalidParamsError( 99 fmt.Errorf(formatErrStr, QueryParam, err)) 100 } 101 params.Query = query 102 103 if debugVal := r.FormValue(debugParam); debugVal != "" { 104 params.Debug, err = strconv.ParseBool(debugVal) 105 if err != nil { 106 return params, xerrors.NewInvalidParamsError( 107 fmt.Errorf(formatErrStr, debugParam, err)) 108 } 109 } 110 111 params.BlockType = models.TypeSingleBlock 112 if blockType := r.FormValue(blockTypeParam); blockType != "" { 113 intVal, err := strconv.ParseInt(blockType, 10, 8) 114 if err != nil { 115 return params, xerrors.NewInvalidParamsError( 116 fmt.Errorf(formatErrStr, blockTypeParam, err)) 117 } 118 119 blockType := models.FetchedBlockType(intVal) 120 121 // Ignore error from receiving an invalid block type, and return default. 122 if err := blockType.Validate(); err != nil { 123 return params, xerrors.NewInvalidParamsError( 124 fmt.Errorf(formatErrStr, blockTypeParam, err)) 125 } 126 127 params.BlockType = blockType 128 } 129 130 // Default to including end if unable to parse the flag 131 endExclusiveVal := r.FormValue(endExclusiveParam) 132 params.IncludeEnd = true 133 if endExclusiveVal != "" { 134 excludeEnd, err := strconv.ParseBool(endExclusiveVal) 135 if err != nil { 136 return params, xerrors.NewInvalidParamsError( 137 fmt.Errorf(formatErrStr, endExclusiveParam, err)) 138 } 139 140 params.IncludeEnd = !excludeEnd 141 } 142 143 params.LookbackDuration = engineOpts.LookbackDuration() 144 if v := fetchOpts.LookbackDuration; v != nil { 145 params.LookbackDuration = *v 146 } 147 148 return params, nil 149 } 150 151 // parseInstantaneousParams parses all params from the GET request 152 func parseInstantaneousParams( 153 r *http.Request, 154 engineOpts executor.EngineOptions, 155 fetchOpts *storage.FetchOptions, 156 ) (models.RequestParams, error) { 157 if err := r.ParseForm(); err != nil { 158 return models.RequestParams{}, xerrors.NewInvalidParamsError(err) 159 } 160 161 if fetchOpts.Step == 0 { 162 fetchOpts.Step = time.Second 163 } 164 165 prometheus.SetDefaultStartEndParamsForInstant(r) 166 params, err := parseParams(r, engineOpts, fetchOpts) 167 if err != nil { 168 return params, err 169 } 170 171 return params, nil 172 } 173 174 // ParseQuery parses a query out of an HTTP request. 175 func ParseQuery(r *http.Request) (string, error) { 176 if err := r.ParseForm(); err != nil { 177 return "", err 178 } 179 180 // NB(schallert): r.Form is generic over GET and POST requests, with body 181 // parameters taking precedence over URL parameters (see r.ParseForm() docs 182 // for more details). We depend on the generic behavior for properly parsing 183 // POST and GET queries. 184 queries, ok := r.Form[QueryParam] 185 if !ok || len(queries) == 0 || queries[0] == "" { 186 return "", errors.ErrNoQueryFound 187 } 188 189 // TODO: currently, we only support one target at a time 190 if len(queries) > 1 { 191 return "", errors.ErrBatchQuery 192 } 193 194 return queries[0], nil 195 } 196 197 // RenderResultsOptions is a set of options for rendering the result. 198 type RenderResultsOptions struct { 199 KeepNaNs bool 200 Start xtime.UnixNano 201 End xtime.UnixNano 202 ReturnedSeriesLimit int 203 ReturnedDatapointsLimit int 204 } 205 206 // RenderResultsResult is the result from rendering results. 207 type RenderResultsResult struct { 208 // Datapoints is the count of datapoints rendered. 209 Datapoints int 210 // Series is the count of series rendered. 211 Series int 212 // TotalSeries is the count of series in total. 213 TotalSeries int 214 // LimitedMaxReturnedData indicates if the results rendering 215 // was truncated by a limit on returned series or datapoints. 216 LimitedMaxReturnedData bool 217 } 218 219 // RenderResultsJSON renders results in JSON for range queries. 220 func RenderResultsJSON( 221 jw json.Writer, 222 result ReadResult, 223 opts RenderResultsOptions, 224 ) RenderResultsResult { 225 var ( 226 series = result.Series 227 warnings = result.Meta.WarningStrings() 228 seriesRendered = 0 229 datapointsRendered = 0 230 limited = false 231 ) 232 233 jw.BeginObject() 234 235 jw.BeginObjectField("status") 236 jw.WriteString("success") 237 238 if len(warnings) > 0 { 239 jw.BeginObjectField("warnings") 240 jw.BeginArray() 241 for _, warn := range warnings { 242 jw.WriteString(warn) 243 } 244 245 jw.EndArray() 246 } 247 248 jw.BeginObjectField("data") 249 jw.BeginObject() 250 251 jw.BeginObjectField("resultType") 252 jw.WriteString("matrix") 253 254 jw.BeginObjectField("result") 255 jw.BeginArray() 256 for _, s := range series { 257 vals := s.Values() 258 length := s.Len() 259 260 // If a limit of the number of datapoints is present, then write 261 // out series' data up until that limit is hit. 262 if opts.ReturnedSeriesLimit > 0 && seriesRendered+1 > opts.ReturnedSeriesLimit { 263 limited = true 264 break 265 } 266 if opts.ReturnedDatapointsLimit > 0 && datapointsRendered+length > opts.ReturnedDatapointsLimit { 267 limited = true 268 break 269 } 270 271 hasData := false 272 for i := 0; i < length; i++ { 273 dp := vals.DatapointAt(i) 274 275 // If keepNaNs is set to false and the value is NaN, drop it from the response. 276 // If the series has no datapoints at all then this datapoint iteration will 277 // count zero total and end up skipping writing the series entirely. 278 if !opts.KeepNaNs && math.IsNaN(dp.Value) { 279 continue 280 } 281 282 // Skip points before the query boundary. Ideal place to adjust these 283 // would be at the result node but that would make it inefficient since 284 // we would need to create another block just for the sake of restricting 285 // the bounds. 286 if dp.Timestamp.Before(opts.Start) || dp.Timestamp.After(opts.End) { 287 continue 288 } 289 290 // On first datapoint for the series, write out the series beginning content. 291 if !hasData { 292 jw.BeginObject() 293 jw.BeginObjectField("metric") 294 jw.BeginObject() 295 for _, t := range s.Tags.Tags { 296 jw.BeginObjectBytesField(t.Name) 297 jw.WriteBytesString(t.Value) 298 } 299 jw.EndObject() 300 301 jw.BeginObjectField("values") 302 jw.BeginArray() 303 304 seriesRendered++ 305 hasData = true 306 } 307 datapointsRendered++ 308 309 jw.BeginArray() 310 jw.WriteInt(int(dp.Timestamp.Seconds())) 311 jw.WriteString(utils.FormatFloat(dp.Value)) 312 jw.EndArray() 313 } 314 315 if !hasData { 316 // No datapoints written for series so continue to 317 // next instead of writing the end content. 318 continue 319 } 320 321 jw.EndArray() 322 fixedStep, ok := s.Values().(ts.FixedResolutionMutableValues) 323 if ok { 324 jw.BeginObjectField("step_size_ms") 325 jw.WriteInt(int(fixedStep.Resolution() / time.Millisecond)) 326 } 327 jw.EndObject() 328 } 329 jw.EndArray() 330 jw.EndObject() 331 332 jw.EndObject() 333 return RenderResultsResult{ 334 Series: seriesRendered, 335 Datapoints: datapointsRendered, 336 TotalSeries: len(series), 337 LimitedMaxReturnedData: limited, 338 } 339 } 340 341 // renderResultsInstantaneousJSON renders results in JSON for instant queries. 342 func renderResultsInstantaneousJSON( 343 jw json.Writer, 344 result ReadResult, 345 opts RenderResultsOptions, 346 ) RenderResultsResult { 347 var ( 348 series = result.Series 349 warnings = result.Meta.WarningStrings() 350 isScalar = result.BlockType == block.BlockScalar || result.BlockType == block.BlockTime 351 keepNaNs = opts.KeepNaNs 352 returnedCount = 0 353 limited = false 354 ) 355 356 resultType := "vector" 357 if isScalar { 358 resultType = "scalar" 359 } 360 361 jw.BeginObject() 362 363 jw.BeginObjectField("status") 364 jw.WriteString("success") 365 366 if len(warnings) > 0 { 367 jw.BeginObjectField("warnings") 368 jw.BeginArray() 369 for _, warn := range warnings { 370 jw.WriteString(warn) 371 } 372 373 jw.EndArray() 374 } 375 376 jw.BeginObjectField("data") 377 jw.BeginObject() 378 379 jw.BeginObjectField("resultType") 380 jw.WriteString(resultType) 381 382 jw.BeginObjectField("result") 383 jw.BeginArray() 384 for _, s := range series { 385 vals := s.Values() 386 length := s.Len() 387 dp := vals.DatapointAt(length - 1) 388 389 if opts.ReturnedSeriesLimit > 0 && returnedCount >= opts.ReturnedSeriesLimit { 390 limited = true 391 break 392 } 393 if opts.ReturnedDatapointsLimit > 0 && returnedCount >= opts.ReturnedDatapointsLimit { 394 limited = true 395 break 396 } 397 398 if isScalar { 399 jw.WriteInt(int(dp.Timestamp.Seconds())) 400 jw.WriteString(utils.FormatFloat(dp.Value)) 401 returnedCount++ 402 continue 403 } 404 405 // If keepNaNs is set to false and the value is NaN, drop it from the response. 406 if !keepNaNs && math.IsNaN(dp.Value) { 407 continue 408 } 409 410 returnedCount++ 411 412 jw.BeginObject() 413 jw.BeginObjectField("metric") 414 jw.BeginObject() 415 for _, t := range s.Tags.Tags { 416 jw.BeginObjectBytesField(t.Name) 417 jw.WriteBytesString(t.Value) 418 } 419 jw.EndObject() 420 421 jw.BeginObjectField("value") 422 jw.BeginArray() 423 jw.WriteInt(int(dp.Timestamp.Seconds())) 424 jw.WriteString(utils.FormatFloat(dp.Value)) 425 jw.EndArray() 426 jw.EndObject() 427 } 428 jw.EndArray() 429 430 jw.EndObject() 431 432 jw.EndObject() 433 434 return RenderResultsResult{ 435 LimitedMaxReturnedData: limited, 436 // Series and datapoints are the same count for instant 437 // queries since a series has one datapoint. 438 Datapoints: returnedCount, 439 Series: returnedCount, 440 TotalSeries: len(series), 441 } 442 }