463 lines
13 KiB
Go
463 lines
13 KiB
Go
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
|
||
}
|