3x-ui/util/sys/sys_linux.go

182 lines
3.2 KiB
Go
Raw Normal View History

2023-03-17 16:07:49 +00:00
//go:build linux
2023-02-09 19:18:06 +00:00
// +build linux
package sys
import (
2025-09-16 12:15:18 +00:00
"bufio"
2023-02-09 19:18:06 +00:00
"bytes"
"fmt"
"io"
"os"
2025-09-16 12:15:18 +00:00
"strconv"
"strings"
"sync"
2023-02-09 19:18:06 +00:00
)
func getLinesNum(filename string) (int, error) {
file, err := os.Open(filename)
if err != nil {
return 0, err
}
defer file.Close()
sum := 0
buf := make([]byte, 8192)
for {
n, err := file.Read(buf)
var buffPosition int
for {
2023-05-22 23:13:15 +00:00
i := bytes.IndexByte(buf[buffPosition:n], '\n')
if i < 0 {
2023-02-09 19:18:06 +00:00
break
}
buffPosition += i + 1
sum++
}
if err == io.EOF {
2023-05-22 23:13:15 +00:00
break
2023-02-09 19:18:06 +00:00
} else if err != nil {
2023-05-22 23:13:15 +00:00
return 0, err
2023-02-09 19:18:06 +00:00
}
}
2023-05-22 23:13:15 +00:00
return sum, nil
2023-02-09 19:18:06 +00:00
}
func GetTCPCount() (int, error) {
root := HostProc()
tcp4, err := safeGetLinesNum(fmt.Sprintf("%v/net/tcp", root))
2023-02-09 19:18:06 +00:00
if err != nil {
2023-05-22 23:13:15 +00:00
return 0, err
2023-02-09 19:18:06 +00:00
}
tcp6, err := safeGetLinesNum(fmt.Sprintf("%v/net/tcp6", root))
2023-02-09 19:18:06 +00:00
if err != nil {
2023-05-22 23:13:15 +00:00
return 0, err
2023-02-09 19:18:06 +00:00
}
return tcp4 + tcp6, nil
}
func GetUDPCount() (int, error) {
root := HostProc()
udp4, err := safeGetLinesNum(fmt.Sprintf("%v/net/udp", root))
2023-02-09 19:18:06 +00:00
if err != nil {
2023-05-22 23:13:15 +00:00
return 0, err
2023-02-09 19:18:06 +00:00
}
udp6, err := safeGetLinesNum(fmt.Sprintf("%v/net/udp6", root))
2023-02-09 19:18:06 +00:00
if err != nil {
2023-05-22 23:13:15 +00:00
return 0, err
2023-02-09 19:18:06 +00:00
}
return udp4 + udp6, nil
}
func safeGetLinesNum(path string) (int, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return 0, nil
} else if err != nil {
return 0, err
}
return getLinesNum(path)
}
2025-09-16 12:15:18 +00:00
// --- CPU Utilization (Linux native) ---
var (
cpuMu sync.Mutex
lastTotal uint64
lastIdleAll uint64
hasLast bool
)
// CPUPercentRaw returns instantaneous total CPU utilization by reading /proc/stat.
// First call initializes and returns 0; subsequent calls return busy/total * 100.
func CPUPercentRaw() (float64, error) {
f, err := os.Open("/proc/stat")
if err != nil {
return 0, err
}
defer f.Close()
rd := bufio.NewReader(f)
line, err := rd.ReadString('\n')
if err != nil && err != io.EOF {
return 0, err
}
// Expect line like: cpu user nice system idle iowait irq softirq steal guest guest_nice
fields := strings.Fields(line)
if len(fields) < 5 || fields[0] != "cpu" {
return 0, fmt.Errorf("unexpected /proc/stat format")
}
var nums []uint64
for i := 1; i < len(fields); i++ {
v, err := strconv.ParseUint(fields[i], 10, 64)
if err != nil {
break
}
nums = append(nums, v)
}
if len(nums) < 4 { // need at least user,nice,system,idle
return 0, fmt.Errorf("insufficient cpu fields")
}
// Conform with standard Linux CPU accounting
var user, nice, system, idle, iowait, irq, softirq, steal uint64
user = nums[0]
if len(nums) > 1 {
nice = nums[1]
}
if len(nums) > 2 {
system = nums[2]
}
if len(nums) > 3 {
idle = nums[3]
}
if len(nums) > 4 {
iowait = nums[4]
}
if len(nums) > 5 {
irq = nums[5]
}
if len(nums) > 6 {
softirq = nums[6]
}
if len(nums) > 7 {
steal = nums[7]
}
idleAll := idle + iowait
nonIdle := user + nice + system + irq + softirq + steal
total := idleAll + nonIdle
cpuMu.Lock()
defer cpuMu.Unlock()
if !hasLast {
lastTotal = total
lastIdleAll = idleAll
hasLast = true
return 0, nil
}
totald := total - lastTotal
idled := idleAll - lastIdleAll
lastTotal = total
lastIdleAll = idleAll
if totald == 0 {
return 0, nil
}
busy := totald - idled
pct := float64(busy) / float64(totald) * 100.0
if pct > 100 {
pct = 100
}
return pct, nil
}