github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/prometheus/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 prometheus
    22  
    23  import (
    24  	goerrors "errors"
    25  	"fmt"
    26  	"io"
    27  	"io/ioutil"
    28  	"net/http"
    29  	"time"
    30  
    31  	"github.com/golang/snappy"
    32  
    33  	"github.com/m3db/m3/src/query/errors"
    34  	"github.com/m3db/m3/src/query/models"
    35  	xpromql "github.com/m3db/m3/src/query/parser/promql"
    36  	"github.com/m3db/m3/src/query/storage"
    37  	"github.com/m3db/m3/src/query/storage/m3/consolidators"
    38  	"github.com/m3db/m3/src/query/ts"
    39  	"github.com/m3db/m3/src/query/util"
    40  	"github.com/m3db/m3/src/query/util/json"
    41  	xerrors "github.com/m3db/m3/src/x/errors"
    42  	xtime "github.com/m3db/m3/src/x/time"
    43  )
    44  
    45  const (
    46  	queryParam   = "query"
    47  	endParam     = "end"
    48  	startParam   = "start"
    49  	nowTimeValue = "now"
    50  	timeParam    = "time"
    51  	formatErrStr = "error parsing param: %s, error: %v"
    52  
    53  	filterNameTagsParam = "tag"
    54  	errFormatStr        = "error parsing param: %s, error: %v"
    55  	tolerance           = 0.0000001
    56  )
    57  
    58  // ParsePromCompressedRequestResult is the result of a
    59  // ParsePromCompressedRequest call.
    60  type ParsePromCompressedRequestResult struct {
    61  	CompressedBody   []byte
    62  	UncompressedBody []byte
    63  }
    64  
    65  // ParsePromCompressedRequest parses a snappy compressed request from Prometheus.
    66  func ParsePromCompressedRequest(
    67  	r *http.Request,
    68  ) (ParsePromCompressedRequestResult, error) {
    69  	body := r.Body
    70  	if r.Body == nil {
    71  		err := fmt.Errorf("empty request body")
    72  		return ParsePromCompressedRequestResult{},
    73  			xerrors.NewInvalidParamsError(err)
    74  	}
    75  
    76  	defer body.Close()
    77  
    78  	compressed, err := ioutil.ReadAll(body)
    79  	if err != nil {
    80  		return ParsePromCompressedRequestResult{}, err
    81  	}
    82  
    83  	reqBuf, err := snappy.Decode(nil, compressed)
    84  	if err != nil {
    85  		return ParsePromCompressedRequestResult{},
    86  			xerrors.NewInvalidParamsError(err)
    87  	}
    88  
    89  	return ParsePromCompressedRequestResult{
    90  		CompressedBody:   compressed,
    91  		UncompressedBody: reqBuf,
    92  	}, nil
    93  }
    94  
    95  // TagCompletionQueries are tag completion queries.
    96  type TagCompletionQueries struct {
    97  	// Queries are the tag completion queries.
    98  	Queries []*storage.CompleteTagsQuery
    99  	// NameOnly indicates name only
   100  	NameOnly bool
   101  }
   102  
   103  // ParseTagCompletionParamsToQueries parses all params from the GET request.
   104  // Returns queries, a boolean indicating if the query completes names only, and
   105  // any errors.
   106  func ParseTagCompletionParamsToQueries(
   107  	r *http.Request,
   108  ) (TagCompletionQueries, error) {
   109  	tagCompletionQueries := TagCompletionQueries{}
   110  	start, err := util.ParseTimeStringWithDefault(r.FormValue("start"),
   111  		time.Unix(0, 0))
   112  	if err != nil {
   113  		return tagCompletionQueries, xerrors.NewInvalidParamsError(err)
   114  	}
   115  
   116  	end, err := util.ParseTimeStringWithDefault(r.FormValue("end"),
   117  		time.Now())
   118  	if err != nil {
   119  		return tagCompletionQueries, xerrors.NewInvalidParamsError(err)
   120  	}
   121  
   122  	// If there is a result type field present, parse it and set
   123  	// complete name only parameter appropriately. Otherwise, default
   124  	// to returning both completed tag names and values
   125  	nameOnly := false
   126  	if result := r.FormValue("result"); result != "" {
   127  		switch result {
   128  		case "default":
   129  			// no-op
   130  		case "tagNamesOnly":
   131  			nameOnly = true
   132  		default:
   133  			return tagCompletionQueries, xerrors.NewInvalidParamsError(
   134  				errors.ErrInvalidResultParamError)
   135  		}
   136  	}
   137  
   138  	tagCompletionQueries.NameOnly = nameOnly
   139  	queries, err := parseTagCompletionQueries(r)
   140  	if err != nil {
   141  		err = fmt.Errorf(errFormatStr, queryParam, err)
   142  		return tagCompletionQueries, xerrors.NewInvalidParamsError(err)
   143  	}
   144  
   145  	tagQueries := make([]*storage.CompleteTagsQuery, 0, len(queries))
   146  	for _, query := range queries {
   147  		tagQuery := &storage.CompleteTagsQuery{
   148  			Start:            xtime.ToUnixNano(start),
   149  			End:              xtime.ToUnixNano(end),
   150  			CompleteNameOnly: nameOnly,
   151  		}
   152  
   153  		matchers, err := models.MatchersFromString(query)
   154  		if err != nil {
   155  			return tagCompletionQueries, xerrors.NewInvalidParamsError(err)
   156  		}
   157  
   158  		tagQuery.TagMatchers = matchers
   159  		filterNameTags := r.Form[filterNameTagsParam]
   160  		tagQuery.FilterNameTags = make([][]byte, len(filterNameTags))
   161  		for i, f := range filterNameTags {
   162  			tagQuery.FilterNameTags[i] = []byte(f)
   163  		}
   164  
   165  		tagQueries = append(tagQueries, tagQuery)
   166  	}
   167  
   168  	tagCompletionQueries.Queries = tagQueries
   169  	return tagCompletionQueries, nil
   170  }
   171  
   172  func parseTagCompletionQueries(r *http.Request) ([]string, error) {
   173  	queries, ok := r.URL.Query()[queryParam]
   174  	if !ok || len(queries) == 0 || queries[0] == "" {
   175  		return nil, xerrors.NewInvalidParamsError(errors.ErrNoQueryFound)
   176  	}
   177  
   178  	return queries, nil
   179  }
   180  
   181  // ParseStartAndEnd parses start and end params from the request.
   182  func ParseStartAndEnd(
   183  	r *http.Request,
   184  	parseOpts xpromql.ParseOptions,
   185  ) (time.Time, time.Time, error) {
   186  	if err := r.ParseForm(); err != nil {
   187  		return time.Time{}, time.Time{}, xerrors.NewInvalidParamsError(err)
   188  	}
   189  
   190  	defaultTime := time.Unix(0, 0)
   191  	start, err := util.ParseTimeStringWithDefault(r.FormValue("start"), defaultTime)
   192  	if err != nil {
   193  		return time.Time{}, time.Time{}, xerrors.NewInvalidParamsError(err)
   194  	}
   195  
   196  	if parseOpts.RequireStartEndTime() && start.Equal(defaultTime) {
   197  		return time.Time{}, time.Time{}, xerrors.NewInvalidParamsError(
   198  			goerrors.New("invalid start time. start time must be set"))
   199  	}
   200  
   201  	end, err := util.ParseTimeStringWithDefault(r.FormValue("end"),
   202  		parseOpts.NowFn()())
   203  	if err != nil {
   204  		return time.Time{}, time.Time{}, xerrors.NewInvalidParamsError(err)
   205  	}
   206  
   207  	if start.After(end) {
   208  		err := fmt.Errorf("start %v must be after end %v", start, end)
   209  		return time.Time{}, time.Time{}, xerrors.NewInvalidParamsError(err)
   210  	}
   211  
   212  	return start, end, nil
   213  }
   214  
   215  // ParseSeriesMatchQuery parses all params from the GET request.
   216  func ParseSeriesMatchQuery(
   217  	r *http.Request,
   218  	parseOpts xpromql.ParseOptions,
   219  	tagOptions models.TagOptions,
   220  ) ([]*storage.FetchQuery, error) {
   221  	if err := r.ParseForm(); err != nil {
   222  		return nil, xerrors.NewInvalidParamsError(err)
   223  	}
   224  
   225  	matcherValues := r.Form["match[]"]
   226  	if len(matcherValues) == 0 {
   227  		return nil, xerrors.NewInvalidParamsError(errors.ErrInvalidMatchers)
   228  	}
   229  
   230  	start, end, err := ParseStartAndEnd(r, parseOpts)
   231  	if err != nil {
   232  		return nil, err
   233  	}
   234  
   235  	matchers, ok, err := ParseMatch(r, parseOpts, tagOptions)
   236  	if err != nil {
   237  		return nil, err
   238  	}
   239  	if !ok {
   240  		return nil, xerrors.NewInvalidParamsError(
   241  			fmt.Errorf("need more than one matcher: expected>=1, actual=%d", len(matchers)))
   242  	}
   243  
   244  	queries := make([]*storage.FetchQuery, 0, len(matcherValues))
   245  	// nolint:gocritic
   246  	for _, m := range matchers {
   247  		queries = append(queries, &storage.FetchQuery{
   248  			Raw:         fmt.Sprintf("match[]=%s", m.Match),
   249  			TagMatchers: m.Matchers,
   250  			Start:       start,
   251  			End:         end,
   252  		})
   253  	}
   254  
   255  	return queries, nil
   256  }
   257  
   258  // ParsedMatch is a parsed matched.
   259  type ParsedMatch struct {
   260  	Match    string
   261  	Matchers models.Matchers
   262  }
   263  
   264  // ParseMatch parses all match params from the GET request.
   265  func ParseMatch(
   266  	r *http.Request,
   267  	parseOpts xpromql.ParseOptions,
   268  	tagOptions models.TagOptions,
   269  ) ([]ParsedMatch, bool, error) {
   270  	if err := r.ParseForm(); err != nil {
   271  		return nil, false, xerrors.NewInvalidParamsError(err)
   272  	}
   273  
   274  	matcherValues := r.Form["match[]"]
   275  	if len(matcherValues) == 0 {
   276  		return nil, false, nil
   277  	}
   278  
   279  	matchers := make([]ParsedMatch, 0, len(matcherValues))
   280  	for _, str := range matcherValues {
   281  		m, err := parseMatch(parseOpts, tagOptions, str)
   282  		if err != nil {
   283  			return nil, false, err
   284  		}
   285  		matchers = append(matchers, ParsedMatch{
   286  			Match:    str,
   287  			Matchers: m,
   288  		})
   289  	}
   290  
   291  	return matchers, true, nil
   292  }
   293  
   294  func parseMatch(
   295  	parseOpts xpromql.ParseOptions,
   296  	tagOptions models.TagOptions,
   297  	matcher string,
   298  ) (models.Matchers, error) {
   299  	fn := parseOpts.MetricSelectorFn()
   300  
   301  	promMatchers, err := fn(matcher)
   302  	if err != nil {
   303  		return nil, xerrors.NewInvalidParamsError(err)
   304  	}
   305  
   306  	matchers, err := xpromql.LabelMatchersToModelMatcher(promMatchers, tagOptions)
   307  	if err != nil {
   308  		return nil, xerrors.NewInvalidParamsError(err)
   309  	}
   310  
   311  	return matchers, nil
   312  }
   313  
   314  func renderNameOnlyTagCompletionResultsJSON(
   315  	w io.Writer,
   316  	results []consolidators.CompletedTag,
   317  	opts RenderSeriesMetadataOptions,
   318  ) (RenderSeriesMetadataResult, error) {
   319  	var (
   320  		total    = len(results)
   321  		rendered = 0
   322  		limited  bool
   323  	)
   324  
   325  	jw := json.NewWriter(w)
   326  	jw.BeginArray()
   327  
   328  	for _, tag := range results {
   329  		if opts.ReturnedSeriesMetadataLimit > 0 && rendered >= opts.ReturnedSeriesMetadataLimit {
   330  			limited = true
   331  			break
   332  		}
   333  		rendered++
   334  		jw.WriteBytesString(tag.Name)
   335  	}
   336  
   337  	jw.EndArray()
   338  
   339  	return RenderSeriesMetadataResult{
   340  		Results:                rendered,
   341  		TotalResults:           total,
   342  		LimitedMaxReturnedData: limited,
   343  	}, jw.Close()
   344  }
   345  
   346  func renderDefaultTagCompletionResultsJSON(
   347  	w io.Writer,
   348  	results []consolidators.CompletedTag,
   349  	opts RenderSeriesMetadataOptions,
   350  ) (RenderSeriesMetadataResult, error) {
   351  	var (
   352  		total    = 0
   353  		rendered = 0
   354  		limited  bool
   355  	)
   356  
   357  	jw := json.NewWriter(w)
   358  	jw.BeginObject()
   359  
   360  	jw.BeginObjectField("hits")
   361  	jw.WriteInt(len(results))
   362  
   363  	jw.BeginObjectField("tags")
   364  	jw.BeginArray()
   365  
   366  	for _, tag := range results {
   367  		total += len(tag.Values)
   368  		if opts.ReturnedSeriesMetadataLimit > 0 && rendered >= opts.ReturnedSeriesMetadataLimit {
   369  			limited = true
   370  			continue
   371  		}
   372  
   373  		jw.BeginObject()
   374  
   375  		jw.BeginObjectField("key")
   376  		jw.WriteBytesString(tag.Name)
   377  
   378  		jw.BeginObjectField("values")
   379  		jw.BeginArray()
   380  		for _, value := range tag.Values {
   381  			if opts.ReturnedSeriesMetadataLimit > 0 && rendered >= opts.ReturnedSeriesMetadataLimit {
   382  				limited = true
   383  				break
   384  			}
   385  			rendered++
   386  
   387  			jw.WriteBytesString(value)
   388  		}
   389  		jw.EndArray()
   390  
   391  		jw.EndObject()
   392  	}
   393  	jw.EndArray()
   394  
   395  	jw.EndObject()
   396  
   397  	return RenderSeriesMetadataResult{
   398  		Results:                rendered,
   399  		TotalResults:           total,
   400  		LimitedMaxReturnedData: limited,
   401  	}, jw.Close()
   402  }
   403  
   404  // RenderSeriesMetadataOptions is a set of options for rendering
   405  // series metadata.
   406  type RenderSeriesMetadataOptions struct {
   407  	ReturnedSeriesMetadataLimit int
   408  }
   409  
   410  // RenderSeriesMetadataResult returns results about a series metadata rendering.
   411  type RenderSeriesMetadataResult struct {
   412  	// Results is how many results were rendered.
   413  	Results int
   414  	// TotalResults is how many results in total there were regardless
   415  	// of rendering.
   416  	TotalResults int
   417  	// LimitedMaxReturnedData indicates if results rendering
   418  	// was truncated by a limit.
   419  	LimitedMaxReturnedData bool
   420  }
   421  
   422  // RenderListTagResultsJSON renders list tag results to json format.
   423  func RenderListTagResultsJSON(
   424  	w io.Writer,
   425  	result *consolidators.CompleteTagsResult,
   426  	opts RenderSeriesMetadataOptions,
   427  ) (RenderSeriesMetadataResult, error) {
   428  	if !result.CompleteNameOnly {
   429  		return RenderSeriesMetadataResult{}, errors.ErrWithNames
   430  	}
   431  
   432  	var (
   433  		total    = len(result.CompletedTags)
   434  		rendered = 0
   435  		limited  bool
   436  	)
   437  
   438  	jw := json.NewWriter(w)
   439  	jw.BeginObject()
   440  
   441  	jw.BeginObjectField("status")
   442  	jw.WriteString("success")
   443  
   444  	jw.BeginObjectField("data")
   445  	jw.BeginArray()
   446  
   447  	for _, t := range result.CompletedTags {
   448  		if opts.ReturnedSeriesMetadataLimit > 0 && rendered >= opts.ReturnedSeriesMetadataLimit {
   449  			limited = true
   450  			break
   451  		}
   452  		rendered++
   453  		jw.WriteBytesString(t.Name)
   454  	}
   455  
   456  	jw.EndArray()
   457  	jw.EndObject()
   458  
   459  	return RenderSeriesMetadataResult{
   460  		Results:                rendered,
   461  		TotalResults:           total,
   462  		LimitedMaxReturnedData: limited,
   463  	}, jw.Close()
   464  }
   465  
   466  // RenderTagCompletionResultsJSON renders tag completion results to json format.
   467  func RenderTagCompletionResultsJSON(
   468  	w io.Writer,
   469  	result consolidators.CompleteTagsResult,
   470  	opts RenderSeriesMetadataOptions,
   471  ) (RenderSeriesMetadataResult, error) {
   472  	results := result.CompletedTags
   473  	if result.CompleteNameOnly {
   474  		return renderNameOnlyTagCompletionResultsJSON(w, results, opts)
   475  	}
   476  
   477  	return renderDefaultTagCompletionResultsJSON(w, results, opts)
   478  }
   479  
   480  // RenderTagValuesResultsJSON renders tag values results to json format.
   481  func RenderTagValuesResultsJSON(
   482  	w io.Writer,
   483  	result *consolidators.CompleteTagsResult,
   484  	opts RenderSeriesMetadataOptions,
   485  ) (RenderSeriesMetadataResult, error) {
   486  	if result.CompleteNameOnly {
   487  		return RenderSeriesMetadataResult{}, errors.ErrNamesOnly
   488  	}
   489  
   490  	tagCount := len(result.CompletedTags)
   491  	if tagCount > 1 {
   492  		return RenderSeriesMetadataResult{}, errors.ErrMultipleResults
   493  	}
   494  
   495  	var (
   496  		total    = 0
   497  		rendered = 0
   498  		limited  bool
   499  	)
   500  
   501  	jw := json.NewWriter(w)
   502  	jw.BeginObject()
   503  
   504  	jw.BeginObjectField("status")
   505  	jw.WriteString("success")
   506  
   507  	jw.BeginObjectField("data")
   508  	jw.BeginArray()
   509  
   510  	if tagCount > 0 {
   511  		// We have our single expected result.
   512  		values := result.CompletedTags[0].Values
   513  		total += len(values)
   514  		for _, value := range values {
   515  			if opts.ReturnedSeriesMetadataLimit > 0 && rendered >= opts.ReturnedSeriesMetadataLimit {
   516  				limited = true
   517  				break
   518  			}
   519  			rendered++
   520  			jw.WriteBytesString(value)
   521  		}
   522  	}
   523  
   524  	jw.EndArray()
   525  
   526  	jw.EndObject()
   527  
   528  	return RenderSeriesMetadataResult{
   529  		Results:                rendered,
   530  		TotalResults:           total,
   531  		LimitedMaxReturnedData: limited,
   532  	}, jw.Close()
   533  }
   534  
   535  // RenderSeriesMatchResultsJSON renders series match results to json format.
   536  func RenderSeriesMatchResultsJSON(
   537  	w io.Writer,
   538  	results []models.Metrics,
   539  	opts RenderSeriesMetadataOptions,
   540  ) (RenderSeriesMetadataResult, error) {
   541  	var (
   542  		total    = 0
   543  		rendered = 0
   544  		limited  bool
   545  	)
   546  
   547  	jw := json.NewWriter(w)
   548  	jw.BeginObject()
   549  
   550  	jw.BeginObjectField("status")
   551  	jw.WriteString("success")
   552  
   553  	jw.BeginObjectField("data")
   554  	jw.BeginArray()
   555  
   556  	for _, result := range results {
   557  		for _, tags := range result {
   558  			total += len(tags.Tags.Tags)
   559  			if opts.ReturnedSeriesMetadataLimit > 0 && rendered >= opts.ReturnedSeriesMetadataLimit {
   560  				limited = true
   561  				continue
   562  			}
   563  
   564  			jw.BeginObject()
   565  			for _, tag := range tags.Tags.Tags {
   566  				if opts.ReturnedSeriesMetadataLimit > 0 && rendered >= opts.ReturnedSeriesMetadataLimit {
   567  					limited = true
   568  					break
   569  				}
   570  				rendered++
   571  
   572  				jw.BeginObjectBytesField(tag.Name)
   573  				jw.WriteBytesString(tag.Value)
   574  			}
   575  
   576  			jw.EndObject()
   577  		}
   578  	}
   579  
   580  	jw.EndArray()
   581  	jw.EndObject()
   582  
   583  	return RenderSeriesMetadataResult{
   584  		Results:                rendered,
   585  		TotalResults:           total,
   586  		LimitedMaxReturnedData: limited,
   587  	}, jw.Close()
   588  }
   589  
   590  // FilterSeriesByOptions removes series tags based on options.
   591  func FilterSeriesByOptions(
   592  	series []*ts.Series,
   593  	opts *storage.FetchOptions,
   594  ) []*ts.Series {
   595  	if opts == nil {
   596  		return series
   597  	}
   598  
   599  	keys := opts.RestrictQueryOptions.GetRestrictByTag().GetFilterByNames()
   600  	if len(keys) > 0 {
   601  		for i, s := range series {
   602  			series[i].Tags = s.Tags.TagsWithoutKeys(keys)
   603  		}
   604  	}
   605  
   606  	return series
   607  }
   608  
   609  // ParseTime parses a time out of a request key, with a default value.
   610  func ParseTime(r *http.Request, key string, now time.Time) (time.Time, error) {
   611  	if t := r.FormValue(key); t != "" {
   612  		if t == nowTimeValue {
   613  			return now, nil
   614  		}
   615  		return util.ParseTimeString(t)
   616  	}
   617  	return time.Time{}, errors.ErrNotFound
   618  }
   619  
   620  // TimeParams represents the time parameters within a request.
   621  type TimeParams struct {
   622  	Now   time.Time
   623  	Start time.Time
   624  	End   time.Time
   625  }
   626  
   627  // ParseTimeParams parses the time params (now, start, end) from a request.
   628  func ParseTimeParams(r *http.Request) (TimeParams, error) {
   629  	var (
   630  		params TimeParams
   631  		err    error
   632  	)
   633  
   634  	params.Now = time.Now()
   635  	if v := r.FormValue(timeParam); v != "" {
   636  		var err error
   637  		params.Now, err = ParseTime(r, timeParam, params.Now)
   638  		if err != nil {
   639  			err = fmt.Errorf(formatErrStr, timeParam, err)
   640  			return params, xerrors.NewInvalidParamsError(err)
   641  		}
   642  	}
   643  
   644  	params.Start, err = ParseTime(r, startParam, params.Now)
   645  	if err != nil {
   646  		err = fmt.Errorf(formatErrStr, startParam, err)
   647  		return params, xerrors.NewInvalidParamsError(err)
   648  	}
   649  
   650  	params.End, err = ParseTime(r, endParam, params.Now)
   651  	if err != nil {
   652  		err = fmt.Errorf(formatErrStr, endParam, err)
   653  		return params, xerrors.NewInvalidParamsError(err)
   654  	}
   655  	if params.Start.After(params.End) {
   656  		err = fmt.Errorf("start (%s) must be before end (%s)", params.Start, params.End)
   657  		return params, xerrors.NewInvalidParamsError(err)
   658  	}
   659  
   660  	return params, nil
   661  }
   662  
   663  // SetDefaultStartEndParamsForInstant sets the start and end values for instant queries. Instant queries
   664  // don't specify start and end, but these params are required to be set to be successfully processed
   665  // by storage.
   666  func SetDefaultStartEndParamsForInstant(r *http.Request) {
   667  	r.Form.Set(startParam, nowTimeValue)
   668  	r.Form.Set(endParam, nowTimeValue)
   669  }