cgroup_linux.go 5.9 KB

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