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

gopsutil is a transitive dependency of another project that I am integrating into an internal build system. We target multiple platforms and as a part of the build system for the large internal repo, we calculate the build graph used to determine what targets have changed and need to be build / tested as a single DAG for all platforms. gopsutil currently does not form a DAG if linux and any other platform are considered at the same time. linux is the only platform where the process package imports the host package. To remove this cycle, the relevant methods have been moved to internal/common with the linux build tag and are consumed the host and process packages.
242 lines
5.3 KiB
Go
242 lines
5.3 KiB
Go
// +build linux
|
|
|
|
package common
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
func DoSysctrl(mib string) ([]string, error) {
|
|
sysctl, err := exec.LookPath("sysctl")
|
|
if err != nil {
|
|
return []string{}, err
|
|
}
|
|
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) {
|
|
f, err := os.Open(HostProc())
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer f.Close()
|
|
|
|
list, err := f.Readdirnames(-1)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return uint64(len(list)), err
|
|
}
|
|
|
|
// cachedBootTime must be accessed via atomic.Load/StoreUint64
|
|
var cachedBootTime uint64
|
|
|
|
// BootTime returns the system boot time expressed in seconds since the epoch.
|
|
func BootTime() (uint64, error) {
|
|
return BootTimeWithContext(context.Background())
|
|
}
|
|
|
|
func BootTimeWithContext(ctx context.Context) (uint64, error) {
|
|
t := atomic.LoadUint64(&cachedBootTime)
|
|
if t != 0 {
|
|
return t, nil
|
|
}
|
|
|
|
system, role, err := Virtualization()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
statFile := "stat"
|
|
if system == "lxc" && role == "guest" {
|
|
// if lxc, /proc/uptime is used.
|
|
statFile = "uptime"
|
|
} else if system == "docker" && role == "guest" {
|
|
// also docker, guest
|
|
statFile = "uptime"
|
|
}
|
|
|
|
filename := HostProc(statFile)
|
|
lines, err := ReadLines(filename)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if statFile == "stat" {
|
|
for _, line := range lines {
|
|
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)
|
|
atomic.StoreUint64(&cachedBootTime, t)
|
|
return t, nil
|
|
}
|
|
}
|
|
} else if statFile == "uptime" {
|
|
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 = uint64(time.Now().Unix()) - uint64(b)
|
|
atomic.StoreUint64(&cachedBootTime, t)
|
|
return t, nil
|
|
}
|
|
|
|
return 0, fmt.Errorf("could not find btime")
|
|
}
|
|
|
|
func Virtualization() (string, string, error) {
|
|
return VirtualizationWithContext(context.Background())
|
|
}
|
|
|
|
func VirtualizationWithContext(ctx context.Context) (string, string, error) {
|
|
var system string
|
|
var role string
|
|
|
|
filename := HostProc("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 = HostProc("modules")
|
|
if PathExists(filename) {
|
|
contents, err := ReadLines(filename)
|
|
if err == nil {
|
|
if StringsContains(contents, "kvm") {
|
|
system = "kvm"
|
|
role = "host"
|
|
} 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 = HostProc("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 = HostProc()
|
|
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, "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(HostEtc("os-release")) {
|
|
p, _, err := GetOSRelease()
|
|
if err == nil && p == "coreos" {
|
|
system = "rkt" // Is it true?
|
|
role = "host"
|
|
}
|
|
}
|
|
return system, role, nil
|
|
}
|
|
|
|
func GetOSRelease() (platform string, version string, err error) {
|
|
contents, err := ReadLines(HostEtc("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 = field[1]
|
|
case "VERSION":
|
|
version = field[1]
|
|
}
|
|
}
|
|
return platform, version, nil
|
|
}
|