|
|
@@ -0,0 +1,212 @@
|
|
|
+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)
|
|
|
+ }
|
|
|
+}
|