github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/prometheus/native/read.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  	"net/http"
    25  
    26  	"github.com/m3db/m3/src/query/api/v1/handler/prometheus/handleroptions"
    27  	"github.com/m3db/m3/src/query/api/v1/options"
    28  	"github.com/m3db/m3/src/query/api/v1/route"
    29  	"github.com/m3db/m3/src/query/errors"
    30  	"github.com/m3db/m3/src/query/util/json"
    31  	"github.com/m3db/m3/src/query/util/logging"
    32  	xhttp "github.com/m3db/m3/src/x/net/http"
    33  	xopentracing "github.com/m3db/m3/src/x/opentracing"
    34  
    35  	opentracingext "github.com/opentracing/opentracing-go/ext"
    36  	opentracinglog "github.com/opentracing/opentracing-go/log"
    37  	"go.uber.org/zap"
    38  )
    39  
    40  const (
    41  	// PromReadURL is the URL for native prom read handler, this matches the
    42  	// default URL for the query range endpoint found on a Prometheus server.
    43  	PromReadURL = route.QueryRangeURL
    44  
    45  	// PromReadInstantURL is the URL for native instantaneous prom read
    46  	// handler, this matches the  default URL for the query endpoint
    47  	// found on a Prometheus server.
    48  	PromReadInstantURL = route.QueryURL
    49  
    50  	// PrometheusReadURL is the URL for native prom read handler.
    51  	PrometheusReadURL = "/prometheus" + PromReadURL
    52  
    53  	// PrometheusReadInstantURL is the URL for native instantaneous prom read handler.
    54  	PrometheusReadInstantURL = "/prometheus" + PromReadInstantURL
    55  
    56  	// M3QueryReadURL is the URL for native m3 query read handler.
    57  	M3QueryReadURL = "/m3query" + PromReadURL
    58  
    59  	// M3QueryReadInstantURL is the URL for native instantaneous m3 query read handler.
    60  	M3QueryReadInstantURL = "/m3query" + PromReadInstantURL
    61  )
    62  
    63  var (
    64  	// PromReadHTTPMethods are the HTTP methods for the read handler.
    65  	PromReadHTTPMethods = []string{
    66  		http.MethodGet,
    67  		http.MethodPost,
    68  	}
    69  
    70  	// PromReadInstantHTTPMethods are the HTTP methods for the instant handler.
    71  	PromReadInstantHTTPMethods = []string{
    72  		http.MethodGet,
    73  		http.MethodPost,
    74  	}
    75  )
    76  
    77  // promReadHandler represents a handler for prometheus read endpoint.
    78  type promReadHandler struct {
    79  	instant         bool
    80  	promReadMetrics promReadMetrics
    81  	opts            options.HandlerOptions
    82  }
    83  
    84  // NewPromReadHandler returns a new prometheus-compatible read handler.
    85  func NewPromReadHandler(opts options.HandlerOptions) http.Handler {
    86  	return newHandler(opts, false)
    87  }
    88  
    89  // NewPromReadInstantHandler returns a new pro instance of handler.
    90  func NewPromReadInstantHandler(opts options.HandlerOptions) http.Handler {
    91  	return newHandler(opts, true)
    92  }
    93  
    94  // newHandler returns a new pro instance of handler.
    95  func newHandler(opts options.HandlerOptions, instant bool) http.Handler {
    96  	name := "native-read"
    97  	if instant {
    98  		name = "native-instant-read"
    99  	}
   100  
   101  	taggedScope := opts.InstrumentOpts().MetricsScope().
   102  		Tagged(map[string]string{"handler": name})
   103  	h := &promReadHandler{
   104  		promReadMetrics: newPromReadMetrics(taggedScope),
   105  		opts:            opts,
   106  		instant:         instant,
   107  	}
   108  	return h
   109  }
   110  
   111  func (h *promReadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   112  	timer := h.promReadMetrics.fetchTimerSuccess.Start()
   113  	defer timer.Stop()
   114  
   115  	iOpts := h.opts.InstrumentOpts()
   116  	logger := logging.WithContext(r.Context(), iOpts)
   117  
   118  	ctx, parsedOptions, rErr := ParseRequest(r.Context(), r, h.instant, h.opts)
   119  	if rErr != nil {
   120  		h.promReadMetrics.incError(rErr)
   121  		logger.Error("could not parse request", zap.Error(rErr))
   122  		xhttp.WriteError(w, rErr)
   123  		return
   124  	}
   125  	ctx = logging.NewContext(ctx,
   126  		iOpts,
   127  		zap.String("query", parsedOptions.Params.Query),
   128  		zap.Time("start", parsedOptions.Params.Start.ToTime()),
   129  		zap.Time("end", parsedOptions.Params.End.ToTime()),
   130  		zap.Duration("step", parsedOptions.Params.Step),
   131  		zap.Duration("timeout", parsedOptions.Params.Timeout),
   132  		zap.Duration("fetchTimeout", parsedOptions.FetchOpts.Timeout),
   133  	)
   134  
   135  	result, err := read(ctx, parsedOptions, h.opts)
   136  	if err != nil {
   137  		sp := xopentracing.SpanFromContextOrNoop(ctx)
   138  		sp.LogFields(opentracinglog.Error(err))
   139  		opentracingext.Error.Set(sp, true)
   140  		logger.Error("m3 query error",
   141  			zap.Error(err),
   142  			zap.Any("parsedOptions", parsedOptions))
   143  		h.promReadMetrics.incError(err)
   144  
   145  		if errors.IsTimeout(err) {
   146  			err = errors.NewErrQueryTimeout(err)
   147  		}
   148  		xhttp.WriteError(w, err)
   149  		return
   150  	}
   151  
   152  	w.Header().Set(xhttp.HeaderContentType, xhttp.ContentTypeJSON)
   153  
   154  	h.promReadMetrics.fetchSuccess.Inc(1)
   155  
   156  	err = handleroptions.AddDBResultResponseHeaders(w, result.Meta, parsedOptions.FetchOpts)
   157  	if err != nil {
   158  		logger.Error("error writing database limit headers", zap.Error(err))
   159  		xhttp.WriteError(w, err)
   160  		return
   161  	}
   162  
   163  	keepNaNs := h.opts.Config().ResultOptions.KeepNaNs
   164  	if !keepNaNs {
   165  		keepNaNs = result.Meta.KeepNaNs
   166  	}
   167  
   168  	renderOpts := RenderResultsOptions{
   169  		Start:                   parsedOptions.Params.Start,
   170  		End:                     parsedOptions.Params.End,
   171  		KeepNaNs:                keepNaNs,
   172  		ReturnedSeriesLimit:     parsedOptions.FetchOpts.ReturnedSeriesLimit,
   173  		ReturnedDatapointsLimit: parsedOptions.FetchOpts.ReturnedDatapointsLimit,
   174  	}
   175  
   176  	// First invoke the results rendering with a noop writer in order to
   177  	// check the returned-data limits. This must be done before the actual rendering
   178  	// so that we can add the returned-data-limited header which must precede body writing.
   179  	var (
   180  		renderResult RenderResultsResult
   181  		noopWriter   = json.NewNoopWriter()
   182  	)
   183  	if h.instant {
   184  		renderResult = renderResultsInstantaneousJSON(noopWriter, result, renderOpts)
   185  	} else {
   186  		renderResult = RenderResultsJSON(noopWriter, result, renderOpts)
   187  	}
   188  
   189  	h.promReadMetrics.returnedDataMetrics.FetchDatapoints.RecordValue(float64(renderResult.Datapoints))
   190  	h.promReadMetrics.returnedDataMetrics.FetchSeries.RecordValue(float64(renderResult.Series))
   191  
   192  	limited := &handleroptions.ReturnedDataLimited{
   193  		Limited:     renderResult.LimitedMaxReturnedData,
   194  		Series:      renderResult.Series,
   195  		TotalSeries: renderResult.TotalSeries,
   196  		Datapoints:  renderResult.Datapoints,
   197  	}
   198  	err = handleroptions.AddReturnedLimitResponseHeaders(w, limited, nil)
   199  	if err != nil {
   200  		logger.Error("error writing returned data limited header", zap.Error(err))
   201  		xhttp.WriteError(w, err)
   202  		return
   203  	}
   204  
   205  	// Write the actual results after having checked for limits and wrote headers if needed.
   206  	responseWriter := json.NewWriter(w)
   207  	if h.instant {
   208  		_ = renderResultsInstantaneousJSON(responseWriter, result, renderOpts)
   209  	} else {
   210  		_ = RenderResultsJSON(responseWriter, result, renderOpts)
   211  	}
   212  
   213  	if err := responseWriter.Close(); err != nil {
   214  		w.WriteHeader(http.StatusInternalServerError)
   215  		logger.Error("failed to render results", zap.Error(err))
   216  	} else {
   217  		w.WriteHeader(http.StatusOK)
   218  	}
   219  }