1package stats
2
3import (
4	"bufio"
5	"bytes"
6	"context"
7	"fmt"
8	"io/ioutil"
9	"net"
10	"os"
11	"path/filepath"
12	"strconv"
13	"strings"
14)
15
16var (
17	delim = []byte{' '}
18)
19
20// Host represents host status.
21type Host struct {
22	Sysname    string
23	Storages   []*Storage
24	Interfaces []*Interface
25}
26
27// MemStats represents the memory statistics.
28type MemStats struct {
29	Total       int64 // total memory in byte
30	PageSize    int64 // a page size in byte
31	KernelPages int64
32	UserPages   Gauge
33	SwapPages   Gauge
34
35	Malloced Gauge // kernel malloced data in byte
36	Graphics Gauge // kernel graphics data in byte
37}
38
39// Gauge is used/available gauge.
40type Gauge struct {
41	Used  int64
42	Avail int64
43}
44
45func (g Gauge) Free() int64 {
46	return g.Avail - g.Used
47}
48
49// ReadMemStats reads memory statistics from /dev/swap.
50func ReadMemStats(ctx context.Context, opts ...Option) (*MemStats, error) {
51	cfg := newConfig(opts...)
52	swap := filepath.Join(cfg.rootdir, "/dev/swap")
53	f, err := os.Open(swap)
54	if err != nil {
55		return nil, err
56	}
57	defer f.Close()
58
59	var stat MemStats
60	m := map[string]interface{}{
61		"memory":        &stat.Total,
62		"pagesize":      &stat.PageSize,
63		"kernel":        &stat.KernelPages,
64		"user":          &stat.UserPages,
65		"swap":          &stat.SwapPages,
66		"kernel malloc": &stat.Malloced,
67		"kernel draw":   &stat.Graphics,
68	}
69	scanner := bufio.NewScanner(f)
70	for scanner.Scan() {
71		fields := bytes.SplitN(scanner.Bytes(), delim, 2)
72		if len(fields) < 2 {
73			continue
74		}
75		switch key := string(fields[1]); key {
76		case "memory", "pagesize", "kernel":
77			v := m[key].(*int64)
78			n, err := strconv.ParseInt(string(fields[0]), 10, 64)
79			if err != nil {
80				return nil, err
81			}
82			*v = n
83		case "user", "swap", "kernel malloc", "kernel draw":
84			v := m[key].(*Gauge)
85			if err := parseGauge(string(fields[0]), v); err != nil {
86				return nil, err
87			}
88		}
89	}
90	if err := scanner.Err(); err != nil {
91		return nil, err
92	}
93	return &stat, nil
94}
95
96func parseGauge(s string, r *Gauge) error {
97	a := strings.SplitN(s, "/", 2)
98	if len(a) != 2 {
99		return fmt.Errorf("can't parse ratio: %s", s)
100	}
101	var p intParser
102	u := p.ParseInt64(a[0], 10)
103	n := p.ParseInt64(a[1], 10)
104	if err := p.Err(); err != nil {
105		return err
106	}
107	r.Used = u
108	r.Avail = n
109	return nil
110}
111
112type Storage struct {
113	Name     string
114	Model    string
115	Capacity int64
116}
117
118type Interface struct {
119	Name string
120	Addr string
121}
122
123const (
124	numEther = 8  // see ether(3)
125	numIpifc = 16 // see ip(3)
126)
127
128// ReadInterfaces reads network interfaces from etherN.
129func ReadInterfaces(ctx context.Context, opts ...Option) ([]*Interface, error) {
130	cfg := newConfig(opts...)
131	var a []*Interface
132	for i := 0; i < numEther; i++ {
133		p, err := readInterface(cfg.rootdir, i)
134		if os.IsNotExist(err) {
135			continue
136		}
137		if err != nil {
138			return nil, err
139		}
140		a = append(a, p)
141	}
142	return a, nil
143}
144
145func readInterface(netroot string, i int) (*Interface, error) {
146	ether := fmt.Sprintf("ether%d", i)
147	dir := filepath.Join(netroot, ether)
148	info, err := os.Stat(dir)
149	if err != nil {
150		return nil, err
151	}
152	if !info.IsDir() {
153		return nil, fmt.Errorf("%s: is not directory", dir)
154	}
155
156	addr, err := ioutil.ReadFile(filepath.Join(dir, "addr"))
157	if err != nil {
158		return nil, err
159	}
160	return &Interface{
161		Name: ether,
162		Addr: string(addr),
163	}, nil
164}
165
166var (
167	netdirs = []string{"/net", "/net.alt"}
168)
169
170// ReadHost reads host status.
171func ReadHost(ctx context.Context, opts ...Option) (*Host, error) {
172	cfg := newConfig(opts...)
173	var h Host
174	name, err := readSysname(cfg.rootdir)
175	if err != nil {
176		return nil, err
177	}
178	h.Sysname = name
179
180	a, err := readStorages(cfg.rootdir)
181	if err != nil {
182		return nil, err
183	}
184	h.Storages = a
185
186	for _, s := range netdirs {
187		netroot := filepath.Join(cfg.rootdir, s)
188		ifaces, err := ReadInterfaces(ctx, WithRootDir(netroot))
189		if err != nil {
190			return nil, err
191		}
192		h.Interfaces = append(h.Interfaces, ifaces...)
193	}
194	return &h, nil
195}
196
197func readSysname(rootdir string) (string, error) {
198	file := filepath.Join(rootdir, "/dev/sysname")
199	b, err := ioutil.ReadFile(file)
200	if err != nil {
201		return "", err
202	}
203	return string(bytes.TrimSpace(b)), nil
204}
205
206func readStorages(rootdir string) ([]*Storage, error) {
207	sdctl := filepath.Join(rootdir, "/dev/sdctl")
208	f, err := os.Open(sdctl)
209	if err != nil {
210		return nil, err
211	}
212	defer f.Close()
213
214	var a []*Storage
215	scanner := bufio.NewScanner(f)
216	for scanner.Scan() {
217		fields := bytes.Split(scanner.Bytes(), delim)
218		if len(fields) == 0 {
219			continue
220		}
221		exp := string(fields[0]) + "*"
222		if !strings.HasPrefix(exp, "sd") {
223			continue
224		}
225		dir := filepath.Join(rootdir, "/dev", exp)
226		m, err := filepath.Glob(dir)
227		if err != nil {
228			return nil, err
229		}
230		for _, dir := range m {
231			s, err := readStorage(dir)
232			if err != nil {
233				return nil, err
234			}
235			a = append(a, s)
236		}
237	}
238	if err := scanner.Err(); err != nil {
239		return nil, err
240	}
241	return a, nil
242}
243
244func readStorage(dir string) (*Storage, error) {
245	ctl := filepath.Join(dir, "ctl")
246	f, err := os.Open(ctl)
247	if err != nil {
248		return nil, err
249	}
250	defer f.Close()
251
252	var s Storage
253	s.Name = filepath.Base(dir)
254	scanner := bufio.NewScanner(f)
255	for scanner.Scan() {
256		line := scanner.Bytes()
257		switch {
258		case bytes.HasPrefix(line, []byte("inquiry")):
259			s.Model = string(bytes.TrimSpace(line[7:]))
260		case bytes.HasPrefix(line, []byte("geometry")):
261			fields := bytes.Split(line, delim)
262			if len(fields) < 3 {
263				continue
264			}
265			var p intParser
266			sec := p.ParseInt64(string(fields[1]), 10)
267			size := p.ParseInt64(string(fields[2]), 10)
268			if err := p.Err(); err != nil {
269				return nil, err
270			}
271			s.Capacity = sec * size
272		}
273	}
274	if err := scanner.Err(); err != nil {
275		return nil, err
276	}
277	return &s, nil
278}
279
280type IPStats struct {
281	ID      int    // number of interface in ipifc dir
282	Device  string // associated physical device
283	MTU     int    // max transfer unit
284	Sendra6 uint8  // on == send router adv
285	Recvra6 uint8  // on == recv router adv
286
287	Pktin  int64 // packets read
288	Pktout int64 // packets written
289	Errin  int64 // read errors
290	Errout int64 // write errors
291}
292
293type Iplifc struct {
294	IP            net.IP
295	Mask          net.IPMask
296	Net           net.IP // ip & mask
297	PerfLifetime  int64  // preferred lifetime
298	ValidLifetime int64  // valid lifetime
299}
300
301type Ipv6rp struct {
302	// TODO(lufia): see ip(2)
303}
304