sysfs.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. // Copyright 2014 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. package sysfs
  15. import (
  16. "bytes"
  17. "fmt"
  18. "io/ioutil"
  19. "os"
  20. "path"
  21. "path/filepath"
  22. "regexp"
  23. "strconv"
  24. "strings"
  25. "k8s.io/klog/v2"
  26. )
  27. const (
  28. blockDir = "/sys/block"
  29. cacheDir = "/sys/devices/system/cpu/cpu"
  30. netDir = "/sys/class/net"
  31. dmiDir = "/sys/class/dmi"
  32. ppcDevTree = "/proc/device-tree"
  33. s390xDevTree = "/etc" // s390/s390x changes
  34. meminfoFile = "meminfo"
  35. distanceFile = "distance"
  36. sysFsCPUTopology = "topology"
  37. // CPUPhysicalPackageID is a physical package id of cpu#. Typically corresponds to a physical socket number,
  38. // but the actual value is architecture and platform dependent.
  39. CPUPhysicalPackageID = "physical_package_id"
  40. // CPUCoreID is the CPU core ID of cpu#. Typically it is the hardware platform's identifier
  41. // (rather than the kernel's). The actual value is architecture and platform dependent.
  42. CPUCoreID = "core_id"
  43. coreIDFilePath = "/" + sysFsCPUTopology + "/core_id"
  44. packageIDFilePath = "/" + sysFsCPUTopology + "/physical_package_id"
  45. // memory size calculations
  46. cpuDirPattern = "cpu*[0-9]"
  47. nodeDirPattern = "node*[0-9]"
  48. //HugePagesNrFile name of nr_hugepages file in sysfs
  49. HugePagesNrFile = "nr_hugepages"
  50. )
  51. var (
  52. nodeDir = "/sys/devices/system/node/"
  53. )
  54. type CacheInfo struct {
  55. // cache id
  56. Id int
  57. // size in bytes
  58. Size uint64
  59. // cache type - instruction, data, unified
  60. Type string
  61. // distance from cpus in a multi-level hierarchy
  62. Level int
  63. // number of cpus that can access this cache.
  64. Cpus int
  65. }
  66. // Abstracts the lowest level calls to sysfs.
  67. type SysFs interface {
  68. // Get NUMA nodes paths
  69. GetNodesPaths() ([]string, error)
  70. // Get paths to CPUs in provided directory e.g. /sys/devices/system/node/node0 or /sys/devices/system/cpu
  71. GetCPUsPaths(cpusPath string) ([]string, error)
  72. // Get physical core id for specified CPU
  73. GetCoreID(coreIDFilePath string) (string, error)
  74. // Get physical package id for specified CPU
  75. GetCPUPhysicalPackageID(cpuPath string) (string, error)
  76. // Get total memory for specified NUMA node
  77. GetMemInfo(nodeDir string) (string, error)
  78. // Get hugepages from specified directory
  79. GetHugePagesInfo(hugePagesDirectory string) ([]os.FileInfo, error)
  80. // Get hugepage_nr from specified directory
  81. GetHugePagesNr(hugePagesDirectory string, hugePageName string) (string, error)
  82. // Get directory information for available block devices.
  83. GetBlockDevices() ([]os.FileInfo, error)
  84. // Get Size of a given block device.
  85. GetBlockDeviceSize(string) (string, error)
  86. // Get scheduler type for the block device.
  87. GetBlockDeviceScheduler(string) (string, error)
  88. // Get device major:minor number string.
  89. GetBlockDeviceNumbers(string) (string, error)
  90. GetNetworkDevices() ([]os.FileInfo, error)
  91. GetNetworkAddress(string) (string, error)
  92. GetNetworkMtu(string) (string, error)
  93. GetNetworkSpeed(string) (string, error)
  94. GetNetworkStatValue(dev string, stat string) (uint64, error)
  95. // Get directory information for available caches accessible to given cpu.
  96. GetCaches(id int) ([]os.FileInfo, error)
  97. // Get information for a cache accessible from the given cpu.
  98. GetCacheInfo(cpu int, cache string) (CacheInfo, error)
  99. GetSystemUUID() (string, error)
  100. // GetDistances returns distance array
  101. GetDistances(string) (string, error)
  102. // IsCPUOnline determines if CPU status from kernel hotplug machanism standpoint.
  103. // See: https://www.kernel.org/doc/html/latest/core-api/cpu_hotplug.html
  104. IsCPUOnline(dir string) bool
  105. }
  106. type realSysFs struct{}
  107. func NewRealSysFs() SysFs {
  108. return &realSysFs{}
  109. }
  110. func (fs *realSysFs) GetNodesPaths() ([]string, error) {
  111. pathPattern := fmt.Sprintf("%s%s", nodeDir, nodeDirPattern)
  112. return filepath.Glob(pathPattern)
  113. }
  114. func (fs *realSysFs) GetCPUsPaths(cpusPath string) ([]string, error) {
  115. pathPattern := fmt.Sprintf("%s/%s", cpusPath, cpuDirPattern)
  116. return filepath.Glob(pathPattern)
  117. }
  118. func (fs *realSysFs) GetCoreID(cpuPath string) (string, error) {
  119. coreIDFilePath := fmt.Sprintf("%s%s", cpuPath, coreIDFilePath)
  120. coreID, err := ioutil.ReadFile(coreIDFilePath)
  121. if err != nil {
  122. return "", err
  123. }
  124. return strings.TrimSpace(string(coreID)), err
  125. }
  126. func (fs *realSysFs) GetCPUPhysicalPackageID(cpuPath string) (string, error) {
  127. packageIDFilePath := fmt.Sprintf("%s%s", cpuPath, packageIDFilePath)
  128. packageID, err := ioutil.ReadFile(packageIDFilePath)
  129. if err != nil {
  130. return "", err
  131. }
  132. return strings.TrimSpace(string(packageID)), err
  133. }
  134. func (fs *realSysFs) GetMemInfo(nodePath string) (string, error) {
  135. meminfoPath := fmt.Sprintf("%s/%s", nodePath, meminfoFile)
  136. meminfo, err := ioutil.ReadFile(meminfoPath)
  137. if err != nil {
  138. return "", err
  139. }
  140. return strings.TrimSpace(string(meminfo)), err
  141. }
  142. func (fs *realSysFs) GetDistances(nodePath string) (string, error) {
  143. distancePath := fmt.Sprintf("%s/%s", nodePath, distanceFile)
  144. distance, err := ioutil.ReadFile(distancePath)
  145. if err != nil {
  146. return "", err
  147. }
  148. return strings.TrimSpace(string(distance)), err
  149. }
  150. func (fs *realSysFs) GetHugePagesInfo(hugePagesDirectory string) ([]os.FileInfo, error) {
  151. return ioutil.ReadDir(hugePagesDirectory)
  152. }
  153. func (fs *realSysFs) GetHugePagesNr(hugepagesDirectory string, hugePageName string) (string, error) {
  154. hugePageFilePath := fmt.Sprintf("%s%s/%s", hugepagesDirectory, hugePageName, HugePagesNrFile)
  155. hugePageFile, err := ioutil.ReadFile(hugePageFilePath)
  156. if err != nil {
  157. return "", err
  158. }
  159. return strings.TrimSpace(string(hugePageFile)), err
  160. }
  161. func (fs *realSysFs) GetBlockDevices() ([]os.FileInfo, error) {
  162. return ioutil.ReadDir(blockDir)
  163. }
  164. func (fs *realSysFs) GetBlockDeviceNumbers(name string) (string, error) {
  165. dev, err := ioutil.ReadFile(path.Join(blockDir, name, "/dev"))
  166. if err != nil {
  167. return "", err
  168. }
  169. return string(dev), nil
  170. }
  171. func (fs *realSysFs) GetBlockDeviceScheduler(name string) (string, error) {
  172. sched, err := ioutil.ReadFile(path.Join(blockDir, name, "/queue/scheduler"))
  173. if err != nil {
  174. return "", err
  175. }
  176. return string(sched), nil
  177. }
  178. func (fs *realSysFs) GetBlockDeviceSize(name string) (string, error) {
  179. size, err := ioutil.ReadFile(path.Join(blockDir, name, "/size"))
  180. if err != nil {
  181. return "", err
  182. }
  183. return string(size), nil
  184. }
  185. func (fs *realSysFs) GetNetworkDevices() ([]os.FileInfo, error) {
  186. files, err := ioutil.ReadDir(netDir)
  187. if err != nil {
  188. return nil, err
  189. }
  190. // Filter out non-directory & non-symlink files
  191. var dirs []os.FileInfo
  192. for _, f := range files {
  193. if f.Mode()|os.ModeSymlink != 0 {
  194. f, err = os.Stat(path.Join(netDir, f.Name()))
  195. if err != nil {
  196. continue
  197. }
  198. }
  199. if f.IsDir() {
  200. dirs = append(dirs, f)
  201. }
  202. }
  203. return dirs, nil
  204. }
  205. func (fs *realSysFs) GetNetworkAddress(name string) (string, error) {
  206. address, err := ioutil.ReadFile(path.Join(netDir, name, "/address"))
  207. if err != nil {
  208. return "", err
  209. }
  210. return string(address), nil
  211. }
  212. func (fs *realSysFs) GetNetworkMtu(name string) (string, error) {
  213. mtu, err := ioutil.ReadFile(path.Join(netDir, name, "/mtu"))
  214. if err != nil {
  215. return "", err
  216. }
  217. return string(mtu), nil
  218. }
  219. func (fs *realSysFs) GetNetworkSpeed(name string) (string, error) {
  220. speed, err := ioutil.ReadFile(path.Join(netDir, name, "/speed"))
  221. if err != nil {
  222. return "", err
  223. }
  224. return string(speed), nil
  225. }
  226. func (fs *realSysFs) GetNetworkStatValue(dev string, stat string) (uint64, error) {
  227. statPath := path.Join(netDir, dev, "/statistics", stat)
  228. out, err := ioutil.ReadFile(statPath)
  229. if err != nil {
  230. return 0, fmt.Errorf("failed to read stat from %q for device %q", statPath, dev)
  231. }
  232. var s uint64
  233. n, err := fmt.Sscanf(string(out), "%d", &s)
  234. if err != nil || n != 1 {
  235. return 0, fmt.Errorf("could not parse value from %q for file %s", string(out), statPath)
  236. }
  237. return s, nil
  238. }
  239. func (fs *realSysFs) GetCaches(id int) ([]os.FileInfo, error) {
  240. cpuPath := fmt.Sprintf("%s%d/cache", cacheDir, id)
  241. return ioutil.ReadDir(cpuPath)
  242. }
  243. func bitCount(i uint64) (count int) {
  244. for i != 0 {
  245. if i&1 == 1 {
  246. count++
  247. }
  248. i >>= 1
  249. }
  250. return
  251. }
  252. func getCPUCount(cache string) (count int, err error) {
  253. out, err := ioutil.ReadFile(path.Join(cache, "/shared_cpu_map"))
  254. if err != nil {
  255. return 0, err
  256. }
  257. masks := strings.Split(string(out), ",")
  258. for _, mask := range masks {
  259. // convert hex string to uint64
  260. m, err := strconv.ParseUint(strings.TrimSpace(mask), 16, 64)
  261. if err != nil {
  262. return 0, fmt.Errorf("failed to parse cpu map %q: %v", string(out), err)
  263. }
  264. count += bitCount(m)
  265. }
  266. return
  267. }
  268. func (fs *realSysFs) GetCacheInfo(cpu int, name string) (CacheInfo, error) {
  269. cachePath := fmt.Sprintf("%s%d/cache/%s", cacheDir, cpu, name)
  270. out, err := ioutil.ReadFile(path.Join(cachePath, "/id"))
  271. if err != nil {
  272. return CacheInfo{}, err
  273. }
  274. var id int
  275. n, err := fmt.Sscanf(string(out), "%d", &id)
  276. if err != nil || n != 1 {
  277. return CacheInfo{}, err
  278. }
  279. out, err = ioutil.ReadFile(path.Join(cachePath, "/size"))
  280. if err != nil {
  281. return CacheInfo{}, err
  282. }
  283. var size uint64
  284. n, err = fmt.Sscanf(string(out), "%dK", &size)
  285. if err != nil || n != 1 {
  286. return CacheInfo{}, err
  287. }
  288. // convert to bytes
  289. size = size * 1024
  290. out, err = ioutil.ReadFile(path.Join(cachePath, "/level"))
  291. if err != nil {
  292. return CacheInfo{}, err
  293. }
  294. var level int
  295. n, err = fmt.Sscanf(string(out), "%d", &level)
  296. if err != nil || n != 1 {
  297. return CacheInfo{}, err
  298. }
  299. out, err = ioutil.ReadFile(path.Join(cachePath, "/type"))
  300. if err != nil {
  301. return CacheInfo{}, err
  302. }
  303. cacheType := strings.TrimSpace(string(out))
  304. cpuCount, err := getCPUCount(cachePath)
  305. if err != nil {
  306. return CacheInfo{}, err
  307. }
  308. return CacheInfo{
  309. Id: id,
  310. Size: size,
  311. Level: level,
  312. Type: cacheType,
  313. Cpus: cpuCount,
  314. }, nil
  315. }
  316. func (fs *realSysFs) GetSystemUUID() (string, error) {
  317. if id, err := ioutil.ReadFile(path.Join(dmiDir, "id", "product_uuid")); err == nil {
  318. return strings.TrimSpace(string(id)), nil
  319. } else if id, err = ioutil.ReadFile(path.Join(ppcDevTree, "system-id")); err == nil {
  320. return strings.TrimSpace(strings.TrimRight(string(id), "\000")), nil
  321. } else if id, err = ioutil.ReadFile(path.Join(ppcDevTree, "vm,uuid")); err == nil {
  322. return strings.TrimSpace(strings.TrimRight(string(id), "\000")), nil
  323. } else if id, err = ioutil.ReadFile(path.Join(s390xDevTree, "machine-id")); err == nil {
  324. return strings.TrimSpace(string(id)), nil
  325. } else {
  326. return "", err
  327. }
  328. }
  329. func (fs *realSysFs) IsCPUOnline(cpuPath string) bool {
  330. onlinePath, err := filepath.Abs(cpuPath + "/../online")
  331. if err != nil {
  332. klog.V(1).Infof("Unable to get absolute path for %s", cpuPath)
  333. return false
  334. }
  335. // Quick check to determine if file exists: if it does not then kernel CPU hotplug is disabled and all CPUs are online.
  336. _, err = os.Stat(onlinePath)
  337. if err != nil && os.IsNotExist(err) {
  338. return true
  339. }
  340. if err != nil {
  341. klog.V(1).Infof("Unable to stat %s: %s", onlinePath, err)
  342. }
  343. cpuID, err := getCPUID(cpuPath)
  344. if err != nil {
  345. klog.V(1).Infof("Unable to get CPU ID from path %s: %s", cpuPath, err)
  346. return false
  347. }
  348. isOnline, err := isCPUOnline(onlinePath, cpuID)
  349. if err != nil {
  350. klog.V(1).Infof("Unable to get online CPUs list: %s", err)
  351. return false
  352. }
  353. return isOnline
  354. }
  355. func getCPUID(dir string) (uint16, error) {
  356. regex := regexp.MustCompile("cpu([0-9]+)")
  357. matches := regex.FindStringSubmatch(dir)
  358. if len(matches) == 2 {
  359. id, err := strconv.Atoi(matches[1])
  360. if err != nil {
  361. return 0, err
  362. }
  363. return uint16(id), nil
  364. }
  365. return 0, fmt.Errorf("can't get CPU ID from %s", dir)
  366. }
  367. // isCPUOnline is copied from github.com/opencontainers/runc/libcontainer/cgroups/fs and modified to suite cAdvisor
  368. // needs as Apache 2.0 license allows.
  369. // It parses CPU list (such as: 0,3-5,10) into a struct that allows to determine quickly if CPU or particular ID is online.
  370. // see: https://github.com/opencontainers/runc/blob/ab27e12cebf148aa5d1ee3ad13d9fc7ae12bf0b6/libcontainer/cgroups/fs/cpuset.go#L45
  371. func isCPUOnline(path string, cpuID uint16) (bool, error) {
  372. fileContent, err := ioutil.ReadFile(path)
  373. if err != nil {
  374. return false, err
  375. }
  376. if len(fileContent) == 0 {
  377. return false, fmt.Errorf("%s found to be empty", path)
  378. }
  379. cpuList := strings.TrimSpace(string(fileContent))
  380. for _, s := range strings.Split(cpuList, ",") {
  381. splitted := strings.SplitN(s, "-", 3)
  382. switch len(splitted) {
  383. case 3:
  384. return false, fmt.Errorf("invalid values in %s", path)
  385. case 2:
  386. min, err := strconv.ParseUint(splitted[0], 10, 16)
  387. if err != nil {
  388. return false, err
  389. }
  390. max, err := strconv.ParseUint(splitted[1], 10, 16)
  391. if err != nil {
  392. return false, err
  393. }
  394. if min > max {
  395. return false, fmt.Errorf("invalid values in %s", path)
  396. }
  397. for i := min; i <= max; i++ {
  398. if uint16(i) == cpuID {
  399. return true, nil
  400. }
  401. }
  402. case 1:
  403. value, err := strconv.ParseUint(s, 10, 16)
  404. if err != nil {
  405. return false, err
  406. }
  407. if uint16(value) == cpuID {
  408. return true, nil
  409. }
  410. }
  411. }
  412. return false, nil
  413. }
  414. // Looks for sysfs cpu path containing given CPU property, e.g. core_id or physical_package_id
  415. // and returns number of unique values of given property, exemplary usage: getting number of CPU physical cores
  416. func GetUniqueCPUPropertyCount(cpuBusPath string, propertyName string) int {
  417. absCPUBusPath, err := filepath.Abs(cpuBusPath)
  418. if err != nil {
  419. klog.Errorf("Cannot make %s absolute", cpuBusPath)
  420. return 0
  421. }
  422. pathPattern := absCPUBusPath + "/cpu*[0-9]"
  423. sysCPUPaths, err := filepath.Glob(pathPattern)
  424. if err != nil {
  425. klog.Errorf("Cannot find files matching pattern (pathPattern: %s), number of unique %s set to 0", pathPattern, propertyName)
  426. return 0
  427. }
  428. onlinePath, err := filepath.Abs(cpuBusPath + "/online")
  429. if err != nil {
  430. klog.V(1).Infof("Unable to get absolute path for %s", cpuBusPath+"/../online")
  431. return 0
  432. }
  433. if err != nil {
  434. klog.V(1).Infof("Unable to get online CPUs list: %s", err)
  435. return 0
  436. }
  437. uniques := make(map[string]bool)
  438. for _, sysCPUPath := range sysCPUPaths {
  439. cpuID, err := getCPUID(sysCPUPath)
  440. if err != nil {
  441. klog.V(1).Infof("Unable to get CPU ID from path %s: %s", sysCPUPath, err)
  442. return 0
  443. }
  444. isOnline, err := isCPUOnline(onlinePath, cpuID)
  445. if err != nil && !os.IsNotExist(err) {
  446. klog.V(1).Infof("Unable to determine CPU online state: %s", err)
  447. continue
  448. }
  449. if !isOnline && !os.IsNotExist(err) {
  450. continue
  451. }
  452. propertyPath := filepath.Join(sysCPUPath, sysFsCPUTopology, propertyName)
  453. propertyVal, err := ioutil.ReadFile(propertyPath)
  454. if err != nil {
  455. klog.Warningf("Cannot open %s, assuming 0 for %s of CPU %d", propertyPath, propertyName, cpuID)
  456. propertyVal = []byte("0")
  457. }
  458. packagePath := filepath.Join(sysCPUPath, sysFsCPUTopology, CPUPhysicalPackageID)
  459. packageVal, err := ioutil.ReadFile(packagePath)
  460. if err != nil {
  461. klog.Warningf("Cannot open %s, assuming 0 %s of CPU %d", packagePath, CPUPhysicalPackageID, cpuID)
  462. packageVal = []byte("0")
  463. }
  464. uniques[fmt.Sprintf("%s_%s", bytes.TrimSpace(propertyVal), bytes.TrimSpace(packageVal))] = true
  465. }
  466. return len(uniques)
  467. }