289 lines
6.5 KiB
Go
289 lines
6.5 KiB
Go
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)
|
|
}
|