Files
mesh-catalog/modules/disk-load/cmd/disk-load/machine.go
T
jschoubben 8288042eb7
mesh/merge-gate building on shanks since 01:09
mesh/check failed: unit: clipmenu/cmd/clipmenu-tools.TestTheLauncherWaitsQuietlyWhileAnotherClipmenudHoldsTheLock: manifest_test.go:199: the launcher …
mesh/delivery superseded: a newer head of the same pull request
Add disk_folders to disk-load: no tool said which folders fill a disk (issue 1804)
2026-10-22 01:02:31 +02:00

133 lines
3.7 KiB
Go

package main
import (
"bytes"
"context"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"time"
)
// CommandTimeout bounds the one command this module runs (the container runtime's list of names).
const CommandTimeout = 5 * time.Second
// StatfsTimeout bounds the look at one mount: a network filesystem whose server is gone blocks statfs
// for as long as it likes, and must cost the answer one line, never the whole call.
const StatfsTimeout = 2 * time.Second
// Runner runs one command and answers its standard output. It is injected so that the tools are tested
// against recorded answers rather than this machine's daemons.
type Runner func(ctx context.Context, name string, args ...string) (string, error)
// ExecRunner runs a command on the machine, bounded by CommandTimeout.
func ExecRunner(ctx context.Context, name string, args ...string) (string, error) {
ctx, cancel := context.WithTimeout(ctx, CommandTimeout)
defer cancel()
cmd := exec.CommandContext(ctx, name, args...)
var stdout, stderr bytes.Buffer
cmd.Stdout, cmd.Stderr = &stdout, &stderr
if err := cmd.Run(); err != nil {
said := strings.TrimSpace(stderr.String())
if said != "" {
return "", fmt.Errorf("%s: %w: %s", name, err, said)
}
return "", fmt.Errorf("%s: %w", name, err)
}
return stdout.String(), nil
}
// FSStat is what statfs says of one mounted filesystem, in bytes and inodes.
type FSStat struct {
Size, Free, Avail uint64
Files, FilesFree uint64
}
// Machine is what the module reads: a filesystem root (the real one, or a test's tree standing in for
// /proc and /sys), a way to run a command, a way to wait out the sampling window, and statfs.
type Machine struct {
Root string
Run Runner
Wait func(time.Duration)
Statfs func(path string) (FSStat, error)
// The folder walk's seams, all optional: the folder reader, the filesystem a path is on, the clock and
// the time the walk may take (60 s when zero).
ReadDir func(path string) ([]os.DirEntry, error)
DevOf func(path string, info fs.FileInfo) uint64
Clock func() time.Time
Budget time.Duration
}
// Here is the machine this process runs on.
func Here() *Machine {
return &Machine{Root: "/", Run: ExecRunner, Wait: time.Sleep, Statfs: statfs}
}
func statfs(path string) (FSStat, error) {
type answer struct {
s FSStat
err error
}
done := make(chan answer, 1)
go func() {
var s syscall.Statfs_t
if err := syscall.Statfs(path, &s); err != nil {
done <- answer{err: err}
return
}
bs := uint64(s.Bsize)
done <- answer{s: FSStat{Size: s.Blocks * bs, Free: s.Bfree * bs, Avail: s.Bavail * bs, Files: s.Files, FilesFree: s.Ffree}}
}()
select {
case a := <-done:
return a.s, a.err
case <-time.After(StatfsTimeout):
return FSStat{}, fmt.Errorf("statfs did not answer within %s", StatfsTimeout)
}
}
func (m *Machine) path(p string) string { return filepath.Join(m.Root, p) }
// read is a file's content; "" when it cannot be read.
func (m *Machine) read(p string) string {
b, err := os.ReadFile(m.path(p))
if err != nil {
return ""
}
return string(b)
}
func (m *Machine) exists(p string) bool {
_, err := os.Stat(m.path(p))
return err == nil
}
// names lists the entries of a directory, sorted; none when it cannot be read.
func (m *Machine) names(dir string) []string {
entries, err := os.ReadDir(m.path(dir))
if err != nil {
return nil
}
out := make([]string, 0, len(entries))
for _, e := range entries {
out = append(out, e.Name())
}
return out
}
// round1 rounds to one decimal, for what a person reads.
func round1(f float64) float64 {
if f < 0 {
return -float64(int64(-f*10+0.5)) / 10
}
return float64(int64(f*10+0.5)) / 10
}
const mib = 1 << 20
const gib = 1 << 30