| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- //go:build linux
- package cpu
- import (
- "os"
- "runtime"
- "sort"
- "strconv"
- "strings"
- "golang.org/x/sys/unix"
- )
- var (
- affinityEnabled bool
- affinitySet unix.CPUSet
- )
- func init() {
- // Optional: enable via env to keep default behavior unchanged.
- //
- // - MAKARNA_CPUSET: e.g. "0-15,32-47"
- // - MAKARNA_NUMA_NODE: e.g. "0" (reads /sys/devices/system/node/node0/cpulist)
- if err := initAffinityFromEnv(); err != nil {
- affinityEnabled = false
- }
- }
- func initAffinityFromEnv() error {
- if cpus, ok := cpusFromEnv("MAKARNA_CPUSET"); ok {
- return setAffinityCPUs(cpus)
- }
- if nodeStr := strings.TrimSpace(os.Getenv("MAKARNA_NUMA_NODE")); nodeStr != "" {
- node, err := strconv.Atoi(nodeStr)
- if err != nil || node < 0 {
- return err
- }
- data, err := os.ReadFile("/sys/devices/system/node/node" + strconv.Itoa(node) + "/cpulist")
- if err != nil {
- return err
- }
- cpus, err := parseCPUList(strings.TrimSpace(string(data)))
- if err != nil {
- return err
- }
- return setAffinityCPUs(cpus)
- }
- return nil
- }
- func cpusFromEnv(key string) ([]int, bool) {
- raw := strings.TrimSpace(os.Getenv(key))
- if raw == "" {
- return nil, false
- }
- cpus, err := parseCPUList(raw)
- if err != nil {
- return nil, false
- }
- return cpus, true
- }
- func parseCPUList(s string) ([]int, error) {
- if s == "" {
- return nil, strconv.ErrSyntax
- }
- var cpus []int
- for _, part := range strings.Split(s, ",") {
- part = strings.TrimSpace(part)
- if part == "" {
- continue
- }
- if lo, hi, ok := strings.Cut(part, "-"); ok {
- start, err := strconv.Atoi(strings.TrimSpace(lo))
- if err != nil {
- return nil, err
- }
- end, err := strconv.Atoi(strings.TrimSpace(hi))
- if err != nil {
- return nil, err
- }
- if start < 0 || end < start {
- return nil, strconv.ErrSyntax
- }
- for c := start; c <= end; c++ {
- cpus = append(cpus, c)
- }
- continue
- }
- v, err := strconv.Atoi(part)
- if err != nil {
- return nil, err
- }
- if v < 0 {
- return nil, strconv.ErrSyntax
- }
- cpus = append(cpus, v)
- }
- if len(cpus) == 0 {
- return nil, strconv.ErrSyntax
- }
- sort.Ints(cpus)
- cpus = compactInts(cpus)
- return cpus, nil
- }
- func compactInts(xs []int) []int {
- if len(xs) < 2 {
- return xs
- }
- out := xs[:1]
- for _, v := range xs[1:] {
- if v == out[len(out)-1] {
- continue
- }
- out = append(out, v)
- }
- return out
- }
- func setAffinityCPUs(cpus []int) error {
- var set unix.CPUSet
- set.Zero()
- for _, c := range cpus {
- set.Set(c)
- }
- affinitySet = set
- affinityEnabled = true
- return nil
- }
- // WithPinnedThread runs fn on a locked OS thread with optional CPU affinity set
- // (enabled via env). If affinity is not configured, it behaves like fn().
- func WithPinnedThread(fn func()) {
- if !affinityEnabled {
- fn()
- return
- }
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- _ = unix.SchedSetaffinity(0, &affinitySet)
- fn()
- }
|