package main import ( "encoding/json" "fmt" "net/http" "os" "os/exec" "runtime" "strconv" "strings" "sync" ) var lastCPU struct { idle, total uint64 mu sync.Mutex } func initCPUUsage() { getCPUUsage() } func systemInfoHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") memPercent, memUsed, memTotal := getMemInfo() diskPercent, diskUsed, diskTotal := getDiskInfo() json.NewEncoder(w).Encode(map[string]any{ "cpu_usage": getCPUUsage(), "cpu_cores": getCPUCore(), "cpu_freq": getCPUFreq(), "mem_percent": memPercent, "mem_used": memUsed, "mem_total": memTotal, "disk_percent": diskPercent, "disk_used": diskUsed, "disk_total": diskTotal, }) } func getCPUUsage() int { lastCPU.mu.Lock() defer lastCPU.mu.Unlock() data, err := os.ReadFile("/proc/stat") if err != nil { return 0 } f := strings.Fields(strings.Split(string(data), "\n")[0]) if len(f) < 5 { return 0 } var idle, total uint64 for i := 1; i < len(f); i++ { v, err := strconv.ParseUint(f[i], 10, 64) if err != nil { continue } total += v // idle + iowait if i == 4 || i == 5 { idle += v } } if lastCPU.total == 0 { lastCPU.idle = idle lastCPU.total = total return 0 } dt := total - lastCPU.total di := idle - lastCPU.idle lastCPU.idle = idle lastCPU.total = total if dt == 0 { return 0 } return int((dt - di) * 100 / dt) } func getCPUCore() int { return runtime.NumCPU() } func getCPUFreq() string { var total, count int for i := 0; ; i++ { data, err := os.ReadFile(fmt.Sprintf("/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq", i)) if err != nil { break } freq, err := strconv.Atoi(strings.TrimSpace(string(data))) if err != nil { continue } total += freq / 1000 count++ } if count > 0 { return strconv.Itoa(total / count) } data, err := os.ReadFile("/proc/cpuinfo") if err == nil { for _, line := range strings.Split(string(data), "\n") { if strings.HasPrefix(line, "cpu MHz") { f := strings.Split(line, ":") if len(f) == 2 { v, err := strconv.ParseFloat(strings.TrimSpace(f[1]), 64) if err == nil { return strconv.Itoa(int(v)) } } } } } return noValue } func getMemInfo() (int, string, string) { data, err := os.ReadFile("/proc/meminfo") if err != nil { return 0, noValue, noValue } var total, avail uint64 for _, line := range strings.Split(string(data), "\n") { f := strings.Fields(line) if len(f) < 2 { continue } switch f[0] { case "MemTotal:": total, _ = strconv.ParseUint(f[1], 10, 64) case "MemAvailable:": avail, _ = strconv.ParseUint(f[1], 10, 64) } } if total == 0 { return 0, noValue, noValue } used := total - avail return int(used * 100 / total), formatSize(used * 1024), formatSize(total * 1024) } func getDiskInfo() (int, string, string) { out, err := exec.Command("df", "-h", "/").Output() if err != nil { return 0, noValue, noValue } lines := strings.Split(string(out), "\n") if len(lines) < 2 { return 0, noValue, noValue } f := strings.Fields(lines[1]) if len(f) < 5 { return 0, noValue, noValue } percent, err := strconv.Atoi(strings.TrimSuffix(f[4], "%")) if err != nil { percent = 0 } return percent, f[2], f[1] } func formatSize(size uint64) string { const ( KB = 1024 MB = KB * 1024 GB = MB * 1024 ) switch { case size >= GB: return fmt.Sprintf("%.1fGB", float64(size)/GB) case size >= MB: return fmt.Sprintf("%.0fMB", float64(size)/MB) case size >= KB: return fmt.Sprintf("%.0fKB", float64(size)/KB) default: return fmt.Sprintf("%dB", size) } }