cpu_solaris.go 7.9 KB

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