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