diff --git a/misc/buildbot/bot.go b/misc/buildbot/bot.go index c3cb00d5e..a9879569c 100644 --- a/misc/buildbot/bot.go +++ b/misc/buildbot/bot.go @@ -40,9 +40,10 @@ import ( ) const ( - interval = 60 * time.Second // polling frequency - warmup = 60 * time.Second // duration before we test if devcam server has started properly - historySize = 30 + interval = 60 * time.Second // polling frequency + warmup = 60 * time.Second // duration before we test if devcam server has started properly + historySize = 30 + maxStderrSize = 1 << 20 // Keep last 1 MB of logging. ) var ( @@ -89,21 +90,67 @@ var ( // Override the os.Stderr used by the default logger so we can provide // more debug info on status page. - logStderr = new(lockedBuffer) + logStderr = newLockedBuffer() ) // lockedBuffer protects all Write calls with a mutex. Users of lockedBuffer // must wrap any calls to Bytes, and use of the resulting slice with calls to // Lock/Unlock. type lockedBuffer struct { - sync.Mutex // guards Buffer - bytes.Buffer + sync.Mutex // guards ringBuffer + *ringBuffer +} + +func newLockedBuffer() *lockedBuffer { + return &lockedBuffer{ringBuffer: newRingBuffer(maxStderrSize)} } func (lb *lockedBuffer) Write(b []byte) (int, error) { lb.Lock() defer lb.Unlock() - return lb.Buffer.Write(b) + return lb.ringBuffer.Write(b) +} + +type ringBuffer struct { + buf []byte + off int // End of ring buffer. + l int // Length of ring buffer filled. +} + +func newRingBuffer(maxSize int) *ringBuffer { + return &ringBuffer{ + buf: make([]byte, maxSize), + } +} + +func (rb *ringBuffer) Bytes() []byte { + if (rb.off - rb.l) >= 0 { + // Partially full buffer with no wrap. + return rb.buf[rb.off-rb.l : rb.off] + } + + // Buffer has been wrapped, copy second half then first half. + start := rb.off - rb.l + if start < 0 { + start = rb.off + } + b := make([]byte, 0, cap(rb.buf)) + b = append(b, rb.buf[start:]...) + b = append(b, rb.buf[:start]...) + return b +} + +func (rb *ringBuffer) Write(buf []byte) (int, error) { + ringLen := cap(rb.buf) + for i, b := range buf { + rb.buf[(rb.off+i)%ringLen] = b + } + rb.off = (rb.off + len(buf)) % ringLen + rb.l = rb.l + len(buf) + if rb.l > ringLen { + rb.l = ringLen + } + return len(buf), nil } var NameToCmd = map[string]string{ diff --git a/misc/buildbot/bot_test.go b/misc/buildbot/bot_test.go new file mode 100644 index 000000000..88ece7b3c --- /dev/null +++ b/misc/buildbot/bot_test.go @@ -0,0 +1,41 @@ +package main + +import ( + "reflect" + "testing" +) + +func TestRingBuffer(t *testing.T) { + rb := newRingBuffer(4) + data := []struct { + in, want string + }{ + {in: "a", want: "a"}, + {in: "b", want: "ab"}, + {in: "c", want: "abc"}, + {in: "d", want: "abcd"}, + {in: "e", want: "bcde"}, + {in: "f", want: "cdef"}, + // Multibyte writes that wrap the ring buffer. + {in: "ghi", want: "fghi"}, + {in: "jkl", want: "ijkl"}, + {in: "mno", want: "lmno"}, + // Write larger than ring buffer. + {in: "pqrstuv", want: "stuv"}, + } + for i, d := range data { + in := []byte(d.in) + n, err := rb.Write(in) + if err != nil { + t.Error(err) + } + if n != len(in) { + t.Error(i, "Wrote", n, "bytes, want", len(in)) + } + got := rb.Bytes() + want := []byte(d.want) + if !reflect.DeepEqual(want, got) { + t.Errorf("%d Got %q want %q", i, got, want) + } + } +}