- 重构 sysconf 为文档模型 INI Parser 与 Config Framework - 强化 hosts 解析、插入校验、写回与异常输入处理 - 完善 StarCmd 生命周期、等待 API、流式输出与 IO 重定向 - 扩展跨平台文件时间、文件锁、内存、进程与网络能力 - 将 Windows 进程适配更新到 b612.me/wincmd v0.1.0 - 移除本地 wincmd/win32api replace,改用发布版依赖 - 将最低 Go 版本提升到 1.18 - 补充 hosts、sysconf、FileLock、StarCmd 与平台适配回归测试
248 lines
5.5 KiB
Go
248 lines
5.5 KiB
Go
//go:build linux
|
|
// +build linux
|
|
|
|
package staros
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/user"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
"unsafe"
|
|
)
|
|
|
|
var (
|
|
clockTicksOnce sync.Once
|
|
clockTicksValue uint64 = 100
|
|
)
|
|
|
|
// StartTime 开机时间
|
|
func StartTime() time.Time {
|
|
tmp, _ := readAsString("/proc/stat")
|
|
data := splitBy(ReplaceByte9(tmp), " ")
|
|
btime, _ := strconv.ParseInt(strings.TrimSpace(data["btime"]), 10, 64)
|
|
return time.Unix(btime, 0)
|
|
}
|
|
|
|
// IsRoot 当前是否是管理员用户
|
|
func IsRoot() bool {
|
|
uid, err := user.Current()
|
|
return err == nil && uid != nil && uid.Uid == "0"
|
|
}
|
|
|
|
func Whoami() (uid, gid int, uname, gname, home string, err error) {
|
|
var me *user.User
|
|
var gup *user.Group
|
|
me, err = user.Current()
|
|
if err != nil {
|
|
return
|
|
}
|
|
uid, _ = strconv.Atoi(me.Uid)
|
|
gid, _ = strconv.Atoi(me.Gid)
|
|
home = me.HomeDir
|
|
uname = me.Username
|
|
gup, err = user.LookupGroupId(me.Gid)
|
|
if err != nil {
|
|
return
|
|
}
|
|
gname = gup.Name
|
|
return
|
|
}
|
|
|
|
func clockTicks() uint64 {
|
|
clockTicksOnce.Do(initClockTicks)
|
|
if clockTicksValue == 0 {
|
|
return 100
|
|
}
|
|
return clockTicksValue
|
|
}
|
|
|
|
func initClockTicks() {
|
|
ticks, err := readClockTicksFromAuxv()
|
|
if err != nil || ticks == 0 {
|
|
return
|
|
}
|
|
clockTicksValue = ticks
|
|
}
|
|
|
|
func readClockTicksFromAuxv() (uint64, error) {
|
|
data, err := os.ReadFile("/proc/self/auxv")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
wordSize := int(unsafe.Sizeof(uintptr(0)))
|
|
if wordSize != 4 && wordSize != 8 {
|
|
return 0, errors.New("unsupported pointer size")
|
|
}
|
|
order := nativeEndian()
|
|
entrySize := wordSize * 2
|
|
for offset := 0; offset+entrySize <= len(data); offset += entrySize {
|
|
key := readAuxvWord(data[offset:offset+wordSize], order)
|
|
val := readAuxvWord(data[offset+wordSize:offset+entrySize], order)
|
|
if key == 0 {
|
|
break
|
|
}
|
|
if key == 17 {
|
|
return val, nil
|
|
}
|
|
}
|
|
return 0, errors.New("AT_CLKTCK not found")
|
|
}
|
|
|
|
func readAuxvWord(data []byte, order binary.ByteOrder) uint64 {
|
|
if len(data) == 4 {
|
|
return uint64(order.Uint32(data))
|
|
}
|
|
return order.Uint64(data)
|
|
}
|
|
|
|
func nativeEndian() binary.ByteOrder {
|
|
var n uint16 = 1
|
|
if *(*byte)(unsafe.Pointer(&n)) == 1 {
|
|
return binary.LittleEndian
|
|
}
|
|
return binary.BigEndian
|
|
}
|
|
|
|
func cpuUsageOverDuration(delta float64, sleep time.Duration) float64 {
|
|
if delta < 0 || sleep <= 0 {
|
|
return 0
|
|
}
|
|
seconds := sleep.Seconds()
|
|
if seconds <= 0 {
|
|
return 0
|
|
}
|
|
return delta / seconds * 100
|
|
}
|
|
|
|
func cpuUsagePercent(busyTicks, totalTicks float64) float64 {
|
|
if busyTicks < 0 || totalTicks <= 0 {
|
|
return 0
|
|
}
|
|
return 100 * busyTicks / totalTicks
|
|
}
|
|
|
|
func getCPUSample() (idle, total uint64) {
|
|
contents, err := ioutil.ReadFile("/proc/stat")
|
|
if err != nil {
|
|
return
|
|
}
|
|
lines := strings.Split(string(contents), "\n")
|
|
for _, line := range lines {
|
|
fields := strings.Fields(line)
|
|
if len(fields) == 0 {
|
|
continue
|
|
}
|
|
if fields[0] == "cpu" {
|
|
numFields := len(fields)
|
|
for i := 1; i < numFields; i++ {
|
|
val, err := strconv.ParseUint(fields[i], 10, 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
total += val // tally up all the numbers to get total ticks
|
|
if i == 4 || i == 5 { // idle is the 5th field in the cpu line
|
|
idle += val
|
|
}
|
|
}
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
func splitProcStat(content []byte) []string {
|
|
nameStart := bytes.IndexByte(content, '(')
|
|
nameEnd := bytes.LastIndexByte(content, ')')
|
|
restFields := strings.Fields(string(content[nameEnd+2:])) // +2 skip ') '
|
|
name := content[nameStart+1 : nameEnd]
|
|
pid := strings.TrimSpace(string(content[:nameStart]))
|
|
fields := make([]string, 3, len(restFields)+3)
|
|
fields[1] = string(pid)
|
|
fields[2] = string(name)
|
|
fields = append(fields, restFields...)
|
|
return fields
|
|
}
|
|
|
|
func getCPUSampleByPid(pid int) float64 {
|
|
contents, err := ioutil.ReadFile("/proc/" + strconv.Itoa(pid) + "/stat")
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
fields := splitProcStat(contents)
|
|
utime, err := strconv.ParseFloat(fields[14], 64)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
|
|
stime, err := strconv.ParseFloat(fields[15], 64)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
|
|
// There is no such thing as iotime in stat file. As an approximation, we
|
|
// will use delayacct_blkio_ticks (aggregated block I/O delays, as per Linux
|
|
// docs). Note: I am assuming at least Linux 2.6.18
|
|
var iotime float64
|
|
if len(fields) > 42 {
|
|
iotime, err = strconv.ParseFloat(fields[42], 64)
|
|
if err != nil {
|
|
iotime = 0 // Ancient linux version, most likely
|
|
}
|
|
} else {
|
|
iotime = 0 // e.g. SmartOS containers
|
|
}
|
|
ticks := float64(clockTicks())
|
|
return utime/ticks + stime/ticks + iotime/ticks
|
|
}
|
|
func CpuUsageByPid(pid int, sleep time.Duration) float64 {
|
|
if sleep <= 0 {
|
|
return 0
|
|
}
|
|
total1 := getCPUSampleByPid(pid)
|
|
time.Sleep(sleep)
|
|
total2 := getCPUSampleByPid(pid)
|
|
return cpuUsageOverDuration(total2-total1, sleep)
|
|
}
|
|
|
|
// CpuUsage 获取CPU使用量
|
|
func CpuUsage(sleep time.Duration) float64 {
|
|
if sleep <= 0 {
|
|
return 0
|
|
}
|
|
idle0, total0 := getCPUSample()
|
|
time.Sleep(sleep)
|
|
idle1, total1 := getCPUSample()
|
|
idleTicks := float64(idle1 - idle0)
|
|
totalTicks := float64(total1 - total0)
|
|
cpuUsage := cpuUsagePercent(totalTicks-idleTicks, totalTicks)
|
|
return cpuUsage
|
|
//fmt.Printf("CPU usage is %f%% [busy: %f, total: %f]\n", cpuUsage, totalTicks-idleTicks, totalTicks)
|
|
}
|
|
|
|
func DiskUsage(path string) (disk DiskStatus) {
|
|
disk, _ = DiskUsageE(path)
|
|
return
|
|
}
|
|
|
|
func DiskUsageE(path string) (disk DiskStatus, err error) {
|
|
if path == "" {
|
|
path = "."
|
|
}
|
|
fs := syscall.Statfs_t{}
|
|
if err = syscall.Statfs(path, &fs); err != nil {
|
|
return
|
|
}
|
|
disk.All = fs.Blocks * uint64(fs.Bsize)
|
|
disk.Free = fs.Bfree * uint64(fs.Bsize)
|
|
disk.Available = fs.Bavail * uint64(fs.Bsize)
|
|
disk.Used = disk.All - disk.Free
|
|
return
|
|
}
|