mirror of
https://git.mirrors.martin98.com/https://github.com/ceph/ceph-csi.git
synced 2025-08-15 22:35:58 +08:00
242 lines
6.3 KiB
Go
242 lines
6.3 KiB
Go
package fs2
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"math"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/opencontainers/cgroups"
|
|
"github.com/opencontainers/cgroups/fscommon"
|
|
)
|
|
|
|
// numToStr converts an int64 value to a string for writing to a
|
|
// cgroupv2 files with .min, .max, .low, or .high suffix.
|
|
// The value of -1 is converted to "max" for cgroupv1 compatibility
|
|
// (which used to write -1 to remove the limit).
|
|
func numToStr(value int64) (ret string) {
|
|
switch {
|
|
case value == 0:
|
|
ret = ""
|
|
case value == -1:
|
|
ret = "max"
|
|
default:
|
|
ret = strconv.FormatInt(value, 10)
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func isMemorySet(r *cgroups.Resources) bool {
|
|
return r.MemoryReservation != 0 || r.Memory != 0 || r.MemorySwap != 0
|
|
}
|
|
|
|
func setMemory(dirPath string, r *cgroups.Resources) error {
|
|
if !isMemorySet(r) {
|
|
return nil
|
|
}
|
|
|
|
if err := CheckMemoryUsage(dirPath, r); err != nil {
|
|
return err
|
|
}
|
|
|
|
swap, err := cgroups.ConvertMemorySwapToCgroupV2Value(r.MemorySwap, r.Memory)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
swapStr := numToStr(swap)
|
|
if swapStr == "" && swap == 0 && r.MemorySwap > 0 {
|
|
// memory and memorySwap set to the same value -- disable swap
|
|
swapStr = "0"
|
|
}
|
|
// never write empty string to `memory.swap.max`, it means set to 0.
|
|
if swapStr != "" {
|
|
if err := cgroups.WriteFile(dirPath, "memory.swap.max", swapStr); err != nil {
|
|
// If swap is not enabled, silently ignore setting to max or disabling it.
|
|
if !(errors.Is(err, os.ErrNotExist) && (swapStr == "max" || swapStr == "0")) {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if val := numToStr(r.Memory); val != "" {
|
|
if err := cgroups.WriteFile(dirPath, "memory.max", val); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// cgroup.Resources.KernelMemory is ignored
|
|
|
|
if val := numToStr(r.MemoryReservation); val != "" {
|
|
if err := cgroups.WriteFile(dirPath, "memory.low", val); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func statMemory(dirPath string, stats *cgroups.Stats) error {
|
|
const file = "memory.stat"
|
|
statsFile, err := cgroups.OpenFile(dirPath, file, os.O_RDONLY)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer statsFile.Close()
|
|
|
|
sc := bufio.NewScanner(statsFile)
|
|
for sc.Scan() {
|
|
t, v, err := fscommon.ParseKeyValue(sc.Text())
|
|
if err != nil {
|
|
return &parseError{Path: dirPath, File: file, Err: err}
|
|
}
|
|
stats.MemoryStats.Stats[t] = v
|
|
}
|
|
if err := sc.Err(); err != nil {
|
|
return &parseError{Path: dirPath, File: file, Err: err}
|
|
}
|
|
stats.MemoryStats.Cache = stats.MemoryStats.Stats["file"]
|
|
// Unlike cgroup v1 which has memory.use_hierarchy binary knob,
|
|
// cgroup v2 is always hierarchical.
|
|
stats.MemoryStats.UseHierarchy = true
|
|
|
|
memoryUsage, err := getMemoryDataV2(dirPath, "")
|
|
if err != nil {
|
|
if errors.Is(err, unix.ENOENT) && dirPath == UnifiedMountpoint {
|
|
// The root cgroup does not have memory.{current,max,peak}
|
|
// so emulate those using data from /proc/meminfo and
|
|
// /sys/fs/cgroup/memory.stat
|
|
return rootStatsFromMeminfo(stats)
|
|
}
|
|
return err
|
|
}
|
|
stats.MemoryStats.Usage = memoryUsage
|
|
swapOnlyUsage, err := getMemoryDataV2(dirPath, "swap")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stats.MemoryStats.SwapOnlyUsage = swapOnlyUsage
|
|
swapUsage := swapOnlyUsage
|
|
// As cgroup v1 reports SwapUsage values as mem+swap combined,
|
|
// while in cgroup v2 swap values do not include memory,
|
|
// report combined mem+swap for v1 compatibility.
|
|
swapUsage.Usage += memoryUsage.Usage
|
|
if swapUsage.Limit != math.MaxUint64 {
|
|
swapUsage.Limit += memoryUsage.Limit
|
|
}
|
|
// The `MaxUsage` of mem+swap cannot simply combine mem with
|
|
// swap. So set it to 0 for v1 compatibility.
|
|
swapUsage.MaxUsage = 0
|
|
stats.MemoryStats.SwapUsage = swapUsage
|
|
|
|
return nil
|
|
}
|
|
|
|
func getMemoryDataV2(path, name string) (cgroups.MemoryData, error) {
|
|
memoryData := cgroups.MemoryData{}
|
|
|
|
moduleName := "memory"
|
|
if name != "" {
|
|
moduleName = "memory." + name
|
|
}
|
|
usage := moduleName + ".current"
|
|
limit := moduleName + ".max"
|
|
maxUsage := moduleName + ".peak"
|
|
|
|
value, err := fscommon.GetCgroupParamUint(path, usage)
|
|
if err != nil {
|
|
if name != "" && os.IsNotExist(err) {
|
|
// Ignore EEXIST as there's no swap accounting
|
|
// if kernel CONFIG_MEMCG_SWAP is not set or
|
|
// swapaccount=0 kernel boot parameter is given.
|
|
return cgroups.MemoryData{}, nil
|
|
}
|
|
return cgroups.MemoryData{}, err
|
|
}
|
|
memoryData.Usage = value
|
|
|
|
value, err = fscommon.GetCgroupParamUint(path, limit)
|
|
if err != nil {
|
|
return cgroups.MemoryData{}, err
|
|
}
|
|
memoryData.Limit = value
|
|
|
|
// `memory.peak` since kernel 5.19
|
|
// `memory.swap.peak` since kernel 6.5
|
|
value, err = fscommon.GetCgroupParamUint(path, maxUsage)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return cgroups.MemoryData{}, err
|
|
}
|
|
memoryData.MaxUsage = value
|
|
|
|
return memoryData, nil
|
|
}
|
|
|
|
func rootStatsFromMeminfo(stats *cgroups.Stats) error {
|
|
const file = "/proc/meminfo"
|
|
f, err := os.Open(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
// Fields we are interested in.
|
|
var (
|
|
swap_free uint64
|
|
swap_total uint64
|
|
)
|
|
mem := map[string]*uint64{
|
|
"SwapFree": &swap_free,
|
|
"SwapTotal": &swap_total,
|
|
}
|
|
|
|
found := 0
|
|
sc := bufio.NewScanner(f)
|
|
for sc.Scan() {
|
|
parts := strings.SplitN(sc.Text(), ":", 3)
|
|
if len(parts) != 2 {
|
|
// Should not happen.
|
|
continue
|
|
}
|
|
k := parts[0]
|
|
p, ok := mem[k]
|
|
if !ok {
|
|
// Unknown field -- not interested.
|
|
continue
|
|
}
|
|
vStr := strings.TrimSpace(strings.TrimSuffix(parts[1], " kB"))
|
|
*p, err = strconv.ParseUint(vStr, 10, 64)
|
|
if err != nil {
|
|
return &parseError{File: file, Err: errors.New("bad value for " + k)}
|
|
}
|
|
|
|
found++
|
|
if found == len(mem) {
|
|
// Got everything we need -- skip the rest.
|
|
break
|
|
}
|
|
}
|
|
if err := sc.Err(); err != nil {
|
|
return &parseError{Path: "", File: file, Err: err}
|
|
}
|
|
|
|
// cgroup v1 `usage_in_bytes` reports memory usage as the sum of
|
|
// - rss (NR_ANON_MAPPED)
|
|
// - cache (NR_FILE_PAGES)
|
|
// cgroup v1 reports SwapUsage values as mem+swap combined
|
|
// cgroup v2 reports rss and cache as anon and file.
|
|
// sum `anon` + `file` to report the same value as `usage_in_bytes` in v1.
|
|
// sum swap usage as combined mem+swap usage for consistency as well.
|
|
stats.MemoryStats.Usage.Usage = stats.MemoryStats.Stats["anon"] + stats.MemoryStats.Stats["file"]
|
|
stats.MemoryStats.Usage.Limit = math.MaxUint64
|
|
stats.MemoryStats.SwapUsage.Usage = (swap_total - swap_free) * 1024
|
|
stats.MemoryStats.SwapUsage.Limit = math.MaxUint64
|
|
stats.MemoryStats.SwapUsage.Usage += stats.MemoryStats.Usage.Usage
|
|
|
|
return nil
|
|
}
|