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 }