The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/expr/hysteresis.go

122 lines
4.5 KiB

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
}