You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
669 lines
17 KiB
669 lines
17 KiB
// +build linux |
|
|
|
package host |
|
|
|
import ( |
|
"bytes" |
|
"context" |
|
"encoding/binary" |
|
"fmt" |
|
"io/ioutil" |
|
"os" |
|
"os/exec" |
|
"path/filepath" |
|
"regexp" |
|
"runtime" |
|
"strconv" |
|
"strings" |
|
"sync/atomic" |
|
"time" |
|
|
|
"github.com/shirou/gopsutil/internal/common" |
|
) |
|
|
|
type LSB struct { |
|
ID string |
|
Release string |
|
Codename string |
|
Description string |
|
} |
|
|
|
// from utmp.h |
|
const USER_PROCESS = 7 |
|
|
|
func Info() (*InfoStat, error) { |
|
return InfoWithContext(context.Background()) |
|
} |
|
|
|
func InfoWithContext(ctx context.Context) (*InfoStat, error) { |
|
ret := &InfoStat{ |
|
OS: runtime.GOOS, |
|
} |
|
|
|
hostname, err := os.Hostname() |
|
if err == nil { |
|
ret.Hostname = hostname |
|
} |
|
|
|
platform, family, version, err := PlatformInformation() |
|
if err == nil { |
|
ret.Platform = platform |
|
ret.PlatformFamily = family |
|
ret.PlatformVersion = version |
|
} |
|
kernelVersion, err := KernelVersion() |
|
if err == nil { |
|
ret.KernelVersion = kernelVersion |
|
} |
|
|
|
system, role, err := Virtualization() |
|
if err == nil { |
|
ret.VirtualizationSystem = system |
|
ret.VirtualizationRole = role |
|
} |
|
|
|
boot, err := BootTime() |
|
if err == nil { |
|
ret.BootTime = boot |
|
ret.Uptime = uptime(boot) |
|
} |
|
|
|
if numProcs, err := common.NumProcs(); err == nil { |
|
ret.Procs = numProcs |
|
} |
|
|
|
sysProductUUID := common.HostSys("class/dmi/id/product_uuid") |
|
switch { |
|
case common.PathExists(sysProductUUID): |
|
lines, err := common.ReadLines(sysProductUUID) |
|
if err == nil && len(lines) > 0 && lines[0] != "" { |
|
ret.HostID = strings.ToLower(lines[0]) |
|
break |
|
} |
|
fallthrough |
|
default: |
|
values, err := common.DoSysctrl("kernel.random.boot_id") |
|
if err == nil && len(values) == 1 && values[0] != "" { |
|
ret.HostID = strings.ToLower(values[0]) |
|
} |
|
} |
|
|
|
return ret, nil |
|
} |
|
|
|
// 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 := common.HostProc(statFile) |
|
lines, err := common.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 uptime(boot uint64) uint64 { |
|
return uint64(time.Now().Unix()) - boot |
|
} |
|
|
|
func Uptime() (uint64, error) { |
|
return UptimeWithContext(context.Background()) |
|
} |
|
|
|
func UptimeWithContext(ctx context.Context) (uint64, error) { |
|
boot, err := BootTime() |
|
if err != nil { |
|
return 0, err |
|
} |
|
return uptime(boot), nil |
|
} |
|
|
|
func Users() ([]UserStat, error) { |
|
return UsersWithContext(context.Background()) |
|
} |
|
|
|
func UsersWithContext(ctx context.Context) ([]UserStat, error) { |
|
utmpfile := common.HostVar("run/utmp") |
|
|
|
file, err := os.Open(utmpfile) |
|
if err != nil { |
|
return nil, err |
|
} |
|
defer file.Close() |
|
|
|
buf, err := ioutil.ReadAll(file) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
count := len(buf) / sizeOfUtmp |
|
|
|
ret := make([]UserStat, 0, count) |
|
|
|
for i := 0; i < count; i++ { |
|
b := buf[i*sizeOfUtmp : (i+1)*sizeOfUtmp] |
|
|
|
var u utmp |
|
br := bytes.NewReader(b) |
|
err := binary.Read(br, binary.LittleEndian, &u) |
|
if err != nil { |
|
continue |
|
} |
|
if u.Type != USER_PROCESS { |
|
continue |
|
} |
|
user := UserStat{ |
|
User: common.IntToString(u.User[:]), |
|
Terminal: common.IntToString(u.Line[:]), |
|
Host: common.IntToString(u.Host[:]), |
|
Started: int(u.Tv.Sec), |
|
} |
|
ret = append(ret, user) |
|
} |
|
|
|
return ret, nil |
|
|
|
} |
|
|
|
func getOSRelease() (platform string, version string, err error) { |
|
contents, err := common.ReadLines(common.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 |
|
} |
|
|
|
func getLSB() (*LSB, error) { |
|
ret := &LSB{} |
|
if common.PathExists(common.HostEtc("lsb-release")) { |
|
contents, err := common.ReadLines(common.HostEtc("lsb-release")) |
|
if err != nil { |
|
return ret, err // return empty |
|
} |
|
for _, line := range contents { |
|
field := strings.Split(line, "=") |
|
if len(field) < 2 { |
|
continue |
|
} |
|
switch field[0] { |
|
case "DISTRIB_ID": |
|
ret.ID = field[1] |
|
case "DISTRIB_RELEASE": |
|
ret.Release = field[1] |
|
case "DISTRIB_CODENAME": |
|
ret.Codename = field[1] |
|
case "DISTRIB_DESCRIPTION": |
|
ret.Description = field[1] |
|
} |
|
} |
|
} else if common.PathExists("/usr/bin/lsb_release") { |
|
lsb_release, err := exec.LookPath("/usr/bin/lsb_release") |
|
if err != nil { |
|
return ret, err |
|
} |
|
out, err := invoke.Command(lsb_release) |
|
if err != nil { |
|
return ret, err |
|
} |
|
for _, line := range strings.Split(string(out), "\n") { |
|
field := strings.Split(line, ":") |
|
if len(field) < 2 { |
|
continue |
|
} |
|
switch field[0] { |
|
case "Distributor ID": |
|
ret.ID = field[1] |
|
case "Release": |
|
ret.Release = field[1] |
|
case "Codename": |
|
ret.Codename = field[1] |
|
case "Description": |
|
ret.Description = field[1] |
|
} |
|
} |
|
|
|
} |
|
|
|
return ret, nil |
|
} |
|
|
|
func PlatformInformation() (platform string, family string, version string, err error) { |
|
return PlatformInformationWithContext(context.Background()) |
|
} |
|
|
|
func PlatformInformationWithContext(ctx context.Context) (platform string, family string, version string, err error) { |
|
|
|
lsb, err := getLSB() |
|
if err != nil { |
|
lsb = &LSB{} |
|
} |
|
|
|
if common.PathExists(common.HostEtc("oracle-release")) { |
|
platform = "oracle" |
|
contents, err := common.ReadLines(common.HostEtc("oracle-release")) |
|
if err == nil { |
|
version = getRedhatishVersion(contents) |
|
} |
|
|
|
} else if common.PathExists(common.HostEtc("enterprise-release")) { |
|
platform = "oracle" |
|
contents, err := common.ReadLines(common.HostEtc("enterprise-release")) |
|
if err == nil { |
|
version = getRedhatishVersion(contents) |
|
} |
|
} else if common.PathExists(common.HostEtc("slackware-version")) { |
|
platform = "slackware" |
|
contents, err := common.ReadLines(common.HostEtc("slackware-version")) |
|
if err == nil { |
|
version = getSlackwareVersion(contents) |
|
} |
|
} else if common.PathExists(common.HostEtc("debian_version")) { |
|
if lsb.ID == "Ubuntu" { |
|
platform = "ubuntu" |
|
version = lsb.Release |
|
} else if lsb.ID == "LinuxMint" { |
|
platform = "linuxmint" |
|
version = lsb.Release |
|
} else { |
|
if common.PathExists("/usr/bin/raspi-config") { |
|
platform = "raspbian" |
|
} else { |
|
platform = "debian" |
|
} |
|
contents, err := common.ReadLines(common.HostEtc("debian_version")) |
|
if err == nil { |
|
version = contents[0] |
|
} |
|
} |
|
} else if common.PathExists(common.HostEtc("redhat-release")) { |
|
contents, err := common.ReadLines(common.HostEtc("redhat-release")) |
|
if err == nil { |
|
version = getRedhatishVersion(contents) |
|
platform = getRedhatishPlatform(contents) |
|
} |
|
} else if common.PathExists(common.HostEtc("system-release")) { |
|
contents, err := common.ReadLines(common.HostEtc("system-release")) |
|
if err == nil { |
|
version = getRedhatishVersion(contents) |
|
platform = getRedhatishPlatform(contents) |
|
} |
|
} else if common.PathExists(common.HostEtc("gentoo-release")) { |
|
platform = "gentoo" |
|
contents, err := common.ReadLines(common.HostEtc("gentoo-release")) |
|
if err == nil { |
|
version = getRedhatishVersion(contents) |
|
} |
|
} else if common.PathExists(common.HostEtc("SuSE-release")) { |
|
contents, err := common.ReadLines(common.HostEtc("SuSE-release")) |
|
if err == nil { |
|
version = getSuseVersion(contents) |
|
platform = getSusePlatform(contents) |
|
} |
|
// TODO: slackware detecion |
|
} else if common.PathExists(common.HostEtc("arch-release")) { |
|
platform = "arch" |
|
version = lsb.Release |
|
} else if common.PathExists(common.HostEtc("alpine-release")) { |
|
platform = "alpine" |
|
contents, err := common.ReadLines(common.HostEtc("alpine-release")) |
|
if err == nil && len(contents) > 0 { |
|
version = contents[0] |
|
} |
|
} else if common.PathExists(common.HostEtc("os-release")) { |
|
p, v, err := getOSRelease() |
|
if err == nil { |
|
platform = p |
|
version = v |
|
} |
|
} else if lsb.ID == "RedHat" { |
|
platform = "redhat" |
|
version = lsb.Release |
|
} else if lsb.ID == "Amazon" { |
|
platform = "amazon" |
|
version = lsb.Release |
|
} else if lsb.ID == "ScientificSL" { |
|
platform = "scientific" |
|
version = lsb.Release |
|
} else if lsb.ID == "XenServer" { |
|
platform = "xenserver" |
|
version = lsb.Release |
|
} else if lsb.ID != "" { |
|
platform = strings.ToLower(lsb.ID) |
|
version = lsb.Release |
|
} |
|
|
|
switch platform { |
|
case "debian", "ubuntu", "linuxmint", "raspbian": |
|
family = "debian" |
|
case "fedora": |
|
family = "fedora" |
|
case "oracle", "centos", "redhat", "scientific", "enterpriseenterprise", "amazon", "xenserver", "cloudlinux", "ibm_powerkvm": |
|
family = "rhel" |
|
case "suse", "opensuse": |
|
family = "suse" |
|
case "gentoo": |
|
family = "gentoo" |
|
case "slackware": |
|
family = "slackware" |
|
case "arch": |
|
family = "arch" |
|
case "exherbo": |
|
family = "exherbo" |
|
case "alpine": |
|
family = "alpine" |
|
case "coreos": |
|
family = "coreos" |
|
} |
|
|
|
return platform, family, version, nil |
|
|
|
} |
|
|
|
func KernelVersion() (version string, err error) { |
|
return KernelVersionWithContext(context.Background()) |
|
} |
|
|
|
func KernelVersionWithContext(ctx context.Context) (version string, err error) { |
|
filename := common.HostProc("sys/kernel/osrelease") |
|
if common.PathExists(filename) { |
|
contents, err := common.ReadLines(filename) |
|
if err != nil { |
|
return "", err |
|
} |
|
|
|
if len(contents) > 0 { |
|
version = contents[0] |
|
} |
|
} |
|
|
|
return version, nil |
|
} |
|
|
|
func getSlackwareVersion(contents []string) string { |
|
c := strings.ToLower(strings.Join(contents, "")) |
|
c = strings.Replace(c, "slackware ", "", 1) |
|
return c |
|
} |
|
|
|
func getRedhatishVersion(contents []string) string { |
|
c := strings.ToLower(strings.Join(contents, "")) |
|
|
|
if strings.Contains(c, "rawhide") { |
|
return "rawhide" |
|
} |
|
if matches := regexp.MustCompile(`release (\d[\d.]*)`).FindStringSubmatch(c); matches != nil { |
|
return matches[1] |
|
} |
|
return "" |
|
} |
|
|
|
func getRedhatishPlatform(contents []string) string { |
|
c := strings.ToLower(strings.Join(contents, "")) |
|
|
|
if strings.Contains(c, "red hat") { |
|
return "redhat" |
|
} |
|
f := strings.Split(c, " ") |
|
|
|
return f[0] |
|
} |
|
|
|
func getSuseVersion(contents []string) string { |
|
version := "" |
|
for _, line := range contents { |
|
if matches := regexp.MustCompile(`VERSION = ([\d.]+)`).FindStringSubmatch(line); matches != nil { |
|
version = matches[1] |
|
} else if matches := regexp.MustCompile(`PATCHLEVEL = ([\d]+)`).FindStringSubmatch(line); matches != nil { |
|
version = version + "." + matches[1] |
|
} |
|
} |
|
return version |
|
} |
|
|
|
func getSusePlatform(contents []string) string { |
|
c := strings.ToLower(strings.Join(contents, "")) |
|
if strings.Contains(c, "opensuse") { |
|
return "opensuse" |
|
} |
|
return "suse" |
|
} |
|
|
|
func Virtualization() (string, string, error) { |
|
return VirtualizationWithContext(context.Background()) |
|
} |
|
|
|
func VirtualizationWithContext(ctx context.Context) (string, string, error) { |
|
var system string |
|
var role string |
|
|
|
filename := common.HostProc("xen") |
|
if common.PathExists(filename) { |
|
system = "xen" |
|
role = "guest" // assume guest |
|
|
|
if common.PathExists(filepath.Join(filename, "capabilities")) { |
|
contents, err := common.ReadLines(filepath.Join(filename, "capabilities")) |
|
if err == nil { |
|
if common.StringsContains(contents, "control_d") { |
|
role = "host" |
|
} |
|
} |
|
} |
|
} |
|
|
|
filename = common.HostProc("modules") |
|
if common.PathExists(filename) { |
|
contents, err := common.ReadLines(filename) |
|
if err == nil { |
|
if common.StringsContains(contents, "kvm") { |
|
system = "kvm" |
|
role = "host" |
|
} else if common.StringsContains(contents, "vboxdrv") { |
|
system = "vbox" |
|
role = "host" |
|
} else if common.StringsContains(contents, "vboxguest") { |
|
system = "vbox" |
|
role = "guest" |
|
} else if common.StringsContains(contents, "vmware") { |
|
system = "vmware" |
|
role = "guest" |
|
} |
|
} |
|
} |
|
|
|
filename = common.HostProc("cpuinfo") |
|
if common.PathExists(filename) { |
|
contents, err := common.ReadLines(filename) |
|
if err == nil { |
|
if common.StringsContains(contents, "QEMU Virtual CPU") || |
|
common.StringsContains(contents, "Common KVM processor") || |
|
common.StringsContains(contents, "Common 32-bit KVM processor") { |
|
system = "kvm" |
|
role = "guest" |
|
} |
|
} |
|
} |
|
|
|
filename = common.HostProc() |
|
if common.PathExists(filepath.Join(filename, "bc", "0")) { |
|
system = "openvz" |
|
role = "host" |
|
} else if common.PathExists(filepath.Join(filename, "vz")) { |
|
system = "openvz" |
|
role = "guest" |
|
} |
|
|
|
// not use dmidecode because it requires root |
|
if common.PathExists(filepath.Join(filename, "self", "status")) { |
|
contents, err := common.ReadLines(filepath.Join(filename, "self", "status")) |
|
if err == nil { |
|
|
|
if common.StringsContains(contents, "s_context:") || |
|
common.StringsContains(contents, "VxID:") { |
|
system = "linux-vserver" |
|
} |
|
// TODO: guest or host |
|
} |
|
} |
|
|
|
if common.PathExists(filepath.Join(filename, "self", "cgroup")) { |
|
contents, err := common.ReadLines(filepath.Join(filename, "self", "cgroup")) |
|
if err == nil { |
|
if common.StringsContains(contents, "lxc") { |
|
system = "lxc" |
|
role = "guest" |
|
} else if common.StringsContains(contents, "docker") { |
|
system = "docker" |
|
role = "guest" |
|
} else if common.StringsContains(contents, "machine-rkt") { |
|
system = "rkt" |
|
role = "guest" |
|
} else if common.PathExists("/usr/bin/lxc-version") { |
|
system = "lxc" |
|
role = "host" |
|
} |
|
} |
|
} |
|
|
|
if common.PathExists(common.HostEtc("os-release")) { |
|
p, _, err := getOSRelease() |
|
if err == nil && p == "coreos" { |
|
system = "rkt" // Is it true? |
|
role = "host" |
|
} |
|
} |
|
return system, role, nil |
|
} |
|
|
|
func SensorsTemperatures() ([]TemperatureStat, error) { |
|
return SensorsTemperaturesWithContext(context.Background()) |
|
} |
|
|
|
func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) { |
|
var temperatures []TemperatureStat |
|
files, err := filepath.Glob(common.HostSys("/class/hwmon/hwmon*/temp*_*")) |
|
if err != nil { |
|
return temperatures, err |
|
} |
|
if len(files) == 0 { |
|
// CentOS has an intermediate /device directory: |
|
// https://github.com/giampaolo/psutil/issues/971 |
|
files, err = filepath.Glob(common.HostSys("/class/hwmon/hwmon*/device/temp*_*")) |
|
if err != nil { |
|
return temperatures, err |
|
} |
|
} |
|
|
|
// example directory |
|
// device/ temp1_crit_alarm temp2_crit_alarm temp3_crit_alarm temp4_crit_alarm temp5_crit_alarm temp6_crit_alarm temp7_crit_alarm |
|
// name temp1_input temp2_input temp3_input temp4_input temp5_input temp6_input temp7_input |
|
// power/ temp1_label temp2_label temp3_label temp4_label temp5_label temp6_label temp7_label |
|
// subsystem/ temp1_max temp2_max temp3_max temp4_max temp5_max temp6_max temp7_max |
|
// temp1_crit temp2_crit temp3_crit temp4_crit temp5_crit temp6_crit temp7_crit uevent |
|
for _, file := range files { |
|
filename := strings.Split(filepath.Base(file), "_") |
|
if filename[1] == "label" { |
|
// Do not try to read the temperature of the label file |
|
continue |
|
} |
|
|
|
// Get the label of the temperature you are reading |
|
var label string |
|
c, _ := ioutil.ReadFile(filepath.Join(filepath.Dir(file), filename[0]+"_label")) |
|
if c != nil { |
|
//format the label from "Core 0" to "core0_" |
|
label = fmt.Sprintf("%s_", strings.Join(strings.Split(strings.TrimSpace(strings.ToLower(string(c))), " "), "")) |
|
} |
|
|
|
// Get the name of the tempearture you are reading |
|
name, err := ioutil.ReadFile(filepath.Join(filepath.Dir(file), "name")) |
|
if err != nil { |
|
return temperatures, err |
|
} |
|
|
|
// Get the temperature reading |
|
current, err := ioutil.ReadFile(file) |
|
if err != nil { |
|
return temperatures, err |
|
} |
|
temperature, err := strconv.ParseFloat(strings.TrimSpace(string(current)), 64) |
|
if err != nil { |
|
continue |
|
} |
|
|
|
tempName := strings.TrimSpace(strings.ToLower(string(strings.Join(filename[1:], "")))) |
|
temperatures = append(temperatures, TemperatureStat{ |
|
SensorKey: fmt.Sprintf("%s_%s%s", strings.TrimSpace(string(name)), label, tempName), |
|
Temperature: temperature / 1000.0, |
|
}) |
|
} |
|
return temperatures, nil |
|
}
|
|
|