Building System Utilities in Go
Introduction
System utilities are essential tools for system administration and automation. This guide covers building robust system utilities in Go.
Go’s simplicity and performance make it ideal for building system utilities that are fast, reliable, and easy to distribute.
System Utility Basics
Simple Utility
package main
import (
"flag"
"fmt"
"os"
)
// DiskUsage calculates disk usage
func DiskUsage(path string) (int64, error) {
var size int64
err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
size += info.Size()
}
return nil
})
return size, err
}
// Main function
func main() {
path := flag.String("path", ".", "Path to analyze")
flag.Parse()
size, err := DiskUsage(*path)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Printf("Total size: %d bytes (%.2f MB)\n", size, float64(size)/1024/1024)
}
Good: Proper System Utility Implementation
package main
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"time"
)
// SystemUtility provides base functionality for system utilities
type SystemUtility struct {
name string
version string
logger *log.Logger
}
// NewSystemUtility creates a new system utility
func NewSystemUtility(name, version string) *SystemUtility {
return &SystemUtility{
name: name,
version: version,
logger: log.New(os.Stderr, fmt.Sprintf("[%s] ", name), log.LstdFlags),
}
}
// Run runs the utility
func (su *SystemUtility) Run(ctx context.Context, fn func(context.Context) error) error {
// Handle signals
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
done := make(chan error, 1)
go func() {
done <- fn(ctx)
}()
select {
case sig := <-sigChan:
su.logger.Printf("Received signal: %v", sig)
return fmt.Errorf("interrupted")
case err := <-done:
return err
case <-ctx.Done():
return ctx.Err()
}
}
// Log logs a message
func (su *SystemUtility) Log(msg string) {
su.logger.Println(msg)
}
// Logf logs a formatted message
func (su *SystemUtility) Logf(format string, args ...interface{}) {
su.logger.Printf(format, args...)
}
// SystemMonitor monitors system resources
type SystemMonitor struct {
utility *SystemUtility
interval time.Duration
}
// NewSystemMonitor creates a new system monitor
func NewSystemMonitor(utility *SystemUtility, interval time.Duration) *SystemMonitor {
return &SystemMonitor{
utility: utility,
interval: interval,
}
}
// Monitor monitors system resources
func (sm *SystemMonitor) Monitor(ctx context.Context) error {
ticker := time.NewTicker(sm.interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
sm.checkResources()
}
}
}
// checkResources checks system resources
func (sm *SystemMonitor) checkResources() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
sm.utility.Logf("Memory: %d MB, Goroutines: %d\n",
m.Alloc/1024/1024, runtime.NumGoroutine())
}
// FileWatcher watches for file changes
type FileWatcher struct {
utility *SystemUtility
path string
handler func(string)
}
// NewFileWatcher creates a new file watcher
func NewFileWatcher(utility *SystemUtility, path string, handler func(string)) *FileWatcher {
return &FileWatcher{
utility: utility,
path: path,
handler: handler,
}
}
// Watch watches for file changes
func (fw *FileWatcher) Watch(ctx context.Context) error {
lastMod := time.Time{}
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
info, err := os.Stat(fw.path)
if err != nil {
fw.utility.Logf("Error: %v\n", err)
continue
}
if info.ModTime().After(lastMod) {
lastMod = info.ModTime()
fw.handler(fw.path)
}
}
}
}
// Example usage
func UtilityExample() {
utility := NewSystemUtility("myutil", "1.0.0")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
monitor := NewSystemMonitor(utility, 2*time.Second)
if err := utility.Run(ctx, monitor.Monitor); err != nil {
utility.Logf("Error: %v\n", err)
os.Exit(1)
}
}
Bad: Improper System Utility
package main
// BAD: No error handling
func BadUtility() {
// No error handling
// No logging
// No signal handling
}
// BAD: No configuration
func BadNoConfig() {
// Hardcoded values
// No flags
// No environment variables
}
// BAD: No graceful shutdown
func BadNoShutdown() {
// No signal handling
// No cleanup
}
Problems:
- No error handling
- No configuration
- No graceful shutdown
- No logging
Common System Utilities
Process Manager
package main
// ProcessManager manages system processes
type ProcessManager struct {
utility *SystemUtility
}
// NewProcessManager creates a new process manager
func NewProcessManager(utility *SystemUtility) *ProcessManager {
return &ProcessManager{utility: utility}
}
// ListProcesses lists running processes
func (pm *ProcessManager) ListProcesses() error {
// Implementation
return nil
}
// KillProcess kills a process
func (pm *ProcessManager) KillProcess(pid int) error {
// Implementation
return nil
}
Log Analyzer
package main
// LogAnalyzer analyzes log files
type LogAnalyzer struct {
utility *SystemUtility
}
// NewLogAnalyzer creates a new log analyzer
func NewLogAnalyzer(utility *SystemUtility) *LogAnalyzer {
return &LogAnalyzer{utility: utility}
}
// AnalyzeLog analyzes a log file
func (la *LogAnalyzer) AnalyzeLog(filename string) error {
// Implementation
return nil
}
Best Practices
1. Handle Signals
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
2. Provide Configuration
flag.String("config", "", "Config file")
3. Log Operations
utility.Logf("Operation completed\n")
4. Handle Errors
if err != nil {
return fmt.Errorf("operation failed: %w", err)
}
Common Pitfalls
1. No Signal Handling
Always handle signals.
2. No Configuration
Provide configuration options.
3. No Logging
Always log operations.
4. No Error Handling
Handle all errors.
Resources
Summary
Building system utilities requires care. Key takeaways:
- Handle signals gracefully
- Provide configuration options
- Log all operations
- Handle errors properly
- Test thoroughly
- Document usage
- Distribute easily
By mastering system utility development, you can build powerful tools.
Comments