All checks were successful
ci / build (push) Successful in 30s
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>
97 lines
2.1 KiB
Go
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")
|
|
}
|
|
}
|