258 lines
8.0 KiB
Go
258 lines
8.0 KiB
Go
|
|
// Package realtimedata define real time data operation functions
|
||
|
|
package realtimedata
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
|
||
|
|
"modelRT/constants"
|
||
|
|
"modelRT/logger"
|
||
|
|
)
|
||
|
|
|
||
|
|
// RealTimeAnalyzer 接口定义了实时数据分析和事件触发的通用方法
|
||
|
|
type RealTimeAnalyzer interface {
|
||
|
|
AnalyzeAndTriggerEvent(ctx context.Context, conf *ComputeConfig, realTimeValues []float64)
|
||
|
|
}
|
||
|
|
|
||
|
|
// teEventThresholds define struct of store the telemetry float point threshold parsed from conf field cause
|
||
|
|
type teEventThresholds struct {
|
||
|
|
up float64
|
||
|
|
upup float64
|
||
|
|
down float64
|
||
|
|
downdown float64
|
||
|
|
isFloatCause bool
|
||
|
|
}
|
||
|
|
|
||
|
|
// parseTEThresholds define func to parse telemetry thresholds by casue map
|
||
|
|
func parseTEThresholds(cause map[string]any) (teEventThresholds, error) {
|
||
|
|
t := teEventThresholds{}
|
||
|
|
floatKeys := map[string]*float64{
|
||
|
|
"upup": &t.upup,
|
||
|
|
"up": &t.up,
|
||
|
|
"down": &t.down,
|
||
|
|
"downdown": &t.downdown,
|
||
|
|
}
|
||
|
|
|
||
|
|
for key, ptr := range floatKeys {
|
||
|
|
if value, exists := cause[key]; exists {
|
||
|
|
if floatVal, ok := value.(float64); ok {
|
||
|
|
*ptr = floatVal
|
||
|
|
t.isFloatCause = true
|
||
|
|
} else {
|
||
|
|
return teEventThresholds{}, fmt.Errorf("key:%s type is incorrect. expected float64, actual %T", key, value)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// quickly check mutual exclusion
|
||
|
|
if _, exists := cause["edge"]; exists && t.isFloatCause {
|
||
|
|
return teEventThresholds{}, errors.New("cause config error: 'up/down' keys and 'edge' key are mutually exclusive, but both found")
|
||
|
|
}
|
||
|
|
return t, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// getTEBreachType define func to determine which type of out-of-limit the telemetry real time data belongs to
|
||
|
|
func getTEBreachType(value float64, t teEventThresholds) string {
|
||
|
|
if t.upup > 0 && value > t.upup {
|
||
|
|
return constants.TelemetryUpUpLimit
|
||
|
|
}
|
||
|
|
if t.up > 0 && value > t.up {
|
||
|
|
return constants.TelemetryUpLimit
|
||
|
|
}
|
||
|
|
if t.downdown > 0 && value < t.downdown {
|
||
|
|
return constants.TelemetryDownDownLimit
|
||
|
|
}
|
||
|
|
if t.down > 0 && value < t.down {
|
||
|
|
return constants.TelemetryDownLimit
|
||
|
|
}
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
|
||
|
|
// TEAnalyzer define struct of store the thresholds required for telemetry and implements the analysis logic.
|
||
|
|
type TEAnalyzer struct {
|
||
|
|
Thresholds teEventThresholds
|
||
|
|
}
|
||
|
|
|
||
|
|
// AnalyzeAndTriggerEvent 实现了 RealTimeAnalyzer 接口
|
||
|
|
func (t *TEAnalyzer) AnalyzeAndTriggerEvent(ctx context.Context, conf *ComputeConfig, realTimeValues []float64) {
|
||
|
|
analyzeTEDataLogic(ctx, conf, t.Thresholds, realTimeValues)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 封装原 analyzeTEDataAndTriggerEvent 的核心逻辑
|
||
|
|
func analyzeTEDataLogic(ctx context.Context, conf *ComputeConfig, thresholds teEventThresholds, realTimeValues []float64) {
|
||
|
|
windowSize := conf.minBreachCount
|
||
|
|
if windowSize <= 0 {
|
||
|
|
logger.Error(ctx, "variable minBreachCount is invalid or zero, analysis skipped", "minBreachCount", windowSize)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// mark whether any events have been triggered in this batch
|
||
|
|
var eventTriggered bool
|
||
|
|
breachTriggers := map[string]bool{
|
||
|
|
"up": false, "upup": false, "down": false, "downdown": false,
|
||
|
|
}
|
||
|
|
|
||
|
|
// implement slide window to determine breach counts
|
||
|
|
for i := 0; i <= len(realTimeValues)-windowSize; i++ {
|
||
|
|
window := realTimeValues[i : i+windowSize]
|
||
|
|
firstValueBreachType := getTEBreachType(window[0], thresholds)
|
||
|
|
|
||
|
|
if firstValueBreachType == "" {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
allMatch := true
|
||
|
|
for j := 1; j < windowSize; j++ {
|
||
|
|
currentValueBreachType := getTEBreachType(window[j], thresholds)
|
||
|
|
if currentValueBreachType != firstValueBreachType {
|
||
|
|
allMatch = false
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if allMatch {
|
||
|
|
// in the case of a continuous sequence of out-of-limit events, check whether this type of event has already been triggered in the current batch of data
|
||
|
|
if !breachTriggers[firstValueBreachType] {
|
||
|
|
// trigger event
|
||
|
|
logger.Warn(ctx, "event triggered by sliding window", "breach_type", firstValueBreachType, "value", window[windowSize-1])
|
||
|
|
|
||
|
|
breachTriggers[firstValueBreachType] = true
|
||
|
|
eventTriggered = true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// TODO 调用 EventRT 接口进行时间推送
|
||
|
|
if eventTriggered {
|
||
|
|
fmt.Println("--- 本次数据切片分析结束:已标记触发事件 ---")
|
||
|
|
} else {
|
||
|
|
fmt.Println("--- 本次数据切片分析结束:未检测到持续越限,不触发事件 ---")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// tiEventThresholds define struct of store the telesignal float point threshold parsed from conf field cause
|
||
|
|
type tiEventThresholds struct {
|
||
|
|
edge string
|
||
|
|
isFloatCause bool
|
||
|
|
}
|
||
|
|
|
||
|
|
// parseTEThresholds define func to parse telesignal thresholds by casue map
|
||
|
|
func parseTIThresholds(cause map[string]any) (tiEventThresholds, error) {
|
||
|
|
edgeKey := "edge"
|
||
|
|
t := tiEventThresholds{
|
||
|
|
isFloatCause: false,
|
||
|
|
}
|
||
|
|
|
||
|
|
if value, exists := cause[edgeKey]; exists {
|
||
|
|
if strVal, ok := value.(string); ok {
|
||
|
|
switch strVal {
|
||
|
|
case "raising", "falling":
|
||
|
|
t.edge = strVal
|
||
|
|
return t, nil
|
||
|
|
default:
|
||
|
|
return tiEventThresholds{}, fmt.Errorf("key:%s value is incorrect, actual value %s. expected 'raising' or 'falling'", edgeKey, strVal)
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
return tiEventThresholds{}, fmt.Errorf("key:%s already exists but type is incorrect. expected string, actual %T", edgeKey, value)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return tiEventThresholds{}, fmt.Errorf("cause map is invalid for telesignal: missing required key '%s'", edgeKey)
|
||
|
|
}
|
||
|
|
|
||
|
|
// getTIBreachType define func to determine which type of out-of-limit the telesignal real time data belongs to
|
||
|
|
func getTIBreachType(currentValue float64, previousValue float64, t tiEventThresholds) string {
|
||
|
|
if t.edge == constants.TelesignalRaising {
|
||
|
|
if previousValue == 0.0 && currentValue == 1.0 {
|
||
|
|
return constants.TIBreachTriggerType
|
||
|
|
}
|
||
|
|
} else if t.edge == constants.TelesignalFalling {
|
||
|
|
if previousValue == 1.0 && currentValue == 0.0 {
|
||
|
|
return constants.TIBreachTriggerType
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
|
||
|
|
// TIAnalyzer define struct of store the thresholds required for remote signaling and implements the analysis logic
|
||
|
|
type TIAnalyzer struct {
|
||
|
|
Thresholds tiEventThresholds
|
||
|
|
}
|
||
|
|
|
||
|
|
// AnalyzeAndTriggerEvent 实现了 RealTimeAnalyzer 接口
|
||
|
|
func (t *TIAnalyzer) AnalyzeAndTriggerEvent(ctx context.Context, conf *ComputeConfig, realTimeValues []float64) {
|
||
|
|
analyzeTIDataLogic(ctx, conf, t.Thresholds, realTimeValues)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 封装原 analyzeTIDataAndTriggerEvent 的核心逻辑 (使用预计算优化版本)
|
||
|
|
func analyzeTIDataLogic(ctx context.Context, conf *ComputeConfig, thresholds tiEventThresholds, realTimeValues []float64) {
|
||
|
|
windowSize := conf.minBreachCount
|
||
|
|
if windowSize <= 0 {
|
||
|
|
logger.Error(ctx, "variable minBreachCount is invalid or zero, analysis skipped", "minBreachCount", windowSize)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
numDataPoints := len(realTimeValues)
|
||
|
|
if numDataPoints < 2 {
|
||
|
|
logger.Info(ctx, "data points less than 2, no change event possible, analysis skipped", "data_points", numDataPoints)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// pre calculate the change event type for all adjacent point pairs
|
||
|
|
numChanges := numDataPoints - 1
|
||
|
|
changeBreachTypes := make([]string, numChanges)
|
||
|
|
|
||
|
|
for i := range numChanges {
|
||
|
|
previousValue := realTimeValues[i]
|
||
|
|
currentValue := realTimeValues[i+1]
|
||
|
|
|
||
|
|
changeBreachTypes[i] = getTIBreachType(currentValue, previousValue, thresholds)
|
||
|
|
}
|
||
|
|
|
||
|
|
if numChanges < windowSize {
|
||
|
|
logger.Error(ctx, "number of change events is less than window size, analysis skipped", "num_changes", numChanges, "window_size", windowSize)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
var eventTriggered bool
|
||
|
|
breachTriggers := map[string]bool{
|
||
|
|
constants.TIBreachTriggerType: false,
|
||
|
|
}
|
||
|
|
|
||
|
|
for i := 0; i <= numChanges-windowSize; i++ {
|
||
|
|
windowBreachTypes := changeBreachTypes[i : i+windowSize]
|
||
|
|
firstBreachType := windowBreachTypes[0]
|
||
|
|
|
||
|
|
if firstBreachType == "" {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
allMatch := true
|
||
|
|
for j := 1; j < windowSize; j++ {
|
||
|
|
if windowBreachTypes[j] != firstBreachType {
|
||
|
|
allMatch = false
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if allMatch {
|
||
|
|
if !breachTriggers[firstBreachType] {
|
||
|
|
finalValueIndex := i + windowSize
|
||
|
|
logger.Warn(ctx, "event triggered by sliding window", "breach_type", firstBreachType, "value", realTimeValues[finalValueIndex])
|
||
|
|
|
||
|
|
breachTriggers[firstBreachType] = true
|
||
|
|
eventTriggered = true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// TODO 调用 EventRT 接口进行时间推送
|
||
|
|
if eventTriggered {
|
||
|
|
fmt.Println("--- 本次数据切片分析结束:已标记触发事件 ---")
|
||
|
|
} else {
|
||
|
|
fmt.Println("--- 本次数据切片分析结束:未检测到持续越限,不触发事件 ---")
|
||
|
|
}
|
||
|
|
}
|