Initial QSfera import
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2019, KADOTA, Kyohei
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
# plan9stats
|
||||
A module for retrieving statistics of Plan 9
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
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)
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package stats provides statistic utilities for Plan 9.
|
||||
package stats
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
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)
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type intParser struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (p *intParser) ParseInt(s string, base int) int {
|
||||
if p.err != nil {
|
||||
return 0
|
||||
}
|
||||
var n int64
|
||||
n, p.err = strconv.ParseInt(s, base, 0)
|
||||
return int(n)
|
||||
}
|
||||
|
||||
func (p *intParser) ParseInt64(s string, base int) int64 {
|
||||
if p.err != nil {
|
||||
return 0
|
||||
}
|
||||
var n int64
|
||||
n, p.err = strconv.ParseInt(s, base, 64)
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *intParser) Err() error {
|
||||
return p.err
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package stats
|
||||
|
||||
type Config struct {
|
||||
rootdir string
|
||||
}
|
||||
|
||||
type Option func(*Config)
|
||||
|
||||
func newConfig(opts ...Option) *Config {
|
||||
var cfg Config
|
||||
for _, opt := range opts {
|
||||
opt(&cfg)
|
||||
}
|
||||
return &cfg
|
||||
}
|
||||
|
||||
func WithRootDir(dir string) Option {
|
||||
return func(cfg *Config) {
|
||||
cfg.rootdir = dir
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type InterfaceStats struct {
|
||||
PacketsReceived int64 // in packets
|
||||
Link int // link status
|
||||
PacketsSent int64 // out packets
|
||||
NumCRCErr int // input CRC errors
|
||||
NumOverflows int // packet overflows
|
||||
NumSoftOverflows int // software overflow
|
||||
NumFramingErr int // framing errors
|
||||
NumBufferingErr int // buffering errors
|
||||
NumOutputErr int // output errors
|
||||
Promiscuous int // number of promiscuous opens
|
||||
Mbps int // megabits per sec
|
||||
Addr string
|
||||
}
|
||||
|
||||
func ReadInterfaceStats(ctx context.Context, opts ...Option) (*InterfaceStats, error) {
|
||||
cfg := newConfig(opts...)
|
||||
file := filepath.Join(cfg.rootdir, "stats")
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var stats InterfaceStats
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
s := strings.TrimSpace(scanner.Text())
|
||||
a := strings.SplitN(s, ":", 2)
|
||||
if len(a) != 2 {
|
||||
continue
|
||||
}
|
||||
var p intParser
|
||||
v := strings.TrimSpace(a[1])
|
||||
switch a[0] {
|
||||
case "in":
|
||||
stats.PacketsReceived = p.ParseInt64(v, 10)
|
||||
case "link":
|
||||
stats.Link = p.ParseInt(v, 10)
|
||||
case "out":
|
||||
stats.PacketsSent = p.ParseInt64(v, 10)
|
||||
case "crc":
|
||||
stats.NumCRCErr = p.ParseInt(v, 10)
|
||||
case "overflows":
|
||||
stats.NumOverflows = p.ParseInt(v, 10)
|
||||
case "soft overflows":
|
||||
stats.NumSoftOverflows = p.ParseInt(v, 10)
|
||||
case "framing errs":
|
||||
stats.NumFramingErr = p.ParseInt(v, 10)
|
||||
case "buffer errs":
|
||||
stats.NumBufferingErr = p.ParseInt(v, 10)
|
||||
case "output errs":
|
||||
stats.NumOutputErr = p.ParseInt(v, 10)
|
||||
case "prom":
|
||||
stats.Promiscuous = p.ParseInt(v, 10)
|
||||
case "mbps":
|
||||
stats.Mbps = p.ParseInt(v, 10)
|
||||
case "addr":
|
||||
stats.Addr = v
|
||||
}
|
||||
if err := p.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
type TCPStats struct {
|
||||
MaxConn int
|
||||
MaxSegment int
|
||||
ActiveOpens int
|
||||
PassiveOpens int
|
||||
EstablishedResets int
|
||||
CurrentEstablished int
|
||||
}
|
||||
Reference in New Issue
Block a user