Skip to content

Commit 147b2a6

Browse files
authored
Merge branch 'henrygd:main' into feat/build-more-arm
2 parents 2b72baf + 0746680 commit 147b2a6

80 files changed

Lines changed: 7713 additions & 1445 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

agent/battery/battery_linux.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ func GetBatteryStats() (batteryPercent uint8, batteryState uint8, err error) {
8282
return batteryPercent, batteryState, errors.ErrUnsupported
8383
}
8484
paths, err := getBatteryPaths()
85+
if err != nil {
86+
return batteryPercent, batteryState, err
87+
}
8588
if len(paths) == 0 {
8689
return batteryPercent, batteryState, errors.New("no batteries")
8790
}

agent/client.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/fxamacker/cbor/v2"
2121
"github.com/lxzan/gws"
2222
"golang.org/x/crypto/ssh"
23+
"golang.org/x/net/proxy"
2324
)
2425

2526
const (
@@ -104,6 +105,11 @@ func (client *WebSocketClient) getOptions() *gws.ClientOption {
104105
}
105106
client.hubURL.Path = path.Join(client.hubURL.Path, "api/beszel/agent-connect")
106107

108+
// make sure BESZEL_AGENT_ALL_PROXY works (GWS only checks ALL_PROXY)
109+
if val := os.Getenv("BESZEL_AGENT_ALL_PROXY"); val != "" {
110+
os.Setenv("ALL_PROXY", val)
111+
}
112+
107113
client.options = &gws.ClientOption{
108114
Addr: client.hubURL.String(),
109115
TlsConfig: &tls.Config{InsecureSkipVerify: true},
@@ -112,6 +118,9 @@ func (client *WebSocketClient) getOptions() *gws.ClientOption {
112118
"X-Token": []string{client.token},
113119
"X-Beszel": []string{beszel.Version},
114120
},
121+
NewDialer: func() (gws.Dialer, error) {
122+
return proxy.FromEnvironment(), nil
123+
},
115124
}
116125
return client.options
117126
}

agent/disk.go

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,34 @@ type diskDiscovery struct {
3434
ctx fsRegistrationContext
3535
}
3636

