github.com/safing/portbase@v0.19.5/metrics/metrics_host.go (about) 1 package metrics 2 3 import ( 4 "runtime" 5 "sync" 6 "time" 7 8 "github.com/shirou/gopsutil/disk" 9 "github.com/shirou/gopsutil/load" 10 "github.com/shirou/gopsutil/mem" 11 12 "github.com/safing/portbase/api" 13 "github.com/safing/portbase/dataroot" 14 "github.com/safing/portbase/log" 15 ) 16 17 const hostStatTTL = 1 * time.Second 18 19 func registerHostMetrics() (err error) { 20 // Register load average metrics. 21 _, err = NewGauge("host/load/avg/1", nil, getFloat64HostStat(LoadAvg1), &Options{Name: "Host Load Avg 1min", Permission: api.PermitUser}) 22 if err != nil { 23 return err 24 } 25 _, err = NewGauge("host/load/avg/5", nil, getFloat64HostStat(LoadAvg5), &Options{Name: "Host Load Avg 5min", Permission: api.PermitUser}) 26 if err != nil { 27 return err 28 } 29 _, err = NewGauge("host/load/avg/15", nil, getFloat64HostStat(LoadAvg15), &Options{Name: "Host Load Avg 15min", Permission: api.PermitUser}) 30 if err != nil { 31 return err 32 } 33 34 // Register memory usage metrics. 35 _, err = NewGauge("host/mem/total", nil, getUint64HostStat(MemTotal), &Options{Name: "Host Memory Total", Permission: api.PermitUser}) 36 if err != nil { 37 return err 38 } 39 _, err = NewGauge("host/mem/used", nil, getUint64HostStat(MemUsed), &Options{Name: "Host Memory Used", Permission: api.PermitUser}) 40 if err != nil { 41 return err 42 } 43 _, err = NewGauge("host/mem/available", nil, getUint64HostStat(MemAvailable), &Options{Name: "Host Memory Available", Permission: api.PermitUser}) 44 if err != nil { 45 return err 46 } 47 _, err = NewGauge("host/mem/used/percent", nil, getFloat64HostStat(MemUsedPercent), &Options{Name: "Host Memory Used in Percent", Permission: api.PermitUser}) 48 if err != nil { 49 return err 50 } 51 52 // Register disk usage metrics. 53 _, err = NewGauge("host/disk/total", nil, getUint64HostStat(DiskTotal), &Options{Name: "Host Disk Total", Permission: api.PermitUser}) 54 if err != nil { 55 return err 56 } 57 _, err = NewGauge("host/disk/used", nil, getUint64HostStat(DiskUsed), &Options{Name: "Host Disk Used", Permission: api.PermitUser}) 58 if err != nil { 59 return err 60 } 61 _, err = NewGauge("host/disk/free", nil, getUint64HostStat(DiskFree), &Options{Name: "Host Disk Free", Permission: api.PermitUser}) 62 if err != nil { 63 return err 64 } 65 _, err = NewGauge("host/disk/used/percent", nil, getFloat64HostStat(DiskUsedPercent), &Options{Name: "Host Disk Used in Percent", Permission: api.PermitUser}) 66 if err != nil { 67 return err 68 } 69 70 return nil 71 } 72 73 func getUint64HostStat(getStat func() (uint64, bool)) func() float64 { 74 return func() float64 { 75 val, _ := getStat() 76 return float64(val) 77 } 78 } 79 80 func getFloat64HostStat(getStat func() (float64, bool)) func() float64 { 81 return func() float64 { 82 val, _ := getStat() 83 return val 84 } 85 } 86 87 var ( 88 loadAvg *load.AvgStat 89 loadAvgExpires time.Time 90 loadAvgLock sync.Mutex 91 ) 92 93 func getLoadAvg() *load.AvgStat { 94 loadAvgLock.Lock() 95 defer loadAvgLock.Unlock() 96 97 // Return cache if still valid. 98 if time.Now().Before(loadAvgExpires) { 99 return loadAvg 100 } 101 102 // Refresh. 103 var err error 104 loadAvg, err = load.Avg() 105 if err != nil { 106 log.Warningf("metrics: failed to get load avg: %s", err) 107 loadAvg = nil 108 } 109 loadAvgExpires = time.Now().Add(hostStatTTL) 110 111 return loadAvg 112 } 113 114 // LoadAvg1 returns the 1-minute average system load. 115 func LoadAvg1() (loadAvg float64, ok bool) { 116 if stat := getLoadAvg(); stat != nil { 117 return stat.Load1 / float64(runtime.NumCPU()), true 118 } 119 return 0, false 120 } 121 122 // LoadAvg5 returns the 5-minute average system load. 123 func LoadAvg5() (loadAvg float64, ok bool) { 124 if stat := getLoadAvg(); stat != nil { 125 return stat.Load5 / float64(runtime.NumCPU()), true 126 } 127 return 0, false 128 } 129 130 // LoadAvg15 returns the 15-minute average system load. 131 func LoadAvg15() (loadAvg float64, ok bool) { 132 if stat := getLoadAvg(); stat != nil { 133 return stat.Load15 / float64(runtime.NumCPU()), true 134 } 135 return 0, false 136 } 137 138 var ( 139 memStat *mem.VirtualMemoryStat 140 memStatExpires time.Time 141 memStatLock sync.Mutex 142 ) 143 144 func getMemStat() *mem.VirtualMemoryStat { 145 memStatLock.Lock() 146 defer memStatLock.Unlock() 147 148 // Return cache if still valid. 149 if time.Now().Before(memStatExpires) { 150 return memStat 151 } 152 153 // Refresh. 154 var err error 155 memStat, err = mem.VirtualMemory() 156 if err != nil { 157 log.Warningf("metrics: failed to get load avg: %s", err) 158 memStat = nil 159 } 160 memStatExpires = time.Now().Add(hostStatTTL) 161 162 return memStat 163 } 164 165 // MemTotal returns the total system memory. 166 func MemTotal() (total uint64, ok bool) { 167 if stat := getMemStat(); stat != nil { 168 return stat.Total, true 169 } 170 return 0, false 171 } 172 173 // MemUsed returns the used system memory. 174 func MemUsed() (used uint64, ok bool) { 175 if stat := getMemStat(); stat != nil { 176 return stat.Used, true 177 } 178 return 0, false 179 } 180 181 // MemAvailable returns the available system memory. 182 func MemAvailable() (available uint64, ok bool) { 183 if stat := getMemStat(); stat != nil { 184 return stat.Available, true 185 } 186 return 0, false 187 } 188 189 // MemUsedPercent returns the percent of used system memory. 190 func MemUsedPercent() (usedPercent float64, ok bool) { 191 if stat := getMemStat(); stat != nil { 192 return stat.UsedPercent, true 193 } 194 return 0, false 195 } 196 197 var ( 198 diskStat *disk.UsageStat 199 diskStatExpires time.Time 200 diskStatLock sync.Mutex 201 ) 202 203 func getDiskStat() *disk.UsageStat { 204 diskStatLock.Lock() 205 defer diskStatLock.Unlock() 206 207 // Return cache if still valid. 208 if time.Now().Before(diskStatExpires) { 209 return diskStat 210 } 211 212 // Check if we have a data root. 213 dataRoot := dataroot.Root() 214 if dataRoot == nil { 215 log.Warning("metrics: cannot get disk stats without data root") 216 diskStat = nil 217 diskStatExpires = time.Now().Add(hostStatTTL) 218 return diskStat 219 } 220 221 // Refresh. 222 var err error 223 diskStat, err = disk.Usage(dataRoot.Path) 224 if err != nil { 225 log.Warningf("metrics: failed to get load avg: %s", err) 226 diskStat = nil 227 } 228 diskStatExpires = time.Now().Add(hostStatTTL) 229 230 return diskStat 231 } 232 233 // DiskTotal returns the total disk space (from the program's data root). 234 func DiskTotal() (total uint64, ok bool) { 235 if stat := getDiskStat(); stat != nil { 236 return stat.Total, true 237 } 238 return 0, false 239 } 240 241 // DiskUsed returns the used disk space (from the program's data root). 242 func DiskUsed() (used uint64, ok bool) { 243 if stat := getDiskStat(); stat != nil { 244 return stat.Used, true 245 } 246 return 0, false 247 } 248 249 // DiskFree returns the available disk space (from the program's data root). 250 func DiskFree() (free uint64, ok bool) { 251 if stat := getDiskStat(); stat != nil { 252 return stat.Free, true 253 } 254 return 0, false 255 } 256 257 // DiskUsedPercent returns the percent of used disk space (from the program's data root). 258 func DiskUsedPercent() (usedPercent float64, ok bool) { 259 if stat := getDiskStat(); stat != nil { 260 return stat.UsedPercent, true 261 } 262 return 0, false 263 }