mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			275 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			275 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
| package expr
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
 | |
| 	"github.com/grafana/grafana/pkg/expr/mathexp"
 | |
| )
 | |
| 
 | |
| // Command is an interface for all expression commands.
 | |
| type Command interface {
 | |
| 	NeedsVars() []string
 | |
| 	Execute(c context.Context, vars mathexp.Vars) (mathexp.Results, error)
 | |
| }
 | |
| 
 | |
| // MathCommand is a command for a math expression such as "1 + $GA / 2"
 | |
| type MathCommand struct {
 | |
| 	RawExpression string
 | |
| 	Expression    *mathexp.Expr
 | |
| 	refID         string
 | |
| }
 | |
| 
 | |
| // NewMathCommand creates a new MathCommand. It will return an error
 | |
| // if there is an error parsing expr.
 | |
| func NewMathCommand(refID, expr string) (*MathCommand, error) {
 | |
| 	parsedExpr, err := mathexp.New(expr)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return &MathCommand{
 | |
| 		RawExpression: expr,
 | |
| 		Expression:    parsedExpr,
 | |
| 		refID:         refID,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // UnmarshalMathCommand creates a MathCommand from Grafana's frontend query.
 | |
| func UnmarshalMathCommand(rn *rawNode) (*MathCommand, error) {
 | |
| 	rawExpr, ok := rn.Query["expression"]
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("math command for refId %v is missing an expression", rn.RefID)
 | |
| 	}
 | |
| 	exprString, ok := rawExpr.(string)
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("expected math command for refId %v expression to be a string, got %T", rn.RefID, rawExpr)
 | |
| 	}
 | |
| 
 | |
| 	gm, err := NewMathCommand(rn.RefID, exprString)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("invalid math command type in '%v': %v", rn.RefID, err)
 | |
| 	}
 | |
| 	return gm, nil
 | |
| }
 | |
| 
 | |
| // NeedsVars returns the variable names (refIds) that are dependencies
 | |
| // to execute the command and allows the command to fulfill the Command interface.
 | |
| func (gm *MathCommand) NeedsVars() []string {
 | |
| 	return gm.Expression.VarNames
 | |
| }
 | |
| 
 | |
| // Execute runs the command and returns the results or an error if the command
 | |
| // failed to execute.
 | |
| func (gm *MathCommand) Execute(ctx context.Context, vars mathexp.Vars) (mathexp.Results, error) {
 | |
| 	return gm.Expression.Execute(gm.refID, vars)
 | |
| }
 | |
| 
 | |
| // ReduceCommand is an expression command for reduction of a timeseries such as a min, mean, or max.
 | |
| type ReduceCommand struct {
 | |
| 	Reducer     string
 | |
| 	VarToReduce string
 | |
| 	refID       string
 | |
| }
 | |
| 
 | |
| // NewReduceCommand creates a new ReduceCMD.
 | |
| func NewReduceCommand(refID, reducer, varToReduce string) *ReduceCommand {
 | |
| 	// TODO: validate reducer here, before execution
 | |
| 	return &ReduceCommand{
 | |
| 		Reducer:     reducer,
 | |
| 		VarToReduce: varToReduce,
 | |
| 		refID:       refID,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // UnmarshalReduceCommand creates a MathCMD from Grafana's frontend query.
 | |
| func UnmarshalReduceCommand(rn *rawNode) (*ReduceCommand, error) {
 | |
| 	rawVar, ok := rn.Query["expression"]
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("no variable specified to reduce for refId %v", rn.RefID)
 | |
| 	}
 | |
| 	varToReduce, ok := rawVar.(string)
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("expected reduce variable to be a string, got %T for refId %v", rawVar, rn.RefID)
 | |
| 	}
 | |
| 	varToReduce = strings.TrimPrefix(varToReduce, "$")
 | |
| 
 | |
| 	rawReducer, ok := rn.Query["reducer"]
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("no reducer specified for refId %v", rn.RefID)
 | |
| 	}
 | |
