host_linux.go 13 KB

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