Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/develop.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
30 changes: 15 additions & 15 deletions app/collectors/cpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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
}
}
29 changes: 29 additions & 0 deletions app/collectors/disk.go
Original file line number Diff line number Diff line change
@@ -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
}
29 changes: 29 additions & 0 deletions app/collectors/disk_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
18 changes: 9 additions & 9 deletions app/collectors/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,32 @@ 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
defer func() { _ = file.Close() }()

// 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 {
Expand Down Expand Up @@ -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
}
}
38 changes: 38 additions & 0 deletions app/collectors/temperature.go
Original file line number Diff line number Diff line change
@@ -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
}
84 changes: 84 additions & 0 deletions app/collectors/temperature_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
14 changes: 8 additions & 6 deletions app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,26 @@ 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"),
collectors.NewDisk("/host/root/")}

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()
if err != nil {
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)
}
}
}
15 changes: 15 additions & 0 deletions helm/pi-agent/templates/daemonset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,29 @@ 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
readOnly: true
- name: procfs
mountPath: /host/proc
readOnly: true
- name: rootfs
mountPath: /host/root
readOnly: true
tolerations:
# Run on all nodes including control plane
- operator: Exists
volumes:
- name: sysfs
hostPath:
path: /sys
- name: procfs
hostPath:
path: /proc
- name: rootfs
hostPath:
path: /
Loading