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) }