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 }