refactor: rework limiter, traffic accounting, and logging (nlog)
This commit is contained in:
@@ -2,24 +2,26 @@ package tracker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cedar2025/xboard-node/internal/kernel"
|
||||
)
|
||||
|
||||
func TestProcess_InitialTraffic(t *testing.T) {
|
||||
tr := New()
|
||||
conns := []kernel.Connection{
|
||||
{ID: "c1", UserID: 1, Upload: 100, Download: 200, SourceIP: "1.1.1.1"},
|
||||
{ID: "c2", UserID: 2, Upload: 50, Download: 80, SourceIP: "2.2.2.2"},
|
||||
cumTraffic := map[int][2]int64{
|
||||
1: {100, 200},
|
||||
2: {50, 80},
|
||||
}
|
||||
|
||||
interval := tr.Process(conns)
|
||||
|
||||
if interval[1] != [2]int64{100, 200} {
|
||||
t.Errorf("user 1 interval: got %v", interval[1])
|
||||
aliveIPs := map[int]map[string]bool{
|
||||
1: {"1.1.1.1": true},
|
||||
2: {"2.2.2.2": true},
|
||||
}
|
||||
if interval[2] != [2]int64{50, 80} {
|
||||
t.Errorf("user 2 interval: got %v", interval[2])
|
||||
tr.Process(cumTraffic, aliveIPs, 2)
|
||||
|
||||
flushed := tr.FlushTraffic()
|
||||
if flushed[1] != [2]int64{100, 200} {
|
||||
t.Errorf("user 1 traffic: got %v", flushed[1])
|
||||
}
|
||||
if flushed[2] != [2]int64{50, 80} {
|
||||
t.Errorf("user 2 traffic: got %v", flushed[2])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,18 +29,15 @@ func TestProcess_DeltaCalculation(t *testing.T) {
|
||||
tr := New()
|
||||
|
||||
// First tick
|
||||
conns := []kernel.Connection{
|
||||
{ID: "c1", UserID: 1, Upload: 100, Download: 200},
|
||||
}
|
||||
tr.Process(conns)
|
||||
tr.Process(map[int][2]int64{1: {100, 200}}, nil, 1)
|
||||
|
||||
// Second tick — counters advanced
|
||||
conns[0].Upload = 300
|
||||
conns[0].Download = 500
|
||||
interval := tr.Process(conns)
|
||||
tr.Process(map[int][2]int64{1: {300, 500}}, nil, 1)
|
||||
|
||||
if interval[1] != [2]int64{200, 300} {
|
||||
t.Errorf("delta: got %v, want [200,300]", interval[1])
|
||||
flushed := tr.FlushTraffic()
|
||||
// Total: first tick 100/200 + delta 200/300 = 300/500
|
||||
if flushed[1] != [2]int64{300, 500} {
|
||||
t.Errorf("accumulated: got %v, want [300,500]", flushed[1])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,73 +45,52 @@ func TestProcess_CounterReset(t *testing.T) {
|
||||
tr := New()
|
||||
|
||||
// First tick with high counters
|
||||
conns := []kernel.Connection{
|
||||
{ID: "c1", UserID: 1, Upload: 1000, Download: 2000},
|
||||
}
|
||||
tr.Process(conns)
|
||||
tr.Process(map[int][2]int64{1: {1000, 2000}}, nil, 1)
|
||||
tr.FlushTraffic() // clear accumulator
|
||||
|
||||
// Counter reset (new value < old value) — treat as fresh
|
||||
conns[0].Upload = 50
|
||||
conns[0].Download = 80
|
||||
interval := tr.Process(conns)
|
||||
tr.Process(map[int][2]int64{1: {50, 80}}, nil, 1)
|
||||
|
||||
if interval[1] != [2]int64{50, 80} {
|
||||
t.Errorf("counter reset: got %v, want [50,80]", interval[1])
|
||||
flushed := tr.FlushTraffic()
|
||||
if flushed[1] != [2]int64{50, 80} {
|
||||
t.Errorf("counter reset: got %v, want [50,80]", flushed[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcess_ConnectionCleanup(t *testing.T) {
|
||||
func TestProcess_UserDisappears(t *testing.T) {
|
||||
tr := New()
|
||||
|
||||
// Tick 1: connection exists
|
||||
tr.Process([]kernel.Connection{
|
||||
{ID: "c1", UserID: 1, Upload: 100, Download: 200},
|
||||
})
|
||||
// Tick 1: user present
|
||||
tr.Process(map[int][2]int64{1: {100, 200}}, nil, 1)
|
||||
|
||||
// Tick 2: connection gone — should clean up state
|
||||
tr.Process([]kernel.Connection{})
|
||||
// Tick 2: user gone
|
||||
tr.Process(map[int][2]int64{}, nil, 0)
|
||||
|
||||
// Tick 3: same conn ID reappears — should count full bytes (not delta)
|
||||
interval := tr.Process([]kernel.Connection{
|
||||
{ID: "c1", UserID: 1, Upload: 50, Download: 30},
|
||||
})
|
||||
// Tick 3: user reappears — should count full bytes (treated as new)
|
||||
tr.Process(map[int][2]int64{1: {50, 30}}, nil, 1)
|
||||
|
||||
if interval[1] != [2]int64{50, 30} {
|
||||
t.Errorf("after cleanup: got %v, want [50,30]", interval[1])
|
||||
flushed := tr.FlushTraffic()
|
||||
// First tick: 100/200, tick 2: 0, tick 3: 50/30 = total 150/230
|
||||
if flushed[1] != [2]int64{150, 230} {
|
||||
t.Errorf("after disappear: got %v, want [150,230]", flushed[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcess_ZeroUserID(t *testing.T) {
|
||||
func TestProcess_ZeroTraffic(t *testing.T) {
|
||||
tr := New()
|
||||
interval := tr.Process([]kernel.Connection{
|
||||
{ID: "c1", UserID: 0, Upload: 100, Download: 200},
|
||||
})
|
||||
tr.Process(map[int][2]int64{}, nil, 0)
|
||||
|
||||
if len(interval) != 0 {
|
||||
t.Errorf("expected no interval traffic for userID=0, got %v", interval)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcess_MultipleConnectionsSameUser(t *testing.T) {
|
||||
tr := New()
|
||||
conns := []kernel.Connection{
|
||||
{ID: "c1", UserID: 1, Upload: 100, Download: 200},
|
||||
{ID: "c2", UserID: 1, Upload: 50, Download: 80},
|
||||
}
|
||||
|
||||
interval := tr.Process(conns)
|
||||
|
||||
if interval[1] != [2]int64{150, 280} {
|
||||
t.Errorf("aggregated: got %v, want [150,280]", interval[1])
|
||||
if tr.HasTraffic() {
|
||||
t.Error("expected no traffic for empty input")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlushTraffic(t *testing.T) {
|
||||
tr := New()
|
||||
tr.Process([]kernel.Connection{
|
||||
{ID: "c1", UserID: 1, Upload: 100, Download: 200},
|
||||
{ID: "c2", UserID: 2, Upload: 50, Download: 80},
|
||||
})
|
||||
tr.Process(map[int][2]int64{
|
||||
1: {100, 200},
|
||||
2: {50, 80},
|
||||
}, nil, 2)
|
||||
|
||||
flushed := tr.FlushTraffic()
|
||||
if flushed[1] != [2]int64{100, 200} {
|
||||
@@ -134,15 +112,9 @@ func TestFlushTraffic(t *testing.T) {
|
||||
|
||||
func TestRestoreTraffic(t *testing.T) {
|
||||
tr := New()
|
||||
|
||||
// Generate some traffic
|
||||
tr.Process([]kernel.Connection{
|
||||
{ID: "c1", UserID: 1, Upload: 100, Download: 200},
|
||||
})
|
||||
tr.Process(map[int][2]int64{1: {100, 200}}, nil, 1)
|
||||
|
||||
flushed := tr.FlushTraffic()
|
||||
|
||||
// Restore (simulating failed push)
|
||||
tr.RestoreTraffic(flushed)
|
||||
|
||||
if !tr.HasTraffic() {
|
||||
@@ -158,27 +130,19 @@ func TestRestoreTraffic(t *testing.T) {
|
||||
func TestRestoreTraffic_Additive(t *testing.T) {
|
||||
tr := New()
|
||||
|
||||
// Generate traffic
|
||||
tr.Process([]kernel.Connection{
|
||||
{ID: "c1", UserID: 1, Upload: 100, Download: 200},
|
||||
})
|
||||
|
||||
// Generate more traffic
|
||||
tr.Process([]kernel.Connection{
|
||||
{ID: "c1", UserID: 1, Upload: 200, Download: 400},
|
||||
})
|
||||
// Two ticks accumulate
|
||||
tr.Process(map[int][2]int64{1: {100, 200}}, nil, 1)
|
||||
tr.Process(map[int][2]int64{1: {200, 400}}, nil, 1)
|
||||
|
||||
// Flush and restore
|
||||
first := tr.FlushTraffic()
|
||||
tr.RestoreTraffic(first)
|
||||
|
||||
// Generate additional traffic
|
||||
tr.Process([]kernel.Connection{
|
||||
{ID: "c1", UserID: 1, Upload: 250, Download: 500},
|
||||
})
|
||||
tr.Process(map[int][2]int64{1: {250, 500}}, nil, 1)
|
||||
|
||||
final := tr.FlushTraffic()
|
||||
// Should be restored(200,400) + delta(50,100) = (250,500)
|
||||
// Restored: 200/400 (initial 100/200 + delta 100/200) + new delta 50/100 = 250/500
|
||||
if final[1][0] != 250 || final[1][1] != 500 {
|
||||
t.Errorf("additive restore: got %v", final[1])
|
||||
}
|
||||
@@ -186,37 +150,37 @@ func TestRestoreTraffic_Additive(t *testing.T) {
|
||||
|
||||
func TestFlushAliveIPs(t *testing.T) {
|
||||
tr := New()
|
||||
tr.Process([]kernel.Connection{
|
||||
{ID: "c1", UserID: 1, Upload: 100, Download: 200, SourceIP: "1.1.1.1"},
|
||||
{ID: "c2", UserID: 1, Upload: 50, Download: 80, SourceIP: "2.2.2.2"},
|
||||
{ID: "c3", UserID: 2, Upload: 30, Download: 40, SourceIP: "3.3.3.3"},
|
||||
})
|
||||
|
||||
aliveIPs := tr.FlushAliveIPs()
|
||||
if len(aliveIPs[1]) != 2 {
|
||||
t.Errorf("user 1 IPs: got %d, want 2", len(aliveIPs[1]))
|
||||
aliveIPs := map[int]map[string]bool{
|
||||
1: {"1.1.1.1": true, "2.2.2.2": true},
|
||||
2: {"3.3.3.3": true},
|
||||
}
|
||||
if len(aliveIPs[2]) != 1 {
|
||||
t.Errorf("user 2 IPs: got %d, want 1", len(aliveIPs[2]))
|
||||
tr.Process(map[int][2]int64{1: {100, 200}, 2: {30, 40}}, aliveIPs, 3)
|
||||
|
||||
flushed := tr.FlushAliveIPs()
|
||||
if len(flushed[1]) != 2 {
|
||||
t.Errorf("user 1 IPs: got %d, want 2", len(flushed[1]))
|
||||
}
|
||||
if len(flushed[2]) != 1 {
|
||||
t.Errorf("user 2 IPs: got %d, want 1", len(flushed[2]))
|
||||
}
|
||||
|
||||
// After flush, should be empty
|
||||
aliveIPs2 := tr.FlushAliveIPs()
|
||||
if len(aliveIPs2) != 0 {
|
||||
t.Errorf("expected empty after second flush, got %v", aliveIPs2)
|
||||
// Without another Process call, aliveIPs still reflect last known state
|
||||
flushed2 := tr.FlushAliveIPs()
|
||||
if len(flushed2[1]) != 2 {
|
||||
t.Errorf("expected same IPs without new Process, got %v", flushed2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlushAliveIPs_DedupSameIP(t *testing.T) {
|
||||
tr := New()
|
||||
tr.Process([]kernel.Connection{
|
||||
{ID: "c1", UserID: 1, Upload: 100, Download: 200, SourceIP: "1.1.1.1"},
|
||||
{ID: "c2", UserID: 1, Upload: 50, Download: 80, SourceIP: "1.1.1.1"},
|
||||
})
|
||||
aliveIPs := map[int]map[string]bool{
|
||||
1: {"1.1.1.1": true},
|
||||
}
|
||||
tr.Process(map[int][2]int64{1: {100, 200}}, aliveIPs, 2)
|
||||
|
||||
aliveIPs := tr.FlushAliveIPs()
|
||||
if len(aliveIPs[1]) != 1 {
|
||||
t.Errorf("expected dedup to 1 IP, got %d", len(aliveIPs[1]))
|
||||
flushed := tr.FlushAliveIPs()
|
||||
if len(flushed[1]) != 1 {
|
||||
t.Errorf("expected 1 IP, got %d", len(flushed[1]))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,9 +190,7 @@ func TestHasTraffic(t *testing.T) {
|
||||
t.Error("new tracker should not have traffic")
|
||||
}
|
||||
|
||||
tr.Process([]kernel.Connection{
|
||||
{ID: "c1", UserID: 1, Upload: 100, Download: 200},
|
||||
})
|
||||
tr.Process(map[int][2]int64{1: {100, 200}}, nil, 1)
|
||||
if !tr.HasTraffic() {
|
||||
t.Error("should have traffic after process")
|
||||
}
|
||||
@@ -242,15 +204,13 @@ func TestHasTraffic(t *testing.T) {
|
||||
func TestProcess_NoTrafficDelta(t *testing.T) {
|
||||
tr := New()
|
||||
|
||||
conns := []kernel.Connection{
|
||||
{ID: "c1", UserID: 1, Upload: 100, Download: 200},
|
||||
}
|
||||
tr.Process(conns)
|
||||
tr.Process(map[int][2]int64{1: {100, 200}}, nil, 1)
|
||||
tr.FlushTraffic() // clear
|
||||
|
||||
// Same values — no delta
|
||||
interval := tr.Process(conns)
|
||||
if len(interval) != 0 {
|
||||
t.Errorf("expected no interval traffic for zero delta, got %v", interval)
|
||||
tr.Process(map[int][2]int64{1: {100, 200}}, nil, 1)
|
||||
if tr.HasTraffic() {
|
||||
t.Error("expected no traffic for zero delta")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,17 +218,11 @@ func TestTrafficAccumulation(t *testing.T) {
|
||||
tr := New()
|
||||
|
||||
// Tick 1
|
||||
tr.Process([]kernel.Connection{
|
||||
{ID: "c1", UserID: 1, Upload: 100, Download: 200},
|
||||
})
|
||||
// Tick 2
|
||||
tr.Process([]kernel.Connection{
|
||||
{ID: "c1", UserID: 1, Upload: 300, Download: 500},
|
||||
})
|
||||
// Tick 3
|
||||
tr.Process([]kernel.Connection{
|
||||
{ID: "c1", UserID: 1, Upload: 350, Download: 600},
|
||||
})
|
||||
tr.Process(map[int][2]int64{1: {100, 200}}, nil, 1)
|
||||
// Tick 2 (cumulative 300, 500 → delta 200, 300)
|
||||
tr.Process(map[int][2]int64{1: {300, 500}}, nil, 1)
|
||||
// Tick 3 (cumulative 350, 600 → delta 50, 100)
|
||||
tr.Process(map[int][2]int64{1: {350, 600}}, nil, 1)
|
||||
|
||||
flushed := tr.FlushTraffic()
|
||||
// Total: 100+200+50 = 350 upload, 200+300+100 = 600 download
|
||||
|
||||
Reference in New Issue
Block a user