forgejo-runner-optimiser/internal/cgroup/parse_test.go
Manuel Ganter 5e470c33a5
All checks were successful
ci / build (push) Successful in 30s
feat(collector): group CPU and memory metrics by cgroup
Add cgroup-based process grouping to the resource collector. Processes are
grouped by their cgroup path, with container names resolved via configurable
process-to-container mapping.

New features:
- Read cgroup info from /proc/[pid]/cgroup (supports v1 and v2)
- Parse K8s resource notation (500m, 1Gi, etc.) for CPU/memory limits
- Group metrics by container using CGROUP_PROCESS_MAP env var
- Calculate usage percentages against limits from CGROUP_LIMITS env var
- Output cgroup metrics with CPU cores used, memory RSS, and percentages

Environment variables:
- CGROUP_PROCESS_MAP: Map process names to container names for discovery
- CGROUP_LIMITS: Define CPU/memory limits per container in K8s notation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 14:50:36 +01:00

84 lines
2 KiB
Go

package cgroup
import (
"testing"
)
func TestParseCPU(t *testing.T) {
tests := []struct {
name string
input string
want float64
wantErr bool
}{
{"millicores 500m", "500m", 0.5, false},
{"millicores 100m", "100m", 0.1, false},
{"millicores 2000m", "2000m", 2.0, false},
{"millicores 1m", "1m", 0.001, false},
{"cores integer", "2", 2.0, false},
{"cores decimal", "1.5", 1.5, false},
{"cores with spaces", " 2 ", 2.0, false},
{"empty string", "", 0, true},
{"invalid format", "abc", 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseCPU(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("ParseCPU() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && got != tt.want {
t.Errorf("ParseCPU() = %v, want %v", got, tt.want)
}
})
}
}
func TestParseMemory(t *testing.T) {
tests := []struct {
name string
input string
want uint64
wantErr bool
}{
// Binary suffixes (powers of 1024)
{"Ki", "1Ki", 1024, false},
{"Mi", "1Mi", 1024 * 1024, false},
{"Gi", "1Gi", 1024 * 1024 * 1024, false},
{"Ti", "1Ti", 1024 * 1024 * 1024 * 1024, false},
{"512Mi", "512Mi", 512 * 1024 * 1024, false},
{"2Gi", "2Gi", 2 * 1024 * 1024 * 1024, false},
// Decimal suffixes (powers of 1000)
{"K", "1K", 1000, false},
{"M", "1M", 1000000, false},
{"G", "1G", 1000000000, false},
{"T", "1T", 1000000000000, false},
// Plain bytes
{"bytes", "1024", 1024, false},
{"large bytes", "1073741824", 1073741824, false},
// With spaces
{"with spaces", " 1Gi ", 1024 * 1024 * 1024, false},
// Error cases
{"empty", "", 0, true},
{"invalid", "abc", 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseMemory(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("ParseMemory() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && got != tt.want {
t.Errorf("ParseMemory() = %v, want %v", got, tt.want)
}
})
}
}