mirror of
https://codeberg.org/superseriousbusiness/gotosocial.git
synced 2024-12-25 18:38:17 +03: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.
589 lines
14 KiB
Go
589 lines
14 KiB
Go
package ebpf
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/cilium/ebpf/asm"
|
|
"github.com/cilium/ebpf/internal"
|
|
"github.com/cilium/ebpf/internal/btf"
|
|
)
|
|
|
|
// CollectionOptions control loading a collection into the kernel.
|
|
//
|
|
// Maps and Programs are passed to NewMapWithOptions and NewProgramsWithOptions.
|
|
type CollectionOptions struct {
|
|
Maps MapOptions
|
|
Programs ProgramOptions
|
|
}
|
|
|
|
// CollectionSpec describes a collection.
|
|
type CollectionSpec struct {
|
|
Maps map[string]*MapSpec
|
|
Programs map[string]*ProgramSpec
|
|
}
|
|
|
|
// Copy returns a recursive copy of the spec.
|
|
func (cs *CollectionSpec) Copy() *CollectionSpec {
|
|
if cs == nil {
|
|
return nil
|
|
}
|
|
|
|
cpy := CollectionSpec{
|
|
Maps: make(map[string]*MapSpec, len(cs.Maps)),
|
|
Programs: make(map[string]*ProgramSpec, len(cs.Programs)),
|
|
}
|
|
|
|
for name, spec := range cs.Maps {
|
|
cpy.Maps[name] = spec.Copy()
|
|
}
|
|
|
|
for name, spec := range cs.Programs {
|
|
cpy.Programs[name] = spec.Copy()
|
|
}
|
|
|
|
return &cpy
|
|
}
|
|
|
|
// RewriteMaps replaces all references to specific maps.
|
|
//
|
|
// Use this function to use pre-existing maps instead of creating new ones
|
|
// when calling NewCollection. Any named maps are removed from CollectionSpec.Maps.
|
|
//
|
|
// Returns an error if a named map isn't used in at least one program.
|
|
func (cs *CollectionSpec) RewriteMaps(maps map[string]*Map) error {
|
|
for symbol, m := range maps {
|
|
// have we seen a program that uses this symbol / map
|
|
seen := false
|
|
fd := m.FD()
|
|
for progName, progSpec := range cs.Programs {
|
|
err := progSpec.Instructions.RewriteMapPtr(symbol, fd)
|
|
|
|
switch {
|
|
case err == nil:
|
|
seen = true
|
|
|
|
case asm.IsUnreferencedSymbol(err):
|
|
// Not all programs need to use the map
|
|
|
|
default:
|
|
return fmt.Errorf("program %s: %w", progName, err)
|
|
}
|
|
}
|
|
|
|
if !seen {
|
|
return fmt.Errorf("map %s not referenced by any programs", symbol)
|
|
}
|
|
|
|
// Prevent NewCollection from creating rewritten maps
|
|
delete(cs.Maps, symbol)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RewriteConstants replaces the value of multiple constants.
|
|
//
|
|
// The constant must be defined like so in the C program:
|
|
//
|
|
// static volatile const type foobar;
|
|
// static volatile const type foobar = default;
|
|
//
|
|
// Replacement values must be of the same length as the C sizeof(type).
|
|
// If necessary, they are marshalled according to the same rules as
|
|
// map values.
|
|
//
|
|
// From Linux 5.5 the verifier will use constants to eliminate dead code.
|
|
//
|
|
// Returns an error if a constant doesn't exist.
|
|
func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error {
|
|
rodata := cs.Maps[".rodata"]
|
|
if rodata == nil {
|
|
return errors.New("missing .rodata section")
|
|
}
|
|
|
|
if rodata.BTF == nil {
|
|
return errors.New(".rodata section has no BTF")
|
|
}
|
|
|
|
if n := len(rodata.Contents); n != 1 {
|
|
return fmt.Errorf("expected one key in .rodata, found %d", n)
|
|
}
|
|
|
|
kv := rodata.Contents[0]
|
|
value, ok := kv.Value.([]byte)
|
|
if !ok {
|
|
return fmt.Errorf("first value in .rodata is %T not []byte", kv.Value)
|
|
}
|
|
|
|
buf := make([]byte, len(value))
|
|
copy(buf, value)
|
|
|
|
err := patchValue(buf, btf.MapValue(rodata.BTF), consts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rodata.Contents[0] = MapKV{kv.Key, buf}
|
|
return nil
|
|
}
|
|
|
|
// Assign the contents of a CollectionSpec to a struct.
|
|
//
|
|
// This function is a short-cut to manually checking the presence
|
|
// of maps and programs in a collection spec. Consider using bpf2go if this
|
|
// sounds useful.
|
|
//
|
|
// The argument to must be a pointer to a struct. A field of the
|
|
// struct is updated with values from Programs or Maps if it
|
|
// has an `ebpf` tag and its type is *ProgramSpec or *MapSpec.
|
|
// The tag gives the name of the program or map as found in
|
|
// the CollectionSpec.
|
|
//
|
|
// struct {
|
|
// Foo *ebpf.ProgramSpec `ebpf:"xdp_foo"`
|
|
// Bar *ebpf.MapSpec `ebpf:"bar_map"`
|
|
// Ignored int
|
|
// }
|
|
//
|
|
// Returns an error if any of the fields can't be found, or
|
|
// if the same map or program is assigned multiple times.
|
|
func (cs *CollectionSpec) Assign(to interface{}) error {
|
|
valueOf := func(typ reflect.Type, name string) (reflect.Value, error) {
|
|
switch typ {
|
|
case reflect.TypeOf((*ProgramSpec)(nil)):
|
|
p := cs.Programs[name]
|
|
if p == nil {
|
|
return reflect.Value{}, fmt.Errorf("missing program %q", name)
|
|
}
|
|
return reflect.ValueOf(p), nil
|
|
case reflect.TypeOf((*MapSpec)(nil)):
|
|
m := cs.Maps[name]
|
|
if m == nil {
|
|
return reflect.Value{}, fmt.Errorf("missing map %q", name)
|
|
}
|
|
return reflect.ValueOf(m), nil
|
|
default:
|
|
return reflect.Value{}, fmt.Errorf("unsupported type %s", typ)
|
|
}
|
|
}
|
|
|
|
return assignValues(to, valueOf)
|
|
}
|
|
|
|
// LoadAndAssign maps and programs into the kernel and assign them to a struct.
|
|
//
|
|
// This function is a short-cut to manually checking the presence
|
|
// of maps and programs in a collection spec. Consider using bpf2go if this
|
|
// sounds useful.
|
|
//
|
|
// The argument to must be a pointer to a struct. A field of the
|
|
// struct is updated with values from Programs or Maps if it
|
|
// has an `ebpf` tag and its type is *Program or *Map.
|
|
// The tag gives the name of the program or map as found in
|
|
// the CollectionSpec.
|
|
//
|
|
// struct {
|
|
// Foo *ebpf.Program `ebpf:"xdp_foo"`
|
|
// Bar *ebpf.Map `ebpf:"bar_map"`
|
|
// Ignored int
|
|
// }
|
|
//
|
|
// opts may be nil.
|
|
//
|
|
// Returns an error if any of the fields can't be found, or
|
|
// if the same map or program is assigned multiple times.
|
|
func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions) error {
|
|
if opts == nil {
|
|
opts = &CollectionOptions{}
|
|
}
|
|
|
|
loadMap, loadProgram, done, cleanup := lazyLoadCollection(cs, opts)
|
|
defer cleanup()
|
|
|
|
valueOf := func(typ reflect.Type, name string) (reflect.Value, error) {
|
|
switch typ {
|
|
case reflect.TypeOf((*Program)(nil)):
|
|
p, err := loadProgram(name)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
return reflect.ValueOf(p), nil
|
|
case reflect.TypeOf((*Map)(nil)):
|
|
m, err := loadMap(name)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
return reflect.ValueOf(m), nil
|
|
default:
|
|
return reflect.Value{}, fmt.Errorf("unsupported type %s", typ)
|
|
}
|
|
}
|
|
|
|
if err := assignValues(to, valueOf); err != nil {
|
|
return err
|
|
}
|
|
|
|
done()
|
|
return nil
|
|
}
|
|
|
|
// Collection is a collection of Programs and Maps associated
|
|
// with their symbols
|
|
type Collection struct {
|
|
Programs map[string]*Program
|
|
Maps map[string]*Map
|
|
}
|
|
|
|
// NewCollection creates a Collection from a specification.
|
|
func NewCollection(spec *CollectionSpec) (*Collection, error) {
|
|
return NewCollectionWithOptions(spec, CollectionOptions{})
|
|
}
|
|
|
|
// NewCollectionWithOptions creates a Collection from a specification.
|
|
func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (*Collection, error) {
|
|
loadMap, loadProgram, done, cleanup := lazyLoadCollection(spec, &opts)
|
|
defer cleanup()
|
|
|
|
for mapName := range spec.Maps {
|
|
_, err := loadMap(mapName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
for progName := range spec.Programs {
|
|
_, err := loadProgram(progName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
maps, progs := done()
|
|
return &Collection{
|
|
progs,
|
|
maps,
|
|
}, nil
|
|
}
|
|
|
|
type btfHandleCache map[*btf.Spec]*btf.Handle
|
|
|
|
func (btfs btfHandleCache) load(spec *btf.Spec) (*btf.Handle, error) {
|
|
if btfs[spec] != nil {
|
|
return btfs[spec], nil
|
|
}
|
|
|
|
handle, err := btf.NewHandle(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
btfs[spec] = handle
|
|
return handle, nil
|
|
}
|
|
|
|
func (btfs btfHandleCache) close() {
|
|
for _, handle := range btfs {
|
|
handle.Close()
|
|
}
|
|
}
|
|
|
|
func lazyLoadCollection(coll *CollectionSpec, opts *CollectionOptions) (
|
|
loadMap func(string) (*Map, error),
|
|
loadProgram func(string) (*Program, error),
|
|
done func() (map[string]*Map, map[string]*Program),
|
|
cleanup func(),
|
|
) {
|
|
var (
|
|
maps = make(map[string]*Map)
|
|
progs = make(map[string]*Program)
|
|
btfs = make(btfHandleCache)
|
|
skipMapsAndProgs = false
|
|
)
|
|
|
|
cleanup = func() {
|
|
btfs.close()
|
|
|
|
if skipMapsAndProgs {
|
|
return
|
|
}
|
|
|
|
for _, m := range maps {
|
|
m.Close()
|
|
}
|
|
|
|
for _, p := range progs {
|
|
p.Close()
|
|
}
|
|
}
|
|
|
|
done = func() (map[string]*Map, map[string]*Program) {
|
|
skipMapsAndProgs = true
|
|
return maps, progs
|
|
}
|
|
|
|
loadMap = func(mapName string) (*Map, error) {
|
|
if m := maps[mapName]; m != nil {
|
|
return m, nil
|
|
}
|
|
|
|
mapSpec := coll.Maps[mapName]
|
|
if mapSpec == nil {
|
|
return nil, fmt.Errorf("missing map %s", mapName)
|
|
}
|
|
|
|
m, err := newMapWithOptions(mapSpec, opts.Maps, btfs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("map %s: %w", mapName, err)
|
|
}
|
|
|
|
maps[mapName] = m
|
|
return m, nil
|
|
}
|
|
|
|
loadProgram = func(progName string) (*Program, error) {
|
|
if prog := progs[progName]; prog != nil {
|
|
return prog, nil
|
|
}
|
|
|
|
progSpec := coll.Programs[progName]
|
|
if progSpec == nil {
|
|
return nil, fmt.Errorf("unknown program %s", progName)
|
|
}
|
|
|
|
progSpec = progSpec.Copy()
|
|
|
|
// Rewrite any reference to a valid map.
|
|
for i := range progSpec.Instructions {
|
|
ins := &progSpec.Instructions[i]
|
|
|
|
if ins.OpCode != asm.LoadImmOp(asm.DWord) || ins.Reference == "" {
|
|
continue
|
|
}
|
|
|
|
if uint32(ins.Constant) != math.MaxUint32 {
|
|
// Don't overwrite maps already rewritten, users can
|
|
// rewrite programs in the spec themselves
|
|
continue
|
|
}
|
|
|
|
m, err := loadMap(ins.Reference)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("program %s: %s", progName, err)
|
|
}
|
|
|
|
fd := m.FD()
|
|
if fd < 0 {
|
|
return nil, fmt.Errorf("map %s: %w", ins.Reference, internal.ErrClosedFd)
|
|
}
|
|
if err := ins.RewriteMapPtr(m.FD()); err != nil {
|
|
return nil, fmt.Errorf("progam %s: map %s: %w", progName, ins.Reference, err)
|
|
}
|
|
}
|
|
|
|
prog, err := newProgramWithOptions(progSpec, opts.Programs, btfs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("program %s: %w", progName, err)
|
|
}
|
|
|
|
progs[progName] = prog
|
|
return prog, nil
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// LoadCollection parses an object file and converts it to a collection.
|
|
func LoadCollection(file string) (*Collection, error) {
|
|
spec, err := LoadCollectionSpec(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return NewCollection(spec)
|
|
}
|
|
|
|
// Close frees all maps and programs associated with the collection.
|
|
//
|
|
// The collection mustn't be used afterwards.
|
|
func (coll *Collection) Close() {
|
|
for _, prog := range coll.Programs {
|
|
prog.Close()
|
|
}
|
|
for _, m := range coll.Maps {
|
|
m.Close()
|
|
}
|
|
}
|
|
|
|
// DetachMap removes the named map from the Collection.
|
|
//
|
|
// This means that a later call to Close() will not affect this map.
|
|
//
|
|
// Returns nil if no map of that name exists.
|
|
func (coll *Collection) DetachMap(name string) *Map {
|
|
m := coll.Maps[name]
|
|
delete(coll.Maps, name)
|
|
return m
|
|
}
|
|
|
|
// DetachProgram removes the named program from the Collection.
|
|
//
|
|
// This means that a later call to Close() will not affect this program.
|
|
//
|
|
// Returns nil if no program of that name exists.
|
|
func (coll *Collection) DetachProgram(name string) *Program {
|
|
p := coll.Programs[name]
|
|
delete(coll.Programs, name)
|
|
return p
|
|
}
|
|
|
|
// Assign the contents of a collection to a struct.
|
|
//
|
|
// Deprecated: use CollectionSpec.Assign instead. It provides the same
|
|
// functionality but creates only the maps and programs requested.
|
|
func (coll *Collection) Assign(to interface{}) error {
|
|
assignedMaps := make(map[string]struct{})
|
|
assignedPrograms := make(map[string]struct{})
|
|
valueOf := func(typ reflect.Type, name string) (reflect.Value, error) {
|
|
switch typ {
|
|
case reflect.TypeOf((*Program)(nil)):
|
|
p := coll.Programs[name]
|
|
if p == nil {
|
|
return reflect.Value{}, fmt.Errorf("missing program %q", name)
|
|
}
|
|
assignedPrograms[name] = struct{}{}
|
|
return reflect.ValueOf(p), nil
|
|
case reflect.TypeOf((*Map)(nil)):
|
|
m := coll.Maps[name]
|
|
if m == nil {
|
|
return reflect.Value{}, fmt.Errorf("missing map %q", name)
|
|
}
|
|
assignedMaps[name] = struct{}{}
|
|
return reflect.ValueOf(m), nil
|
|
default:
|
|
return reflect.Value{}, fmt.Errorf("unsupported type %s", typ)
|
|
}
|
|
}
|
|
|
|
if err := assignValues(to, valueOf); err != nil {
|
|
return err
|
|
}
|
|
|
|
for name := range assignedPrograms {
|
|
coll.DetachProgram(name)
|
|
}
|
|
|
|
for name := range assignedMaps {
|
|
coll.DetachMap(name)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func assignValues(to interface{}, valueOf func(reflect.Type, string) (reflect.Value, error)) error {
|
|
type structField struct {
|
|
reflect.StructField
|
|
value reflect.Value
|
|
}
|
|
|
|
var (
|
|
fields []structField
|
|
visitedTypes = make(map[reflect.Type]bool)
|
|
flattenStruct func(reflect.Value) error
|
|
)
|
|
|
|
flattenStruct = func(structVal reflect.Value) error {
|
|
structType := structVal.Type()
|
|
if structType.Kind() != reflect.Struct {
|
|
return fmt.Errorf("%s is not a struct", structType)
|
|
}
|
|
|
|
if visitedTypes[structType] {
|
|
return fmt.Errorf("recursion on type %s", structType)
|
|
}
|
|
|
|
for i := 0; i < structType.NumField(); i++ {
|
|
field := structField{structType.Field(i), structVal.Field(i)}
|
|
|
|
name := field.Tag.Get("ebpf")
|
|
if name != "" {
|
|
fields = append(fields, field)
|
|
continue
|
|
}
|
|
|
|
var err error
|
|
switch field.Type.Kind() {
|
|
case reflect.Ptr:
|
|
if field.Type.Elem().Kind() != reflect.Struct {
|
|
continue
|
|
}
|
|
|
|
if field.value.IsNil() {
|
|
return fmt.Errorf("nil pointer to %s", structType)
|
|
}
|
|
|
|
err = flattenStruct(field.value.Elem())
|
|
|
|
case reflect.Struct:
|
|
err = flattenStruct(field.value)
|
|
|
|
default:
|
|
continue
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("field %s: %s", field.Name, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
toValue := reflect.ValueOf(to)
|
|
if toValue.Type().Kind() != reflect.Ptr {
|
|
return fmt.Errorf("%T is not a pointer to struct", to)
|
|
}
|
|
|
|
if toValue.IsNil() {
|
|
return fmt.Errorf("nil pointer to %T", to)
|
|
}
|
|
|
|
if err := flattenStruct(toValue.Elem()); err != nil {
|
|
return err
|
|
}
|
|
|
|
type elem struct {
|
|
// Either *Map or *Program
|
|
typ reflect.Type
|
|
name string
|
|
}
|
|
|
|
assignedTo := make(map[elem]string)
|
|
for _, field := range fields {
|
|
name := field.Tag.Get("ebpf")
|
|
if strings.Contains(name, ",") {
|
|
return fmt.Errorf("field %s: ebpf tag contains a comma", field.Name)
|
|
}
|
|
|
|
e := elem{field.Type, name}
|
|
if assignedField := assignedTo[e]; assignedField != "" {
|
|
return fmt.Errorf("field %s: %q was already assigned to %s", field.Name, name, assignedField)
|
|
}
|
|
|
|
value, err := valueOf(field.Type, name)
|
|
if err != nil {
|
|
return fmt.Errorf("field %s: %w", field.Name, err)
|
|
}
|
|
|
|
if !field.value.CanSet() {
|
|
return fmt.Errorf("field %s: can't set value", field.Name)
|
|
}
|
|
|
|
field.value.Set(value)
|
|
assignedTo[e] = field.Name
|
|
}
|
|
|
|
return nil
|
|
}
|