Live: array for Processor, Outputter and Subscriber in channel rule top level (#39677)
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
package pipeline
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
)
|
||||
|
||||
type ThresholdOutputConfig struct {
|
||||
FieldName string `json:"fieldName"`
|
||||
Channel string `json:"channel"`
|
||||
}
|
||||
|
||||
//go:generate mockgen -destination=frame_output_threshold_mock.go -package=pipeline github.com/grafana/grafana/pkg/services/live/pipeline FrameGetSetter
|
||||
|
||||
type FrameGetSetter interface {
|
||||
Get(orgID int64, channel string) (*data.Frame, bool, error)
|
||||
Set(orgID int64, channel string, frame *data.Frame) error
|
||||
}
|
||||
|
||||
// ThresholdOutput can monitor threshold transitions of the specified field and output
|
||||
// special state frame to the configured channel.
|
||||
type ThresholdOutput struct {
|
||||
frameStorage FrameGetSetter
|
||||
config ThresholdOutputConfig
|
||||
}
|
||||
|
||||
func NewThresholdOutput(frameStorage FrameGetSetter, config ThresholdOutputConfig) *ThresholdOutput {
|
||||
return &ThresholdOutput{frameStorage: frameStorage, config: config}
|
||||
}
|
||||
|
||||
const FrameOutputTypeThreshold = "threshold"
|
||||
|
||||
func (out *ThresholdOutput) Type() string {
|
||||
return FrameOutputTypeThreshold
|
||||
}
|
||||
|
||||
func (out *ThresholdOutput) OutputFrame(_ context.Context, vars Vars, frame *data.Frame) ([]*ChannelFrame, error) {
|
||||
if frame == nil {
|
||||
return nil, nil
|
||||
}
|
||||
previousFrame, previousFrameOk, err := out.frameStorage.Get(vars.OrgID, out.config.Channel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fieldName := out.config.FieldName
|
||||
|
||||
currentFrameFieldIndex := -1
|
||||
for i, f := range frame.Fields {
|
||||
if f.Name == fieldName {
|
||||
currentFrameFieldIndex = i
|
||||
}
|
||||
}
|
||||
if currentFrameFieldIndex < 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if frame.Fields[currentFrameFieldIndex].Config == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if frame.Fields[currentFrameFieldIndex].Config.Thresholds == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mode := frame.Fields[currentFrameFieldIndex].Config.Thresholds.Mode
|
||||
if mode != data.ThresholdsModeAbsolute {
|
||||
return nil, fmt.Errorf("unsupported threshold mode: %s", mode)
|
||||
}
|
||||
|
||||
if len(frame.Fields[currentFrameFieldIndex].Config.Thresholds.Steps) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
previousFrameFieldIndex := -1
|
||||
if previousFrameOk {
|
||||
for i, f := range previousFrame.Fields {
|
||||
if f.Name == fieldName {
|
||||
previousFrameFieldIndex = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var previousState *string
|
||||
if previousFrameOk && previousFrameFieldIndex >= 0 {
|
||||
var previousThreshold data.Threshold
|
||||
value, ok := previousFrame.Fields[previousFrameFieldIndex].At(previousFrame.Fields[0].Len() - 1).(*float64)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
if value == nil {
|
||||
// TODO: what should we do here?
|
||||
return nil, nil
|
||||
}
|
||||
emptyState := ""
|
||||
previousState = &emptyState
|
||||
for _, threshold := range frame.Fields[currentFrameFieldIndex].Config.Thresholds.Steps {
|
||||
if *value >= float64(threshold.Value) {
|
||||
previousThreshold = threshold
|
||||
previousState = &previousThreshold.State
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fTime := data.NewFieldFromFieldType(data.FieldTypeTime, 0)
|
||||
fTime.Name = "time"
|
||||
f1 := data.NewFieldFromFieldType(data.FieldTypeFloat64, 0)
|
||||
f1.Name = "value"
|
||||
f2 := data.NewFieldFromFieldType(data.FieldTypeString, 0)
|
||||
f2.Name = "state"
|
||||
f3 := data.NewFieldFromFieldType(data.FieldTypeString, 0)
|
||||
f3.Name = "color"
|
||||
|
||||
for i := 0; i < frame.Fields[currentFrameFieldIndex].Len(); i++ {
|
||||
// TODO: support other numeric types.
|
||||
value, ok := frame.Fields[currentFrameFieldIndex].At(i).(*float64)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
if value == nil {
|
||||
// TODO: what should we do here?
|
||||
return nil, nil
|
||||
}
|
||||
var currentThreshold data.Threshold
|
||||
for _, threshold := range frame.Fields[currentFrameFieldIndex].Config.Thresholds.Steps {
|
||||
if *value >= float64(threshold.Value) {
|
||||
currentThreshold = threshold
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if previousState == nil || currentThreshold.State != *previousState {
|
||||
fTime.Append(time.Now())
|
||||
f1.Append(*value)
|
||||
f2.Append(currentThreshold.State)
|
||||
f3.Append(currentThreshold.Color)
|
||||
previousState = ¤tThreshold.State
|
||||
}
|
||||
}
|
||||
|
||||
if fTime.Len() > 0 {
|
||||
stateFrame := data.NewFrame("state", fTime, f1, f2, f3)
|
||||
err := out.frameStorage.Set(vars.OrgID, out.config.Channel, frame)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*ChannelFrame{{
|
||||
Channel: out.config.Channel,
|
||||
Frame: stateFrame,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
return nil, out.frameStorage.Set(vars.OrgID, out.config.Channel, frame)
|
||||
}
|
||||
Reference in New Issue
Block a user