mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-23 17:40:25 +08:00
438 lines
10 KiB
Go
438 lines
10 KiB
Go
|
|
package machine
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bufio"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"sort"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
type FSInfo struct {
|
||
|
|
MountPoint string
|
||
|
|
Used uint64
|
||
|
|
Free uint64
|
||
|
|
}
|
||
|
|
|
||
|
|
type NetIntfInfo struct {
|
||
|
|
IPv4 string
|
||
|
|
IPv6 string
|
||
|
|
Rx uint64
|
||
|
|
Tx uint64
|
||
|
|
}
|
||
|
|
|
||
|
|
type cpuRaw struct {
|
||
|
|
User uint64 // time spent in user mode
|
||
|
|
Nice uint64 // time spent in user mode with low priority (nice)
|
||
|
|
System uint64 // time spent in system mode
|
||
|
|
Idle uint64 // time spent in the idle task
|
||
|
|
Iowait uint64 // time spent waiting for I/O to complete (since Linux 2.5.41)
|
||
|
|
Irq uint64 // time spent servicing interrupts (since 2.6.0-test4)
|
||
|
|
SoftIrq uint64 // time spent servicing softirqs (since 2.6.0-test4)
|
||
|
|
Steal uint64 // time spent in other OSes when running in a virtualized environment
|
||
|
|
Guest uint64 // time spent running a virtual CPU for guest operating systems under the control of the Linux kernel.
|
||
|
|
Total uint64 // total of all time fields
|
||
|
|
}
|
||
|
|
|
||
|
|
type CPUInfo struct {
|
||
|
|
User float32
|
||
|
|
Nice float32
|
||
|
|
System float32
|
||
|
|
Idle float32
|
||
|
|
Iowait float32
|
||
|
|
Irq float32
|
||
|
|
SoftIrq float32
|
||
|
|
Steal float32
|
||
|
|
Guest float32
|
||
|
|
}
|
||
|
|
|
||
|
|
type Stats struct {
|
||
|
|
Uptime time.Duration
|
||
|
|
Hostname string
|
||
|
|
Load1 string
|
||
|
|
Load5 string
|
||
|
|
Load10 string
|
||
|
|
RunningProcs string
|
||
|
|
TotalProcs string
|
||
|
|
MemTotal uint64
|
||
|
|
MemFree uint64
|
||
|
|
MemBuffers uint64
|
||
|
|
MemCached uint64
|
||
|
|
SwapTotal uint64
|
||
|
|
SwapFree uint64
|
||
|
|
FSInfos []FSInfo
|
||
|
|
NetIntf map[string]NetIntfInfo
|
||
|
|
CPU CPUInfo // or []CPUInfo to get all the cpu-core's stats?
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Cli) GetAllStats() *Stats {
|
||
|
|
res, _ := c.Run(getShellContent("stats"))
|
||
|
|
infos := strings.Split(*res, "-----")
|
||
|
|
stats := new(Stats)
|
||
|
|
getUptime(infos[0], stats)
|
||
|
|
getHostname(infos[1], stats)
|
||
|
|
getLoad(infos[2], stats)
|
||
|
|
getMemInfo(infos[3], stats)
|
||
|
|
getFSInfo(infos[4], stats)
|
||
|
|
getInterfaces(infos[5], stats)
|
||
|
|
getInterfaceInfo(infos[6], stats)
|
||
|
|
getCPU(infos[7], stats)
|
||
|
|
return stats
|
||
|
|
}
|
||
|
|
|
||
|
|
func getUptime(uptime string, stats *Stats) (err error) {
|
||
|
|
parts := strings.Fields(uptime)
|
||
|
|
if len(parts) == 2 {
|
||
|
|
var upsecs float64
|
||
|
|
upsecs, err = strconv.ParseFloat(parts[0], 64)
|
||
|
|
if err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
stats.Uptime = time.Duration(upsecs * 1e9)
|
||
|
|
}
|
||
|
|
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func getHostname(hostname string, stats *Stats) (err error) {
|
||
|
|
stats.Hostname = strings.TrimSpace(hostname)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func getLoad(loadInfo string, stats *Stats) (err error) {
|
||
|
|
parts := strings.Fields(loadInfo)
|
||
|
|
if len(parts) == 5 {
|
||
|
|
stats.Load1 = parts[0]
|
||
|
|
stats.Load5 = parts[1]
|
||
|
|
stats.Load10 = parts[2]
|
||
|
|
if i := strings.Index(parts[3], "/"); i != -1 {
|
||
|
|
stats.RunningProcs = parts[3][0:i]
|
||
|
|
if i+1 < len(parts[3]) {
|
||
|
|
stats.TotalProcs = parts[3][i+1:]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func getMemInfo(memInfo string, stats *Stats) (err error) {
|
||
|
|
// "/bin/cat /proc/meminfo"
|
||
|
|
scanner := bufio.NewScanner(strings.NewReader(memInfo))
|
||
|
|
for scanner.Scan() {
|
||
|
|
line := scanner.Text()
|
||
|
|
parts := strings.Fields(line)
|
||
|
|
if len(parts) == 3 {
|
||
|
|
val, err := strconv.ParseUint(parts[1], 10, 64)
|
||
|
|
if err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
val *= 1024
|
||
|
|
switch parts[0] {
|
||
|
|
case "MemTotal:":
|
||
|
|
stats.MemTotal = val
|
||
|
|
case "MemFree:":
|
||
|
|
stats.MemFree = val
|
||
|
|
case "Buffers:":
|
||
|
|
stats.MemBuffers = val
|
||
|
|
case "Cached:":
|
||
|
|
stats.MemCached = val
|
||
|
|
case "SwapTotal:":
|
||
|
|
stats.SwapTotal = val
|
||
|
|
case "SwapFree:":
|
||
|
|
stats.SwapFree = val
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func getFSInfo(fsInfo string, stats *Stats) (err error) {
|
||
|
|
// "/bin/df -B1"
|
||
|
|
scanner := bufio.NewScanner(strings.NewReader(fsInfo))
|
||
|
|
flag := 0
|
||
|
|
for scanner.Scan() {
|
||
|
|
line := scanner.Text()
|
||
|
|
parts := strings.Fields(line)
|
||
|
|
n := len(parts)
|
||
|
|
dev := n > 0 && strings.Index(parts[0], "/dev/") == 0
|
||
|
|
if n == 1 && dev {
|
||
|
|
flag = 1
|
||
|
|
} else if (n == 5 && flag == 1) || (n == 6 && dev) {
|
||
|
|
i := flag
|
||
|
|
flag = 0
|
||
|
|
used, err := strconv.ParseUint(parts[2-i], 10, 64)
|
||
|
|
if err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
free, err := strconv.ParseUint(parts[3-i], 10, 64)
|
||
|
|
if err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
stats.FSInfos = append(stats.FSInfos, FSInfo{
|
||
|
|
parts[5-i], used, free,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func getInterfaces(iInfo string, stats *Stats) (err error) {
|
||
|
|
// "/sbin/ip -o addr"
|
||
|
|
if stats.NetIntf == nil {
|
||
|
|
stats.NetIntf = make(map[string]NetIntfInfo)
|
||
|
|
}
|
||
|
|
|
||
|
|
scanner := bufio.NewScanner(strings.NewReader(iInfo))
|
||
|
|
for scanner.Scan() {
|
||
|
|
line := scanner.Text()
|
||
|
|
parts := strings.Fields(line)
|
||
|
|
if len(parts) >= 4 && (parts[2] == "inet" || parts[2] == "inet6") {
|
||
|
|
ipv4 := parts[2] == "inet"
|
||
|
|
intfname := parts[1]
|
||
|
|
if info, ok := stats.NetIntf[intfname]; ok {
|
||
|
|
if ipv4 {
|
||
|
|
info.IPv4 = parts[3]
|
||
|
|
} else {
|
||
|
|
info.IPv6 = parts[3]
|
||
|
|
}
|
||
|
|
stats.NetIntf[intfname] = info
|
||
|
|
} else {
|
||
|
|
info := NetIntfInfo{}
|
||
|
|
if ipv4 {
|
||
|
|
info.IPv4 = parts[3]
|
||
|
|
} else {
|
||
|
|
info.IPv6 = parts[3]
|
||
|
|
}
|
||
|
|
stats.NetIntf[intfname] = info
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func getInterfaceInfo(iInfo string, stats *Stats) (err error) {
|
||
|
|
// /bin/cat /proc/net/dev
|
||
|
|
if stats.NetIntf == nil {
|
||
|
|
return
|
||
|
|
} // should have been here already
|
||
|
|
|
||
|
|
scanner := bufio.NewScanner(strings.NewReader(iInfo))
|
||
|
|
for scanner.Scan() {
|
||
|
|
line := scanner.Text()
|
||
|
|
parts := strings.Fields(line)
|
||
|
|
if len(parts) == 17 {
|
||
|
|
intf := strings.TrimSpace(parts[0])
|
||
|
|
intf = strings.TrimSuffix(intf, ":")
|
||
|
|
if info, ok := stats.NetIntf[intf]; ok {
|
||
|
|
rx, err := strconv.ParseUint(parts[1], 10, 64)
|
||
|
|
if err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
tx, err := strconv.ParseUint(parts[9], 10, 64)
|
||
|
|
if err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
info.Rx = rx
|
||
|
|
info.Tx = tx
|
||
|
|
stats.NetIntf[intf] = info
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func parseCPUFields(fields []string, stat *cpuRaw) {
|
||
|
|
numFields := len(fields)
|
||
|
|
for i := 1; i < numFields; i++ {
|
||
|
|
val, err := strconv.ParseUint(fields[i], 10, 64)
|
||
|
|
if err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
stat.Total += val
|
||
|
|
switch i {
|
||
|
|
case 1:
|
||
|
|
stat.User = val
|
||
|
|
case 2:
|
||
|
|
stat.Nice = val
|
||
|
|
case 3:
|
||
|
|
stat.System = val
|
||
|
|
case 4:
|
||
|
|
stat.Idle = val
|
||
|
|
case 5:
|
||
|
|
stat.Iowait = val
|
||
|
|
case 6:
|
||
|
|
stat.Irq = val
|
||
|
|
case 7:
|
||
|
|
stat.SoftIrq = val
|
||
|
|
case 8:
|
||
|
|
stat.Steal = val
|
||
|
|
case 9:
|
||
|
|
stat.Guest = val
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// the CPU stats that were fetched last time round
|
||
|
|
var preCPU cpuRaw
|
||
|
|
|
||
|
|
func getCPU(cpuInfo string, stats *Stats) (err error) {
|
||
|
|
var (
|
||
|
|
nowCPU cpuRaw
|
||
|
|
total float32
|
||
|
|
)
|
||
|
|
|
||
|
|
scanner := bufio.NewScanner(strings.NewReader(cpuInfo))
|
||
|
|
for scanner.Scan() {
|
||
|
|
line := scanner.Text()
|
||
|
|
fields := strings.Fields(line)
|
||
|
|
if len(fields) > 0 && fields[0] == "cpu" { // changing here if want to get every cpu-core's stats
|
||
|
|
parseCPUFields(fields, &nowCPU)
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if preCPU.Total == 0 { // having no pre raw cpu data
|
||
|
|
goto END
|
||
|
|
}
|
||
|
|
|
||
|
|
total = float32(nowCPU.Total - preCPU.Total)
|
||
|
|
stats.CPU.User = float32(nowCPU.User-preCPU.User) / total * 100
|
||
|
|
stats.CPU.Nice = float32(nowCPU.Nice-preCPU.Nice) / total * 100
|
||
|
|
stats.CPU.System = float32(nowCPU.System-preCPU.System) / total * 100
|
||
|
|
stats.CPU.Idle = float32(nowCPU.Idle-preCPU.Idle) / total * 100
|
||
|
|
stats.CPU.Iowait = float32(nowCPU.Iowait-preCPU.Iowait) / total * 100
|
||
|
|
stats.CPU.Irq = float32(nowCPU.Irq-preCPU.Irq) / total * 100
|
||
|
|
stats.CPU.SoftIrq = float32(nowCPU.SoftIrq-preCPU.SoftIrq) / total * 100
|
||
|
|
stats.CPU.Guest = float32(nowCPU.Guest-preCPU.Guest) / total * 100
|
||
|
|
END:
|
||
|
|
preCPU = nowCPU
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func fmtUptime(stats *Stats) string {
|
||
|
|
dur := stats.Uptime
|
||
|
|
dur = dur - (dur % time.Second)
|
||
|
|
var days int
|
||
|
|
for dur.Hours() > 24.0 {
|
||
|
|
days++
|
||
|
|
dur -= 24 * time.Hour
|
||
|
|
}
|
||
|
|
s1 := dur.String()
|
||
|
|
s2 := ""
|
||
|
|
if days > 0 {
|
||
|
|
s2 = fmt.Sprintf("%dd ", days)
|
||
|
|
}
|
||
|
|
for _, ch := range s1 {
|
||
|
|
s2 += string(ch)
|
||
|
|
if ch == 'h' || ch == 'm' {
|
||
|
|
s2 += " "
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return s2
|
||
|
|
}
|
||
|
|
|
||
|
|
func fmtBytes(val uint64) string {
|
||
|
|
if val < 1024 {
|
||
|
|
return fmt.Sprintf("%d bytes", val)
|
||
|
|
} else if val < 1024*1024 {
|
||
|
|
return fmt.Sprintf("%6.2f KiB", float64(val)/1024.0)
|
||
|
|
} else if val < 1024*1024*1024 {
|
||
|
|
return fmt.Sprintf("%6.2f MiB", float64(val)/1024.0/1024.0)
|
||
|
|
} else {
|
||
|
|
return fmt.Sprintf("%6.2f GiB", float64(val)/1024.0/1024.0/1024.0)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func ShowStats(output io.Writer, stats *Stats) {
|
||
|
|
used := stats.MemTotal - stats.MemFree - stats.MemBuffers - stats.MemCached
|
||
|
|
fmt.Fprintf(output,
|
||
|
|
`%s%s%s%s up %s%s%s
|
||
|
|
Load:
|
||
|
|
%s%s %s %s%s
|
||
|
|
CPU:
|
||
|
|
%s%.2f%s%% user, %s%.2f%s%% sys, %s%.2f%s%% nice, %s%.2f%s%% idle, %s%.2f%s%% iowait, %s%.2f%s%% hardirq, %s%.2f%s%% softirq, %s%.2f%s%% guest
|
||
|
|
Processes:
|
||
|
|
%s%s%s running of %s%s%s total
|
||
|
|
Memory:
|
||
|
|
free = %s%s%s
|
||
|
|
used = %s%s%s
|
||
|
|
buffers = %s%s%s
|
||
|
|
cached = %s%s%s
|
||
|
|
swap = %s%s%s free of %s%s%s
|
||
|
|
`,
|
||
|
|
escClear,
|
||
|
|
escBrightWhite, stats.Hostname, escReset,
|
||
|
|
escBrightWhite, fmtUptime(stats), escReset,
|
||
|
|
escBrightWhite, stats.Load1, stats.Load5, stats.Load10, escReset,
|
||
|
|
escBrightWhite, stats.CPU.User, escReset,
|
||
|
|
escBrightWhite, stats.CPU.System, escReset,
|
||
|
|
escBrightWhite, stats.CPU.Nice, escReset,
|
||
|
|
escBrightWhite, stats.CPU.Idle, escReset,
|
||
|
|
escBrightWhite, stats.CPU.Iowait, escReset,
|
||
|
|
escBrightWhite, stats.CPU.Irq, escReset,
|
||
|
|
escBrightWhite, stats.CPU.SoftIrq, escReset,
|
||
|
|
escBrightWhite, stats.CPU.Guest, escReset,
|
||
|
|
escBrightWhite, stats.RunningProcs, escReset,
|
||
|
|
escBrightWhite, stats.TotalProcs, escReset,
|
||
|
|
escBrightWhite, fmtBytes(stats.MemFree), escReset,
|
||
|
|
escBrightWhite, fmtBytes(used), escReset,
|
||
|
|
escBrightWhite, fmtBytes(stats.MemBuffers), escReset,
|
||
|
|
escBrightWhite, fmtBytes(stats.MemCached), escReset,
|
||
|
|
escBrightWhite, fmtBytes(stats.SwapFree), escReset,
|
||
|
|
escBrightWhite, fmtBytes(stats.SwapTotal), escReset,
|
||
|
|
)
|
||
|
|
if len(stats.FSInfos) > 0 {
|
||
|
|
fmt.Println("Filesystems:")
|
||
|
|
for _, fs := range stats.FSInfos {
|
||
|
|
fmt.Fprintf(output, " %s%8s%s: %s%s%s free of %s%s%s\n",
|
||
|
|
escBrightWhite, fs.MountPoint, escReset,
|
||
|
|
escBrightWhite, fmtBytes(fs.Free), escReset,
|
||
|
|
escBrightWhite, fmtBytes(fs.Used+fs.Free), escReset,
|
||
|
|
)
|
||
|
|
}
|
||
|
|
fmt.Println()
|
||
|
|
}
|
||
|
|
if len(stats.NetIntf) > 0 {
|
||
|
|
fmt.Println("Network Interfaces:")
|
||
|
|
keys := make([]string, 0, len(stats.NetIntf))
|
||
|
|
for intf := range stats.NetIntf {
|
||
|
|
keys = append(keys, intf)
|
||
|
|
}
|
||
|
|
sort.Strings(keys)
|
||
|
|
for _, intf := range keys {
|
||
|
|
info := stats.NetIntf[intf]
|
||
|
|
fmt.Fprintf(output, " %s%s%s - %s%s%s",
|
||
|
|
escBrightWhite, intf, escReset,
|
||
|
|
escBrightWhite, info.IPv4, escReset,
|
||
|
|
)
|
||
|
|
if len(info.IPv6) > 0 {
|
||
|
|
fmt.Fprintf(output, ", %s%s%s\n",
|
||
|
|
escBrightWhite, info.IPv6, escReset,
|
||
|
|
)
|
||
|
|
} else {
|
||
|
|
fmt.Fprintf(output, "\n")
|
||
|
|
}
|
||
|
|
fmt.Fprintf(output, " rx = %s%s%s, tx = %s%s%s\n",
|
||
|
|
escBrightWhite, fmtBytes(info.Rx), escReset,
|
||
|
|
escBrightWhite, fmtBytes(info.Tx), escReset,
|
||
|
|
)
|
||
|
|
fmt.Println()
|
||
|
|
}
|
||
|
|
fmt.Println()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const (
|
||
|
|
escClear = ""
|
||
|
|
escRed = ""
|
||
|
|
escReset = ""
|
||
|
|
escBrightWhite = ""
|
||
|
|
)
|