From f4e08c8ae3d99732245549a042ffd183ff604c97 Mon Sep 17 00:00:00 2001 From: Alireza Ahmadi Date: Sun, 8 Mar 2026 01:50:49 +0100 Subject: [PATCH] downsampling for traffic chart #987 --- service/stats.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/service/stats.go b/service/stats.go index 22faf79..15be555 100644 --- a/service/stats.go +++ b/service/stats.go @@ -1,6 +1,7 @@ package service import ( + "sort" "time" "github.com/alireza0/s-ui/database" @@ -93,9 +94,56 @@ func (s *StatsService) GetStats(resource string, tag string, limit int) ([]model if err != nil { return nil, err } + + result = s.downsampleStats(result, 60) // 60 rows for 30 buckets return result, nil } +// downsampleStats reduces stats to maxRows rows. +// Each bucket outputs two rows (direction false and true) with average Traffic. +func (s *StatsService) downsampleStats(stats []model.Stats, maxRows int) []model.Stats { + if len(stats) <= maxRows { + return stats + } + numBuckets := int(maxRows / 2) + sort.Slice(stats, func(i, j int) bool { return stats[i].DateTime < stats[j].DateTime }) + timeMin, timeMax := stats[0].DateTime, stats[len(stats)-1].DateTime + bucketSpan := (timeMax - timeMin) / int64(numBuckets) + if bucketSpan == 0 { + bucketSpan = 1 + } + downsampled := make([]model.Stats, 0, maxRows) + for i := 0; i < numBuckets; i++ { + bucketStart := timeMin + int64(i)*bucketSpan + bucketEnd := timeMin + int64(i+1)*bucketSpan + if i == numBuckets-1 { + bucketEnd = timeMax + 1 + } + for _, dir := range []bool{false, true} { + var sum int64 + var count int + for _, r := range stats { + if r.DateTime >= bucketStart && r.DateTime < bucketEnd && r.Direction == dir { + sum += r.Traffic + count++ + } + } + avg := int64(0) + if count > 0 { + avg = sum / int64(count) + } + downsampled = append(downsampled, model.Stats{ + DateTime: bucketStart, + Resource: stats[0].Resource, + Tag: stats[0].Tag, + Direction: dir, + Traffic: avg, + }) + } + } + return downsampled +} + func (s *StatsService) GetOnlines() (onlines, error) { return *onlineResources, nil }