hostload

package
v1.2.11 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package hostload measures how busy a machine is, as a percent of all its cores: the CPU busy percent over an interval (the number Activity Monitor and top print), or, where that cannot be measured, the one-minute load average over the logical cores. A load average counts threads waiting as well as running, so it passes 100% on a machine that is not saturated; it is the fallback only.

Linux reads /proc/stat and /proc/loadavg. darwin asks top for its CPU usage line (a process of its own, so at most once every TopEvery) and reads vm.loadavg with sysctl, in Go. Elsewhere only a given value is known.

A member does not measure at its beat: a Sampler takes the busy percent once a second (Linux: /proc/stat counters, a read of a file; darwin: iostat, which takes its own second at about 4 ms of CPU, where top costs 300) and a Ring keeps the last ten, so the beat can carry the highest of them.

Every read of the machine is a Source function, so a caller measures with Local and a test with its own inputs; Measure is a pure function of the source, the state the previous measurement left, and the clock.

Beside the load, every measurement reads the machine's open file descriptors and, over a warn bound, the processes holding the most (files.go): the fleet beat carries the reading in the state it keeps.

Index

Constants

View Source
const (
	FilesWarnDefault  = 50000
	FilesAlarmDefault = 150000
)

The default bounds, the workshop tool's (fd-watch --warn and --alarm, 2026-10-03): a count of open descriptors above which the reading says warn, and alarm.

View Source
const (
	LevelOK    = "ok"
	LevelWarn  = "warn"
	LevelAlarm = "alarm"
)

The levels of a reading against its bounds.

View Source
const (
	HowCPU   = "cpu"   // CPU busy percent over an interval
	HowLoad1 = "load1" // one-minute load average over the logical cores
	HowGiven = "given" // a load another meter gave (fleet beat --load)
)

The ways a percent was measured.

View Source
const FilesTop = 10

FilesTop is how many holders a reading lists.

View Source
const HoldersEvery = 30 * time.Second

HoldersEvery is the least time between two reads of the holders: between them the last list stands.

View Source
const HoldersTimeout = 10 * time.Second

HoldersTimeout bounds one read of the holders, under a beat window: a read that runs over says so and the count stands with no list.

View Source
const MaxPercent = 1000.0

MaxPercent caps a measured percent: a load average over cores has no upper bound, and a cell wants a number that fits.

View Source
const RingSize = 10

RingSize is how many one-second samples the ring holds: the ten seconds the fleet table's load cell shows the highest of.

View Source
const TopEvery = 10 * time.Second

TopEvery is the least time between two runs of top on darwin: between them the last reading stands.

Variables

This section is empty.

Functions

func BusyPercent

func BusyPercent(prev, cur Ticks) (float64, bool)

BusyPercent is the busy percent between two readings of the counters: false when the counters did not move forward (the same reading, or a reboot between them).

func FilesBounds

func FilesBounds(src Source) (warn, alarm int)

FilesBounds is a source's bounds, each zero or less its default.

func HoldersTimedOut

func HoldersTimedOut(tool string) error

HoldersTimedOut is the reason a holders' read that ran past HoldersTimeout gives.

func LoadPercent

func LoadPercent(load1 float64, ncpu int) float64

LoadPercent is a load average over the logical cores as a percent, capped at MaxPercent; ncpu zero or less is runtime.NumCPU.

func ParseFileNr

func ParseFileNr(s string) (open, limit int, ok bool)

ParseFileNr is Linux's /proc/sys/fs/file-nr: the handles allocated, the unused (0 since 2.6) and the maximum.

func ParseProcLoadavg

func ParseProcLoadavg(s string) (float64, bool)

ParseProcLoadavg is the one-minute load average, the first field of /proc/loadavg ("0.52 0.58 0.59 1/389 12345").

func ParseTopCPU

func ParseTopCPU(s string) (float64, bool)

ParseTopCPU is darwin top's "CPU usage: a% user, b% sys, c% idle" line; busy is 100 minus idle.

Types

type Files

type Files struct {
	At     time.Time `json:"at"`
	Open   int       `json:"open"`
	Max    int       `json:"max,omitempty"`
	Warn   int       `json:"warn"`
	Alarm  int       `json:"alarm"`
	Top    []Holder  `json:"top,omitempty"`
	TopAt  time.Time `json:"top_at,omitzero"`
	TopErr string    `json:"top_err,omitempty"`
}

Files is one reading of the machine's open file descriptors: when it was taken, the count and the system's limit (0 when not known), the bounds it was read against, and, over the warn bound, the processes holding the most, most first, when they were read, or why they could not be.

func MeasureFiles

func MeasureFiles(src Source, prev *Files, now time.Time) *Files

MeasureFiles is the reading at now: nil when the source cannot count the machine's descriptors. Over the warn bound it lists the top holders, read again once HoldersEvery has passed since the last read (the last reading's, prev), else kept.

func (Files) Level

func (f Files) Level() string

Level is the reading against its bounds: alarm above the alarm bound, warn above the warn bound, else ok.

func (Files) TopText

func (f Files) TopText() string

TopText is the holders as one line, most first, or why they are not listed.

type Holder

type Holder struct {
	PID     int    `json:"pid"`
	Command string `json:"cmd"`
	User    string `json:"user,omitempty"`
	Open    int    `json:"open"`
}

Holder is one process and how many file descriptors it has open.

func ProcHolders

func ProcHolders(root string, deadline time.Time) ([]Holder, error)

