diff --git a/client/stats/ring.go b/client/stats/ring.go new file mode 100644 index 000000000..d20dd895c --- /dev/null +++ b/client/stats/ring.go @@ -0,0 +1,45 @@ +package stats + +import ( + "fmt" +) + +var ( + defaultCap = 60 +) + +type RingBuff struct { + head int + buff []interface{} +} + +func NewRingBuff(capacity int) (*RingBuff, error) { + if capacity < 1 { + return nil, fmt.Errorf("can not create a ring buffer with capacity: %v", capacity) + } + return &RingBuff{buff: make([]interface{}, capacity), head: -1}, nil +} + +func (r *RingBuff) Enqueue(value interface{}) { + r.head += 1 + if r.head == len(r.buff) { + r.head = 0 + } + r.buff[r.head] = value +} + +func (r *RingBuff) Peek() interface{} { + return r.buff[r.head] +} + +func (r *RingBuff) Values() []interface{} { + if r.head == len(r.buff)-1 { + vals := make([]interface{}, len(r.buff)) + copy(vals, r.buff) + return vals + } + + slice1 := r.buff[r.head+1:] + slice2 := r.buff[:r.head+1] + return append(slice1, slice2...) +} diff --git a/client/stats/ring_test.go b/client/stats/ring_test.go new file mode 100644 index 000000000..11e4028e2 --- /dev/null +++ b/client/stats/ring_test.go @@ -0,0 +1,83 @@ +package stats + +import ( + "testing" +) + +func TestRingBuffInvalid(t *testing.T) { + if _, err := NewRingBuff(0); err == nil { + t.Fatalf("expected err") + } +} + +func TestRingBuffEnqueue(t *testing.T) { + rb, err := NewRingBuff(3) + if err != nil { + t.Fatalf("err: %v", err) + } + + rb.Enqueue(1) + rb.Enqueue(2) + rb.Enqueue(3) + if val := rb.Peek(); val != 3 { + t.Fatalf("expected: %v, actual: %v", 3, val) + } + + rb.Enqueue(4) + rb.Enqueue(5) + if val := rb.Peek(); val != 5 { + t.Fatalf("expected: %v, actual: %v", 5, val) + } +} + +func TestRingBuffValues(t *testing.T) { + rb, err := NewRingBuff(3) + if err != nil { + t.Fatalf("err: %v", err) + } + rb.Enqueue(1) + rb.Enqueue(2) + rb.Enqueue(3) + rb.Enqueue(4) + + expected := []interface{}{2, 3, 4} + if !sliceEq(expected, rb.Values()) { + t.Fatalf("expected: %v, actual: %v", expected, rb.Values()) + } + + rb.Enqueue(5) + expected = []interface{}{3, 4, 5} + if !sliceEq(expected, rb.Values()) { + t.Fatalf("expected: %v, actual: %v", expected, rb.Values()) + } + + rb.Enqueue(6) + expected = []interface{}{4, 5, 6} + if !sliceEq(expected, rb.Values()) { + t.Fatalf("expected: %v, actual: %v", expected, rb.Values()) + } + +} + +func sliceEq(slice1, slice2 []interface{}) bool { + + if slice1 == nil && slice2 == nil { + return true + } + + if slice1 == nil || slice2 == nil { + return false + } + + if len(slice1) != len(slice2) { + return false + } + + for i := range slice1 { + if slice1[i] != slice2[i] { + return false + } + } + + return true +}