1package cpu
2
3import (
4	"context"
5	"errors"
6	"fmt"
7	"os/exec"
8	"regexp"
9	"sort"
10	"strconv"
11	"strings"
12
13	"github.com/shirou/gopsutil/internal/common"
14)
15
16var ClocksPerSec = float64(128)
17
18func init() {
19	getconf, err := exec.LookPath("/usr/bin/getconf")
20	if err != nil {
21		return
22	}
23	out, err := invoke.Command(getconf, "CLK_TCK")
24	// ignore errors
25	if err == nil {
26		i, err := strconv.ParseFloat(strings.TrimSpace(string(out)), 64)
27		if err == nil {
28			ClocksPerSec = float64(i)
29		}
30	}
31}
32
33func Times(percpu bool) ([]TimesStat, error) {
34	return TimesWithContext(context.Background(), percpu)
35}
36
37func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
38	return []TimesStat{}, common.ErrNotImplementedError
39}
40
41func Info() ([]InfoStat, error) {
42	return InfoWithContext(context.Background())
43}
44
45func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
46	psrInfo, err := exec.LookPath("/usr/sbin/psrinfo")
47	if err != nil {
48		return nil, fmt.Errorf("cannot find psrinfo: %s", err)
49	}
50	psrInfoOut, err := invoke.CommandWithContext(ctx, psrInfo, "-p", "-v")
51	if err != nil {
52		return nil, fmt.Errorf("cannot execute psrinfo: %s", err)
53	}
54
55	isaInfo, err := exec.LookPath("/usr/bin/isainfo")
56	if err != nil {
57		return nil, fmt.Errorf("cannot find isainfo: %s", err)
58	}
59	isaInfoOut, err := invoke.CommandWithContext(ctx, isaInfo, "-b", "-v")
60	if err != nil {
61		return nil, fmt.Errorf("cannot execute isainfo: %s", err)
62	}
63
64	procs, err := parseProcessorInfo(string(psrInfoOut))
65	if err != nil {
66		return nil, fmt.Errorf("error parsing psrinfo output: %s", err)
67	}
68
69	flags, err := parseISAInfo(string(isaInfoOut))
70	if err != nil {
71		return nil, fmt.Errorf("error parsing isainfo output: %s", err)
72	}
73
74	result := make([]InfoStat, 0, len(flags))
75	for _, proc := range procs {
76		procWithFlags := proc
77		procWithFlags.Flags = flags
78		result = append(result, procWithFlags)
79	}
80
81	return result, nil
82}
83
84var flagsMatch = regexp.MustCompile(`[\w\.]+`)
85
86func parseISAInfo(cmdOutput string) ([]string, error) {
87	words := flagsMatch.FindAllString(cmdOutput, -1)
88
89	// Sanity check the output
90	if len(words) < 4 || words[1] != "bit" || words[3] != "applications" {
91		return nil, errors.New("attempted to parse invalid isainfo output")
92	}
93
94	flags := make([]string, len(words)-4)
95	for i, val := range words[4:] {
96		flags[i] = val
97	}
98	sort.Strings(flags)
99
100	return flags, nil
101}
102
103var psrInfoMatch = regexp.MustCompile(`The physical processor has (?:([\d]+) virtual processor \(([\d]+)\)|([\d]+) cores and ([\d]+) virtual processors[^\n]+)\n(?:\s+ The core has.+\n)*\s+.+ \((\w+) ([\S]+) family (.+) model (.+) step (.+) clock (.+) MHz\)\n[\s]*(.*)`)
104
105const (
106	psrNumCoresOffset   = 1
107	psrNumCoresHTOffset = 3
108	psrNumHTOffset      = 4
109	psrVendorIDOffset   = 5
110	psrFamilyOffset     = 7
111	psrModelOffset      = 8
112	psrStepOffset       = 9
113	psrClockOffset      = 10
114	psrModelNameOffset  = 11
115)
116
117func parseProcessorInfo(cmdOutput string) ([]InfoStat, error) {
118	matches := psrInfoMatch.FindAllStringSubmatch(cmdOutput, -1)
119
120	var infoStatCount int32
121	result := make([]InfoStat, 0, len(matches))
122	for physicalIndex, physicalCPU := range matches {
123		var step int32
124		var clock float64
125
126		if physicalCPU[psrStepOffset] != "" {
127			stepParsed, err := strconv.ParseInt(physicalCPU[psrStepOffset], 10, 32)
128			if err != nil {
129				return nil, fmt.Errorf("cannot parse value %q for step as 32-bit integer: %s", physicalCPU[9], err)
130			}
131			step = int32(stepParsed)
132		}
133
134		if physicalCPU[psrClockOffset] != "" {
135			clockParsed, err := strconv.ParseInt(physicalCPU[psrClockOffset], 10, 64)
136			if err != nil {
137				return nil, fmt.Errorf("cannot parse value %q for clock as 32-bit integer: %s", physicalCPU[10], err)
138			}
139			clock = float64(clockParsed)
140		}
141
142		var err error
143		var numCores int64
144		var numHT int64
145		switch {
146		case physicalCPU[psrNumCoresOffset] != "":
147			numCores, err = strconv.ParseInt(physicalCPU[psrNumCoresOffset], 10, 32)
148			if err != nil {
149				return nil, fmt.Errorf("cannot parse value %q for core count as 32-bit integer: %s", physicalCPU[1], err)
150			}
151
152			for i := 0; i < int(numCores); i++ {
153				result = append(result, InfoStat{
154					CPU:        infoStatCount,
155					PhysicalID: strconv.Itoa(physicalIndex),
156					CoreID:     strconv.Itoa(i),
157					Cores:      1,
158					VendorID:   physicalCPU[psrVendorIDOffset],
159					ModelName:  physicalCPU[psrModelNameOffset],
160					Family:     physicalCPU[psrFamilyOffset],
161					Model:      physicalCPU[psrModelOffset],
162					Stepping:   step,
163					Mhz:        clock,
164				})
165				infoStatCount++
166			}
167		case physicalCPU[psrNumCoresHTOffset] != "":
168			numCores, err = strconv.ParseInt(physicalCPU[psrNumCoresHTOffset], 10, 32)
169			if err != nil {
170				return nil, fmt.Errorf("cannot parse value %q for core count as 32-bit integer: %s", physicalCPU[3], err)
171			}
172
173			numHT, err = strconv.ParseInt(physicalCPU[psrNumHTOffset], 10, 32)
174			if err != nil {
175				return nil, fmt.Errorf("cannot parse value %q for hyperthread count as 32-bit integer: %s", physicalCPU[4], err)
176			}
177
178			for i := 0; i < int(numCores); i++ {
179				result = append(result, InfoStat{
180					CPU:        infoStatCount,
181					PhysicalID: strconv.Itoa(physicalIndex),
182					CoreID:     strconv.Itoa(i),
183					Cores:      int32(numHT) / int32(numCores),
184					VendorID:   physicalCPU[psrVendorIDOffset],
185					ModelName:  physicalCPU[psrModelNameOffset],
186					Family:     physicalCPU[psrFamilyOffset],
187					Model:      physicalCPU[psrModelOffset],
188					Stepping:   step,
189					Mhz:        clock,
190				})
191				infoStatCount++
192			}
193		default:
194			return nil, errors.New("values for cores with and without hyperthreading are both set")
195		}
196	}
197	return result, nil
198}
199