forgejo-runner-optimiser/internal/proc/cgroup_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

97 lines
2.1 KiB
Go

package proc
import (
"os"
"path/filepath"
"testing"
)
func TestReadCgroup(t *testing.T) {
tests := []struct {
name string
cgroupFile string
wantPath string
wantErr bool
}{
{
name: "cgroup v2 unified",
cgroupFile: `0::/kubepods/pod-abc/container-123
`,
wantPath: "/kubepods/pod-abc/container-123",
wantErr: false,
},
{
name: "cgroup v2 with trailing newline",
cgroupFile: `0::/docker/abc123def456
`,
wantPath: "/docker/abc123def456",
wantErr: false,
},
{
name: "cgroup v1 multiple controllers",
cgroupFile: `12:blkio:/user.slice
11:memory:/docker/abc123
10:cpu,cpuacct:/docker/abc123
9:pids:/docker/abc123
`,
wantPath: "/docker/abc123",
wantErr: false,
},
{
name: "cgroup v2 preferred over v1",
cgroupFile: `11:memory:/docker/old-path
0::/kubepods/new-path
`,
wantPath: "/kubepods/new-path",
wantErr: false,
},
{
name: "empty file",
cgroupFile: "",
wantPath: "",
wantErr: false,
},
{
name: "root cgroup",
cgroupFile: `0::/
`,
wantPath: "/",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a temp directory structure mimicking /proc
tmpDir := t.TempDir()
procDir := filepath.Join(tmpDir, "proc")
pidDir := filepath.Join(procDir, "1234")
if err := os.MkdirAll(pidDir, 0755); err != nil {
t.Fatalf("Failed to create pid dir: %v", err)
}
if err := os.WriteFile(filepath.Join(pidDir, "cgroup"), []byte(tt.cgroupFile), 0644); err != nil {
t.Fatalf("Failed to write cgroup file: %v", err)
}
got, err := ReadCgroup(procDir, 1234)
if (err != nil) != tt.wantErr {
t.Errorf("ReadCgroup() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && got.Path != tt.wantPath {
t.Errorf("ReadCgroup() path = %q, want %q", got.Path, tt.wantPath)
}
})
}
}
func TestReadCgroup_FileNotFound(t *testing.T) {
tmpDir := t.TempDir()
_, err := ReadCgroup(tmpDir, 1234)
if err == nil {
t.Error("ReadCgroup() expected error for missing file, got nil")
}
}