mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			229 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			229 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
| package expr
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"time"
 | |
| 
 | |
| 	"go.opentelemetry.io/otel/attribute"
 | |
| 	"gonum.org/v1/gonum/graph/simple"
 | |
| 	"gonum.org/v1/gonum/graph/topo"
 | |
| 
 | |
| 	"github.com/grafana/grafana/pkg/expr/mathexp"
 | |
| )
 | |
| 
 | |
| // NodeType is the type of a DPNode. Currently either a expression command or datasource query.
 | |
| type NodeType int
 | |
| 
 | |
| const (
 | |
| 	// TypeCMDNode is a NodeType for expression commands.
 | |
| 	TypeCMDNode NodeType = iota
 | |
| 	// TypeDatasourceNode is a NodeType for datasource queries.
 | |
| 	TypeDatasourceNode
 | |
| )
 | |
| 
 | |
| func (nt NodeType) String() string {
 | |
| 	switch nt {
 | |
| 	case TypeCMDNode:
 | |
| 		return "Expression"
 | |
| 	case TypeDatasourceNode:
 | |
| 		return "Datasource"
 | |
| 	default:
 | |
| 		return "Unknown"
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Node is a node in a Data Pipeline. Node is either a expression command or a datasource query.
 | |
| type Node interface {
 | |
| 	ID() int64 // ID() allows the gonum graph node interface to be fulfilled
 | |
| 	NodeType() NodeType
 | |
| 	RefID() string
 | |
| 	Execute(ctx context.Context, now time.Time, vars mathexp.Vars, s *Service) (mathexp.Results, error)
 | |
| 	String() string
 | |
| }
 | |
| 
 | |
| // DataPipeline is an ordered set of nodes returned from DPGraph processing.
 | |
| type DataPipeline []Node
 | |
| 
 | |
| // execute runs all the command/datasource requests in the pipeline return a
 | |
| // map of the refId of the of each command
 | |
| func (dp *DataPipeline) execute(c context.Context, now time.Time, s *Service) (mathexp.Vars, error) {
 | |
| 	vars := make(mathexp.Vars)
 | |
| 	for _, node := range *dp {
 | |
| 		c, span := s.tracer.Start(c, "SSE.ExecuteNode")
 | |
| 		span.SetAttributes("node.refId", node.RefID(), attribute.Key("node.refId").String(node.RefID()))
 | |
| 		if node.NodeType() == TypeCMDNode {
 | |
| 			cmdNode := node.(*CMDNode)
 | |
| 			inputRefIDs := cmdNode.Command.NeedsVars()
 | |
| 			span.SetAttributes("node.inputRefIDs", inputRefIDs, attribute.Key("node.inputRefIDs").StringSlice(inputRefIDs))
 | |
| 		}
 | |
| 		defer span.End()
 | |
| 
 | |
| 		res, err := node.Execute(c, now, vars, s)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		vars[node.RefID()] = res
 | |
| 	}
 | |
| 	return vars, nil
 | |
| }
 | |
| 
 | |
| // BuildPipeline builds a graph of the nodes, and returns the nodes in an
 | |
| // executable order.
 | |
| func (s *Service) buildPipeline(req *Request) (DataPipeline, error) {
 | |
| 	if req != nil && len(req.Headers) == 0 {
 | |
| 		req.Headers = map[string]string{}
 | |
| 	}
 | |
| 
 | |
| 	graph, err := s.buildDependencyGraph(req)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	nodes, err := buildExecutionOrder(graph)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return nodes, nil
 | |
| }
 | |
| 
 | |
| // buildDependencyGraph returns a dependency graph for a set of queries.
 | |
| func (s *Service) buildDependencyGraph(req *Request) (*simple.DirectedGraph, error) {
 | |
| 	graph, err := s.buildGraph(req)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	registry := buildNodeRegistry(graph)
 | |
| 
 | |
| 	if err := buildGraphEdges(graph, registry); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return graph, nil
 | |
| }
 | |
| 
 | |
| // buildExecutionOrder returns a sequence of nodes ordered by dependency.
 | |
| func buildExecutionOrder(graph *simple.DirectedGraph) ([]Node, error) {
 | |
| 	sortedNodes, err := topo.Sort(graph)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	nodes := make([]Node, len(sortedNodes))
 | |
| 	for i, v := range sortedNodes {
 | |
| 		nodes[i] = v.(Node)
 | |
| 	}
 | |
| 
 | |
| 	return nodes, nil
 | |
| }
 | |
| 
 | |
| // buildNodeRegistry returns a lookup table for reference IDs to respective node.
 | |
| func buildNodeRegistry(g *simple.DirectedGraph) map[string]Node {
 | |
| 	res := make(map[string]Node)
 | |
| 
 | |
| 	nodeIt := g.Nodes()
 | |
| 
 | |
| 	for nodeIt.Next() {
 | |
| 		if dpNode, ok := nodeIt.Node().(Node); ok {
 | |
| 			res[dpNode.RefID()] = dpNode
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| // buildGraph creates a new graph populated with nodes for every query.
 | |
| func (s *Service) buildGraph(req *Request) (*simple.DirectedGraph, error) {
 | |
| 	dp := simple.NewDirectedGraph()
 | |
| 
 | |
| 	for _, query := range req.Queries {
 | |
| 		if query.DataSource == nil || query.DataSource.UID == "" {
 | |
| 			return nil, fmt.Errorf("missing datasource uid in query with refId %v", query.RefID)
 | |
| 		}
 | |
| 
 | |
| 		rawQueryProp := make(map[string]interface{})
 | |
| 		queryBytes, err := query.JSON.MarshalJSON()
 | |
| 
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		err = json.Unmarshal(queryBytes, &rawQueryProp)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		rn := &rawNode{
 | |
| 			Query:      rawQueryProp,
 | |
| 			RefID:      query.RefID,
 | |
| 			TimeRange:  query.TimeRange,
 | |
| 			QueryType:  query.QueryType,
 | |
| 			DataSource: query.DataSource,
 | |
| 		}
 | |
| 
 | |
| 		var node Node
 | |
| 
 | |
| 		if IsDataSource(rn.DataSource.UID) {
 | |
| 			node, err = buildCMDNode(dp, rn)
 | |
| 		} else {
 | |
| 			node, err = s.buildDSNode(dp, rn, req)
 | |
| 		}
 | |
| 
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		dp.AddNode(node)
 | |
| 	}
 | |
| 	return dp, nil
 | |
| }
 | |
| 
 | |
| // buildGraphEdges generates graph edges based on each node's dependencies.
 | |
| func buildGraphEdges(dp *simple.DirectedGraph, registry map[string]Node) error {
 | |
| 	nodeIt := dp.Nodes()
 | |
| 
 | |
| 	for nodeIt.Next() {
 | |
| 		node := nodeIt.Node().(Node)
 | |
| 
 | |
| 		if node.NodeType() != TypeCMDNode {
 | |
| 			// datasource node, nothing to do for now. Although if we want expression results to be
 | |
| 			// used as datasource query params some day this will need change
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		cmdNode := node.(*CMDNode)
 | |
| 
 | |
| 		for _, neededVar := range cmdNode.Command.NeedsVars() {
 | |
| 			neededNode, ok := registry[neededVar]
 | |
| 			if !ok {
 | |
| 				return fmt.Errorf("unable to find dependent node '%v'", neededVar)
 | |
| 			}
 | |
| 
 | |
| 			if neededNode.ID() == cmdNode.ID() {
 | |
| 				return fmt.Errorf("expression '%v' cannot reference itself. Must be query or another expression", neededVar)
 | |
| 			}
 | |
| 
 | |
| 			if cmdNode.CMDType == TypeClassicConditions {
 | |
| 				if neededNode.NodeType() != TypeDatasourceNode {
 | |
| 					return fmt.Errorf("only data source queries may be inputs to a classic condition, %v is a %v", neededVar, neededNode.NodeType())
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if neededNode.NodeType() == TypeCMDNode {
 | |
| 				if neededNode.(*CMDNode).CMDType == TypeClassicConditions {
 | |
| 					return fmt.Errorf("classic conditions may not be the input for other expressions, but %v is the input for %v", neededVar, cmdNode.RefID())
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			edge := dp.NewEdge(neededNode, cmdNode)
 | |
| 
 | |
| 			dp.SetEdge(edge)
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 |