@@ -2,13 +2,16 @@ package monitor
22
33import (
44 "runtime"
5+ "strings"
6+ "sync"
57 "time"
68
79 "github.com/cedar2025/xboard-node/internal/nlog"
810 "github.com/shirou/gopsutil/v4/cpu"
911 "github.com/shirou/gopsutil/v4/disk"
1012 "github.com/shirou/gopsutil/v4/load"
1113 "github.com/shirou/gopsutil/v4/mem"
14+ "github.com/shirou/gopsutil/v4/net"
1215)
1316
1417var startTime = time .Now ()
@@ -18,6 +21,9 @@ func init() {
1821 // always returns 0% because it has no prior sample. This throwaway call
1922 // seeds the baseline so subsequent Collect() calls return real values.
2023 cpu .Percent (500 * time .Millisecond , false )
24+
25+ // Seed the network baseline so the first Collect() can compute rates.
26+ collectNetSpeed ()
2127}
2228
2329// Status holds system resource metrics
@@ -36,11 +42,81 @@ type Status struct {
3642 DiskUsed uint64
3743 Goroutines int
3844
45+ // Network speed (bytes/sec), -1 means unavailable (first sample).
46+ NetInSpeed float64
47+ NetOutSpeed float64
48+
3949 // GC metrics (process-wide)
4050 NumGC uint32
4151 LastPauseMS float64
4252}
4353
54+ // netBaseline tracks the previous network counters for rate calculation.
55+ var (
56+ netMu sync.Mutex
57+ netPrevRecv uint64
58+ netPrevSent uint64
59+ netPrevTime time.Time
60+ netHasBase bool
61+ )
62+
63+ // skipInterface returns true for loopback and common virtual interfaces.
64+ func skipInterface (name string ) bool {
65+ lower := strings .ToLower (name )
66+ for _ , prefix := range []string {"lo" , "docker" , "veth" , "br-" , "virbr" , "vnet" , "tun" , "tap" } {
67+ if strings .HasPrefix (lower , prefix ) {
68+ return true
69+ }
70+ }
71+ return false
72+ }
73+
74+ // collectNetSpeed calculates network in/out bytes per second since last call.
75+ // Returns -1, -1 on first call or if counters decreased (reboot).
76+ func collectNetSpeed () (inSpeed , outSpeed float64 ) {
77+ counters , err := net .IOCounters (true ) // per-interface
78+ if err != nil {
79+ nlog .Core ().Debug ("failed to get network counters" , "error" , err )
80+ return - 1 , - 1
81+ }
82+
83+ var totalRecv , totalSent uint64
84+ for _ , c := range counters {
85+ if skipInterface (c .Name ) {
86+ continue
87+ }
88+ totalRecv += c .BytesRecv
89+ totalSent += c .BytesSent
90+ }
91+
92+ now := time .Now ()
93+
94+ netMu .Lock ()
95+ defer netMu .Unlock ()
96+
97+ if ! netHasBase {
98+ netPrevRecv , netPrevSent , netPrevTime , netHasBase = totalRecv , totalSent , now , true
99+ return - 1 , - 1
100+ }
101+
102+ elapsed := now .Sub (netPrevTime ).Seconds ()
103+ if elapsed <= 0 {
104+ return - 1 , - 1
105+ }
106+
107+ // Counter decreased → system reboot or interface reset; reset baseline.
108+ if totalRecv < netPrevRecv || totalSent < netPrevSent {
109+ netPrevRecv , netPrevSent , netPrevTime = totalRecv , totalSent , now
110+ return - 1 , - 1
111+ }
112+
113+ inSpeed = float64 (totalRecv - netPrevRecv ) / elapsed
114+ outSpeed = float64 (totalSent - netPrevSent ) / elapsed
115+
116+ netPrevRecv , netPrevSent , netPrevTime = totalRecv , totalSent , now
117+ return inSpeed , outSpeed
118+ }
119+
44120// Collect gathers current system metrics
45121func Collect () Status {
46122 var s Status
@@ -79,6 +155,8 @@ func Collect() Status {
79155 s .DiskUsed = diskStat .Used
80156 }
81157
158+ s .NetInSpeed , s .NetOutSpeed = collectNetSpeed ()
159+
82160 // GC metrics
83161 var ms runtime.MemStats
84162 runtime .ReadMemStats (& ms )
0 commit comments