From 537adab9211c172275067c011a39ccc68819df00 Mon Sep 17 00:00:00 2001 From: August Felso <77752049+amfelso@users.noreply.github.com> Date: Fri, 6 Mar 2026 05:59:15 -0500 Subject: [PATCH 1/9] Attach proc directory --- helm/pi-agent/templates/daemonset.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/helm/pi-agent/templates/daemonset.yaml b/helm/pi-agent/templates/daemonset.yaml index 2eeabf1..332381f 100644 --- a/helm/pi-agent/templates/daemonset.yaml +++ b/helm/pi-agent/templates/daemonset.yaml @@ -22,6 +22,9 @@ spec: - name: sysfs mountPath: /host/sys readOnly: true + - name: procfs + mountPath: /host/proc + readOnly: true tolerations: # Run on all nodes including control plane - operator: Exists @@ -29,3 +32,6 @@ spec: - name: sysfs hostPath: path: /sys + - name: procfs + hostPath: + path: /proc From 60a6e167330f35210319e7484ca6e4698423c843 Mon Sep 17 00:00:00 2001 From: August Felso <77752049+amfelso@users.noreply.github.com> Date: Fri, 6 Mar 2026 06:18:28 -0500 Subject: [PATCH 2/9] Add temperature metric --- app/collectors/temperature.go | 38 ++++++++++++++++++++++++++ app/main.go | 13 +++++---- helm/pi-agent/templates/daemonset.yaml | 3 ++ 3 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 app/collectors/temperature.go diff --git a/app/collectors/temperature.go b/app/collectors/temperature.go new file mode 100644 index 0000000..e797a07 --- /dev/null +++ b/app/collectors/temperature.go @@ -0,0 +1,38 @@ +package collectors + +import ( + "log" + "os" + "strconv" + "strings" +) + +type Temperature struct{ + fileName string +} + +func NewTemperature(path string) *Temperature { + return &Temperature{fileName: path} +} + +func (t *Temperature)Name() string { + return "temp_f" +} + +func (t *Temperature)Collect() (float64, error) { + // Open the file + file, err := os.ReadFile(t.fileName) + if err != nil { + log.Printf("Error reading file: %s", err) + return 0.0, err + } + + tempC, err := strconv.ParseInt(strings.TrimSpace(string(file)), 10, 64) + if err != nil { + return 0.0, err + } + + // Calculate Fahrenheit + tempF := ((float64(tempC) / 1000.0) * (9.0 / 5.0)) + 32.0 + return tempF, nil +} \ No newline at end of file diff --git a/app/main.go b/app/main.go index d52e1b7..e363b98 100644 --- a/app/main.go +++ b/app/main.go @@ -9,16 +9,17 @@ import ( ) func main() { - var seconds int + var schedule int - flag.IntVar(&seconds, "seconds", 300, "the time between metric collection cycles") + flag.IntVar(&schedule, "schedule", 300, "seconds between metric collection cycles") flag.Parse() - log.Printf("Seconds: %d\n", seconds) + log.Printf("Schedule: %d seconds\n", schedule) - metrics := []collectors.Collector{collectors.NewCpu("/host/proc/stat"), collectors.NewMemory("/host/proc/meminfo")} + metrics := []collectors.Collector{collectors.NewCpu("/host/proc/stat"), collectors.NewMemory("/host/proc/meminfo"), + collectors.NewTemperature("/host/sys/class/thermal/thermal_zone0/temp")} - ticker := time.NewTicker(time.Duration(seconds) * time.Second) + ticker := time.NewTicker(time.Duration(schedule) * time.Second) for range ticker.C { for _, c := range metrics { val, err := c.Collect() @@ -26,7 +27,7 @@ func main() { log.Printf("Couldn't get metric value: %s\n", err) continue } - log.Printf("Found metric value: %.2f\n", val) + log.Printf("%s: %.2f\n", c.Name(), val) } } } diff --git a/helm/pi-agent/templates/daemonset.yaml b/helm/pi-agent/templates/daemonset.yaml index 332381f..40aa46e 100644 --- a/helm/pi-agent/templates/daemonset.yaml +++ b/helm/pi-agent/templates/daemonset.yaml @@ -18,6 +18,9 @@ spec: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} + args: + - "--schedule" + - "{{ .Values.collector.schedule }}" volumeMounts: - name: sysfs mountPath: /host/sys From e254b5c7c9128c384dc71735d8167ed8b710550b Mon Sep 17 00:00:00 2001 From: August Felso <77752049+amfelso@users.noreply.github.com> Date: Fri, 6 Mar 2026 06:35:51 -0500 Subject: [PATCH 3/9] Add tests for temperature --- app/collectors/temperature_test.go | 84 ++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 app/collectors/temperature_test.go diff --git a/app/collectors/temperature_test.go b/app/collectors/temperature_test.go new file mode 100644 index 0000000..9164f83 --- /dev/null +++ b/app/collectors/temperature_test.go @@ -0,0 +1,84 @@ +package collectors + +import ( + "os" + "testing" +) + +func writeTempInfo(t *testing.T, content string) string { + t.Helper() + f, err := os.CreateTemp("", "sys-class-thermal-thermal_zone0-temp-*") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = os.Remove(f.Name()) }) + if _, err := f.WriteString(content); err != nil { + t.Fatal(err) + } + if err := f.Close(); err != nil { + t.Fatal(err) + } + return f.Name() +} + +func TestTempName(t *testing.T) { + tp := &Temperature{} + if tp.Name() != "temp_f" { + t.Errorf("expected name 'temp_f', got %s", tp.Name()) + } +} + +func TestTempCollect_ZeroVal(t *testing.T) { + // Celsius=0 → fahrenheight = 32.0 + content := "00000\n" + path := writeTempInfo(t, content) + tp := &Temperature{fileName: path} + + val, err := tp.Collect() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if val != 32.0 { + t.Errorf("expected 32.0, got %.2f", val) + } +} + +func TestTempCollect_PositiveVal(t *testing.T) { + // Celsius=50.634 → fahrenheight = 123.14 + content := "50634\n" + path := writeTempInfo(t, content) + tp := &Temperature{fileName: path} + + val, err := tp.Collect() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if val < 123.14 || val > 123.15 { + t.Errorf("expected ~123.14, got %.4f", val) + } +} + +func TestTempCollect_NegativeVal(t *testing.T) { + // Celsius=-6.666 → fahrenheight = 20.0 + content := "-6666\n" + path := writeTempInfo(t, content) + tp := &Temperature{fileName: path} + + val, err := tp.Collect() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if val < 20.0 || val > 20.01 { + t.Errorf("expected ~20.0, got %.4f", val) + } +} + +func TestTempCollect_EmptyFile(t *testing.T) { + path := writeTempInfo(t, "") + tp := &Temperature{fileName: path} + + _, err := tp.Collect() + if err == nil { + t.Error("expected error for empty file, got nil") + } +} \ No newline at end of file From b09ce60c4587c5821bcf8a989dec75d61a246c92 Mon Sep 17 00:00:00 2001 From: August Felso <77752049+amfelso@users.noreply.github.com> Date: Fri, 6 Mar 2026 06:54:11 -0500 Subject: [PATCH 4/9] Mount rootfs --- helm/pi-agent/templates/daemonset.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/helm/pi-agent/templates/daemonset.yaml b/helm/pi-agent/templates/daemonset.yaml index 40aa46e..7fe193a 100644 --- a/helm/pi-agent/templates/daemonset.yaml +++ b/helm/pi-agent/templates/daemonset.yaml @@ -28,6 +28,9 @@ spec: - name: procfs mountPath: /host/proc readOnly: true + - name: rootfs + mountPath: /host/root + readOnly: true tolerations: # Run on all nodes including control plane - operator: Exists @@ -38,3 +41,6 @@ spec: - name: procfs hostPath: path: /proc + - name: rootfs + hostPath: + path: / From d67d971eb369d7946951646e0d50ca48fef6cc4a Mon Sep 17 00:00:00 2001 From: August Felso <77752049+amfelso@users.noreply.github.com> Date: Fri, 6 Mar 2026 06:57:18 -0500 Subject: [PATCH 5/9] Add disk metric --- app/collectors/disk.go | 29 +++++++++++++++++++++++++++++ app/main.go | 3 ++- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 app/collectors/disk.go diff --git a/app/collectors/disk.go b/app/collectors/disk.go new file mode 100644 index 0000000..01ba30d --- /dev/null +++ b/app/collectors/disk.go @@ -0,0 +1,29 @@ +package collectors + +import ( + "syscall" +) + +type Disk struct{ + path string +} + +func NewDisk(path string) *Disk { + return &Disk{path: path} +} + +func (d *Disk)Name() string { + return "disk" +} + +func (d *Disk)Collect() (float64, error) { + // Call Statfs + var stat syscall.Statfs_t + err := syscall.Statfs(d.path, &stat) + if err != nil { + return 0.0, err + } + + usagePercent := (1.0 - float64(stat.Bavail) / float64(stat.Blocks)) * 100.0 + return usagePercent, nil +} \ No newline at end of file diff --git a/app/main.go b/app/main.go index e363b98..8519900 100644 --- a/app/main.go +++ b/app/main.go @@ -17,7 +17,8 @@ func main() { log.Printf("Schedule: %d seconds\n", schedule) metrics := []collectors.Collector{collectors.NewCpu("/host/proc/stat"), collectors.NewMemory("/host/proc/meminfo"), - collectors.NewTemperature("/host/sys/class/thermal/thermal_zone0/temp")} + collectors.NewTemperature("/host/sys/class/thermal/thermal_zone0/temp"), + collectors.NewDisk("/host/root/")} ticker := time.NewTicker(time.Duration(schedule) * time.Second) for range ticker.C { From eecf686e3dba0a8db312510271ba9a494e53ad8b Mon Sep 17 00:00:00 2001 From: August Felso <77752049+amfelso@users.noreply.github.com> Date: Fri, 6 Mar 2026 06:57:39 -0500 Subject: [PATCH 6/9] Run gofmt --- app/collectors/cpu.go | 30 +++++++++++++++--------------- app/collectors/disk.go | 14 +++++++------- app/collectors/memory.go | 18 +++++++++--------- app/collectors/temperature.go | 16 ++++++++-------- app/collectors/temperature_test.go | 2 +- 5 files changed, 40 insertions(+), 40 deletions(-) diff --git a/app/collectors/cpu.go b/app/collectors/cpu.go index 27a0553..adc4a01 100644 --- a/app/collectors/cpu.go +++ b/app/collectors/cpu.go @@ -9,26 +9,26 @@ import ( "strings" ) -type Cpu struct{ +type Cpu struct { prevIdle uint64 - prevTotal uint64 - fileName string + prevTotal uint64 + fileName string } func NewCpu(path string) *Cpu { return &Cpu{fileName: path} } - -func (c *Cpu)Name() string { - return "cpu" + +func (c *Cpu) Name() string { + return "cpu" } -func (c *Cpu)Collect() (float64, error) { +func (c *Cpu) Collect() (float64, error) { // Open the file file, err := os.Open(c.fileName) if err != nil { log.Printf("Error opening file: %s", err) - return 0.0, err + return 0.0, err } // Ensure the file is closed the the function exits @@ -52,17 +52,17 @@ func (c *Cpu)Collect() (float64, error) { } // Calculate idleDelta - idleDelta := currentIdle - c.prevIdle + idleDelta := currentIdle - c.prevIdle // calculate currentTotal - var currentTotal uint64 - for _, stat := range stats { + var currentTotal uint64 + for _, stat := range stats { stat, err := strconv.ParseUint(stat, 10, 64) if err != nil { return 0.0, err } - currentTotal += uint64(stat) - } + currentTotal += uint64(stat) + } // values for first run if c.prevTotal == 0 { @@ -75,11 +75,11 @@ func (c *Cpu)Collect() (float64, error) { totalDelta := currentTotal - c.prevTotal // Usage Percent = (1 - idleDelta/totalDelta) * 100 - usagePercent := (1.0 - float64(idleDelta)/float64(totalDelta))* 100 + usagePercent := (1.0 - float64(idleDelta)/float64(totalDelta)) * 100 // Save current readings c.prevIdle = currentIdle c.prevTotal = currentTotal return usagePercent, nil -} \ No newline at end of file +} diff --git a/app/collectors/disk.go b/app/collectors/disk.go index 01ba30d..9cf0fb2 100644 --- a/app/collectors/disk.go +++ b/app/collectors/disk.go @@ -4,19 +4,19 @@ import ( "syscall" ) -type Disk struct{ +type Disk struct { path string } func NewDisk(path string) *Disk { return &Disk{path: path} } - -func (d *Disk)Name() string { - return "disk" + +func (d *Disk) Name() string { + return "disk" } -func (d *Disk)Collect() (float64, error) { +func (d *Disk) Collect() (float64, error) { // Call Statfs var stat syscall.Statfs_t err := syscall.Statfs(d.path, &stat) @@ -24,6 +24,6 @@ func (d *Disk)Collect() (float64, error) { return 0.0, err } - usagePercent := (1.0 - float64(stat.Bavail) / float64(stat.Blocks)) * 100.0 + usagePercent := (1.0 - float64(stat.Bavail)/float64(stat.Blocks)) * 100.0 return usagePercent, nil -} \ No newline at end of file +} diff --git a/app/collectors/memory.go b/app/collectors/memory.go index 60f0968..25bfcd0 100644 --- a/app/collectors/memory.go +++ b/app/collectors/memory.go @@ -9,24 +9,24 @@ import ( "strings" ) -type Memory struct{ +type Memory struct { fileName string } func NewMemory(path string) *Memory { return &Memory{fileName: path} } - -func (m *Memory)Name() string { - return "memory" + +func (m *Memory) Name() string { + return "memory" } -func (m *Memory)Collect() (float64, error) { +func (m *Memory) Collect() (float64, error) { // Open the file file, err := os.Open(m.fileName) if err != nil { log.Printf("Error opening file: %s", err) - return 0.0, err + return 0.0, err } // Ensure the file is closed the the function exits @@ -34,7 +34,7 @@ func (m *Memory)Collect() (float64, error) { // Create a new Scanner for the file scanner := bufio.NewScanner(file) - + // Get memTotal from line 0 if !scanner.Scan() { if err := scanner.Err(); err != nil { @@ -68,6 +68,6 @@ func (m *Memory)Collect() (float64, error) { } // Calculate usagePercent - usagePercent := float64(memTotal - memAvailable) / float64(memTotal) * 100 + usagePercent := float64(memTotal-memAvailable) / float64(memTotal) * 100 return usagePercent, nil -} \ No newline at end of file +} diff --git a/app/collectors/temperature.go b/app/collectors/temperature.go index e797a07..228fd13 100644 --- a/app/collectors/temperature.go +++ b/app/collectors/temperature.go @@ -7,26 +7,26 @@ import ( "strings" ) -type Temperature struct{ +type Temperature struct { fileName string } func NewTemperature(path string) *Temperature { return &Temperature{fileName: path} } - -func (t *Temperature)Name() string { - return "temp_f" + +func (t *Temperature) Name() string { + return "temp_f" } -func (t *Temperature)Collect() (float64, error) { +func (t *Temperature) Collect() (float64, error) { // Open the file file, err := os.ReadFile(t.fileName) if err != nil { log.Printf("Error reading file: %s", err) - return 0.0, err + return 0.0, err } - + tempC, err := strconv.ParseInt(strings.TrimSpace(string(file)), 10, 64) if err != nil { return 0.0, err @@ -35,4 +35,4 @@ func (t *Temperature)Collect() (float64, error) { // Calculate Fahrenheit tempF := ((float64(tempC) / 1000.0) * (9.0 / 5.0)) + 32.0 return tempF, nil -} \ No newline at end of file +} diff --git a/app/collectors/temperature_test.go b/app/collectors/temperature_test.go index 9164f83..61d4c66 100644 --- a/app/collectors/temperature_test.go +++ b/app/collectors/temperature_test.go @@ -81,4 +81,4 @@ func TestTempCollect_EmptyFile(t *testing.T) { if err == nil { t.Error("expected error for empty file, got nil") } -} \ No newline at end of file +} From 6f483130fa6fc6129065fff357436273d35649a4 Mon Sep 17 00:00:00 2001 From: August Felso <77752049+amfelso@users.noreply.github.com> Date: Fri, 6 Mar 2026 07:00:45 -0500 Subject: [PATCH 7/9] Add lint check for gofmt --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index b00f975..de6cd9a 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,7 @@ install-tools: .PHONY: lint lint: + @if [ -n "$$(cd app && gofmt -l .)" ]; then echo "gofmt: run 'gofmt -w app/'"; exit 1; fi @cd app && golangci-lint run ./...; @helm lint helm/pi-agent; @hadolint docker/Dockerfile; From f876aeb3e2476ac65cd9f75262ef155af146f6d1 Mon Sep 17 00:00:00 2001 From: August Felso <77752049+amfelso@users.noreply.github.com> Date: Fri, 6 Mar 2026 07:07:58 -0500 Subject: [PATCH 8/9] Add disk test.go --- app/collectors/disk_test.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 app/collectors/disk_test.go diff --git a/app/collectors/disk_test.go b/app/collectors/disk_test.go new file mode 100644 index 0000000..7d632dd --- /dev/null +++ b/app/collectors/disk_test.go @@ -0,0 +1,29 @@ +package collectors + +import "testing" + +func TestDiskName(t *testing.T) { + d := &Disk{} + if d.Name() != "disk" { + t.Errorf("expected name 'disk', got %s", d.Name()) + } +} + +func TestDiskCollect_ReturnsValidPercent(t *testing.T) { + d := &Disk{path: "/tmp"} + val, err := d.Collect() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if val < 0 || val > 100 { + t.Errorf("expected value between 0 and 100, got %.2f", val) + } +} + +func TestDiskCollect_InvalidPath(t *testing.T) { + d := &Disk{path: "/nonexistent/path"} + _, err := d.Collect() + if err == nil { + t.Error("expected error for invalid path, got nil") + } +} From 1a9f7c26908f8bc286f1e2e5329885202ec3f590 Mon Sep 17 00:00:00 2001 From: August Felso <77752049+amfelso@users.noreply.github.com> Date: Fri, 6 Mar 2026 07:11:31 -0500 Subject: [PATCH 9/9] Add gofmt check in CI --- .github/workflows/develop.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/develop.yaml b/.github/workflows/develop.yaml index 5098cf7..7bc2f0f 100644 --- a/.github/workflows/develop.yaml +++ b/.github/workflows/develop.yaml @@ -16,6 +16,12 @@ jobs: - uses: hadolint/hadolint-action@v3.1.0 with: dockerfile: docker/Dockerfile + - name: Check gofmt + run: | + if [ -n "$(cd app && gofmt -l .)" ]; then + echo "gofmt: run 'gofmt -w app/'" + exit 1 + fi - uses: golangci/golangci-lint-action@v6 with: working-directory: app