Commit b0e6d54a authored by “李磊”'s avatar “李磊”
Browse files

feat: Add some toolkits

parent dd9ff9ae
...@@ -3,3 +3,14 @@ module linkfog.com/public ...@@ -3,3 +3,14 @@ module linkfog.com/public
go 1.23.2 go 1.23.2
require golang.org/x/sys v0.26.0 require golang.org/x/sys v0.26.0
require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/kr/text v0.1.0 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.8.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
package cgroup
import (
"os"
"path/filepath"
"sync"
"linkfog.com/public/lib/l"
"linkfog.com/public/option"
)
var (
HostCgroup = option.HostPrefix + "/sys/fs/cgroup/"
DefaultCgroup = "/sys/fs/cgroup/"
cgroup2ControllersPaths = []string{
"/proc/1/root/sys/fs/cgroup/cgroup.controllers",
filepath.Join(HostCgroup, "cgroup.controllers"),
filepath.Join(DefaultCgroup, "cgroup.controllers"),
}
isCgroup2Once sync.Once
isCgroup2 bool
)
func IsCgroup2() bool {
isCgroup2Once.Do(func() {
for _, path := range cgroup2ControllersPaths {
if _, err := os.Stat(path); err == nil {
l.Info(path, "exist, is cgroup v2")
isCgroup2 = true
break
}
}
if !isCgroup2 {
l.Info("cgroup.controllers not exist, is cgroup v1")
}
})
return isCgroup2
}
package cgroup
import (
"bufio"
"fmt"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"linkfog.com/public/lib/cgroup/fs"
)
func GetContainerMemoryCgroupPath(pid int) (string, error) {
if IsCgroup2() {
return GetContainerCgroupPath(pid, "")
}
return GetContainerCgroupPath(pid, fs.MemorySubSystemName())
}
func GetContainerCPUCgroupPath(pid int) (string, error) {
if IsCgroup2() {
return GetContainerCgroupPath(pid, "")
}
return GetContainerCgroupPath(pid, fs.CPUSubSystemName())
}
func GetContainerIOCgroupPath(pid int) (string, error) {
if IsCgroup2() {
return GetContainerCgroupPath(pid, "")
}
return GetContainerCgroupPath(pid, fs.IOSubSystemName())
}
// 获取容器cgroup路径,当pid为-1时,代表进程自身,cgroup路径选择顺序如下:
// 1.主机视角,有hostPID权限:/proc/1/root/sys/fs/cgroup[/subsystem]/kubepods...
// 2.主机视角,挂载主机host:/host/sys/fs/cgroup[/subsystem]/kubepods...
// 3.容器视角,/proc/<pid>/root/sys/fs/cgroup[/subsystem]
func GetContainerCgroupPath(pid int, subsystem string) (string, error) {
path, err := getContainerCgroupPathFromHost(pid, subsystem)
if err == nil {
return path, nil
}
path, err = getContainerCgroupPathFromContainer(pid, subsystem)
if err == nil {
return path, nil
}
return "", fmt.Errorf("unable to get container cgroup path")
}
// 主机视角,获取容器cgroup的特征
func GetContainerCgroupSpec(pid int, subsystem string) (string, error) {
// 获取进程cgroup配置文件
var pidCgroup string
if pid == -1 {
pidCgroup = "/proc/self/cgroup"
} else {
pidCgroup = filepath.Join("/proc", strconv.Itoa(pid), "cgroup")
}
// 解析进程cgroup配置文件,获取相对cgroup路径
var relativeCgroupPath string
var err error
if IsCgroup2() {
relativeCgroupPath, err = parseRelativeCgroupPathV2(pidCgroup)
} else {
relativeCgroupPath, err = parseRelativeCgroupPathV1(pidCgroup, subsystem)
}
return relativeCgroupPath, err
}
// 主机视角,获取容器cgroup路径
func getContainerCgroupPathFromHost(pid int, subsystem string) (string, error) {
// 获取进程cgroup配置文件
var pidCgroup string
if pid == -1 {
pidCgroup = "/proc/self/cgroup"
} else {
pidCgroup = filepath.Join("/proc", strconv.Itoa(pid), "cgroup")
}
// 解析进程cgroup配置文件,获取相对cgroup路径
var relativeCgroupPath string
var err error
if IsCgroup2() {
relativeCgroupPath, err = parseRelativeCgroupPathV2(pidCgroup)
} else {
relativeCgroupPath, err = parseRelativeCgroupPathV1(pidCgroup, subsystem)
}
if err != nil {
return "", err
}
// 生成绝对cgroup路径
cgroupPrefixs := []string{
"/proc/1/root/sys/fs/cgroup",
HostCgroup,
}
var absoluteCgroupPath string
for _, prefix := range cgroupPrefixs {
if IsCgroup2() {
absoluteCgroupPath = filepath.Join(prefix, relativeCgroupPath)
} else {
absoluteCgroupPath = filepath.Join(prefix, subsystem, relativeCgroupPath)
}
if _, err := os.Stat(absoluteCgroupPath); err == nil {
return absoluteCgroupPath, nil
}
}
return "", fmt.Errorf("unable to get container cgroup path from host")
}
// 容器视角,获取容器cgroup路径
func getContainerCgroupPathFromContainer(pid int, subsystem string) (string, error) {
var absoluteCgroupPath string
if pid == -1 {
absoluteCgroupPath = "/proc/self/root/sys/fs/cgroup"
} else {
absoluteCgroupPath = filepath.Join("/proc", strconv.Itoa(pid), "root/sys/fs/cgroup")
}
if !IsCgroup2() {
absoluteCgroupPath = filepath.Join(absoluteCgroupPath, subsystem)
}
if _, err := os.Stat(absoluteCgroupPath); err == nil {
return absoluteCgroupPath, nil
}
return "", fmt.Errorf("unable to get container cgroup path from container")
}
func parseRelativeCgroupPathV1(path string, subsystem string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
if strings.Contains(scanner.Text(), subsystem) {
tokens := strings.SplitN(scanner.Text(), ":", 3)
if len(tokens) == 3 {
return tokens[2], nil
}
break
}
}
return "", fmt.Errorf("unable to find subsystem(%s) relative cgroup path "+
"in container cgroup file", subsystem)
}
func parseRelativeCgroupPathV2(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
}
defer f.Close()
reg := regexp.MustCompile("([0-9a-z]){64}")
relativePath := ""
scanner := bufio.NewScanner(f)
for scanner.Scan() {
tokens := strings.SplitN(scanner.Text(), ":", 3)
if len(tokens) == 3 {
// example: "0::/kubepods/besteffort/podad1189b4-15b6-4ee5-b509-08..."
if strings.HasPrefix(tokens[2], "/kubepods") {
return tokens[2], nil
}
// 当找不到/kubepods时,用于兜底
if relativePath == "" && reg.FindString(tokens[2]) != "" {
relativePath = tokens[2]
}
}
}
if relativePath != "" {
return relativePath, nil
}
return "", fmt.Errorf("unable to find relative cgroup path in container cgroup file")
}
package cgroup
import "testing"
func TestParseRelativeCgroupPathV1(t *testing.T) {
relativePath, err := parseRelativeCgroupPathV1("testdata/cgroup_v1", "memory")
if err != nil {
t.Fatal(err)
}
if relativePath != "/kubepods/besteffort/pod30d45881-d876-4898-a212-f8c95e1b08db/b5209b0109b0fc07d889f4f7878354670c8fbc6a977201be3a6f6aa389187f1b" {
t.Fatal("parse cgroup_v1 memory failed")
}
relativePath, err = parseRelativeCgroupPathV1("testdata/cgroup_v1", "cpuacct")
if err != nil {
t.Fatal(err)
}
if relativePath != "/kubepods/besteffort/pod30d45881-d876-4898-a212-f8c95e1b08db/b5209b0109b0fc07d889f4f7878354670c8fbc6a977201be3a6f6aa389187f1b" {
t.Fatal("parse cgroup_v1 cpuacct failed")
}
_, err = parseRelativeCgroupPathV1("testdata/cgroup_v1", "unknown")
if err == nil {
t.Fatal("parse cgroup_v1 unknown failed")
}
}
func TestParseRelativeCgroupPathV2(t *testing.T) {
relativePath, err := parseRelativeCgroupPathV2("testdata/cgroup_v2_k8s")
if err != nil {
t.Fatal(err)
}
if relativePath != "/kubepods.slice/kubepods-burstable.slice/xxxxxxxxxx-1654a9da9950616391265776d0c38f1ab55c95afbc5581ed9f7ff0c370b25ebf.scope" {
t.Fatal("parse cgroup_v2_k8s failed")
}
relativePath, err = parseRelativeCgroupPathV2("testdata/cgroup_v2_docker")
if err != nil {
t.Fatal(err)
}
if relativePath != "/system.slice/docker-1654a9da9950616391265776d0c38f1ab55c95afbc5581ed9f7ff0c370b25ebf.scope" {
t.Fatal("parse cgroup_v2_docker failed")
}
_, err = parseRelativeCgroupPathV2("testdata/cgroup_v2_systemd")
if err == nil {
t.Fatal("parse cgroup_v2_systemd failed")
}
}
package fs
import (
"fmt"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"linkfog.com/public/lib/cgroup/types"
"linkfog.com/public/lib/l"
)
func IOSubSystemName() string {
return "blkio"
}
func SetIORWLimit(cgroupDir string, devinfos []types.DevInfo, rlimit, wlimit uint64) (map[string]*types.DevLimit, error) {
ioRdLimitPath := path.Join(cgroupDir, "blkio.throttle.read_bps_device")
ioWrLimitPath := path.Join(cgroupDir, "blkio.throttle.write_bps_device")
l.Infof("io read limit path: %s, limit size: %d", ioRdLimitPath, rlimit)
l.Infof("io write limit path: %s, limit size: %d", ioWrLimitPath, wlimit)
fr, err := os.OpenFile(ioRdLimitPath, os.O_WRONLY|os.O_CREATE, 0)
if err != nil {
return nil, err
}
defer fr.Close()
fw, err := os.OpenFile(ioWrLimitPath, os.O_WRONLY|os.O_CREATE, 0)
if err != nil {
return nil, err
}
defer fw.Close()
devLmts := make(map[string]*types.DevLimit, 0)
ioRdLimitSuccCnt := 0
ioWrLimitSuccCnt := 0
for _, devinfo := range devinfos {
devKey := fmt.Sprintf("%s:%s", devinfo.Major, devinfo.Minor)
devLmts[devKey] = &types.DevLimit{
DevName: devinfo.Name,
RdLimit: false,
WrLimit: false,
}
rlimitStr := fmt.Sprintf("%s:%s %d", devinfo.Major, devinfo.Minor, rlimit)
wlimitStr := fmt.Sprintf("%s:%s %d", devinfo.Major, devinfo.Minor, wlimit)
// set io read limit
_, err := fr.WriteString(rlimitStr)
if err != nil {
l.Debugf("set %s io read limit error %v, config:%s", devinfo.Name, err, rlimitStr)
} else {
devLmts[devKey].RdLimit = true
ioRdLimitSuccCnt++
l.Debugf("set %s io read limit success, config:%s", devinfo.Name, rlimitStr)
}
// set io write limit
_, err = fw.WriteString(wlimitStr)
if err != nil {
l.Debugf("set %s io write limit error %v, config:%s", devinfo.Name, err, wlimitStr)
} else {
devLmts[devKey].WrLimit = true
ioWrLimitSuccCnt++
l.Debugf("set %s io write limit success, config:%s", devinfo.Name, wlimitStr)
}
}
if ioRdLimitSuccCnt == 0 || ioWrLimitSuccCnt == 0 {
return nil, fmt.Errorf("set io limit error, read_limit:%d, write_limit:%d",
ioRdLimitSuccCnt, ioWrLimitSuccCnt)
}
return devLmts, nil
}
// #cat blkio.throttle.io_service_bytes
// 8:0 Read 5829550080
// 8:0 Write 4474982400
// 8:0 Sync 4415606784
// 8:0 Async 5888925696
// 8:0 Total 10304532480
// Total 10304532480
func GetAllDevIOStat(cgroupDir string) (map[string]*types.IOStat, error) {
data, err := os.ReadFile(filepath.Join(cgroupDir, "blkio.throttle.io_service_bytes"))
if err != nil {
return nil, err
}
ioStats := make(map[string]*types.IOStat, 0)
ioStats["total"] = &types.IOStat{}
for _, line := range strings.Split(string(data), "\n") {
line = strings.TrimSpace(line)
// line content: 8:0 Read 8433958912
fields := strings.Split(line, " ")
if len(fields) != 3 {
continue
}
device := fields[0]
event := fields[1]
size := fields[2]
if event == "Read" || event == "Write" {
sizeUint64, err := strconv.ParseUint(size, 10, 64)
if err != nil {
return ioStats, fmt.Errorf("unable to convert size to uint64: %s", err)
}
if _, ok := ioStats[device]; !ok {
ioStats[device] = &types.IOStat{}
}
if event == "Read" {
ioStats[device].Read = sizeUint64
ioStats["total"].Read += sizeUint64
}
if event == "Write" {
ioStats[device].Write = sizeUint64
ioStats["total"].Write += sizeUint64
}
}
}
return ioStats, nil
}
package fs
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"linkfog.com/public/lib/cgroup/types"
)
func TestSetIORWLimit(t *testing.T) {
tmpCgroupDir, err := ioutil.TempDir("/tmp", "cgroup-")
if err != nil {
t.Fatal("Cannot create temporary file", err)
}
defer os.RemoveAll(tmpCgroupDir)
tmpIORdLimitFile := filepath.Join(tmpCgroupDir, "blkio.throttle.read_bps_device")
tmpIOWrLimitFile := filepath.Join(tmpCgroupDir, "blkio.throttle.write_bps_device")
devInfos := []types.DevInfo{{Name: "test-sda", Major: "1", Minor: "8"}}
type ioTest struct {
limit uint64
expected string
}
ioTests := []ioTest{
{limit: 10 * 1024 * 1024, expected: "1:8 10485760"},
{limit: 0, expected: "1:8 0"},
}
for i, test := range ioTests {
// 清除测试环境
os.Remove(tmpIORdLimitFile)
os.Remove(tmpIOWrLimitFile)
devLmts, err := SetIORWLimit(tmpCgroupDir, devInfos, test.limit, test.limit)
if err != nil {
t.Fatal(i, err)
}
if devLmt, ok := devLmts["1:8"]; !ok {
t.Fatal(i, "device 1:8 not exist")
} else if !devLmt.RdLimit || !devLmt.WrLimit {
t.Fatal(i, "device 1:8 rw limit error")
}
ioRdLimitContent, err := ioutil.ReadFile(tmpIORdLimitFile)
if err != nil {
t.Fatal(i, err)
}
if string(ioRdLimitContent) != test.expected {
t.Logf("%d io read limit error, content: %s", i, string(ioRdLimitContent))
}
ioWrLimitContent, err := ioutil.ReadFile(tmpIOWrLimitFile)
if err != nil {
t.Fatal(i, err)
}
if string(ioWrLimitContent) != test.expected {
t.Fatalf("%d io write limit error, content: %s", i, string(ioWrLimitContent))
}
}
}
package fs
import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"linkfog.com/public/lib/cgroup/fscommon"
"linkfog.com/public/lib/l"
)
var (
defaultCPU = "-1"
)
func CPUSubSystemName() string {
return "cpuacct"
}
func GetCPULimit(cgroupDir string, defaultLimit float64) float64 {
// parse quota
contents, err := fscommon.GetCgroupParamString(filepath.Join(cgroupDir, "cpu.cfs_quota_us"))
if err != nil {
l.Errorf("read cpu.cfs_quota_us file error:%v, use default value", err)
return defaultLimit
}
if contents == defaultCPU {
l.Warn("cpu.cfs_quota_us is defaultCPU, use default value")
return defaultLimit
}
cpuQuota, err := fscommon.ParseUint(contents, 10, 64)
if err != nil {
l.Errorf("parse cpu.cfs_quota_us error:%v, contents:%s, use default value", err, contents)
return defaultLimit
}
if cpuQuota == 0 {
l.Warn("cpu.cfs_quota_us is 0, use default value")
return defaultLimit
}
// parse period
cpuPeriod, err := fscommon.GetCgroupParamUint(filepath.Join(cgroupDir, "cpu.cfs_period_us"))
if err != nil {
l.Errorf("read cpu.cfs_period_us file error:%v, use default value", err)
return defaultLimit
}
if cpuPeriod == 0 {
l.Warn("cpu.cfs_period_us is 0, use default value")
return defaultLimit
}
return float64(cpuQuota) / float64(cpuPeriod)
}
func GetCPUUsage(cgroupDir string) (totalUsage uint64, perCPUUsage []uint64, err error) {
totalUsage, err = fscommon.GetCgroupParamUint(filepath.Join(cgroupDir, "cpuacct.usage"))
if err != nil {
return
}
perCPUUsage, err = getPerCPUUsage(cgroupDir)
if err != nil {
return
}
if totalUsage == 0 {
l.Warn("get cpu total usage is 0, use per-cpu usage accumulation value")
for _, usage := range perCPUUsage {
totalUsage += usage
}
}
return
}
func getPerCPUUsage(cgroupDir string) ([]uint64, error) {
var perCPUUsage []uint64
data, err := os.ReadFile(filepath.Join(cgroupDir, "cpuacct.usage_percpu"))
if err != nil {
return perCPUUsage, err
}
for _, value := range strings.Fields(string(data)) {
value, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return perCPUUsage, fmt.Errorf("unable to convert param value to uint64: %s", err)
}
perCPUUsage = append(perCPUUsage, value)
}
return perCPUUsage, nil
}
package fs
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestGetCPULimit(t *testing.T) {
tmpCgroupDir, err := ioutil.TempDir("/tmp", "cgroup-")
if err != nil {
t.Fatal("Cannot create temporary file", err)
}
defer os.RemoveAll(tmpCgroupDir)
tmpCPUPeriodFile := filepath.Join(tmpCgroupDir, "cpu.cfs_period_us")
tmpCPUQuotaFile := filepath.Join(tmpCgroupDir, "cpu.cfs_quota_us")
DefaultCPULimit := float64(2)
type cpuTest struct {
period string
quota string
limit float64
}
cpuTests := []cpuTest{
{period: "100000", quota: defaultCPU, limit: DefaultCPULimit},
{period: "100000", quota: "200000", limit: float64(2)},
{period: "100000", quota: "0", limit: DefaultCPULimit},
{period: "0", quota: "200000", limit: DefaultCPULimit},
}
for i, test := range cpuTests {
err = ioutil.WriteFile(tmpCPUPeriodFile, []byte(test.period), 0)
if err != nil {
t.Fatal(i, "Failed to write to temporary file", err)
}
err = ioutil.WriteFile(tmpCPUQuotaFile, []byte(test.quota), 0)
if err != nil {
t.Fatal(i, "Failed to write to temporary file", err)
}
cpuLimit := GetCPULimit(tmpCgroupDir, DefaultCPULimit)
if cpuLimit != test.limit {
t.Fatalf("%d cpuLimit(%f) != expected(%f)", i, cpuLimit, test.limit)
}
}
}
package fs
import (
"bufio"
"os"
"path/filepath"
"linkfog.com/public/lib/cgroup/fscommon"
"linkfog.com/public/lib/l"
)
var (
defaultMem = "9223372036854771712"
)
func MemorySubSystemName() string {
return "memory"
}
func GetMemoryLimit(cgroupDir string, defaultLimit float64) float64 {
contents, err := fscommon.GetCgroupParamString(filepath.Join(cgroupDir, "memory.limit_in_bytes"))
if err != nil {
l.Errorf("read memory.limit_in_bytes error:%v, use default value", err)
return defaultLimit
}
if contents == defaultMem {
l.Warn("memory.limit_in_bytes is defaultMem, use default value")
return defaultLimit
}
memLimit, err := fscommon.ParseUint(contents, 10, 64)
if err != nil {
l.Errorf("parse memory.limit_in_bytes error:%v, contents:%s, use default value", err, contents)
return defaultLimit
}
if memLimit == 0 {
l.Warn("memory.limit_in_bytes is 0, use default value")
return defaultLimit
}
return float64(memLimit)
}
func GetMemoryUsage(cgroupDir string) (usage uint64, workingSet uint64, err error) {
var stats map[string]uint64
stats, err = ParseMemoryStat(filepath.Join(cgroupDir, "memory.stat"))
if err != nil {
return
}
usage, err = fscommon.GetCgroupParamUint(filepath.Join(cgroupDir, "memory.usage_in_bytes"))
if err != nil {
return
}
workingSet = usage
if v, ok := stats["total_inactive_file"]; ok {
if workingSet < v {
workingSet = 0
} else {
workingSet -= v
}
}
return
}
func ParseMemoryStat(statPath string) (map[string]uint64, error) {
statFile, err := os.Open(statPath)
if err != nil {
return nil, err
}
defer statFile.Close()
stats := make(map[string]uint64)
sc := bufio.NewScanner(statFile)
for sc.Scan() {
t, v, err := fscommon.ParseKeyValue(sc.Text())
if err != nil {
return nil, err
}
stats[t] = v
}
if err = sc.Err(); err != nil {
return nil, err
}
return stats, nil
}
func GetKernelMemoryUsage(cgroupDir string) (usage uint64, err error) {
return fscommon.GetCgroupParamUint(filepath.Join(cgroupDir, "memory.kmem.usage_in_bytes"))
}
func GetKernelMemorySlabInfo(cgroupDir string) (slabInfo SlabInfo, err error) {
return ParseSlabInfo(filepath.Join(cgroupDir, "memory.kmem.slabinfo"))
}
package fs
import (
"io/ioutil"
"os"
"path/filepath"
"sort"
"testing"
)
func TestGetMemoryLimit(t *testing.T) {
tmpCgroupDir, err := ioutil.TempDir("/tmp", "cgroup-")
if err != nil {
t.Fatal("Cannot create temporary file", err)
}
defer os.RemoveAll(tmpCgroupDir)
tmpMemLimitFile := filepath.Join(tmpCgroupDir, "memory.limit_in_bytes")
DefaultMemLimit := float64(3 * 1024 * 1024 * 1024)
type memTest struct {
limitSet string
limitGet float64
}
memTests := []memTest{
{limitSet: defaultMem, limitGet: DefaultMemLimit},
{limitSet: "1024000000", limitGet: float64(1024000000)},
{limitSet: "0", limitGet: DefaultMemLimit},
}
for i, test := range memTests {
err = ioutil.WriteFile(tmpMemLimitFile, []byte(test.limitSet), 0)
if err != nil {
t.Fatal(i, "Failed to write to temporary file", err)
}
memLimit := GetMemoryLimit(tmpCgroupDir, DefaultMemLimit)
if memLimit != test.limitGet {
t.Fatalf("%d memLimit(%f) != expected(%f)", i, memLimit, test.limitGet)
}
}
}
func TestGetMemoryUsage(t *testing.T) {
usage, workingSet, err := GetMemoryUsage("../testdata/cg1_memory")
if err != nil {
t.Error("err:", err)
return
}
if usage != 102400000 {
t.Errorf("unexpected usage %d, should be %d", usage, 102400000)
return
}
if workingSet != 100000000 {
t.Errorf("unexpected usage %d, should be %d", workingSet, 100000000)
return
}
}
func TestGetKernelMemoryUsage(t *testing.T) {
kmemUsage, err := GetKernelMemoryUsage("../testdata/cg1_memory")
if err != nil {
t.Error("err:", err)
return
}
if kmemUsage != 10240000 {
t.Errorf("unexpected kmem usage %d, should be %d", kmemUsage, 10240000)
return
}
}
func TestGetKernelMemorySlabInfo(t *testing.T) {
slabInfo, err := GetKernelMemorySlabInfo("../testdata/cg1_memory")
if err != nil {
t.Error("err:", err)
return
}
sort.Sort(slabInfo)
if len(slabInfo.Slabs) < 2 {
t.Error("unexpected slabInfo len", len(slabInfo.Slabs))
return
}
if slabInfo.Slabs[0].Name != "proc_inode_cache" || slabInfo.Slabs[1].Name != "dentry" {
t.Errorf("unexpected slabInfo sort, top1:%s, top2:%s", slabInfo.Slabs[0].Name, slabInfo.Slabs[1].Name)
return
}
}
package fs
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"regexp"
"strconv"
"strings"
)
// copy from github.com/prometheus/procfs@v0.8.0/slab.go
var (
slabSpace = regexp.MustCompile(`\s+`)
slabVer = regexp.MustCompile(`slabinfo -`)
slabHeader = regexp.MustCompile(`# name`)
)
// Slab represents a slab pool in the kernel.
type Slab struct {
Name string
ObjActive int64
ObjNum int64
ObjSize int64
ObjPerSlab int64
PagesPerSlab int64
// tunables
Limit int64
Batch int64
SharedFactor int64
SlabActive int64
SlabNum int64
SharedAvail int64
// custom
Cache int64 // ObjNum * ObjSize
}
// SlabInfo represents info for all slabs.
type SlabInfo struct {
Slabs []*Slab
}
func (s SlabInfo) Len() int {
return len(s.Slabs)
}
func (s SlabInfo) Less(i, j int) bool {
return s.Slabs[i].Cache > s.Slabs[j].Cache
}
func (s SlabInfo) Swap(i, j int) {
s.Slabs[i], s.Slabs[j] = s.Slabs[j], s.Slabs[i]
}
func shouldParseSlab(line string) bool {
if slabVer.MatchString(line) {
return false
}
if slabHeader.MatchString(line) {
return false
}
return true
}
// parseV21SlabEntry is used to parse a line from /proc/slabinfo version 2.1.
func parseV21SlabEntry(line string) (*Slab, error) {
// First cleanup whitespace.
l := slabSpace.ReplaceAllString(line, " ")
s := strings.Split(l, " ")
if len(s) != 16 {
return nil, fmt.Errorf("unable to parse: %q", line)
}
var err error
i := &Slab{Name: s[0]}
i.ObjActive, err = strconv.ParseInt(s[1], 10, 64)
if err != nil {
return nil, err
}
i.ObjNum, err = strconv.ParseInt(s[2], 10, 64)
if err != nil {
return nil, err
}
i.ObjSize, err = strconv.ParseInt(s[3], 10, 64)
if err != nil {
return nil, err
}
i.ObjPerSlab, err = strconv.ParseInt(s[4], 10, 64)
if err != nil {
return nil, err
}
i.PagesPerSlab, err = strconv.ParseInt(s[5], 10, 64)
if err != nil {
return nil, err
}
i.Limit, err = strconv.ParseInt(s[8], 10, 64)
if err != nil {
return nil, err
}
i.Batch, err = strconv.ParseInt(s[9], 10, 64)
if err != nil {
return nil, err
}
i.SharedFactor, err = strconv.ParseInt(s[10], 10, 64)
if err != nil {
return nil, err
}
i.SlabActive, err = strconv.ParseInt(s[13], 10, 64)
if err != nil {
return nil, err
}
i.SlabNum, err = strconv.ParseInt(s[14], 10, 64)
if err != nil {
return nil, err
}
i.SharedAvail, err = strconv.ParseInt(s[15], 10, 64)
if err != nil {
return nil, err
}
// custom
i.Cache = i.ObjNum * i.ObjSize
return i, nil
}
// parseSlabInfo21 is used to parse a slabinfo 2.1 file.
func parseSlabInfo21(r *bytes.Reader) (SlabInfo, error) {
scanner := bufio.NewScanner(r)
s := SlabInfo{Slabs: []*Slab{}}
for scanner.Scan() {
line := scanner.Text()
if !shouldParseSlab(line) {
continue
}
slab, err := parseV21SlabEntry(line)
if err != nil {
return s, err
}
s.Slabs = append(s.Slabs, slab)
}
return s, nil
}
// ParseSlabInfo reads data from slabinfo file
func ParseSlabInfo(filename string) (SlabInfo, error) {
// TODO: Consider passing options to allow for parsing different
// slabinfo versions. However, slabinfo 2.1 has been stable since
// kernel 2.6.10 and later.
data, err := ReadFileNoStat(filename)
if err != nil {
return SlabInfo{}, err
}
return parseSlabInfo21(bytes.NewReader(data))
}
// ReadFileNoStat uses io.ReadAll to read contents of entire file.
// This is similar to os.ReadFile but without the call to os.Stat, because
// many files in /proc and /sys report incorrect file sizes (either 0 or 4096).
// Reads a max file size of 1024kB. For files larger than this, a scanner
// should be used.
func ReadFileNoStat(filename string) ([]byte, error) {
const maxBufferSize = 1024 * 1024
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
reader := io.LimitReader(f, maxBufferSize)
return io.ReadAll(reader)
}
package fs2
import (
"bufio"
"os"
"path/filepath"
"strings"
"linkfog.com/public/lib/cgroup/fscommon"
"linkfog.com/public/lib/l"
)
func GetCPULimit(cgroupDir string, defaultLimit float64) float64 {
contents, err := fscommon.GetCgroupParamString(filepath.Join(cgroupDir, "cpu.max"))
if err != nil {
l.Errorf("read cpu.max file error:%v, use default value", err)
return defaultLimit
}
fields := strings.Split(contents, " ")
if len(fields) != 2 {
l.Errorf("parse cpu.max file error, contents:%s, use default value", contents)
return defaultLimit
}
// parse quota
if fields[0] == "max" {
l.Warn("cpu.max quota is max, use default value")
return defaultLimit
}
cpuQuota, err := fscommon.ParseUint(fields[0], 10, 64)
if err != nil {
l.Errorf("parse cpu.max quota error:%v, contents:%s, use default value", err, fields[0])
return defaultLimit
}
if cpuQuota == 0 {
l.Warn("cpu.max quota is 0, use default value")
return defaultLimit
}
// parse period
cpuPeriod, err := fscommon.ParseUint(fields[1], 10, 64)
if err != nil {
l.Errorf("parse cpu.max period error:%v, contents:%s, use default value", err, fields[1])
return defaultLimit
}
if cpuPeriod == 0 {
l.Warn("cpu.max period is 0, use default value")
return defaultLimit
}
return float64(cpuQuota) / float64(cpuPeriod)
}
func GetCPUUsage(cgroupDir string) (totalUsage uint64, err error) {
return statCPU(cgroupDir)
}
// Copied from: https://github.com/opencontainers/runc/blob/main/libcontainer/cgroups/fs2/cpu.go
func statCPU(dirPath string) (cpuUsage uint64, err error) {
f, err := os.Open(filepath.Join(dirPath, "cpu.stat"))
if err != nil {
return cpuUsage, err
}
defer f.Close()
sc := bufio.NewScanner(f)
for sc.Scan() {
t, v, err := fscommon.ParseKeyValue(sc.Text())
if err != nil {
return cpuUsage, err
}
switch t {
case "usage_usec":
return v * 1000, nil
}
}
if err := sc.Err(); err != nil {
return cpuUsage, err
}
return cpuUsage, nil
}
package fs2
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestGetCPULimit(t *testing.T) {
tmpCgroupDir, err := ioutil.TempDir("/tmp", "cgroup-")
if err != nil {
t.Fatal("Cannot create temporary file", err)
}
defer os.RemoveAll(tmpCgroupDir)
tmpCPUMaxFile := filepath.Join(tmpCgroupDir, "cpu.max")
DefaultCPULimit := float64(2)
type cpuTest struct {
period string
quota string
limit float64
}
cpuTests := []cpuTest{
{period: "100000", quota: "max", limit: DefaultCPULimit},
{period: "100000", quota: "200000", limit: float64(2)},
{period: "100000", quota: "0", limit: DefaultCPULimit},
{period: "0", quota: "200000", limit: DefaultCPULimit},
}
for i, test := range cpuTests {
err = ioutil.WriteFile(tmpCPUMaxFile, []byte(fmt.Sprintf("%s %s", test.quota, test.period)), 0)
if err != nil {
t.Fatal(i, "Failed to write to temporary file", err)
}
cpuLimit := GetCPULimit(tmpCgroupDir, DefaultCPULimit)
if cpuLimit != test.limit {
t.Fatalf("%d cpuLimit(%f) != expected(%f)", i, cpuLimit, test.limit)
}
}
}
package fs2
import (
"bufio"
"fmt"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"linkfog.com/public/lib/cgroup/types"
"linkfog.com/public/lib/l"
)
func SetIORWLimit(cgroupDir string, devinfos []types.DevInfo, rlimit, wlimit uint64) (map[string]*types.DevLimit, error) {
ioLimitPath := path.Join(cgroupDir, "io.max")
l.Infof("io limit path: %s, rlimit size: %d, wlimit size: %d", ioLimitPath, rlimit, wlimit)
f, err := os.OpenFile(ioLimitPath, os.O_WRONLY|os.O_CREATE, 0)
if err != nil {
return nil, err
}
defer f.Close()
devLmts := make(map[string]*types.DevLimit, 0)
ioRdLimitSuccCnt := 0
ioWrLimitSuccCnt := 0
for _, devinfo := range devinfos {
devKey := fmt.Sprintf("%s:%s", devinfo.Major, devinfo.Minor)
devLmts[devKey] = &types.DevLimit{
DevName: devinfo.Name,
RdLimit: false,
WrLimit: false,
}
var ioRdLimit string
var ioWrLimit string
if rlimit == 0 { // delete limit
ioRdLimit = fmt.Sprintf("%s:%s %s=max", devinfo.Major, devinfo.Minor, "rbps")
} else {
ioRdLimit = fmt.Sprintf("%s:%s %s=%d", devinfo.Major, devinfo.Minor, "rbps", rlimit)
}
if wlimit == 0 { // delete limit
ioWrLimit = fmt.Sprintf("%s:%s %s=max", devinfo.Major, devinfo.Minor, "wbps")
} else {
ioWrLimit = fmt.Sprintf("%s:%s %s=%d", devinfo.Major, devinfo.Minor, "wbps", wlimit)
}
// set io read limit
_, err := f.WriteString(ioRdLimit)
if err != nil {
l.Debugf("set %s io read limit error %v, config:%s", devinfo.Name, err, ioRdLimit)
} else {
devLmts[devKey].RdLimit = true
ioRdLimitSuccCnt++
l.Debugf("set %s io read limit success, config:%s", devinfo.Name, ioRdLimit)
}
// set io write limit
_, err = f.WriteString(ioWrLimit)
if err != nil {
l.Debugf("set %s io write limit error %v, config:%s", devinfo.Name, err, ioWrLimit)
} else {
devLmts[devKey].WrLimit = true
ioWrLimitSuccCnt++
l.Debugf("set %s io write limit success, config:%s", devinfo.Name, ioWrLimit)
}
}
if ioRdLimitSuccCnt == 0 || ioWrLimitSuccCnt == 0 {
return nil, fmt.Errorf("set io limit error, read_limit:%d, write_limit:%d",
ioRdLimitSuccCnt, ioWrLimitSuccCnt)
}
return devLmts, nil
}
// # cat io.stat
// 253:0 rbytes=0 wbytes=12288 rios=0 wios=3 dbytes=0 dios=0
// 8:0 rbytes=0 wbytes=12288 rios=0 wios=3 dbytes=0 dios=0
func GetAllDevIOStat(cgroupDir string) (map[string]*types.IOStat, error) {
values, err := parseIOStatFile(filepath.Join(cgroupDir, "io.stat"))
if err != nil {
return nil, err
}
ioStats := make(map[string]*types.IOStat, 0)
ioStats["total"] = &types.IOStat{}
for k, v := range values {
d := strings.Split(k, ":")
if len(d) != 2 {
continue
}
device := k
for _, item := range v {
d := strings.Split(item, "=")
if len(d) != 2 {
continue
}
op := d[0]
if op == "rbytes" || op == "wbytes" {
sizeUint64, err := strconv.ParseUint(d[1], 10, 64)
if err != nil {
return nil, err
}
if _, ok := ioStats[device]; !ok {
ioStats[device] = &types.IOStat{}
}
if op == "rbytes" {
ioStats[device].Read = sizeUint64
ioStats["total"].Read += sizeUint64
}
if op == "wbytes" {
ioStats[device].Write = sizeUint64
ioStats["total"].Write += sizeUint64
}
}
}
}
return ioStats, nil
}
func parseIOStatFile(ioStatFile string) (map[string][]string, error) {
f, err := os.OpenFile(ioStatFile, os.O_RDONLY, 0)
if err != nil {
return nil, err
}
defer f.Close()
ret := map[string][]string{}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if len(parts) < 2 {
continue
}
ret[parts[0]] = parts[1:]
}
if err := scanner.Err(); err != nil {
return nil, err
}
return ret, nil
}
package fs2
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"linkfog.com/public/lib/cgroup/types"
)
func TestSetIORWLimit(t *testing.T) {
tmpCgroupDir, err := ioutil.TempDir("/tmp", "cgroup-")
if err != nil {
t.Fatal("Cannot create temporary file", err)
}
defer os.RemoveAll(tmpCgroupDir)
tmpIOMaxFile := filepath.Join(tmpCgroupDir, "io.max")
devInfos := []types.DevInfo{{Name: "test-sda", Major: "1", Minor: "8"}}
type ioTest struct {
limit uint64
expected string
}
// 实际格式 8:0 rbytes=0 wbytes=12288 rios=0 wios=3 dbytes=0 dios=0
// 因写文件无法模拟真实的cgroup设置,使用非真实的期望结果做判断
ioTests := []ioTest{
{limit: 10 * 1024 * 1024, expected: "1:8 rbps=104857601:8 wbps=10485760"},
{limit: 0, expected: "1:8 rbps=max1:8 wbps=max"},
}
for i, test := range ioTests {
// 清除测试环境
os.Remove(tmpIOMaxFile)
devLmts, err := SetIORWLimit(tmpCgroupDir, devInfos, test.limit, test.limit)
if err != nil {
t.Fatal(i, err)
}
if devLmt, ok := devLmts["1:8"]; !ok {
t.Fatal(i, "device 1:8 not exist")
} else if !devLmt.RdLimit || !devLmt.WrLimit {
t.Fatal(i, "device 1:8 rw limit error")
}
ioMaxLimitContent, err := ioutil.ReadFile(tmpIOMaxFile)
if err != nil {
t.Fatal(i, err)
}
if string(ioMaxLimitContent) != test.expected {
t.Fatalf("%d io max limit error, content: %s", i, string(ioMaxLimitContent))
}
}
}
package fs2
import (
"errors"
"path/filepath"
"linkfog.com/public/lib/cgroup/fs"
"linkfog.com/public/lib/cgroup/fscommon"
"linkfog.com/public/lib/l"
)
func GetMemoryLimit(cgroupDir string, defaultLimit float64) float64 {
contents, err := fscommon.GetCgroupParamString(filepath.Join(cgroupDir, "memory.max"))
if err != nil {
l.Errorf("read memory.max error:%v, use default value", err)
return defaultLimit
}
if contents == "max" {
l.Warn("memory.max is max, use default value")
return defaultLimit
}
memLimit, err := fscommon.ParseUint(contents, 10, 64)
if err != nil {
l.Errorf("parse memory.max error:%v, contents:%s, use default value", err, contents)
return defaultLimit
}
if memLimit == 0 {
l.Warn("memory.max is 0, use default value")
return defaultLimit
}
return float64(memLimit)
}
func GetMemoryUsage(cgroupDir string) (usage uint64, workingSet uint64, err error) {
var stats map[string]uint64
stats, err = fs.ParseMemoryStat(filepath.Join(cgroupDir, "memory.stat"))
if err != nil {
return
}
usage, err = fscommon.GetCgroupParamUint(filepath.Join(cgroupDir, "memory.current"))
if err != nil {
return
}
workingSet = usage
if v, ok := stats["inactive_file"]; ok {
if workingSet < v {
workingSet = 0
} else {
workingSet -= v
}
}
return
}
func GetKernelMemoryUsage(cgroupDir string) (usage uint64, err error) {
return 0, errors.New("cgroup2: get kmem usage not implemented")
}
func GetKernelMemorySlabInfo(cgroupDir string) (slabInfo fs.SlabInfo, err error) {
return fs.SlabInfo{}, errors.New("cgroup2: get kmem slabinfo not implemented")
}
package fs2
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestGetMemoryLimit(t *testing.T) {
tmpCgroupDir, err := ioutil.TempDir("/tmp", "cgroup-")
if err != nil {
t.Fatal("Cannot create temporary file", err)
}
defer os.RemoveAll(tmpCgroupDir)
tmpMemLimitFile := filepath.Join(tmpCgroupDir, "memory.max")
DefaultMemLimit := float64(3 * 1024 * 1024 * 1024)
type memTest struct {
limitSet string
limitGet float64
}
memTests := []memTest{
{limitSet: "max", limitGet: DefaultMemLimit},
{limitSet: "1024000000", limitGet: float64(1024000000)},
{limitSet: "0", limitGet: DefaultMemLimit},
}
for i, test := range memTests {
err = ioutil.WriteFile(tmpMemLimitFile, []byte(test.limitSet), 0)
if err != nil {
t.Fatal(i, "Failed to write to temporary file", err)
}
memLimit := GetMemoryLimit(tmpCgroupDir, DefaultMemLimit)
if memLimit != test.limitGet {
t.Fatalf("%d memLimit(%f) != expected(%f)", i, memLimit, test.limitGet)
}
}
}
func TestGetMemoryUsage(t *testing.T) {
usage, workingSet, err := GetMemoryUsage("../testdata/cg2")
if err != nil {
t.Error("err:", err)
return
}
if usage != 102400001 {
t.Errorf("unexpected usage %d, should be %d", usage, 102400000)
return
}
if workingSet != 100000001 {
t.Errorf("unexpected usage %d, should be %d", workingSet, 100000000)
return
}
}
func TestGetKernelMemoryUsage(t *testing.T) {
_, err := GetKernelMemoryUsage("../testdata/cg2")
if err == nil {
t.Error("err should not be nil")
return
}
}
func TestGetKernelMemorySlabInfo(t *testing.T) {
_, err := GetKernelMemorySlabInfo("../testdata/cg2")
if err == nil {
t.Error("err should not be nil")
return
}
}
package fscommon
import (
"fmt"
"math"
"os"
"strconv"
"strings"
)
// Copied from: github.com/opencontainers/runc/libcontainer/cgroups/fscommon/utils.go
// ParseUint converts a string to an uint64 integer.
// Negative values are returned at zero as, due to kernel bugs,
// some of the memory cgroup stats can be negative.
func ParseUint(s string, base, bitSize int) (uint64, error) {
value, err := strconv.ParseUint(s, base, bitSize)
if err != nil {
intValue, intErr := strconv.ParseInt(s, base, bitSize)
// 1. Handle negative values greater than MinInt64 (and)
// 2. Handle negative values lesser than MinInt64
if intErr == nil && intValue < 0 {
return 0, nil
} else if intErr != nil && intErr.(*strconv.NumError).Err == strconv.ErrRange && intValue < 0 {
return 0, nil
}
return value, err
}
return value, nil
}
// ParseKeyValue parses a space-separated "name value" kind of cgroup
// parameter and returns its key as a string, and its value as uint64
// (ParseUint is used to convert the value). For example,
// "io_service_bytes 1234" will be returned as "io_service_bytes", 1234.
func ParseKeyValue(t string) (string, uint64, error) {
parts := strings.SplitN(t, " ", 3)
if len(parts) != 2 {
return "", 0, fmt.Errorf("line %q is not in key value format", t)
}
value, err := ParseUint(parts[1], 10, 64)
if err != nil {
return "", 0, fmt.Errorf("unable to convert to uint64: %v", err)
}
return parts[0], value, nil
}
// GetCgroupParamUint reads a single uint64 value from the specified cgroup file.
// If the value read is "max", the math.MaxUint64 is returned.
func GetCgroupParamUint(path string) (uint64, error) {
contents, err := GetCgroupParamString(path)
if err != nil {
return 0, err
}
contents = strings.TrimSpace(contents)
if contents == "max" {
return math.MaxUint64, nil
}
res, err := ParseUint(contents, 10, 64)
if err != nil {
return res, fmt.Errorf("unable to parse file %q", path)
}
return res, nil
}
// GetCgroupParamString reads a string from the specified cgroup file.
func GetCgroupParamString(path string) (string, error) {
contents, err := os.ReadFile(path)
if err != nil {
return "", err
}
return strings.TrimSpace(string(contents)), nil
}
package cgroup
import (
"fmt"
"os"
"path/filepath"
"strconv"
"linkfog.com/public/lib/l"
"linkfog.com/public/option"
)
var HostCgroupCfg string
func SetHostCgroup() (err error) {
if HostCgroupCfg != "" {
return nil
}
for i := 1; i < 10; i++ {
HostCgroupCfg, err = GetCgroup(i)
if err != nil {
l.Info(getCgroupPath(i), err)
continue
}
return nil
}
return fmt.Errorf("set host cgroup err")
}
func IsContainerCgroup(cgroup string) bool {
if HostCgroupCfg == "" || cgroup == "" {
return false
}
return cgroup != HostCgroupCfg
}
func GetCgroup(pid int) (string, error) {
cgroup, err := os.ReadFile(getCgroupPath(pid))
if err != nil {
return "", err
}
return string(cgroup), nil
}
func getCgroupPath(pid int) string {
return filepath.Join(option.HostProc, strconv.Itoa(pid), "cgroup")
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment