github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/prometheus/remote/match.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 remote
    22  
    23  import (
    24  	"io/ioutil"
    25  	"net/http"
    26  
    27  	"github.com/m3db/m3/src/query/api/v1/handler/prometheus"
    28  	"github.com/m3db/m3/src/query/api/v1/handler/prometheus/handleroptions"
    29  	"github.com/m3db/m3/src/query/api/v1/options"
    30  	"github.com/m3db/m3/src/query/block"
    31  	"github.com/m3db/m3/src/query/models"
    32  	"github.com/m3db/m3/src/query/parser/promql"
    33  	"github.com/m3db/m3/src/query/storage"
    34  	"github.com/m3db/m3/src/query/util/logging"
    35  	"github.com/m3db/m3/src/x/instrument"
    36  	xhttp "github.com/m3db/m3/src/x/net/http"
    37  
    38  	"go.uber.org/zap"
    39  )
    40  
    41  // PromSeriesMatchHTTPMethods are the HTTP methods for this handler.
    42  var PromSeriesMatchHTTPMethods = []string{http.MethodGet, http.MethodPost}
    43  
    44  // PromSeriesMatchHandler represents a handler for
    45  // the prometheus series matcher endpoint.
    46  type PromSeriesMatchHandler struct {
    47  	storage             storage.Storage
    48  	tagOptions          models.TagOptions
    49  	fetchOptionsBuilder handleroptions.FetchOptionsBuilder
    50  	instrumentOpts      instrument.Options
    51  	parseOpts           promql.ParseOptions
    52  }
    53  
    54  // NewPromSeriesMatchHandler returns a new instance of handler.
    55  // TODO: Remove series match handler, not part of Prometheus HTTP API
    56  // and not used anywhere or documented.
    57  func NewPromSeriesMatchHandler(opts options.HandlerOptions) http.Handler {
    58  	return &PromSeriesMatchHandler{
    59  		tagOptions:          opts.TagOptions(),
    60  		storage:             opts.Storage(),
    61  		fetchOptionsBuilder: opts.FetchOptionsBuilder(),
    62  		instrumentOpts:      opts.InstrumentOpts(),
    63  		parseOpts: opts.Engine().Options().ParseOptions().
    64  			SetRequireStartEndTime(opts.Config().Query.RequireSeriesEndpointStartEndTime),
    65  	}
    66  }
    67  
    68  func (h *PromSeriesMatchHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    69  	w.Header().Set(xhttp.HeaderContentType, xhttp.ContentTypeJSON)
    70  	w.Header().Set("Access-Control-Allow-Origin", "*")
    71  
    72  	ctx, opts, rErr := h.fetchOptionsBuilder.NewFetchOptions(r.Context(), r)
    73  	if rErr != nil {
    74  		xhttp.WriteError(w, rErr)
    75  		return
    76  	}
    77  
    78  	logger := logging.WithContext(ctx, h.instrumentOpts)
    79  
    80  	queries, err := prometheus.ParseSeriesMatchQuery(r, h.parseOpts, h.tagOptions)
    81  	if err != nil {
    82  		logger.Error("unable to parse series match values to query", zap.Error(err))
    83  		xhttp.WriteError(w, err)
    84  		return
    85  	}
    86  
    87  	results := make([]models.Metrics, len(queries))
    88  	meta := block.NewResultMetadata()
    89  	for i, query := range queries {
    90  		result, err := h.storage.SearchSeries(ctx, query, opts)
    91  		if err != nil {
    92  			logger.Error("unable to get matched series", zap.Error(err))
    93  			xhttp.WriteError(w, err)
    94  			return
    95  		}
    96  
    97  		results[i] = result.Metrics
    98  		meta = meta.CombineMetadata(result.Metadata)
    99  	}
   100  
   101  	err = handleroptions.AddDBResultResponseHeaders(w, meta, opts)
   102  	if err != nil {
   103  		logger.Error("error writing database limit headers", zap.Error(err))
   104  		xhttp.WriteError(w, err)
   105  		return
   106  	}
   107  
   108  	// First write out results to zero output to check if will limit
   109  	// results and if so then write the header about truncation if occurred.
   110  	var (
   111  		noopWriter = ioutil.Discard
   112  		renderOpts = prometheus.RenderSeriesMetadataOptions{
   113  			ReturnedSeriesMetadataLimit: opts.ReturnedSeriesMetadataLimit,
   114  		}
   115  	)
   116  	renderResult, err := prometheus.RenderSeriesMatchResultsJSON(noopWriter, results, renderOpts)
   117  	if err != nil {
   118  		logger.Error("unable to render match series results", zap.Error(err))
   119  		xhttp.WriteError(w, err)
   120  		return
   121  	}
   122  
   123  	limited := &handleroptions.ReturnedMetadataLimited{
   124  		Results:      renderResult.Results,
   125  		TotalResults: renderResult.TotalResults,
   126  		Limited:      renderResult.LimitedMaxReturnedData,
   127  	}
   128  	if err := handleroptions.AddReturnedLimitResponseHeaders(w, nil, limited); err != nil {
   129  		logger.Error("unable to returned data headers", zap.Error(err))
   130  		xhttp.WriteError(w, err)
   131  		return
   132  	}
   133  
   134  	// TODO: Support multiple result types
   135  	_, err = prometheus.RenderSeriesMatchResultsJSON(w, results, renderOpts)
   136  	if err != nil {
   137  		logger.Error("unable to render match series", zap.Error(err))
   138  	}
   139  }