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

Executing the command does the lookup if needed and returns the same error when not found, no need to do it separately.
203 lines
4.8 KiB
Go
203 lines
4.8 KiB
Go
package host
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"encoding/csv"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/shirou/gopsutil/v3/internal/common"
|
|
)
|
|
|
|
func HostIDWithContext(ctx context.Context) (string, error) {
|
|
platform, err := parseReleaseFile()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if platform == "SmartOS" {
|
|
// If everything works, use the current zone ID as the HostID if present.
|
|
out, err := invoke.CommandWithContext(ctx, "zonename")
|
|
if err == nil {
|
|
sc := bufio.NewScanner(bytes.NewReader(out))
|
|
for sc.Scan() {
|
|
line := sc.Text()
|
|
|
|
// If we're in the global zone, rely on the hostname.
|
|
if line == "global" {
|
|
hostname, err := os.Hostname()
|
|
if err == nil {
|
|
return hostname, nil
|
|
}
|
|
} else {
|
|
return strings.TrimSpace(line), nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If HostID is still unknown, use hostid(1), which can lie to callers but at
|
|
// this point there are no hardware facilities available. This behavior
|
|
// matches that of other supported OSes.
|
|
out, err := invoke.CommandWithContext(ctx, "hostid")
|
|
if err == nil {
|
|
sc := bufio.NewScanner(bytes.NewReader(out))
|
|
for sc.Scan() {
|
|
line := sc.Text()
|
|
return strings.TrimSpace(line), nil
|
|
}
|
|
}
|
|
|
|
return "", nil
|
|
}
|
|
|
|
// Count number of processes based on the number of entries in /proc
|
|
func numProcs(ctx context.Context) (uint64, error) {
|
|
dirs, err := ioutil.ReadDir("/proc")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return uint64(len(dirs)), nil
|
|
}
|
|
|
|
var kstatMatch = regexp.MustCompile(`([^\s]+)[\s]+([^\s]*)`)
|
|
|
|
func BootTimeWithContext(ctx context.Context) (uint64, error) {
|
|
out, err := invoke.CommandWithContext(ctx, "kstat", "-p", "unix:0:system_misc:boot_time")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
kstats := kstatMatch.FindAllStringSubmatch(string(out), -1)
|
|
if len(kstats) != 1 {
|
|
return 0, fmt.Errorf("expected 1 kstat, found %d", len(kstats))
|
|
}
|
|
|
|
return strconv.ParseUint(kstats[0][2], 10, 64)
|
|
}
|
|
|
|
func UptimeWithContext(ctx context.Context) (uint64, error) {
|
|
bootTime, err := BootTime()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return timeSince(bootTime), nil
|
|
}
|
|
|
|
func UsersWithContext(ctx context.Context) ([]UserStat, error) {
|
|
return []UserStat{}, common.ErrNotImplementedError
|
|
}
|
|
|
|
func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
|
|
var ret []TemperatureStat
|
|
|
|
out, err := invoke.CommandWithContext(ctx, "ipmitool", "-c", "sdr", "list")
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
|
|
r := csv.NewReader(strings.NewReader(string(out)))
|
|
// Output may contain errors, e.g. "bmc_send_cmd: Permission denied", don't expect a consistent number of records
|
|
r.FieldsPerRecord = -1
|
|
for {
|
|
record, err := r.Read()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
// CPU1 Temp,40,degrees C,ok
|
|
if len(record) < 3 || record[1] == "" || record[2] != "degrees C" {
|
|
continue
|
|
}
|
|
v, err := strconv.ParseFloat(record[1], 64)
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
ts := TemperatureStat{
|
|
SensorKey: strings.TrimSuffix(record[0], " Temp"),
|
|
Temperature: v,
|
|
}
|
|
ret = append(ret, ts)
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func VirtualizationWithContext(ctx context.Context) (string, string, error) {
|
|
return "", "", common.ErrNotImplementedError
|
|
}
|
|
|
|
// Find distribution name from /etc/release
|
|
func parseReleaseFile() (string, error) {
|
|
b, err := ioutil.ReadFile("/etc/release")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
s := string(b)
|
|
s = strings.TrimSpace(s)
|
|
|
|
var platform string
|
|
|
|
switch {
|
|
case strings.HasPrefix(s, "SmartOS"):
|
|
platform = "SmartOS"
|
|
case strings.HasPrefix(s, "OpenIndiana"):
|
|
platform = "OpenIndiana"
|
|
case strings.HasPrefix(s, "OmniOS"):
|
|
platform = "OmniOS"
|
|
case strings.HasPrefix(s, "Open Storage"):
|
|
platform = "NexentaStor"
|
|
case strings.HasPrefix(s, "Solaris"):
|
|
platform = "Solaris"
|
|
case strings.HasPrefix(s, "Oracle Solaris"):
|
|
platform = "Solaris"
|
|
default:
|
|
platform = strings.Fields(s)[0]
|
|
}
|
|
|
|
return platform, nil
|
|
}
|
|
|
|
// parseUnameOutput returns platformFamily, kernelVersion and platformVersion
|
|
func parseUnameOutput(ctx context.Context) (string, string, string, error) {
|
|
out, err := invoke.CommandWithContext(ctx, "uname", "-srv")
|
|
if err != nil {
|
|
return "", "", "", err
|
|
}
|
|
|
|
fields := strings.Fields(string(out))
|
|
if len(fields) < 3 {
|
|
return "", "", "", fmt.Errorf("malformed `uname` output")
|
|
}
|
|
|
|
return fields[0], fields[1], fields[2], nil
|
|
}
|
|
|
|
func KernelVersionWithContext(ctx context.Context) (string, error) {
|
|
_, kernelVersion, _, err := parseUnameOutput(ctx)
|
|
return kernelVersion, err
|
|
}
|
|
|
|
func PlatformInformationWithContext(ctx context.Context) (string, string, string, error) {
|
|
platform, err := parseReleaseFile()
|
|
if err != nil {
|
|
return "", "", "", err
|
|
}
|
|
|
|
platformFamily, _, platformVersion, err := parseUnameOutput(ctx)
|
|
if err != nil {
|
|
return "", "", "", err
|
|
}
|
|
|
|
return platform, platformFamily, platformVersion, nil
|
|
}
|