| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- package stats
- import (
- "bufio"
- "bytes"
- "context"
- "errors"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "sort"
- "strconv"
- "strings"
- "time"
- )
- // CPUType represents /dev/cputype.
- type CPUType struct {
- Name string
- Clock int // clock rate in MHz
- }
- func ReadCPUType(ctx context.Context, opts ...Option) (*CPUType, error) {
- cfg := newConfig(opts...)
- var c CPUType
- if err := readCPUType(cfg.rootdir, &c); err != nil {
- return nil, err
- }
- return &c, nil
- }
- type SysStats struct {
- ID int
- NumCtxSwitch int64
- NumInterrupt int64
- NumSyscall int64
- NumFault int64
- NumTLBFault int64
- NumTLBPurge int64
- LoadAvg int64 // in units of milli-CPUs and is decayed over time
- Idle int // percentage
- Interrupt int // percentage
- }
- // ReadSysStats reads system statistics from /dev/sysstat.
- func ReadSysStats(ctx context.Context, opts ...Option) ([]*SysStats, error) {
- cfg := newConfig(opts...)
- file := filepath.Join(cfg.rootdir, "/dev/sysstat")
- f, err := os.Open(file)
- if err != nil {
- return nil, err
- }
- defer f.Close()
- scanner := bufio.NewScanner(f)
- var stats []*SysStats
- for scanner.Scan() {
- a := strings.Fields(scanner.Text())
- if len(a) != 10 {
- continue
- }
- var (
- p intParser
- stat SysStats
- )
- stat.ID = p.ParseInt(a[0], 10)
- stat.NumCtxSwitch = p.ParseInt64(a[1], 10)
- stat.NumInterrupt = p.ParseInt64(a[2], 10)
- stat.NumSyscall = p.ParseInt64(a[3], 10)
- stat.NumFault = p.ParseInt64(a[4], 10)
- stat.NumTLBFault = p.ParseInt64(a[5], 10)
- stat.NumTLBPurge = p.ParseInt64(a[6], 10)
- stat.LoadAvg = p.ParseInt64(a[7], 10)
- stat.Idle = p.ParseInt(a[8], 10)
- stat.Interrupt = p.ParseInt(a[9], 10)
- if err := p.Err(); err != nil {
- return nil, err
- }
- stats = append(stats, &stat)
- }
- if err := scanner.Err(); err != nil {
- return nil, err
- }
- return stats, nil
- }
- func readCPUType(rootdir string, c *CPUType) error {
- file := filepath.Join(rootdir, "/dev/cputype")
- b, err := ioutil.ReadFile(file)
- if err != nil {
- return err
- }
- b = bytes.TrimSpace(b)
- i := bytes.LastIndexByte(b, ' ')
- if i < 0 {
- return fmt.Errorf("%s: invalid format", file)
- }
- clock, err := strconv.Atoi(string(b[i+1:]))
- if err != nil {
- return err
- }
- c.Name = string(b[:i])
- c.Clock = clock
- return nil
- }
- // Time represents /dev/time.
- type Time struct {
- Unix time.Duration
- UnixNano time.Duration
- Ticks int64 // clock ticks
- Freq int64 //cloc frequency
- }
- // Uptime returns uptime.
- func (t *Time) Uptime() time.Duration {
- v := float64(t.Ticks) / float64(t.Freq)
- return time.Duration(v*1000_000_000) * time.Nanosecond
- }
- func ReadTime(ctx context.Context, opts ...Option) (*Time, error) {
- cfg := newConfig(opts...)
- file := filepath.Join(cfg.rootdir, "/dev/time")
- var t Time
- if err := readTime(file, &t); err != nil {
- return nil, err
- }
- return &t, nil
- }
- // ProcStatus represents a /proc/n/status.
- type ProcStatus struct {
- Name string
- User string
- State string
- Times CPUTime
- MemUsed int64 // in units of 1024 bytes
- BasePriority uint32 // 0(low) to 19(high)
- Priority uint32 // 0(low) to 19(high)
- }
- // CPUTime represents /dev/cputime or a part of /proc/n/status.
- type CPUTime struct {
- User time.Duration // the time in user mode (millisecconds)
- Sys time.Duration
- Real time.Duration
- ChildUser time.Duration // exited children and descendants time in user mode
- ChildSys time.Duration
- ChildReal time.Duration
- }
- // CPUStats emulates Linux's /proc/stat.
- type CPUStats struct {
- User time.Duration
- Sys time.Duration
- Idle time.Duration
- }
- func ReadCPUStats(ctx context.Context, opts ...Option) (*CPUStats, error) {
- cfg := newConfig(opts...)
- a, err := ReadSysStats(ctx, opts...)
- if err != nil {
- return nil, err
- }
- dir := filepath.Join(cfg.rootdir, "/proc")
- d, err := os.Open(dir)
- if err != nil {
- return nil, err
- }
- defer d.Close()
- names, err := d.Readdirnames(0)
- if err != nil {
- return nil, err
- }
- var up uint32parser
- pids := make([]uint32, len(names))
- for i, s := range names {
- pids[i] = up.Parse(s)
- }
- if up.err != nil {
- return nil, err
- }
- sort.Slice(pids, func(i, j int) bool {
- return pids[i] < pids[j]
- })
- var stat CPUStats
- for _, pid := range pids {
- s := strconv.FormatUint(uint64(pid), 10)
- file := filepath.Join(dir, s, "status")
- var p ProcStatus
- if err := readProcStatus(file, &p); err != nil {
- return nil, err
- }
- stat.User += p.Times.User
- stat.Sys += p.Times.Sys
- }
- var t Time
- file := filepath.Join(cfg.rootdir, "/dev/time")
- if err := readTime(file, &t); err != nil {
- return nil, err
- }
- // In multi-processor host, Idle should multiple by number of cores.
- u := t.Uptime() * time.Duration(len(a))
- stat.Idle = u - stat.User - stat.Sys
- return &stat, nil
- }
- func readProcStatus(file string, p *ProcStatus) error {
- b, err := ioutil.ReadFile(file)
- if err != nil {
- if os.IsNotExist(err) {
- return nil
- }
- return err
- }
- fields := strings.Fields(string(b))
- if len(fields) != 12 {
- return errors.New("invalid format")
- }
- p.Name = string(fields[0])
- p.User = string(fields[1])
- p.State = string(fields[2])
- var up uint32parser
- p.Times.User = time.Duration(up.Parse(fields[3])) * time.Millisecond
- p.Times.Sys = time.Duration(up.Parse(fields[4])) * time.Millisecond
- p.Times.Real = time.Duration(up.Parse(fields[5])) * time.Millisecond
- p.Times.ChildUser = time.Duration(up.Parse(fields[6])) * time.Millisecond
- p.Times.ChildSys = time.Duration(up.Parse(fields[7])) * time.Millisecond
- p.Times.ChildReal = time.Duration(up.Parse(fields[8])) * time.Millisecond
- p.MemUsed, err = strconv.ParseInt(fields[9], 10, 64)
- if err != nil {
- return err
- }
- p.BasePriority = up.Parse(fields[10])
- p.Priority = up.Parse(fields[11])
- return up.err
- }
- func readTime(file string, t *Time) error {
- b, err := ioutil.ReadFile(file)
- if err != nil {
- return err
- }
- fields := strings.Fields(string(b))
- if len(fields) != 4 {
- return errors.New("invalid format")
- }
- n, err := strconv.ParseInt(fields[0], 10, 32)
- if err != nil {
- return err
- }
- t.Unix = time.Duration(n) * time.Second
- v, err := strconv.ParseInt(fields[1], 10, 64)
- if err != nil {
- return err
- }
- t.UnixNano = time.Duration(v) * time.Nanosecond
- t.Ticks, err = strconv.ParseInt(fields[2], 10, 64)
- if err != nil {
- return err
- }
- t.Freq, err = strconv.ParseInt(fields[3], 10, 64)
- if err != nil {
- return err
- }
- return nil
- }
- type uint32parser struct {
- err error
- }
- func (p *uint32parser) Parse(s string) uint32 {
- if p.err != nil {
- return 0
- }
- n, err := strconv.ParseUint(s, 10, 32)
- if err != nil {
- p.err = err
- return 0
- }
- return uint32(n)
- }
|