123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669 |
- // +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
- }
|