37+
// prevDisk stores previous per-device disk counters for a given cache interval
38+
type prevDisk struct {
39+
readBytes uint64
40+
writeBytes uint64
41+
readTime uint64 // cumulative ms spent on reads (from ReadTime)
42+
writeTime uint64 // cumulative ms spent on writes (from WriteTime)
43+
ioTime uint64 // cumulative ms spent doing I/O (from IoTime)
44+
weightedIO uint64 // cumulative weighted ms (queue-depth × ms, from WeightedIO)
45+
readCount uint64 // cumulative read operation count
46+
writeCount uint64 // cumulative write operation count
47+
at time.Time
48+
}
49+
50+
// prevDiskFromCounter creates a prevDisk snapshot from a disk.IOCountersStat at time t.
51+
func prevDiskFromCounter(d disk.IOCountersStat, t time.Time) prevDisk {
52+
return prevDisk{
53+
readBytes: d.ReadBytes,
54+
writeBytes: d.WriteBytes,
55+
readTime: d.ReadTime,
56+
writeTime: d.WriteTime,
57+
ioTime: d.IoTime,
58+
weightedIO: d.WeightedIO,
59+
readCount: d.ReadCount,
60+
writeCount: d.WriteCount,
61+
at: t,
62+
}
63+
}
64+
3765
// parseFilesystemEntry parses a filesystem entry in the format "device__customname"
3866
// Returns the device/filesystem part and the custom name part
3967
func parseFilesystemEntry(entry string) (device, customName string) {
@@ -581,16 +609,29 @@ func (a *Agent) updateDiskIo(cacheTimeMs uint16, systemStats *system.Stats) {
581609
prev, hasPrev := a.diskPrev[cacheTimeMs][name]
582610
if !hasPrev {
583611
// Seed from agent-level fsStats if present, else seed from current
584-
prev = prevDisk{readBytes: stats.TotalRead, writeBytes: stats.TotalWrite, at: stats.Time}
612+
prev = prevDisk{
613+
readBytes: stats.TotalRead,
614+
writeBytes: stats.TotalWrite,
615+
readTime: d.ReadTime,
616+
writeTime: d.WriteTime,
617+
ioTime: d.IoTime,
618+
weightedIO: d.WeightedIO,
619+
readCount: d.ReadCount,
620+
writeCount: d.WriteCount,
621+
at: stats.Time,
622+
}
585623
if prev.at.IsZero() {
586-
prev = prevDisk{readBytes: d.ReadBytes, writeBytes: d.WriteBytes, at: now}
624+
prev = prevDiskFromCounter(d, now)
587625
}
588626
}
589627

590628
msElapsed := uint64(now.Sub(prev.at).Milliseconds())
629+
630+
// Update per-interval snapshot
631+
a.diskPrev[cacheTimeMs][name] = prevDiskFromCounter(d, now)
632+
633+
// Avoid division by zero or clock issues
591634
if msElapsed < 100 {
592-
// Avoid division by zero or clock issues; update snapshot and continue
593-
a.diskPrev[cacheTimeMs][name] = prevDisk{readBytes: d.ReadBytes, writeBytes: d.WriteBytes, at: now}
594635
continue
595636
}
596637

@@ -602,15 +643,38 @@ func (a *Agent) updateDiskIo(cacheTimeMs uint16, systemStats *system.Stats) {
602643
// validate values
603644
if readMbPerSecond > 50_000 || writeMbPerSecond > 50_000 {
604645
slog.Warn("Invalid disk I/O. Resetting.", "name", d.Name, "read", readMbPerSecond, "write", writeMbPerSecond)
605-
// Reset interval snapshot and seed from current
606-
a.diskPrev[cacheTimeMs][name] = prevDisk{readBytes: d.ReadBytes, writeBytes: d.WriteBytes, at: now}
607646
// also refresh agent baseline to avoid future negatives
608647
a.initializeDiskIoStats(ioCounters)
609648
continue
610649
}
611650

612-
// Update per-interval snapshot
613-
a.diskPrev[cacheTimeMs][name] = prevDisk{readBytes: d.ReadBytes, writeBytes: d.WriteBytes, at: now}
651+
// These properties are calculated differently on different platforms,
652+
// but generally represent cumulative time spent doing reads/writes on the device.
653+
// This can surpass 100% if there are multiple concurrent I/O operations.
654+
// Linux kernel docs:
655+
// This is the total number of milliseconds spent by all reads (as
656+
// measured from __make_request() to end_that_request_last()).
657+
// https://www.kernel.org/doc/Documentation/iostats.txt (fields 4, 8)
658+
diskReadTime := utils.TwoDecimals(float64(d.ReadTime-prev.readTime) / float64(msElapsed) * 100)
659+
diskWriteTime := utils.TwoDecimals(float64(d.WriteTime-prev.writeTime) / float64(msElapsed) * 100)
660+
661+
// I/O utilization %: fraction of wall time the device had any I/O in progress (0-100).
662+
diskIoUtilPct := utils.TwoDecimals(float64(d.IoTime-prev.ioTime) / float64(msElapsed) * 100)
663+
664+
// Weighted I/O: queue-depth weighted I/O time, normalized to interval (can exceed 100%).
665+
// Linux kernel field 11: incremented by iops_in_progress × ms_since_last_update.
666+
// Used to display queue depth. Multipled by 100 to increase accuracy of digit truncation (divided by 100 in UI).
667+
diskWeightedIO := utils.TwoDecimals(float64(d.WeightedIO-prev.weightedIO) / float64(msElapsed) * 100)
668+
669+
// r_await / w_await: average time per read/write operation in milliseconds.
670+
// Equivalent to r_await and w_await in iostat.
671+
var rAwait, wAwait float64
672+
if deltaReadCount := d.ReadCount - prev.readCount; deltaReadCount > 0 {
673+
rAwait = utils.TwoDecimals(float64(d.ReadTime-prev.readTime) / float64(deltaReadCount))
674+
}
675+
if deltaWriteCount := d.WriteCount - prev.writeCount; deltaWriteCount > 0 {
676+
wAwait = utils.TwoDecimals(float64(d.WriteTime-prev.writeTime) / float64(deltaWriteCount))
677+
}
614678

615679
// Update global fsStats baseline for cross-interval correctness
616680
stats.Time = now
@@ -620,12 +684,24 @@ func (a *Agent) updateDiskIo(cacheTimeMs uint16, systemStats *system.Stats) {
620684
stats.DiskWritePs = writeMbPerSecond
621685
stats.DiskReadBytes = diskIORead
622686
stats.DiskWriteBytes = diskIOWrite
687+
stats.DiskIoStats[0] = diskReadTime
688+
stats.DiskIoStats[1] = diskWriteTime
689+
stats.DiskIoStats[2] = diskIoUtilPct
690+
stats.DiskIoStats[3] = rAwait
691+
stats.DiskIoStats[4] = wAwait
692+
stats.DiskIoStats[5] = diskWeightedIO
623693

624694
if stats.Root {
625695
systemStats.DiskReadPs = stats.DiskReadPs
626696
systemStats.DiskWritePs = stats.DiskWritePs
627697
systemStats.DiskIO[0] = diskIORead
628698
systemStats.DiskIO[1] = diskIOWrite
699+
systemStats.DiskIoStats[0] = diskReadTime
700+
systemStats.DiskIoStats[1] = diskWriteTime
701+
systemStats.DiskIoStats[2] = diskIoUtilPct
702+
systemStats.DiskIoStats[3] = rAwait
703+
systemStats.DiskIoStats[4] = wAwait
704+
systemStats.DiskIoStats[5] = diskWeightedIO
629705
}
630706
}
631707
}

agent/gpu_amd_linux.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ func (gm *GPUManager) updateAmdGpuData(cardPath string) bool {
156156
func readSysfsFloat(path string) (float64, error) {
157157
val, err := utils.ReadStringFileLimited(path, 64)
158158
if err != nil {
159+
slog.Debug("Failed to read sysfs value", "path", path, "error", err)
159160
return 0, err
160161
}
161162
return strconv.ParseFloat(val, 64)

agent/smart.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,6 +1118,9 @@ func (sm *SmartManager) parseSmartForNvme(output []byte) (bool, int) {
11181118
smartData.SerialNumber = data.SerialNumber
11191119
smartData.FirmwareVersion = data.FirmwareVersion
11201120
smartData.Capacity = data.UserCapacity.Bytes
1121+
if smartData.Capacity == 0 {
1122+
smartData.Capacity = data.NVMeTotalCapacity
1123+
}
11211124
if smartData.Capacity == 0 && (runtime.GOOS == "darwin" || sm.darwinNvmeProvider != nil) {
11221125
smartData.Capacity = sm.lookupDarwinNvmeCapacity(data.SerialNumber)
11231126
}

agent/system.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"os"
99
"runtime"
1010
"strings"
11-
"time"
1211

1312
"github.com/henrygd/beszel"
1413
"github.com/henrygd/beszel/agent/battery"
@@ -23,13 +22,6 @@ import (
2322
"github.com/shirou/gopsutil/v4/mem"
2423
)
2524

26-
// prevDisk stores previous per-device disk counters for a given cache interval
27-
type prevDisk struct {
28-
readBytes uint64
29-
writeBytes uint64
30-
at time.Time
31-
}
32-
3325
// Sets initial / non-changing values about the host system
3426
func (a *Agent) refreshSystemDetails() {
3527
a.systemInfo.AgentVersion = beszel.Version

agent/utils/utils.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
// Package utils provides utility functions for the agent.
12
package utils
23

34
import (
5+
"fmt"
46
"io"
57
"math"
68
"os"
@@ -68,6 +70,9 @@ func ReadStringFileLimited(path string, maxSize int) (string, error) {
6870
if err != nil && err != io.EOF {
6971
return "", err
7072
}
73+
if n < 0 {
74+
return "", fmt.Errorf("%s returned negative bytes: %d", path, n)
75+
}
7176
return strings.TrimSpace(string(buf[:n])), nil
7277
}
7378

beszel.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import "github.com/blang/semver"
66

77
const (
88
// Version is the current version of the application.
9-
Version = "0.18.6"
9+
Version = "0.18.7"
1010
// AppName is the name of the application.
1111
AppName = "beszel"
1212
)

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ require (
1010
github.com/gliderlabs/ssh v0.3.8
1111
github.com/google/uuid v1.6.0
1212
github.com/lxzan/gws v1.9.1
13-
github.com/nicholas-fedor/shoutrrr v0.14.1
13+
github.com/nicholas-fedor/shoutrrr v0.14.3
1414
github.com/pocketbase/dbx v1.12.0
15-
github.com/pocketbase/pocketbase v0.36.7
15+
github.com/pocketbase/pocketbase v0.36.8
1616
github.com/shirou/gopsutil/v4 v4.26.3
1717
github.com/spf13/cast v1.10.0
1818
github.com/spf13/cobra v1.10.2
@@ -64,5 +64,5 @@ require (
6464
modernc.org/libc v1.70.0 // indirect
6565
modernc.org/mathutil v1.7.1 // indirect
6666
modernc.org/memory v1.11.0 // indirect
67-
modernc.org/sqlite v1.46.2 // indirect
67+
modernc.org/sqlite v1.48.0 // indirect
6868
)

go.sum

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
8585
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
8686
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
8787
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
88-
github.com/nicholas-fedor/shoutrrr v0.14.1 h1:6sx4cJNfNuUtD6ygGlB0dqcCQ+abfsUh+b+6jgujf6A=
89-
github.com/nicholas-fedor/shoutrrr v0.14.1/go.mod h1:U7IywBkLpBV7rgn8iLbQ9/LklJG1gm24bFv5cXXsDKs=
88+
github.com/nicholas-fedor/shoutrrr v0.14.3 h1:aBX2iw9a7jl5wfHd3bi9LnS5ucoYIy6KcLH9XVF+gig=
89+
github.com/nicholas-fedor/shoutrrr v0.14.3/go.mod h1:U7IywBkLpBV7rgn8iLbQ9/LklJG1gm24bFv5cXXsDKs=
9090
github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
9191
github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
9292
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
@@ -96,17 +96,15 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
9696
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
9797
github.com/pocketbase/dbx v1.12.0 h1:/oLErM+A0b4xI0PWTGPqSDVjzix48PqI/bng2l0PzoA=
9898
github.com/pocketbase/dbx v1.12.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
99-
github.com/pocketbase/pocketbase v0.36.7 h1:MrViB7BptPYrf2Nt25pJEYBqUdFjuhRKu1p5GTrkvPA=
100-
github.com/pocketbase/pocketbase v0.36.7/go.mod h1:qX4HuVjoKXtEg41fSJVM0JLfGWXbBmHxVv/FaE446r4=
99+
github.com/pocketbase/pocketbase v0.36.8 h1:gCNqoesZ44saYOD3J7edhi5nDwUWKyQG7boM/kVwz2c=
100+
github.com/pocketbase/pocketbase v0.36.8/go.mod h1:OY4WaXbP0WnF/EXoBbboWJK+ZSZ1A85tiA0sjrTKxTA=
101101
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
102102
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
103103
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
104104
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
105105
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
106106
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
107107
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
108-
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
109-
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
110108
github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc=
111109
github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
112110
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
@@ -199,8 +197,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
199197
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
200198
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
201199
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
202-
modernc.org/sqlite v1.46.2 h1:gkXQ6R0+AjxFC/fTDaeIVLbNLNrRoOK7YYVz5BKhTcE=
203-
modernc.org/sqlite v1.46.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
200+
modernc.org/sqlite v1.48.0 h1:ElZyLop3Q2mHYk5IFPPXADejZrlHu7APbpB0sF78bq4=
201+
modernc.org/sqlite v1.48.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
204202
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
205203
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
206204
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

0 commit comments

Comments
 (0)