1package cpu 2 3import ( 4 "context" 5 "errors" 6 "fmt" 7 "os/exec" 8 "regexp" 9 "runtime" 10 "sort" 11 "strconv" 12 "strings" 13 14 "github.com/tklauser/go-sysconf" 15) 16 17var ClocksPerSec = float64(128) 18 19func init() { 20 clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK) 21 // ignore errors 22 if err == nil { 23 ClocksPerSec = float64(clkTck) 24 } 25} 26 27//sum all values in a float64 map with float64 keys 28func msum(x map[float64]float64) float64 { 29 total := 0.0 30 for _, y := range x { 31 total += y 32 } 33 return total 34} 35 36func Times(percpu bool) ([]TimesStat, error) { 37 return TimesWithContext(context.Background(), percpu) 38} 39 40func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { 41 kstatSys, err := exec.LookPath("kstat") 42 if err != nil { 43 return nil, fmt.Errorf("cannot find kstat: %s", err) 44 } 45 cpu := make(map[float64]float64) 46 idle := make(map[float64]float64) 47 user := make(map[float64]float64) 48 kern := make(map[float64]float64) 49 iowt := make(map[float64]float64) 50 //swap := make(map[float64]float64) 51 kstatSysOut, err := invoke.CommandWithContext(ctx, kstatSys, "-p", "cpu_stat:*:*:/^idle$|^user$|^kernel$|^iowait$|^swap$/") 52 if err != nil { 53 return nil, fmt.Errorf("cannot execute kstat: %s", err) 54 } 55 re := regexp.MustCompile(`[:\s]+`) 56 for _, line := range strings.Split(string(kstatSysOut), "\n") { 57 fields := re.Split(line, -1) 58 if fields[0] != "cpu_stat" { 59 continue 60 } 61 cpuNumber, err := strconv.ParseFloat(fields[1], 64) 62 if err != nil { 63 return nil, fmt.Errorf("cannot parse cpu number: %s", err) 64 } 65 cpu[cpuNumber] = cpuNumber 66 switch fields[3] { 67 case "idle": 68 idle[cpuNumber], err = strconv.ParseFloat(fields[4], 64) 69 if err != nil { 70 return nil, fmt.Errorf("cannot parse idle: %s", err) 71 } 72 case "user": 73 user[cpuNumber], err = strconv.ParseFloat(fields[4], 64) 74 if err != nil { 75 return nil, fmt.Errorf("cannot parse user: %s", err) 76 } 77 case "kernel": 78 kern[cpuNumber], err = strconv.ParseFloat(fields[4], 64) 79 if err != nil { 80 return nil, fmt.Errorf("cannot parse kernel: %s", err) 81 } 82 case "iowait": 83 iowt[cpuNumber], err = strconv.ParseFloat(fields[4], 64) 84 if err != nil { 85 return nil, fmt.Errorf("cannot parse iowait: %s", err) 86 } 87 //not sure how this translates, don't report, add to kernel, something else? 88 /*case "swap": 89 swap[cpuNumber], err = strconv.ParseFloat(fields[4], 64) 90 if err != nil { 91 return nil, fmt.Errorf("cannot parse swap: %s", err) 92 } */ 93 } 94 } 95 ret := make([]TimesStat, 0, len(cpu)) 96 if percpu { 97 for _, c := range cpu { 98 ct := &TimesStat{ 99 CPU: fmt.Sprintf("cpu%d", int(cpu[c])), 100 Idle: idle[c] / ClocksPerSec, 101 User: user[c] / ClocksPerSec, 102 System: kern[c] / ClocksPerSec, 103 Iowait: iowt[c] / ClocksPerSec, 104 } 105 ret = append(ret, *ct) 106 } 107 } else { 108 ct := &TimesStat{ 109 CPU: "cpu-total", 110 Idle: msum(idle) / ClocksPerSec, 111 User: msum(user) / ClocksPerSec, 112 System: msum(kern) / ClocksPerSec, 113 Iowait: msum(iowt) / ClocksPerSec, 114 } 115 ret = append(ret, *ct) 116 } 117 return ret, nil 118} 119 120func Info() ([]InfoStat, error) { 121 return InfoWithContext(context.Background()) 122} 123 124func InfoWithContext(ctx context.Context) ([]InfoStat, error) { 125 psrInfo, err := exec.LookPath("psrinfo") 126 if err != nil { 127 return nil, fmt.Errorf("cannot find psrinfo: %s", err) 128 } 129 psrInfoOut, err := invoke.CommandWithContext(ctx, psrInfo, "-p", "-v") 130 if err != nil { 131 return nil, fmt.Errorf("cannot execute psrinfo: %s", err) 132 } 133 134 isaInfo, err := exec.LookPath("isainfo") 135 if err != nil { 136 return nil, fmt.Errorf("cannot find isainfo: %s", err) 137 } 138 isaInfoOut, err := invoke.CommandWithContext(ctx, isaInfo, "-b", "-v") 139 if err != nil { 140 return nil, fmt.Errorf("cannot execute isainfo: %s", err) 141 } 142 143 procs, err := parseProcessorInfo(string(psrInfoOut)) 144 if err != nil { 145 return nil, fmt.Errorf("error parsing psrinfo output: %s", err) 146 } 147 148 flags, err := parseISAInfo(string(isaInfoOut)) 149 if err != nil { 150 return nil, fmt.Errorf("error parsing isainfo output: %s", err) 151 } 152 153 result := make([]InfoStat, 0, len(flags)) 154 for _, proc := range procs { 155 procWithFlags := proc 156 procWithFlags.Flags = flags 157 result = append(result, procWithFlags) 158 } 159 160 return result, nil 161} 162 163var flagsMatch = regexp.MustCompile(`[\w\.]+`) 164 165func parseISAInfo(cmdOutput string) ([]string, error) { 166 words := flagsMatch.FindAllString(cmdOutput, -1) 167 168 // Sanity check the output 169 if len(words) < 4 || words[1] != "bit" || words[3] != "applications" { 170 return nil, errors.New("attempted to parse invalid isainfo output") 171 } 172 173 flags := make([]string, len(words)-4) 174 for i, val := range words[4:] { 175 flags[i] = val 176 } 177 sort.Strings(flags) 178 179 return flags, nil 180} 181 182var psrInfoMatch = regexp.MustCompile(`The physical processor has (?:([\d]+) virtual processors? \(([\d-]+)\)|([\d]+) cores and ([\d]+) virtual processors[^\n]+)\n(?:\s+ The core has.+\n)*\s+.+ \((\w+) ([\S]+) family (.+) model (.+) step (.+) clock (.+) MHz\)\n[\s]*(.*)`) 183 184const ( 185 psrNumCoresOffset = 1 186 psrNumCoresHTOffset = 3 187 psrNumHTOffset = 4 188 psrVendorIDOffset = 5 189 psrFamilyOffset = 7 190 psrModelOffset = 8 191 psrStepOffset = 9 192 psrClockOffset = 10 193 psrModelNameOffset = 11 194) 195 196func parseProcessorInfo(cmdOutput string) ([]InfoStat, error) { 197 matches := psrInfoMatch.FindAllStringSubmatch(cmdOutput, -1) 198 199 var infoStatCount int32 200 result := make([]InfoStat, 0, len(matches)) 201 for physicalIndex, physicalCPU := range matches { 202 var step int32 203 var clock float64 204 205 if physicalCPU[psrStepOffset] != "" { 206 stepParsed, err := strconv.ParseInt(physicalCPU[psrStepOffset], 10, 32) 207 if err != nil { 208 return nil, fmt.Errorf("cannot parse value %q for step as 32-bit integer: %s", physicalCPU[9], err) 209 } 210 step = int32(stepParsed) 211 } 212 213 if physicalCPU[psrClockOffset] != "" { 214 clockParsed, err := strconv.ParseInt(physicalCPU[psrClockOffset], 10, 64) 215 if err != nil { 216 return nil, fmt.Errorf("cannot parse value %q for clock as 32-bit integer: %s", physicalCPU[10], err) 217 } 218 clock = float64(clockParsed) 219 } 220 221 var err error 222 var numCores int64 223 var numHT int64 224 switch { 225 case physicalCPU[psrNumCoresOffset] != "": 226 numCores, err = strconv.ParseInt(physicalCPU[psrNumCoresOffset], 10, 32) 227 if err != nil { 228 return nil, fmt.Errorf("cannot parse value %q for core count as 32-bit integer: %s", physicalCPU[1], err) 229 } 230 231 for i := 0; i < int(numCores); i++ { 232 result = append(result, InfoStat{ 233 CPU: infoStatCount, 234 PhysicalID: strconv.Itoa(physicalIndex), 235 CoreID: strconv.Itoa(i), 236 Cores: 1, 237 VendorID: physicalCPU[psrVendorIDOffset], 238 ModelName: physicalCPU[psrModelNameOffset], 239 Family: physicalCPU[psrFamilyOffset], 240 Model: physicalCPU[psrModelOffset], 241 Stepping: step, 242 Mhz: clock, 243 }) 244 infoStatCount++ 245 } 246 case physicalCPU[psrNumCoresHTOffset] != "": 247 numCores, err = strconv.ParseInt(physicalCPU[psrNumCoresHTOffset], 10, 32) 248 if err != nil { 249 return nil, fmt.Errorf("cannot parse value %q for core count as 32-bit integer: %s", physicalCPU[3], err) 250 } 251 252 numHT, err = strconv.ParseInt(physicalCPU[psrNumHTOffset], 10, 32) 253 if err != nil { 254 return nil, fmt.Errorf("cannot parse value %q for hyperthread count as 32-bit integer: %s", physicalCPU[4], err) 255 } 256 257 for i := 0; i < int(numCores); i++ { 258 result = append(result, InfoStat{ 259 CPU: infoStatCount, 260 PhysicalID: strconv.Itoa(physicalIndex), 261 CoreID: strconv.Itoa(i), 262 Cores: int32(numHT) / int32(numCores), 263 VendorID: physicalCPU[psrVendorIDOffset], 264 ModelName: physicalCPU[psrModelNameOffset], 265 Family: physicalCPU[psrFamilyOffset], 266 Model: physicalCPU[psrModelOffset], 267 Stepping: step, 268 Mhz: clock, 269 }) 270 infoStatCount++ 271 } 272 default: 273 return nil, errors.New("values for cores with and without hyperthreading are both set") 274 } 275 } 276 return result, nil 277} 278 279func CountsWithContext(ctx context.Context, logical bool) (int, error) { 280 return runtime.NumCPU(), nil 281} 282