Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/util
diff options
context:
space:
mode:
authormhsanaei <ho3ein.sanaei@gmail.com>2025-09-16 15:15:18 +0300
committermhsanaei <ho3ein.sanaei@gmail.com>2025-09-16 15:15:18 +0300
commitecfffa882a159bb2a12954eed0f68eb4ec1a6120 (patch)
tree18b34d40f61fa6b4103fd663e6c7b1f8a614a487 /util
parent3af5026abe639e9cb5a9f695c02624ac6e173077 (diff)
CPU History, CPU Utilization
Diffstat (limited to 'util')
-rw-r--r--util/sys/sys_darwin.go71
-rw-r--r--util/sys/sys_linux.go100
-rw-r--r--util/sys/sys_windows.go81
3 files changed, 252 insertions, 0 deletions
diff --git a/util/sys/sys_darwin.go b/util/sys/sys_darwin.go
index 3f5b2072..6ecc78c0 100644
--- a/util/sys/sys_darwin.go
+++ b/util/sys/sys_darwin.go
@@ -4,7 +4,12 @@
package sys
import (
+ "encoding/binary"
+ "fmt"
+ "sync"
+
"github.com/shirou/gopsutil/v4/net"
+ "golang.org/x/sys/unix"
)
func GetTCPCount() (int, error) {
@@ -22,3 +27,69 @@ func GetUDPCount() (int, error) {
}
return len(stats), nil
}
+
+// --- CPU Utilization (macOS native) ---
+
+// sysctl kern.cp_time returns an array of 5 longs: user, nice, sys, idle, intr.
+// We compute utilization deltas without cgo.
+var (
+ cpuMu sync.Mutex
+ lastTotals [5]uint64
+ hasLastCPUT bool
+)
+
+func CPUPercentRaw() (float64, error) {
+ raw, err := unix.SysctlRaw("kern.cp_time")
+ if err != nil {
+ return 0, err
+ }
+ // Expect either 5*8 bytes (uint64) or 5*4 bytes (uint32)
+ var out [5]uint64
+ switch len(raw) {
+ case 5 * 8:
+ for i := 0; i < 5; i++ {
+ out[i] = binary.LittleEndian.Uint64(raw[i*8 : (i+1)*8])
+ }
+ case 5 * 4:
+ for i := 0; i < 5; i++ {
+ out[i] = uint64(binary.LittleEndian.Uint32(raw[i*4 : (i+1)*4]))
+ }
+ default:
+ return 0, fmt.Errorf("unexpected kern.cp_time size: %d", len(raw))
+ }
+
+ // user, nice, sys, idle, intr
+ user := out[0]
+ nice := out[1]
+ sysv := out[2]
+ idle := out[3]
+ intr := out[4]
+
+ cpuMu.Lock()
+ defer cpuMu.Unlock()
+
+ if !hasLastCPUT {
+ lastTotals = out
+ hasLastCPUT = true
+ return 0, nil
+ }
+
+ dUser := user - lastTotals[0]
+ dNice := nice - lastTotals[1]
+ dSys := sysv - lastTotals[2]
+ dIdle := idle - lastTotals[3]
+ dIntr := intr - lastTotals[4]
+
+ lastTotals = out
+
+ totald := dUser + dNice + dSys + dIdle + dIntr
+ if totald == 0 {
+ return 0, nil
+ }
+ busy := totald - dIdle
+ pct := float64(busy) / float64(totald) * 100.0
+ if pct > 100 {
+ pct = 100
+ }
+ return pct, nil
+}
diff --git a/util/sys/sys_linux.go b/util/sys/sys_linux.go
index d4e6e8ae..8a494d62 100644
--- a/util/sys/sys_linux.go
+++ b/util/sys/sys_linux.go
@@ -4,10 +4,14 @@
package sys
import (
+ "bufio"
"bytes"
"fmt"
"io"
"os"
+ "strconv"
+ "strings"
+ "sync"
)
func getLinesNum(filename string) (int, error) {
@@ -79,3 +83,99 @@ func safeGetLinesNum(path string) (int, error) {
}
return getLinesNum(path)
}
+
+// --- 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
+}
diff --git a/util/sys/sys_windows.go b/util/sys/sys_windows.go
index fd51d470..f3eae076 100644
--- a/util/sys/sys_windows.go
+++ b/util/sys/sys_windows.go
@@ -5,6 +5,9 @@ package sys
import (
"errors"
+ "sync"
+ "syscall"
+ "unsafe"
"github.com/shirou/gopsutil/v4/net"
)
@@ -28,3 +31,81 @@ func GetTCPCount() (int, error) {
func GetUDPCount() (int, error) {
return GetConnectionCount("udp")
}
+
+// --- CPU Utilization (Windows native) ---
+
+var (
+ modKernel32 = syscall.NewLazyDLL("kernel32.dll")
+ procGetSystemTimes = modKernel32.NewProc("GetSystemTimes")
+
+ cpuMu sync.Mutex
+ lastIdle uint64
+ lastKernel uint64
+ lastUser uint64
+ hasLast bool
+)
+
+type filetime struct {
+ LowDateTime uint32
+ HighDateTime uint32
+}
+
+func ftToUint64(ft filetime) uint64 {
+ return (uint64(ft.HighDateTime) << 32) | uint64(ft.LowDateTime)
+}
+
+// CPUPercentRaw returns the instantaneous total CPU utilization percentage using
+// Windows GetSystemTimes across all logical processors. The first call returns 0
+// as it initializes the baseline. Subsequent calls compute deltas.
+func CPUPercentRaw() (float64, error) {
+ var idleFT, kernelFT, userFT filetime
+ r1, _, e1 := procGetSystemTimes.Call(
+ uintptr(unsafe.Pointer(&idleFT)),
+ uintptr(unsafe.Pointer(&kernelFT)),
+ uintptr(unsafe.Pointer(&userFT)),
+ )
+ if r1 == 0 { // failure
+ if e1 != nil {
+ return 0, e1
+ }
+ return 0, syscall.GetLastError()
+ }
+
+ idle := ftToUint64(idleFT)
+ kernel := ftToUint64(kernelFT)
+ user := ftToUint64(userFT)
+
+ cpuMu.Lock()
+ defer cpuMu.Unlock()
+
+ if !hasLast {
+ lastIdle = idle
+ lastKernel = kernel
+ lastUser = user
+ hasLast = true
+ return 0, nil
+ }
+
+ idleDelta := idle - lastIdle
+ kernelDelta := kernel - lastKernel
+ userDelta := user - lastUser
+
+ // Update for next call
+ lastIdle = idle
+ lastKernel = kernel
+ lastUser = user
+
+ total := kernelDelta + userDelta
+ if total == 0 {
+ return 0, nil
+ }
+ // On Windows, kernel time includes idle time; busy = total - idle
+ busy := total - idleDelta
+
+ pct := float64(busy) / float64(total) * 100.0
+ // lower bound not needed; ratios of uint64 are non-negative
+ if pct > 100 {
+ pct = 100
+ }
+ return pct, nil
+}