diff --git a/README.md b/README.md index 2cbdc71a3..6fe9cc347 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,17 @@ As of v3.23.6, it is now possible to pass a path location using `context`: impor First priority is given to the value set in `context`, then the value from the environment variable, and finally the default location. +### Caching + +As of v3.24.1, it is now possible to cached some values. These values default to false, not cached. + +Be very careful that enabling the cache may cause inconsistencies. For example, if you enable caching of boottime on Linux, be aware that unintended values may be returned if [the boottime is changed by NTP after booted](https://github.com/shirou/gopsutil/issues/1070#issuecomment-842512782). + +- `host` + - EnableBootTimeCache +- `process` + - EnableBootTimeCache + ## Documentation See https://pkg.go.dev/github.com/shirou/gopsutil/v3 or https://godocs.io/github.com/shirou/gopsutil/v3 diff --git a/host/host.go b/host/host.go index c7e84e3a5..ee9486369 100644 --- a/host/host.go +++ b/host/host.go @@ -62,6 +62,13 @@ func (t TemperatureStat) String() string { return string(s) } +var enableBootTimeCache bool + +// EnableBootTimeCache change cache behavior of BootTime. If true, cache BootTime value. Default is false. +func EnableBootTimeCache(enable bool) { + enableBootTimeCache = enable +} + func Info() (*InfoStat, error) { return InfoWithContext(context.Background()) } diff --git a/host/host_bsd.go b/host/host_bsd.go index 67ae900bc..f9a296148 100644 --- a/host/host_bsd.go +++ b/host/host_bsd.go @@ -14,16 +14,20 @@ import ( var cachedBootTime uint64 func BootTimeWithContext(ctx context.Context) (uint64, error) { - t := atomic.LoadUint64(&cachedBootTime) - if t != 0 { - return t, nil + if enableBootTimeCache { + t := atomic.LoadUint64(&cachedBootTime) + if t != 0 { + return t, nil + } } tv, err := unix.SysctlTimeval("kern.boottime") if err != nil { return 0, err } - atomic.StoreUint64(&cachedBootTime, uint64(tv.Sec)) + if enableBootTimeCache { + atomic.StoreUint64(&cachedBootTime, uint64(tv.Sec)) + } return uint64(tv.Sec), nil } diff --git a/host/host_linux.go b/host/host_linux.go index 0618a6967..86044d3d0 100644 --- a/host/host_linux.go +++ b/host/host_linux.go @@ -71,7 +71,7 @@ func numProcs(ctx context.Context) (uint64, error) { } func BootTimeWithContext(ctx context.Context) (uint64, error) { - return common.BootTimeWithContext(ctx) + return common.BootTimeWithContext(ctx, enableBootTimeCache) } func UptimeWithContext(ctx context.Context) (uint64, error) { diff --git a/host/host_test.go b/host/host_test.go index 0aa092598..d3b75859e 100644 --- a/host/host_test.go +++ b/host/host_test.go @@ -195,3 +195,17 @@ func TestPlatformInformation(t *testing.T) { t.Logf("PlatformInformation(): %v, %v, %v", platform, family, version) } + +func BenchmarkBootTimeWithCache(b *testing.B) { + EnableBootTimeCache(true) + for i := 0; i < b.N; i++ { + BootTime() + } +} + +func BenchmarkBootTimeWithoutCache(b *testing.B) { + EnableBootTimeCache(false) + for i := 0; i < b.N; i++ { + BootTime() + } +} diff --git a/host/host_windows.go b/host/host_windows.go index 1fe0551b3..b83ad6db1 100644 --- a/host/host_windows.go +++ b/host/host_windows.go @@ -127,16 +127,20 @@ func uptimeMillis() (uint64, error) { var cachedBootTime uint64 func BootTimeWithContext(ctx context.Context) (uint64, error) { - t := atomic.LoadUint64(&cachedBootTime) - if t != 0 { - return t, nil + if enableBootTimeCache { + t := atomic.LoadUint64(&cachedBootTime) + if t != 0 { + return t, nil + } } up, err := uptimeMillis() if err != nil { return 0, err } - t = uint64((time.Duration(timeSinceMillis(up)) * time.Millisecond).Seconds()) - atomic.StoreUint64(&cachedBootTime, t) + t := uint64((time.Duration(timeSinceMillis(up)) * time.Millisecond).Seconds()) + if enableBootTimeCache { + atomic.StoreUint64(&cachedBootTime, t) + } return t, nil } diff --git a/internal/common/common_linux.go b/internal/common/common_linux.go index a644687ba..b08cf33ae 100644 --- a/internal/common/common_linux.go +++ b/internal/common/common_linux.go @@ -12,10 +12,14 @@ import ( "strconv" "strings" "sync" + "sync/atomic" "syscall" "time" ) +// cachedBootTime must be accessed via atomic.Load/StoreUint64 +var cachedBootTime uint64 + func DoSysctrl(mib string) ([]string, error) { cmd := exec.Command("sysctl", "-n", mib) cmd.Env = getSysctrlEnv(os.Environ()) @@ -56,7 +60,14 @@ func NumProcsWithContext(ctx context.Context) (uint64, error) { return cnt, nil } -func BootTimeWithContext(ctx context.Context) (uint64, error) { +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 @@ -72,7 +83,13 @@ func BootTimeWithContext(ctx context.Context) (uint64, error) { } if useStatFile { - return readBootTimeStat(ctx) + t, err := readBootTimeStat(ctx) + if err != nil { + return 0, err + } + if enableCache { + atomic.StoreUint64(&cachedBootTime, t) + } } filename := HostProcWithContext(ctx, "uptime") @@ -90,6 +107,11 @@ func BootTimeWithContext(ctx context.Context) (uint64, error) { } currentTime := float64(time.Now().UnixNano()) / float64(time.Second) t := currentTime - b + + if enableCache { + atomic.StoreUint64(&cachedBootTime, uint64(t)) + } + return uint64(t), nil } diff --git a/process/process.go b/process/process.go index 1a7fe1b80..1bb27abf8 100644 --- a/process/process.go +++ b/process/process.go @@ -171,6 +171,13 @@ func (p NumCtxSwitchesStat) String() string { return string(s) } +var enableBootTimeCache bool + +// EnableBootTimeCache change cache behavior of BootTime. If true, cache BootTime value. Default is false. +func EnableBootTimeCache(enable bool) { + enableBootTimeCache = enable +} + // Pids returns a slice of process ID list which are running now. func Pids() ([]int32, error) { return PidsWithContext(context.Background()) diff --git a/process/process_linux.go b/process/process_linux.go index f7989cd21..557435b34 100644 --- a/process/process_linux.go +++ b/process/process_linux.go @@ -1071,7 +1071,7 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (ui Iowait: iotime / float64(clockTicks), } - bootTime, _ := common.BootTimeWithContext(ctx) + bootTime, _ := common.BootTimeWithContext(ctx, enableBootTimeCache) t, err := strconv.ParseUint(fields[22], 10, 64) if err != nil { return 0, 0, nil, 0, 0, 0, nil, err