feat: add Wails runtime library and initial application setup
- Created package.json for Wails JavaScript runtime library. - Added TypeScript definitions for runtime functions. - Implemented runtime.js with event handling and logging functions. - Initialized Go module with dependencies for Wails and other libraries. - Added main.go for application entry point and asset embedding. - Configured wails.json for application settings and build commands.
This commit is contained in:
140
backend/sysinfo.go
Normal file
140
backend/sysinfo.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
ps "github.com/shirou/gopsutil/v3/process"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
type SysInfo struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func NewSysInfo(ctx context.Context) *SysInfo {
|
||||
cctx, cancel := context.WithCancel(ctx)
|
||||
s := &SysInfo{ctx: cctx, cancel: cancel}
|
||||
go s.startEmitter()
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *SysInfo) startEmitter() {
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
info, _ := s.CollectOnce()
|
||||
// try to attach GPU info if available
|
||||
name, tot, used, util := queryGPU(s.ctx)
|
||||
if name != "" {
|
||||
info.GPUName = name
|
||||
info.GPUTotal = tot
|
||||
info.GPUUsed = used
|
||||
info.GPUUtil = util
|
||||
}
|
||||
// emit via Wails runtime
|
||||
runtime.EventsEmit(s.ctx, "metrics", info)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ProcessInfo struct {
|
||||
PID int32 `json:"pid"`
|
||||
Name string `json:"name"`
|
||||
CPU float64 `json:"cpu"`
|
||||
Mem uint64 `json:"mem"`
|
||||
}
|
||||
|
||||
type Metrics struct {
|
||||
CPUPercent float64 `json:"cpu_percent"`
|
||||
TotalMem uint64 `json:"total_mem"`
|
||||
FreeMem uint64 `json:"free_mem"`
|
||||
Processes []ProcessInfo `json:"processes"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
GPUName string `json:"gpu_name,omitempty"`
|
||||
GPUTotal uint64 `json:"gpu_total_mem,omitempty"`
|
||||
GPUUsed uint64 `json:"gpu_used_mem,omitempty"`
|
||||
GPUUtil float64 `json:"gpu_util_percent,omitempty"`
|
||||
}
|
||||
|
||||
// CollectOnce gathers a snapshot of system metrics.
|
||||
func (s *SysInfo) CollectOnce() (*Metrics, error) {
|
||||
cpuPercents, _ := cpu.PercentWithContext(s.ctx, 0, false)
|
||||
vm, _ := mem.VirtualMemory()
|
||||
|
||||
// collect top processes by CPU (sample few)
|
||||
procs, _ := ps.Processes()
|
||||
var list []ProcessInfo
|
||||
for i, p := range procs {
|
||||
if i >= 30 {
|
||||
break
|
||||
}
|
||||
name, _ := p.Name()
|
||||
cpuPct, _ := p.CPUPercent()
|
||||
memInfo, _ := p.MemoryInfo()
|
||||
var memBytes uint64
|
||||
if memInfo != nil {
|
||||
memBytes = memInfo.RSS
|
||||
}
|
||||
list = append(list, ProcessInfo{PID: p.Pid, Name: name, CPU: cpuPct, Mem: memBytes})
|
||||
}
|
||||
|
||||
var cpuVal float64
|
||||
if len(cpuPercents) > 0 {
|
||||
cpuVal = cpuPercents[0]
|
||||
}
|
||||
|
||||
return &Metrics{
|
||||
CPUPercent: cpuVal,
|
||||
TotalMem: vm.Total,
|
||||
FreeMem: vm.Available,
|
||||
Processes: list,
|
||||
Timestamp: time.Now().UnixMilli(),
|
||||
GPUName: "",
|
||||
GPUTotal: 0,
|
||||
GPUUsed: 0,
|
||||
GPUUtil: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// try to query GPU info via nvidia-smi; returns name, totalMB, usedMB, utilPercent
|
||||
func queryGPU(ctx context.Context) (string, uint64, uint64, float64) {
|
||||
// Use nvidia-smi if available
|
||||
cmd := exec.CommandContext(ctx, "nvidia-smi", "--query-gpu=name,memory.total,memory.used,utilization.gpu", "--format=csv,noheader,nounits")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", 0, 0, 0
|
||||
}
|
||||
line := strings.TrimSpace(string(out))
|
||||
if line == "" {
|
||||
return "", 0, 0, 0
|
||||
}
|
||||
// fields: name, totalMB, usedMB, util
|
||||
parts := strings.Split(line, ",")
|
||||
if len(parts) < 4 {
|
||||
return "", 0, 0, 0
|
||||
}
|
||||
name := strings.TrimSpace(parts[0])
|
||||
totStr := strings.TrimSpace(parts[1])
|
||||
usedStr := strings.TrimSpace(parts[2])
|
||||
utilStr := strings.TrimSpace(parts[3])
|
||||
tot, _ := strconv.ParseUint(strings.Fields(totStr)[0], 10, 64)
|
||||
used, _ := strconv.ParseUint(strings.Fields(usedStr)[0], 10, 64)
|
||||
utilVal, _ := strconv.ParseFloat(strings.Fields(utilStr)[0], 64)
|
||||
return name, tot * 1024 * 1024, used * 1024 * 1024, utilVal
|
||||
}
|
||||
|
||||
// GetMetrics is an exported method callable from frontend.
|
||||
func (s *SysInfo) GetMetrics() (*Metrics, error) {
|
||||
return s.CollectOnce()
|
||||
}
|
||||
Reference in New Issue
Block a user