mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-11 06:03:57 +08:00
1be06760ed
Initially, this was a commit to switch from strings.Fields to strings.SplitN in getCpuUsageBreakdown, since strings.Fields was probably slower than strings.SplitN in some old Go versions. Afterwards, strings.Cut was also considered for potential speed improvements. After writing a benchmark test, we learned that: - strings.Fields performance is now adequate; - strings.SplitN is slower than strings.Fields; - strings.Cut had <5% performance gain from strings.Fields; So, remove the TODO and keep the benchmark test. Signed-off-by: Stavros Panakakis <stavrospanakakis@gmail.com>
167 lines
4.5 KiB
Go
167 lines
4.5 KiB
Go
package fs
|
|
|
|
import (
|
|
"bufio"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/opencontainers/runc/libcontainer/cgroups"
|
|
"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
|
|
"github.com/opencontainers/runc/libcontainer/configs"
|
|
)
|
|
|
|
const (
|
|
cgroupCpuacctStat = "cpuacct.stat"
|
|
cgroupCpuacctUsageAll = "cpuacct.usage_all"
|
|
|
|
nanosecondsInSecond = 1000000000
|
|
|
|
userModeColumn = 1
|
|
kernelModeColumn = 2
|
|
cuacctUsageAllColumnsNumber = 3
|
|
|
|
// The value comes from `C.sysconf(C._SC_CLK_TCK)`, and
|
|
// on Linux it's a constant which is safe to be hard coded,
|
|
// so we can avoid using cgo here. For details, see:
|
|
// https://github.com/containerd/cgroups/pull/12
|
|
clockTicks uint64 = 100
|
|
)
|
|
|
|
type CpuacctGroup struct{}
|
|
|
|
func (s *CpuacctGroup) Name() string {
|
|
return "cpuacct"
|
|
}
|
|
|
|
func (s *CpuacctGroup) Apply(path string, _ *configs.Resources, pid int) error {
|
|
return apply(path, pid)
|
|
}
|
|
|
|
func (s *CpuacctGroup) Set(_ string, _ *configs.Resources) error {
|
|
return nil
|
|
}
|
|
|
|
func (s *CpuacctGroup) GetStats(path string, stats *cgroups.Stats) error {
|
|
if !cgroups.PathExists(path) {
|
|
return nil
|
|
}
|
|
userModeUsage, kernelModeUsage, err := getCpuUsageBreakdown(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
totalUsage, err := fscommon.GetCgroupParamUint(path, "cpuacct.usage")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
percpuUsage, err := getPercpuUsage(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
percpuUsageInKernelmode, percpuUsageInUsermode, err := getPercpuUsageInModes(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
stats.CpuStats.CpuUsage.TotalUsage = totalUsage
|
|
stats.CpuStats.CpuUsage.PercpuUsage = percpuUsage
|
|
stats.CpuStats.CpuUsage.PercpuUsageInKernelmode = percpuUsageInKernelmode
|
|
stats.CpuStats.CpuUsage.PercpuUsageInUsermode = percpuUsageInUsermode
|
|
stats.CpuStats.CpuUsage.UsageInUsermode = userModeUsage
|
|
stats.CpuStats.CpuUsage.UsageInKernelmode = kernelModeUsage
|
|
return nil
|
|
}
|
|
|
|
// Returns user and kernel usage breakdown in nanoseconds.
|
|
func getCpuUsageBreakdown(path string) (uint64, uint64, error) {
|
|
var userModeUsage, kernelModeUsage uint64
|
|
const (
|
|
userField = "user"
|
|
systemField = "system"
|
|
file = cgroupCpuacctStat
|
|
)
|
|
|
|
// Expected format:
|
|
// user <usage in ticks>
|
|
// system <usage in ticks>
|
|
data, err := cgroups.ReadFile(path, file)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
fields := strings.Fields(data)
|
|
if len(fields) < 4 || fields[0] != userField || fields[2] != systemField {
|
|
return 0, 0, malformedLine(path, file, data)
|
|
}
|
|
if userModeUsage, err = strconv.ParseUint(fields[1], 10, 64); err != nil {
|
|
return 0, 0, &parseError{Path: path, File: file, Err: err}
|
|
}
|
|
if kernelModeUsage, err = strconv.ParseUint(fields[3], 10, 64); err != nil {
|
|
return 0, 0, &parseError{Path: path, File: file, Err: err}
|
|
}
|
|
|
|
return (userModeUsage * nanosecondsInSecond) / clockTicks, (kernelModeUsage * nanosecondsInSecond) / clockTicks, nil
|
|
}
|
|
|
|
func getPercpuUsage(path string) ([]uint64, error) {
|
|
const file = "cpuacct.usage_percpu"
|
|
percpuUsage := []uint64{}
|
|
data, err := cgroups.ReadFile(path, file)
|
|
if err != nil {
|
|
return percpuUsage, err
|
|
}
|
|
// TODO: use strings.SplitN instead.
|
|
for _, value := range strings.Fields(data) {
|
|
value, err := strconv.ParseUint(value, 10, 64)
|
|
if err != nil {
|
|
return percpuUsage, &parseError{Path: path, File: file, Err: err}
|
|
}
|
|
percpuUsage = append(percpuUsage, value)
|
|
}
|
|
return percpuUsage, nil
|
|
}
|
|
|
|
func getPercpuUsageInModes(path string) ([]uint64, []uint64, error) {
|
|
usageKernelMode := []uint64{}
|
|
usageUserMode := []uint64{}
|
|
const file = cgroupCpuacctUsageAll
|
|
|
|
fd, err := cgroups.OpenFile(path, file, os.O_RDONLY)
|
|
if os.IsNotExist(err) {
|
|
return usageKernelMode, usageUserMode, nil
|
|
} else if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
defer fd.Close()
|
|
|
|
scanner := bufio.NewScanner(fd)
|
|
scanner.Scan() // skipping header line
|
|
|
|
for scanner.Scan() {
|
|
lineFields := strings.SplitN(scanner.Text(), " ", cuacctUsageAllColumnsNumber+1)
|
|
if len(lineFields) != cuacctUsageAllColumnsNumber {
|
|
continue
|
|
}
|
|
|
|
usageInKernelMode, err := strconv.ParseUint(lineFields[kernelModeColumn], 10, 64)
|
|
if err != nil {
|
|
return nil, nil, &parseError{Path: path, File: file, Err: err}
|
|
}
|
|
usageKernelMode = append(usageKernelMode, usageInKernelMode)
|
|
|
|
usageInUserMode, err := strconv.ParseUint(lineFields[userModeColumn], 10, 64)
|
|
if err != nil {
|
|
return nil, nil, &parseError{Path: path, File: file, Err: err}
|
|
}
|
|
usageUserMode = append(usageUserMode, usageInUserMode)
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, nil, &parseError{Path: path, File: file, Err: err}
|
|
}
|
|
|
|
return usageKernelMode, usageUserMode, nil
|
|
}
|