mirror of
https://github.com/grafana/grafana.git
synced 2024-12-01 21:19:28 -06:00
123 lines
4.5 KiB
Go
123 lines
4.5 KiB
Go
|
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
|
||
|
}
|
||
|
|
||
|
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
|
||
|
}
|