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>
84 lines
2 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|