Files
wehub-resource-sync 36b3af2e3d
PR Check / Code Quality: Format (push) Failing after 1s
PR Check / Code Quality: Lint (darwin) (push) Failing after 0s
PR Check / Code Quality: Lint (freebsd) (push) Failing after 1s
PR Check / Code Quality: Lint (windows) (push) Failing after 1s
PR Check / Code Quality: Lint (linux) (push) Failing after 1s
PR Check / Security: Vulnerability Scan (push) Failing after 0s
Update Documentation / update-docs (push) Failing after 2s
PR Check / Code Quality: Vendor (push) Failing after 1s
PR Check / Code Quality: Coverage (push) Failing after 0s
PR Check / Tests: Unit (macos-latest) (push) Has been cancelled
PR Check / Tests: Unit (ubuntu-24.04) (push) Has been cancelled
PR Check / Tests: Unit (ubuntu-24.04-arm) (push) Has been cancelled
PR Check / Tests: Unit (windows-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:22:06 +08:00

171 lines
5.0 KiB
Go

//go:build windows
package proc
import (
"sync"
"syscall"
"time"
"unsafe"
"github.com/pranshuparmar/witr/pkg/model"
)
var procGlobalMemoryStatusEx = modkernel32.NewProc("GlobalMemoryStatusEx")
// memoryStatusEx mirrors MEMORYSTATUSEX. dwLength must be set to sizeof(struct)
// before GlobalMemoryStatusEx is called.
type memoryStatusEx struct {
Length uint32
MemoryLoad uint32
TotalPhys uint64
AvailPhys uint64
TotalPageFile uint64
AvailPageFile uint64
TotalVirtual uint64
AvailVirtual uint64
AvailExtendedVirtual uint64
}
var (
totalPhysOnce sync.Once
totalPhysVal uint64
)
// windowsTotalPhysicalMemory returns total physical RAM in bytes. The value is
// fixed for the life of the process, so it's read once and cached.
func windowsTotalPhysicalMemory() uint64 {
totalPhysOnce.Do(func() {
var ms memoryStatusEx
ms.Length = uint32(unsafe.Sizeof(ms))
if ret, _, _ := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&ms))); ret != 0 {
totalPhysVal = ms.TotalPhys
}
})
return totalPhysVal
}
// windowsMemoryPercent expresses a resident byte count as a percentage of total
// physical RAM.
func windowsMemoryPercent(rss uint64) float64 {
total := windowsTotalPhysicalMemory()
if total == 0 {
return 0
}
return float64(rss) / float64(total) * 100.0
}
// windowsProcMetrics opens a process once and returns its resident set size
// (WorkingSetSize, in bytes), lifetime-average CPU%, total CPU time, and start
// time. All are zero values when the handle can't be opened, which is expected
// for protected/system processes without elevation — callers keep the identity
// fields and report zeros rather than failing. It reads only fixed-size kernel
// structures (no remote process-memory access), so it's safe to call across the
// full process list.
func windowsProcMetrics(pid int) (rss uint64, cpu float64, cpuTime time.Duration, started time.Time) {
handle, err := syscall.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
if err != nil {
return 0, 0, 0, time.Time{}
}
defer syscall.CloseHandle(handle)
var pmc processMemoryCountersEx
pmc.CB = uint32(unsafe.Sizeof(pmc))
if ret, _, _ := procGetProcessMemoryInfo.Call(
uintptr(handle),
uintptr(unsafe.Pointer(&pmc)),
uintptr(pmc.CB),
); ret != 0 {
rss = uint64(pmc.WorkingSetSize)
}
var creation, exit, kernel, user syscall.Filetime
if ret, _, _ := procGetProcessTimes.Call(
uintptr(handle),
uintptr(unsafe.Pointer(&creation)),
uintptr(unsafe.Pointer(&exit)),
uintptr(unsafe.Pointer(&kernel)),
uintptr(unsafe.Pointer(&user)),
); ret != 0 {
started = time.Unix(0, creation.Nanoseconds())
cpuTime = filetimeTicksToDuration(kernel) + filetimeTicksToDuration(user)
wall := time.Since(started)
if wall > 0 {
cpu = float64(cpuTime) / float64(wall) * 100.0
}
}
return rss, cpu, cpuTime, started
}
// windowsHealth derives a health status from a process's resident memory and
// total CPU time. Windows has no zombie/stopped equivalent, so it reports the
// resource conditions, matching the Unix >2h CPU and >1GiB RSS thresholds.
func windowsHealth(rss uint64, cpuTime time.Duration) string {
switch {
case cpuTime > 2*time.Hour:
return "high-cpu"
case rss > 1<<30: // 1 GiB
return "high-mem"
default:
return "healthy"
}
}
// GetResourceContext returns CPU and memory usage for a process.
//
// CPU usage is the lifetime average — total kernel + user CPU time divided
// by wall-clock time since the process started — not an instantaneous %.
// Memory is the private commit (PrivateUsage).
func GetResourceContext(pid int) *model.ResourceContext {
handle, err := syscall.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
if err != nil {
return nil
}
defer syscall.CloseHandle(handle)
var (
cpu float64
mem uint64
)
var pmc processMemoryCountersEx
pmc.CB = uint32(unsafe.Sizeof(pmc))
if ret, _, _ := procGetProcessMemoryInfo.Call(
uintptr(handle),
uintptr(unsafe.Pointer(&pmc)),
uintptr(pmc.CB),
); ret != 0 {
mem = uint64(pmc.PrivateUsage)
}
var creation, exit, kernel, user syscall.Filetime
if ret, _, _ := procGetProcessTimes.Call(
uintptr(handle),
uintptr(unsafe.Pointer(&creation)),
uintptr(unsafe.Pointer(&exit)),
uintptr(unsafe.Pointer(&kernel)),
uintptr(unsafe.Pointer(&user)),
); ret != 0 {
startTime := time.Unix(0, creation.Nanoseconds())
wall := time.Since(startTime)
cpuTime := filetimeTicksToDuration(kernel) + filetimeTicksToDuration(user)
if wall > 0 {
cpu = float64(cpuTime) / float64(wall) * 100.0
}
}
return &model.ResourceContext{
CPUUsage: cpu,
MemoryUsage: mem,
}
}
// filetimeTicksToDuration treats a Filetime as a count of 100-ns ticks (the
// shape kernel/user time take in GetProcessTimes), not as an absolute
// timestamp.
func filetimeTicksToDuration(ft syscall.Filetime) time.Duration {
ticks := uint64(ft.HighDateTime)<<32 | uint64(ft.LowDateTime)
return time.Duration(ticks) * 100 * time.Nanosecond
}