Hardware System Info Monitor (CPU/Memory/Disk)
Cross-platform sampler that periodically collects CPU, memory, disk, and network stats for health monitoring.
main.go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
package main
import ( "encoding/json"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/net")
// SystemStats holds aggregated system resource statistics
type SystemStats struct {
Timestamp time.Time `json:"timestamp"`
CPUUsage float64 `json:"cpu_usage_percent"` // Overall CPU usage (%)
MemoryStats struct {
Total uint64 `json:"total_bytes"`
Used uint64 `json:"used_bytes"`
UsedPercent float64 `json:"used_percent"`
} `json:"memory"`
DiskStats []struct {
MountPoint string `json:"mount_point"`
Total uint64 `json:"total_bytes"`
Used uint64 `json:"used_bytes"`
UsedPercent float64 `json:"used_percent"`
} `json:"disk"`
NetworkStats []struct {
Interface string `json:"interface"`
BytesSent uint64 `json:"bytes_sent"`
BytesRecv uint64 `json:"bytes_recv"`
} `json:"network"`
}
// SystemMonitor collects system stats at specified intervals
type SystemMonitor struct {
interval time.Duration // Sampling interval
stopChan chan struct{}
}
// NewSystemMonitor creates a new monitor with sampling interval
func NewSystemMonitor(interval time.Duration) *SystemMonitor {
return &SystemMonitor{
interval: interval,
stopChan: make(chan struct{}),
}
}
// CollectStats gathers a single snapshot of system statistics
func (m *SystemMonitor) CollectStats() (*SystemStats, error) {
stats := &SystemStats{Timestamp: time.Now()}
// 1. Collect CPU usage (percent over 1-second interval)
cpuPercent, err := cpu.Percent(1*time.Second, false)
if err != nil {
return nil, fmt.Errorf("failed to get CPU usage: %v", err)
}
if len(cpuPercent) > 0 {
stats.CPUUsage = cpuPercent[0]
}
// 2. Collect memory statistics
memInfo, err := mem.VirtualMemory()
if err != nil {
return nil, fmt.Errorf("failed to get memory info: %v", err)
}
stats.MemoryStats.Total = memInfo.Total
stats.MemoryStats.Used = memInfo.Used
stats.MemoryStats.UsedPercent = memInfo.UsedPercent
// 3. Collect disk statistics (all mounted partitions)
diskParts, err := disk.Partitions(true)
if err != nil {
return nil, fmt.Errorf("failed to get disk partitions: %v", err)
}
for _, part := range diskParts {
diskInfo, err := disk.Usage(part.Mountpoint)
if err != nil {
log.Printf("Failed to get disk usage for %s: %v", part.Mountpoint, err)
continue
}
stats.DiskStats = append(stats.DiskStats, struct {
MountPoint string `json:"mount_point"`
Total uint64 `json:"total_bytes"`
Used uint64 `json:"used_bytes"`
UsedPercent float64 `json:"used_percent"`
}{
MountPoint: part.Mountpoint,
Total: diskInfo.Total,
Used: diskInfo.Used,
UsedPercent: diskInfo.UsedPercent,
})
}
// 4. Collect network statistics (all interfaces)
netIO, err := net.IOCounters(true)
if err != nil {
return nil, fmt.Errorf("failed to get network stats: %v", err)
}
for _, io := range netIO {
stats.NetworkStats = append(stats.NetworkStats, struct {
Interface string `json:"interface"`
BytesSent uint64 `json:"bytes_sent"`
BytesRecv uint64 `json:"bytes_recv"`
}{
Interface: io.Name,
BytesSent: io.BytesSent,
BytesRecv: io.BytesRecv,
})
}
return stats, nil
}
// Start begins periodic statistics collection and printing
func (m *SystemMonitor) Start() {
log.Printf("System monitor started (sampling interval: %v)", m.interval)
ticker := time.NewTicker(m.interval)
defer ticker.Stop()
for {
select {
case <-m.stopChan:
log.Println("System monitor stopped")
return
case <-ticker.C:
stats, err := m.CollectStats()
if err != nil {
log.Printf("Failed to collect stats: %v", err)
continue
}
// Print stats as pretty-printed JSON
jsonData, err := json.MarshalIndent(stats, "", " ")
if err != nil {
log.Printf("Failed to marshal stats: %v", err)
continue
}
fmt.Println("--- System Stats ---")
fmt.Println(string(jsonData))
}
}
}
// Stop terminates the monitor
func (m *SystemMonitor) Stop() {
close(m.stopChan)
}
// Example Usage
func main() {
if len(os.Args) < 2 {
log.Fatal("Usage: go run main.go <interval-seconds>")
}
intervalSecs := 0
fmt.Sscanf(os.Args[1], "%d", &intervalSecs)
if intervalSecs < 1 {
intervalSecs = 5
}
// Create and start monitor
monitor := NewSystemMonitor(time.Duration(intervalSecs) * time.Second)
go monitor.Start()
// Wait for SIGINT/SIGTERM
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
monitor.Stop()
}How It Works
Cross-platform sampler that periodically collects CPU, memory, disk, and network stats for health monitoring.
Uses system libraries to read metrics, aggregates them at intervals, prints JSON snapshots, and supports graceful stop via signals so the sampler exits cleanly.
Key Concepts
- 1Periodic ticker drives metric collection without busy loops.
- 2Structures capture CPU load, memory usage, disk capacity, and network counters.
- 3Signal handling stops sampling cleanly for long-running agents.
When to Use This Pattern
- Building lightweight node exporters or sidecar monitors.
- Collecting host metrics for dashboards and alerts.
- Baseline performance measurements during testing.
Best Practices
- Choose a sampling interval that balances fidelity and overhead.
- Run with permissions sufficient for reading system stats only.
- Emit metrics in a structured format for parsers such as JSON or Prometheus.
Go Version1.17
Difficultyintermediate
Production ReadyYes
Lines of Code172