ProcHolders is every process under root (/proc) with its open descriptors: the entries of <pid>/fd, its command from <pid>/comm and its user from the owner of <pid>. A process this user cannot read, or that ended during the walk, is skipped; a walk still going at the deadline stops and says so.

func TopHolders

func TopHolders(hs []Holder, n int) []Holder

TopHolders is the n holders with the most open, most first, the lower pid first of a tie.

func (Holder) String

func (h Holder) String() string

String is the holder as the beat and the judgment say it, the workshop tool's words: "node pid=300 user=build fds=40000".

type Ring

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

Ring is the last RingSize samples of a busy percent, each capped at MaxPercent. The zero Ring is empty.

func (*Ring) Add

func (r *Ring) Add(pct float64)

Add puts one sample in, in place of the oldest once the ring is full.

func (*Ring) Count

func (r *Ring) Count() uint64

Count is how many samples were ever added.

func (*Ring) MaxSince

func (r *Ring) MaxSince(count uint64) (float64, bool)

MaxSince is the highest of the samples added after the first count of them, at most the RingSize held; false when none was added since.

type Sampler

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

Sampler takes the machine's busy percent once a second into a Ring. Its readings are the Source's: ProcStat counters (the percent between two readings), else CPUSecond (a meter that takes its own second). Safe for use from the goroutine that runs it and the one that reads it.

func NewSampler

func NewSampler(src Source) *Sampler

NewSampler is a Sampler over src with nothing sampled yet.

func (*Sampler) Peak

func (s *Sampler) Peak(since uint64) (pct float64, total uint64, ok bool)

Peak is the highest sample added after the first since of them (at most the last RingSize), and how many samples there have been in all, which the caller passes as since next time once its beat is written; false when none is new.

func (*Sampler) Run

func (s *Sampler) Run(ctx context.Context)

Run steps once a second until ctx ends. A source that takes its own second sets the pace; one that does not is waited on for the rest of the second.

func (*Sampler) Step

func (s *Sampler) Step() (float64, bool)

Step takes one reading and, when it gives a percent, adds it to the ring and returns it. With CPUSecond it takes about a second; with ProcStat it returns at once and the percent is the busy share of the ticks since the last Step (false on the first).

type Source

type Source struct {
	// NCPU is the logical cores; zero is runtime.NumCPU.
	NCPU int
	// ProcStat is the text of /proc/stat (Linux).
	ProcStat func() (string, error)
	// Top is the output of top's one-sample header (darwin).
	Top func() (string, error)
	// CPUSecond is the busy percent over the next second, taking that second
	// (darwin: iostat). The Sampler's source where there is no ProcStat.
	CPUSecond func() (float64, error)
	// Load1 is the one-minute load average.
	Load1 func() (float64, bool)
	// Given is a load another meter gave, a percent of all the cores (fleet beat
	// --load: a member's one-second samples): the measurement's load, HowGiven,
	// with the open files measured beside it.
	Given func() (float64, bool)
	// OpenFiles is the system's open file descriptors and its limit (0 when not
	// known): darwin's kern.num_files and kern.maxfiles, Linux's
	// /proc/sys/fs/file-nr.
	OpenFiles func() (open, limit int, err error)
	// Holders is every process this user can see with its open descriptors
	// (darwin: lsof; Linux: /proc/<pid>/fd), bounded by HoldersTimeout; read only
	// over the warn bound.
	Holders func() ([]Holder, error)
	// FilesWarn and FilesAlarm are the bounds of the open descriptors, each zero
	// or less its default (FilesWarnDefault, FilesAlarmDefault).
	FilesWarn, FilesAlarm int
}

Source is how a machine is read. A nil function is a reading this machine does not have.

func Local

func Local() Source

Local is this machine's source.

type State

type State struct {
	Ticks  *Ticks    `json:"ticks,omitempty"`
	TopAt  time.Time `json:"top_at,omitzero"`
	TopPct float64   `json:"top_pct,omitempty"`
	Files  *Files    `json:"files,omitempty"`
}

State is what one measurement leaves for the next: the last CPU counters (Linux, where the busy percent is the change between two readings), and the last top reading and when it was taken (darwin), and the last reading of the machine's open file descriptors (files.go), whose holders stand for HoldersEvery.

func Measure

func Measure(src Source, prev State, now time.Time) (pct float64, how string, next State, ok bool)

Measure is the machine's load as a percent of all its cores at now, how it was measured, and the state for the next measurement, which carries the machine's open file descriptors read at now (MeasureFiles; nil when they cannot be read). ok is false when no load could be measured.

type Ticks

type Ticks struct {
	Busy  uint64 `json:"busy"`
	Total uint64 `json:"total"`
}

Ticks is one reading of the kernel's cumulative CPU counters: busy is every tick but idle and iowait, total is every tick.

func ParseProcStat

func ParseProcStat(s string) (Ticks, bool)

ParseProcStat is the cpu line of /proc/stat: user nice system idle iowait irq softirq steal guest guest_nice. guest and guest_nice overlap user and nice (Linux account_guest_time adds each guest interval to both USER/NICE and GUEST/GUEST_NICE, so /proc/stat emits the redundant fields), so they are parsed and validated but omitted from the total: counting them would sum the guest time twice and inflate the load. The guest tick stays inside user/nice, which is busy, so busy is every tick but idle and iowait as before.

Jump to

Keyboard shortcuts

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