mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2024-12-18 08:43:11 +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.
239 lines
5.1 KiB
Go
239 lines
5.1 KiB
Go
package ebpf
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/cilium/ebpf/internal"
|
|
)
|
|
|
|
// MapInfo describes a map.
|
|
type MapInfo struct {
|
|
Type MapType
|
|
id MapID
|
|
KeySize uint32
|
|
ValueSize uint32
|
|
MaxEntries uint32
|
|
Flags uint32
|
|
// Name as supplied by user space at load time.
|
|
Name string
|
|
}
|
|
|
|
func newMapInfoFromFd(fd *internal.FD) (*MapInfo, error) {
|
|
info, err := bpfGetMapInfoByFD(fd)
|
|
if errors.Is(err, syscall.EINVAL) {
|
|
return newMapInfoFromProc(fd)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &MapInfo{
|
|
MapType(info.map_type),
|
|
MapID(info.id),
|
|
info.key_size,
|
|
info.value_size,
|
|
info.max_entries,
|
|
info.map_flags,
|
|
// name is available from 4.15.
|
|
internal.CString(info.name[:]),
|
|
}, nil
|
|
}
|
|
|
|
func newMapInfoFromProc(fd *internal.FD) (*MapInfo, error) {
|
|
var mi MapInfo
|
|
err := scanFdInfo(fd, map[string]interface{}{
|
|
"map_type": &mi.Type,
|
|
"key_size": &mi.KeySize,
|
|
"value_size": &mi.ValueSize,
|
|
"max_entries": &mi.MaxEntries,
|
|
"map_flags": &mi.Flags,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &mi, nil
|
|
}
|
|
|
|
// ID returns the map ID.
|
|
//
|
|
// Available from 4.13.
|
|
//
|
|
// The bool return value indicates whether this optional field is available.
|
|
func (mi *MapInfo) ID() (MapID, bool) {
|
|
return mi.id, mi.id > 0
|
|
}
|
|
|
|
// programStats holds statistics of a program.
|
|
type programStats struct {
|
|
// Total accumulated runtime of the program ins ns.
|
|
runtime time.Duration
|
|
// Total number of times the program was called.
|
|
runCount uint64
|
|
}
|
|
|
|
// ProgramInfo describes a program.
|
|
type ProgramInfo struct {
|
|
Type ProgramType
|
|
id ProgramID
|
|
// Truncated hash of the BPF bytecode.
|
|
Tag string
|
|
// Name as supplied by user space at load time.
|
|
Name string
|
|
|
|
stats *programStats
|
|
}
|
|
|
|
func newProgramInfoFromFd(fd *internal.FD) (*ProgramInfo, error) {
|
|
info, err := bpfGetProgInfoByFD(fd)
|
|
if errors.Is(err, syscall.EINVAL) {
|
|
return newProgramInfoFromProc(fd)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ProgramInfo{
|
|
Type: ProgramType(info.prog_type),
|
|
id: ProgramID(info.id),
|
|
// tag is available if the kernel supports BPF_PROG_GET_INFO_BY_FD.
|
|
Tag: hex.EncodeToString(info.tag[:]),
|
|
// name is available from 4.15.
|
|
Name: internal.CString(info.name[:]),
|
|
stats: &programStats{
|
|
runtime: time.Duration(info.run_time_ns),
|
|
runCount: info.run_cnt,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func newProgramInfoFromProc(fd *internal.FD) (*ProgramInfo, error) {
|
|
var info ProgramInfo
|
|
err := scanFdInfo(fd, map[string]interface{}{
|
|
"prog_type": &info.Type,
|
|
"prog_tag": &info.Tag,
|
|
})
|
|
if errors.Is(err, errMissingFields) {
|
|
return nil, &internal.UnsupportedFeatureError{
|
|
Name: "reading program info from /proc/self/fdinfo",
|
|
MinimumVersion: internal.Version{4, 10, 0},
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &info, nil
|
|
}
|
|
|
|
// ID returns the program ID.
|
|
//
|
|
// Available from 4.13.
|
|
//
|
|
// The bool return value indicates whether this optional field is available.
|
|
func (pi *ProgramInfo) ID() (ProgramID, bool) {
|
|
return pi.id, pi.id > 0
|
|
}
|
|
|
|
// RunCount returns the total number of times the program was called.
|
|
//
|
|
// Can return 0 if the collection of statistics is not enabled. See EnableStats().
|
|
// The bool return value indicates whether this optional field is available.
|
|
func (pi *ProgramInfo) RunCount() (uint64, bool) {
|
|
if pi.stats != nil {
|
|
return pi.stats.runCount, true
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
// Runtime returns the total accumulated runtime of the program.
|
|
//
|
|
// Can return 0 if the collection of statistics is not enabled. See EnableStats().
|
|
// The bool return value indicates whether this optional field is available.
|
|
func (pi *ProgramInfo) Runtime() (time.Duration, bool) {
|
|
if pi.stats != nil {
|
|
return pi.stats.runtime, true
|
|
}
|
|
return time.Duration(0), false
|
|
}
|
|
|
|
func scanFdInfo(fd *internal.FD, fields map[string]interface{}) error {
|
|
raw, err := fd.Value()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", raw))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer fh.Close()
|
|
|
|
if err := scanFdInfoReader(fh, fields); err != nil {
|
|
return fmt.Errorf("%s: %w", fh.Name(), err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var errMissingFields = errors.New("missing fields")
|
|
|
|
func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error {
|
|
var (
|
|
scanner = bufio.NewScanner(r)
|
|
scanned int
|
|
)
|
|
|
|
for scanner.Scan() {
|
|
parts := strings.SplitN(scanner.Text(), "\t", 2)
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
|
|
name := strings.TrimSuffix(parts[0], ":")
|
|
field, ok := fields[string(name)]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if n, err := fmt.Sscanln(parts[1], field); err != nil || n != 1 {
|
|
return fmt.Errorf("can't parse field %s: %v", name, err)
|
|
}
|
|
|
|
scanned++
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if scanned != len(fields) {
|
|
return errMissingFields
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// EnableStats starts the measuring of the runtime
|
|
// and run counts of eBPF programs.
|
|
//
|
|
// Collecting statistics can have an impact on the performance.
|
|
//
|
|
// Requires at least 5.8.
|
|
func EnableStats(which uint32) (io.Closer, error) {
|
|
attr := internal.BPFEnableStatsAttr{
|
|
StatsType: which,
|
|
}
|
|
|
|
fd, err := internal.BPFEnableStats(&attr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return fd, nil
|
|
}
|