package api
import (
"bytes"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
// clockTicks is USER_HZ, which /proc's start times are counted in. It
// is 100 on every Linux this forge runs on; reading it needs cgo.
const clockTicks = 100
// childProcsIn lists a process's own zit children from /proc: verb,
// start time and resident memory. A process that ends mid-read is
// skipped, which is the truth by the time anyone reads the panel.
// childProcsIn reads a /proc tree rooted at root for the zit children
// of self. Split from childProcs so a test can hand it a tree of its own.
func childProcsIn(root string, self int) []childProc {
boot := bootTime(root)
if boot.IsZero() {
return nil
}
ents, err := os.ReadDir(root)
if err != nil {
return nil
}
page := uint64(os.Getpagesize())
var out []childProc
for _, e := range ents {
pid, err := strconv.Atoi(e.Name())
if err != nil {
continue
}
dir := filepath.Join(root, e.Name())
stat, err := os.ReadFile(filepath.Join(dir, "stat"))
if err != nil {
continue
}
// The command name is parenthesised and may hold spaces, so the
// fields are read after its LAST closing parenthesis.
i := bytes.LastIndexByte(stat, ')')
if i < 0 {
continue
}
f := strings.Fields(string(stat[i+1:]))
// f[0] is the state, f[1] the parent, f[19] the start time.
if len(f) < 20 {
continue
}
if ppid, _ := strconv.Atoi(f[1]); ppid != self {
continue
}
args := bytes.Split(bytes.TrimRight(mustRead(filepath.Join(dir, "cmdline")), "\x00"), []byte{0})
if len(args) < 2 || filepath.Base(string(args[0])) != "zit" {
continue
}
ticks, _ := strconv.ParseUint(f[19], 10, 64)
p := childProc{PID: pid, Verb: string(args[1]),
Start: boot.Add(time.Duration(ticks) * time.Second / clockTicks)}
if sm := strings.Fields(string(mustRead(filepath.Join(dir, "statm")))); len(sm) > 1 {
if res, err := strconv.ParseUint(sm[1], 10, 64); err == nil {
p.RSS = res * page
}
}
out = append(out, p)
}
return out
}
func mustRead(path string) []byte {
b, _ := os.ReadFile(path)
return b
}
// bootTime is when the machine booted, from /proc/stat's btime.
func bootTime(root string) time.Time {
b, err := os.ReadFile(filepath.Join(root, "stat"))
if err != nil {
return time.Time{}
}
for _, line := range strings.Split(string(b), "\n") {
if rest, ok := strings.CutPrefix(line, "btime "); ok {
if s, err := strconv.ParseInt(strings.TrimSpace(rest), 10, 64); err == nil {
return time.Unix(s, 0)
}
}
}
return time.Time{}
}