cgroup_linux.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. package internal
  2. import (
  3. "bufio"
  4. "fmt"
  5. "os"
  6. "path"
  7. "strconv"
  8. "strings"
  9. "sync"
  10. "time"
  11. "github.com/zeromicro/go-zero/core/iox"
  12. "github.com/zeromicro/go-zero/core/lang"
  13. "golang.org/x/sys/unix"
  14. )
  15. const (
  16. cgroupDir = "/sys/fs/cgroup"
  17. cpuStatFile = cgroupDir + "/cpu.stat"
  18. cpusetFile = cgroupDir + "/cpuset.cpus.effective"
  19. )
  20. var (
  21. isUnifiedOnce sync.Once
  22. isUnified bool
  23. inUserNS bool
  24. nsOnce sync.Once
  25. )
  26. type cgroup interface {
  27. cpuQuotaUs() (int64, error)
  28. cpuPeriodUs() (uint64, error)
  29. cpus() ([]uint64, error)
  30. usageAllCpus() (uint64, error)
  31. }
  32. func currentCgroup() (cgroup, error) {
  33. if isCgroup2UnifiedMode() {
  34. return currentCgroupV2()
  35. }
  36. return currentCgroupV1()
  37. }
  38. type cgroupV1 struct {
  39. cgroups map[string]string
  40. }
  41. func (c *cgroupV1) cpuQuotaUs() (int64, error) {
  42. data, err := iox.ReadText(path.Join(c.cgroups["cpu"], "cpu.cfs_quota_us"))
  43. if err != nil {
  44. return 0, err
  45. }
  46. return strconv.ParseInt(data, 10, 64)
  47. }
  48. func (c *cgroupV1) cpuPeriodUs() (uint64, error) {
  49. data, err := iox.ReadText(path.Join(c.cgroups["cpu"], "cpu.cfs_period_us"))
  50. if err != nil {
  51. return 0, err
  52. }
  53. return parseUint(data)
  54. }
  55. func (c *cgroupV1) cpus() ([]uint64, error) {
  56. data, err := iox.ReadText(path.Join(c.cgroups["cpuset"], "cpuset.cpus"))
  57. if err != nil {
  58. return nil, err
  59. }
  60. return parseUints(data)
  61. }
  62. func (c *cgroupV1) usageAllCpus() (uint64, error) {
  63. data, err := iox.ReadText(path.Join(c.cgroups["cpuacct"], "cpuacct.usage"))
  64. if err != nil {
  65. return 0, err
  66. }
  67. return parseUint(data)
  68. }
  69. type cgroupV2 struct {
  70. cgroups map[string]string
  71. }
  72. func (c *cgroupV2) cpuQuotaUs() (int64, error) {
  73. data, err := iox.ReadText(path.Join(cgroupDir, "cpu.cfs_quota_us"))
  74. if err != nil {
  75. return 0, err
  76. }
  77. return strconv.ParseInt(data, 10, 64)
  78. }
  79. func (c *cgroupV2) cpuPeriodUs() (uint64, error) {
  80. data, err := iox.ReadText(path.Join(cgroupDir, "cpu.cfs_period_us"))
  81. if err != nil {
  82. return 0, err
  83. }
  84. return parseUint(data)
  85. }
  86. func (c *cgroupV2) cpus() ([]uint64, error) {
  87. data, err := iox.ReadText(cpusetFile)
  88. if err != nil {
  89. return nil, err
  90. }
  91. return parseUints(data)
  92. }
  93. func (c *cgroupV2) usageAllCpus() (uint64, error) {
  94. usec, err := parseUint(c.cgroups["usage_usec"])
  95. if err != nil {
  96. return 0, err
  97. }
  98. return usec * uint64(time.Microsecond), nil
  99. }
  100. func currentCgroupV1() (cgroup, error) {
  101. cgroupFile := fmt.Sprintf("/proc/%d/cgroup", os.Getpid())
  102. lines, err := iox.ReadTextLines(cgroupFile, iox.WithoutBlank())
  103. if err != nil {
  104. return nil, err
  105. }
  106. cgroups := make(map[string]string)
  107. for _, line := range lines {
  108. cols := strings.Split(line, ":")
  109. if len(cols) != 3 {
  110. return nil, fmt.Errorf("invalid cgroup line: %s", line)
  111. }
  112. subsys := cols[1]
  113. // only read cpu staff
  114. if !strings.HasPrefix(subsys, "cpu") {
  115. continue
  116. }
  117. // https://man7.org/linux/man-pages/man7/cgroups.7.html
  118. // comma-separated list of controllers for cgroup version 1
  119. fields := strings.Split(subsys, ",")
  120. for _, val := range fields {
  121. cgroups[val] = path.Join(cgroupDir, val)
  122. }
  123. }
  124. return &cgroupV1{
  125. cgroups: cgroups,
  126. }, nil
  127. }
  128. func currentCgroupV2() (cgroup, error) {
  129. lines, err := iox.ReadTextLines(cpuStatFile, iox.WithoutBlank())
  130. if err != nil {
  131. return nil, err
  132. }
  133. cgroups := make(map[string]string)
  134. for _, line := range lines {
  135. cols := strings.Fields(line)
  136. if len(cols) != 2 {
  137. return nil, fmt.Errorf("invalid cgroupV2 line: %s", line)
  138. }
  139. cgroups[cols[0]] = cols[1]
  140. }
  141. return &cgroupV2{
  142. cgroups: cgroups,
  143. }, nil
  144. }
  145. // isCgroup2UnifiedMode returns whether we are running in cgroup v2 unified mode.
  146. func isCgroup2UnifiedMode() bool {
  147. isUnifiedOnce.Do(func() {
  148. var st unix.Statfs_t
  149. err := unix.Statfs(cgroupDir, &st)
  150. if err != nil {
  151. if os.IsNotExist(err) && runningInUserNS() {
  152. // ignore the "not found" error if running in userns
  153. isUnified = false
  154. return
  155. }
  156. panic(fmt.Sprintf("cannot statfs cgroup root: %s", err))
  157. }
  158. isUnified = st.Type == unix.CGROUP2_SUPER_MAGIC
  159. })
  160. return isUnified
  161. }
  162. func parseUint(s string) (uint64, error) {
  163. v, err := strconv.ParseInt(s, 10, 64)
  164. if err != nil {
  165. if err.(*strconv.NumError).Err == strconv.ErrRange {
  166. return 0, nil
  167. }
  168. return 0, fmt.Errorf("cgroup: bad int format: %s", s)
  169. }
  170. if v < 0 {
  171. return 0, nil
  172. }
  173. return uint64(v), nil
  174. }
  175. func parseUints(val string) ([]uint64, error) {
  176. if val == "" {
  177. return nil, nil
  178. }
  179. ints := make(map[uint64]lang.PlaceholderType)
  180. cols := strings.Split(val, ",")
  181. for _, r := range cols {
  182. if strings.Contains(r, "-") {
  183. fields := strings.SplitN(r, "-", 2)
  184. min, err := parseUint(fields[0])
  185. if err != nil {
  186. return nil, fmt.Errorf("cgroup: bad int list format: %s", val)
  187. }
  188. max, err := parseUint(fields[1])
  189. if err != nil {
  190. return nil, fmt.Errorf("cgroup: bad int list format: %s", val)
  191. }
  192. if max < min {
  193. return nil, fmt.Errorf("cgroup: bad int list format: %s", val)
  194. }
  195. for i := min; i <= max; i++ {
  196. ints[i] = lang.Placeholder
  197. }
  198. } else {
  199. v, err := parseUint(r)
  200. if err != nil {
  201. return nil, err
  202. }
  203. ints[v] = lang.Placeholder
  204. }
  205. }
  206. var sets []uint64
  207. for k := range ints {
  208. sets = append(sets, k)
  209. }
  210. return sets, nil
  211. }
  212. // runningInUserNS detects whether we are currently running in a user namespace.
  213. func runningInUserNS() bool {
  214. nsOnce.Do(func() {
  215. file, err := os.Open("/proc/self/uid_map")
  216. if err != nil {
  217. // This kernel-provided file only exists if user namespaces are supported
  218. return
  219. }
  220. defer file.Close()
  221. buf := bufio.NewReader(file)
  222. l, _, err := buf.ReadLine()
  223. if err != nil {
  224. return
  225. }
  226. line := string(l)
  227. var a, b, c int64
  228. fmt.Sscanf(line, "%d %d %d", &a, &b, &c)
  229. /*
  230. * We assume we are in the initial user namespace if we have a full
  231. * range - 4294967295 uids starting at uid 0.
  232. */
  233. if a == 0 && b == 0 && c == 4294967295 {
  234. return
  235. }
  236. inUserNS = true
  237. })
  238. return inUserNS
  239. }