12128189eb
full diff: https://github.com/cilium/ebpf/compare/v0.9.1...v0.11.0 Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
128 lines
3.3 KiB
Go
128 lines
3.3 KiB
Go
package internal
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
)
|
|
|
|
// NewBufferedSectionReader wraps an io.ReaderAt in an appropriately-sized
|
|
// buffered reader. It is a convenience function for reading subsections of
|
|
// ELF sections while minimizing the amount of read() syscalls made.
|
|
//
|
|
// Syscall overhead is non-negligible in continuous integration context
|
|
// where ELFs might be accessed over virtual filesystems with poor random
|
|
// access performance. Buffering reads makes sense because (sub)sections
|
|
// end up being read completely anyway.
|
|
//
|
|
// Use instead of the r.Seek() + io.LimitReader() pattern.
|
|
func NewBufferedSectionReader(ra io.ReaderAt, off, n int64) *bufio.Reader {
|
|
// Clamp the size of the buffer to one page to avoid slurping large parts
|
|
// of a file into memory. bufio.NewReader uses a hardcoded default buffer
|
|
// of 4096. Allow arches with larger pages to allocate more, but don't
|
|
// allocate a fixed 4k buffer if we only need to read a small segment.
|
|
buf := n
|
|
if ps := int64(os.Getpagesize()); n > ps {
|
|
buf = ps
|
|
}
|
|
|
|
return bufio.NewReaderSize(io.NewSectionReader(ra, off, n), int(buf))
|
|
}
|
|
|
|
// DiscardZeroes makes sure that all written bytes are zero
|
|
// before discarding them.
|
|
type DiscardZeroes struct{}
|
|
|
|
func (DiscardZeroes) Write(p []byte) (int, error) {
|
|
for _, b := range p {
|
|
if b != 0 {
|
|
return 0, errors.New("encountered non-zero byte")
|
|
}
|
|
}
|
|
return len(p), nil
|
|
}
|
|
|
|
// ReadAllCompressed decompresses a gzipped file into memory.
|
|
func ReadAllCompressed(file string) ([]byte, error) {
|
|
fh, err := os.Open(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer fh.Close()
|
|
|
|
gz, err := gzip.NewReader(fh)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer gz.Close()
|
|
|
|
return io.ReadAll(gz)
|
|
}
|
|
|
|
// ReadUint64FromFile reads a uint64 from a file.
|
|
//
|
|
// format specifies the contents of the file in fmt.Scanf syntax.
|
|
func ReadUint64FromFile(format string, path ...string) (uint64, error) {
|
|
filename := filepath.Join(path...)
|
|
data, err := os.ReadFile(filename)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("reading file %q: %w", filename, err)
|
|
}
|
|
|
|
var value uint64
|
|
n, err := fmt.Fscanf(bytes.NewReader(data), format, &value)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("parsing file %q: %w", filename, err)
|
|
}
|
|
if n != 1 {
|
|
return 0, fmt.Errorf("parsing file %q: expected 1 item, got %d", filename, n)
|
|
}
|
|
|
|
return value, nil
|
|
}
|
|
|
|
type uint64FromFileKey struct {
|
|
format, path string
|
|
}
|
|
|
|
var uint64FromFileCache = struct {
|
|
sync.RWMutex
|
|
values map[uint64FromFileKey]uint64
|
|
}{
|
|
values: map[uint64FromFileKey]uint64{},
|
|
}
|
|
|
|
// ReadUint64FromFileOnce is like readUint64FromFile but memoizes the result.
|
|
func ReadUint64FromFileOnce(format string, path ...string) (uint64, error) {
|
|
filename := filepath.Join(path...)
|
|
key := uint64FromFileKey{format, filename}
|
|
|
|
uint64FromFileCache.RLock()
|
|
if value, ok := uint64FromFileCache.values[key]; ok {
|
|
uint64FromFileCache.RUnlock()
|
|
return value, nil
|
|
}
|
|
uint64FromFileCache.RUnlock()
|
|
|
|
value, err := ReadUint64FromFile(format, filename)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
uint64FromFileCache.Lock()
|
|
defer uint64FromFileCache.Unlock()
|
|
|
|
if value, ok := uint64FromFileCache.values[key]; ok {
|
|
// Someone else got here before us, use what is cached.
|
|
return value, nil
|
|
}
|
|
|
|
uint64FromFileCache.values[key] = value
|
|
return value, nil
|
|
}
|