package fs2 import ( "bufio" "errors" "fmt" "os" "strconv" "strings" "golang.org/x/sys/unix" "github.com/opencontainers/cgroups" ) func statPSI(dirPath string, file string) (*cgroups.PSIStats, error) { f, err := cgroups.OpenFile(dirPath, file, os.O_RDONLY) if err != nil { if errors.Is(err, os.ErrNotExist) { // Kernel < 4.20, or CONFIG_PSI is not set, // or PSI stats are turned off for the cgroup // ("echo 0 > cgroup.pressure", kernel >= 6.1). return nil, nil } return nil, err } defer f.Close() var psistats cgroups.PSIStats sc := bufio.NewScanner(f) for sc.Scan() { parts := strings.Fields(sc.Text()) var pv *cgroups.PSIData switch parts[0] { case "some": pv = &psistats.Some case "full": pv = &psistats.Full } if pv != nil { *pv, err = parsePSIData(parts[1:]) if err != nil { return nil, &parseError{Path: dirPath, File: file, Err: err} } } } if err := sc.Err(); err != nil { if errors.Is(err, unix.ENOTSUP) { // Some kernels (e.g. CS9) may return ENOTSUP on read // if psi=1 kernel cmdline parameter is required. return nil, nil } return nil, &parseError{Path: dirPath, File: file, Err: err} } return &psistats, nil } func parsePSIData(psi []string) (cgroups.PSIData, error) { data := cgroups.PSIData{} for _, f := range psi { key, val, ok := strings.Cut(f, "=") if !ok { return data, fmt.Errorf("invalid psi data: %q", f) } var pv *float64 switch key { case "avg10": pv = &data.Avg10 case "avg60": pv = &data.Avg60 case "avg300": pv = &data.Avg300 case "total": v, err := strconv.ParseUint(val, 10, 64) if err != nil { return data, fmt.Errorf("invalid %s PSI value: %w", key, err) } data.Total = v } if pv != nil { v, err := strconv.ParseFloat(val, 64) if err != nil { return data, fmt.Errorf("invalid %s PSI value: %w", key, err) } *pv = v } } return data, nil }