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