Files
monitor/backend/internal/handler/processor.go
2025-12-05 11:13:25 +08:00

463 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handler
import (
"fmt"
"sort"
"strconv"
"strings"
"time"
"github.com/monitor/backend/internal/storage"
)
// MetricData 处理后的监控数据
type MetricData struct {
Time time.Time `json:"time"`
Value float64 `json:"value"`
}
// ParseInterval 解析时间区间字符串,返回秒数
// 支持的格式10s, 1m, 5m, 1h, 1d等
func ParseInterval(intervalStr string) (int, error) {
if intervalStr == "" {
return 10, nil // 默认10秒
}
intervalStr = strings.ToLower(intervalStr)
var multiplier int
// 解析时间单位
switch {
case strings.HasSuffix(intervalStr, "s"):
multiplier = 1
intervalStr = strings.TrimSuffix(intervalStr, "s")
case strings.HasSuffix(intervalStr, "m"):
multiplier = 60
intervalStr = strings.TrimSuffix(intervalStr, "m")
case strings.HasSuffix(intervalStr, "h"):
multiplier = 3600
intervalStr = strings.TrimSuffix(intervalStr, "h")
case strings.HasSuffix(intervalStr, "d"):
multiplier = 86400
intervalStr = strings.TrimSuffix(intervalStr, "d")
default:
return 0, fmt.Errorf("unsupported interval format: %s", intervalStr)
}
// 解析数字部分
value, err := strconv.Atoi(intervalStr)
if err != nil {
return 0, fmt.Errorf("invalid interval value: %s", intervalStr)
}
return value * multiplier, nil
}
// FormatTimeByInterval 根据时间区间格式化时间
func FormatTimeByInterval(t time.Time, intervalSeconds int) string {
// 按指定秒数对齐时间
rounded := t.Truncate(time.Duration(intervalSeconds) * time.Second)
// 根据区间长度选择不同的格式化字符串
if intervalSeconds < 60 {
// 秒级,显示到秒
return rounded.Format("2006-01-02 15:04:05")
} else if intervalSeconds < 3600 {
// 分钟级,显示到分钟
return rounded.Format("2006-01-02 15:04:00")
} else {
// 小时级,显示到小时
return rounded.Format("2006-01-02 15:00:00")
}
}
// ProcessMetricData 处理监控数据,支持动态时间区间
func ProcessMetricData(points []storage.MetricPoint, aggregation string, intervalStr string, startTime, endTime string) []MetricData {
// 解析时间区间
intervalSeconds, err := ParseInterval(intervalStr)
if err != nil {
// 解析失败使用默认值10秒
intervalSeconds = 10
}
// 解析开始和结束时间
var start, end time.Time
var parseErr error
// 解析开始时间
if strings.HasPrefix(startTime, "-") || startTime == "now()" {
// 相对时间,使用当前时间作为基准
now := time.Now()
if startTime == "now()" {
start = now
} else {
// 相对时间,如-24h
relDuration, parseErr := time.ParseDuration(startTime)
if parseErr == nil {
start = now.Add(relDuration)
} else {
// 解析失败,使用默认时间
start = now.Add(-24 * time.Hour)
}
}
} else {
// 绝对时间,尝试解析
start, parseErr = time.Parse(time.RFC3339, startTime)
if parseErr != nil {
// 尝试其他格式
start, parseErr = time.Parse("2006-01-02T15:04", startTime)
if parseErr != nil {
// 尝试YYYY-MM-DD HH:MM格式
start, parseErr = time.Parse("2006-01-02 15:04", startTime)
if parseErr != nil {
// 解析失败,使用默认时间
start = time.Now().Add(-24 * time.Hour)
}
}
}
}
// 解析结束时间
if endTime == "now()" {
end = time.Now()
} else if strings.HasPrefix(endTime, "-") {
// 相对时间,使用当前时间作为基准
now := time.Now()
relDuration, parseErr := time.ParseDuration(endTime)
if parseErr == nil {
end = now.Add(relDuration)
} else {
// 解析失败,使用当前时间
end = now
}
} else {
// 绝对时间,尝试解析
end, parseErr = time.Parse(time.RFC3339, endTime)
if parseErr != nil {
// 尝试其他格式
end, parseErr = time.Parse("2006-01-02T15:04", endTime)
if parseErr != nil {
// 尝试YYYY-MM-DD HH:MM格式
end, parseErr = time.Parse("2006-01-02 15:04", endTime)
if parseErr != nil {
// 解析失败,使用当前时间
end = time.Now()
}
}
}
}
// 如果没有数据点,生成覆盖整个时间范围的空数据点
if len(points) == 0 {
return generateEmptyData(start, end, intervalSeconds)
}
// 根据时间区段精细程度自动调整聚合策略
// 如果时间区段精细程度为1s显示全部数据去重后
// 如果时间区段精细程度>1s根据聚合类型进行聚合
if intervalSeconds == 1 {
// 1秒时间区段返回全部原始数据去重后
return processRawData(points)
} else {
// 大于1秒时间区段根据聚合类型处理
switch aggregation {
case "raw":
// 对于raw聚合类型返回去重后的原始数据
return processRawData(points)
case "average":
return processAverageData(points, intervalSeconds, start, end)
case "max":
return processMaxData(points, intervalSeconds, start, end)
case "min":
return processMinData(points, intervalSeconds, start, end)
case "sum":
return processSumData(points, intervalSeconds, start, end)
default:
// 默认返回平均值
return processAverageData(points, intervalSeconds, start, end)
}
}
}
// generateEmptyData 生成覆盖整个时间范围的空数据点
func generateEmptyData(start, end time.Time, intervalSeconds int) []MetricData {
// 生成空数据点,确保覆盖整个时间范围
result := make([]MetricData, 0)
currentTime := start.Truncate(time.Duration(intervalSeconds) * time.Second)
// 循环生成数据点,直到超过结束时间
for currentTime.Before(end) {
result = append(result, MetricData{
Time: currentTime,
Value: 0, // 使用0表示无数据
})
currentTime = currentTime.Add(time.Duration(intervalSeconds) * time.Second)
}
return result
}
// processRawData 处理原始数据,添加去重逻辑
func processRawData(points []storage.MetricPoint) []MetricData {
// 使用map进行去重key为时间戳精确到秒+值的组合
deduplicated := make(map[string]storage.MetricPoint)
for _, point := range points {
// 精确到秒的时间戳
key := fmt.Sprintf("%d_%.2f", point.Time.Unix(), point.Value)
deduplicated[key] = point
}
// 将去重后的数据转换为切片
result := make([]MetricData, 0, len(deduplicated))
for _, point := range deduplicated {
result = append(result, MetricData{
Time: point.Time,
Value: point.Value,
})
}
// 按时间排序
sort.Slice(result, func(i, j int) bool {
return result[i].Time.Before(result[j].Time)
})
return result
}
// processAverageData 处理平均值数据
func processAverageData(points []storage.MetricPoint, intervalSeconds int, start, end time.Time) []MetricData {
// 按指定时间区间聚合的平均值
// 如果没有数据点,生成空数据
if len(points) == 0 {
return generateEmptyData(start, end, intervalSeconds)
}
// 先对原始数据按时间排序
sort.Slice(points, func(i, j int) bool {
return points[i].Time.Before(points[j].Time)
})
// 初始化结果和指针
result := make([]MetricData, 0)
currentTime := start.Truncate(time.Duration(intervalSeconds) * time.Second)
pointIndex := 0
pointCount := len(points)
// 循环处理每个时间区间
for currentTime.Before(end.Add(time.Duration(intervalSeconds) * time.Second)) {
intervalStart := currentTime
intervalEnd := intervalStart.Add(time.Duration(intervalSeconds) * time.Second)
// 收集当前区间内的数据点
var sum float64 = 0
var count int = 0
for pointIndex < pointCount && points[pointIndex].Time.Before(intervalEnd) {
// 只处理区间内的数据点
if points[pointIndex].Time.After(intervalStart) || points[pointIndex].Time.Equal(intervalStart) {
sum += points[pointIndex].Value
count++
}
pointIndex++
}
// 计算平均值
var value float64 = 0
if count > 0 {
value = sum / float64(count)
}
// 添加到结果
result = append(result, MetricData{
Time: currentTime,
Value: value,
})
// 移动到下一个时间区间
currentTime = intervalEnd
}
return result
}
// processMaxData 处理最大值数据
func processMaxData(points []storage.MetricPoint, intervalSeconds int, start, end time.Time) []MetricData {
// 按指定时间区间聚合的最大值
// 如果没有数据点,生成空数据
if len(points) == 0 {
return generateEmptyData(start, end, intervalSeconds)
}
// 先对原始数据按时间排序
sort.Slice(points, func(i, j int) bool {
return points[i].Time.Before(points[j].Time)
})
// 初始化结果和指针
result := make([]MetricData, 0)
currentTime := start.Truncate(time.Duration(intervalSeconds) * time.Second)
pointIndex := 0
pointCount := len(points)
// 循环处理每个时间区间
for currentTime.Before(end.Add(time.Duration(intervalSeconds) * time.Second)) {
intervalStart := currentTime
intervalEnd := intervalStart.Add(time.Duration(intervalSeconds) * time.Second)
// 查找当前区间内的最大值
var value float64 = 0
var foundData bool = false
for pointIndex < pointCount && points[pointIndex].Time.Before(intervalEnd) {
// 只处理区间内的数据点
if points[pointIndex].Time.After(intervalStart) || points[pointIndex].Time.Equal(intervalStart) {
if !foundData {
value = points[pointIndex].Value
foundData = true
} else if points[pointIndex].Value > value {
value = points[pointIndex].Value
}
}
pointIndex++
}
// 添加到结果
result = append(result, MetricData{
Time: currentTime,
Value: value,
})
// 移动到下一个时间区间
currentTime = intervalEnd
}
return result
}
// processMinData 处理最小值数据
func processMinData(points []storage.MetricPoint, intervalSeconds int, start, end time.Time) []MetricData {
// 按指定时间区间聚合的最小值
// 如果没有数据点,生成空数据
if len(points) == 0 {
return generateEmptyData(start, end, intervalSeconds)
}
// 先对原始数据按时间排序
sort.Slice(points, func(i, j int) bool {
return points[i].Time.Before(points[j].Time)
})
// 初始化结果和指针
result := make([]MetricData, 0)
currentTime := start.Truncate(time.Duration(intervalSeconds) * time.Second)
pointIndex := 0
pointCount := len(points)
// 循环处理每个时间区间
for currentTime.Before(end.Add(time.Duration(intervalSeconds) * time.Second)) {
intervalStart := currentTime
intervalEnd := intervalStart.Add(time.Duration(intervalSeconds) * time.Second)
// 查找当前区间内的最小值
var value float64 = 0
var foundData bool = false
for pointIndex < pointCount && points[pointIndex].Time.Before(intervalEnd) {
// 只处理区间内的数据点
if points[pointIndex].Time.After(intervalStart) || points[pointIndex].Time.Equal(intervalStart) {
if !foundData {
value = points[pointIndex].Value
foundData = true
} else if points[pointIndex].Value < value {
value = points[pointIndex].Value
}
}
pointIndex++
}
// 添加到结果
result = append(result, MetricData{
Time: currentTime,
Value: value,
})
// 移动到下一个时间区间
currentTime = intervalEnd
}
return result
}
// processSumData 处理总和数据
func processSumData(points []storage.MetricPoint, intervalSeconds int, start, end time.Time) []MetricData {
// 按指定时间区间聚合的总和
// 如果没有数据点,生成空数据
if len(points) == 0 {
return generateEmptyData(start, end, intervalSeconds)
}
// 先对原始数据按时间排序
sort.Slice(points, func(i, j int) bool {
return points[i].Time.Before(points[j].Time)
})
// 初始化结果和指针
result := make([]MetricData, 0)
currentTime := start.Truncate(time.Duration(intervalSeconds) * time.Second)
pointIndex := 0
pointCount := len(points)
// 循环处理每个时间区间
for currentTime.Before(end.Add(time.Duration(intervalSeconds) * time.Second)) {
intervalStart := currentTime
intervalEnd := intervalStart.Add(time.Duration(intervalSeconds) * time.Second)
// 收集当前区间内的数据点
intervalPoints := make([]storage.MetricPoint, 0)
for pointIndex < pointCount && points[pointIndex].Time.Before(intervalEnd) {
// 只处理区间内的数据点
if points[pointIndex].Time.After(intervalStart) || points[pointIndex].Time.Equal(intervalStart) {
intervalPoints = append(intervalPoints, points[pointIndex])
}
pointIndex++
}
// 计算总和
sum := 0.0
intervalPointCount := len(intervalPoints)
if intervalPointCount > 0 {
if intervalPointCount < 2 {
// 如果数据点数量小于2直接返回第一个数据点的值
sum = intervalPoints[0].Value
} else {
// 计算区间内的流量总和
for i := 1; i < intervalPointCount; i++ {
// 计算时间差(秒)
timeDiff := intervalPoints[i].Time.Sub(intervalPoints[i-1].Time).Seconds()
if timeDiff > 0 {
// 计算这个时间段的平均速率
averageRate := (intervalPoints[i].Value + intervalPoints[i-1].Value) / 2
// 计算这个时间段的流量
flow := averageRate * timeDiff
// 累加到总和
sum += flow
}
}
}
}
// 添加到结果
result = append(result, MetricData{
Time: currentTime,
Value: sum,
})
// 移动到下一个时间区间
currentTime = intervalEnd
}
return result
}