2023-10-10 09:51:50 -05:00
package expr
import (
"context"
"fmt"
"time"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/expr/mathexp"
"github.com/grafana/grafana/pkg/infra/tracing"
)
type Fingerprints map [ data . Fingerprint ] struct { }
// HysteresisCommand is a special case of ThresholdCommand that encapsulates two thresholds that are applied depending on the results of the previous evaluations:
// - first threshold - "loading", is used when the metric is determined as not loaded, i.e. it does not exist in the data provided by the reader.
// - second threshold - "unloading", is used when the metric is determined as loaded.
// To determine whether a metric is loaded, the command uses LoadedDimensions that is supposed to contain data.Fingerprint of
// the metrics that were loaded during the previous evaluation.
// The result of the execution of the command is the same as ThresholdCommand: 0 or 1 for each metric.
type HysteresisCommand struct {
RefID string
ReferenceVar string
LoadingThresholdFunc ThresholdCommand
UnloadingThresholdFunc ThresholdCommand
LoadedDimensions Fingerprints
}
func ( h * HysteresisCommand ) NeedsVars ( ) [ ] string {
return [ ] string { h . ReferenceVar }
}
func ( h * HysteresisCommand ) Execute ( ctx context . Context , now time . Time , vars mathexp . Vars , tracer tracing . Tracer ) ( mathexp . Results , error ) {
results := vars [ h . ReferenceVar ]
// shortcut for NoData
if results . IsNoData ( ) {
return mathexp . Results { Values : mathexp . Values { mathexp . NewNoData ( ) } } , nil
}
if h . LoadedDimensions == nil || len ( h . LoadedDimensions ) == 0 {
return h . LoadingThresholdFunc . Execute ( ctx , now , vars , tracer )
}
var loadedVals , unloadedVals mathexp . Values
for _ , value := range results . Values {
_ , ok := h . LoadedDimensions [ value . GetLabels ( ) . Fingerprint ( ) ]
if ok {
loadedVals = append ( loadedVals , value )
} else {
unloadedVals = append ( unloadedVals , value )
}
}
if len ( loadedVals ) == 0 { // if all values are unloaded
return h . LoadingThresholdFunc . Execute ( ctx , now , vars , tracer )
}
if len ( unloadedVals ) == 0 { // if all values are loaded
return h . UnloadingThresholdFunc . Execute ( ctx , now , vars , tracer )
}
defer func ( ) {
// return back the old values
vars [ h . ReferenceVar ] = results
} ( )
vars [ h . ReferenceVar ] = mathexp . Results { Values : unloadedVals }
loadingResults , err := h . LoadingThresholdFunc . Execute ( ctx , now , vars , tracer )
if err != nil {
return mathexp . Results { } , fmt . Errorf ( "failed to execute loading threshold: %w" , err )
}
vars [ h . ReferenceVar ] = mathexp . Results { Values : loadedVals }
unloadingResults , err := h . UnloadingThresholdFunc . Execute ( ctx , now , vars , tracer )
if err != nil {
return mathexp . Results { } , fmt . Errorf ( "failed to execute unloading threshold: %w" , err )
}
return mathexp . Results { Values : append ( loadingResults . Values , unloadingResults . Values ... ) } , nil
}
2024-03-19 09:00:03 -05:00
func ( h HysteresisCommand ) Type ( ) string {
return "hysteresis"
}
2023-10-10 09:51:50 -05:00
func NewHysteresisCommand ( refID string , referenceVar string , loadCondition ThresholdCommand , unloadCondition ThresholdCommand , l Fingerprints ) ( * HysteresisCommand , error ) {
return & HysteresisCommand {
RefID : refID ,
LoadingThresholdFunc : loadCondition ,
UnloadingThresholdFunc : unloadCondition ,
ReferenceVar : referenceVar ,
LoadedDimensions : l ,
} , nil
}
// FingerprintsFromFrame converts data.Frame to Fingerprints.
// The input data frame must have a single field of uint64 type.
// Returns error if the input data frame has invalid format
func FingerprintsFromFrame ( frame * data . Frame ) ( Fingerprints , error ) {
frameType , frameVersion := frame . TypeInfo ( "" )
if frameType != "fingerprints" {
return nil , fmt . Errorf ( "invalid format of loaded dimensions frame: expected frame type 'fingerprints'" )
}
if frameVersion . Greater ( data . FrameTypeVersion { 1 , 0 } ) {
return nil , fmt . Errorf ( "invalid format of loaded dimensions frame: expected frame type 'fingerprints' of version 1.0 or lower" )
}
if len ( frame . Fields ) != 1 {
return nil , fmt . Errorf ( "invalid format of loaded dimensions frame: expected a single field but got %d" , len ( frame . Fields ) )
}
fld := frame . Fields [ 0 ]
if fld . Type ( ) != data . FieldTypeUint64 {
return nil , fmt . Errorf ( "invalid format of loaded dimensions frame: the field type must be uint64 but got %s" , fld . Type ( ) . String ( ) )
}
result := make ( Fingerprints , fld . Len ( ) )
for i := 0 ; i < fld . Len ( ) ; i ++ {
val , ok := fld . ConcreteAt ( i )
if ! ok {
continue
}
switch v := val . ( type ) {
case uint64 :
result [ data . Fingerprint ( v ) ] = struct { } { }
default :
return nil , fmt . Errorf ( "cannot read the value at index [%d], expected uint64 but got '%T'" , i , val )
}
}
return result , nil
}
2023-12-11 14:40:31 -06:00
// FingerprintsToFrame converts Fingerprints to data.Frame.
func FingerprintsToFrame ( fingerprints Fingerprints ) * data . Frame {
fp := make ( [ ] uint64 , 0 , len ( fingerprints ) )
for fingerprint := range fingerprints {
fp = append ( fp , uint64 ( fingerprint ) )
}
frame := data . NewFrame ( "" , data . NewField ( "fingerprints" , nil , fp ) )
frame . SetMeta ( & data . FrameMeta {
Type : "fingerprints" ,
TypeVersion : data . FrameTypeVersion { 1 , 0 } ,
} )
return frame
}