mirror of
https://github.com/shirou/gopsutil.git
synced 2025-04-28 13:48:49 +08:00

add missing return statement for boot time value retrieved from stat file. Also move current time fetch to be closer to where the "time since boot file" is read
357 lines
7.9 KiB
Go
357 lines
7.9 KiB
Go
//go:build linux
|
|
// +build linux
|
|
|
|
package common
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"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())
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
return []string{}, err
|
|
}
|
|
v := strings.Replace(string(out), "{ ", "", 1)
|
|
v = strings.Replace(string(v), " }", "", 1)
|
|
values := strings.Fields(string(v))
|
|
|
|
return values, nil
|
|
}
|
|
|
|
func NumProcs() (uint64, error) {
|
|
return NumProcsWithContext(context.Background())
|
|
}
|
|
|
|
func NumProcsWithContext(ctx context.Context) (uint64, error) {
|
|
f, err := os.Open(HostProcWithContext(ctx))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer f.Close()
|
|
|
|
list, err := f.Readdirnames(-1)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
var cnt uint64
|
|
|
|
for _, v := range list {
|
|
if _, err = strconv.ParseUint(v, 10, 64); err == nil {
|
|
cnt++
|
|
}
|
|
}
|
|
|
|
return cnt, nil
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
filename := HostProcWithContext(ctx, "uptime")
|
|
lines, err := ReadLines(filename)
|
|
if err != nil {
|
|
return handleBootTimeFileReadErr(err)
|
|
}
|
|
currentTime := float64(time.Now().UnixNano()) / float64(time.Second)
|
|
|
|
if len(lines) != 1 {
|
|
return 0, fmt.Errorf("wrong uptime format")
|
|
}
|
|
f := strings.Fields(lines[0])
|
|
b, err := strconv.ParseFloat(f[0], 64)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
t := currentTime - b
|
|
|
|
if enableCache {
|
|
atomic.StoreUint64(&cachedBootTime, uint64(t))
|
|
}
|
|
|
|
return uint64(t), nil
|
|
}
|
|
|
|
func handleBootTimeFileReadErr(err error) (uint64, error) {
|
|
if os.IsPermission(err) {
|
|
var info syscall.Sysinfo_t
|
|
err := syscall.Sysinfo(&info)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
currentTime := time.Now().UnixNano() / int64(time.Second)
|
|
t := currentTime - int64(info.Uptime)
|
|
return uint64(t), nil
|
|
}
|
|
return 0, err
|
|
}
|
|
|
|
func readBootTimeStat(ctx context.Context) (uint64, error) {
|
|
filename := HostProcWithContext(ctx, "stat")
|
|
line, err := ReadLine(filename, "btime")
|
|
if err != nil {
|
|
return handleBootTimeFileReadErr(err)
|
|
}
|
|
if strings.HasPrefix(line, "btime") {
|
|
f := strings.Fields(line)
|
|
if len(f) != 2 {
|
|
return 0, fmt.Errorf("wrong btime format")
|
|
}
|
|
b, err := strconv.ParseInt(f[1], 10, 64)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
t := uint64(b)
|
|
return t, nil
|
|
}
|
|
return 0, fmt.Errorf("could not find btime")
|
|
}
|
|
|
|
func Virtualization() (string, string, error) {
|
|
return VirtualizationWithContext(context.Background())
|
|
}
|
|
|
|
// required variables for concurrency safe virtualization caching
|
|
var (
|
|
cachedVirtMap map[string]string
|
|
cachedVirtMutex sync.RWMutex
|
|
cachedVirtOnce sync.Once
|
|
)
|
|
|
|
func VirtualizationWithContext(ctx context.Context) (string, string, error) {
|
|
var system, role string
|
|
|
|
// if cached already, return from cache
|
|
cachedVirtMutex.RLock() // unlock won't be deferred so concurrent reads don't wait for long
|
|
if cachedVirtMap != nil {
|
|
cachedSystem, cachedRole := cachedVirtMap["system"], cachedVirtMap["role"]
|
|
cachedVirtMutex.RUnlock()
|
|
return cachedSystem, cachedRole, nil
|
|
}
|
|
cachedVirtMutex.RUnlock()
|
|
|
|
filename := HostProcWithContext(ctx, "xen")
|
|
if PathExists(filename) {
|
|
system = "xen"
|
|
role = "guest" // assume guest
|
|
|
|
if PathExists(filepath.Join(filename, "capabilities")) {
|
|
contents, err := ReadLines(filepath.Join(filename, "capabilities"))
|
|
if err == nil {
|
|
if StringsContains(contents, "control_d") {
|
|
role = "host"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
filename = HostProcWithContext(ctx, "modules")
|
|
if PathExists(filename) {
|
|
contents, err := ReadLines(filename)
|
|
if err == nil {
|
|
if StringsContains(contents, "kvm") {
|
|
system = "kvm"
|
|
role = "host"
|
|
} else if StringsContains(contents, "hv_util") {
|
|
system = "hyperv"
|
|
role = "guest"
|
|
} else if StringsContains(contents, "vboxdrv") {
|
|
system = "vbox"
|
|
role = "host"
|
|
} else if StringsContains(contents, "vboxguest") {
|
|
system = "vbox"
|
|
role = "guest"
|
|
} else if StringsContains(contents, "vmware") {
|
|
system = "vmware"
|
|
role = "guest"
|
|
}
|
|
}
|
|
}
|
|
|
|
filename = HostProcWithContext(ctx, "cpuinfo")
|
|
if PathExists(filename) {
|
|
contents, err := ReadLines(filename)
|
|
if err == nil {
|
|
if StringsContains(contents, "QEMU Virtual CPU") ||
|
|
StringsContains(contents, "Common KVM processor") ||
|
|
StringsContains(contents, "Common 32-bit KVM processor") {
|
|
system = "kvm"
|
|
role = "guest"
|
|
}
|
|
}
|
|
}
|
|
|
|
filename = HostProcWithContext(ctx, "bus/pci/devices")
|
|
if PathExists(filename) {
|
|
contents, err := ReadLines(filename)
|
|
if err == nil {
|
|
if StringsContains(contents, "virtio-pci") {
|
|
role = "guest"
|
|
}
|
|
}
|
|
}
|
|
|
|
filename = HostProcWithContext(ctx)
|
|
if PathExists(filepath.Join(filename, "bc", "0")) {
|
|
system = "openvz"
|
|
role = "host"
|
|
} else if PathExists(filepath.Join(filename, "vz")) {
|
|
system = "openvz"
|
|
role = "guest"
|
|
}
|
|
|
|
// not use dmidecode because it requires root
|
|
if PathExists(filepath.Join(filename, "self", "status")) {
|
|
contents, err := ReadLines(filepath.Join(filename, "self", "status"))
|
|
if err == nil {
|
|
if StringsContains(contents, "s_context:") ||
|
|
StringsContains(contents, "VxID:") {
|
|
system = "linux-vserver"
|
|
}
|
|
// TODO: guest or host
|
|
}
|
|
}
|
|
|
|
if PathExists(filepath.Join(filename, "1", "environ")) {
|
|
contents, err := ReadFile(filepath.Join(filename, "1", "environ"))
|
|
|
|
if err == nil {
|
|
if strings.Contains(contents, "container=lxc") {
|
|
system = "lxc"
|
|
role = "guest"
|
|
}
|
|
}
|
|
}
|
|
|
|
if PathExists(filepath.Join(filename, "self", "cgroup")) {
|
|
contents, err := ReadLines(filepath.Join(filename, "self", "cgroup"))
|
|
if err == nil {
|
|
if StringsContains(contents, "lxc") {
|
|
system = "lxc"
|
|
role = "guest"
|
|
} else if StringsContains(contents, "docker") {
|
|
system = "docker"
|
|
role = "guest"
|
|
} else if StringsContains(contents, "machine-rkt") {
|
|
system = "rkt"
|
|
role = "guest"
|
|
} else if PathExists("/usr/bin/lxc-version") {
|
|
system = "lxc"
|
|
role = "host"
|
|
}
|
|
}
|
|
}
|
|
|
|
if PathExists(HostEtcWithContext(ctx, "os-release")) {
|
|
p, _, err := GetOSReleaseWithContext(ctx)
|
|
if err == nil && p == "coreos" {
|
|
system = "rkt" // Is it true?
|
|
role = "host"
|
|
}
|
|
}
|
|
|
|
if PathExists(HostRootWithContext(ctx, ".dockerenv")) {
|
|
system = "docker"
|
|
role = "guest"
|
|
}
|
|
|
|
// before returning for the first time, cache the system and role
|
|
cachedVirtOnce.Do(func() {
|
|
cachedVirtMutex.Lock()
|
|
defer cachedVirtMutex.Unlock()
|
|
cachedVirtMap = map[string]string{
|
|
"system": system,
|
|
"role": role,
|
|
}
|
|
})
|
|
|
|
return system, role, nil
|
|
}
|
|
|
|
func GetOSRelease() (platform string, version string, err error) {
|
|
return GetOSReleaseWithContext(context.Background())
|
|
}
|
|
|
|
func GetOSReleaseWithContext(ctx context.Context) (platform string, version string, err error) {
|
|
contents, err := ReadLines(HostEtcWithContext(ctx, "os-release"))
|
|
if err != nil {
|
|
return "", "", nil // return empty
|
|
}
|
|
for _, line := range contents {
|
|
field := strings.Split(line, "=")
|
|
if len(field) < 2 {
|
|
continue
|
|
}
|
|
switch field[0] {
|
|
case "ID": // use ID for lowercase
|
|
platform = trimQuotes(field[1])
|
|
case "VERSION_ID":
|
|
version = trimQuotes(field[1])
|
|
}
|
|
}
|
|
|
|
// cleanup amazon ID
|
|
if platform == "amzn" {
|
|
platform = "amazon"
|
|
}
|
|
|
|
return platform, version, nil
|
|
}
|
|
|
|
// Remove quotes of the source string
|
|
func trimQuotes(s string) string {
|
|
if len(s) >= 2 {
|
|
if s[0] == '"' && s[len(s)-1] == '"' {
|
|
return s[1 : len(s)-1]
|
|
}
|
|
}
|
|
return s
|
|
}
|