Ctrl K
ComunidadGuías…

utils/stall/cpuwait_linux.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
//go:build linux

package stall

import (
	"bytes"
	"encoding/binary"
	"os"
	"strconv"
	"time"

	"golang.org/x/sys/unix"
)

// taskDir holds a directory per thread of the process.
const taskDir = "/proc/self/task"

// procStat is the kernel's per-CPU times, steal among them.
const procStat = "/proc/stat"

// userHZ is the unit of /proc/stat's times: USER_HZ, 100 on every Linux.
const userHZ = 100

// threadWaits reads how long the process's threads were held off a CPU: the
// kernel's run delay of each (/proc/self/task/TID/schedstat: time spent
// runnable on a run queue, or in a throttled cgroup, not running), and the
// time the hypervisor took the virtual CPU the thread runs on (steal, in
// /proc/stat): a thread running on a virtual CPU the host has not scheduled
// does not run, and neither its CPU time nor its run delay grows. The kernel
// adds a wait to the thread's run delay as the thread gets the CPU, and the
// steal to the CPU's at its first tick back, so a read cannot see either
// while it lasts; what it can see is a runnable thread that has not moved
// since the last read (held). Not safe for concurrent use: the sampler makes
// one read at a time (sampler.readMu).
type threadWaits struct {
	last  map[string]threadStat // each thread at the last read
	steal map[string]int64      // each CPU's steal at the last read, in USER_HZ ticks
	reads uint64                // reads so far
	held  map[string]uint64     // each thread held off at the last read: the read that first found it held, this time
}

// threadStat is a thread's account: its CPU time, run delay and times run
// (schedstat), whether it is runnable, and the CPU it is on (stat).
type threadStat struct {
	exec, delay, runs int64
	runnable          bool
	cpu               string
}

// longest is the most any one thread was held off a CPU since the last call,
// and whether a thread is held off now (runnable, and not moved since the
// last call: no CPU time, no wait ended, not run): for how many calls in a
// row, this one included, the one held off longest has been, 0 if none is.
// 0 and 0 where the threads cannot be read (no /proc, a kernel without
// CONFIG_SCHED_INFO).
func (w *threadWaits) longest() (time.Duration, uint64) {
	return w.longestIn(taskDir, procStat)
}

// longestIn is longest, the threads read from their directories in tasksDir
// and the CPUs' steal from statFile (the tests' are not the kernel's).
func (w *threadWaits) longestIn(tasksDir, statFile string) (time.Duration, uint64) {
	tasks, err := os.ReadDir(tasksDir)
	if err != nil {
		return 0, 0
	}
	seen := make(map[string]threadStat, len(tasks))
	kept := false
	for _, task := range tasks {
		if st, ok := readThread(tasksDir, task.Name()); ok {
			seen[task.Name()] = st
			kept = kept || st.runs > 0
		} // else the thread has exited
	}
	if !kept {
		// No thread has run, the one reading included: the kernel keeps
		// no account and shows each as "0 0 0" (before 5.14, built without
		// schedstats and booted with nodelayacct). No thread would ever
		// move, so every runnable one would be held off at every read,
		// and a verdict waiting for it would never come. Nothing is read.
		seen = nil
	}
	var steal map[string]int64
	if b, err := os.ReadFile(statFile); err == nil {
		steal = parseSteal(b)
	}
	return w.account(seen, steal)
}

// account is longest's sum over the threads seen now and each CPU's steal,
// against the last read; it keeps them for the next.
func (w *threadWaits) account(seen map[string]threadStat, steal map[string]int64) (most time.Duration, heldFor uint64) {
	w.reads++
	held := make(map[string]uint64)
	for tid, st := range seen {
		// A thread the last read did not see started since: all of its
		// account is since then, and none of it is a wait in progress.
		was, known := w.last[tid]
		moved := !known || st.exec != was.exec || st.delay != was.delay || st.runs != was.runs
		wait := time.Duration(st.delay - was.delay)
		if moved || st.runnable {
			// It ran or waited to since the last read: the steal of the
			// CPU it is on held it off too. A thread that slept through
			// it was not.
			if s, ok := steal[st.cpu]; ok {
				wait += time.Duration(max(s-w.steal[st.cpu], 0)) * (time.Second / userHZ)
			}
		}
		most = max(most, wait)
		if known && !moved && st.runnable {
			from, ok := w.held[tid]
			if !ok {
				from = w.reads
			}
			held[tid] = from
			heldFor = max(heldFor, w.reads-from+1)
		}
	}
	w.last, w.steal, w.held = seen, steal, held
	return most, heldFor
}

// sleepFor sleeps the calling thread in the kernel for d, and says whether
// stop closed first. The thread is woken where its sleep ends, so on a host
// that holds the process off its wait for a CPU from then on is its own run
// delay (threadRunDelay). It looks at stop only after a sleep: whoever closes
// stop wakes w after it, and the sleep ends. A signal can end the sleep after
// stop closed and before the wake came: the sampling then ends without
// taking the wake, and the next one takes it before its first sleep (run).
func sleepFor(d time.Duration, stop <-chan struct{}, w *waker) (stopped bool) {
	end := time.Now().Add(d)
	for {
		// Cut short by a signal, or by a wake that came while this
		// sampling ran (a release's, as the next acquire began), it sleeps
		// what is left.
		w.sleep(time.Until(end))
		select {
		case <-stop:
			return true
		default:
		}
		if time.Until(end) <= 0 {
			return false
		}
	}
}

