cpu.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. package stats
  2. import (
  3. "bufio"
  4. "bytes"
  5. "context"
  6. "errors"
  7. "fmt"
  8. "io/ioutil"
  9. "os"
  10. "path/filepath"
  11. "sort"
  12. "strconv"
  13. "strings"
  14. "time"
  15. )
  16. // CPUType represents /dev/cputype.
  17. type CPUType struct {
  18. Name string
  19. Clock int // clock rate in MHz
  20. }
  21. func ReadCPUType(ctx context.Context, opts ...Option) (*CPUType, error) {
  22. cfg := newConfig(opts...)
  23. var c CPUType
  24. if err := readCPUType(cfg.rootdir, &c); err != nil {
  25. return nil, err
  26. }
  27. return &c, nil
  28. }
  29. type SysStats struct {
  30. ID int
  31. NumCtxSwitch int64
  32. NumInterrupt int64
  33. NumSyscall int64
  34. NumFault int64
  35. NumTLBFault int64
  36. NumTLBPurge int64
  37. LoadAvg int64 // in units of milli-CPUs and is decayed over time
  38. Idle int // percentage
  39. Interrupt int // percentage
  40. }
  41. // ReadSysStats reads system statistics from /dev/sysstat.
  42. func ReadSysStats(ctx context.Context, opts ...Option) ([]*SysStats, error) {
  43. cfg := newConfig(opts...)
  44. file := filepath.Join(cfg.rootdir, "/dev/sysstat")
  45. f, err := os.Open(file)
  46. if err != nil {
  47. return nil, err
  48. }
  49. defer f.Close()
  50. scanner := bufio.NewScanner(f)
  51. var stats []*SysStats
  52. for scanner.Scan() {
  53. a := strings.Fields(scanner.Text())
  54. if len(a) != 10 {
  55. continue
  56. }
  57. var (
  58. p intParser
  59. stat SysStats
  60. )
  61. stat.ID = p.ParseInt(a[0], 10)
  62. stat.NumCtxSwitch = p.ParseInt64(a[1], 10)
  63. stat.NumInterrupt = p.ParseInt64(a[2], 10)
  64. stat.NumSyscall = p.ParseInt64(a[3], 10)
  65. stat.NumFault = p.ParseInt64(a[4], 10)
  66. stat.NumTLBFault = p.ParseInt64(a[5], 10)
  67. stat.NumTLBPurge = p.ParseInt64(a[6], 10)
  68. stat.LoadAvg = p.ParseInt64(a[7], 10)
  69. stat.Idle = p.ParseInt(a[8], 10)
  70. stat.Interrupt = p.ParseInt(a[9], 10)
  71. if err := p.Err(); err != nil {
  72. return nil, err
  73. }
  74. stats = append(stats, &stat)
  75. }
  76. if err := scanner.Err(); err != nil {
  77. return nil, err
  78. }
  79. return stats, nil
  80. }
  81. func readCPUType(rootdir string, c *CPUType) error {
  82. file := filepath.Join(rootdir, "/dev/cputype")
  83. b, err := ioutil.ReadFile(file)
  84. if err != nil {
  85. return err
  86. }
  87. b = bytes.TrimSpace(b)
  88. i := bytes.LastIndexByte(b, ' ')
  89. if i < 0 {
  90. return fmt.Errorf("%s: invalid format", file)
  91. }
  92. clock, err := strconv.Atoi(string(b[i+1:]))
  93. if err != nil {
  94. return err
  95. }
  96. c.Name = string(b[:i])
  97. c.Clock = clock
  98. return nil
  99. }
  100. // Time represents /dev/time.
  101. type Time struct {
  102. Unix time.Duration
  103. UnixNano time.Duration
  104. Ticks int64 // clock ticks
  105. Freq int64 //cloc frequency
  106. }
  107. // Uptime returns uptime.
  108. func (t *Time) Uptime() time.Duration {
  109. v := float64(t.Ticks) / float64(t.Freq)
  110. return time.Duration(v*1000_000_000) * time.Nanosecond
  111. }
  112. func ReadTime(ctx context.Context, opts ...Option) (*Time, error) {
  113. cfg := newConfig(opts...)
  114. file := filepath.Join(cfg.rootdir, "/dev/time")
  115. var t Time
  116. if err := readTime(file, &t); err != nil {
  117. return nil, err
  118. }
  119. return &t, nil
  120. }
  121. // ProcStatus represents a /proc/n/status.
  122. type ProcStatus struct {
  123. Name string
  124. User string
  125. State string
  126. Times CPUTime
  127. MemUsed int64 // in units of 1024 bytes
  128. BasePriority uint32 // 0(low) to 19(high)
  129. Priority uint32 // 0(low) to 19(high)
  130. }
  131. // CPUTime represents /dev/cputime or a part of /proc/n/status.
  132. type CPUTime struct {
  133. User time.Duration // the time in user mode (millisecconds)
  134. Sys time.Duration
  135. Real time.Duration
  136. ChildUser time.Duration // exited children and descendants time in user mode
  137. ChildSys time.Duration
  138. ChildReal time.Duration
  139. }
  140. // CPUStats emulates Linux's /proc/stat.
  141. type CPUStats struct {
  142. User time.Duration
  143. Sys time.Duration
  144. Idle time.Duration
  145. }
  146. func ReadCPUStats(ctx context.Context, opts ...Option) (*CPUStats, error) {
  147. cfg := newConfig(opts...)
  148. a, err := ReadSysStats(ctx, opts...)
  149. if err != nil {
  150. return nil, err
  151. }
  152. dir := filepath.Join(cfg.rootdir, "/proc")
  153. d, err := os.Open(dir)
  154. if err != nil {
  155. return nil, err
  156. }
  157. defer d.Close()
  158. names, err := d.Readdirnames(0)
  159. if err != nil {
  160. return nil, err
  161. }
  162. var up uint32parser
  163. pids := make([]uint32, len(names))
  164. for i, s := range names {
  165. pids[i] = up.Parse(s)
  166. }
  167. if up.err != nil {
  168. return nil, err
  169. }
  170. sort.Slice(pids, func(i, j int) bool {
  171. return pids[i] < pids[j]
  172. })
  173. var stat CPUStats
  174. for _, pid := range pids {
  175. s := strconv.FormatUint(uint64(pid), 10)
  176. file := filepath.Join(dir, s, "status")
  177. var p ProcStatus
  178. if err := readProcStatus(file, &p); err != nil {
  179. return nil, err
  180. }
  181. stat.User += p.Times.User
  182. stat.Sys += p.Times.Sys
  183. }
  184. var t Time
  185. file := filepath.Join(cfg.rootdir, "/dev/time")
  186. if err := readTime(file, &t); err != nil {
  187. return nil, err
  188. }
  189. // In multi-processor host, Idle should multiple by number of cores.
  190. u := t.Uptime() * time.Duration(len(a))
  191. stat.Idle = u - stat.User - stat.Sys
  192. return &stat, nil
  193. }
  194. func readProcStatus(file string, p *ProcStatus) error {
  195. b, err := ioutil.ReadFile(file)
  196. if err != nil {
  197. if os.IsNotExist(err) {
  198. return nil
  199. }
  200. return err
  201. }
  202. fields := strings.Fields(string(b))
  203. if len(fields) != 12 {
  204. return errors.New("invalid format")
  205. }
  206. p.Name = string(fields[0])
  207. p.User = string(fields[1])
  208. p.State = string(fields[2])
  209. var up uint32parser
  210. p.Times.User = time.Duration(up.Parse(fields[3])) * time.Millisecond
  211. p.Times.Sys = time.Duration(up.Parse(fields[4])) * time.Millisecond
  212. p.Times.Real = time.Duration(up.Parse(fields[5])) * time.Millisecond
  213. p.Times.ChildUser = time.Duration(up.Parse(fields[6])) * time.Millisecond
  214. p.Times.ChildSys = time.Duration(up.Parse(fields[7])) * time.Millisecond
  215. p.Times.ChildReal = time.Duration(up.Parse(fields[8])) * time.Millisecond
  216. p.MemUsed, err = strconv.ParseInt(fields[9], 10, 64)
  217. if err != nil {
  218. return err
  219. }
  220. p.BasePriority = up.Parse(fields[10])
  221. p.Priority = up.Parse(fields[11])
  222. return up.err
  223. }
  224. func readTime(file string, t *Time) error {
  225. b, err := ioutil.ReadFile(file)
  226. if err != nil {
  227. return err
  228. }
  229. fields := strings.Fields(string(b))
  230. if len(fields) != 4 {
  231. return errors.New("invalid format")
  232. }
  233. n, err := strconv.ParseInt(fields[0], 10, 32)
  234. if err != nil {
  235. return err
  236. }
  237. t.Unix = time.Duration(n) * time.Second
  238. v, err := strconv.ParseInt(fields[1], 10, 64)
  239. if err != nil {
  240. return err
  241. }
  242. t.UnixNano = time.Duration(v) * time.Nanosecond
  243. t.Ticks, err = strconv.ParseInt(fields[2], 10, 64)
  244. if err != nil {
  245. return err
  246. }
  247. t.Freq, err = strconv.ParseInt(fields[3], 10, 64)
  248. if err != nil {
  249. return err
  250. }
  251. return nil
  252. }
  253. type uint32parser struct {
  254. err error
  255. }
  256. func (p *uint32parser) Parse(s string) uint32 {
  257. if p.err != nil {
  258. return 0
  259. }
  260. n, err := strconv.ParseUint(s, 10, 32)
  261. if err != nil {
  262. p.err = err
  263. return 0
  264. }
  265. return uint32(n)
  266. }