Skip to main content
โšก Calmops

Building System Utilities in Go

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