| 	redFunc, ok := rawReducer.(string)
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("expected reducer to be a string, got %T for refId %v", rawReducer, rn.RefID)
 | |
| 	}
 | |
| 
 | |
| 	return NewReduceCommand(rn.RefID, redFunc, varToReduce), nil
 | |
| }
 | |
| 
 | |
| // NeedsVars returns the variable names (refIds) that are dependencies
 | |
| // to execute the command and allows the command to fulfill the Command interface.
 | |
| func (gr *ReduceCommand) NeedsVars() []string {
 | |
| 	return []string{gr.VarToReduce}
 | |
| }
 | |
| 
 | |
| // Execute runs the command and returns the results or an error if the command
 | |
| // failed to execute.
 | |
| func (gr *ReduceCommand) Execute(ctx context.Context, vars mathexp.Vars) (mathexp.Results, error) {
 | |
| 	newRes := mathexp.Results{}
 | |
| 	for _, val := range vars[gr.VarToReduce].Values {
 | |
| 		series, ok := val.(mathexp.Series)
 | |
| 		if !ok {
 | |
| 			return newRes, fmt.Errorf("can only reduce type series, got type %v", val.Type())
 | |
| 		}
 | |
| 		num, err := series.Reduce(gr.refID, gr.Reducer)
 | |
| 		if err != nil {
 | |
| 			return newRes, err
 | |
| 		}
 | |
| 		newRes.Values = append(newRes.Values, num)
 | |
| 	}
 | |
| 	return newRes, nil
 | |
| }
 | |
| 
 | |
| // ResampleCommand is an expression command for resampling of a timeseries.
 | |
| type ResampleCommand struct {
 | |
| 	Window        time.Duration
 | |
| 	VarToResample string
 | |
| 	Downsampler   string
 | |
| 	Upsampler     string
 | |
| 	TimeRange     TimeRange
 | |
| 	refID         string
 | |
| }
 | |
| 
 | |
| // NewResampleCommand creates a new ResampleCMD.
 | |
| func NewResampleCommand(refID, rawWindow, varToResample string, downsampler string, upsampler string, tr TimeRange) (*ResampleCommand, error) {
 | |
| 	// TODO: validate reducer here, before execution
 | |
| 	window, err := gtime.ParseDuration(rawWindow)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf(`failed to parse resample "window" duration field %q: %w`, window, err)
 | |
| 	}
 | |
| 	return &ResampleCommand{
 | |
| 		Window:        window,
 | |
| 		VarToResample: varToResample,
 | |
| 		Downsampler:   downsampler,
 | |
| 		Upsampler:     upsampler,
 | |
| 		TimeRange:     tr,
 | |
| 		refID:         refID,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // UnmarshalResampleCommand creates a ResampleCMD from Grafana's frontend query.
 | |
| func UnmarshalResampleCommand(rn *rawNode) (*ResampleCommand, error) {
 | |
| 	rawVar, ok := rn.Query["expression"]
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("no variable to resample for refId %v", rn.RefID)
 | |
| 	}
 | |
| 	varToReduce, ok := rawVar.(string)
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("expected resample input variable to be type string, but got type %T for refId %v", rawVar, rn.RefID)
 | |
| 	}
 | |
| 	varToReduce = strings.TrimPrefix(varToReduce, "$")
 | |
| 	varToResample := varToReduce
 | |
| 
 | |
| 	rawWindow, ok := rn.Query["window"]
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("no time duration specified for the window in resample command for refId %v", rn.RefID)
 | |
| 	}
 | |
| 	window, ok := rawWindow.(string)
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("expected resample window to be a string, got %T for refId %v", rawWindow, rn.RefID)
 | |
| 	}
 | |
| 
 | |
| 	rawDownsampler, ok := rn.Query["downsampler"]
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("no downsampler function specified in resample command for refId %v", rn.RefID)
 | |
