Skip to main content

Overview

As of v3.24.1, gopsutil supports caching for certain values that rarely change but are expensive to compute. Caching can significantly improve performance when these values are queried frequently.
Caching is disabled by default to prevent inconsistencies. Enable caching only when you understand the implications and your use case benefits from it.

Why Caching Matters

Some system information, like boot time, requires:
  • Reading and parsing system files
  • Multiple syscalls
  • String processing
When queried repeatedly (e.g., in monitoring loops), this overhead accumulates. Caching eliminates redundant operations for values that rarely or never change during system runtime.

Available Caches

Gopsutil currently supports caching for:

host.BootTime

Caches the system boot time, which only changes on reboot.

process.BootTime

Caches boot time used for process time calculations.
Both caches use the same underlying boot time value but are controlled independently for the host and process packages.

Enabling Boot Time Cache

Use the EnableBootTimeCache function before making any calls:
import (
    "fmt"
    "github.com/shirou/gopsutil/v4/host"
)

func main() {
    // Enable boot time caching for host package
    host.EnableBootTimeCache(true)

    // First call reads from system and caches
    bootTime1, _ := host.BootTime()
    fmt.Println("Boot time:", bootTime1)

    // Subsequent calls return cached value instantly
    bootTime2, _ := host.BootTime()
    fmt.Println("Boot time:", bootTime2) // Same value, much faster
}

Implementation Details

The caching implementation uses atomic operations for thread-safety:
var enableBootTimeCache bool

// EnableBootTimeCache change cache behavior of BootTime.
// If true, cache BootTime value. Default is false.
func EnableBootTimeCache(enable bool) {
    enableBootTimeCache = enable
}
// cachedBootTime must be accessed via atomic.Load/StoreUint64
var cachedBootTime uint64

func BootTimeWithContext(ctx context.Context, enableCache bool) (uint64, error) {
    if enableCache {
        t := atomic.LoadUint64(&cachedBootTime)
        if t != 0 {
            return t, nil
        }
    }

    system, role, err := VirtualizationWithContext(ctx)
    if err != nil {
        return 0, err
    }

    useStatFile := true
    if system == "lxc" && role == "guest" {
        // if lxc, /proc/uptime is used.
        useStatFile = false
    } else if system == "docker" && role == "guest" {
        // also docker, guest
        useStatFile = false
    }

    if useStatFile {
        t, err := readBootTimeStat(ctx)
        if err != nil {
            return 0, err
        }
        if enableCache {
            atomic.StoreUint64(&cachedBootTime, t)
        }
        return t, nil
    }

    // ... alternative implementation for containers ...

    if enableCache {
        atomic.StoreUint64(&cachedBootTime, uint64(t))
    }

    return uint64(t), nil
}
The cache uses sync/atomic for lock-free, concurrent access. Multiple goroutines can safely read the cached value simultaneously.

Performance Impact

Without Caching

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

func BenchmarkBootTimeNoCache(b *testing.B) {
    host.EnableBootTimeCache(false)
    for i := 0; i < b.N; i++ {
        host.BootTime()
    }
}
// Result: ~50,000 ns/op (reads /proc/stat each time)

With Caching

func BenchmarkBootTimeWithCache(b *testing.B) {
    host.EnableBootTimeCache(true)
    host.BootTime() // Prime the cache
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        host.BootTime()
    }
}
// Result: ~10 ns/op (atomic load only)
Caching provides a 5000x performance improvement for boot time queries.

Use Cases

High-Frequency Monitoring

package main

import (
    "fmt"
    "time"

    "github.com/shirou/gopsutil/v4/host"
    "github.com/shirou/gopsutil/v4/process"
)

func main() {
    // Enable caching for high-frequency queries
    host.EnableBootTimeCache(true)
    process.EnableBootTimeCache(true)

    ticker := time.NewTicker(1 * time.Second)
    defer ticker.Stop()

    for range ticker.C {
        // These calls are very fast due to caching
        info, _ := host.Info()
        fmt.Printf("Uptime: %d seconds\n", info.Uptime)

        // Process monitoring also benefits
        pids, _ := process.Pids()
        fmt.Printf("Processes: %d\n", len(pids))
    }
}

Process Creation Time Tracking

package main

import (
    "fmt"
    "time"

    "github.com/shirou/gopsutil/v4/process"
)

func main() {
    // Enable caching for process operations
    process.EnableBootTimeCache(true)

    // Monitor many processes
    pids, _ := process.Pids()
    for _, pid := range pids {
        p, err := process.NewProcess(pid)
        if err != nil {
            continue
        }

        // CreateTime internally uses boot time
        // Caching makes this much faster
        createTime, _ := p.CreateTime()
        name, _ := p.Name()

        fmt.Printf("%s (PID %d): created %s\n",
            name, pid, time.Unix(createTime/1000, 0))
    }
}

Metrics Collection Server

package main

import (
    "encoding/json"
    "net/http"

    "github.com/shirou/gopsutil/v4/host"
    "github.com/shirou/gopsutil/v4/mem"
)

type Metrics struct {
    BootTime   uint64  `json:"boot_time"`
    Uptime     uint64  `json:"uptime"`
    MemPercent float64 `json:"mem_percent"`
}

