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") } } // ProcessMetrics 处理监控数据,支持动态时间区间 func ProcessMetrics(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 { // 生成空数据点,确保覆盖整个时间范围 result := make([]MetricData, 0) currentTime := start // 循环生成数据点,直到超过结束时间 for currentTime.Before(end) { result = append(result, MetricData{ Time: currentTime, Value: 0, // 使用0表示无数据 }) currentTime = currentTime.Add(time.Duration(intervalSeconds) * time.Second) } return result } // 根据时间区段精细程度自动调整聚合策略 // 如果时间区段精细程度为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) } } } // 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 { // 按指定时间区间聚合的平均值 // 按指定区间分组,保留UTC时区信息 intervalData := make(map[string][]float64) for _, point := range points { // 使用UTC时间,按指定区间对齐 utcTime := point.Time.UTC() // 格式化时间键 intervalKey := FormatTimeByInterval(utcTime, intervalSeconds) value := point.Value intervalData[intervalKey] = append(intervalData[intervalKey], value) } // 生成覆盖整个请求时间范围的完整时间序列 result := make([]MetricData, 0) currentTime := start.Truncate(time.Duration(intervalSeconds) * time.Second) // 循环生成每个时间区间的数据点 for currentTime.Before(end.Add(time.Duration(intervalSeconds) * time.Second)) { // 格式化当前时间为区间键 intervalKey := FormatTimeByInterval(currentTime, intervalSeconds) // 计算当前区间的平均值 var value float64 = 0 if values, exists := intervalData[intervalKey]; exists && len(values) > 0 { // 计算平均值 sum := 0.0 for _, v := range values { sum += v } value = sum / float64(len(values)) } // 添加到结果 result = append(result, MetricData{ Time: currentTime, Value: value, }) // 移动到下一个时间区间 currentTime = currentTime.Add(time.Duration(intervalSeconds) * time.Second) } return result } // processMaxData 处理最大值数据 func processMaxData(points []storage.MetricPoint, intervalSeconds int, start, end time.Time) []MetricData { // 按指定时间区间聚合的最大值 // 按指定区间分组,保留UTC时区信息 intervalData := make(map[string][]float64) for _, point := range points { // 使用UTC时间,按指定区间对齐 utcTime := point.Time.UTC() // 格式化时间键 intervalKey := FormatTimeByInterval(utcTime, intervalSeconds) value := point.Value intervalData[intervalKey] = append(intervalData[intervalKey], value) } // 生成覆盖整个请求时间范围的完整时间序列 result := make([]MetricData, 0) currentTime := start.Truncate(time.Duration(intervalSeconds) * time.Second) // 循环生成每个时间区间的数据点 for currentTime.Before(end.Add(time.Duration(intervalSeconds) * time.Second)) { // 格式化当前时间为区间键 intervalKey := FormatTimeByInterval(currentTime, intervalSeconds) // 计算当前区间的最大值 var value float64 = 0 if values, exists := intervalData[intervalKey]; exists && len(values) > 0 { // 计算最大值 max := values[0] for _, v := range values { if v > max { max = v } } value = max } // 添加到结果 result = append(result, MetricData{ Time: currentTime, Value: value, }) // 移动到下一个时间区间 currentTime = currentTime.Add(time.Duration(intervalSeconds) * time.Second) } return result } // processMinData 处理最小值数据 func processMinData(points []storage.MetricPoint, intervalSeconds int, start, end time.Time) []MetricData { // 按指定时间区间聚合的最小值 // 按指定区间分组,保留UTC时区信息 intervalData := make(map[string][]float64) for _, point := range points { // 使用UTC时间,按指定区间对齐 utcTime := point.Time.UTC() // 格式化时间键 intervalKey := FormatTimeByInterval(utcTime, intervalSeconds) value := point.Value intervalData[intervalKey] = append(intervalData[intervalKey], value) } // 生成覆盖整个请求时间范围的完整时间序列 result := make([]MetricData, 0) currentTime := start.Truncate(time.Duration(intervalSeconds) * time.Second) // 循环生成每个时间区间的数据点 for currentTime.Before(end.Add(time.Duration(intervalSeconds) * time.Second)) { // 格式化当前时间为区间键 intervalKey := FormatTimeByInterval(currentTime, intervalSeconds) // 计算当前区间的最小值 var value float64 = 0 if values, exists := intervalData[intervalKey]; exists && len(values) > 0 { // 计算最小值 min := values[0] for _, v := range values { if v < min { min = v } } value = min } // 添加到结果 result = append(result, MetricData{ Time: currentTime, Value: value, }) // 移动到下一个时间区间 currentTime = currentTime.Add(time.Duration(intervalSeconds) * time.Second) } return result } // processSumData 处理总和数据 func processSumData(points []storage.MetricPoint, intervalSeconds int, start, end time.Time) []MetricData { // 按指定时间区间聚合的总和 // 按指定区间分组,保留UTC时区信息 intervalData := make(map[string][]storage.MetricPoint) for _, point := range points { // 使用UTC时间,按指定区间对齐 utcTime := point.Time.UTC() // 格式化时间键 intervalKey := FormatTimeByInterval(utcTime, intervalSeconds) intervalData[intervalKey] = append(intervalData[intervalKey], point) } // 生成覆盖整个请求时间范围的完整时间序列 result := make([]MetricData, 0) currentTime := start.Truncate(time.Duration(intervalSeconds) * time.Second) // 循环生成每个时间区间的数据点 for currentTime.Before(end.Add(time.Duration(intervalSeconds) * time.Second)) { // 格式化当前时间为区间键 intervalKey := FormatTimeByInterval(currentTime, intervalSeconds) // 计算当前区间的总和 sum := 0.0 if intervalPoints, exists := intervalData[intervalKey]; exists { // 如果数据点数量小于2,直接返回第一个数据点的值 if len(intervalPoints) < 2 { if len(intervalPoints) > 0 { sum = intervalPoints[0].Value } } else { // 按时间排序 sort.Slice(intervalPoints, func(i, j int) bool { return intervalPoints[i].Time.Before(intervalPoints[j].Time) }) for i := 1; i < len(intervalPoints); 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 = currentTime.Add(time.Duration(intervalSeconds) * time.Second) } return result }