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