Files
mesh-catalog/modules/memory-pressure/cmd/memory-pressure/machine.go
T
jochen b098b64b22 Phase 3: asus-zephyrus-g14 and memory-pressure, with Go tools and long-running code
The laptop model's hardware module and a memory-pressure module for any
machine (hq research 027/03, 026/05, to-be 42 phase 3). The predecessor's
polling auto-profile and mem-guard user scripts become each module's own
Go code launched by the node runtime (ADR 0198): a profile switcher woken
by the kernel's power-supply uevents, and a guard that warns on RAM, swap
or PSI before systemd-oomd acts, on the desktop over the account's bus and
always as an event. supergfxctl and triggerhappy are kept as found
(research 027 Q1).
2026-10-04 15:42:56 +02:00

107 lines
3.0 KiB
Go

package main
import (
"bytes"
"context"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
)
// CommandTimeout bounds every command a tool or the guard runs: a journal that takes long to search
// must cost a tool call twenty seconds, never the runtime's thirty.
const CommandTimeout = 20 * time.Second
// Runner runs one command and answers its standard output. It is injected so that every tool is
// 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. A failure carries what the
// command said on stderr, because "exit status 1" names nothing.
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
err := cmd.Run()
if ctx.Err() == context.DeadlineExceeded {
return stdout.String(), fmt.Errorf("%s did not answer within %s", name, CommandTimeout)
}
if err != nil {
said := strings.TrimSpace(stderr.String())
if said == "" {
said = strings.TrimSpace(stdout.String())
}
if said != "" {
return stdout.String(), fmt.Errorf("%s %s: %w: %s", name, strings.Join(args, " "), err, said)
}
return stdout.String(), fmt.Errorf("%s %s: %w", name, strings.Join(args, " "), err)
}
return stdout.String(), nil
}
// Machine is what the module reads and acts on: a filesystem root (the real one, or a test's tree of
// /proc, /sys and /etc) and a way to run commands.
type Machine struct {
Root string
Run Runner
}
// Here is the machine this process runs on.
func Here() *Machine { return &Machine{Root: "/", Run: ExecRunner} }
func (m *Machine) path(p string) string { return filepath.Join(m.Root, p) }
// read is a file's content, trimmed; "" when it cannot be read.
func (m *Machine) read(p string) string {
b, err := os.ReadFile(m.path(p))
if err != nil {
return ""
}
return strings.TrimSpace(string(b))
}
// readInt is a file holding one integer; ok false when it is absent or not a number.
func (m *Machine) readInt(p string) (int64, bool) {
s := m.read(p)
if s == "" {
return 0, false
}
n, err := strconv.ParseInt(s, 10, 64)
return n, err == nil
}
func (m *Machine) glob(pattern string) []string {
found, _ := filepath.Glob(m.path(pattern))
out := make([]string, 0, len(found))
for _, f := range found {
rel, err := filepath.Rel(m.Root, f)
if err != nil {
continue
}
out = append(out, "/"+filepath.ToSlash(rel))
}
return out
}
// notInstalled says a command failed because it is not on this machine at all.
func notInstalled(err error) bool { return errors.Is(err, exec.ErrNotFound) }
// round to one decimal, for watts and percentages a person reads.
func round1(f float64) float64 {
return float64(int64(f*10+sign(f)*0.5)) / 10
}
func sign(f float64) float64 {
if f < 0 {
return -1
}
return 1
}