// 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 }