304 lines
6.1 KiB
Go
304 lines
6.1 KiB
Go
package stats
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
delim = []byte{' '}
|
|
)
|
|
|
|
// Host represents host status.
|
|
type Host struct {
|
|
Sysname string
|
|
Storages []*Storage
|
|
Interfaces []*Interface
|
|
}
|
|
|
|
// MemStats represents the memory statistics.
|
|
type MemStats struct {
|
|
Total int64 // total memory in byte
|
|
PageSize int64 // a page size in byte
|
|
KernelPages int64
|
|
UserPages Gauge
|
|
SwapPages Gauge
|
|
|
|
Malloced Gauge // kernel malloced data in byte
|
|
Graphics Gauge // kernel graphics data in byte
|
|
}
|
|
|
|
// Gauge is used/available gauge.
|
|
type Gauge struct {
|
|
Used int64
|
|
Avail int64
|
|
}
|
|
|
|
func (g Gauge) Free() int64 {
|
|
return g.Avail - g.Used
|
|
}
|
|
|
|
// ReadMemStats reads memory statistics from /dev/swap.
|
|
func ReadMemStats(ctx context.Context, opts ...Option) (*MemStats, error) {
|
|
cfg := newConfig(opts...)
|
|
swap := filepath.Join(cfg.rootdir, "/dev/swap")
|
|
f, err := os.Open(swap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
var stat MemStats
|
|
m := map[string]interface{}{
|
|
"memory": &stat.Total,
|
|
"pagesize": &stat.PageSize,
|
|
"kernel": &stat.KernelPages,
|
|
"user": &stat.UserPages,
|
|
"swap": &stat.SwapPages,
|
|
"kernel malloc": &stat.Malloced,
|
|
"kernel draw": &stat.Graphics,
|
|
}
|
|
scanner := bufio.NewScanner(f)
|
|
for scanner.Scan() {
|
|
fields := bytes.SplitN(scanner.Bytes(), delim, 2)
|
|
if len(fields) < 2 {
|
|
continue
|
|
}
|
|
switch key := string(fields[1]); key {
|
|
case "memory", "pagesize", "kernel":
|
|
v := m[key].(*int64)
|
|
n, err := strconv.ParseInt(string(fields[0]), 10, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
*v = n
|
|
case "user", "swap", "kernel malloc", "kernel draw":
|
|
v := m[key].(*Gauge)
|
|
if err := parseGauge(string(fields[0]), v); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return &stat, nil
|
|
}
|
|
|
|
func parseGauge(s string, r *Gauge) error {
|
|
a := strings.SplitN(s, "/", 2)
|
|
if len(a) != 2 {
|
|
return fmt.Errorf("can't parse ratio: %s", s)
|
|
}
|
|
var p intParser
|
|
u := p.ParseInt64(a[0], 10)
|
|
n := p.ParseInt64(a[1], 10)
|
|
if err := p.Err(); err != nil {
|
|
return err
|
|
}
|
|
r.Used = u
|
|
r.Avail = n
|
|
return nil
|
|
}
|
|
|
|
type Storage struct {
|
|
Name string
|
|
Model string
|
|
Capacity int64
|
|
}
|
|
|
|
type Interface struct {
|
|
Name string
|
|
Addr string
|
|
}
|
|
|
|
const (
|
|
numEther = 8 // see ether(3)
|
|
numIpifc = 16 // see ip(3)
|
|
)
|
|
|
|
// ReadInterfaces reads network interfaces from etherN.
|
|
func ReadInterfaces(ctx context.Context, opts ...Option) ([]*Interface, error) {
|
|
cfg := newConfig(opts...)
|
|
var a []*Interface
|
|
for i := 0; i < numEther; i++ {
|
|
p, err := readInterface(cfg.rootdir, i)
|
|
if os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
a = append(a, p)
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
func readInterface(netroot string, i int) (*Interface, error) {
|
|
ether := fmt.Sprintf("ether%d", i)
|
|
dir := filepath.Join(netroot, ether)
|
|
info, err := os.Stat(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !info.IsDir() {
|
|
return nil, fmt.Errorf("%s: is not directory", dir)
|
|
}
|
|
|
|
addr, err := ioutil.ReadFile(filepath.Join(dir, "addr"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Interface{
|
|
Name: ether,
|
|
Addr: string(addr),
|
|
}, nil
|
|
}
|
|
|
|
var (
|
|
netdirs = []string{"/net", "/net.alt"}
|
|
)
|
|
|
|
// ReadHost reads host status.
|
|
func ReadHost(ctx context.Context, opts ...Option) (*Host, error) {
|
|
cfg := newConfig(opts...)
|
|
var h Host
|
|
name, err := readSysname(cfg.rootdir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
h.Sysname = name
|
|
|
|
a, err := readStorages(cfg.rootdir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
h.Storages = a
|
|
|
|
for _, s := range netdirs {
|
|
netroot := filepath.Join(cfg.rootdir, s)
|
|
ifaces, err := ReadInterfaces(ctx, WithRootDir(netroot))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
h.Interfaces = append(h.Interfaces, ifaces...)
|
|
}
|
|
return &h, nil
|
|
}
|
|
|
|
func readSysname(rootdir string) (string, error) {
|
|
file := filepath.Join(rootdir, "/dev/sysname")
|
|
b, err := ioutil.ReadFile(file)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(bytes.TrimSpace(b)), nil
|
|
}
|
|
|
|
func readStorages(rootdir string) ([]*Storage, error) {
|
|
sdctl := filepath.Join(rootdir, "/dev/sdctl")
|
|
f, err := os.Open(sdctl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
var a []*Storage
|
|
scanner := bufio.NewScanner(f)
|
|
for scanner.Scan() {
|
|
fields := bytes.Split(scanner.Bytes(), delim)
|
|
if len(fields) == 0 {
|
|
continue
|
|
}
|
|
exp := string(fields[0]) + "*"
|
|
if !strings.HasPrefix(exp, "sd") {
|
|
continue
|
|
}
|
|
dir := filepath.Join(rootdir, "/dev", exp)
|
|
m, err := filepath.Glob(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, dir := range m {
|
|
s, err := readStorage(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
a = append(a, s)
|
|
}
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
func readStorage(dir string) (*Storage, error) {
|
|
ctl := filepath.Join(dir, "ctl")
|
|
f, err := os.Open(ctl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
var s Storage
|
|
s.Name = filepath.Base(dir)
|
|
scanner := bufio.NewScanner(f)
|
|
for scanner.Scan() {
|
|
line := scanner.Bytes()
|
|
switch {
|
|
case bytes.HasPrefix(line, []byte("inquiry")):
|
|
s.Model = string(bytes.TrimSpace(line[7:]))
|
|
case bytes.HasPrefix(line, []byte("geometry")):
|
|
fields := bytes.Split(line, delim)
|
|
if len(fields) < 3 {
|
|
continue
|
|
}
|
|
var p intParser
|
|
sec := p.ParseInt64(string(fields[1]), 10)
|
|
size := p.ParseInt64(string(fields[2]), 10)
|
|
if err := p.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
s.Capacity = sec * size
|
|
}
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return &s, nil
|
|
}
|
|
|
|
type IPStats struct {
|
|
ID int // number of interface in ipifc dir
|
|
Device string // associated physical device
|
|
MTU int // max transfer unit
|
|
Sendra6 uint8 // on == send router adv
|
|
Recvra6 uint8 // on == recv router adv
|
|
|
|
Pktin int64 // packets read
|
|
Pktout int64 // packets written
|
|
Errin int64 // read errors
|
|
Errout int64 // write errors
|
|
}
|
|
|
|
type Iplifc struct {
|
|
IP net.IP
|
|
Mask net.IPMask
|
|
Net net.IP // ip & mask
|
|
PerfLifetime int64 // preferred lifetime
|
|
ValidLifetime int64 // valid lifetime
|
|
}
|
|
|
|
type Ipv6rp struct {
|
|
// TODO(lufia): see ip(2)
|
|
}
|