package metrics import ( "runtime" "sync" "time" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" "github.com/safing/portbase/api" "github.com/safing/portbase/dataroot" "github.com/safing/portbase/log" ) const hostStatTTL = 1 * time.Second func registerHostMetrics() (err error) { // Register load average metrics. _, err = NewGauge("host/load/avg/1", nil, getFloat64HostStat(LoadAvg1), &Options{Name: "Host Load Avg 1min", Permission: api.PermitUser}) if err != nil { return err } _, err = NewGauge("host/load/avg/5", nil, getFloat64HostStat(LoadAvg5), &Options{Name: "Host Load Avg 5min", Permission: api.PermitUser}) if err != nil { return err } _, err = NewGauge("host/load/avg/15", nil, getFloat64HostStat(LoadAvg15), &Options{Name: "Host Load Avg 15min", Permission: api.PermitUser}) if err != nil { return err } // Register memory usage metrics. _, err = NewGauge("host/mem/total", nil, getUint64HostStat(MemTotal), &Options{Name: "Host Memory Total", Permission: api.PermitUser}) if err != nil { return err } _, err = NewGauge("host/mem/used", nil, getUint64HostStat(MemUsed), &Options{Name: "Host Memory Used", Permission: api.PermitUser}) if err != nil { return err } _, err = NewGauge("host/mem/available", nil, getUint64HostStat(MemAvailable), &Options{Name: "Host Memory Available", Permission: api.PermitUser}) if err != nil { return err } _, err = NewGauge("host/mem/used/percent", nil, getFloat64HostStat(MemUsedPercent), &Options{Name: "Host Memory Used in Percent", Permission: api.PermitUser}) if err != nil { return err } // Register disk usage metrics. _, err = NewGauge("host/disk/total", nil, getUint64HostStat(DiskTotal), &Options{Name: "Host Disk Total", Permission: api.PermitUser}) if err != nil { return err } _, err = NewGauge("host/disk/used", nil, getUint64HostStat(DiskUsed), &Options{Name: "Host Disk Used", Permission: api.PermitUser}) if err != nil { return err } _, err = NewGauge("host/disk/free", nil, getUint64HostStat(DiskFree), &Options{Name: "Host Disk Free", Permission: api.PermitUser}) if err != nil { return err } _, err = NewGauge("host/disk/used/percent", nil, getFloat64HostStat(DiskUsedPercent), &Options{Name: "Host Disk Used in Percent", Permission: api.PermitUser}) if err != nil { return err } return nil } func getUint64HostStat(getStat func() (uint64, bool)) func() float64 { return func() float64 { val, _ := getStat() return float64(val) } } func getFloat64HostStat(getStat func() (float64, bool)) func() float64 { return func() float64 { val, _ := getStat() return val } } var ( loadAvg *load.AvgStat loadAvgExpires time.Time loadAvgLock sync.Mutex ) func getLoadAvg() *load.AvgStat { loadAvgLock.Lock() defer loadAvgLock.Unlock() // Return cache if still valid. if time.Now().Before(loadAvgExpires) { return loadAvg } // Refresh. var err error loadAvg, err = load.Avg() if err != nil { log.Warningf("metrics: failed to get load avg: %s", err) loadAvg = nil } loadAvgExpires = time.Now().Add(hostStatTTL) return loadAvg } // LoadAvg1 returns the 1-minute average system load. func LoadAvg1() (loadAvg float64, ok bool) { if stat := getLoadAvg(); stat != nil { return stat.Load1 / float64(runtime.NumCPU()), true } return 0, false } // LoadAvg5 returns the 5-minute average system load. func LoadAvg5() (loadAvg float64, ok bool) { if stat := getLoadAvg(); stat != nil { return stat.Load5 / float64(runtime.NumCPU()), true } return 0, false } // LoadAvg15 returns the 15-minute average system load. func LoadAvg15() (loadAvg float64, ok bool) { if stat := getLoadAvg(); stat != nil { return stat.Load15 / float64(runtime.NumCPU()), true } return 0, false } var ( memStat *mem.VirtualMemoryStat memStatExpires time.Time memStatLock sync.Mutex ) func getMemStat() *mem.VirtualMemoryStat { memStatLock.Lock() defer memStatLock.Unlock() // Return cache if still valid. if time.Now().Before(memStatExpires) { return memStat } // Refresh. var err error memStat, err = mem.VirtualMemory() if err != nil { log.Warningf("metrics: failed to get load avg: %s", err) memStat = nil } memStatExpires = time.Now().Add(hostStatTTL) return memStat } // MemTotal returns the total system memory. func MemTotal() (total uint64, ok bool) { if stat := getMemStat(); stat != nil { return stat.Total, true } return 0, false } // MemUsed returns the used system memory. func MemUsed() (used uint64, ok bool) { if stat := getMemStat(); stat != nil { return stat.Used, true } return 0, false } // MemAvailable returns the available system memory. func MemAvailable() (available uint64, ok bool) { if stat := getMemStat(); stat != nil { return stat.Available, true } return 0, false } // MemUsedPercent returns the percent of used system memory. func MemUsedPercent() (usedPercent float64, ok bool) { if stat := getMemStat(); stat != nil { return stat.UsedPercent, true } return 0, false } var ( diskStat *disk.UsageStat diskStatExpires time.Time diskStatLock sync.Mutex ) func getDiskStat() *disk.UsageStat { diskStatLock.Lock() defer diskStatLock.Unlock() // Return cache if still valid. if time.Now().Before(diskStatExpires) { return diskStat } // Check if we have a data root. dataRoot := dataroot.Root() if dataRoot == nil { log.Warning("metrics: cannot get disk stats without data root") diskStat = nil diskStatExpires = time.Now().Add(hostStatTTL) return diskStat } // Refresh. var err error diskStat, err = disk.Usage(dataRoot.Path) if err != nil { log.Warningf("metrics: failed to get load avg: %s", err) diskStat = nil } diskStatExpires = time.Now().Add(hostStatTTL) return diskStat } // DiskTotal returns the total disk space (from the program's data root). func DiskTotal() (total uint64, ok bool) { if stat := getDiskStat(); stat != nil { return stat.Total, true } return 0, false } // DiskUsed returns the used disk space (from the program's data root). func DiskUsed() (used uint64, ok bool) { if stat := getDiskStat(); stat != nil { return stat.Used, true } return 0, false } // DiskFree returns the available disk space (from the program's data root). func DiskFree() (free uint64, ok bool) { if stat := getDiskStat(); stat != nil { return stat.Free, true } return 0, false } // DiskUsedPercent returns the percent of used disk space (from the program's data root). func DiskUsedPercent() (usedPercent float64, ok bool) { if stat := getDiskStat(); stat != nil { return stat.UsedPercent, true } return 0, false }