github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/cgroups/fs/cpuacct.go (about) 1 package fs 2 3 import ( 4 "bufio" 5 "os" 6 "strconv" 7 "strings" 8 9 "github.com/opencontainers/runc/libcontainer/cgroups" 10 "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 11 "github.com/opencontainers/runc/libcontainer/configs" 12 ) 13 14 const ( 15 cgroupCpuacctStat = "cpuacct.stat" 16 cgroupCpuacctUsageAll = "cpuacct.usage_all" 17 18 nanosecondsInSecond = 1000000000 19 20 userModeColumn = 1 21 kernelModeColumn = 2 22 cuacctUsageAllColumnsNumber = 3 23 24 // The value comes from `C.sysconf(C._SC_CLK_TCK)`, and 25 // on Linux it's a constant which is safe to be hard coded, 26 // so we can avoid using cgo here. For details, see: 27 // https://github.com/containerd/cgroups/pull/12 28 clockTicks uint64 = 100 29 ) 30 31 type CpuacctGroup struct{} 32 33 func (s *CpuacctGroup) Name() string { 34 return "cpuacct" 35 } 36 37 func (s *CpuacctGroup) Apply(path string, _ *configs.Resources, pid int) error { 38 return apply(path, pid) 39 } 40 41 func (s *CpuacctGroup) Set(_ string, _ *configs.Resources) error { 42 return nil 43 } 44 45 func (s *CpuacctGroup) GetStats(path string, stats *cgroups.Stats) error { 46 if !cgroups.PathExists(path) { 47 return nil 48 } 49 userModeUsage, kernelModeUsage, err := getCpuUsageBreakdown(path) 50 if err != nil { 51 return err 52 } 53 54 totalUsage, err := fscommon.GetCgroupParamUint(path, "cpuacct.usage") 55 if err != nil { 56 return err 57 } 58 59 percpuUsage, err := getPercpuUsage(path) 60 if err != nil { 61 return err 62 } 63 64 percpuUsageInKernelmode, percpuUsageInUsermode, err := getPercpuUsageInModes(path) 65 if err != nil { 66 return err 67 } 68 69 stats.CpuStats.CpuUsage.TotalUsage = totalUsage 70 stats.CpuStats.CpuUsage.PercpuUsage = percpuUsage 71 stats.CpuStats.CpuUsage.PercpuUsageInKernelmode = percpuUsageInKernelmode 72 stats.CpuStats.CpuUsage.PercpuUsageInUsermode = percpuUsageInUsermode 73 stats.CpuStats.CpuUsage.UsageInUsermode = userModeUsage 74 stats.CpuStats.CpuUsage.UsageInKernelmode = kernelModeUsage 75 return nil 76 } 77 78 // Returns user and kernel usage breakdown in nanoseconds. 79 func getCpuUsageBreakdown(path string) (uint64, uint64, error) { 80 var userModeUsage, kernelModeUsage uint64 81 const ( 82 userField = "user" 83 systemField = "system" 84 file = cgroupCpuacctStat 85 ) 86 87 // Expected format: 88 // user <usage in ticks> 89 // system <usage in ticks> 90 data, err := cgroups.ReadFile(path, file) 91 if err != nil { 92 return 0, 0, err 93 } 94 // TODO: use strings.SplitN instead. 95 fields := strings.Fields(data) 96 if len(fields) < 4 || fields[0] != userField || fields[2] != systemField { 97 return 0, 0, malformedLine(path, file, data) 98 } 99 if userModeUsage, err = strconv.ParseUint(fields[1], 10, 64); err != nil { 100 return 0, 0, &parseError{Path: path, File: file, Err: err} 101 } 102 if kernelModeUsage, err = strconv.ParseUint(fields[3], 10, 64); err != nil { 103 return 0, 0, &parseError{Path: path, File: file, Err: err} 104 } 105 106 return (userModeUsage * nanosecondsInSecond) / clockTicks, (kernelModeUsage * nanosecondsInSecond) / clockTicks, nil 107 } 108 109 func getPercpuUsage(path string) ([]uint64, error) { 110 const file = "cpuacct.usage_percpu" 111 percpuUsage := []uint64{} 112 data, err := cgroups.ReadFile(path, file) 113 if err != nil { 114 return percpuUsage, err 115 } 116 // TODO: use strings.SplitN instead. 117 for _, value := range strings.Fields(data) { 118 value, err := strconv.ParseUint(value, 10, 64) 119 if err != nil { 120 return percpuUsage, &parseError{Path: path, File: file, Err: err} 121 } 122 percpuUsage = append(percpuUsage, value) 123 } 124 return percpuUsage, nil 125 } 126 127 func getPercpuUsageInModes(path string) ([]uint64, []uint64, error) { 128 usageKernelMode := []uint64{} 129 usageUserMode := []uint64{} 130 const file = cgroupCpuacctUsageAll 131 132 fd, err := cgroups.OpenFile(path, file, os.O_RDONLY) 133 if os.IsNotExist(err) { 134 return usageKernelMode, usageUserMode, nil 135 } else if err != nil { 136 return nil, nil, err 137 } 138 defer fd.Close() 139 140 scanner := bufio.NewScanner(fd) 141 scanner.Scan() // skipping header line 142 143 for scanner.Scan() { 144 lineFields := strings.SplitN(scanner.Text(), " ", cuacctUsageAllColumnsNumber+1) 145 if len(lineFields) != cuacctUsageAllColumnsNumber { 146 continue 147 } 148 149 usageInKernelMode, err := strconv.ParseUint(lineFields[kernelModeColumn], 10, 64) 150 if err != nil { 151 return nil, nil, &parseError{Path: path, File: file, Err: err} 152 } 153 usageKernelMode = append(usageKernelMode, usageInKernelMode) 154 155 usageInUserMode, err := strconv.ParseUint(lineFields[userModeColumn], 10, 64) 156 if err != nil { 157 return nil, nil, &parseError{Path: path, File: file, Err: err} 158 } 159 usageUserMode = append(usageUserMode, usageInUserMode) 160 } 161 if err := scanner.Err(); err != nil { 162 return nil, nil, &parseError{Path: path, File: file, Err: err} 163 } 164 165 return usageKernelMode, usageUserMode, nil 166 }