package main import ( "testing" "time" ) // A frame lands in the bucket its receive stamp falls in, whenever the worker // got round to draining it. func TestRxObserveBucketsByStamp(t *testing.T) { var s rxStats run := rateRun{stats: &s} run.add(3*int64(time.Millisecond), 100) run.add(5*int64(time.Millisecond), 200) run.add(rateBucketNs+int64(time.Millisecond), 300) run.flush() if f, b := s.bucket(0); f != 2 || b != 300 { t.Errorf("epoch 0 = %d frames, %d bytes; want 2, 300", f, b) } if f, b := s.bucket(1); f != 1 || b != 300 { t.Errorf("epoch 1 = %d frames, %d bytes; want 1, 300", f, b) } if got := s.newest.Load(); got != 1 { t.Errorf("newest = %d, want 1", got) } } // A slot coming round again belongs to its new epoch, and the epoch it replaced // reports nothing rather than the stale counts. func TestRxBucketWraps(t *testing.T) { var s rxStats run := rateRun{stats: &s} run.add(1, 100) run.add(rateBuckets*rateBucketNs+1, 200) run.flush() if f, b := s.bucket(0); f != 0 || b != 0 { t.Errorf("evicted epoch = %d frames, %d bytes; want 0, 0", f, b) } if f, b := s.bucket(rateBuckets); f != 1 || b != 200 { t.Errorf("new epoch = %d frames, %d bytes; want 1, 200", f, b) } } // The newest epoch may still be filling, so the rate comes from the one before. func TestDirectionRateReadsOneBucketBack(t *testing.T) { d := &direction{rxStats: []*rxStats{{}, {}}} r0 := rateRun{stats: d.rxStats[0]} r1 := rateRun{stats: d.rxStats[1]} r0.add(rateBucketNs+1, 500) r1.add(rateBucketNs+2, 700) r0.add(2*rateBucketNs+1, 900) r0.flush() r1.flush() d.readRateBucket(time.Now()) if d.rateFrames != 2 || d.rateBytes != 1200 { t.Errorf("rate bucket = %d frames, %d bytes; want the completed epoch, 2 and 1200", d.rateFrames, d.rateBytes) } } // A stamp only advances when a frame arrives, so an epoch that stops moving is // a quiet wire and must not keep reporting the last bucket. func TestDirectionRateGoesStale(t *testing.T) { d := &direction{rxStats: []*rxStats{{}}} run := rateRun{stats: d.rxStats[0]} run.add(rateBucketNs+1, 500) run.add(2*rateBucketNs+1, 900) run.flush() now := time.Now() d.readRateBucket(now) if d.rateFrames == 0 { t.Fatal("expected a rate while the epoch was still moving") } d.readRateBucket(now.Add(2 * rateStale)) if d.rateFrames != 0 || d.rateBytes != 0 { t.Errorf("stale rate = %d frames, %d bytes; want 0, 0", d.rateFrames, d.rateBytes) } } // Indexed oldest first, so the error window still spans the whole ring once it // has wrapped. func TestRateWindowIndexesOldestFirst(t *testing.T) { w := newRateWindow(3) for i := 1; i <= 5; i++ { w.push(counterSet{s: sample{rxFrames: uint64(i)}}) } if got := w.count(); got != 3 { t.Fatalf("count = %d, want 3", got) } if got := w.at(0).s.rxFrames; got != 3 { t.Errorf("oldest = %d, want 3", got) } if got := w.at(2).s.rxFrames; got != 5 { t.Errorf("newest = %d, want 5", got) } } // Which counter feeds which bucket is the whole taxonomy the panel reports, so // it is pinned here rather than left to whoever reads errsBetween next. func TestErrsBetweenBuckets(t *testing.T) { n := counterSet{ s: sample{ lost: 1, late: 7, crcErr: 2, badMagic: 3, badLen: 4, badHdr: 10, txErrs: 6, rxErrs: 5, }, drops: 9, nic: 8, } got := errsBetween(counterSet{}, n) want := errs{lost: 1, corrupt: 2 + 3 + 4 + 10, link: 8 + 5, internal: 9 + 7 + 6} if got != want { t.Errorf("errsBetween = %+v, want %+v", got, want) } if got.total() != 55 { t.Errorf("total = %d, want 55", got.total()) } } // A reset re-bases from a fresh capture while the ring still holds buckets from // just before it, so the newest bucket must not be left behind the new origin. func TestResetDoesNotUnderflowTotals(t *testing.T) { d := &direction{ win: newRateWindow(8), cable: newCableStats(), rxStats: []*rxStats{{}}, } d.rxStats[0].frames.Store(100) d.rxStats[0].bytes.Store(6400) d.sample() d.rxStats[0].frames.Store(150) d.rxStats[0].bytes.Store(9600) d.reset() v := d.displayView() if v.rxFrames != 0 { t.Errorf("rxFrames = %d, want 0", v.rxFrames) } if v.rxBytes != 0 { t.Errorf("rxBytes = %d, want 0", v.rxBytes) } // The rolling window and the rates are about now rather than since the // reset, so the buckets from before it have to survive. if got := d.win.count(); got < 2 { t.Errorf("ring holds %d buckets after reset, want the pre-reset history kept", got) } }