Note, dependency has to be pinned to a specific release version commit hashes since it doesn't follow Go module v2+ guidelines. see: https://github.com/shirou/gopsutil/issues/663 https://github.com/golang/go/issues/34402
		
			
				
	
	
		
			265 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			265 lines
		
	
	
		
			5.7 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
 | 
						|
	}
 | 
						|
	var cnt uint64
 | 
						|
 | 
						|
	for _, v := range list {
 | 
						|
		if _, err = strconv.ParseUint(v, 10, 64); err == nil {
 | 
						|
			cnt++
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return cnt, nil
 | 
						|
}
 | 
						|
 | 
						|
// cachedBootTime must be accessed via atomic.Load/StoreUint64
 | 
						|
var cachedBootTime uint64
 | 
						|
 | 
						|
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("bus/pci/devices")
 | 
						|
	if PathExists(filename) {
 | 
						|
		contents, err := ReadLines(filename)
 | 
						|
		if err == nil {
 | 
						|
			if StringsContains(contents, "virtio-pci") {
 | 
						|
				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 = trimQuotes(field[1])
 | 
						|
		case "VERSION":
 | 
						|
			version = trimQuotes(field[1])
 | 
						|
		}
 | 
						|
	}
 | 
						|
	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
 | 
						|
}
 |