Initial Beach: homelab control panel
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
6938da4d7a
18 files changed
+2071
No files matched your search
@@ -0,0 +1,129 @@
|
||||
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
|
||||
}
|
||||
Reference in new issue
Block a user