Files
cabletest/loss.go
T

82 lines
1.6 KiB
Go

package main
import (
"math/bits"
"sync"
"sync/atomic"
)
const (
lossSlots = 1 << 16
lossWords = lossSlots / 64
)
type lossWindow struct {
mu sync.Mutex
base uint64
inited bool
bits []uint64
lost atomic.Uint64
late atomic.Uint64
_ [16]byte
}
func newLossWindows(n int) []lossWindow {
w := make([]lossWindow, n)
for i := range w {
w[i].bits = make([]uint64, lossWords)
}
return w
}
// A sequence number is only judged once it falls out of the window, so frames
// still queued in another rx worker are never miscounted as lost.
func (w *lossWindow) observe(seq uint64) {
w.mu.Lock()
if !w.inited {
// Start half a window below the first sequence seen, so frames another
// rx worker is still holding land inside the window rather than late.
if seq > lossSlots/2 {
w.base = seq - lossSlots/2
}
w.inited = true
}
if seq < w.base {
w.mu.Unlock()
w.late.Add(1)
return
}
if seq >= w.base+lossSlots {
w.evict(seq - lossSlots + 1)
}
idx := seq & (lossSlots - 1)
w.bits[idx>>6] |= 1 << (idx & 63)
w.mu.Unlock()
}
func (w *lossWindow) evict(newBase uint64) {
span := newBase - w.base
if span >= lossSlots {
var missing uint64
for i := range w.bits {
missing += uint64(64 - bits.OnesCount64(w.bits[i]))
w.bits[i] = 0
}
w.lost.Add(missing + span - lossSlots)
w.base = newBase
return
}
var missing uint64
for s := w.base; s < newBase; s++ {
idx := s & (lossSlots - 1)
word, bit := idx>>6, uint64(1)<<(idx&63)
if w.bits[word]&bit == 0 {
missing++
continue
}
w.bits[word] &^= bit
}
w.lost.Add(missing)
w.base = newBase
}