2020-11-19 06:17:00 -06:00
package expr
import (
"context"
"encoding/json"
2023-12-11 14:40:31 -06:00
"errors"
2020-11-19 06:17:00 -06:00
"fmt"
2023-06-15 08:20:08 -05:00
"strings"
2020-11-19 06:17:00 -06:00
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
2024-03-04 10:22:56 -06:00
"github.com/grafana/grafana-plugin-sdk-go/data/utils/jsoniter"
2024-03-08 10:12:59 -06:00
data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1"
2023-04-18 07:04:51 -05:00
"go.opentelemetry.io/otel/attribute"
2023-10-03 07:54:20 -05:00
"go.opentelemetry.io/otel/codes"
2023-01-30 02:38:51 -06:00
"gonum.org/v1/gonum/graph/simple"
2022-05-23 09:08:14 -05:00
2021-03-02 12:51:33 -06:00
"github.com/grafana/grafana/pkg/expr/classic"
2020-11-19 06:17:00 -06:00
"github.com/grafana/grafana/pkg/expr/mathexp"
2021-05-07 08:16:21 -05:00
"github.com/grafana/grafana/pkg/infra/log"
2022-06-27 11:23:15 -05:00
"github.com/grafana/grafana/pkg/services/datasources"
2023-04-12 11:24:34 -05:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
2020-11-19 06:17:00 -06:00
)
2023-07-20 13:44:12 -05:00
// label that is used when all mathexp.Series have 0 labels to make them identifiable by labels. The value of this label is extracted from value field names
const nameLabelName = "__name__"
2021-05-07 08:16:21 -05:00
var (
logger = log . New ( "expr" )
)
2021-08-27 07:57:41 -05:00
// baseNode includes common properties used across DPNodes.
2020-11-19 06:17:00 -06:00
type baseNode struct {
id int64
refID string
}
type rawNode struct {
2022-12-01 12:08:36 -06:00
RefID string ` json:"refId" `
2023-08-30 10:46:47 -05:00
Query map [ string ] any
2023-07-13 12:37:50 -05:00
QueryRaw [ ] byte
2022-12-01 12:08:36 -06:00
QueryType string
TimeRange TimeRange
DataSource * datasources . DataSource
2023-08-18 06:49:59 -05:00
// We use this index as the id of the node graph so the order can remain during a the stable sort of the dependency graph execution order.
// Some data sources, such as cloud watch, have order dependencies between queries.
idx int64
2020-11-19 06:17:00 -06:00
}
2024-02-16 18:59:11 -06:00
func getExpressionCommandTypeString ( rawQuery map [ string ] any ) ( string , error ) {
2023-12-11 14:40:31 -06:00
rawType , ok := rawQuery [ "type" ]
2020-11-19 06:17:00 -06:00
if ! ok {
2024-02-16 18:59:11 -06:00
return "" , errors . New ( "no expression command type in query" )
2020-11-19 06:17:00 -06:00
}
typeString , ok := rawType . ( string )
if ! ok {
2024-02-16 18:59:11 -06:00
return "" , fmt . Errorf ( "expected expression command type to be a string, got type %T" , rawType )
}
return typeString , nil
}
func GetExpressionCommandType ( rawQuery map [ string ] any ) ( c CommandType , err error ) {
typeString , err := getExpressionCommandTypeString ( rawQuery )
if err != nil {
return c , err
2020-11-19 06:17:00 -06:00
}
return ParseCommandType ( typeString )
}
// String returns a string representation of the node. In particular for
2021-06-09 18:59:44 -05:00
// %v formatting in error messages.
2020-11-19 06:17:00 -06:00
func ( b * baseNode ) String ( ) string {
return b . refID
}
// CMDNode is a DPNode that holds an expression command.
type CMDNode struct {
baseNode
CMDType CommandType
Command Command
}
// ID returns the id of the node so it can fulfill the gonum's graph Node interface.
func ( b * baseNode ) ID ( ) int64 {
return b . id
}
// RefID returns the refId of the node.
func ( b * baseNode ) RefID ( ) string {
return b . refID
}
// NodeType returns the data pipeline node type.
func ( gn * CMDNode ) NodeType ( ) NodeType {
return TypeCMDNode
}
2023-09-13 12:58:16 -05:00
func ( gn * CMDNode ) NeedsVars ( ) [ ] string {
return gn . Command . NeedsVars ( )
}
2020-11-19 06:17:00 -06:00
// Execute runs the node and adds the results to vars. If the node requires
// other nodes they must have already been executed and their results must
// already by in vars.
2023-04-18 07:04:51 -05:00
func ( gn * CMDNode ) Execute ( ctx context . Context , now time . Time , vars mathexp . Vars , s * Service ) ( mathexp . Results , error ) {
return gn . Command . Execute ( ctx , now , vars , s . tracer )
2020-11-19 06:17:00 -06:00
}
2023-10-10 09:51:50 -05:00
func buildCMDNode ( rn * rawNode , toggles featuremgmt . FeatureToggles ) ( * CMDNode , error ) {
2023-12-11 14:40:31 -06:00
commandType , err := GetExpressionCommandType ( rn . Query )
2020-11-19 06:17:00 -06:00
if err != nil {
2022-09-21 14:14:11 -05:00
return nil , fmt . Errorf ( "invalid command type in expression '%v': %w" , rn . RefID , err )
2020-11-19 06:17:00 -06:00
}
node := & CMDNode {
baseNode : baseNode {
2023-08-18 06:49:59 -05:00
id : rn . idx ,
2020-11-19 06:17:00 -06:00
refID : rn . RefID ,
} ,
2021-04-27 06:22:11 -05:00
CMDType : commandType ,
2020-11-19 06:17:00 -06:00
}
2024-02-16 18:59:11 -06:00
if toggles . IsEnabledGlobally ( featuremgmt . FlagExpressionParser ) {
rn . QueryType , err = getExpressionCommandTypeString ( rn . Query )
if err != nil {
return nil , err // should not happen because the command was parsed first thing
}
// NOTE: this structure of this is weird now, because it is targeting a structure
// where this is actually run in the root loop, however we want to verify the individual
// node parsing before changing the full tree parser
2024-03-04 10:22:56 -06:00
reader := NewExpressionQueryReader ( toggles )
iter , err := jsoniter . ParseBytes ( jsoniter . ConfigDefault , rn . QueryRaw )
2024-02-16 18:59:11 -06:00
if err != nil {
return nil , err
}
2024-03-08 10:12:59 -06:00
q , err := reader . ReadQuery ( data . NewDataQuery ( map [ string ] any {
"refId" : rn . RefID ,
"type" : rn . QueryType ,
} ) , iter )
2024-02-16 18:59:11 -06:00
if err != nil {
return nil , err
}
node . Command = q . Command
return node , err
}
2020-11-19 06:17:00 -06:00
switch commandType {
case TypeMath :
node . Command , err = UnmarshalMathCommand ( rn )
case TypeReduce :
node . Command , err = UnmarshalReduceCommand ( rn )
case TypeResample :
node . Command , err = UnmarshalResampleCommand ( rn )
2021-03-02 12:51:33 -06:00
case TypeClassicConditions :
node . Command , err = classic . UnmarshalConditionsCmd ( rn . Query , rn . RefID )
2022-09-26 09:05:44 -05:00
case TypeThreshold :
2023-10-10 09:51:50 -05:00
node . Command , err = UnmarshalThresholdCommand ( rn , toggles )
2024-02-27 15:16:00 -06:00
case TypeSQL :
node . Command , err = UnmarshalSQLCommand ( rn )
2020-11-19 06:17:00 -06:00
default :
2022-09-21 14:14:11 -05:00
return nil , fmt . Errorf ( "expression command type '%v' in expression '%v' not implemented" , commandType , rn . RefID )
2020-11-19 06:17:00 -06:00
}
if err != nil {
2022-09-21 14:14:11 -05:00
return nil , fmt . Errorf ( "failed to parse expression '%v': %w" , rn . RefID , err )
2020-11-19 06:17:00 -06:00
}
return node , nil
}
const (
defaultIntervalMS = int64 ( 64 )
defaultMaxDP = int64 ( 5000 )
)
// DSNode is a DPNode that holds a datasource request.
type DSNode struct {
baseNode
2022-12-01 12:08:36 -06:00
query json . RawMessage
datasource * datasources . DataSource
2021-01-15 10:33:50 -06:00
orgID int64
queryType string
2021-04-23 09:52:32 -05:00
timeRange TimeRange
2021-01-15 10:33:50 -06:00
intervalMS int64
maxDP int64
2021-07-09 06:43:22 -05:00
request Request
2020-11-19 06:17:00 -06:00
}
2024-03-19 09:00:03 -05:00
func ( dn * DSNode ) String ( ) string {
if dn . datasource == nil {
return "unknown"
}
return dn . datasource . Type
}
2020-11-19 06:17:00 -06:00
// NodeType returns the data pipeline node type.
func ( dn * DSNode ) NodeType ( ) NodeType {
return TypeDatasourceNode
}
2023-09-13 12:58:16 -05:00
// NodeType returns the data pipeline node type.
func ( dn * DSNode ) NeedsVars ( ) [ ] string {
return [ ] string { }
}
2021-07-09 06:43:22 -05:00
func ( s * Service ) buildDSNode ( dp * simple . DirectedGraph , rn * rawNode , req * Request ) ( * DSNode , error ) {
2022-10-26 15:13:58 -05:00
if rn . TimeRange == nil {
return nil , fmt . Errorf ( "time range must be specified for refID %s" , rn . RefID )
}
2020-11-19 06:17:00 -06:00
encodedQuery , err := json . Marshal ( rn . Query )
if err != nil {
return nil , err
}
dsNode := & DSNode {
baseNode : baseNode {
2023-08-18 06:49:59 -05:00
id : rn . idx ,
2020-11-19 06:17:00 -06:00
refID : rn . RefID ,
} ,
2022-12-01 12:08:36 -06:00
orgID : req . OrgId ,
query : json . RawMessage ( encodedQuery ) ,
queryType : rn . QueryType ,
intervalMS : defaultIntervalMS ,
maxDP : defaultMaxDP ,
timeRange : rn . TimeRange ,
request : * req ,
datasource : rn . DataSource ,
2020-11-19 06:17:00 -06:00
}
var floatIntervalMS float64
2021-08-10 02:59:48 -05:00
if rawIntervalMS , ok := rn . Query [ "intervalMs" ] ; ok {
2020-11-19 06:17:00 -06:00
if floatIntervalMS , ok = rawIntervalMS . ( float64 ) ; ! ok {
return nil , fmt . Errorf ( "expected intervalMs to be an float64, got type %T for refId %v" , rawIntervalMS , rn . RefID )
}
dsNode . intervalMS = int64 ( floatIntervalMS )
}
var floatMaxDP float64
2021-08-10 02:59:48 -05:00
if rawMaxDP , ok := rn . Query [ "maxDataPoints" ] ; ok {
2020-11-19 06:17:00 -06:00
if floatMaxDP , ok = rawMaxDP . ( float64 ) ; ! ok {
return nil , fmt . Errorf ( "expected maxDataPoints to be an float64, got type %T for refId %v" , rawMaxDP , rn . RefID )
}
dsNode . maxDP = int64 ( floatMaxDP )
}
return dsNode , nil
}
2023-08-18 06:49:59 -05:00
// executeDSNodesGrouped groups datasource node queries by the datasource instance, and then sends them
// in a single request with one or more queries to the datasource.
2023-09-13 12:58:16 -05:00
func executeDSNodesGrouped ( ctx context . Context , now time . Time , vars mathexp . Vars , s * Service , nodes [ ] * DSNode ) {
2023-08-18 06:49:59 -05:00
type dsKey struct {
uid string // in theory I think this all I need for the key, but rather be safe
id int64
orgID int64
2020-11-19 06:17:00 -06:00
}
2023-08-18 06:49:59 -05:00
byDS := make ( map [ dsKey ] [ ] * DSNode )
for _ , node := range nodes {
k := dsKey { id : node . datasource . ID , uid : node . datasource . UID , orgID : node . orgID }
byDS [ k ] = append ( byDS [ k ] , node )
2020-11-19 06:17:00 -06:00
}
2023-08-18 06:49:59 -05:00
for _ , nodeGroup := range byDS {
2023-09-13 12:58:16 -05:00
func ( ) {
2023-08-18 06:49:59 -05:00
ctx , span := s . tracer . Start ( ctx , "SSE.ExecuteDatasourceQuery" )
defer span . End ( )
firstNode := nodeGroup [ 0 ]
pCtx , err := s . pCtxProvider . GetWithDataSource ( ctx , firstNode . datasource . Type , firstNode . request . User , firstNode . datasource )
if err != nil {
2023-09-13 12:58:16 -05:00
for _ , dn := range nodeGroup {
vars [ dn . refID ] = mathexp . Results { Error : datasources . ErrDataSourceNotFound }
}
return
2023-08-18 06:49:59 -05:00
}
logger := logger . FromContext ( ctx ) . New ( "datasourceType" , firstNode . datasource . Type ,
"queryRefId" , firstNode . refID ,
"datasourceUid" , firstNode . datasource . UID ,
"datasourceVersion" , firstNode . datasource . Version ,
)
2023-10-03 07:54:20 -05:00
span . SetAttributes (
attribute . String ( "datasource.type" , firstNode . datasource . Type ) ,
attribute . String ( "datasource.uid" , firstNode . datasource . UID ) ,
)
2023-08-18 06:49:59 -05:00
req := & backend . QueryDataRequest {
PluginContext : pCtx ,
Headers : firstNode . request . Headers ,
}
for _ , dn := range nodeGroup {
req . Queries = append ( req . Queries , backend . DataQuery {
RefID : dn . refID ,
MaxDataPoints : dn . maxDP ,
Interval : time . Duration ( int64 ( time . Millisecond ) * dn . intervalMS ) ,
JSON : dn . query ,
TimeRange : dn . timeRange . AbsoluteTime ( now ) ,
QueryType : dn . queryType ,
2023-07-12 13:59:02 -05:00
} )
2023-08-18 06:49:59 -05:00
}
2023-09-13 12:58:16 -05:00
instrument := func ( e error , rt string ) {
respStatus := "success"
responseType := rt
2023-08-18 06:49:59 -05:00
if e != nil {
responseType = "error"
respStatus = "failure"
2023-10-03 07:54:20 -05:00
span . SetStatus ( codes . Error , "failed to query data source" )
span . RecordError ( e )
2023-08-18 06:49:59 -05:00
}
logger . Debug ( "Data source queried" , "responseType" , responseType )
useDataplane := strings . HasPrefix ( responseType , "dataplane-" )
s . metrics . dsRequests . WithLabelValues ( respStatus , fmt . Sprintf ( "%t" , useDataplane ) , firstNode . datasource . Type ) . Inc ( )
2023-09-13 12:58:16 -05:00
}
2023-08-18 06:49:59 -05:00
resp , err := s . dataService . QueryData ( ctx , req )
if err != nil {
2023-09-13 12:58:16 -05:00
for _ , dn := range nodeGroup {
vars [ dn . refID ] = mathexp . Results { Error : MakeQueryError ( firstNode . refID , firstNode . datasource . UID , err ) }
}
instrument ( err , "" )
return
2023-08-18 06:49:59 -05:00
}
for _ , dn := range nodeGroup {
dataFrames , err := getResponseFrame ( resp , dn . refID )
if err != nil {
2023-09-13 12:58:16 -05:00
vars [ dn . refID ] = mathexp . Results { Error : MakeQueryError ( dn . refID , dn . datasource . UID , err ) }
instrument ( err , "" )
return
2023-08-18 06:49:59 -05:00
}
var result mathexp . Results
2024-03-04 10:22:56 -06:00
responseType , result , err := s . converter . Convert ( ctx , dn . datasource . Type , dataFrames , s . allowLongFrames )
2023-08-18 06:49:59 -05:00
if err != nil {
2023-09-13 12:58:16 -05:00
result . Error = makeConversionError ( dn . RefID ( ) , err )
2023-08-18 06:49:59 -05:00
}
2023-09-13 12:58:16 -05:00
instrument ( err , responseType )
2023-08-18 06:49:59 -05:00
vars [ dn . refID ] = result
}
2023-09-13 12:58:16 -05:00
} ( )
2020-11-19 06:17:00 -06:00
}
2023-08-18 06:49:59 -05:00
}
2020-11-19 06:17:00 -06:00
2023-08-18 06:49:59 -05:00
// Execute runs the node and adds the results to vars. If the node requires
// other nodes they must have already been executed and their results must
// already by in vars.
func ( dn * DSNode ) Execute ( ctx context . Context , now time . Time , _ mathexp . Vars , s * Service ) ( r mathexp . Results , e error ) {
2023-09-07 15:02:07 -05:00
logger := logger . FromContext ( ctx ) . New ( "datasourceType" , dn . datasource . Type , "queryRefId" , dn . refID , "datasourceUid" , dn . datasource . UID , "datasourceVersion" , dn . datasource . Version )
ctx , span := s . tracer . Start ( ctx , "SSE.ExecuteDatasourceQuery" )
defer span . End ( )
pCtx , err := s . pCtxProvider . GetWithDataSource ( ctx , dn . datasource . Type , dn . request . User , dn . datasource )
if err != nil {
return mathexp . Results { } , err
}
2023-10-03 07:54:20 -05:00
span . SetAttributes (
attribute . String ( "datasource.type" , dn . datasource . Type ) ,
attribute . String ( "datasource.uid" , dn . datasource . UID ) ,
)
2023-09-07 15:02:07 -05:00
req := & backend . QueryDataRequest {
PluginContext : pCtx ,
Queries : [ ] backend . DataQuery {
{
RefID : dn . refID ,
MaxDataPoints : dn . maxDP ,
Interval : time . Duration ( int64 ( time . Millisecond ) * dn . intervalMS ) ,
JSON : dn . query ,
TimeRange : dn . timeRange . AbsoluteTime ( now ) ,
QueryType : dn . queryType ,
} ,
} ,
Headers : dn . request . Headers ,
}
responseType := "unknown"
respStatus := "success"
defer func ( ) {
if e != nil {
responseType = "error"
respStatus = "failure"
2023-10-03 07:54:20 -05:00
span . SetStatus ( codes . Error , "failed to query data source" )
span . RecordError ( e )
2023-09-07 15:02:07 -05:00
}
logger . Debug ( "Data source queried" , "responseType" , responseType )
useDataplane := strings . HasPrefix ( responseType , "dataplane-" )
s . metrics . dsRequests . WithLabelValues ( respStatus , fmt . Sprintf ( "%t" , useDataplane ) , dn . datasource . Type ) . Inc ( )
} ( )
resp , err := s . dataService . QueryData ( ctx , req )
if err != nil {
return mathexp . Results { } , MakeQueryError ( dn . refID , dn . datasource . UID , err )
}
dataFrames , err := getResponseFrame ( resp , dn . refID )
if err != nil {
return mathexp . Results { } , MakeQueryError ( dn . refID , dn . datasource . UID , err )
}
var result mathexp . Results
2024-03-04 10:22:56 -06:00
responseType , result , err = s . converter . Convert ( ctx , dn . datasource . Type , dataFrames , s . allowLongFrames )
2023-09-07 15:02:07 -05:00
if err != nil {
2023-09-13 12:58:16 -05:00
err = makeConversionError ( dn . refID , err )
2023-09-07 15:02:07 -05:00
}
return result , err
2023-06-15 08:20:08 -05:00
}