docs: publish Preview 19 dogfood evidence

Export the reviewed allowlisted snapshot from private source commit 05928cebd01b586cf9e9d4b8c8537a7605a6068c. This records the exact candidate, bounded capacity result, stateful migration scratch requirement, authenticated batch identity proof, and immediate live acceptance evidence.

AI-Assisted: OpenAI Codex
Signed-off-by: Cole Speelman <crspeelman@gmail.com>
This commit is contained in:
2026-08-18 21:47:08 -04:00
commit 92a66db3df
201 changed files with 38227 additions and 0 deletions
+487
View File
@@ -0,0 +1,487 @@
// SPDX-License-Identifier: AGPL-3.0-only
package hostmetrics
import (
"bufio"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"
"time"
"unicode/utf8"
"gamertan.com/observatory/internal/model"
)
const maxSourceBytes = 1 << 20
type Config struct {
ProcRoot string `json:"proc_root"`
CgroupRoot string `json:"cgroup_root,omitempty"`
Filesystems []Filesystem `json:"filesystems,omitempty"`
Processes []Process `json:"processes,omitempty"`
ControlGroups []Cgroup `json:"cgroups,omitempty"`
}
type Filesystem struct {
Name string `json:"name"`
Path string `json:"path"`
}
type Process struct {
Name string `json:"name"`
PIDFile string `json:"pid_file"`
}
type Cgroup struct {
Name string `json:"name"`
Path string `json:"path"`
}
func (configuration Config) Validate() error {
if runtime.GOOS != "linux" {
return errors.New("Linux metrics require Linux")
}
if err := absoluteClean("proc_root", configuration.ProcRoot); err != nil {
return err
}
if configuration.CgroupRoot != "" {
if err := absoluteClean("cgroup_root", configuration.CgroupRoot); err != nil {
return err
}
}
if len(configuration.Filesystems) > 32 || len(configuration.Processes) > 32 || len(configuration.ControlGroups) > 32 {
return errors.New("Linux metric selector limit exceeded")
}
names := map[string]bool{}
for _, filesystem := range configuration.Filesystems {
if err := selectorName(filesystem.Name, names); err != nil {
return fmt.Errorf("filesystem: %w", err)
}
if err := absoluteClean("filesystem path", filesystem.Path); err != nil {
return err
}
}
for _, process := range configuration.Processes {
if err := selectorName(process.Name, names); err != nil {
return fmt.Errorf("process: %w", err)
}
if err := absoluteClean("pid_file", process.PIDFile); err != nil {
return err
}
}
for _, group := range configuration.ControlGroups {
if err := selectorName(group.Name, names); err != nil {
return fmt.Errorf("cgroup: %w", err)
}
if configuration.CgroupRoot == "" {
return errors.New("cgroup_root is required when cgroups are selected")
}
if group.Path == "" || filepath.IsAbs(group.Path) || filepath.Clean(group.Path) != group.Path || group.Path == "." || strings.HasPrefix(group.Path, ".."+string(os.PathSeparator)) {
return errors.New("cgroup path must be a clean relative path below cgroup_root")
}
}
return nil
}
func Collect(configuration Config, now time.Time) ([]model.Observation, error) {
if err := configuration.Validate(); err != nil {
return nil, err
}
if now.IsZero() {
return nil, errors.New("collection time is required")
}
if err := secureDirectory(configuration.ProcRoot); err != nil {
return nil, errors.New("proc_root is unavailable")
}
var observations []model.Observation
var problems []error
appendResult := func(result []model.Observation, err error) {
observations = append(observations, result...)
if err != nil {
problems = append(problems, err)
}
}
result, err := collectStat(configuration.ProcRoot, now)
appendResult(result, err)
result, err = collectMemory(configuration.ProcRoot, now)
appendResult(result, err)
result, err = collectLoad(configuration.ProcRoot, now)
appendResult(result, err)
result, err = collectNetwork(configuration.ProcRoot, now)
appendResult(result, err)
for _, filesystem := range configuration.Filesystems {
result, err = collectFilesystem(filesystem, now)
appendResult(result, err)
}
for _, process := range configuration.Processes {
result, err = collectProcess(configuration.ProcRoot, process, now)
appendResult(result, err)
}
for _, group := range configuration.ControlGroups {
result, err = collectCgroup(configuration.CgroupRoot, group, now)
appendResult(result, err)
}
if len(observations) > model.MaxRecords {
return nil, errors.New("Linux metric record limit exceeded")
}
return observations, errors.Join(problems...)
}
func collectStat(root string, now time.Time) ([]model.Observation, error) {
body, err := readBounded(filepath.Join(root, "stat"))
if err != nil {
return nil, errors.New("read proc stat")
}
var observations []model.Observation
for _, line := range strings.Split(string(body), "\n") {
fields := strings.Fields(line)
if len(fields) < 5 || fields[0] != "cpu" {
continue
}
states := []string{"user", "nice", "system", "idle", "iowait", "irq", "softirq", "steal", "guest", "guest_nice"}
for index := 1; index < len(fields) && index <= len(states); index++ {
value, parseErr := strconv.ParseFloat(fields[index], 64)
if parseErr != nil {
return nil, errors.New("proc stat contains an invalid CPU counter")
}
observations = append(observations, metric(now, "system.cpu.time_ticks", value, "ticks", map[string]string{"state": states[index-1]}))
}
break
}
uptime, err := readBounded(filepath.Join(root, "uptime"))
if err == nil {
fields := strings.Fields(string(uptime))
if len(fields) > 0 {
if value, parseErr := strconv.ParseFloat(fields[0], 64); parseErr == nil {
observations = append(observations, metric(now, "system.uptime", value, "seconds", nil))
}
}
}
if len(observations) == 0 {
return nil, errors.New("proc stat contains no aggregate CPU record")
}
return observations, nil
}
func collectMemory(root string, now time.Time) ([]model.Observation, error) {
body, err := readBounded(filepath.Join(root, "meminfo"))
if err != nil {
return nil, errors.New("read proc meminfo")
}
wanted := map[string]string{"MemTotal": "total", "MemAvailable": "available", "SwapTotal": "swap_total", "SwapFree": "swap_free"}
var observations []model.Observation
for _, line := range strings.Split(string(body), "\n") {
fields := strings.Fields(line)
if len(fields) < 2 {
continue
}
state, ok := wanted[strings.TrimSuffix(fields[0], ":")]
if !ok {
continue
}
value, parseErr := strconv.ParseFloat(fields[1], 64)
if parseErr != nil {
return nil, errors.New("proc meminfo contains an invalid value")
}
if len(fields) > 2 && fields[2] == "kB" {
value *= 1024
}
observations = append(observations, metric(now, "system.memory", value, "bytes", map[string]string{"state": state}))
}
if len(observations) != len(wanted) {
return observations, errors.New("proc meminfo is missing required values")
}
return observations, nil
}
func collectLoad(root string, now time.Time) ([]model.Observation, error) {
body, err := readBounded(filepath.Join(root, "loadavg"))
if err != nil {
return nil, errors.New("read proc loadavg")
}
fields := strings.Fields(string(body))
if len(fields) < 3 {
return nil, errors.New("proc loadavg is incomplete")
}
periods := []string{"1m", "5m", "15m"}
observations := make([]model.Observation, 0, 3)
for index := range periods {
value, parseErr := strconv.ParseFloat(fields[index], 64)
if parseErr != nil {
return nil, errors.New("proc loadavg contains an invalid value")
}
observations = append(observations, metric(now, "system.load.average", value, "1", map[string]string{"period": periods[index]}))
}
return observations, nil
}
func collectNetwork(root string, now time.Time) ([]model.Observation, error) {
body, err := readBounded(filepath.Join(root, "net", "dev"))
if err != nil {
return nil, errors.New("read proc network counters")
}
var observations []model.Observation
for _, line := range strings.Split(string(body), "\n") {
separator := strings.IndexByte(line, ':')
if separator < 0 {
continue
}
name := strings.TrimSpace(line[:separator])
if !safeLabel(name) {
return nil, errors.New("proc network interface name is invalid")
}
fields := strings.Fields(line[separator+1:])
if len(fields) != 16 {
return nil, errors.New("proc network counter record is invalid")
}
indexes := []struct {
field int
name string
dir string
}{{0, "system.network.bytes", "receive"}, {1, "system.network.packets", "receive"}, {3, "system.network.dropped_packets", "receive"}, {8, "system.network.bytes", "transmit"}, {9, "system.network.packets", "transmit"}, {11, "system.network.dropped_packets", "transmit"}}
for _, selected := range indexes {
value, parseErr := strconv.ParseFloat(fields[selected.field], 64)
if parseErr != nil {
return nil, errors.New("proc network counter is invalid")
}
unit := "1"
if selected.name == "system.network.bytes" {
unit = "bytes"
}
observations = append(observations, metric(now, selected.name, value, unit, map[string]string{"interface": name, "direction": selected.dir}))
}
if len(observations) > 128*6 {
return nil, errors.New("proc network interface limit exceeded")
}
}
return observations, nil
}
func collectFilesystem(selected Filesystem, now time.Time) ([]model.Observation, error) {
info, err := os.Lstat(selected.Path)
if err != nil || !info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
return nil, fmt.Errorf("filesystem %s is unavailable", selected.Name)
}
var status syscall.Statfs_t
if err = syscall.Statfs(selected.Path, &status); err != nil {
return nil, fmt.Errorf("inspect filesystem %s", selected.Name)
}
blockSize := float64(status.Bsize)
attributes := map[string]string{"filesystem": selected.Name}
return []model.Observation{
metric(now, "system.filesystem.bytes", float64(status.Blocks)*blockSize, "bytes", with(attributes, "state", "total")),
metric(now, "system.filesystem.bytes", float64(status.Bavail)*blockSize, "bytes", with(attributes, "state", "available")),
metric(now, "system.filesystem.inodes", float64(status.Files), "1", with(attributes, "state", "total")),
metric(now, "system.filesystem.inodes", float64(status.Ffree), "1", with(attributes, "state", "free")),
}, nil
}
func collectProcess(procRoot string, selected Process, now time.Time) ([]model.Observation, error) {
pidBody, err := readBounded(selected.PIDFile)
if err != nil {
return []model.Observation{metric(now, "process.up", 0, "1", map[string]string{"process": selected.Name})}, fmt.Errorf("process %s PID is unavailable", selected.Name)
}
pidText := strings.TrimSpace(string(pidBody))
pid, err := strconv.ParseUint(pidText, 10, 31)
if err != nil || pid == 0 {
return []model.Observation{metric(now, "process.up", 0, "1", map[string]string{"process": selected.Name})}, fmt.Errorf("process %s PID is invalid", selected.Name)
}
stat, err := readBounded(filepath.Join(procRoot, pidText, "stat"))
if err != nil {
return []model.Observation{metric(now, "process.up", 0, "1", map[string]string{"process": selected.Name})}, fmt.Errorf("process %s is unavailable", selected.Name)
}
closing := strings.LastIndexByte(string(stat), ')')
if closing < 2 {
return nil, fmt.Errorf("process %s stat is invalid", selected.Name)
}
fields := strings.Fields(string(stat)[closing+1:])
// fields begin with state (field 3); utime, stime, starttime, vsize, rss
// are therefore indexes 11, 12, 19, 20, and 21 in this slice.
if len(fields) < 22 {
return nil, fmt.Errorf("process %s stat is incomplete", selected.Name)
}
values := make([]float64, 5)
for index, field := range []int{11, 12, 19, 20, 21} {
values[index], err = strconv.ParseFloat(fields[field], 64)
if err != nil {
return nil, fmt.Errorf("process %s stat contains an invalid counter", selected.Name)
}
}
pageSize := float64(os.Getpagesize())
base := map[string]string{"process": selected.Name}
observations := []model.Observation{
metric(now, "process.up", 1, "1", base),
metric(now, "process.start_time_ticks", values[2], "ticks", base),
metric(now, "process.cpu.time_ticks", values[0], "ticks", with(base, "state", "user")),
metric(now, "process.cpu.time_ticks", values[1], "ticks", with(base, "state", "system")),
metric(now, "process.memory.virtual", values[3], "bytes", base),
metric(now, "process.memory.resident", values[4]*pageSize, "bytes", base),
}
if ioBody, ioErr := readBounded(filepath.Join(procRoot, pidText, "io")); ioErr == nil {
for _, line := range strings.Split(string(ioBody), "\n") {
fields := strings.Fields(line)
if len(fields) != 2 || fields[0] != "read_bytes:" && fields[0] != "write_bytes:" {
continue
}
value, parseErr := strconv.ParseFloat(fields[1], 64)
if parseErr != nil {
continue
}
direction := strings.TrimSuffix(fields[0], "_bytes:")
observations = append(observations, metric(now, "process.io.bytes", value, "bytes", with(base, "direction", direction)))
}
}
return observations, nil
}
func collectCgroup(root string, selected Cgroup, now time.Time) ([]model.Observation, error) {
directory, err := secureRelativeDirectory(root, selected.Path)
if err != nil {
return []model.Observation{metric(now, "cgroup.up", 0, "1", map[string]string{"cgroup": selected.Name})}, fmt.Errorf("cgroup %s is unavailable", selected.Name)
}
base := map[string]string{"cgroup": selected.Name}
observations := []model.Observation{metric(now, "cgroup.up", 1, "1", base)}
for _, scalar := range []struct {
file, name, unit, state string
}{{"memory.current", "cgroup.memory", "bytes", "current"}, {"memory.peak", "cgroup.memory", "bytes", "peak"}, {"memory.swap.current", "cgroup.memory", "bytes", "swap"}, {"pids.current", "cgroup.pids", "1", "current"}} {
body, readErr := readBounded(filepath.Join(directory, scalar.file))
if readErr != nil {
continue
}
value, parseErr := strconv.ParseFloat(strings.TrimSpace(string(body)), 64)
if parseErr == nil {
observations = append(observations, metric(now, scalar.name, value, scalar.unit, with(base, "state", scalar.state)))
}
}
if body, readErr := readBounded(filepath.Join(directory, "cpu.stat")); readErr == nil {
for _, line := range strings.Split(string(body), "\n") {
fields := strings.Fields(line)
if len(fields) != 2 || fields[0] != "usage_usec" && fields[0] != "user_usec" && fields[0] != "system_usec" {
continue
}
value, parseErr := strconv.ParseFloat(fields[1], 64)
if parseErr == nil {
observations = append(observations, metric(now, "cgroup.cpu.time", value, "microseconds", with(base, "state", strings.TrimSuffix(fields[0], "_usec"))))
}
}
}
if body, readErr := readBounded(filepath.Join(directory, "io.stat")); readErr == nil {
var readBytes, writeBytes float64
for _, line := range strings.Split(string(body), "\n") {
for _, field := range strings.Fields(line) {
key, text, found := strings.Cut(field, "=")
if !found || key != "rbytes" && key != "wbytes" {
continue
}
value, parseErr := strconv.ParseFloat(text, 64)
if parseErr != nil {
continue
}
if key == "rbytes" {
readBytes += value
} else {
writeBytes += value
}
}
}
observations = append(observations,
metric(now, "cgroup.io.bytes", readBytes, "bytes", with(base, "direction", "read")),
metric(now, "cgroup.io.bytes", writeBytes, "bytes", with(base, "direction", "write")),
)
}
return observations, nil
}
func metric(now time.Time, name string, value float64, unit string, attributes map[string]string) model.Observation {
copied := with(attributes, "unit", unit)
return model.Observation{Timestamp: now.UTC(), Name: name, Value: &value, Attributes: copied}
}
func with(source map[string]string, key, value string) map[string]string {
result := make(map[string]string, len(source)+1)
for existingKey, existingValue := range source {
result[existingKey] = existingValue
}
result[key] = value
return result
}
func readBounded(path string) ([]byte, error) {
before, err := os.Lstat(path)
if err != nil || !before.Mode().IsRegular() || before.Mode()&os.ModeSymlink != 0 || before.Size() > maxSourceBytes {
return nil, errors.New("metric source is not a bounded regular file")
}
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
after, err := file.Stat()
if err != nil || !after.Mode().IsRegular() || !os.SameFile(before, after) {
return nil, errors.New("metric source changed during open")
}
body, err := io.ReadAll(io.LimitReader(bufio.NewReader(file), maxSourceBytes+1))
if err != nil || len(body) > maxSourceBytes || !utf8.Valid(body) || strings.IndexByte(string(body), 0) >= 0 {
return nil, errors.New("metric source exceeds accepted bounds")
}
return body, nil
}
func secureDirectory(path string) error {
info, err := os.Lstat(path)
if err != nil || !info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
return errors.New("path is not a non-symlink directory")
}
return nil
}
func secureRelativeDirectory(root, relative string) (string, error) {
if err := secureDirectory(root); err != nil {
return "", err
}
current := root
for _, part := range strings.Split(filepath.ToSlash(relative), "/") {
current = filepath.Join(current, part)
if err := secureDirectory(current); err != nil {
return "", err
}
}
return current, nil
}
func absoluteClean(label, value string) error {
if !filepath.IsAbs(value) || filepath.Clean(value) != value {
return fmt.Errorf("%s must be absolute and clean", label)
}
return nil
}
func selectorName(value string, names map[string]bool) error {
if !safeLabel(value) {
return errors.New("selector name is invalid")
}
if names[value] {
return errors.New("selector name is duplicated")
}
names[value] = true
return nil
}
func safeLabel(value string) bool {
if value == "" || len(value) > 128 {
return false
}
for _, character := range value {
if !(character >= 'a' && character <= 'z' || character >= 'A' && character <= 'Z' || character >= '0' && character <= '9' || strings.ContainsRune("._-", character)) {
return false
}
}
return true
}
+158
View File
@@ -0,0 +1,158 @@
// SPDX-License-Identifier: AGPL-3.0-only
package hostmetrics
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
"gamertan.com/observatory/internal/model"
)
func TestCollectReadsOnlyConfiguredLinuxEvidence(t *testing.T) {
root := t.TempDir()
proc := filepath.Join(root, "proc")
cgroup := filepath.Join(root, "cgroup")
filesystem := filepath.Join(root, "filesystem")
pidFile := filepath.Join(root, "service.pid")
for _, directory := range []string{filepath.Join(proc, "net"), filepath.Join(proc, "123"), filepath.Join(cgroup, "system.slice", "example.service"), filesystem} {
if err := os.MkdirAll(directory, 0o700); err != nil {
t.Fatal(err)
}
}
write := func(path, body string) {
t.Helper()
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
t.Fatal(err)
}
}
write(filepath.Join(proc, "stat"), "cpu 100 2 30 400 5 6 7 8 9 10\n")
write(filepath.Join(proc, "uptime"), "99.5 80.0\n")
write(filepath.Join(proc, "meminfo"), "MemTotal: 1000 kB\nMemAvailable: 750 kB\nSwapTotal: 200 kB\nSwapFree: 150 kB\n")
write(filepath.Join(proc, "loadavg"), "0.10 0.20 0.30 1/100 123\n")
write(filepath.Join(proc, "net", "dev"), "Inter-| Receive | Transmit\nlo: 100 2 0 1 0 0 0 0 200 3 0 2 0 0 0 0\n")
write(pidFile, "123\n")
write(filepath.Join(proc, "123", "stat"), "123 (example worker) S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23\n")
write(filepath.Join(proc, "123", "io"), "read_bytes: 4096\nwrite_bytes: 8192\n")
group := filepath.Join(cgroup, "system.slice", "example.service")
write(filepath.Join(group, "memory.current"), "1024\n")
write(filepath.Join(group, "memory.peak"), "2048\n")
write(filepath.Join(group, "memory.swap.current"), "0\n")
write(filepath.Join(group, "pids.current"), "3\n")
write(filepath.Join(group, "cpu.stat"), "usage_usec 300\nuser_usec 200\nsystem_usec 100\n")
write(filepath.Join(group, "io.stat"), "8:0 rbytes=100 wbytes=200 rios=1 wios=2\n8:1 rbytes=300 wbytes=400 rios=3 wios=4\n")
configuration := Config{
ProcRoot: proc, CgroupRoot: cgroup,
Filesystems: []Filesystem{{Name: "data", Path: filesystem}},
Processes: []Process{{Name: "web", PIDFile: pidFile}},
ControlGroups: []Cgroup{{Name: "web-service", Path: filepath.Join("system.slice", "example.service")}},
}
now := time.Date(2026, 8, 17, 6, 0, 0, 0, time.UTC)
observations, err := Collect(configuration, now)
if err != nil {
t.Fatal(err)
}
wanted := map[string]bool{
"system.cpu.time_ticks": false, "system.memory": false, "system.load.average": false,
"system.network.bytes": false, "system.filesystem.bytes": false, "process.up": false,
"process.start_time_ticks": false, "process.io.bytes": false, "cgroup.up": false, "cgroup.io.bytes": false,
}
for _, observation := range observations {
if _, ok := wanted[observation.Name]; ok {
wanted[observation.Name] = true
}
for _, value := range observation.Attributes {
if strings.Contains(value, root) {
t.Fatalf("local path leaked in attributes: %+v", observation)
}
}
if _, found := observation.Attributes["start_ticks"]; found {
t.Fatalf("dynamic process identity leaked into metric attributes: %+v", observation)
}
}
for name, found := range wanted {
if !found {
t.Errorf("missing %s", name)
}
}
batch := model.Batch{Version: model.BatchVersion, SourceID: "source", StreamID: "host-metrics", Sequence: 1, ObservedAt: now, Signal: model.SignalMetrics, Records: observations}
if err = batch.Validate(now); err != nil {
t.Fatalf("collected batch: %v", err)
}
}
func TestCollectRejectsSymlinkMetricSource(t *testing.T) {
proc := createMinimalProc(t)
target := filepath.Join(t.TempDir(), "pid")
if err := os.WriteFile(target, []byte("123\n"), 0o600); err != nil {
t.Fatal(err)
}
link := filepath.Join(t.TempDir(), "selected.pid")
if err := os.Symlink(target, link); err != nil {
t.Fatal(err)
}
observations, err := Collect(Config{ProcRoot: proc, Processes: []Process{{Name: "selected", PIDFile: link}}}, time.Date(2026, 8, 17, 6, 0, 0, 0, time.UTC))
if err == nil {
t.Fatal("symlink PID source accepted without a collection warning")
}
for _, observation := range observations {
if observation.Name == "process.up" && observation.Value != nil && *observation.Value == 0 {
return
}
}
t.Fatal("rejected symlink source did not retain process.up=0 evidence")
}
func TestCollectReportsMissingSelectedProcessWithoutDroppingHostMetrics(t *testing.T) {
proc := createMinimalProc(t)
now := time.Date(2026, 8, 17, 6, 0, 0, 0, time.UTC)
observations, err := Collect(Config{ProcRoot: proc, Processes: []Process{{Name: "missing", PIDFile: filepath.Join(t.TempDir(), "missing.pid")}}}, now)
if err == nil || len(observations) == 0 {
t.Fatalf("observations=%d err=%v", len(observations), err)
}
foundDown := false
for _, observation := range observations {
if observation.Name == "process.up" && observation.Value != nil && *observation.Value == 0 {
foundDown = true
}
}
if !foundDown {
t.Fatal("missing process did not emit process.up=0")
}
}
func TestValidationRejectsEscapingAndDuplicateSelectors(t *testing.T) {
root := t.TempDir()
for _, configuration := range []Config{
{ProcRoot: "relative"},
{ProcRoot: root, CgroupRoot: root, ControlGroups: []Cgroup{{Name: "bad", Path: "../escape"}}},
{ProcRoot: root, Filesystems: []Filesystem{{Name: "same", Path: root}}, Processes: []Process{{Name: "same", PIDFile: filepath.Join(root, "pid")}}},
} {
if err := configuration.Validate(); err == nil {
t.Fatalf("invalid configuration accepted: %+v", configuration)
}
}
}
func createMinimalProc(t *testing.T) string {
t.Helper()
proc := filepath.Join(t.TempDir(), "proc")
if err := os.MkdirAll(filepath.Join(proc, "net"), 0o700); err != nil {
t.Fatal(err)
}
files := map[string]string{
"stat": "cpu 1 1 1 1\n", "uptime": "1 1\n",
"meminfo": "MemTotal: 1 kB\nMemAvailable: 1 kB\nSwapTotal: 0 kB\nSwapFree: 0 kB\n",
"loadavg": "0 0 0 1/1 1\n", filepath.Join("net", "dev"): "Inter-| Receive | Transmit\n",
}
for name, body := range files {
if err := os.WriteFile(filepath.Join(proc, name), []byte(body), 0o600); err != nil {
t.Fatal(err)
}
}
return proc
}