| 	}
 | |
| 	downsampler, ok := rawDownsampler.(string)
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("expected resample downsampler to be a string, got type %T for refId %v", downsampler, rn.RefID)
 | |
| 	}
 | |
| 
 | |
| 	rawUpsampler, ok := rn.Query["upsampler"]
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("no downsampler specified in resample command for refId %v", rn.RefID)
 | |
| 	}
 | |
| 	upsampler, ok := rawUpsampler.(string)
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("expected resample downsampler to be a string, got type %T for refId %v", upsampler, rn.RefID)
 | |
| 	}
 | |
| 
 | |
| 	return NewResampleCommand(rn.RefID, window, varToResample, downsampler, upsampler, rn.TimeRange)
 | |
| }
 | |
| 
 | |
| // NeedsVars returns the variable names (refIds) that are dependencies
 | |
| // to execute the command and allows the command to fulfill the Command interface.
 | |
| func (gr *ResampleCommand) NeedsVars() []string {
 | |
| 	return []string{gr.VarToResample}
 | |
| }
 | |
| 
 | |
| // Execute runs the command and returns the results or an error if the command
 | |
| // failed to execute.
 | |
| func (gr *ResampleCommand) Execute(ctx context.Context, vars mathexp.Vars) (mathexp.Results, error) {
 | |
| 	newRes := mathexp.Results{}
 | |
| 	for _, val := range vars[gr.VarToResample].Values {
 | |
| 		series, ok := val.(mathexp.Series)
 | |
| 		if !ok {
 | |
| 			return newRes, fmt.Errorf("can only resample type series, got type %v", val.Type())
 | |
| 		}
 | |
| 		num, err := series.Resample(gr.refID, gr.Window, gr.Downsampler, gr.Upsampler, gr.TimeRange.From, gr.TimeRange.To)
 | |
| 		if err != nil {
 | |
| 			return newRes, err
 | |
| 		}
 | |
| 		newRes.Values = append(newRes.Values, num)
 | |
| 	}
 | |
| 	return newRes, nil
 | |
| }
 | |
| 
 | |
| // CommandType is the type of the expression command.
 | |
| type CommandType int
 | |
| 
 | |
| const (
 | |
| 	// TypeUnknown is the CMDType for an unrecognized expression type.
 | |
| 	TypeUnknown CommandType = iota
 | |
| 	// TypeMath is the CMDType for a math expression.
 | |
| 	TypeMath
 | |
| 	// TypeReduce is the CMDType for a reduction expression.
 | |
| 	TypeReduce
 | |
| 	// TypeResample is the CMDType for a resampling expression.
 | |
| 	TypeResample
 | |
| 	// TypeClassicConditions is the CMDType for the classic condition operation.
 | |
| 	TypeClassicConditions
 | |
| )
 | |
| 
 | |
| func (gt CommandType) String() string {
 | |
| 	switch gt {
 | |
| 	case TypeMath:
 | |
| 		return "math"
 | |
| 	case TypeReduce:
 | |
| 		return "reduce"
 | |
| 	case TypeResample:
 | |
| 		return "resample"
 | |
| 	case TypeClassicConditions:
 | |
| 		return "classic_conditions"
 | |
| 	default:
 | |
| 		return "unknown"
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // ParseCommandType returns a CommandType from its string representation.
 | |
| func ParseCommandType(s string) (CommandType, error) {
 | |
| 	switch s {
 | |
| 	case "math":
 | |
| 		return TypeMath, nil
 | |
| 	case "reduce":
 | |
| 		return TypeReduce, nil
 | |
| 	case "resample":
 | |
| 		return TypeResample, nil
 | |
| 	case "classic_conditions":
 | |
| 		return TypeClassicConditions, nil
 | |
| 	default:
 | |
| 		return TypeUnknown, fmt.Errorf("'%v' is not a recognized expression type", s)
 | |
| 	}
 | |
| }
 |