130 lines
3.2 KiB
Go
130 lines
3.2 KiB
Go
package beach
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Host is the machine Beach runs on.
|
|
type Host struct {
|
|
Hostname string `json:"hostname"`
|
|
Uptime float64 `json:"uptimeSeconds"`
|
|
Load [3]float64 `json:"load"`
|
|
CPUPct float64 `json:"cpuPct"`
|
|
CPUs int `json:"cpus"`
|
|
MemTotal uint64 `json:"memTotal"`
|
|
MemUsed uint64 `json:"memUsed"`
|
|
SwapTotal uint64 `json:"swapTotal"`
|
|
SwapUsed uint64 `json:"swapUsed"`
|
|
Disks []Filesystem `json:"filesystems"`
|
|
Supported bool `json:"supported"`
|
|
}
|
|
|
|
type Filesystem struct {
|
|
Mount string `json:"mount"`
|
|
Total uint64 `json:"total"`
|
|
Used uint64 `json:"used"`
|
|
}
|
|
|
|
// cpuTimes is the aggregate line of /proc/stat.
|
|
type cpuTimes struct{ busy, total uint64 }
|
|
|
|
func parseCPU(stat string) (cpuTimes, error) {
|
|
for _, line := range strings.Split(stat, "\n") {
|
|
f := strings.Fields(line)
|
|
if len(f) < 5 || f[0] != "cpu" {
|
|
continue
|
|
}
|
|
var v []uint64
|
|
for _, s := range f[1:] {
|
|
n, err := strconv.ParseUint(s, 10, 64)
|
|
if err != nil {
|
|
return cpuTimes{}, err
|
|
}
|
|
v = append(v, n)
|
|
}
|
|
var total uint64
|
|
for i, n := range v {
|
|
if i < 8 { // guest times are already counted in user/nice
|
|
total += n
|
|
}
|
|
}
|
|
idle := v[3]
|
|
if len(v) > 4 {
|
|
idle += v[4] // iowait
|
|
}
|
|
return cpuTimes{busy: total - idle, total: total}, nil
|
|
}
|
|
return cpuTimes{}, fmt.Errorf("no cpu line")
|
|
}
|
|
|
|
// cpuPct is the busy share between two samples.
|
|
func cpuPct(a, b cpuTimes) float64 {
|
|
if b.total <= a.total || b.busy < a.busy {
|
|
return 0
|
|
}
|
|
return float64(b.busy-a.busy) / float64(b.total-a.total) * 100
|
|
}
|
|
|
|
func parseMeminfo(s string) (total, used, swapTotal, swapUsed uint64) {
|
|
kb := map[string]uint64{}
|
|
for _, line := range strings.Split(s, "\n") {
|
|
f := strings.Fields(line)
|
|
if len(f) >= 2 {
|
|
n, _ := strconv.ParseUint(f[1], 10, 64)
|
|
kb[strings.TrimSuffix(f[0], ":")] = n * 1024
|
|
}
|
|
}
|
|
total = kb["MemTotal"]
|
|
avail, ok := kb["MemAvailable"]
|
|
if !ok {
|
|
avail = kb["MemFree"] + kb["Buffers"] + kb["Cached"]
|
|
}
|
|
if avail <= total {
|
|
used = total - avail
|
|
}
|
|
swapTotal = kb["SwapTotal"]
|
|
if kb["SwapFree"] <= swapTotal {
|
|
swapUsed = swapTotal - kb["SwapFree"]
|
|
}
|
|
return
|
|
}
|
|
|
|
func parseLoad(s string) (l [3]float64) {
|
|
f := strings.Fields(s)
|
|
for i := 0; i < 3 && i < len(f); i++ {
|
|
l[i], _ = strconv.ParseFloat(f[i], 64)
|
|
}
|
|
return
|
|
}
|
|
|
|
func parseUptime(s string) float64 {
|
|
f := strings.Fields(s)
|
|
if len(f) == 0 {
|
|
return 0
|
|
}
|
|
v, _ := strconv.ParseFloat(f[0], 64)
|
|
return v
|
|
}
|
|
|
|
// realMounts picks the mounts worth showing from /proc/mounts: real block or
|
|
// ZFS filesystems, once per device, without Docker's own overlay mounts.
|
|
func realMounts(mounts string) []string {
|
|
ok := map[string]bool{"ext4": true, "xfs": true, "btrfs": true, "zfs": true, "ext3": true, "f2fs": true, "vfat": true, "ntfs": true, "ntfs3": true, "exfat": true}
|
|
seen := map[string]bool{}
|
|
var out []string
|
|
for _, line := range strings.Split(mounts, "\n") {
|
|
f := strings.Fields(line)
|
|
if len(f) < 3 || !ok[f[2]] || seen[f[0]] {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(f[1], "/var/lib/docker") || strings.HasPrefix(f[1], "/proc") || strings.HasPrefix(f[1], "/sys") {
|
|
continue
|
|
}
|
|
seen[f[0]] = true
|
|
out = append(out, f[1])
|
|
}
|
|
return out
|
|
}
|