caddy-log-exporter/vendor/github.com/lufia/plan9stats/cpu.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)
}