feat: implement cross-platform metric collector with gopsutil, rename module to nexusrmm.local/agent

This commit is contained in:
Claude Agent
2026-03-19 12:14:14 +01:00
parent 7c85afe39b
commit 51052261f5
5 changed files with 183 additions and 11 deletions

View File

@@ -1,24 +1,26 @@
module github.com/nexusrmm/agent
go 1.22
go 1.26
require (
github.com/shirou/gopsutil/v4 v4.24.0
github.com/shirou/gopsutil/v3 v3.24.5
google.golang.org/grpc v1.60.0
google.golang.org/protobuf v1.32.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/ebitengine/purego v0.7.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d84 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.8.0 // indirect
github.com/yusufpapurcu/info v0.0.0-20240514213526-8f62d0eb11ac // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect
)

51
Agent/go.sum Normal file
View File

@@ -0,0 +1,51 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/golang/protobuf v1.5.3 h1:KESQyS83zrBXM35gw0xMqGD/8xf9AZf6GR9pWqJBKqw=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2rrLzc1mTrFQ63LlQEbvLSi8aE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7v2A13j3pWY9+33e/9vnzlU7epo6pCKQ=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa60QWvxHNAJo0c1V8AcQ+XO1zNY/e3FFCs=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itlqbqQBLa3VwOU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:3xiWY+VwXwWNwfYXSZR2ysTbLB0WXIM8j3t2nXEd9k4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60ntMRR/VsD2xjcStor2M=
google.golang.org/grpc v1.60.0 h1:6DUVg+gIvjLCktBoNt4d7+fJ/onRuLi1+vyYR5g0gY=
google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqLSIlD9rQ0Drv3KfMAy5fxYvb/dgVzlj0g=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex/HNYcPYe3EkladybiguVstpQQelQR5bkY=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GDeJLau1oL+D3tIQCmnaqTuStpLJ3XZ+T5+wqE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,100 @@
package collector
import (
"time"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/host"
"github.com/shirou/gopsutil/v3/mem"
psnet "github.com/shirou/gopsutil/v3/net"
)
type Metrics struct {
CPUPercent float64
MemoryPercent float64
MemoryTotal uint64
MemoryAvailable uint64
Disks []DiskInfo
Networks []NetInfo
UptimeSeconds float64
}
type DiskInfo struct {
MountPoint string
Total uint64
Free uint64
Filesystem string
}
type NetInfo struct {
Name string
IPAddress string
MAC string
BytesSent uint64
BytesRecv uint64
}
func Collect() (*Metrics, error) {
cpuPercent, err := cpu.Percent(time.Second, false)
if err != nil {
return nil, err
}
memInfo, err := mem.VirtualMemory()
if err != nil {
return nil, err
}
uptime, _ := host.Uptime()
m := &Metrics{
CPUPercent: cpuPercent[0],
MemoryPercent: memInfo.UsedPercent,
MemoryTotal: memInfo.Total,
MemoryAvailable: memInfo.Available,
UptimeSeconds: float64(uptime),
}
// Disks
partitions, _ := disk.Partitions(false)
for _, p := range partitions {
usage, err := disk.Usage(p.Mountpoint)
if err != nil {
continue
}
m.Disks = append(m.Disks, DiskInfo{
MountPoint: p.Mountpoint,
Total: usage.Total,
Free: usage.Free,
Filesystem: p.Fstype,
})
}
// Network
interfaces, _ := psnet.Interfaces()
counters, _ := psnet.IOCounters(true)
counterMap := make(map[string]psnet.IOCountersStat)
for _, c := range counters {
counterMap[c.Name] = c
}
for _, iface := range interfaces {
if len(iface.Addrs) == 0 {
continue
}
ni := NetInfo{
Name: iface.Name,
MAC: iface.HardwareAddr,
}
if len(iface.Addrs) > 0 {
ni.IPAddress = iface.Addrs[0].Addr
}
if c, ok := counterMap[iface.Name]; ok {
ni.BytesSent = c.BytesSent
ni.BytesRecv = c.BytesRecv
}
m.Networks = append(m.Networks, ni)
}
return m, nil
}

View File

@@ -0,0 +1,19 @@
package collector
import "testing"
func TestCollect(t *testing.T) {
metrics, err := Collect()
if err != nil {
t.Fatalf("Collect() error: %v", err)
}
if metrics.CPUPercent < 0 || metrics.CPUPercent > 100 {
t.Errorf("CPU percent out of range: %f", metrics.CPUPercent)
}
if metrics.MemoryTotal == 0 {
t.Error("MemoryTotal should not be 0")
}
if len(metrics.Disks) == 0 {
t.Error("Expected at least one disk")
}
}

View File

@@ -3,7 +3,7 @@ syntax = "proto3";
package nexusrmm;
option csharp_namespace = "NexusRMM.Protos";
option go_package = "github.com/nexusrmm/agent/pkg/proto";
option go_package = "nexusrmm.local/agent/pkg/proto";
// --- Agent Enrollment ---
message EnrollRequest {