修复图表时间和查询区间不匹配问题

This commit is contained in:
Alex Yang
2025-12-02 23:20:10 +08:00
commit 4d66bdf633
49 changed files with 16275 additions and 0 deletions

View File

@@ -0,0 +1,437 @@
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
}