341 lines
7.6 KiB
Go
341 lines
7.6 KiB
Go
package file
|
||
|
||
import (
|
||
"context"
|
||
"encoding/binary"
|
||
"fmt"
|
||
"io"
|
||
"sort"
|
||
"time"
|
||
|
||
"git.pyer.club/kingecg/gotidb/pkg/engine"
|
||
)
|
||
|
||
// queryRaw 实现原始数据查询
|
||
func (e *FileEngine) queryRaw(ctx context.Context, query engine.Query) (engine.QueryResult, error) {
|
||
result := &engine.TimeSeriesResult{
|
||
SeriesID: query.SeriesID,
|
||
Points: make([]engine.DataPoint, 0),
|
||
}
|
||
|
||
// 如果指定了SeriesID,只查询特定序列
|
||
if query.SeriesID != "" {
|
||
points, err := e.querySeriesPoints(query.SeriesID, query.StartTime, query.EndTime)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
result.Points = points
|
||
return result, nil
|
||
}
|
||
|
||
// 否则查询所有匹配的序列
|
||
matchedSeries := e.findMatchingSeries(query)
|
||
for _, seriesID := range matchedSeries {
|
||
points, err := e.querySeriesPoints(seriesID, query.StartTime, query.EndTime)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
result.Points = append(result.Points, points...)
|
||
}
|
||
|
||
// 按时间戳排序
|
||
sort.Slice(result.Points, func(i, j int) bool {
|
||
return result.Points[i].Timestamp < result.Points[j].Timestamp
|
||
})
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// queryLatest 实现最新数据查询
|
||
func (e *FileEngine) queryLatest(ctx context.Context, query engine.Query) (engine.QueryResult, error) {
|
||
result := &engine.TimeSeriesResult{
|
||
SeriesID: query.SeriesID,
|
||
Points: make([]engine.DataPoint, 0),
|
||
}
|
||
|
||
if query.SeriesID != "" {
|
||
point, err := e.queryLatestPoint(query.SeriesID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if point != nil {
|
||
result.Points = append(result.Points, *point)
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
matchedSeries := e.findMatchingSeries(query)
|
||
for _, seriesID := range matchedSeries {
|
||
point, err := e.queryLatestPoint(seriesID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if point != nil {
|
||
result.Points = append(result.Points, *point)
|
||
}
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// queryAggregate 实现聚合查询
|
||
func (e *FileEngine) queryAggregate(ctx context.Context, query engine.Query) (engine.QueryResult, error) {
|
||
result := &engine.AggregateResult{
|
||
SeriesID: query.SeriesID,
|
||
Groups: make([]engine.AggregateGroup, 0),
|
||
}
|
||
|
||
// 创建时间窗口
|
||
windows := splitTimeRange(query.StartTime, query.EndTime, query.AggInterval)
|
||
|
||
// 如果指定了SeriesID,只聚合特定序列
|
||
if query.SeriesID != "" {
|
||
groups, err := e.aggregateSeriesInWindows(query.SeriesID, windows, query.Aggregation)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
result.Groups = groups
|
||
return result, nil
|
||
}
|
||
|
||
// 否则聚合所有匹配的序列
|
||
matchedSeries := e.findMatchingSeries(query)
|
||
for _, window := range windows {
|
||
group := engine.AggregateGroup{
|
||
StartTime: window.start,
|
||
EndTime: window.end,
|
||
}
|
||
|
||
var totalSum float64
|
||
var totalCount int
|
||
|
||
for _, seriesID := range matchedSeries {
|
||
points, err := e.querySeriesPoints(seriesID, window.start, window.end)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
windowSum, windowCount := aggregatePoints(points, query.Aggregation)
|
||
totalSum += windowSum
|
||
totalCount += windowCount
|
||
}
|
||
|
||
if totalCount > 0 {
|
||
group.Value = calculateAggregateValue(totalSum, totalCount, query.Aggregation)
|
||
group.Count = totalCount
|
||
result.Groups = append(result.Groups, group)
|
||
}
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// 辅助方法
|
||
|
||
func (e *FileEngine) querySeriesPoints(seriesID string, startTime, endTime int64) ([]engine.DataPoint, error) {
|
||
e.timeIndex.mu.RLock()
|
||
windows := e.timeIndex.windows[seriesID]
|
||
e.timeIndex.mu.RUnlock()
|
||
|
||
var points []engine.DataPoint
|
||
for _, window := range windows {
|
||
if window.endTime < startTime || window.startTime > endTime {
|
||
continue
|
||
}
|
||
|
||
segment, ok := e.segments[window.segmentID]
|
||
if !ok {
|
||
continue
|
||
}
|
||
|
||
segmentPoints, err := e.readSegmentPoints(segment, window.offset, startTime, endTime)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
points = append(points, segmentPoints...)
|
||
}
|
||
|
||
return points, nil
|
||
}
|
||
|
||
func (e *FileEngine) queryLatestPoint(seriesID string) (*engine.DataPoint, error) {
|
||
e.timeIndex.mu.RLock()
|
||
windows := e.timeIndex.windows[seriesID]
|
||
e.timeIndex.mu.RUnlock()
|
||
|
||
if len(windows) == 0 {
|
||
return nil, nil
|
||
}
|
||
|
||
// 找到最新的时间窗口
|
||
var latestWindow timeWindow
|
||
for _, window := range windows {
|
||
if window.endTime > latestWindow.endTime {
|
||
latestWindow = window
|
||
}
|
||
}
|
||
|
||
segment, ok := e.segments[latestWindow.segmentID]
|
||
if !ok {
|
||
return nil, nil
|
||
}
|
||
|
||
points, err := e.readSegmentPoints(segment, latestWindow.offset, 0, time.Now().UnixNano())
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if len(points) == 0 {
|
||
return nil, nil
|
||
}
|
||
|
||
// 返回最新的点
|
||
latestPoint := points[len(points)-1]
|
||
return &latestPoint, nil
|
||
}
|
||
|
||
func (e *FileEngine) readSegmentPoints(segment *Segment, offset int64, startTime, endTime int64) ([]engine.DataPoint, error) {
|
||
segment.mu.RLock()
|
||
defer segment.mu.RUnlock()
|
||
|
||
var points []engine.DataPoint
|
||
|
||
// 移动到指定偏移
|
||
if _, err := segment.file.Seek(offset, io.SeekStart); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 读取数据点
|
||
for {
|
||
var timestamp int64
|
||
var value float64
|
||
|
||
err := binary.Read(segment.file, binary.BigEndian, ×tamp)
|
||
if err == io.EOF {
|
||
break
|
||
}
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
err = binary.Read(segment.file, binary.BigEndian, &value)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if timestamp >= startTime && timestamp <= endTime {
|
||
points = append(points, engine.DataPoint{
|
||
Timestamp: timestamp,
|
||
Value: value,
|
||
})
|
||
}
|
||
}
|
||
|
||
return points, nil
|
||
}
|
||
|
||
func (e *FileEngine) findMatchingSeries(query engine.Query) []string {
|
||
e.tagIndex.mu.RLock()
|
||
defer e.tagIndex.mu.RUnlock()
|
||
|
||
var matchedSeries []string
|
||
seriesMap := make(map[string]bool)
|
||
|
||
// 首先根据标签过滤器找到匹配的序列
|
||
for _, filter := range query.TagFilters {
|
||
if values, ok := e.tagIndex.index[filter.Key]; ok {
|
||
if series, ok := values[filter.Value]; ok {
|
||
for _, seriesID := range series {
|
||
seriesMap[seriesID] = true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 转换为切片
|
||
for seriesID := range seriesMap {
|
||
matchedSeries = append(matchedSeries, seriesID)
|
||
}
|
||
|
||
return matchedSeries
|
||
}
|
||
|
||
func (e *FileEngine) aggregateSeriesInWindows(seriesID string, windows []timeWindow, aggType engine.AggregationType) ([]engine.AggregateGroup, error) {
|
||
var groups []engine.AggregateGroup
|
||
|
||
for _, window := range windows {
|
||
points, err := e.querySeriesPoints(seriesID, window.start, window.end)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if len(points) > 0 {
|
||
sum, count := aggregatePoints(points, aggType)
|
||
groups = append(groups, engine.AggregateGroup{
|
||
StartTime: window.start,
|
||
EndTime: window.end,
|
||
Value: calculateAggregateValue(sum, count, aggType),
|
||
Count: count,
|
||
})
|
||
}
|
||
}
|
||
|
||
return groups, nil
|
||
}
|
||
|
||
func aggregatePoints(points []engine.DataPoint, aggType engine.AggregationType) (float64, int) {
|
||
if len(points) == 0 {
|
||
return 0, 0
|
||
}
|
||
|
||
var sum float64
|
||
for _, p := range points {
|
||
sum += p.Value
|
||
}
|
||
|
||
return sum, len(points)
|
||
}
|
||
|
||
func calculateAggregateValue(sum float64, count int, aggType engine.AggregationType) float64 {
|
||
if count == 0 {
|
||
return 0
|
||
}
|
||
|
||
switch aggType {
|
||
case engine.AggSum:
|
||
return sum
|
||
case engine.AggAvg:
|
||
return sum / float64(count)
|
||
case engine.AggCount:
|
||
return float64(count)
|
||
default:
|
||
return sum
|
||
}
|
||
}
|
||
|
||
func splitTimeRange(start, end int64, interval time.Duration) []timeWindow {
|
||
var windows []timeWindow
|
||
intervalNanos := interval.Nanoseconds()
|
||
|
||
for windowStart := start; windowStart < end; windowStart += intervalNanos {
|
||
windowEnd := windowStart + intervalNanos
|
||
if windowEnd > end {
|
||
windowEnd = end
|
||
}
|
||
windows = append(windows, timeWindow{
|
||
start: windowStart,
|
||
end: windowEnd,
|
||
})
|
||
}
|
||
|
||
return windows
|
||
}
|
||
|
||
// 错误处理辅助函数
|
||
func wrapError(op string, err error) error {
|
||
if err == nil {
|
||
return nil
|
||
}
|
||
return fmt.Errorf("%s: %v", op, err)
|
||
}
|