github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/graphite/render.go (about)

     1  // Copyright (c) 2019 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 graphite
    22  
    23  import (
    24  	"fmt"
    25  	"net/http"
    26  	"sort"
    27  	"sync"
    28  
    29  	"go.uber.org/zap"
    30  	"go.uber.org/zap/zapcore"
    31  
    32  	"github.com/m3db/m3/src/query/api/v1/handler/prometheus/handleroptions"
    33  	"github.com/m3db/m3/src/query/api/v1/options"
    34  	"github.com/m3db/m3/src/query/api/v1/route"
    35  	"github.com/m3db/m3/src/query/block"
    36  	queryerrors "github.com/m3db/m3/src/query/errors"
    37  	"github.com/m3db/m3/src/query/graphite/common"
    38  	"github.com/m3db/m3/src/query/graphite/native"
    39  	graphite "github.com/m3db/m3/src/query/graphite/storage"
    40  	"github.com/m3db/m3/src/query/graphite/ts"
    41  	"github.com/m3db/m3/src/query/models"
    42  	"github.com/m3db/m3/src/query/util/logging"
    43  	"github.com/m3db/m3/src/x/errors"
    44  	"github.com/m3db/m3/src/x/instrument"
    45  	xhttp "github.com/m3db/m3/src/x/net/http"
    46  )
    47  
    48  const (
    49  	// ReadURL is the url for the graphite query handler.
    50  	ReadURL = route.Prefix + "/graphite/render"
    51  )
    52  
    53  // ReadHTTPMethods are the HTTP methods used with this resource.
    54  var ReadHTTPMethods = []string{http.MethodGet, http.MethodPost}
    55  
    56  // A renderHandler implements the graphite /render endpoint, including full
    57  // support for executing functions. It only works against data in M3.
    58  type renderHandler struct {
    59  	opts                options.HandlerOptions
    60  	engine              *native.Engine
    61  	fetchOptionsBuilder handleroptions.FetchOptionsBuilder
    62  	queryContextOpts    models.QueryContextOptions
    63  	graphiteOpts        graphite.M3WrappedStorageOptions
    64  	instrumentOpts      instrument.Options
    65  }
    66  
    67  type respError struct {
    68  	err  error
    69  	code int
    70  }
    71  
    72  // NewRenderHandler returns a new render handler around the given storage.
    73  func NewRenderHandler(opts options.HandlerOptions) http.Handler {
    74  	wrappedStore := graphite.NewM3WrappedStorage(opts.Storage(),
    75  		opts.M3DBOptions(), opts.InstrumentOpts(), opts.GraphiteStorageOptions())
    76  	return &renderHandler{
    77  		opts: opts,
    78  		engine: native.NewEngine(wrappedStore, native.CompileOptions{
    79  			EscapeAllNotOnlyQuotes: opts.GraphiteStorageOptions().CompileEscapeAllNotOnlyQuotes,
    80  		}),
    81  		fetchOptionsBuilder: opts.GraphiteRenderFetchOptionsBuilder(),
    82  		queryContextOpts:    opts.QueryContextOptions(),
    83  		graphiteOpts:        opts.GraphiteStorageOptions(),
    84  		instrumentOpts:      opts.InstrumentOpts(),
    85  	}
    86  }
    87  
    88  func sendError(errorCh chan error, err error) {
    89  	select {
    90  	case errorCh <- err:
    91  	default:
    92  	}
    93  }
    94  
    95  // ServeHTTP processes the render requests.
    96  func (h *renderHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    97  	if err := h.serveHTTP(w, r); err != nil {
    98  		if queryerrors.IsTimeout(err) {
    99  			err = queryerrors.NewErrQueryTimeout(err)
   100  		}
   101  		xhttp.WriteError(w, err)
   102  	}
   103  }
   104  
   105  func (h *renderHandler) serveHTTP(
   106  	w http.ResponseWriter,
   107  	r *http.Request,
   108  ) error {
   109  	reqCtx, p, fetchOpts, err := ParseRenderRequest(r.Context(), r, h.fetchOptionsBuilder)
   110  	if err != nil {
   111  		return xhttp.NewError(err, http.StatusBadRequest)
   112  	}
   113  
   114  	var (
   115  		results = make([]ts.SeriesList, len(p.Targets))
   116  		errorCh = make(chan error, 1)
   117  		mu      sync.Mutex
   118  	)
   119  
   120  	ctx := common.NewContext(common.ContextOptions{
   121  		Engine:        h.engine,
   122  		Start:         p.From,
   123  		End:           p.Until,
   124  		Timeout:       p.Timeout,
   125  		MaxDataPoints: p.MaxDataPoints,
   126  		FetchOpts:     fetchOpts,
   127  	})
   128  
   129  	// Set the request context.
   130  	ctx.SetRequestContext(reqCtx)
   131  	defer ctx.Close()
   132  
   133  	var wg sync.WaitGroup
   134  	meta := block.NewResultMetadata()
   135  	wg.Add(len(p.Targets))
   136  	for i, target := range p.Targets {
   137  		i, target := i, target
   138  		go func() {
   139  			childCtx := ctx.NewChildContext(common.NewChildContextOptions())
   140  			defer func() {
   141  				if err := recover(); err != nil {
   142  					// Allow recover from panic.
   143  					sendError(errorCh, fmt.Errorf("error target '%s' caused panic: %v", target, err))
   144  
   145  					// Log panic.
   146  					logger := logging.WithContext(r.Context(), h.instrumentOpts).
   147  						WithOptions(zap.AddStacktrace(zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
   148  							return lvl >= zapcore.ErrorLevel
   149  						})))
   150  					logger.Error("panic captured", zap.Any("stack", err))
   151  				}
   152  				_ = childCtx.Close()
   153  				wg.Done()
   154  			}()
   155  
   156  			exp, err := h.engine.Compile(target)
   157  			if err != nil {
   158  				sendError(errorCh, errors.NewRenamedError(err,
   159  					fmt.Errorf("invalid 'target': %s => %s", target, err)))
   160  				return
   161  			}
   162  
   163  			targetSeries, err := exp.Execute(childCtx)
   164  			if err != nil {
   165  				sendError(errorCh, errors.NewRenamedError(err,
   166  					fmt.Errorf("error target '%s' returned: %w", target, err)))
   167  				return
   168  			}
   169  
   170  			// Apply LTTB downsampling to any series that hasn't been resized
   171  			// to fit max datapoints explicitly using "consolidateBy" function.
   172  			for i, s := range targetSeries.Values {
   173  				resizeMillisPerStep, needResize := s.ResizeToMaxDataPointsMillisPerStep(p.MaxDataPoints)
   174  				if !needResize {
   175  					continue
   176  				}
   177  
   178  				targetSeries.Values[i] = ts.LTTB(s, s.StartTime(), s.EndTime(), resizeMillisPerStep)
   179  			}
   180  
   181  			mu.Lock()
   182  			meta = meta.CombineMetadata(targetSeries.Metadata)
   183  			results[i] = targetSeries
   184  			mu.Unlock()
   185  		}()
   186  	}
   187  
   188  	wg.Wait()
   189  	close(errorCh)
   190  	err = <-errorCh
   191  	if err != nil {
   192  		return err
   193  	}
   194  
   195  	// Count and sort the groups if not sorted already.
   196  	// NB(r): For certain things like stacking different targets in Grafana
   197  	// returning targets in order matters to give a deterministic order for
   198  	// the series to display when stacking. However we should only mutate
   199  	// the order if no expressions have explicitly applied their own sort.
   200  	numSeries := 0
   201  	for _, r := range results {
   202  		numSeries += r.Len()
   203  		if !r.SortApplied {
   204  			// Use sort.Stable for deterministic output.
   205  			sort.Stable(ts.SeriesByName(r.Values))
   206  		}
   207  	}
   208  
   209  	series := make([]*ts.Series, 0, numSeries)
   210  	for _, r := range results {
   211  		series = append(series, r.Values...)
   212  	}
   213  
   214  	// We've always sorted the response by this point
   215  	response := ts.SeriesList{
   216  		Values:      series,
   217  		SortApplied: true,
   218  	}
   219  
   220  	if err := handleroptions.AddDBResultResponseHeaders(w, meta, fetchOpts); err != nil {
   221  		return err
   222  	}
   223  
   224  	return WriteRenderResponse(w, response, p.Format, renderResultsJSONOptions{
   225  		renderSeriesAllNaNs: h.graphiteOpts.RenderSeriesAllNaNs,
   226  	})
   227  }