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  }