Skip to main content

Overview

gopsutil provides a unified API across different operating systems while handling platform-specific implementations internally. This guide shows you how to work with cross-platform code and handle platform differences in your applications.

Understanding Platform-Specific Files

gopsutil uses Go’s build tag system to compile different implementations for each platform. Files are organized with platform-specific suffixes:
cpu/
├── cpu.go                    // Common types and interfaces
├── cpu_linux.go              // Linux implementation
├── cpu_darwin.go             // macOS implementation  
├── cpu_windows.go            // Windows implementation
├── cpu_freebsd.go            // FreeBSD implementation
└── cpu_fallback.go           // Unsupported platforms

Build Tags

Platform-specific files use build tags at the top:
cpu_linux.go
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux

package cpu
cpu_fallback.go
// SPDX-License-Identifier: BSD-3-Clause
//go:build !darwin && !linux && !freebsd && !openbsd && !windows

package cpu

Writing Cross-Platform Applications

Use Context-Aware Functions

Always prefer functions that accept context.Context for better control:
import (
    "context"
    "time"
    "github.com/shirou/gopsutil/v4/cpu"
)

func getCPUInfo() error {
    // Create a context with timeout
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    // Use context-aware function
    info, err := cpu.InfoWithContext(ctx)
    if err != nil {
        return err
    }
    
    // Process info...
    return nil
}

Handle Platform Differences

Some fields are platform-specific. Check availability before using them:
import (
    "runtime"
    "github.com/shirou/gopsutil/v4/mem"
)

func getMemoryDetails(ctx context.Context) error {
    v, err := mem.VirtualMemoryWithContext(ctx)
    if err != nil {
        return err
    }
    
    // These fields are available on all platforms
    fmt.Printf("Total: %d\n", v.Total)
    fmt.Printf("Available: %d\n", v.Available)
    fmt.Printf("Used: %d\n", v.Used)
    
    // Platform-specific fields
    if runtime.GOOS == "linux" {
        fmt.Printf("Buffers: %d\n", v.Buffers)
        fmt.Printf("Cached: %d\n", v.Cached)
        fmt.Printf("Slab: %d\n", v.Slab)
    }
    
    if runtime.GOOS == "darwin" || runtime.GOOS == "freebsd" {
        fmt.Printf("Active: %d\n", v.Active)
        fmt.Printf("Inactive: %d\n", v.Inactive)
        fmt.Printf("Wired: %d\n", v.Wired)
    }
    
    return nil
}

Runtime Detection

Use runtime.GOOS to detect the platform at runtime:
import (
    "runtime"
    "github.com/shirou/gopsutil/v4/cpu"
)

func displayCPUInfo(ctx context.Context) error {
    times, err := cpu.TimesWithContext(ctx, false)
    if err != nil {
        return err
    }
    
    for _, t := range times {
        fmt.Printf("User: %.2f\n", t.User)
        fmt.Printf("System: %.2f\n", t.System)
        fmt.Printf("Idle: %.2f\n", t.Idle)
        
        // Linux-specific fields
        if runtime.GOOS == "linux" {
            fmt.Printf("Iowait: %.2f\n", t.Iowait)
            fmt.Printf("Irq: %.2f\n", t.Irq)
            fmt.Printf("Softirq: %.2f\n", t.Softirq)
            fmt.Printf("Steal: %.2f\n", t.Steal)
            fmt.Printf("Guest: %.2f\n", t.Guest)
        }
    }
    
    return nil
}

Handling Unsupported Features

Check for Not Implemented Errors

On unsupported platforms, functions return common.ErrNotImplementedError:
import (
    "errors"
    "github.com/shirou/gopsutil/v4/cpu"
    "github.com/shirou/gopsutil/v4/internal/common"
)

func tryGetCPUInfo(ctx context.Context) {
    info, err := cpu.InfoWithContext(ctx)
    if err != nil {
        if errors.Is(err, common.ErrNotImplementedError) {
            fmt.Println("CPU info not available on this platform")
            return
        }
        // Handle other errors
        log.Fatal(err)
    }
    
    // Use info...
}

Graceful Degradation

Design your application to degrade gracefully when features aren’t available:
type SystemStats struct {
    CPUCount    int
    CPUModel    string // May be empty on some platforms
    MemoryTotal uint64
    Platform    string
}

func getSystemStats(ctx context.Context) (*SystemStats, error) {
    stats := &SystemStats{
        Platform: runtime.GOOS,
    }
    
    // CPU count is available on all platforms
    count, err := cpu.CountsWithContext(ctx, true)
    if err != nil {
        return nil, err
    }
    stats.CPUCount = count
    
    // CPU info may not be available on all platforms
    info, err := cpu.InfoWithContext(ctx)
    if err == nil && len(info) > 0 {
        stats.CPUModel = info[0].ModelName
    }
    
    // Memory total is available on all platforms
    v, err := mem.VirtualMemoryWithContext(ctx)
    if err != nil {
        return nil, err
    }
    stats.MemoryTotal = v.Total
    
    return stats, nil
}

Testing Across Platforms

Use Build Tags in Tests

Write platform-specific tests using build tags:
cpu_linux_test.go
//go:build linux

package mypackage

import (
    "testing"
    "github.com/shirou/gopsutil/v4/cpu"
)

func TestLinuxSpecificCPU(t *testing.T) {
    times, err := cpu.Times(false)
    if err != nil {
        t.Fatal(err)
    }
    
    // Test Linux-specific fields
    if times[0].Iowait < 0 {
        t.Error("Iowait should be non-negative")
    }
}

Mock Platform Detection

For unit tests, consider abstracting platform detection:
type PlatformDetector interface {
    GetOS() string
}

type RuntimePlatform struct{}

func (r RuntimePlatform) GetOS() string {
    return runtime.GOOS
}

type MockPlatform struct {
    OS string
}

func (m MockPlatform) GetOS() string {
    return m.OS
}

Environment Variables for Testing

gopsutil supports environment variables to override system paths (useful for testing):

HOST_PROC

Override /proc directory (Linux)

HOST_SYS

Override /sys directory (Linux)

HOST_ETC

Override /etc directory

HOST_VAR

Override /var directory
import (
    "context"
    "github.com/shirou/gopsutil/v4/common"
    "github.com/shirou/gopsutil/v4/cpu"
)

func testWithCustomProc() {
    // Create context with custom environment
    env := common.EnvMap{
        common.HostProcEnvKey: "/tmp/test/proc",
        common.HostSysEnvKey:  "/tmp/test/sys",
    }
    ctx := context.WithValue(context.Background(), common.EnvKey, env)
    
    // This will read from /tmp/test/proc instead of /proc
    times, err := cpu.TimesWithContext(ctx, false)
    // ...
}

Best Practices

1

Always use context-aware functions

Use *WithContext variants for better control and cancellation support.
2

Check runtime.GOOS for platform logic

Use runtime detection for conditional platform-specific code.
3

Handle ErrNotImplementedError

Gracefully handle unsupported features on different platforms.
4

Test on target platforms

Don’t assume behavior - test on actual target operating systems.
5

Document platform requirements

Clearly document which platforms your code supports.

Platform Support Matrix

FeatureLinuxmacOSWindowsFreeBSDOthers
CPU Info⚠️
CPU Times
Memory Info
Disk Usage
Process Info⚠️
Network Stats⚠️
✅ Fully supported | ⚠️ Partial support | ❌ Not supported

Error Handling

Learn how to handle errors across platforms

Performance

Optimize performance for each platform

Build docs developers (and LLMs) love