machine.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. // Copyright 2015 Google Inc. All Rights Reserved.
  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. // The machine package contains functions that extract machine-level specs.
  15. package machine
  16. import (
  17. "fmt"
  18. "io/ioutil"
  19. "os"
  20. "path"
  21. "regexp"
  22. // s390/s390x changes
  23. "runtime"
  24. "strconv"
  25. "strings"
  26. info "github.com/google/cadvisor/info/v1"
  27. "github.com/google/cadvisor/utils"
  28. "github.com/google/cadvisor/utils/sysfs"
  29. "github.com/google/cadvisor/utils/sysinfo"
  30. "k8s.io/klog/v2"
  31. "golang.org/x/sys/unix"
  32. )
  33. var (
  34. coreRegExp = regexp.MustCompile(`(?m)^core id\s*:\s*([0-9]+)$`)
  35. nodeRegExp = regexp.MustCompile(`(?m)^physical id\s*:\s*([0-9]+)$`)
  36. // Power systems have a different format so cater for both
  37. cpuClockSpeedMHz = regexp.MustCompile(`(?:cpu MHz|CPU MHz|clock)\s*:\s*([0-9]+\.[0-9]+)(?:MHz)?`)
  38. memoryCapacityRegexp = regexp.MustCompile(`MemTotal:\s*([0-9]+) kB`)
  39. swapCapacityRegexp = regexp.MustCompile(`SwapTotal:\s*([0-9]+) kB`)
  40. vendorIDRegexp = regexp.MustCompile(`vendor_id\s*:\s*(\w+)`)
  41. cpuBusPath = "/sys/bus/cpu/devices/"
  42. isMemoryController = regexp.MustCompile("mc[0-9]+")
  43. isDimm = regexp.MustCompile("dimm[0-9]+")
  44. machineArch = getMachineArch()
  45. maxFreqFile = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"
  46. )
  47. const memTypeFileName = "dimm_mem_type"
  48. const sizeFileName = "size"
  49. // GetCPUVendorID returns "vendor_id" reading /proc/cpuinfo file.
  50. func GetCPUVendorID(procInfo []byte) string {
  51. vendorID := ""
  52. matches := vendorIDRegexp.FindSubmatch(procInfo)
  53. if len(matches) != 2 {
  54. klog.Warning("Cannot read vendor id correctly, set empty.")
  55. return vendorID
  56. }
  57. vendorID = string(matches[1])
  58. return vendorID
  59. }
  60. // GetPhysicalCores returns number of CPU cores reading /proc/cpuinfo file or if needed information from sysfs cpu path
  61. func GetPhysicalCores(procInfo []byte) int {
  62. numCores := getUniqueMatchesCount(string(procInfo), coreRegExp)
  63. if numCores == 0 {
  64. // read number of cores from /sys/bus/cpu/devices/cpu*/topology/core_id to deal with processors
  65. // for which 'core id' is not available in /proc/cpuinfo
  66. numCores = sysfs.GetUniqueCPUPropertyCount(cpuBusPath, sysfs.CPUCoreID)
  67. }
  68. if numCores == 0 {
  69. klog.Errorf("Cannot read number of physical cores correctly, number of cores set to %d", numCores)
  70. }
  71. return numCores
  72. }
  73. // GetSockets returns number of CPU sockets reading /proc/cpuinfo file or if needed information from sysfs cpu path
  74. func GetSockets(procInfo []byte) int {
  75. numSocket := getUniqueMatchesCount(string(procInfo), nodeRegExp)
  76. if numSocket == 0 {
  77. // read number of sockets from /sys/bus/cpu/devices/cpu*/topology/physical_package_id to deal with processors
  78. // for which 'physical id' is not available in /proc/cpuinfo
  79. numSocket = sysfs.GetUniqueCPUPropertyCount(cpuBusPath, sysfs.CPUPhysicalPackageID)
  80. }
  81. if numSocket == 0 {
  82. klog.Errorf("Cannot read number of sockets correctly, number of sockets set to %d", numSocket)
  83. }
  84. return numSocket
  85. }
  86. // GetClockSpeed returns the CPU clock speed, given a []byte formatted as the /proc/cpuinfo file.
  87. func GetClockSpeed(procInfo []byte) (uint64, error) {
  88. // First look through sys to find a max supported cpu frequency.
  89. if utils.FileExists(maxFreqFile) {
  90. val, err := ioutil.ReadFile(maxFreqFile)
  91. if err != nil {
  92. return 0, err
  93. }
  94. var maxFreq uint64
  95. n, err := fmt.Sscanf(string(val), "%d", &maxFreq)
  96. if err != nil || n != 1 {
  97. return 0, fmt.Errorf("could not parse frequency %q", val)
  98. }
  99. return maxFreq, nil
  100. }
  101. // s390/s390x, mips64, riscv64, aarch64 and arm32 changes
  102. if isMips64() || isSystemZ() || isAArch64() || isArm32() || isRiscv64() {
  103. return 0, nil
  104. }
  105. // Fall back to /proc/cpuinfo
  106. matches := cpuClockSpeedMHz.FindSubmatch(procInfo)
  107. if len(matches) != 2 {
  108. return 0, fmt.Errorf("could not detect clock speed from output: %q", string(procInfo))
  109. }
  110. speed, err := strconv.ParseFloat(string(matches[1]), 64)
  111. if err != nil {
  112. return 0, err
  113. }
  114. // Convert to kHz
  115. return uint64(speed * 1000), nil
  116. }
  117. // GetMachineMemoryCapacity returns the machine's total memory from /proc/meminfo.
  118. // Returns the total memory capacity as an uint64 (number of bytes).
  119. func GetMachineMemoryCapacity() (uint64, error) {
  120. out, err := ioutil.ReadFile("/proc/meminfo")
  121. if err != nil {
  122. return 0, err
  123. }
  124. memoryCapacity, err := parseCapacity(out, memoryCapacityRegexp)
  125. if err != nil {
  126. return 0, err
  127. }
  128. return memoryCapacity, err
  129. }
  130. // GetMachineMemoryByType returns information about memory capacity and number of DIMMs.
  131. // Information is retrieved from sysfs edac per-DIMM API (/sys/devices/system/edac/mc/)
  132. // introduced in kernel 3.6. Documentation can be found at
  133. // https://www.kernel.org/doc/Documentation/admin-guide/ras.rst.
  134. // Full list of memory types can be found in edac_mc.c
  135. // (https://github.com/torvalds/linux/blob/v5.5/drivers/edac/edac_mc.c#L198)
  136. func GetMachineMemoryByType(edacPath string) (map[string]*info.MemoryInfo, error) {
  137. memory := map[string]*info.MemoryInfo{}
  138. names, err := ioutil.ReadDir(edacPath)
  139. // On some architectures (such as ARM) memory controller device may not exist.
  140. // If this is the case then we ignore error and return empty slice.
  141. _, ok := err.(*os.PathError)
  142. if err != nil && ok {
  143. return memory, nil
  144. } else if err != nil {
  145. return memory, err
  146. }
  147. for _, controllerDir := range names {
  148. controller := controllerDir.Name()
  149. if !isMemoryController.MatchString(controller) {
  150. continue
  151. }
  152. dimms, err := ioutil.ReadDir(path.Join(edacPath, controllerDir.Name()))
  153. if err != nil {
  154. return map[string]*info.MemoryInfo{}, err
  155. }
  156. for _, dimmDir := range dimms {
  157. dimm := dimmDir.Name()
  158. if !isDimm.MatchString(dimm) {
  159. continue
  160. }
  161. memType, err := ioutil.ReadFile(path.Join(edacPath, controller, dimm, memTypeFileName))
  162. if err != nil {
  163. return map[string]*info.MemoryInfo{}, err
  164. }
  165. readableMemType := strings.TrimSpace(string(memType))
  166. if _, exists := memory[readableMemType]; !exists {
  167. memory[readableMemType] = &info.MemoryInfo{}
  168. }
  169. size, err := ioutil.ReadFile(path.Join(edacPath, controller, dimm, sizeFileName))
  170. if err != nil {
  171. return map[string]*info.MemoryInfo{}, err
  172. }
  173. capacity, err := strconv.Atoi(strings.TrimSpace(string(size)))
  174. if err != nil {
  175. return map[string]*info.MemoryInfo{}, err
  176. }
  177. memory[readableMemType].Capacity += uint64(mbToBytes(capacity))
  178. memory[readableMemType].DimmCount++
  179. }
  180. }
  181. return memory, nil
  182. }
  183. func mbToBytes(megabytes int) int {
  184. return megabytes * 1024 * 1024
  185. }
  186. // GetMachineSwapCapacity returns the machine's total swap from /proc/meminfo.
  187. // Returns the total swap capacity as an uint64 (number of bytes).
  188. func GetMachineSwapCapacity() (uint64, error) {
  189. out, err := ioutil.ReadFile("/proc/meminfo")
  190. if err != nil {
  191. return 0, err
  192. }
  193. swapCapacity, err := parseCapacity(out, swapCapacityRegexp)
  194. if err != nil {
  195. return 0, err
  196. }
  197. return swapCapacity, err
  198. }
  199. // GetTopology returns CPU topology reading information from sysfs
  200. func GetTopology(sysFs sysfs.SysFs) ([]info.Node, int, error) {
  201. // s390/s390x changes
  202. if isSystemZ() {
  203. return nil, getNumCores(), nil
  204. }
  205. return sysinfo.GetNodesInfo(sysFs)
  206. }
  207. // parseCapacity matches a Regexp in a []byte, returning the resulting value in bytes.
  208. // Assumes that the value matched by the Regexp is in KB.
  209. func parseCapacity(b []byte, r *regexp.Regexp) (uint64, error) {
  210. matches := r.FindSubmatch(b)
  211. if len(matches) != 2 {
  212. return 0, fmt.Errorf("failed to match regexp in output: %q", string(b))
  213. }
  214. m, err := strconv.ParseUint(string(matches[1]), 10, 64)
  215. if err != nil {
  216. return 0, err
  217. }
  218. // Convert to bytes.
  219. return m * 1024, err
  220. }
  221. // getUniqueMatchesCount returns number of unique matches in given argument using provided regular expression
  222. func getUniqueMatchesCount(s string, r *regexp.Regexp) int {
  223. matches := r.FindAllString(s, -1)
  224. uniques := make(map[string]bool)
  225. for _, match := range matches {
  226. uniques[match] = true
  227. }
  228. return len(uniques)
  229. }
  230. func getMachineArch() string {
  231. uname := unix.Utsname{}
  232. err := unix.Uname(&uname)
  233. if err != nil {
  234. klog.Errorf("Cannot get machine architecture, err: %v", err)
  235. return ""
  236. }
  237. return string(uname.Machine[:])
  238. }
  239. // arm32 changes
  240. func isArm32() bool {
  241. return strings.Contains(machineArch, "arm")
  242. }
  243. // aarch64 changes
  244. func isAArch64() bool {
  245. return strings.Contains(machineArch, "aarch64")
  246. }
  247. // s390/s390x changes
  248. func isSystemZ() bool {
  249. return strings.Contains(machineArch, "390")
  250. }
  251. // riscv64 changes
  252. func isRiscv64() bool {
  253. return strings.Contains(machineArch, "riscv64")
  254. }
  255. // mips64 changes
  256. func isMips64() bool {
  257. return strings.Contains(machineArch, "mips64")
  258. }
  259. // s390/s390x changes
  260. func getNumCores() int {
  261. maxProcs := runtime.GOMAXPROCS(0)
  262. numCPU := runtime.NumCPU()
  263. if maxProcs < numCPU {
  264. return maxProcs
  265. }
  266. return numCPU
  267. }