mirror of https://github.com/glanceapp/glance.git
Add server-stats widget
parent
306fb3cb33
commit
37f35281b4
@ -0,0 +1,140 @@
|
||||
{{ template "widget-base.html" . }}
|
||||
|
||||
{{- define "widget-content" }}
|
||||
{{- range .Servers }}
|
||||
<div class="server">
|
||||
<div class="server-info">
|
||||
<div class="server-details">
|
||||
<div class="server-name color-highlight size-h3">{{ if .Name }}{{ .Name }}{{ else }}{{ .Info.Hostname }}{{ end }}</div>
|
||||
<div>
|
||||
{{- if .IsReachable }}
|
||||
{{ if .Info.HostInfoIsAvailable }}<span {{ dynamicRelativeTimeAttrs .Info.BootTime }}></span>{{ else }}unknown{{ end }} uptime
|
||||
{{- else }}
|
||||
unreachable
|
||||
{{- end }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="shrink-0"{{ if .IsReachable }} data-popover-type="html" data-popover-margin="0.2rem" data-popover-max-width="400px"{{ end }}>
|
||||
{{- if .IsReachable }}
|
||||
<div data-popover-html>
|
||||
<div class="size-h5 text-compact">PLATFORM</div>
|
||||
<div class="color-highlight">{{ if .Info.HostInfoIsAvailable }}{{ .Info.Platform }}{{ else }}Unknown{{ end }}</div>
|
||||
</div>
|
||||
{{- end }}
|
||||
<svg class="server-icon" stroke="var(--color-{{ if .IsReachable }}positive{{ else }}negative{{ end }})" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 17.25v-.228a4.5 4.5 0 0 0-.12-1.03l-2.268-9.64a3.375 3.375 0 0 0-3.285-2.602H7.923a3.375 3.375 0 0 0-3.285 2.602l-2.268 9.64a4.5 4.5 0 0 0-.12 1.03v.228m19.5 0a3 3 0 0 1-3 3H5.25a3 3 0 0 1-3-3m19.5 0a3 3 0 0 0-3-3H5.25a3 3 0 0 0-3 3m16.5 0h.008v.008h-.008v-.008Zm-3 0h.008v.008h-.008v-.008Z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="server-stats">
|
||||
<div class="flex-1{{ if not .Info.CPU.LoadIsAvailable }} server-stat-unavailable{{ end }}">
|
||||
<div class="flex items-end size-h5">
|
||||
<div>CPU</div>
|
||||
{{- if and .Info.CPU.TemperatureIsAvailable (ge .Info.CPU.TemperatureC 80) }}
|
||||
<svg class="server-spicy-cpu-icon" fill="var(--color-negative)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" >
|
||||
<path fill-rule="evenodd" d="M8.074.945A4.993 4.993 0 0 0 6 5v.032c.004.6.114 1.176.311 1.709.16.428-.204.91-.61.7a5.023 5.023 0 0 1-1.868-1.677c-.202-.304-.648-.363-.848-.058a6 6 0 1 0 8.017-1.901l-.004-.007a4.98 4.98 0 0 1-2.18-2.574c-.116-.31-.477-.472-.744-.28Zm.78 6.178a3.001 3.001 0 1 1-3.473 4.341c-.205-.365.215-.694.62-.59a4.008 4.008 0 0 0 1.873.03c.288-.065.413-.386.321-.666A3.997 3.997 0 0 1 8 8.999c0-.585.126-1.14.351-1.641a.42.42 0 0 1 .503-.235Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
{{- end }}
|
||||
<div class="color-highlight margin-left-auto text-very-compact">{{ if .Info.CPU.LoadIsAvailable }}{{ .Info.CPU.Load1Percent }} <span class="color-base">%</span>{{ else }}n/a{{ end }}</div>
|
||||
</div>
|
||||
<div{{ if .Info.CPU.LoadIsAvailable }} data-popover-type="html"{{ end }}>
|
||||
{{- if .Info.CPU.LoadIsAvailable }}
|
||||
<div data-popover-html>
|
||||
<div class="flex">
|
||||
<div class="size-h5">1M AVG</div>
|
||||
<div class="value-separator"></div>
|
||||
<div class="color-highlight text-very-compact">{{ .Info.CPU.Load1Percent }} <span class="color-base size-h5">%</span></div>
|
||||
</div>
|
||||
<div class="flex margin-top-3">
|
||||
<div class="size-h5">15M AVG</div>
|
||||
<div class="value-separator"></div>
|
||||
<div class="color-highlight text-very-compact">{{ .Info.CPU.Load15Percent }} <span class="color-base size-h5">%</span></div>
|
||||
</div>
|
||||
{{- if .Info.CPU.TemperatureIsAvailable }}
|
||||
<div class="flex margin-top-3">
|
||||
<div class="size-h5">TEMP C</div>
|
||||
<div class="value-separator"></div>
|
||||
<div class="color-highlight text-very-compact">{{ .Info.CPU.TemperatureC }} <span class="color-base size-h5">°</span></div>
|
||||
</div>
|
||||
{{- end }}
|
||||
</div>
|
||||
{{- end }}
|
||||
<div class="progress-bar progress-bar-combined">
|
||||
{{- if .Info.CPU.LoadIsAvailable }}
|
||||
<div class="progress-value{{ if ge .Info.CPU.Load1Percent 85 }} progress-value-notice{{ end }}" style="--percent: {{ .Info.CPU.Load1Percent }}"></div>
|
||||
<div class="progress-value{{ if ge .Info.CPU.Load15Percent 85 }} progress-value-notice{{ end }}" style="--percent: {{ .Info.CPU.Load15Percent }}"></div>
|
||||
{{- end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1{{ if not .Info.Memory.IsAvailable }} server-stat-unavailable{{ end }}">
|
||||
<div class="flex justify-between items-end size-h5">
|
||||
<div>RAM</div>
|
||||
<div class="color-highlight text-very-compact">{{ if .Info.Memory.IsAvailable }}{{ .Info.Memory.UsedPercent }} <span class="color-base">%</span>{{ else }}n/a{{ end }}</div>
|
||||
</div>
|
||||
<div{{ if .Info.Memory.IsAvailable }} data-popover-type="html"{{ end }}>
|
||||
{{- if .Info.Memory.IsAvailable }}
|
||||
<div data-popover-html>
|
||||
<div class="flex">
|
||||
<div class="size-h5">RAM</div>
|
||||
<div class="value-separator"></div>
|
||||
<div class="color-highlight text-very-compact">
|
||||
{{ .Info.Memory.UsedMB | formatServerMegabytes }} <span class="color-base size-h5">/</span> {{ .Info.Memory.TotalMB | formatServerMegabytes }}
|
||||
</div>
|
||||
</div>
|
||||
{{- if and (not .HideSwap) .Info.Memory.SwapIsAvailable }}
|
||||
<div class="flex margin-top-3">
|
||||
<div class="size-h5">SWAP</div>
|
||||
<div class="value-separator"></div>
|
||||
<div class="color-highlight text-very-compact">
|
||||
{{ .Info.Memory.SwapUsedMB | formatServerMegabytes }} <span class="color-base size-h5">/</span> {{ .Info.Memory.SwapTotalMB | formatServerMegabytes }}
|
||||
</div>
|
||||
</div>
|
||||
{{- end }}
|
||||
</div>
|
||||
{{- end }}
|
||||
<div class="progress-bar progress-bar-combined">
|
||||
{{- if .Info.Memory.IsAvailable }}
|
||||
<div class="progress-value{{ if ge .Info.Memory.UsedPercent 85 }} progress-value-notice{{ end }}" style="--percent: {{ .Info.Memory.UsedPercent }}"></div>
|
||||
{{- if and (not .HideSwap) .Info.Memory.SwapIsAvailable }}
|
||||
<div class="progress-value{{ if ge .Info.Memory.SwapUsedPercent 85 }} progress-value-notice{{ end }}" style="--percent: {{ .Info.Memory.SwapUsedPercent }}"></div>
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1{{ if not .Info.Mountpoints }} server-stat-unavailable{{ end }}">
|
||||
<div class="flex justify-between items-end size-h5">
|
||||
<div>DISK</div>
|
||||
<div class="color-highlight text-very-compact">{{ if .Info.Mountpoints }}{{ (index .Info.Mountpoints 0).UsedPercent }} <span class="color-base">%</span>{{ else }}n/a{{ end }}</div>
|
||||
</div>
|
||||
<div{{ if .Info.Mountpoints }} data-popover-type="html"{{ end }}>
|
||||
{{- if .Info.Mountpoints }}
|
||||
<div data-popover-html>
|
||||
<ul class="list list-gap-2">
|
||||
{{- range .Info.Mountpoints }}
|
||||
<li class="flex">
|
||||
<div class="size-h5">{{ if .Name }}{{ .Name }}{{ else }}{{ .Path }}{{ end }}</div>
|
||||
<div class="value-separator"></div>
|
||||
<div class="color-highlight text-very-compact">
|
||||
{{ .UsedMB | formatServerMegabytes }} <span class="color-base size-h5">/</span> {{ .TotalMB | formatServerMegabytes }}
|
||||
</div>
|
||||
</li>
|
||||
{{- end }}
|
||||
</ul>
|
||||
</div>
|
||||
{{- end }}
|
||||
<div class="progress-bar progress-bar-combined">
|
||||
{{- if .Info.Mountpoints }}
|
||||
<div class="progress-value{{ if ge ((index .Info.Mountpoints 0).UsedPercent) 85 }} progress-value-notice{{ end }}" style="--percent: {{ (index .Info.Mountpoints 0).UsedPercent }}"></div>
|
||||
{{- if ge (len .Info.Mountpoints) 2 }}
|
||||
<div class="progress-value{{ if ge ((index .Info.Mountpoints 1).UsedPercent) 85 }} progress-value-notice{{ end }}" style="--percent: {{ (index .Info.Mountpoints 1).UsedPercent }}"></div>
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@ -0,0 +1,117 @@
|
||||
package glance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"html/template"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/glanceapp/glance/pkg/sysinfo"
|
||||
)
|
||||
|
||||
var serverStatsWidgetTemplate = mustParseTemplate("server-stats.html", "widget-base.html")
|
||||
|
||||
type serverStatsWidget struct {
|
||||
widgetBase `yaml:",inline"`
|
||||
Servers []serverStatsRequest `yaml:"servers"`
|
||||
}
|
||||
|
||||
func (widget *serverStatsWidget) initialize() error {
|
||||
widget.withTitle("Server Stats").withCacheDuration(15 * time.Second)
|
||||
widget.widgetBase.WIP = true
|
||||
|
||||
if len(widget.Servers) == 0 {
|
||||
widget.Servers = []serverStatsRequest{{Type: "local"}}
|
||||
}
|
||||
|
||||
for i := range widget.Servers {
|
||||
widget.Servers[i].URL = strings.TrimRight(widget.Servers[i].URL, "/")
|
||||
|
||||
if widget.Servers[i].Timeout == 0 {
|
||||
widget.Servers[i].Timeout = durationField(3 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (widget *serverStatsWidget) update(context.Context) {
|
||||
// Refactor later, most of it may change depending on feedback
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := range widget.Servers {
|
||||
serv := &widget.Servers[i]
|
||||
|
||||
if serv.Type == "local" {
|
||||
info, errs := sysinfo.Collect(serv.SystemInfoRequest)
|
||||
|
||||
if len(errs) > 0 {
|
||||
for i := range errs {
|
||||
slog.Warn("Getting system info: " + errs[i].Error())
|
||||
}
|
||||
}
|
||||
|
||||
serv.IsReachable = true
|
||||
serv.Info = info
|
||||
} else {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
info, err := fetchRemoteServerInfo(serv)
|
||||
if err != nil {
|
||||
slog.Warn("Getting remote system info: " + err.Error())
|
||||
serv.IsReachable = false
|
||||
serv.Info = &sysinfo.SystemInfo{
|
||||
Hostname: "Unnamed server #" + strconv.Itoa(i+1),
|
||||
}
|
||||
} else {
|
||||
serv.IsReachable = true
|
||||
serv.Info = info
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
widget.withError(nil).scheduleNextUpdate()
|
||||
}
|
||||
|
||||
func (widget *serverStatsWidget) Render() template.HTML {
|
||||
return widget.renderTemplate(widget, serverStatsWidgetTemplate)
|
||||
}
|
||||
|
||||
type serverStatsRequest struct {
|
||||
*sysinfo.SystemInfoRequest `yaml:",inline"`
|
||||
Info *sysinfo.SystemInfo `yaml:"-"`
|
||||
IsReachable bool `yaml:"-"`
|
||||
StatusText string `yaml:"-"`
|
||||
Name string `yaml:"name"`
|
||||
HideSwap bool `yaml:"hide-swap"`
|
||||
Type string `yaml:"type"`
|
||||
URL string `yaml:"url"`
|
||||
Token string `yaml:"token"`
|
||||
Timeout durationField `yaml:"timeout"`
|
||||
// Support for other agents
|
||||
// Provider string `yaml:"provider"`
|
||||
}
|
||||
|
||||
func fetchRemoteServerInfo(infoReq *serverStatsRequest) (*sysinfo.SystemInfo, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(infoReq.Timeout))
|
||||
defer cancel()
|
||||
|
||||
request, _ := http.NewRequestWithContext(ctx, "GET", infoReq.URL+"/api/sysinfo/all", nil)
|
||||
if infoReq.Token != "" {
|
||||
request.Header.Set("Authorization", "Bearer "+infoReq.Token)
|
||||
}
|
||||
|
||||
info, err := decodeJsonFromRequest[*sysinfo.SystemInfo](defaultHTTPClient, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
@ -0,0 +1,252 @@
|
||||
package sysinfo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/disk"
|
||||
"github.com/shirou/gopsutil/v4/host"
|
||||
"github.com/shirou/gopsutil/v4/load"
|
||||
"github.com/shirou/gopsutil/v4/mem"
|
||||
"github.com/shirou/gopsutil/v4/sensors"
|
||||
)
|
||||
|
||||
type timestampJSON struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func (t timestampJSON) MarshalJSON() ([]byte, error) {
|
||||
return []byte(strconv.FormatInt(t.Unix(), 10)), nil
|
||||
}
|
||||
|
||||
func (t *timestampJSON) UnmarshalJSON(data []byte) error {
|
||||
i, err := strconv.ParseInt(string(data), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.Time = time.Unix(i, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
type SystemInfo struct {
|
||||
HostInfoIsAvailable bool `json:"host_info_is_available"`
|
||||
BootTime timestampJSON `json:"boot_time"`
|
||||
Hostname string `json:"hostname"`
|
||||
Platform string `json:"platform"`
|
||||
|
||||
CPU struct {
|
||||
LoadIsAvailable bool `json:"load_is_available"`
|
||||
Load1Percent uint8 `json:"load1_percent"`
|
||||
Load15Percent uint8 `json:"load15_percent"`
|
||||
|
||||
TemperatureIsAvailable bool `json:"temperature_is_available"`
|
||||
TemperatureC uint8 `json:"temperature_c"`
|
||||
} `json:"cpu"`
|
||||
|
||||
Memory struct {
|
||||
IsAvailable bool `json:"memory_is_available"`
|
||||
TotalMB uint64 `json:"total_mb"`
|
||||
UsedMB uint64 `json:"used_mb"`
|
||||
UsedPercent uint8 `json:"used_percent"`
|
||||
|
||||
SwapIsAvailable bool `json:"swap_is_available"`
|
||||
SwapTotalMB uint64 `json:"swap_total_mb"`
|
||||
SwapUsedMB uint64 `json:"swap_used_mb"`
|
||||
SwapUsedPercent uint8 `json:"swap_used_percent"`
|
||||
} `json:"memory"`
|
||||
|
||||
Mountpoints []MountpointInfo `json:"mountpoints"`
|
||||
}
|
||||
|
||||
type MountpointInfo struct {
|
||||
Path string `json:"path"`
|
||||
Name string `json:"name"`
|
||||
TotalMB uint64 `json:"total_mb"`
|
||||
UsedMB uint64 `json:"used_mb"`
|
||||
UsedPercent uint8 `json:"used_percent"`
|
||||
}
|
||||
|
||||
type SystemInfoRequest struct {
|
||||
CPUTempSensor string `yaml:"cpu-temp-sensor"`
|
||||
Mountpoints map[string]MointpointRequest `yaml:"mountpoints"`
|
||||
}
|
||||
|
||||
type MointpointRequest struct {
|
||||
Name string `yaml:"name"`
|
||||
Hide bool `yaml:"hide"`
|
||||
}
|
||||
|
||||
// Currently caches hostname indefinitely which isn't ideal
|
||||
// Potential issue with caching boot time as it may not initially get reported correctly:
|
||||
// https://github.com/shirou/gopsutil/issues/842#issuecomment-1908972344
|
||||
var cachedHostInfo = struct {
|
||||
available bool
|
||||
hostname string
|
||||
platform string
|
||||
bootTime timestampJSON
|
||||
}{}
|
||||
|
||||
func Collect(req *SystemInfoRequest) (*SystemInfo, []error) {
|
||||
if req == nil {
|
||||
req = &SystemInfoRequest{}
|
||||
}
|
||||
|
||||
var errs []error
|
||||
|
||||
addErr := func(err error) {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
info := &SystemInfo{
|
||||
Mountpoints: []MountpointInfo{},
|
||||
}
|
||||
|
||||
applyCachedHostInfo := func() {
|
||||
info.HostInfoIsAvailable = true
|
||||
info.BootTime = cachedHostInfo.bootTime
|
||||
info.Hostname = cachedHostInfo.hostname
|
||||
info.Platform = cachedHostInfo.platform
|
||||
}
|
||||
|
||||
if cachedHostInfo.available {
|
||||
applyCachedHostInfo()
|
||||
} else {
|
||||
hostInfo, err := host.Info()
|
||||
if err == nil {
|
||||
cachedHostInfo.available = true
|
||||
cachedHostInfo.bootTime = timestampJSON{time.Unix(int64(hostInfo.BootTime), 0)}
|
||||
cachedHostInfo.hostname = hostInfo.Hostname
|
||||
cachedHostInfo.platform = hostInfo.Platform
|
||||
|
||||
applyCachedHostInfo()
|
||||
} else {
|
||||
addErr(fmt.Errorf("getting host info: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
coreCount, err := cpu.Counts(true)
|
||||
if err == nil {
|
||||
loadAvg, err := load.Avg()
|
||||
if err == nil {
|
||||
info.CPU.LoadIsAvailable = true
|
||||
if runtime.GOOS == "windows" {
|
||||
// The numbers returned here seem unreliable on Windows. Even with the CPU pegged
|
||||
// at close to 50% for multiple minutes, load1 is sometimes way under or way over
|
||||
// with no clear pattern. Dividing by core count gives numbers that are way too
|
||||
// low so that's likely not necessary as it is with unix.
|
||||
info.CPU.Load1Percent = uint8(math.Min(loadAvg.Load1*100, 100))
|
||||
info.CPU.Load15Percent = uint8(math.Min(loadAvg.Load15*100, 100))
|
||||
} else {
|
||||
info.CPU.Load1Percent = uint8(math.Min((loadAvg.Load1/float64(coreCount))*100, 100))
|
||||
info.CPU.Load15Percent = uint8(math.Min((loadAvg.Load15/float64(coreCount))*100, 100))
|
||||
}
|
||||
} else {
|
||||
addErr(fmt.Errorf("getting load avg: %v", err))
|
||||
}
|
||||
} else {
|
||||
addErr(fmt.Errorf("getting core count: %v", err))
|
||||
}
|
||||
|
||||
memory, err := mem.VirtualMemory()
|
||||
if err == nil {
|
||||
info.Memory.IsAvailable = true
|
||||
info.Memory.TotalMB = memory.Total / 1024 / 1024
|
||||
info.Memory.UsedMB = memory.Used / 1024 / 1024
|
||||
info.Memory.UsedPercent = uint8(math.Min(memory.UsedPercent, 100))
|
||||
} else {
|
||||
addErr(fmt.Errorf("getting memory info: %v", err))
|
||||
}
|
||||
|
||||
swapMemory, err := mem.SwapMemory()
|
||||
if err == nil {
|
||||
info.Memory.SwapIsAvailable = true
|
||||
info.Memory.SwapTotalMB = swapMemory.Total / 1024 / 1024
|
||||
info.Memory.SwapUsedMB = swapMemory.Used / 1024 / 1024
|
||||
info.Memory.SwapUsedPercent = uint8(math.Min(swapMemory.UsedPercent, 100))
|
||||
} else {
|
||||
addErr(fmt.Errorf("getting swap memory info: %v", err))
|
||||
}
|
||||
|
||||
// currently disabled on Windows because it requires elevated privilidges, otherwise
|
||||
// keeps returning a single sensor with key "ACPI\\ThermalZone\\TZ00_0" which
|
||||
// doesn't seem to be the CPU sensor or correspond to anything useful when
|
||||
// compared against the temperatures Libre Hardware Monitor reports
|
||||
if runtime.GOOS != "windows" {
|
||||
sensorReadings, err := sensors.SensorsTemperatures()
|
||||
if err == nil {
|
||||
if req.CPUTempSensor != "" {
|
||||
for i := range sensorReadings {
|
||||
if sensorReadings[i].SensorKey == req.CPUTempSensor {
|
||||
info.CPU.TemperatureIsAvailable = true
|
||||
info.CPU.TemperatureC = uint8(sensorReadings[i].Temperature)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !info.CPU.TemperatureIsAvailable {
|
||||
addErr(fmt.Errorf("CPU temperature sensor %s not found", req.CPUTempSensor))
|
||||
}
|
||||
} else if cpuTempSensor := inferCPUTempSensor(sensorReadings); cpuTempSensor != nil {
|
||||
info.CPU.TemperatureIsAvailable = true
|
||||
info.CPU.TemperatureC = uint8(cpuTempSensor.Temperature)
|
||||
}
|
||||
} else {
|
||||
addErr(fmt.Errorf("getting sensor readings: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
filesystems, err := disk.Partitions(false)
|
||||
if err == nil {
|
||||
for _, fs := range filesystems {
|
||||
mpReq, ok := req.Mountpoints[fs.Mountpoint]
|
||||
if ok && mpReq.Hide {
|
||||
continue
|
||||
}
|
||||
|
||||
usage, err := disk.Usage(fs.Mountpoint)
|
||||
if err == nil {
|
||||
mpInfo := MountpointInfo{
|
||||
Path: fs.Mountpoint,
|
||||
Name: mpReq.Name,
|
||||
TotalMB: usage.Total / 1024 / 1024,
|
||||
UsedMB: usage.Used / 1024 / 1024,
|
||||
UsedPercent: uint8(math.Min(usage.UsedPercent, 100)),
|
||||
}
|
||||
|
||||
info.Mountpoints = append(info.Mountpoints, mpInfo)
|
||||
} else {
|
||||
addErr(fmt.Errorf("getting filesystem usage for %s: %v", fs.Mountpoint, err))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addErr(fmt.Errorf("getting filesystems: %v", err))
|
||||
}
|
||||
|
||||
sort.Slice(info.Mountpoints, func(a, b int) bool {
|
||||
return info.Mountpoints[a].UsedPercent > info.Mountpoints[b].UsedPercent
|
||||
})
|
||||
|
||||
return info, errs
|
||||
}
|
||||
|
||||
func inferCPUTempSensor(sensors []sensors.TemperatureStat) *sensors.TemperatureStat {
|
||||
for i := range sensors {
|
||||
switch sensors[i].SensorKey {
|
||||
case
|
||||
"coretemp_package_id_0", // intel / linux
|
||||
"coretemp", // intel / linux
|
||||
"k10temp", // amd / linux
|
||||
"zenpower", // amd / linux
|
||||
"cpu_thermal": // raspberry pi / linux
|
||||
return &sensors[i]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Loading…
Reference in New Issue