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