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