github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/prometheus/native/parse_query.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 "fmt" 25 "net/http" 26 "strings" 27 "time" 28 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/executor" 32 "github.com/m3db/m3/src/query/models" 33 "github.com/m3db/m3/src/query/parser" 34 "github.com/m3db/m3/src/query/parser/promql" 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 const ( 42 // PromParseURL is the url for native prom parse handler, this parses out the 43 // query and returns a JSON representation of the execution DAG. 44 PromParseURL = route.Prefix + "/parse" 45 46 // PromParseHTTPMethod is the HTTP method used with this resource. 47 PromParseHTTPMethod = http.MethodGet 48 ) 49 50 // PromParseHandler represents a handler for prometheus parse endpoint. 51 type promParseHandler struct { 52 engine executor.Engine 53 instrumentOpts instrument.Options 54 } 55 56 // NewPromParseHandler returns a new instance of handler. 57 func NewPromParseHandler(opts options.HandlerOptions) http.Handler { 58 return &promParseHandler{ 59 engine: opts.Engine(), 60 instrumentOpts: opts.InstrumentOpts(), 61 } 62 } 63 64 // FunctionNode is a JSON representation of a function. 65 type FunctionNode struct { 66 // Name is the name of the function node. 67 Name string `json:"name,omitempty"` 68 // Children are any children this function node has. 69 Children []FunctionNode `json:"children,omitempty"` 70 // parents are any parents this node has; this is private since it is only 71 // used to construct the function node map. 72 parents []FunctionNode 73 // node is the actual execution node. 74 node parser.Node 75 } 76 77 // String prints the string representation of a function node. 78 func (n FunctionNode) String() string { 79 var sb strings.Builder 80 sb.WriteString(n.Name) 81 if len(n.Children) > 0 { 82 sb.WriteString(": ") 83 sb.WriteString(fmt.Sprint(n.Children)) 84 } 85 86 return sb.String() 87 } 88 89 type nodeMap map[string]FunctionNode 90 91 func (n nodeMap) rootNode() (FunctionNode, error) { 92 var ( 93 found bool 94 node FunctionNode 95 ) 96 97 for _, v := range n { 98 if len(v.parents) == 0 { 99 if found { 100 return node, fmt.Errorf( 101 "multiple roots found for map: %s", 102 fmt.Sprint(n), 103 ) 104 } 105 106 found = true 107 node = v 108 } 109 } 110 111 if !found { 112 return node, fmt.Errorf("no root found for map: %s", fmt.Sprint(n)) 113 } 114 115 return node, nil 116 } 117 118 func constructNodeMap(nodes parser.Nodes, edges parser.Edges) (nodeMap, error) { 119 nodeMap := make(map[string]FunctionNode, len(nodes)) 120 for _, node := range nodes { 121 name := node.Op.OpType() 122 nodeMap[string(node.ID)] = FunctionNode{ 123 Name: name, 124 node: node, 125 } 126 } 127 128 for _, edge := range edges { 129 // NB: due to how execution occurs, `parent` and `child` have the opposite 130 // meaning in the `edge` context compared to the expected lexical reading, 131 // so they should be swapped here. 132 parent := string(edge.ChildID) 133 child := string(edge.ParentID) 134 parentNode, ok := nodeMap[parent] 135 if !ok { 136 return nil, fmt.Errorf("parent node with ID %s not found", parent) 137 } 138 139 childNode, ok := nodeMap[child] 140 if !ok { 141 return nil, fmt.Errorf("child node with ID %s not found", child) 142 } 143 144 parentNode.Children = append(parentNode.Children, childNode) 145 childNode.parents = append(childNode.parents, parentNode) 146 nodeMap[parent] = parentNode 147 nodeMap[child] = childNode 148 } 149 150 return nodeMap, nil 151 } 152 153 func parseRootNode( 154 r *http.Request, 155 engine executor.Engine, 156 logger *zap.Logger, 157 ) (FunctionNode, error) { 158 query, err := ParseQuery(r) 159 if err != nil { 160 logger.Error("cannot parse query string", zap.Error(err)) 161 return FunctionNode{}, err 162 } 163 164 parseOpts := engine.Options().ParseOptions() 165 parser, err := promql.Parse(query, time.Second, 166 models.NewTagOptions(), parseOpts) 167 if err != nil { 168 logger.Error("cannot parse query PromQL", zap.Error(err)) 169 return FunctionNode{}, err 170 } 171 172 nodes, edges, err := parser.DAG() 173 if err != nil { 174 logger.Error("cannot extract query DAG", zap.Error(err)) 175 return FunctionNode{}, err 176 } 177 178 funcMap, err := constructNodeMap(nodes, edges) 179 if err != nil { 180 logger.Error("cannot construct node map", zap.Error(err)) 181 return FunctionNode{}, err 182 } 183 184 root, err := funcMap.rootNode() 185 if err != nil { 186 logger.Error("cannot fetch root node", zap.Error(err)) 187 return FunctionNode{}, err 188 } 189 190 return root, nil 191 } 192 193 func (h *promParseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 194 logger := h.instrumentOpts.Logger() 195 root, err := parseRootNode(r, h.engine, logger) 196 if err != nil { 197 xhttp.WriteError(w, xhttp.NewError(err, http.StatusBadRequest)) 198 return 199 } 200 201 xhttp.WriteJSONResponse(w, root, logger) 202 }