mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-12-02 08:32:45 +00:00
acc333c40b
When GTS is running in a container runtime which has configured CPU or memory limits or under an init system that uses cgroups to impose CPU and memory limits the values the Go runtime sees for GOMAXPROCS and GOMEMLIMIT are still based on the host resources, not the cgroup. At least for the throttling middlewares which use GOMAXPROCS to configure their queue size, this can result in GTS running with values too big compared to the resources that will actuall be available to it. This introduces 2 dependencies which can pick up resource contraints from the current cgroup and tune the Go runtime accordingly. This should result in the different queues being appropriately sized and in general more predictable performance. These dependencies are a no-op on non-Linux systems or if running in a cgroup that doesn't set a limit on CPU or memory. The automatic tuning of GOMEMLIMIT can be disabled by either explicitly setting GOMEMLIMIT yourself or by setting AUTOMEMLIMIT=off. The automatic tuning of GOMAXPROCS can similarly be counteracted by setting GOMAXPROCS yourself.
282 lines
7.7 KiB
Go
282 lines
7.7 KiB
Go
package btf
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
|
|
"github.com/cilium/ebpf/asm"
|
|
"github.com/cilium/ebpf/internal"
|
|
)
|
|
|
|
type btfExtHeader struct {
|
|
Magic uint16
|
|
Version uint8
|
|
Flags uint8
|
|
HdrLen uint32
|
|
|
|
FuncInfoOff uint32
|
|
FuncInfoLen uint32
|
|
LineInfoOff uint32
|
|
LineInfoLen uint32
|
|
}
|
|
|
|
type btfExtCoreHeader struct {
|
|
CoreReloOff uint32
|
|
CoreReloLen uint32
|
|
}
|
|
|
|
func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (funcInfo, lineInfo map[string]extInfo, coreRelos map[string]bpfCoreRelos, err error) {
|
|
var header btfExtHeader
|
|
var coreHeader btfExtCoreHeader
|
|
if err := binary.Read(r, bo, &header); err != nil {
|
|
return nil, nil, nil, fmt.Errorf("can't read header: %v", err)
|
|
}
|
|
|
|
if header.Magic != btfMagic {
|
|
return nil, nil, nil, fmt.Errorf("incorrect magic value %v", header.Magic)
|
|
}
|
|
|
|
if header.Version != 1 {
|
|
return nil, nil, nil, fmt.Errorf("unexpected version %v", header.Version)
|
|
}
|
|
|
|
if header.Flags != 0 {
|
|
return nil, nil, nil, fmt.Errorf("unsupported flags %v", header.Flags)
|
|
}
|
|
|
|
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
|
|
if remainder < 0 {
|
|
return nil, nil, nil, errors.New("header is too short")
|
|
}
|
|
|
|
coreHdrSize := int64(binary.Size(&coreHeader))
|
|
if remainder >= coreHdrSize {
|
|
if err := binary.Read(r, bo, &coreHeader); err != nil {
|
|
return nil, nil, nil, fmt.Errorf("can't read CO-RE relocation header: %v", err)
|
|
}
|
|
remainder -= coreHdrSize
|
|
}
|
|
|
|
// Of course, the .BTF.ext header has different semantics than the
|
|
// .BTF ext header. We need to ignore non-null values.
|
|
_, err = io.CopyN(ioutil.Discard, r, remainder)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("header padding: %v", err)
|
|
}
|
|
|
|
if _, err := r.Seek(int64(header.HdrLen+header.FuncInfoOff), io.SeekStart); err != nil {
|
|
return nil, nil, nil, fmt.Errorf("can't seek to function info section: %v", err)
|
|
}
|
|
|
|
buf := bufio.NewReader(io.LimitReader(r, int64(header.FuncInfoLen)))
|
|
funcInfo, err = parseExtInfo(buf, bo, strings)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("function info: %w", err)
|
|
}
|
|
|
|
if _, err := r.Seek(int64(header.HdrLen+header.LineInfoOff), io.SeekStart); err != nil {
|
|
return nil, nil, nil, fmt.Errorf("can't seek to line info section: %v", err)
|
|
}
|
|
|
|
buf = bufio.NewReader(io.LimitReader(r, int64(header.LineInfoLen)))
|
|
lineInfo, err = parseExtInfo(buf, bo, strings)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("line info: %w", err)
|
|
}
|
|
|
|
if coreHeader.CoreReloOff > 0 && coreHeader.CoreReloLen > 0 {
|
|
if _, err := r.Seek(int64(header.HdrLen+coreHeader.CoreReloOff), io.SeekStart); err != nil {
|
|
return nil, nil, nil, fmt.Errorf("can't seek to CO-RE relocation section: %v", err)
|
|
}
|
|
|
|
coreRelos, err = parseExtInfoRelos(io.LimitReader(r, int64(coreHeader.CoreReloLen)), bo, strings)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("CO-RE relocation info: %w", err)
|
|
}
|
|
}
|
|
|
|
return funcInfo, lineInfo, coreRelos, nil
|
|
}
|
|
|
|
type btfExtInfoSec struct {
|
|
SecNameOff uint32
|
|
NumInfo uint32
|
|
}
|
|
|
|
type extInfoRecord struct {
|
|
InsnOff uint64
|
|
Opaque []byte
|
|
}
|
|
|
|
type extInfo struct {
|
|
recordSize uint32
|
|
records []extInfoRecord
|
|
}
|
|
|
|
func (ei extInfo) append(other extInfo, offset uint64) (extInfo, error) {
|
|
if other.recordSize != ei.recordSize {
|
|
return extInfo{}, fmt.Errorf("ext_info record size mismatch, want %d (got %d)", ei.recordSize, other.recordSize)
|
|
}
|
|
|
|
records := make([]extInfoRecord, 0, len(ei.records)+len(other.records))
|
|
records = append(records, ei.records...)
|
|
for _, info := range other.records {
|
|
records = append(records, extInfoRecord{
|
|
InsnOff: info.InsnOff + offset,
|
|
Opaque: info.Opaque,
|
|
})
|
|
}
|
|
return extInfo{ei.recordSize, records}, nil
|
|
}
|
|
|
|
func (ei extInfo) MarshalBinary() ([]byte, error) {
|
|
if len(ei.records) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
buf := bytes.NewBuffer(make([]byte, 0, int(ei.recordSize)*len(ei.records)))
|
|
for _, info := range ei.records {
|
|
// The kernel expects offsets in number of raw bpf instructions,
|
|
// while the ELF tracks it in bytes.
|
|
insnOff := uint32(info.InsnOff / asm.InstructionSize)
|
|
if err := binary.Write(buf, internal.NativeEndian, insnOff); err != nil {
|
|
return nil, fmt.Errorf("can't write instruction offset: %v", err)
|
|
}
|
|
|
|
buf.Write(info.Opaque)
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]extInfo, error) {
|
|
const maxRecordSize = 256
|
|
|
|
var recordSize uint32
|
|
if err := binary.Read(r, bo, &recordSize); err != nil {
|
|
return nil, fmt.Errorf("can't read record size: %v", err)
|
|
}
|
|
|
|
if recordSize < 4 {
|
|
// Need at least insnOff
|
|
return nil, errors.New("record size too short")
|
|
}
|
|
if recordSize > maxRecordSize {
|
|
return nil, fmt.Errorf("record size %v exceeds %v", recordSize, maxRecordSize)
|
|
}
|
|
|
|
result := make(map[string]extInfo)
|
|
for {
|
|
secName, infoHeader, err := parseExtInfoHeader(r, bo, strings)
|
|
if errors.Is(err, io.EOF) {
|
|
return result, nil
|
|
}
|
|
|
|
var records []extInfoRecord
|
|
for i := uint32(0); i < infoHeader.NumInfo; i++ {
|
|
var byteOff uint32
|
|
if err := binary.Read(r, bo, &byteOff); err != nil {
|
|
return nil, fmt.Errorf("section %v: can't read extended info offset: %v", secName, err)
|
|
}
|
|
|
|
buf := make([]byte, int(recordSize-4))
|
|
if _, err := io.ReadFull(r, buf); err != nil {
|
|
return nil, fmt.Errorf("section %v: can't read record: %v", secName, err)
|
|
}
|
|
|
|
if byteOff%asm.InstructionSize != 0 {
|
|
return nil, fmt.Errorf("section %v: offset %v is not aligned with instruction size", secName, byteOff)
|
|
}
|
|
|
|
records = append(records, extInfoRecord{uint64(byteOff), buf})
|
|
}
|
|
|
|
result[secName] = extInfo{
|
|
recordSize,
|
|
records,
|
|
}
|
|
}
|
|
}
|
|
|
|
// bpfCoreRelo matches `struct bpf_core_relo` from the kernel
|
|
type bpfCoreRelo struct {
|
|
InsnOff uint32
|
|
TypeID TypeID
|
|
AccessStrOff uint32
|
|
ReloKind coreReloKind
|
|
}
|
|
|
|
type bpfCoreRelos []bpfCoreRelo
|
|
|
|
// append two slices of extInfoRelo to each other. The InsnOff of b are adjusted
|
|
// by offset.
|
|
func (r bpfCoreRelos) append(other bpfCoreRelos, offset uint64) bpfCoreRelos {
|
|
result := make([]bpfCoreRelo, 0, len(r)+len(other))
|
|
result = append(result, r...)
|
|
for _, relo := range other {
|
|
relo.InsnOff += uint32(offset)
|
|
result = append(result, relo)
|
|
}
|
|
return result
|
|
}
|
|
|
|
var extInfoReloSize = binary.Size(bpfCoreRelo{})
|
|
|
|
func parseExtInfoRelos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]bpfCoreRelos, error) {
|
|
var recordSize uint32
|
|
if err := binary.Read(r, bo, &recordSize); err != nil {
|
|
return nil, fmt.Errorf("read record size: %v", err)
|
|
}
|
|
|
|
if recordSize != uint32(extInfoReloSize) {
|
|
return nil, fmt.Errorf("expected record size %d, got %d", extInfoReloSize, recordSize)
|
|
}
|
|
|
|
result := make(map[string]bpfCoreRelos)
|
|
for {
|
|
secName, infoHeader, err := parseExtInfoHeader(r, bo, strings)
|
|
if errors.Is(err, io.EOF) {
|
|
return result, nil
|
|
}
|
|
|
|
var relos []bpfCoreRelo
|
|
for i := uint32(0); i < infoHeader.NumInfo; i++ {
|
|
var relo bpfCoreRelo
|
|
if err := binary.Read(r, bo, &relo); err != nil {
|
|
return nil, fmt.Errorf("section %v: read record: %v", secName, err)
|
|
}
|
|
|
|
if relo.InsnOff%asm.InstructionSize != 0 {
|
|
return nil, fmt.Errorf("section %v: offset %v is not aligned with instruction size", secName, relo.InsnOff)
|
|
}
|
|
|
|
relos = append(relos, relo)
|
|
}
|
|
|
|
result[secName] = relos
|
|
}
|
|
}
|
|
|
|
func parseExtInfoHeader(r io.Reader, bo binary.ByteOrder, strings stringTable) (string, *btfExtInfoSec, error) {
|
|
var infoHeader btfExtInfoSec
|
|
if err := binary.Read(r, bo, &infoHeader); err != nil {
|
|
return "", nil, fmt.Errorf("read ext info header: %w", err)
|
|
}
|
|
|
|
secName, err := strings.Lookup(infoHeader.SecNameOff)
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("get section name: %w", err)
|
|
}
|
|
|
|
if infoHeader.NumInfo == 0 {
|
|
return "", nil, fmt.Errorf("section %s has zero records", secName)
|
|
}
|
|
|
|
return secName, &infoHeader, nil
|
|
}
|