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  }