// waker ends the sampler's sleep (sleepFor) at the release of its last user,
// so the release returns once the sampling thread runs, not at its next
// look: an eventfd the release writes to and the sleep polls along with its
// timer. One per sampler, made at its first acquire and never closed, so no
// sleep ever polls a number the process has since given another file. fd < 0:
// the kernel made none, and the sleep wakes a Tick at a time to look.
type waker struct{ fd int }

// newWaker makes a sampler's waker.
func newWaker() *waker {
	fd, err := unix.Eventfd(0, unix.EFD_CLOEXEC|unix.EFD_NONBLOCK)
	if err != nil {
		return &waker{fd: -1}
	}
	return &waker{fd: fd}
}

// wake ends the sleep in progress, or the next one. Without an eventfd it
// writes to none (EBADF).
func (w *waker) wake() {
	var one [8]byte
	binary.NativeEndian.PutUint64(one[:], 1)
	_, _ = unix.Write(w.fd, one[:])
}

// take takes the wakes written so far, if any, without waiting (the eventfd
// does not block). Without an eventfd it reads none (EBADF).
func (w *waker) take() {
	var b [8]byte
	_, _ = unix.Read(w.fd, b[:])
}

// sleep sleeps the calling thread in the kernel for d at most (none if d is
// not positive: the kernel refuses a negative time), and ends at a wake,
// which it takes. Without an eventfd it sleeps a Tick at most.
func (w *waker) sleep(d time.Duration) {
	if w.fd < 0 {
		ts := unix.NsecToTimespec(int64(min(d, Tick)))
		_ = unix.Nanosleep(&ts, nil)
		return
	}
	ts := unix.NsecToTimespec(int64(d))
	fds := []unix.PollFd{{Fd: int32(w.fd), Events: unix.POLLIN}}
	if n, _ := unix.Ppoll(fds, &ts, nil); n > 0 {
		w.take()
	}
}

// threadRunDelay is the calling thread's run delay so far (schedstat); false
// if the kernel keeps none.
func threadRunDelay() (time.Duration, bool) {
	b, err := os.ReadFile("/proc/thread-self/schedstat")
	if err != nil {
		return 0, false
	}
	_, delay, _, ok := parseSchedstat(b)
	return time.Duration(delay), ok
}

// readThread reads the account of thread tid, in tasksDir; false if it has
// exited or the kernel keeps none.
func readThread(tasksDir, tid string) (threadStat, bool) {
	var st threadStat
	b, err := os.ReadFile(tasksDir + "/" + tid + "/schedstat")
	if err != nil {
		return st, false
	}
	var ok bool
	if st.exec, st.delay, st.runs, ok = parseSchedstat(b); !ok {
		return st, false
	}
	if b, err = os.ReadFile(tasksDir + "/" + tid + "/stat"); err != nil {
		return st, false
	}
	st.runnable, st.cpu, ok = parseStat(b)
	return st, ok
}

// parseSchedstat reads schedstat's three fields: time on a CPU, run delay,
// times run; all counters.
func parseSchedstat(b []byte) (exec, delay, runs int64, ok bool) {
	f := bytes.Fields(b)
	if len(f) < 3 {
		return 0, 0, 0, false
	}
	var ok1, ok2, ok3 bool
	exec, ok1 = parseInt(f[0])
	delay, ok2 = parseInt(f[1])
	runs, ok3 = parseInt(f[2])
	return exec, delay, runs, ok1 && ok2 && ok3
}

// parseStat reads a thread's stat, "TID (comm) STATE ...": whether it is
// runnable (R), and the CPU it last ran on (the 39th field), by its
// /proc/stat name ("cpu3"). comm may hold spaces and parentheses, so the
// fields count from its last ')'.
func parseStat(b []byte) (runnable bool, cpu string, ok bool) {
	i := bytes.LastIndexByte(b, ')')
	if i < 0 {
		return false, "", false
	}
	g := bytes.Fields(b[i+1:])
	if len(g) < 37 {
		return false, "", false
	}
	if _, ok := parseInt(g[36]); !ok {
		return false, "", false
	}
	return string(g[0]) == "R", "cpu" + string(g[36]), true
}

// parseSteal reads each CPU's steal so far from /proc/stat, by its name
// ("cpu3"), in USER_HZ ticks.
func parseSteal(b []byte) map[string]int64 {
	steal := make(map[string]int64)
	for line := range bytes.SplitSeq(b, []byte{'\n'}) {
		// cpuN user nice system idle iowait irq softirq steal ...
		f := bytes.Fields(line)
		if len(f) < 9 || !bytes.HasPrefix(f[0], []byte("cpu")) || len(f[0]) == 3 {
			continue
		}
		if s, ok := parseInt(f[8]); ok {
			steal[string(f[0])] = s
		}
	}
	return steal
}

func parseInt(b []byte) (int64, bool) {
	n, err := strconv.ParseInt(string(b), 10, 64)
	return n, err == nil
}