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>
59 lines
1.4 KiB
Go
59 lines
1.4 KiB
Go
// ABOUTME: Reads cgroup information from /proc/[pid]/cgroup.
|
|
// ABOUTME: Supports both cgroup v1 and v2 formats.
|
|
package proc
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// CgroupInfo holds cgroup information for a process
|
|
type CgroupInfo struct {
|
|
Path string // The cgroup path (unified for v2, or from memory controller for v1)
|
|
}
|
|
|
|
// ReadCgroup reads /proc/[pid]/cgroup and extracts the cgroup path
|
|
func ReadCgroup(procPath string, pid int) (*CgroupInfo, error) {
|
|
path := fmt.Sprintf("%s/%d/cgroup", procPath, pid)
|
|
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("opening cgroup file: %w", err)
|
|
}
|
|
defer func() { _ = file.Close() }()
|
|
|
|
var cgroupPath string
|
|
scanner := bufio.NewScanner(file)
|
|
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
|
|
// Try cgroup v2 first (unified hierarchy)
|
|
// Format: 0::/path
|
|
if path, found := strings.CutPrefix(line, "0::"); found {
|
|
cgroupPath = path
|
|
break
|
|
}
|
|
|
|
// Fall back to cgroup v1 - look for memory controller
|
|
// Format: X:memory:/path or X:memory,other:/path
|
|
parts := strings.SplitN(line, ":", 3)
|
|
if len(parts) == 3 {
|
|
controllers := parts[1]
|
|
if strings.Contains(controllers, "memory") {
|
|
cgroupPath = parts[2]
|
|
// Don't break - prefer v2 if found later
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, fmt.Errorf("scanning cgroup file: %w", err)
|
|
}
|
|
|
|
return &CgroupInfo{
|
|
Path: cgroupPath,
|
|
}, nil
|
|
}
|