machine.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  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. "bytes"
  18. "fmt"
  19. "io/ioutil"
  20. "path/filepath"
  21. "regexp"
  22. "strconv"
  23. "strings"
  24. // s390/s390x changes
  25. "runtime"
  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"
  31. "golang.org/x/sys/unix"
  32. )
  33. var (
  34. cpuRegExp = regexp.MustCompile(`^processor\s*:\s*([0-9]+)$`)
  35. coreRegExp = regexp.MustCompile(`^core id\s*:\s*([0-9]+)$`)
  36. nodeRegExp = regexp.MustCompile(`^physical id\s*:\s*([0-9]+)$`)
  37. nodeBusRegExp = regexp.MustCompile(`^node([0-9]+)$`)
  38. // Power systems have a different format so cater for both
  39. cpuClockSpeedMHz = regexp.MustCompile(`(?:cpu MHz|clock)\s*:\s*([0-9]+\.[0-9]+)(?:MHz)?`)
  40. memoryCapacityRegexp = regexp.MustCompile(`MemTotal:\s*([0-9]+) kB`)
  41. swapCapacityRegexp = regexp.MustCompile(`SwapTotal:\s*([0-9]+) kB`)
  42. )
  43. const maxFreqFile = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"
  44. const cpuBusPath = "/sys/bus/cpu/devices/"
  45. // GetClockSpeed returns the CPU clock speed, given a []byte formatted as the /proc/cpuinfo file.
  46. func GetClockSpeed(procInfo []byte) (uint64, error) {
  47. // s390/s390x, aarch64 and arm32 changes
  48. if isSystemZ() || isAArch64() || isArm32() {
  49. return 0, nil
  50. }
  51. // First look through sys to find a max supported cpu frequency.
  52. if utils.FileExists(maxFreqFile) {
  53. val, err := ioutil.ReadFile(maxFreqFile)
  54. if err != nil {
  55. return 0, err
  56. }
  57. var maxFreq uint64
  58. n, err := fmt.Sscanf(string(val), "%d", &maxFreq)
  59. if err != nil || n != 1 {
  60. return 0, fmt.Errorf("could not parse frequency %q", val)
  61. }
  62. return maxFreq, nil
  63. }
  64. // Fall back to /proc/cpuinfo
  65. matches := cpuClockSpeedMHz.FindSubmatch(procInfo)
  66. if len(matches) != 2 {
  67. return 0, fmt.Errorf("could not detect clock speed from output: %q", string(procInfo))
  68. }
  69. speed, err := strconv.ParseFloat(string(matches[1]), 64)
  70. if err != nil {
  71. return 0, err
  72. }
  73. // Convert to kHz
  74. return uint64(speed * 1000), nil
  75. }
  76. // GetMachineMemoryCapacity returns the machine's total memory from /proc/meminfo.
  77. // Returns the total memory capacity as an uint64 (number of bytes).
  78. func GetMachineMemoryCapacity() (uint64, error) {
  79. out, err := ioutil.ReadFile("/proc/meminfo")
  80. if err != nil {
  81. return 0, err
  82. }
  83. memoryCapacity, err := parseCapacity(out, memoryCapacityRegexp)
  84. if err != nil {
  85. return 0, err
  86. }
  87. return memoryCapacity, err
  88. }
  89. // GetMachineSwapCapacity returns the machine's total swap from /proc/meminfo.
  90. // Returns the total swap capacity as an uint64 (number of bytes).
  91. func GetMachineSwapCapacity() (uint64, error) {
  92. out, err := ioutil.ReadFile("/proc/meminfo")
  93. if err != nil {
  94. return 0, err
  95. }
  96. swapCapacity, err := parseCapacity(out, swapCapacityRegexp)
  97. if err != nil {
  98. return 0, err
  99. }
  100. return swapCapacity, err
  101. }
  102. // parseCapacity matches a Regexp in a []byte, returning the resulting value in bytes.
  103. // Assumes that the value matched by the Regexp is in KB.
  104. func parseCapacity(b []byte, r *regexp.Regexp) (uint64, error) {
  105. matches := r.FindSubmatch(b)
  106. if len(matches) != 2 {
  107. return 0, fmt.Errorf("failed to match regexp in output: %q", string(b))
  108. }
  109. m, err := strconv.ParseUint(string(matches[1]), 10, 64)
  110. if err != nil {
  111. return 0, err
  112. }
  113. // Convert to bytes.
  114. return m * 1024, err
  115. }
  116. /* Look for sysfs cpu path containing core_id */
  117. /* Such as: sys/bus/cpu/devices/cpu0/topology/core_id */
  118. func getCoreIdFromCpuBus(cpuBusPath string, threadId int) (int, error) {
  119. path := filepath.Join(cpuBusPath, fmt.Sprintf("cpu%d/topology", threadId))
  120. file := filepath.Join(path, "core_id")
  121. num, err := ioutil.ReadFile(file)
  122. if err != nil {
  123. return threadId, err
  124. }
  125. coreId, err := strconv.ParseInt(string(bytes.TrimSpace(num)), 10, 32)
  126. if err != nil {
  127. return threadId, err
  128. }
  129. if coreId < 0 {
  130. // report threadId if found coreId < 0
  131. coreId = int64(threadId)
  132. }
  133. return int(coreId), nil
  134. }
  135. /* Look for sysfs cpu path containing node id */
  136. /* Such as: /sys/bus/cpu/devices/cpu0/node%d */
  137. func getNodeIdFromCpuBus(cpuBusPath string, threadId int) (int, error) {
  138. path := filepath.Join(cpuBusPath, fmt.Sprintf("cpu%d", threadId))
  139. files, err := ioutil.ReadDir(path)
  140. if err != nil {
  141. return 0, err
  142. }
  143. nodeId := 0
  144. for _, file := range files {
  145. filename := file.Name()
  146. isNode, error := regexp.MatchString("^node([0-9]+)$", filename)
  147. if error != nil {
  148. continue
  149. }
  150. if !isNode {
  151. continue
  152. }
  153. ok, val, _ := extractValue(filename, nodeBusRegExp)
  154. if err != nil {
  155. continue
  156. }
  157. if ok {
  158. if val < 0 {
  159. continue
  160. }
  161. nodeId = val
  162. }
  163. }
  164. return nodeId, nil
  165. }
  166. func GetTopology(sysFs sysfs.SysFs, cpuinfo string) ([]info.Node, int, error) {
  167. nodes := []info.Node{}
  168. // s390/s390x changes
  169. if true == isSystemZ() {
  170. return nodes, getNumCores(), nil
  171. }
  172. numCores := 0
  173. lastThread := -1
  174. lastCore := -1
  175. lastNode := -1
  176. for _, line := range strings.Split(cpuinfo, "\n") {
  177. if line == "" {
  178. continue
  179. }
  180. ok, val, err := extractValue(line, cpuRegExp)
  181. if err != nil {
  182. return nil, -1, fmt.Errorf("could not parse cpu info from %q: %v", line, err)
  183. }
  184. if ok {
  185. thread := val
  186. numCores++
  187. if lastThread != -1 {
  188. // New cpu section. Save last one.
  189. nodeIdx, err := addNode(&nodes, lastNode)
  190. if err != nil {
  191. return nil, -1, fmt.Errorf("failed to add node %d: %v", lastNode, err)
  192. }
  193. nodes[nodeIdx].AddThread(lastThread, lastCore)
  194. lastCore = -1
  195. lastNode = -1
  196. }
  197. lastThread = thread
  198. /* On Arm platform, no 'core id' and 'physical id' in '/proc/cpuinfo'. */
  199. /* So we search sysfs cpu path directly. */
  200. /* This method can also be used on other platforms, such as x86, ppc64le... */
  201. /* /sys/bus/cpu/devices/cpu%d contains the information of 'core_id' & 'node_id'. */
  202. /* Such as: /sys/bus/cpu/devices/cpu0/topology/core_id */
  203. /* Such as: /sys/bus/cpu/devices/cpu0/node0 */
  204. if isAArch64() {
  205. val, err = getCoreIdFromCpuBus(cpuBusPath, lastThread)
  206. if err != nil {
  207. // Report thread id if no NUMA
  208. val = lastThread
  209. }
  210. lastCore = val
  211. val, err = getNodeIdFromCpuBus(cpuBusPath, lastThread)
  212. if err != nil {
  213. // Report node 0 if no NUMA
  214. val = 0
  215. }
  216. lastNode = val
  217. }
  218. continue
  219. }
  220. if isAArch64() {
  221. /* On Arm platform, no 'core id' and 'physical id' in '/proc/cpuinfo'. */
  222. continue
  223. }
  224. ok, val, err = extractValue(line, coreRegExp)
  225. if err != nil {
  226. return nil, -1, fmt.Errorf("could not parse core info from %q: %v", line, err)
  227. }
  228. if ok {
  229. lastCore = val
  230. continue
  231. }
  232. ok, val, err = extractValue(line, nodeRegExp)
  233. if err != nil {
  234. return nil, -1, fmt.Errorf("could not parse node info from %q: %v", line, err)
  235. }
  236. if ok {
  237. lastNode = val
  238. continue
  239. }
  240. }
  241. nodeIdx, err := addNode(&nodes, lastNode)
  242. if err != nil {
  243. return nil, -1, fmt.Errorf("failed to add node %d: %v", lastNode, err)
  244. }
  245. nodes[nodeIdx].AddThread(lastThread, lastCore)
  246. if numCores < 1 {
  247. return nil, numCores, fmt.Errorf("could not detect any cores")
  248. }
  249. for idx, node := range nodes {
  250. caches, err := sysinfo.GetCacheInfo(sysFs, node.Cores[0].Threads[0])
  251. if err != nil {
  252. klog.Errorf("failed to get cache information for node %d: %v", node.Id, err)
  253. continue
  254. }
  255. numThreadsPerCore := len(node.Cores[0].Threads)
  256. numThreadsPerNode := len(node.Cores) * numThreadsPerCore
  257. for _, cache := range caches {
  258. c := info.Cache{
  259. Size: cache.Size,
  260. Level: cache.Level,
  261. Type: cache.Type,
  262. }
  263. if cache.Cpus == numThreadsPerNode && cache.Level > 2 {
  264. // Add a node-level cache.
  265. nodes[idx].AddNodeCache(c)
  266. } else if cache.Cpus == numThreadsPerCore {
  267. // Add to each core.
  268. nodes[idx].AddPerCoreCache(c)
  269. }
  270. // Ignore unknown caches.
  271. }
  272. }
  273. return nodes, numCores, nil
  274. }
  275. func extractValue(s string, r *regexp.Regexp) (bool, int, error) {
  276. matches := r.FindSubmatch([]byte(s))
  277. if len(matches) == 2 {
  278. val, err := strconv.ParseInt(string(matches[1]), 10, 32)
  279. if err != nil {
  280. return false, -1, err
  281. }
  282. return true, int(val), nil
  283. }
  284. return false, -1, nil
  285. }
  286. func findNode(nodes []info.Node, id int) (bool, int) {
  287. for i, n := range nodes {
  288. if n.Id == id {
  289. return true, i
  290. }
  291. }
  292. return false, -1
  293. }
  294. func addNode(nodes *[]info.Node, id int) (int, error) {
  295. var idx int
  296. if id == -1 {
  297. // Some VMs don't fill topology data. Export single package.
  298. id = 0
  299. }
  300. ok, idx := findNode(*nodes, id)
  301. if !ok {
  302. // New node
  303. node := info.Node{Id: id}
  304. // Add per-node memory information.
  305. meminfo := fmt.Sprintf("/sys/devices/system/node/node%d/meminfo", id)
  306. out, err := ioutil.ReadFile(meminfo)
  307. // Ignore if per-node info is not available.
  308. if err == nil {
  309. m, err := parseCapacity(out, memoryCapacityRegexp)
  310. if err != nil {
  311. return -1, err
  312. }
  313. node.Memory = uint64(m)
  314. }
  315. *nodes = append(*nodes, node)
  316. idx = len(*nodes) - 1
  317. }
  318. return idx, nil
  319. }
  320. // s390/s390x changes
  321. func getMachineArch() (string, error) {
  322. uname := unix.Utsname{}
  323. err := unix.Uname(&uname)
  324. if err != nil {
  325. return "", err
  326. }
  327. return string(uname.Machine[:]), nil
  328. }
  329. // arm32 chanes
  330. func isArm32() bool {
  331. arch, err := getMachineArch()
  332. if err == nil {
  333. return strings.Contains(arch, "arm")
  334. }
  335. return false
  336. }
  337. // aarch64 changes
  338. func isAArch64() bool {
  339. arch, err := getMachineArch()
  340. if err == nil {
  341. return strings.Contains(arch, "aarch64")
  342. }
  343. return false
  344. }
  345. // s390/s390x changes
  346. func isSystemZ() bool {
  347. arch, err := getMachineArch()
  348. if err == nil {
  349. return strings.Contains(arch, "390")
  350. }
  351. return false
  352. }
  353. // s390/s390x changes
  354. func getNumCores() int {
  355. maxProcs := runtime.GOMAXPROCS(0)
  356. numCPU := runtime.NumCPU()
  357. if maxProcs < numCPU {
  358. return maxProcs
  359. }
  360. return numCPU
  361. }