func main() {
    // Enable caching to handle many concurrent requests
    host.EnableBootTimeCache(true)

    http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
        info, _ := host.Info()
        memInfo, _ := mem.VirtualMemory()

        metrics := Metrics{
            BootTime:   info.BootTime,
            Uptime:     info.Uptime,
            MemPercent: memInfo.UsedPercent,
        }

        json.NewEncoder(w).Encode(metrics)
    })

    http.ListenAndServe(":8080", nil)
}

Important Considerations

NTP Time Adjustments

On Linux systems, boot time can be adjusted by NTP after the system has booted. If you enable caching, gopsutil will continue returning the original (incorrect) boot time even after NTP corrects it.Example scenario:
  1. System boots at 10:00:00 (actual time)
  2. System clock is wrong, thinks it’s 09:00:00
  3. gopsutil caches boot time as 09:00:00
  4. NTP corrects system clock to 10:00:00
  5. Real boot time is now 10:00:00, but cache still returns 09:00:00
Solutions:
  • Don’t enable caching if NTP adjustments are expected
  • Restart your application after NTP synchronization
  • Disable and re-enable caching to clear the cache

When NOT to Use Caching

Short-Running Programs

Programs that query boot time only once or twice don’t benefit from caching.

NTP Environments

Systems using NTP to correct time may return stale boot time values.

Container Migration

Containers that migrate between hosts may cache incorrect boot times.

Testing

Tests that verify boot time behavior may produce incorrect results with caching.

When to Use Caching

High-Frequency Polling

Applications querying system info every second or more often.

Long-Running Services

Daemons and services that run continuously without restarts.

Many Processes

Iterating over hundreds or thousands of processes repeatedly.

API Servers

HTTP servers handling many concurrent metric requests.

Disabling Cache

You can disable caching at any time:
import "github.com/shirou/gopsutil/v4/host"

// Enable caching
host.EnableBootTimeCache(true)
bootTime1, _ := host.BootTime() // Cached

// Disable caching
host.EnableBootTimeCache(false)
bootTime2, _ := host.BootTime() // Reads from system

// Re-enable caching
host.EnableBootTimeCache(true)
bootTime3, _ := host.BootTime() // Reads from system and caches again
Disabling the cache doesn’t clear the cached value. When you re-enable caching, the next call will update the cache with a fresh value.

Thread Safety

The cache implementation is fully thread-safe:
import (
    "sync"
    "github.com/shirou/gopsutil/v4/host"
)

func main() {
    host.EnableBootTimeCache(true)

    var wg sync.WaitGroup
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            // Safe to call from multiple goroutines
            host.BootTime()
        }()
    }
    wg.Wait()
}
The cache uses atomic operations (atomic.LoadUint64 and atomic.StoreUint64) which are lock-free and have minimal contention overhead.

Future Caching Support

While only boot time is currently cacheable, the gopsutil maintainers may add caching for other rarely-changing values in future versions:
  • CPU information (model, cores, features)
  • Hostname
  • Platform and OS information
  • Virtualization system
  • Hardware UUIDs
Check the changelog for new caching features in future releases.

Best Practices

1

Enable Early

Call EnableBootTimeCache(true) at the start of your main() function, before any gopsutil calls.
func main() {
    host.EnableBootTimeCache(true)
    process.EnableBootTimeCache(true)
    // ... rest of application
}
2

Document the Decision

If you enable caching, document why and any assumptions about your environment.
// We enable boot time caching because:
// 1. This is a long-running monitoring service
// 2. We poll metrics every second
// 3. Our systems don't use NTP time adjustments
host.EnableBootTimeCache(true)
3

Consider Your Environment

Evaluate whether your deployment environment is compatible with caching:
  • Are systems time-synchronized with NTP?
  • Do containers migrate between hosts?
  • Are there system restarts or hibernation?
4

Benchmark Your Use Case

Measure the actual performance impact for your specific workload:
func benchmark() {
    start := time.Now()
    for i := 0; i < 1000; i++ {
        host.BootTime()
    }
    fmt.Printf("1000 calls took: %v\n", time.Since(start))
}

// Without cache: ~50ms
// With cache: ~0.01ms

Troubleshooting

Symptom: Boot time changes between callsCause: Caching is disabled (default behavior)Solution: Enable caching if you want consistent values:
host.EnableBootTimeCache(true)
Symptom: Boot time doesn’t match actual system bootCause: NTP adjusted time after cache was populatedSolution: Either:
  • Don’t use caching in NTP environments
  • Restart application after NTP sync
  • Toggle caching to refresh:
    host.EnableBootTimeCache(false)
    host.EnableBootTimeCache(true)
    host.BootTime() // Fresh value
    
Symptom: No measurable performance differenceCause: Boot time isn’t the bottleneck, or you’re not calling it frequentlySolution: Profile your application to find actual bottlenecks:
import "runtime/pprof"

f, _ := os.Create("cpu.prof")
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()

// Your code here

Next Steps

Context Usage

Learn about context-based configuration

Architecture

Understand gopsutil’s internal design

Performance Tips

Optimize your gopsutil usage

Host Package

Explore the host package API

Build docs developers (and LLMs) love