diff --git a/go.mod b/go.mod index 48c0480f..513c8af0 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( git.sr.ht/~nelsam/hel v0.4.6 github.com/adrg/xdg v0.4.0 github.com/alessio/shellescape v1.4.1 - github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 github.com/aws/aws-sdk-go-v2 v1.17.6 github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20230227212328-9f4511cd144a github.com/containerd/containerd v1.7.7 diff --git a/go.sum b/go.sum index 290cd0e6..e1df0bde 100644 --- a/go.sum +++ b/go.sum @@ -64,8 +64,6 @@ github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230219212500-1f9a474cc2dc h1:ikxgKfnYm4kXCOohe1uCkVFwZcABDZbVsqginko+GY8= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230219212500-1f9a474cc2dc/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= -github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloDxZfhMm0xrLXZS8+COSu2bXmEQs= -github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/aws/aws-sdk-go-v2 v1.17.4/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2 v1.17.5/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2 v1.17.6 h1:Y773UK7OBqhzi5VDXMi1zVGsoj+CVHs2eaC2bDsLwi0= diff --git a/logbus/command.go b/logbus/command.go index d4a4b49e..2afdda21 100644 --- a/logbus/command.go +++ b/logbus/command.go @@ -5,8 +5,8 @@ import ( "sync/atomic" "time" - "github.com/armon/circbuf" "github.com/earthly/cloud-api/logstream" + "github.com/earthly/earthly/util/circbuf" "github.com/pkg/errors" ) diff --git a/outmon/vertexmon.go b/outmon/vertexmon.go index 4943541b..106805ef 100644 --- a/outmon/vertexmon.go +++ b/outmon/vertexmon.go @@ -10,8 +10,8 @@ import ( "strings" "time" - "github.com/armon/circbuf" "github.com/earthly/earthly/conslogging" + "github.com/earthly/earthly/util/circbuf" "github.com/earthly/earthly/util/progressbar" "github.com/earthly/earthly/util/vertexmeta" "github.com/mattn/go-isatty" diff --git a/util/circbuf/circbuf.go b/util/circbuf/circbuf.go new file mode 100644 index 00000000..cd2873e4 --- /dev/null +++ b/util/circbuf/circbuf.go @@ -0,0 +1,75 @@ +package circbuf + +import "errors" + +// Buffer represents a dynamic circular (ring) buffer. It will append data +// freely to the underlying buffer until maxSize is hit. Once hit, new writes +// will be written in a circular fashion. +type Buffer struct { + data []byte + offset int + maxSize int + written int +} + +// NewBuffer creates and returns a Buffer pointer with a max size. +func NewBuffer(maxSize int) (*Buffer, error) { + if maxSize < 0 { + return nil, errors.New("size must be a positive int") + } + return &Buffer{ + maxSize: int(maxSize), + }, nil +} + +// Write implements io.Writer. +func (c *Buffer) Write(buf []byte) (int, error) { + l := len(c.data) + n := len(buf) + c.written += n + + if n > c.maxSize { + buf = buf[n-c.maxSize:] + } + + if l < c.maxSize { + r := c.maxSize - l + if n > r { + c.data = append(c.data, buf[:r]...) + buf = buf[r:] + c.offset = 0 + } else { + c.data = append(c.data, buf...) + c.offset = n + return n, nil + } + } + + remain := c.maxSize - c.offset + copy(c.data[c.offset:], buf) + if len(buf) > remain { + copy(c.data, buf[remain:]) + } + + c.offset = (c.offset + len(buf)) % c.maxSize + + return n, nil +} + +func (c *Buffer) Bytes() []byte { + if len(c.data) < c.maxSize { + return c.data + } + ret := make([]byte, c.maxSize) + copy(ret, c.data[c.offset:]) + copy(ret[c.maxSize-c.offset:], c.data[:c.offset]) + return ret +} + +func (c *Buffer) Size() int { + return len(c.data) +} + +func (c *Buffer) TotalWritten() int { + return c.written +} diff --git a/util/circbuf/circbuf_test.go b/util/circbuf/circbuf_test.go new file mode 100644 index 00000000..cb3aff86 --- /dev/null +++ b/util/circbuf/circbuf_test.go @@ -0,0 +1,56 @@ +package circbuf + +import ( + "io" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewError(t *testing.T) { + _, err := NewBuffer(-1) + require.Error(t, err) +} + +func TestWriteGrow(t *testing.T) { + b := &Buffer{maxSize: 5} + n, err := io.WriteString(b, "foo") + r := require.New(t) + r.NoError(err) + r.Equal(3, n) + r.Len(b.data, 3) + r.Equal("foo", string(b.Bytes())) +} + +func TestWriteOverflow(t *testing.T) { + b := &Buffer{maxSize: 5} + n, err := io.WriteString(b, "foobarbaz") + + r := require.New(t) + r.NoError(err) + r.Equal(9, n) + r.Equal("arbaz", string(b.data)) + r.Equal("arbaz", string(b.Bytes())) +} + +func TestWriteMulti(t *testing.T) { + b := &Buffer{maxSize: 5} + r := require.New(t) + + n, err := io.WriteString(b, "mr") + r.NoError(err) + r.Equal(2, n) + + n, err = io.WriteString(b, "world") + r.NoError(err) + r.Equal(5, n) + + n, err = io.WriteString(b, "wide") + r.NoError(err) + r.Equal(4, n) + + r.Equal("edwid", string(b.data)) + r.Equal(1, b.offset) + + r.Equal("dwide", string(b.Bytes())) +}