procsched

package
v0.0.0-...-7c0b2e9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 17, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Package procsched reads the kernel's per-thread scheduler accounting for a process from procfs and folds it into monotonic per-process totals.

It exists to tell two kinds of idle-CPU overhead apart (issue #1131): a process that wakes MORE often (timeslices and voluntary switches per second go up) from one whose wakeups COST more (run-queue delay per timeslice, CPU migrations and involuntary switches go up at the same wakeup rate). The Go runtime's own metrics cannot say which: they see goroutines, not the threads the kernel schedules.

The kernel keeps these counters per THREAD — /proc/<pid>/schedstat and the ctxt_switches lines of /proc/<pid>/status describe only the thread-group leader — so Reader walks /proc/<pid>/task. A thread that exits takes its counts with it; Accumulator turns the per-thread readings into totals that never go backwards. Everything is a plain file read under a configurable root (the tests drive it from a fake tree); nothing needs ptrace or privileges for the caller's own process.

The supervisor's stall sampler (agent/internal/proxy/hotrestart/procstat.go, #1093/#1100) reads the same files for Envoy's threads. It lists them with Reader.ThreadIDs and parses their schedstat with ParseSchedstat, so the two cannot disagree about the kernel's format; what it samples besides (thread STATE, wait channels, per-CPU node load) is its own and stays there.

Index

Constants

This section is empty.

Variables

View Source
var ErrUnsupported = errors.New("procfs scheduler accounting unavailable")

ErrUnsupported reports that procfs scheduler accounting is not available here (not Linux, or /proc not mounted).

Functions

func ParseSchedstat

func ParseSchedstat(b []byte) (cpu, runq, slices uint64, err error)

ParseSchedstat parses the content of a /proc/<pid>/task/<tid>/schedstat file, "<cpu_ns> <runq_ns> <timeslices>": nanoseconds on a CPU, nanoseconds runnable but waiting for one, and the number of timeslices run. The kernel always writes all three (three zeros when scheduler accounting is off), so fewer is an error.

Types

type Accumulator

type Accumulator struct {
	// contains filtered or unexported fields
}

Accumulator folds successive per-thread readings into monotonic Totals: it adds each thread's growth since its previous reading and so keeps the contribution of threads that have since exited. It is safe for concurrent use (an OTel callback runs once per metric reader).

func (*Accumulator) Update

func (a *Accumulator) Update(threads map[int]ThreadCounters) Totals

Update adds a reading and returns the new totals.

type Reader

type Reader struct {
	// Root is the procfs mount point; empty means "/proc".
	Root string
	// PID is the process directory under Root; empty means "self".
	PID string
}

Reader reads the scheduler counters of every thread of one process.

func (Reader) Probe

func (r Reader) Probe() error

Probe checks that r can read at least one thread, wrapping ErrUnsupported when the task directory does not exist.

func (Reader) ThreadIDs

func (r Reader) ThreadIDs() ([]int, error)

ThreadIDs lists the process's thread IDs: the numeric entries of its task directory. A process that is gone yields an error wrapping os.ErrNotExist.

func (Reader) Threads

func (r Reader) Threads() (map[int]ThreadCounters, error)

Threads returns the counters of every live thread, keyed by thread ID. A thread that exits between the directory listing and its reads is left out. It fails only when the task directory cannot be listed, or no thread at all could be read.

type ThreadCounters

type ThreadCounters struct {
	// CPUNs and RunDelayNs are the first two fields of
	// /proc/<pid>/task/<tid>/schedstat: nanoseconds spent on a CPU and
	// nanoseconds spent runnable but waiting for one. Timeslices is the third:
	// how many times the thread was put on a CPU — one per wakeup, plus one per
	// preemption that got it back.
	CPUNs      uint64
	RunDelayNs uint64
	Timeslices uint64
	// Voluntary and Involuntary are the ctxt_switches lines of
	// /proc/<pid>/task/<tid>/status: switches the thread asked for (it blocked or
	// slept) and switches forced on it (preempted).
	Voluntary   uint64
	Involuntary uint64
	// Migrations is se.nr_migrations from /proc/<pid>/task/<tid>/sched: how many
	// times the thread was moved to another CPU. That file exists only on a
	// kernel built with CONFIG_SCHED_DEBUG; HasMigrations says whether it did.
	Migrations    uint64
	HasMigrations bool
}

ThreadCounters is one reading of one thread's cumulative counters.

type Totals

type Totals struct {
	CPUNs       uint64
	RunDelayNs  uint64
	Timeslices  uint64
	Voluntary   uint64
	Involuntary uint64
	Migrations  uint64
	// HasMigrations is whether any thread's migrations were readable.
	HasMigrations bool
	// Threads is the number of threads in the latest reading.
	Threads int
}

Totals are process-wide counters summed over every thread ever observed. They never decrease.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL