-
Notifications
You must be signed in to change notification settings - Fork 0
/
cpu.go
355 lines (325 loc) · 7.77 KB
/
cpu.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
package cpu
import (
"bufio"
"container/heap"
"fmt"
"os"
"strconv"
"strings"
)
// For /proc/stat field order
type cputimeidx int
const (
cputNr cputimeidx = iota
cputUser
cputNice
cputSys
cputIdle
cputIowait
cputIrq
cputSoftirq
cputSteal
cputGuest
cputGuest_nice
)
type CpuTime struct {
nr string // cpu number
// times are in USER_HZ which is defined by sysconf(_SC_CLK_TCK)
user int // time in user mode
nice int
sys int
idle int
iowait int
irq int
softirq int
steal int
guest int
guest_nice int
}
// Used to store calculated fractional values
type CpuStat struct {
nr string // cpu number
user float32
nice float32
sys float32
idle float32
iowait float32
irq float32
softirq float32
steal float32
guest float32
guest_nice float32
}
// Lines in the /proc/cpuinfo file
type cpuinfoidx int
const (
cinf_processor = iota
cinf_vendor_id
cinf_cpu_family
cinf_model
cinf_model_name
cinf_stepping
cinf_microcode
cinf_cpu_mhz
cinf_cache_size
cinf_physical_id
cinf_siblings
cinf_core_id
cinf_cpu_cores
cinf_apicid
cinf_initial_apicid
cinf_fpu
cinf_fpu_exception
cinf_cpuid_level
cinf_wp
cinf_flags
cinf_bugs
cinf_bogomips
cinf_tlb_size
cinf_clflush_size
cinf_cache_alignment
cinf_address_sizes
cinf_power_management
cinfcinf_power_management
)
// Holds all cpu information including previous, current counts and estimated values for percent time spent in each.
type CpuInfo struct {
Cores int
Mhz float64
Siblings int // this is threads
Stats []*CpuTime
OldStats []*CpuTime
calcstats *calculatedstats
SummaryStats *CpuStat
}
// This will be a heap to quickly get cpu sorted by user time
type calculatedstats []*CpuStat
func (h calculatedstats) Len() int { return len(h) }
// TODO: use configurable method
func (h calculatedstats) Less(i, j int) bool {
return h[i].user > h[j].user
}
func (h calculatedstats) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *calculatedstats) Push(x any) { *h = append(*h, x.(*CpuStat)) }
func (h *calculatedstats) Pop() any {
old := *h
n := len(old)
if n == 0 {
return nil
}
x := old[n-1]
*h = old[0 : n-1]
return x
}
func (cpu *CpuTime) total() int {
return cpu.user + cpu.nice + cpu.sys + cpu.idle + cpu.iowait + cpu.irq + cpu.softirq + cpu.steal + cpu.guest + cpu.guest_nice
}
func (cpu *CpuInfo) estimate() {
if len(cpu.OldStats) == 0 {
return
}
prev := cpu.OldStats
cur := cpu.Stats
cpu.calcstats = new(calculatedstats)
heap.Init(cpu.calcstats)
for i := range cur {
c := new(CpuStat)
denom := float32(cur[i].total() - prev[i].total())
c.nr = cur[i].nr
c.user = float32((cur[i].user - prev[i].user)) / denom
c.sys = float32((cur[i].sys - prev[i].sys)) / denom
c.idle = float32((cur[i].idle - prev[i].idle)) / denom
c.iowait = float32((cur[i].iowait - prev[i].iowait)) / denom
c.irq = float32((cur[i].irq - prev[i].irq)) / denom
c.softirq = float32((cur[i].softirq - prev[i].softirq)) / denom
c.steal = float32((cur[i].steal - prev[i].steal)) / denom
c.guest = float32((cur[i].guest - prev[i].guest)) / denom
c.guest_nice = float32((cur[i].guest_nice - prev[i].guest_nice)) / denom
if i == 0 {
cpu.SummaryStats = c
} else {
heap.Push(cpu.calcstats, c)
}
}
}
// TODO - update this to string representation of CpuInfo
func (cpu *CpuInfo) InfoPrint(num_cpus int) string {
// clktck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
// if err != nil {
// log.Fatal("error getting system clock", err)
// }
num_cpus = min(cpu.Siblings, num_cpus)
if len(cpu.OldStats) == 0 {
// TODO: - be smarter than just skipping it the first time through?
return "-mt-"
}
var sb strings.Builder
sb.WriteString(fmt.Sprintf("vc:%d\tf: %.2f\n", cpu.Siblings, cpu.Mhz/1000))
sb.WriteString(fmt.Sprintf("CPU: usr:%.2f sys:%.2f: idle:%.2f\n",
cpu.SummaryStats.user, cpu.SummaryStats.sys, cpu.SummaryStats.idle))
for i := 0; i < num_cpus; i++ {
c := heap.Pop(cpu.calcstats).(*CpuStat)
sb.WriteString(
fmt.Sprintf(
"%s: usr:%.2f sys:%.2f: idle:%.2f iowait:%.2f irq:%.2f softirq:%.2f steal:%.2f guest:%.2f gnice:%.2f\n",
c.nr,
c.user,
c.sys,
c.idle,
c.iowait,
c.irq,
c.softirq,
c.steal,
c.guest,
c.guest_nice,
))
}
return sb.String()
}
func get_cpuinfo() (*CpuInfo, error) {
f, err := os.Open("/proc/cpuinfo")
if err != nil {
return nil, err
}
defer f.Close()
// f2, err := mmap.Open("/proc/cpuinfo")
// if err != nil {
// return nil, err
// }
// defer f2.Close()
scanner := bufio.NewScanner(f)
cpuinfo := new(CpuInfo)
var line cpuinfoidx
line = 0
for scanner.Scan() {
sp := strings.Split(scanner.Text(), ":")
for i := range sp {
sp[i] = strings.TrimSpace(sp[i])
}
switch line {
case cinf_siblings:
cpuinfo.Siblings, err = strconv.Atoi(sp[1])
if err != nil {
return nil, err
}
case cinf_cpu_mhz:
cpuinfo.Mhz, err = strconv.ParseFloat(sp[1], 64)
if err != nil {
return nil, err
}
case cinf_cpu_cores:
cpuinfo.Cores, err = strconv.Atoi(sp[1])
if err != nil {
return nil, err
}
}
line++
// unless there is a compelling reason, don't bother with rest of cpuinfo
if line > cinf_cpu_cores {
break
}
}
return cpuinfo, nil
}
// Get cpunums stats. The -1 value is special and gets the overall stats
// For every cpunum add an entry to the returned slice of cputimes
func getCpuTime(numcpu int) ([]*CpuTime, error) {
// The first line in stat is the overall CPU stats. We should make sure that's always in cpunums
pathCpuTime := "/proc/stat"
f, err := os.Open(pathCpuTime)
if err != nil {
return nil, err
}
defer f.Close()
var times []*CpuTime
scanner := bufio.NewScanner(f)
// For cputime struct only the first numcpu+1 lines have right content
line := 0
for scanner.Scan() {
tsrc := strings.Fields(scanner.Text())
times = append(times, new(CpuTime))
var i cputimeidx
// TODO: omg there has to be a better way
for i = cputNr; i < cputGuest_nice; i++ {
switch i {
case cputNr:
times[line].nr = tsrc[i]
case cputUser:
times[line].user, err = strconv.Atoi(tsrc[i])
if err != nil {
return nil, err
}
case cputNice:
times[line].nice, err = strconv.Atoi(tsrc[i])
if err != nil {
return nil, err
}
case cputSys:
times[line].sys, err = strconv.Atoi(tsrc[i])
if err != nil {
return nil, err
}
case cputIdle:
times[line].idle, err = strconv.Atoi(tsrc[i])
if err != nil {
return nil, err
}
case cputIowait:
times[line].iowait, err = strconv.Atoi(tsrc[i])
if err != nil {
return nil, err
}
case cputIrq:
times[line].irq, err = strconv.Atoi(tsrc[i])
if err != nil {
return nil, err
}
case cputSoftirq:
times[line].softirq, err = strconv.Atoi(tsrc[i])
if err != nil {
return nil, err
}
case cputSteal:
times[line].steal, err = strconv.Atoi(tsrc[i])
if err != nil {
return nil, err
}
case cputGuest:
times[line].guest, err = strconv.Atoi(tsrc[i])
if err != nil {
return nil, err
}
case cputGuest_nice:
times[line].guest_nice, err = strconv.Atoi(tsrc[i])
if err != nil {
return nil, err
}
}
}
line++
if line > numcpu {
break
}
}
return times, nil
}
func CPUStats(ci *CpuInfo) (*CpuInfo, error) {
// only update info if it's empty? is it common enough for num cpu etc to
// change for this to be checked everytime?
var err error
// this seems like a bad idea but the norm in golang?
// but also it's nonsesne for a real cpuinfo to have zero cores
if ci.Cores == 0 {
ci, err = get_cpuinfo()
if err != nil {
return nil, err
}
}
ci.OldStats = ci.Stats
ci.Stats, err = getCpuTime(ci.Siblings)
if err != nil {
return nil, err
}
ci.estimate()
return ci, nil
}