github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/graphite/find.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  	"errors"
    25  	"net/http"
    26  	"sync"
    27  
    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/api/v1/route"
    31  	"github.com/m3db/m3/src/query/graphite/graphite"
    32  	graphitestorage "github.com/m3db/m3/src/query/graphite/storage"
    33  	"github.com/m3db/m3/src/query/storage/m3/consolidators"
    34  	"github.com/m3db/m3/src/query/util/logging"
    35  	xerrors "github.com/m3db/m3/src/x/errors"
    36  	"github.com/m3db/m3/src/x/instrument"
    37  	xhttp "github.com/m3db/m3/src/x/net/http"
    38  
    39  	"go.uber.org/zap"
    40  )
    41  
    42  const (
    43  	// FindURL is the url for finding graphite metrics.
    44  	FindURL = route.Prefix + "/graphite/metrics/find"
    45  )
    46  
    47  // FindHTTPMethods are the HTTP methods for this handler.
    48  var FindHTTPMethods = []string{http.MethodGet, http.MethodPost}
    49  
    50  type grahiteFindHandler struct {
    51  	storage             graphitestorage.Storage
    52  	graphiteStorageOpts graphitestorage.M3WrappedStorageOptions
    53  	fetchOptionsBuilder handleroptions.FetchOptionsBuilder
    54  	instrumentOpts      instrument.Options
    55  }
    56  
    57  // NewFindHandler returns a new instance of handler.
    58  func NewFindHandler(opts options.HandlerOptions) http.Handler {
    59  	wrappedStore := graphitestorage.NewM3WrappedStorage(opts.Storage(),
    60  		opts.M3DBOptions(), opts.InstrumentOpts(), opts.GraphiteStorageOptions())
    61  	return &grahiteFindHandler{
    62  		storage:             wrappedStore,
    63  		graphiteStorageOpts: opts.GraphiteStorageOptions(),
    64  		fetchOptionsBuilder: opts.GraphiteFindFetchOptionsBuilder(),
    65  		instrumentOpts:      opts.InstrumentOpts(),
    66  	}
    67  }
    68  
    69  type nodeDescriptor struct {
    70  	isLeaf      bool
    71  	hasChildren bool
    72  }
    73  
    74  func mergeTags(
    75  	terminatedResult *consolidators.CompleteTagsResult,
    76  	childResult *consolidators.CompleteTagsResult,
    77  ) (map[string]nodeDescriptor, error) {
    78  	var (
    79  		terminatedResultTags = 0
    80  		childResultTags      = 0
    81  	)
    82  
    83  	// Sanity check the queries aren't complete name only queries.
    84  	if terminatedResult != nil {
    85  		terminatedResultTags = len(terminatedResult.CompletedTags)
    86  		if terminatedResult.CompleteNameOnly {
    87  			return nil, errors.New("terminated result is completing name only")
    88  		}
    89  	}
    90  
    91  	if childResult != nil {
    92  		childResultTags = len(childResult.CompletedTags)
    93  		if childResult.CompleteNameOnly {
    94  			return nil, errors.New("child result is completing name only")
    95  		}
    96  	}
    97  
    98  	size := terminatedResultTags + childResultTags
    99  	tagMap := make(map[string]nodeDescriptor, size)
   100  	if terminatedResult != nil {
   101  		for _, tag := range terminatedResult.CompletedTags {
   102  			for _, value := range tag.Values {
   103  				descriptor := tagMap[string(value)]
   104  				descriptor.isLeaf = true
   105  				tagMap[string(value)] = descriptor
   106  			}
   107  		}
   108  	}
   109  
   110  	if childResult != nil {
   111  		for _, tag := range childResult.CompletedTags {
   112  			for _, value := range tag.Values {
   113  				descriptor := tagMap[string(value)]
   114  				descriptor.hasChildren = true
   115  				tagMap[string(value)] = descriptor
   116  			}
   117  		}
   118  	}
   119  
   120  	return tagMap, nil
   121  }
   122  
   123  func (h *grahiteFindHandler) ServeHTTP(
   124  	w http.ResponseWriter,
   125  	r *http.Request,
   126  ) {
   127  	ctx, opts, err := h.fetchOptionsBuilder.NewFetchOptions(r.Context(), r)
   128  	if err != nil {
   129  		xhttp.WriteError(w, err)
   130  		return
   131  	}
   132  
   133  	logger := logging.WithContext(ctx, h.instrumentOpts)
   134  	w.Header().Set(xhttp.HeaderContentType, xhttp.ContentTypeJSON)
   135  
   136  	// NB: need to run two separate queries, one of which will match only the
   137  	// provided matchers, and one which will match the provided matchers with at
   138  	// least one more child node. For further information, refer to the comment
   139  	// for parseFindParamsToQueries.
   140  	terminatedQuery, childQuery, raw, err := parseFindParamsToQueries(r)
   141  	if err != nil {
   142  		xhttp.WriteError(w, err)
   143  		return
   144  	}
   145  
   146  	var (
   147  		terminatedResult *consolidators.CompleteTagsResult
   148  		tErr             error
   149  		childResult      *consolidators.CompleteTagsResult
   150  		cErr             error
   151  		wg               sync.WaitGroup
   152  	)
   153  	if terminatedQuery != nil {
   154  		// Sometimes we only perform the child query, so only perform
   155  		// terminated query if not nil.
   156  		wg.Add(1)
   157  		go func() {
   158  			terminatedResult, tErr = h.storage.CompleteTags(ctx, terminatedQuery, opts)
   159  			wg.Done()
   160  		}()
   161  	}
   162  	// Always perform child query.
   163  	wg.Add(1)
   164  	go func() {
   165  		childResult, cErr = h.storage.CompleteTags(ctx, childQuery, opts)
   166  		wg.Done()
   167  	}()
   168  
   169  	wg.Wait()
   170  
   171  	if err := xerrors.FirstError(tErr, cErr); err != nil {
   172  		logger.Error("unable to find search", zap.Error(err))
   173  		xhttp.WriteError(w, err)
   174  		return
   175  	}
   176  
   177  	meta := childResult.Metadata
   178  	if terminatedResult != nil {
   179  		meta = terminatedResult.Metadata.CombineMetadata(childResult.Metadata)
   180  	}
   181  
   182  	// NB: merge results from both queries to specify which series have children
   183  	seenMap, err := mergeTags(terminatedResult, childResult)
   184  	if err != nil {
   185  		logger.Error("unable to find merge", zap.Error(err))
   186  		xhttp.WriteError(w, err)
   187  		return
   188  	}
   189  
   190  	prefix := graphite.DropLastMetricPart(raw)
   191  	if len(prefix) > 0 {
   192  		prefix += "."
   193  	}
   194  
   195  	err = handleroptions.AddDBResultResponseHeaders(w, meta, opts)
   196  	if err != nil {
   197  		logger.Error("unable to render find header", zap.Error(err))
   198  		xhttp.WriteError(w, err)
   199  		return
   200  	}
   201  
   202  	// TODO: Support multiple result types
   203  	resultOpts := findResultsOptions{
   204  		includeBothExpandableAndLeaf: h.graphiteStorageOpts.FindResultsIncludeBothExpandableAndLeaf,
   205  	}
   206  	if err := findResultsJSON(w, prefix, seenMap, resultOpts); err != nil {
   207  		logger.Error("unable to render find results", zap.Error(err))
   208  	}
   209  }