Files
yoloyolo/aggregate/window.go
T
gurix 2a8fd7dd05 feat(01-03): implement ticker-driven window aggregator with TDD
- Add Aggregate function reading ClassifiedPackets, emitting WindowSnapshots per time window
- Add DefaultWindowMs=500 constant for configurable window duration
- Flush partial window on done channel close or closed events channel
- Call optional onSnapshot callback per window for verbose mode
- Add 5 tests: EmitsSnapshot, MultipleClasses, DoneFlushesPartial, EmptyWindow, WindowIndex
2026-03-25 12:16:36 +01:00

64 lines
1.5 KiB
Go

package aggregate
import (
"time"
"github.com/netsynth/netsynth/classify"
)
// DefaultWindowMs is the default time window duration in milliseconds.
const DefaultWindowMs = 500
// Aggregate reads ClassifiedPackets from events, accumulates counts per TrafficClass
// in time windows of windowMs duration, and emits a WindowSnapshot per window.
// When done is closed, flushes the current partial window and closes the output channel.
// If onSnapshot is non-nil, it is called with each snapshot before sending to the output channel
// (used for --verbose stderr output).
func Aggregate(done <-chan struct{}, events <-chan classify.ClassifiedPacket,
windowMs int, onSnapshot func(classify.WindowSnapshot)) <-chan classify.WindowSnapshot {
out := make(chan classify.WindowSnapshot, 8)
go func() {
defer close(out)
ticker := time.NewTicker(time.Duration(windowMs) * time.Millisecond)
defer ticker.Stop()
counts := make(map[classify.TrafficClass]int64)
var totalPackets int64
windowIndex := 0
flush := func() {
snap := classify.WindowSnapshot{
Counts: counts,
TotalPackets: totalPackets,
WindowIndex: windowIndex,
}
if onSnapshot != nil {
onSnapshot(snap)
}
out <- snap
counts = make(map[classify.TrafficClass]int64)
totalPackets = 0
windowIndex++
}
for {
select {
case <-done:
flush() // flush final partial window
return
case <-ticker.C:
flush()
case ev, ok := <-events:
if !ok {
flush()
return
}
counts[ev.Class]++
totalPackets++
}
}
}()
return out
}