...

Source file src/github.com/shirou/gopsutil/host/host_linux.go

Documentation: github.com/shirou/gopsutil/host

     1  // +build linux
     2  
     3  package host
     4  
     5  import (
     6  	"bytes"
     7  	"context"
     8  	"encoding/binary"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"regexp"
    15  	"strconv"
    16  	"strings"
    17  
    18  	"github.com/shirou/gopsutil/internal/common"
    19  	"golang.org/x/sys/unix"
    20  )
    21  
    22  type LSB struct {
    23  	ID          string
    24  	Release     string
    25  	Codename    string
    26  	Description string
    27  }
    28  
    29  // from utmp.h
    30  const USER_PROCESS = 7
    31  
    32  func HostIDWithContext(ctx context.Context) (string, error) {
    33  	sysProductUUID := common.HostSys("class/dmi/id/product_uuid")
    34  	machineID := common.HostEtc("machine-id")
    35  	procSysKernelRandomBootID := common.HostProc("sys/kernel/random/boot_id")
    36  	switch {
    37  	// In order to read this file, needs to be supported by kernel/arch and run as root
    38  	// so having fallback is important
    39  	case common.PathExists(sysProductUUID):
    40  		lines, err := common.ReadLines(sysProductUUID)
    41  		if err == nil && len(lines) > 0 && lines[0] != "" {
    42  			return strings.ToLower(lines[0]), nil
    43  		}
    44  		fallthrough
    45  	// Fallback on GNU Linux systems with systemd, readable by everyone
    46  	case common.PathExists(machineID):
    47  		lines, err := common.ReadLines(machineID)
    48  		if err == nil && len(lines) > 0 && len(lines[0]) == 32 {
    49  			st := lines[0]
    50  			return fmt.Sprintf("%s-%s-%s-%s-%s", st[0:8], st[8:12], st[12:16], st[16:20], st[20:32]), nil
    51  		}
    52  		fallthrough
    53  	// Not stable between reboot, but better than nothing
    54  	default:
    55  		lines, err := common.ReadLines(procSysKernelRandomBootID)
    56  		if err == nil && len(lines) > 0 && lines[0] != "" {
    57  			return strings.ToLower(lines[0]), nil
    58  		}
    59  	}
    60  
    61  	return "", nil
    62  }
    63  
    64  func numProcs(ctx context.Context) (uint64, error) {
    65  	return common.NumProcs()
    66  }
    67  
    68  func BootTimeWithContext(ctx context.Context) (uint64, error) {
    69  	return common.BootTimeWithContext(ctx)
    70  }
    71  
    72  func UptimeWithContext(ctx context.Context) (uint64, error) {
    73  	sysinfo := &unix.Sysinfo_t{}
    74  	if err := unix.Sysinfo(sysinfo); err != nil {
    75  		return 0, err
    76  	}
    77  	return uint64(sysinfo.Uptime), nil
    78  }
    79  
    80  func UsersWithContext(ctx context.Context) ([]UserStat, error) {
    81  	utmpfile := common.HostVar("run/utmp")
    82  
    83  	file, err := os.Open(utmpfile)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	defer file.Close()
    88  
    89  	buf, err := ioutil.ReadAll(file)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	count := len(buf) / sizeOfUtmp
    95  
    96  	ret := make([]UserStat, 0, count)
    97  
    98  	for i := 0; i < count; i++ {
    99  		b := buf[i*sizeOfUtmp : (i+1)*sizeOfUtmp]
   100  
   101  		var u utmp
   102  		br := bytes.NewReader(b)
   103  		err := binary.Read(br, binary.LittleEndian, &u)
   104  		if err != nil {
   105  			continue
   106  		}
   107  		if u.Type != USER_PROCESS {
   108  			continue
   109  		}
   110  		user := UserStat{
   111  			User:     common.IntToString(u.User[:]),
   112  			Terminal: common.IntToString(u.Line[:]),
   113  			Host:     common.IntToString(u.Host[:]),
   114  			Started:  int(u.Tv.Sec),
   115  		}
   116  		ret = append(ret, user)
   117  	}
   118  
   119  	return ret, nil
   120  
   121  }
   122  
   123  func getLSB() (*LSB, error) {
   124  	ret := &LSB{}
   125  	if common.PathExists(common.HostEtc("lsb-release")) {
   126  		contents, err := common.ReadLines(common.HostEtc("lsb-release"))
   127  		if err != nil {
   128  			return ret, err // return empty
   129  		}
   130  		for _, line := range contents {
   131  			field := strings.Split(line, "=")
   132  			if len(field) < 2 {
   133  				continue
   134  			}
   135  			switch field[0] {
   136  			case "DISTRIB_ID":
   137  				ret.ID = field[1]
   138  			case "DISTRIB_RELEASE":
   139  				ret.Release = field[1]
   140  			case "DISTRIB_CODENAME":
   141  				ret.Codename = field[1]
   142  			case "DISTRIB_DESCRIPTION":
   143  				ret.Description = field[1]
   144  			}
   145  		}
   146  	} else if common.PathExists("/usr/bin/lsb_release") {
   147  		lsb_release, err := exec.LookPath("lsb_release")
   148  		if err != nil {
   149  			return ret, err
   150  		}
   151  		out, err := invoke.Command(lsb_release)
   152  		if err != nil {
   153  			return ret, err
   154  		}
   155  		for _, line := range strings.Split(string(out), "\n") {
   156  			field := strings.Split(line, ":")
   157  			if len(field) < 2 {
   158  				continue
   159  			}
   160  			switch field[0] {
   161  			case "Distributor ID":
   162  				ret.ID = field[1]
   163  			case "Release":
   164  				ret.Release = field[1]
   165  			case "Codename":
   166  				ret.Codename = field[1]
   167  			case "Description":
   168  				ret.Description = field[1]
   169  			}
   170  		}
   171  
   172  	}
   173  
   174  	return ret, nil
   175  }
   176  
   177  func PlatformInformationWithContext(ctx context.Context) (platform string, family string, version string, err error) {
   178  	lsb, err := getLSB()
   179  	if err != nil {
   180  		lsb = &LSB{}
   181  	}
   182  
   183  	if common.PathExists(common.HostEtc("oracle-release")) {
   184  		platform = "oracle"
   185  		contents, err := common.ReadLines(common.HostEtc("oracle-release"))
   186  		if err == nil {
   187  			version = getRedhatishVersion(contents)
   188  		}
   189  
   190  	} else if common.PathExists(common.HostEtc("enterprise-release")) {
   191  		platform = "oracle"
   192  		contents, err := common.ReadLines(common.HostEtc("enterprise-release"))
   193  		if err == nil {
   194  			version = getRedhatishVersion(contents)
   195  		}
   196  	} else if common.PathExists(common.HostEtc("slackware-version")) {
   197  		platform = "slackware"
   198  		contents, err := common.ReadLines(common.HostEtc("slackware-version"))
   199  		if err == nil {
   200  			version = getSlackwareVersion(contents)
   201  		}
   202  	} else if common.PathExists(common.HostEtc("debian_version")) {
   203  		if lsb.ID == "Ubuntu" {
   204  			platform = "ubuntu"
   205  			version = lsb.Release
   206  		} else if lsb.ID == "LinuxMint" {
   207  			platform = "linuxmint"
   208  			version = lsb.Release
   209  		} else {
   210  			if common.PathExists("/usr/bin/raspi-config") {
   211  				platform = "raspbian"
   212  			} else {
   213  				platform = "debian"
   214  			}
   215  			contents, err := common.ReadLines(common.HostEtc("debian_version"))
   216  			if err == nil && len(contents) > 0 && contents[0] != "" {
   217  				version = contents[0]
   218  			}
   219  		}
   220  	} else if common.PathExists(common.HostEtc("redhat-release")) {
   221  		contents, err := common.ReadLines(common.HostEtc("redhat-release"))
   222  		if err == nil {
   223  			version = getRedhatishVersion(contents)
   224  			platform = getRedhatishPlatform(contents)
   225  		}
   226  	} else if common.PathExists(common.HostEtc("system-release")) {
   227  		contents, err := common.ReadLines(common.HostEtc("system-release"))
   228  		if err == nil {
   229  			version = getRedhatishVersion(contents)
   230  			platform = getRedhatishPlatform(contents)
   231  		}
   232  	} else if common.PathExists(common.HostEtc("gentoo-release")) {
   233  		platform = "gentoo"
   234  		contents, err := common.ReadLines(common.HostEtc("gentoo-release"))
   235  		if err == nil {
   236  			version = getRedhatishVersion(contents)
   237  		}
   238  	} else if common.PathExists(common.HostEtc("SuSE-release")) {
   239  		contents, err := common.ReadLines(common.HostEtc("SuSE-release"))
   240  		if err == nil {
   241  			version = getSuseVersion(contents)
   242  			platform = getSusePlatform(contents)
   243  		}
   244  		// TODO: slackware detecion
   245  	} else if common.PathExists(common.HostEtc("arch-release")) {
   246  		platform = "arch"
   247  		version = lsb.Release
   248  	} else if common.PathExists(common.HostEtc("alpine-release")) {
   249  		platform = "alpine"
   250  		contents, err := common.ReadLines(common.HostEtc("alpine-release"))
   251  		if err == nil && len(contents) > 0 && contents[0] != "" {
   252  			version = contents[0]
   253  		}
   254  	} else if common.PathExists(common.HostEtc("os-release")) {
   255  		p, v, err := common.GetOSRelease()
   256  		if err == nil {
   257  			platform = p
   258  			version = v
   259  		}
   260  	} else if lsb.ID == "RedHat" {
   261  		platform = "redhat"
   262  		version = lsb.Release
   263  	} else if lsb.ID == "Amazon" {
   264  		platform = "amazon"
   265  		version = lsb.Release
   266  	} else if lsb.ID == "ScientificSL" {
   267  		platform = "scientific"
   268  		version = lsb.Release
   269  	} else if lsb.ID == "XenServer" {
   270  		platform = "xenserver"
   271  		version = lsb.Release
   272  	} else if lsb.ID != "" {
   273  		platform = strings.ToLower(lsb.ID)
   274  		version = lsb.Release
   275  	}
   276  
   277  	switch platform {
   278  	case "debian", "ubuntu", "linuxmint", "raspbian":
   279  		family = "debian"
   280  	case "fedora":
   281  		family = "fedora"
   282  	case "oracle", "centos", "redhat", "scientific", "enterpriseenterprise", "amazon", "xenserver", "cloudlinux", "ibm_powerkvm", "rocky":
   283  		family = "rhel"
   284  	case "suse", "opensuse", "opensuse-leap", "opensuse-tumbleweed", "opensuse-tumbleweed-kubic", "sles", "sled", "caasp":
   285  		family = "suse"
   286  	case "gentoo":
   287  		family = "gentoo"
   288  	case "slackware":
   289  		family = "slackware"
   290  	case "arch":
   291  		family = "arch"
   292  	case "exherbo":
   293  		family = "exherbo"
   294  	case "alpine":
   295  		family = "alpine"
   296  	case "coreos":
   297  		family = "coreos"
   298  	case "solus":
   299  		family = "solus"
   300  	}
   301  
   302  	return platform, family, version, nil
   303  
   304  }
   305  
   306  func KernelVersionWithContext(ctx context.Context) (version string, err error) {
   307  	var utsname unix.Utsname
   308  	err = unix.Uname(&utsname)
   309  	if err != nil {
   310  		return "", err
   311  	}
   312  	return string(utsname.Release[:bytes.IndexByte(utsname.Release[:], 0)]), nil
   313  }
   314  
   315  func getSlackwareVersion(contents []string) string {
   316  	c := strings.ToLower(strings.Join(contents, ""))
   317  	c = strings.Replace(c, "slackware ", "", 1)
   318  	return c
   319  }
   320  
   321  func getRedhatishVersion(contents []string) string {
   322  	c := strings.ToLower(strings.Join(contents, ""))
   323  
   324  	if strings.Contains(c, "rawhide") {
   325  		return "rawhide"
   326  	}
   327  	if matches := regexp.MustCompile(`release (\d[\d.]*)`).FindStringSubmatch(c); matches != nil {
   328  		return matches[1]
   329  	}
   330  	return ""
   331  }
   332  
   333  func getRedhatishPlatform(contents []string) string {
   334  	c := strings.ToLower(strings.Join(contents, ""))
   335  
   336  	if strings.Contains(c, "red hat") {
   337  		return "redhat"
   338  	}
   339  	f := strings.Split(c, " ")
   340  
   341  	return f[0]
   342  }
   343  
   344  func getSuseVersion(contents []string) string {
   345  	version := ""
   346  	for _, line := range contents {
   347  		if matches := regexp.MustCompile(`VERSION = ([\d.]+)`).FindStringSubmatch(line); matches != nil {
   348  			version = matches[1]
   349  		} else if matches := regexp.MustCompile(`PATCHLEVEL = ([\d]+)`).FindStringSubmatch(line); matches != nil {
   350  			version = version + "." + matches[1]
   351  		}
   352  	}
   353  	return version
   354  }
   355  
   356  func getSusePlatform(contents []string) string {
   357  	c := strings.ToLower(strings.Join(contents, ""))
   358  	if strings.Contains(c, "opensuse") {
   359  		return "opensuse"
   360  	}
   361  	return "suse"
   362  }
   363  
   364  func VirtualizationWithContext(ctx context.Context) (string, string, error) {
   365  	return common.VirtualizationWithContext(ctx)
   366  }
   367  
   368  func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
   369  	var temperatures []TemperatureStat
   370  	files, err := filepath.Glob(common.HostSys("/class/hwmon/hwmon*/temp*_*"))
   371  	if err != nil {
   372  		return temperatures, err
   373  	}
   374  	if len(files) == 0 {
   375  		// CentOS has an intermediate /device directory:
   376  		// https://github.com/giampaolo/psutil/issues/971
   377  		files, err = filepath.Glob(common.HostSys("/class/hwmon/hwmon*/device/temp*_*"))
   378  		if err != nil {
   379  			return temperatures, err
   380  		}
   381  	}
   382  	var warns Warnings
   383  
   384  	if len(files) == 0 { // handle distributions without hwmon, like raspbian #391, parse legacy thermal_zone files
   385  		files, err = filepath.Glob(common.HostSys("/class/thermal/thermal_zone*/"))
   386  		if err != nil {
   387  			return temperatures, err
   388  		}
   389  		for _, file := range files {
   390  			// Get the name of the temperature you are reading
   391  			name, err := ioutil.ReadFile(filepath.Join(file, "type"))
   392  			if err != nil {
   393  				warns.Add(err)
   394  				continue
   395  			}
   396  			// Get the temperature reading
   397  			current, err := ioutil.ReadFile(filepath.Join(file, "temp"))
   398  			if err != nil {
   399  				warns.Add(err)
   400  				continue
   401  			}
   402  			temperature, err := strconv.ParseInt(strings.TrimSpace(string(current)), 10, 64)
   403  			if err != nil {
   404  				warns.Add(err)
   405  				continue
   406  			}
   407  
   408  			temperatures = append(temperatures, TemperatureStat{
   409  				SensorKey:   strings.TrimSpace(string(name)),
   410  				Temperature: float64(temperature) / 1000.0,
   411  			})
   412  		}
   413  		return temperatures, warns.Reference()
   414  	}
   415  
   416  	// example directory
   417  	// device/           temp1_crit_alarm  temp2_crit_alarm  temp3_crit_alarm  temp4_crit_alarm  temp5_crit_alarm  temp6_crit_alarm  temp7_crit_alarm
   418  	// name              temp1_input       temp2_input       temp3_input       temp4_input       temp5_input       temp6_input       temp7_input
   419  	// power/            temp1_label       temp2_label       temp3_label       temp4_label       temp5_label       temp6_label       temp7_label
   420  	// subsystem/        temp1_max         temp2_max         temp3_max         temp4_max         temp5_max         temp6_max         temp7_max
   421  	// temp1_crit        temp2_crit        temp3_crit        temp4_crit        temp5_crit        temp6_crit        temp7_crit        uevent
   422  	for _, file := range files {
   423  		filename := strings.Split(filepath.Base(file), "_")
   424  		if filename[1] == "label" {
   425  			// Do not try to read the temperature of the label file
   426  			continue
   427  		}
   428  
   429  		// Get the label of the temperature you are reading
   430  		var label string
   431  		c, _ := ioutil.ReadFile(filepath.Join(filepath.Dir(file), filename[0]+"_label"))
   432  		if c != nil {
   433  			//format the label from "Core 0" to "core0_"
   434  			label = fmt.Sprintf("%s_", strings.Join(strings.Split(strings.TrimSpace(strings.ToLower(string(c))), " "), ""))
   435  		}
   436  
   437  		// Get the name of the temperature you are reading
   438  		name, err := ioutil.ReadFile(filepath.Join(filepath.Dir(file), "name"))
   439  		if err != nil {
   440  			warns.Add(err)
   441  			continue
   442  		}
   443  
   444  		// Get the temperature reading
   445  		current, err := ioutil.ReadFile(file)
   446  		if err != nil {
   447  			warns.Add(err)
   448  			continue
   449  		}
   450  		temperature, err := strconv.ParseFloat(strings.TrimSpace(string(current)), 64)
   451  		if err != nil {
   452  			warns.Add(err)
   453  			continue
   454  		}
   455  
   456  		tempName := strings.TrimSpace(strings.ToLower(string(strings.Join(filename[1:], ""))))
   457  		temperatures = append(temperatures, TemperatureStat{
   458  			SensorKey:   fmt.Sprintf("%s_%s%s", strings.TrimSpace(string(name)), label, tempName),
   459  			Temperature: temperature / 1000.0,
   460  		})
   461  	}
   462  	return temperatures, warns.Reference()
   463  }
   464  

View as plain text