zygostates/internal/zygostates/telemetry_test.go

139 lines
4.4 KiB
Go

package zygostates
import (
"bufio"
"bytes"
"context"
"encoding/json"
"math"
"os"
"path/filepath"
"sync/atomic"
"testing"
"time"
)
func TestReadCPUTimeAndBusyDelta(t *testing.T) {
path := writeTestFile(t, "stat", "cpu 100 20 30 400 50 0 0 0 0 0\ncpu0 1 2 3 4\n")
value, err := readCPUTime(path)
if err != nil {
t.Fatal(err)
}
if value.total != 600 || value.idle != 450 {
t.Fatalf("CPU time=%#v", value)
}
}
func TestStartTelemetryWritesParseableSamples(t *testing.T) {
store := testArtifactStore(t)
var phase atomic.Value
phase.Store("test")
ctx, cancel := context.WithCancel(context.Background())
wait := startTelemetry(ctx, store, &phase, time.Millisecond, &TelemetryCollector{procRoot: "/proc", sysRoot: "/sys"})
deadline := time.Now().Add(time.Second)
collect:
for time.Now().Before(deadline) {
snapshots, err := store.Snapshots()
if err != nil {
t.Fatal(err)
}
for _, snapshot := range snapshots {
if snapshot.Name == "telemetry.ndjson" && bytes.Count(snapshot.Contents, []byte{'\n'}) >= 2 {
break collect
}
}
time.Sleep(time.Millisecond)
}
cancel()
wait.Wait()
file, err := os.Open(filepath.Join(store.Directory(), "telemetry.ndjson"))
if err != nil {
t.Fatal(err)
}
defer func() { _ = file.Close() }()
scanner := bufio.NewScanner(file)
count := 0
for scanner.Scan() {
var sample TelemetrySample
if err := json.Unmarshal(scanner.Bytes(), &sample); err != nil {
t.Fatal(err)
}
if sample.Phase != "test" {
t.Fatalf("phase=%q", sample.Phase)
}
count++
}
if err := scanner.Err(); err != nil {
t.Fatal(err)
}
if count < 2 {
t.Fatalf("samples=%d", count)
}
}
func TestEnergyDeltaHandlesCounterWrap(t *testing.T) {
previous := EnergySample{microjoules: 900, maximum: 1000}
current := EnergySample{microjoules: 100, maximum: 1000}
if delta := energyDelta(previous, current); delta != 200 {
t.Fatalf("delta=%d", delta)
}
}
func TestTelemetryCollectorSamplesLinuxInputs(t *testing.T) {
root := t.TempDir()
proc := filepath.Join(root, "proc")
sys := filepath.Join(root, "sys")
for _, directory := range []string{
filepath.Join(proc, "sys/kernel/random"),
filepath.Join(sys, "class/hwmon/hwmon0"),
filepath.Join(sys, "devices/system/cpu/cpu0/cpufreq"),
filepath.Join(sys, "class/powercap/intel-rapl:0"),
} {
if err := os.MkdirAll(directory, 0o755); err != nil {
t.Fatal(err)
}
}
files := map[string]string{
filepath.Join(proc, "loadavg"): "1.25 2.50 3.75 1/10 1\n",
filepath.Join(proc, "meminfo"): "MemTotal: 2000 kB\nMemAvailable: 1000 kB\n",
filepath.Join(proc, "stat"): "cpu 100 0 100 800 0 0 0 0 0 0\n",
filepath.Join(proc, "sys/kernel/random/boot_id"): "boot\n",
filepath.Join(sys, "class/hwmon/hwmon0/name"): "k10temp\n",
filepath.Join(sys, "class/hwmon/hwmon0/temp1_label"): "Tctl\n",
filepath.Join(sys, "class/hwmon/hwmon0/temp1_input"): "42500\n",
filepath.Join(sys, "devices/system/cpu/cpu0/cpufreq/scaling_cur_freq"): "4100000\n",
filepath.Join(sys, "class/powercap/intel-rapl:0/energy_uj"): "1000000\n",
filepath.Join(sys, "class/powercap/intel-rapl:0/max_energy_range_uj"): "10000000\n",
}
for path, contents := range files {
if err := os.WriteFile(path, []byte(contents), 0o600); err != nil {
t.Fatal(err)
}
}
collector := TelemetryCollector{procRoot: proc, sysRoot: sys}
firstTime := time.Unix(10, 0)
first, err := collector.Sample(firstTime, "idle")
if err != nil {
t.Fatal(err)
}
if first.Load1 != 1.25 || first.MemoryAvailableKiB != 1000 || first.TemperaturesC["k10temp/Tctl"] != 42.5 || first.CPUFrequencyKHz["cpu0"] != 4100000 {
t.Fatalf("sample=%#v", first)
}
if err := os.WriteFile(filepath.Join(proc, "stat"), []byte("cpu 150 0 150 900 0 0 0 0 0 0\n"), 0o600); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(sys, "class/powercap/intel-rapl:0/energy_uj"), []byte("3000000\n"), 0o600); err != nil {
t.Fatal(err)
}
second, err := collector.Sample(firstTime.Add(time.Second), "cpu")
if err != nil {
t.Fatal(err)
}
if second.CPUBusyPercent == nil || math.Abs(*second.CPUBusyPercent-50) > 1e-9 {
t.Fatalf("busy=%v", second.CPUBusyPercent)
}
if second.PackageWatts == nil || math.Abs(*second.PackageWatts-2) > 1e-9 {
t.Fatalf("watts=%v", second.PackageWatts)
}
}