github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/prometheus/native/parse_threshold.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 native
    22  
    23  import (
    24  	"net/http"
    25  
    26  	"github.com/m3db/m3/src/query/api/v1/options"
    27  	"github.com/m3db/m3/src/query/api/v1/route"
    28  	"github.com/m3db/m3/src/query/executor"
    29  	"github.com/m3db/m3/src/query/functions/scalar"
    30  	"github.com/m3db/m3/src/x/instrument"
    31  	xhttp "github.com/m3db/m3/src/x/net/http"
    32  
    33  	"go.uber.org/zap"
    34  )
    35  
    36  const (
    37  	// PromThresholdURL is the url for native prom threshold handler, this  parses
    38  	// out the  query and returns a JSON representation of the execution DAG.
    39  	PromThresholdURL = route.Prefix + "/threshold"
    40  
    41  	// PromThresholdHTTPMethod is the HTTP method used with this resource.
    42  	PromThresholdHTTPMethod = http.MethodGet
    43  )
    44  
    45  // PromThresholdHandler represents a handler for prometheus threshold endpoint.
    46  type promThresholdHandler struct {
    47  	engine         executor.Engine
    48  	instrumentOpts instrument.Options
    49  }
    50  
    51  // NewPromThresholdHandler returns a new instance of handler.
    52  func NewPromThresholdHandler(opts options.HandlerOptions) http.Handler {
    53  	return &promThresholdHandler{
    54  		engine:         opts.Engine(),
    55  		instrumentOpts: opts.InstrumentOpts(),
    56  	}
    57  }
    58  
    59  // QueryRepresentation is a JSON representation of a query after attempting to
    60  // extract any threshold-specific parameters.
    61  //
    62  // NB: this is always presented with the threshold value (if present) as
    63  // appearing on the right of the query.
    64  // e.g. `1 > up` will instead be inverted to `up < 1`
    65  type QueryRepresentation struct {
    66  	// Query is the non-threshold part of a query.
    67  	Query FunctionNode `json:"query,omitempty"`
    68  	// Threshold is any detected threshold (top level comparison functions).
    69  	// NB: this is a pointer so it does not have to be present in output if unset.
    70  	Thresold *Threshold `json:"threshold,omitempty"`
    71  }
    72  
    73  // Threshold is a JSON representation of a threshold, represented by a top level
    74  // comparison function.
    75  type Threshold struct {
    76  	// Comparator is the threshold comparator.
    77  	Comparator string `json:"comparator"`
    78  	// Value is the threshold value.
    79  	Value float64 `json:"value"`
    80  }
    81  
    82  var thresholdNames = []string{">", ">=", "<", "<=", "==", "!="}
    83  
    84  // NB: Thresholds are standardized to have the threshold value on the RHS;
    85  // if the scalar is on the left, the operation must be inverted. Strict
    86  // equalities remain untouched.
    87  var invertedNames = []string{"<", "<=", ">", ">=", "==", "!="}
    88  
    89  type thresholdInfo struct {
    90  	thresholdIdx int
    91  	queryIdx     int
    92  	comparator   string
    93  }
    94  
    95  // isThreshold determines if this function node is a candidate for being a
    96  // threshold function, and returns the index of the child representing the
    97  // threshold, and the index of the child for the rest of the query.
    98  func (n FunctionNode) isThreshold() (bool, thresholdInfo) {
    99  	info := thresholdInfo{-1, -1, ""}
   100  	// NB: only root nodes with two children may be thresholds.
   101  	if len(n.parents) != 0 || len(n.Children) != 2 {
   102  		return false, info
   103  	}
   104  
   105  	for i, name := range thresholdNames {
   106  		if n.Name != name {
   107  			continue
   108  		}
   109  
   110  		scalarLeft := n.Children[0].node.Op.OpType() == scalar.ScalarType
   111  		scalarRight := n.Children[1].node.Op.OpType() == scalar.ScalarType
   112  
   113  		// NB: if both sides are scalars, it's a calculator, not a query.
   114  		if scalarLeft && scalarRight {
   115  			return false, info
   116  		}
   117  
   118  		if scalarLeft {
   119  			// NB: Thresholds are standardized to have the threshold value on the RHS;
   120  			// if the scalar is on the left, the operation must be inverted.
   121  			inverted := invertedNames[i]
   122  			return true, thresholdInfo{
   123  				thresholdIdx: 0,
   124  				queryIdx:     1,
   125  				comparator:   inverted,
   126  			}
   127  		}
   128  
   129  		if scalarRight {
   130  			return true, thresholdInfo{
   131  				thresholdIdx: 1,
   132  				queryIdx:     0,
   133  				comparator:   name,
   134  			}
   135  		}
   136  
   137  		break
   138  	}
   139  
   140  	return false, info
   141  }
   142  
   143  // QueryRepresentation gives the query representation of the function node.
   144  func (n FunctionNode) QueryRepresentation() (QueryRepresentation, error) {
   145  	isThreshold, thresholdInfo := n.isThreshold()
   146  	if !isThreshold {
   147  		return QueryRepresentation{
   148  			Query: n,
   149  		}, nil
   150  	}
   151  
   152  	queryPart := n.Children[thresholdInfo.queryIdx]
   153  	thresholdPart := n.Children[thresholdInfo.thresholdIdx]
   154  	thresholdOp := thresholdPart.node.Op
   155  	scalarOp, ok := thresholdOp.(*scalar.ScalarOp)
   156  	if !ok {
   157  		return QueryRepresentation{}, instrument.InvariantErrorf(
   158  			"operation %s must be a scalar", thresholdOp.String())
   159  	}
   160  
   161  	return QueryRepresentation{
   162  		Query: queryPart,
   163  		Thresold: &Threshold{
   164  			Comparator: thresholdInfo.comparator,
   165  			Value:      scalarOp.Value(),
   166  		},
   167  	}, nil
   168  }
   169  
   170  func (h *promThresholdHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   171  	logger := h.instrumentOpts.Logger()
   172  	root, err := parseRootNode(r, h.engine, logger)
   173  	if err != nil {
   174  		xhttp.WriteError(w, xhttp.NewError(err, http.StatusBadRequest))
   175  		return
   176  	}
   177  
   178  	queryRepresentation, err := root.QueryRepresentation()
   179  	if err != nil {
   180  		xhttp.WriteError(w, xhttp.NewError(err, http.StatusBadRequest))
   181  		logger.Error("cannot convert to query representation", zap.Error(err))
   182  		return
   183  	}
   184  
   185  	xhttp.WriteJSONResponse(w, queryRepresentation, logger)
   186  }