github.com/waldiirawan/apm-agent-go/v2@v2.2.2/internal/ringbuffer/buffer.go (about)

     1  // Licensed to Elasticsearch B.V. under one or more contributor
     2  // license agreements. See the NOTICE file distributed with
     3  // this work for additional information regarding copyright
     4  // ownership. Elasticsearch B.V. licenses this file to you under
     5  // the Apache License, Version 2.0 (the "License"); you may
     6  // not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package ringbuffer
    19  
    20  import (
    21  	"bytes"
    22  	"encoding/binary"
    23  	"io"
    24  	"io/ioutil"
    25  )
    26  
    27  // BlockHeaderSize is the size of the block header, in bytes.
    28  const BlockHeaderSize = 5
    29  
    30  // BlockTag is a block tag, which can be used for classification.
    31  type BlockTag uint8
    32  
    33  // BlockHeader holds a fixed-size block header.
    34  type BlockHeader struct {
    35  	// Tag is the block's tag.
    36  	Tag BlockTag
    37  
    38  	// Size is the size of the block data, in bytes.
    39  	Size uint32
    40  }
    41  
    42  // Buffer is a ring buffer of byte blocks.
    43  type Buffer struct {
    44  	buf       []byte
    45  	headerbuf [BlockHeaderSize]byte
    46  	len       int
    47  	write     int
    48  	read      int
    49  
    50  	// Evicted will be called when an old block is evicted to make place for a new one.
    51  	Evicted func(BlockHeader)
    52  }
    53  
    54  // New returns a new Buffer with the given size in bytes.
    55  func New(size int) *Buffer {
    56  	return &Buffer{
    57  		buf:     make([]byte, size),
    58  		Evicted: func(BlockHeader) {},
    59  	}
    60  }
    61  
    62  // Len returns the number of bytes currently in the buffer, including
    63  // block-accounting bytes.
    64  func (b *Buffer) Len() int {
    65  	return b.len
    66  }
    67  
    68  // Cap returns the capacity of the buffer.
    69  func (b *Buffer) Cap() int {
    70  	return len(b.buf)
    71  }
    72  
    73  // WriteBlockTo writes the oldest block in b to w, returning the block header and the number of bytes written to w.
    74  func (b *Buffer) WriteBlockTo(w io.Writer) (header BlockHeader, written int64, err error) {
    75  	if b.len == 0 {
    76  		return header, 0, io.EOF
    77  	}
    78  	if n := copy(b.headerbuf[:], b.buf[b.read:]); n < len(b.headerbuf) {
    79  		b.read = copy(b.headerbuf[n:], b.buf[:])
    80  	} else {
    81  		b.read = (b.read + n) % b.Cap()
    82  	}
    83  	b.len -= len(b.headerbuf)
    84  	header.Tag = BlockTag(b.headerbuf[0])
    85  	header.Size = binary.LittleEndian.Uint32(b.headerbuf[1:])
    86  	size := int(header.Size)
    87  
    88  	if b.read+size > b.Cap() {
    89  		tail := b.buf[b.read:]
    90  		n, err := w.Write(tail)
    91  		if err != nil {
    92  			b.read = (b.read + size) % b.Cap()
    93  			b.len -= size + len(b.headerbuf)
    94  			return header, int64(n), err
    95  		}
    96  		size -= n
    97  		written = int64(n)
    98  		b.read = 0
    99  		b.len -= n
   100  	}
   101  	n, err := w.Write(b.buf[b.read : b.read+size])
   102  	if err != nil {
   103  		return header, written + int64(n), err
   104  	}
   105  	written += int64(n)
   106  	b.read = (b.read + size) % b.Cap()
   107  	b.len -= size
   108  	return header, written, nil
   109  }
   110  
   111  // WriteBlock writes p as a block to b, with tag t.
   112  //
   113  // If len(p)+BlockHeaderSize > b.Cap(), bytes.ErrTooLarge will be returned.
   114  // If the buffer does not currently have room for the block, then the
   115  // oldest blocks will be evicted until enough room is available.
   116  func (b *Buffer) WriteBlock(p []byte, tag BlockTag) (int, error) {
   117  	lenp := len(p)
   118  	if lenp+BlockHeaderSize > b.Cap() {
   119  		return 0, bytes.ErrTooLarge
   120  	}
   121  	for lenp+BlockHeaderSize > b.Cap()-b.Len() {
   122  		header, _, err := b.WriteBlockTo(ioutil.Discard)
   123  		if err != nil {
   124  			return 0, err
   125  		}
   126  		b.Evicted(header)
   127  	}
   128  	b.headerbuf[0] = uint8(tag)
   129  	binary.LittleEndian.PutUint32(b.headerbuf[1:], uint32(lenp))
   130  	if n := copy(b.buf[b.write:], b.headerbuf[:]); n < len(b.headerbuf) {
   131  		b.write = copy(b.buf, b.headerbuf[n:])
   132  	} else {
   133  		b.write = (b.write + n) % b.Cap()
   134  	}
   135  	if n := copy(b.buf[b.write:], p); n < lenp {
   136  		b.write = copy(b.buf, p[n:])
   137  	} else {
   138  		b.write = (b.write + n) % b.Cap()
   139  	}
   140  	b.len += lenp + BlockHeaderSize
   141  	return lenp, nil
   142  }