numcpus_linux.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. // Copyright 2018 Tobias Klauser
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package numcpus
  15. import (
  16. "fmt"
  17. "os"
  18. "path/filepath"
  19. "strconv"
  20. "strings"
  21. "golang.org/x/sys/unix"
  22. )
  23. const (
  24. sysfsCPUBasePath = "/sys/devices/system/cpu"
  25. offline = "offline"
  26. online = "online"
  27. possible = "possible"
  28. present = "present"
  29. )
  30. func getFromCPUAffinity() (int, error) {
  31. var cpuSet unix.CPUSet
  32. if err := unix.SchedGetaffinity(0, &cpuSet); err != nil {
  33. return 0, err
  34. }
  35. return cpuSet.Count(), nil
  36. }
  37. func readCPURangeWith[T any](file string, f func(cpus string) (T, error)) (T, error) {
  38. var zero T
  39. buf, err := os.ReadFile(filepath.Join(sysfsCPUBasePath, file))
  40. if err != nil {
  41. return zero, err
  42. }
  43. return f(string(buf))
  44. }
  45. func countCPURange(cpus string) (int, error) {
  46. list, err := listCPURange(cpus)
  47. return len(list), err
  48. }
  49. func listCPURange(cpus string) ([]int, error) {
  50. cpus = strings.Trim(cpus, "\n ")
  51. // Treat empty file as valid. This might be the case if there are no offline CPUs in which
  52. // case /sys/devices/system/cpu/offline is empty.
  53. if cpus == "" {
  54. return []int{}, nil
  55. }
  56. var list []int
  57. for cpuRange := range strings.SplitSeq(cpus, ",") {
  58. if cpuRange == "" {
  59. return nil, fmt.Errorf("empty CPU range in CPU string %q", cpus)
  60. }
  61. from, to, found := strings.Cut(cpuRange, "-")
  62. first, err := strconv.ParseUint(from, 10, 32)
  63. if err != nil {
  64. return nil, err
  65. }
  66. if !found {
  67. // range containing a single element
  68. list = append(list, int(first))
  69. continue
  70. }
  71. last, err := strconv.ParseUint(to, 10, 32)
  72. if err != nil {
  73. return nil, err
  74. }
  75. if last < first {
  76. return nil, fmt.Errorf("last CPU in range (%d) less than first (%d)", last, first)
  77. }
  78. for cpu := int(first); cpu <= int(last); cpu++ {
  79. list = append(list, cpu)
  80. }
  81. }
  82. return list, nil
  83. }
  84. func getConfigured() (int, error) {
  85. entries, err := os.ReadDir(sysfsCPUBasePath)
  86. if err != nil {
  87. return 0, err
  88. }
  89. count := 0
  90. for _, entry := range entries {
  91. if name := entry.Name(); entry.IsDir() && strings.HasPrefix(name, "cpu") {
  92. _, err := strconv.ParseInt(name[3:], 10, 64)
  93. if err == nil {
  94. count++
  95. }
  96. }
  97. }
  98. return count, nil
  99. }
  100. func getKernelMax() (int, error) {
  101. buf, err := os.ReadFile(filepath.Join(sysfsCPUBasePath, "kernel_max"))
  102. if err != nil {
  103. return 0, err
  104. }
  105. n, err := strconv.ParseInt(strings.Trim(string(buf), "\n "), 10, 64)
  106. if err != nil {
  107. return 0, err
  108. }
  109. return int(n), nil
  110. }
  111. func getOffline() (int, error) {
  112. return readCPURangeWith(offline, countCPURange)
  113. }
  114. func getOnline() (int, error) {
  115. if n, err := getFromCPUAffinity(); err == nil {
  116. return n, nil
  117. }
  118. return readCPURangeWith(online, countCPURange)
  119. }
  120. func getPossible() (int, error) {
  121. return readCPURangeWith(possible, countCPURange)
  122. }
  123. func getPresent() (int, error) {
  124. return readCPURangeWith(present, countCPURange)
  125. }
  126. func listOffline() ([]int, error) {
  127. return readCPURangeWith(offline, listCPURange)
  128. }
  129. func listOnline() ([]int, error) {
  130. return readCPURangeWith(online, listCPURange)
  131. }
  132. func listPossible() ([]int, error) {
  133. return readCPURangeWith(possible, listCPURange)
  134. }
  135. func listPresent() ([]int, error) {
  136. return readCPURangeWith(present, listCPURange)
  137. }