1 package cpu
2
3 import (
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
17 var ClocksPerSec = float64(128)
18
19 func init() {
20 clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
21
22 if err == nil {
23 ClocksPerSec = float64(clkTck)
24 }
25 }
26
27
28 func msum(x map[float64]float64) float64 {
29 total := 0.0
30 for _, y := range x {
31 total += y
32 }
33 return total
34 }
35
36 func Times(percpu bool) ([]TimesStat, error) {
37 return TimesWithContext(context.Background(), percpu)
38 }
39
40 func 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
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
88
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
120 func Info() ([]InfoStat, error) {
121 return InfoWithContext(context.Background())
122 }
123
124 func 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
163 var flagsMatch = regexp.MustCompile(`[\w\.]+`)
164
165 func parseISAInfo(cmdOutput string) ([]string, error) {
166 words := flagsMatch.FindAllString(cmdOutput, -1)
167
168
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
182 var 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
184 const (
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
196 func 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
279 func CountsWithContext(ctx context.Context, logical bool) (int, error) {
280 return runtime.NumCPU(), nil
281 }
282
View as plain text