summaryrefslogtreecommitdiff
path: root/internal/statsengine/engine_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/statsengine/engine_test.go')
-rw-r--r--internal/statsengine/engine_test.go130
1 files changed, 130 insertions, 0 deletions
diff --git a/internal/statsengine/engine_test.go b/internal/statsengine/engine_test.go
new file mode 100644
index 0000000..943fe9c
--- /dev/null
+++ b/internal/statsengine/engine_test.go
@@ -0,0 +1,130 @@
+package statsengine
+
+import (
+ "ior/internal/event"
+ "ior/internal/file"
+ "ior/internal/types"
+ "math"
+ "testing"
+ "time"
+)
+
+type fakeClock struct {
+ now time.Time
+}
+
+func (c *fakeClock) Now() time.Time {
+ return c.now
+}
+
+func (c *fakeClock) Advance(d time.Duration) {
+ c.now = c.now.Add(d)
+}
+
+func TestEngineIngestAndSnapshotIntegration(t *testing.T) {
+ clock := &fakeClock{now: time.Unix(1000, 0)}
+ engine := newEngineWithClock(2, clock.Now)
+
+ engine.Ingest(newEnginePair(types.SYS_ENTER_READ, 100, types.READ_CLASSIFIED, "proc-a", 1, "/tmp/a", 100, 10, 3))
+ clock.Advance(500 * time.Millisecond)
+ engine.Ingest(newEnginePair(types.SYS_ENTER_WRITE, -1, types.WRITE_CLASSIFIED, "proc-a", 1, "/tmp/a", 50, 20, 5))
+ clock.Advance(500 * time.Millisecond)
+ engine.Ingest(newEnginePair(types.SYS_ENTER_COPY_FILE_RANGE, 80, types.TRANSFER_CLASSIFIED, "proc-b", 2, "/tmp/b", 20, 40, 8))
+ clock.Advance(1 * time.Second)
+
+ snap := engine.Snapshot()
+ if snap == nil {
+ t.Fatalf("expected snapshot")
+ }
+
+ if snap.TotalSyscalls != 3 || snap.TotalErrors != 1 || snap.TotalBytes != 170 {
+ t.Fatalf("unexpected totals: syscalls=%d errors=%d bytes=%d", snap.TotalSyscalls, snap.TotalErrors, snap.TotalBytes)
+ }
+ if snap.LatencyMeanNs != (10+20+40)/3.0 {
+ t.Fatalf("unexpected latency mean: %v", snap.LatencyMeanNs)
+ }
+ if snap.GapMeanNs != (3+5+8)/3.0 {
+ t.Fatalf("unexpected gap mean: %v", snap.GapMeanNs)
+ }
+
+ if math.Abs(snap.SyscallRatePerSec-1.5) > 1e-9 {
+ t.Fatalf("unexpected syscall rate: %v", snap.SyscallRatePerSec)
+ }
+ if math.Abs(snap.ErrorRatePerSec-0.5) > 1e-9 {
+ t.Fatalf("unexpected error rate: %v", snap.ErrorRatePerSec)
+ }
+ if math.Abs(snap.ReadBytesPerSec-60.0) > 1e-9 {
+ t.Fatalf("unexpected read bytes rate: %v", snap.ReadBytesPerSec)
+ }
+ if math.Abs(snap.WriteBytesPerSec-35.0) > 1e-9 {
+ t.Fatalf("unexpected write bytes rate: %v", snap.WriteBytesPerSec)
+ }
+
+ if len(snap.Syscalls()) != 3 {
+ t.Fatalf("expected 3 syscall rows, got %d", len(snap.Syscalls()))
+ }
+ if len(snap.Files()) != 2 {
+ t.Fatalf("expected top 2 files due to topN=2, got %d", len(snap.Files()))
+ }
+ if len(snap.Processes()) != 2 {
+ t.Fatalf("expected 2 process rows, got %d", len(snap.Processes()))
+ }
+ if snap.LatencyHistogram.Total != 3 || snap.GapHistogram.Total != 3 {
+ t.Fatalf("unexpected histogram totals: latency=%d gap=%d", snap.LatencyHistogram.Total, snap.GapHistogram.Total)
+ }
+}
+
+func TestEngineSnapshotWithNoEvents(t *testing.T) {
+ clock := &fakeClock{now: time.Unix(2000, 0)}
+ engine := newEngineWithClock(10, clock.Now)
+
+ snap := engine.Snapshot()
+ if snap == nil {
+ t.Fatalf("expected snapshot")
+ }
+ if snap.TotalSyscalls != 0 || snap.TotalErrors != 0 || snap.TotalBytes != 0 {
+ t.Fatalf("expected zero totals, got %+v", snap)
+ }
+ if len(snap.Syscalls()) != 0 || len(snap.Files()) != 0 || len(snap.Processes()) != 0 {
+ t.Fatalf("expected empty rows in zero snapshot")
+ }
+}
+
+func TestEngineTrendDetection(t *testing.T) {
+ if got := detectTrend(make([]float64, trendWindowSlots*2)); got.Direction != TrendStable {
+ t.Fatalf("expected stable for flat data, got %+v", got)
+ }
+
+ series := make([]float64, trendWindowSlots*2)
+ for i := 0; i < trendWindowSlots; i++ {
+ series[i] = 10
+ }
+ for i := trendWindowSlots; i < trendWindowSlots*2; i++ {
+ series[i] = 30
+ }
+ if got := detectTrend(series); got.Direction != TrendRising {
+ t.Fatalf("expected rising trend, got %+v", got)
+ }
+
+ for i := 0; i < trendWindowSlots; i++ {
+ series[i] = 40
+ }
+ for i := trendWindowSlots; i < trendWindowSlots*2; i++ {
+ series[i] = 10
+ }
+ if got := detectTrend(series); got.Direction != TrendFalling {
+ t.Fatalf("expected falling trend, got %+v", got)
+ }
+}
+
+func newEnginePair(traceID types.TraceId, ret int64, retType uint32, comm string, pid uint32, path string, bytes uint64, duration uint64, gap uint64) *event.Pair {
+ return &event.Pair{
+ EnterEv: &types.RetEvent{TraceId: traceID, Pid: pid},
+ ExitEv: &types.RetEvent{TraceId: traceID, Pid: pid, Ret: ret, RetType: retType},
+ Comm: comm,
+ Duration: duration,
+ DurationToPrev: gap,
+ Bytes: bytes,
+ File: file.NewFd(3, path, -1),
+ }
+}