package discovery import ( "fmt" "os" "path/filepath" "testing" "time" ) // fakeProc is a /proc with two CPUs, a memory picture and processes. type fakeProc struct { t *testing.T root string } func newFakeProc(t *testing.T) *fakeProc { f := &fakeProc{t: t, root: t.TempDir()} f.write("loadavg", "1.50 0.75 0.25 2/300 4242\n") f.write("meminfo", "MemTotal: 8000000 kB\nMemFree: 1000000 kB\nMemAvailable: 3000000 kB\n") return f } func (f *fakeProc) write(name, body string) { f.t.Helper() p := filepath.Join(f.root, name) if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil { f.t.Fatal(err) } if err := os.WriteFile(p, []byte(body), 0o644); err != nil { f.t.Fatal(err) } } // cpu sets the aggregate counters: user and idle, the rest zero. func (f *fakeProc) cpu(user, idle uint64) { f.write("stat", fmt.Sprintf("cpu %d 0 0 %d 0 0 0 0 0 0\ncpu0 0 0 0 0 0 0 0 0 0 0\ncpu1 0 0 0 0 0 0 0 0 0 0\nintr 1\n", user, idle)) } // proc sets one process: its group, CPU jiffies (as utime) and RSS in pages. func (f *fakeProc) proc(pid, pgrp int, jiffies uint64, rss int64) { // The name holds a space and a parenthesis, as real ones can. f.write(fmt.Sprintf("%d/stat", pid), fmt.Sprintf( "%d (llama-server (x)) S 1 %d %d 0 -1 0 0 0 0 0 %d 0 0 0 20 0 1 0 100 1000 %d 0\n", pid, pgrp, pgrp, jiffies, rss)) } func TestLoadIsTheDifferenceBetweenTwoReadings(t *testing.T) { p := newFakeProc(t) at := time.Unix(1000, 0) s := newLoadSampler(p.root, func() time.Time { return at }) p.cpu(100, 900) p.proc(42, 42, 1000, 10) p.proc(43, 42, 500, 5) // a worker in the engine's group p.proc(7, 7, 99999, 1) // something else entirely if _, ok := s.Sample(map[string]int{"shard-a": 42}); ok { t.Fatal("the first reading has nothing to compare with and should report nothing") } // Two seconds later: 300 of the 400 jiffies that passed were busy, and // the engine's group used 400 of them. at = at.Add(2 * time.Second) p.cpu(400, 1000) p.proc(42, 42, 1300, 10) p.proc(43, 42, 600, 6) l, ok := s.Sample(map[string]int{"shard-a": 42}) if !ok { t.Fatal("the second reading should report") } if l.CPUPercent != 75 || l.CPUCount != 2 { t.Errorf("cpu %.1f%% of %d, want 75%% of 2", l.CPUPercent, l.CPUCount) } if l.Load1 != 1.5 || l.Load15 != 0.25 { t.Errorf("load %v %v", l.Load1, l.Load15) } if l.MemUsed != 5000000*1024 || l.MemAvail != 3000000*1024 { t.Errorf("memory used %d available %d", l.MemUsed, l.MemAvail) } if len(l.Shards) != 1 { t.Fatalf("shards = %+v", l.Shards) } sh := l.Shards[0] // 400 jiffies over 2 seconds at 100 a second is two cores. if sh.ID != "shard-a" || sh.CPUCores != 2 || sh.RSSBytes != 16*int64(os.Getpagesize()) { t.Errorf("shard = %+v", sh) } } func TestNoProcMeansNoLoad(t *testing.T) { s := newLoadSampler(filepath.Join(t.TempDir(), "missing"), time.Now) s.Sample(nil) if _, ok := s.Sample(nil); ok { t.Error("a system without /proc should report nothing, not zeros") } }