page1_handler.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "os"
  7. "os/exec"
  8. "path/filepath"
  9. "runtime"
  10. "strconv"
  11. "strings"
  12. "sync"
  13. )
  14. var lastCPU struct {
  15. idle, total uint64
  16. mu sync.Mutex
  17. }
  18. func initCPUUsage() {
  19. getCPUUsage()
  20. }
  21. func systemInfoHandler(w http.ResponseWriter, r *http.Request) {
  22. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  23. memPercent, memUsed, memTotal := getMemInfo()
  24. diskPercent, diskUsed, diskTotal := getDiskInfo()
  25. json.NewEncoder(w).Encode(map[string]any{
  26. "cpu_usage": getCPUUsage(),
  27. "cpu_cores": getCPUCore(),
  28. "cpu_freq": getCPUFreq(),
  29. "mem_percent": memPercent,
  30. "mem_used": memUsed,
  31. "mem_total": memTotal,
  32. "disk_percent": diskPercent,
  33. "disk_used": diskUsed,
  34. "disk_total": diskTotal,
  35. })
  36. }
  37. func getCPUUsage() int {
  38. lastCPU.mu.Lock()
  39. defer lastCPU.mu.Unlock()
  40. data, err := os.ReadFile("/proc/stat")
  41. if err != nil {
  42. return 0
  43. }
  44. f := strings.Fields(strings.Split(string(data), "\n")[0])
  45. if len(f) < 5 {
  46. return 0
  47. }
  48. var idle, total uint64
  49. for i := 1; i < len(f); i++ {
  50. v, err := strconv.ParseUint(f[i], 10, 64)
  51. if err != nil {
  52. continue
  53. }
  54. total += v
  55. // idle + iowait
  56. if i == 4 || i == 5 {
  57. idle += v
  58. }
  59. }
  60. if lastCPU.total == 0 {
  61. lastCPU.idle = idle
  62. lastCPU.total = total
  63. return 0
  64. }
  65. dt := total - lastCPU.total
  66. di := idle - lastCPU.idle
  67. lastCPU.idle = idle
  68. lastCPU.total = total
  69. if dt == 0 {
  70. return 0
  71. }
  72. return int((dt - di) * 100 / dt)
  73. }
  74. func getCPUCore() int {
  75. return runtime.NumCPU()
  76. }
  77. func getCPUFreq() string {
  78. var total, count int
  79. for i := 0; ; i++ {
  80. data, err := os.ReadFile(fmt.Sprintf("/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq", i))
  81. if err != nil {
  82. break
  83. }
  84. freq, err := strconv.Atoi(strings.TrimSpace(string(data)))
  85. if err != nil {
  86. continue
  87. }
  88. total += freq / 1000
  89. count++
  90. }
  91. if count > 0 {
  92. return strconv.Itoa(total / count)
  93. }
  94. data, err := os.ReadFile("/proc/cpuinfo")
  95. if err == nil {
  96. for _, line := range strings.Split(string(data), "\n") {
  97. if strings.HasPrefix(line, "cpu MHz") {
  98. f := strings.Split(line, ":")
  99. if len(f) == 2 {
  100. v, err := strconv.ParseFloat(strings.TrimSpace(f[1]), 64)
  101. if err == nil {
  102. return strconv.Itoa(int(v))
  103. }
  104. }
  105. }
  106. }
  107. }
  108. return noValue
  109. }
  110. func getMemInfo() (int, string, string) {
  111. data, err := os.ReadFile("/proc/meminfo")
  112. if err != nil {
  113. return 0, noValue, noValue
  114. }
  115. var total, avail uint64
  116. for _, line := range strings.Split(string(data), "\n") {
  117. f := strings.Fields(line)
  118. if len(f) < 2 {
  119. continue
  120. }
  121. switch f[0] {
  122. case "MemTotal:":
  123. total, _ = strconv.ParseUint(f[1], 10, 64)
  124. case "MemAvailable:":
  125. avail, _ = strconv.ParseUint(f[1], 10, 64)
  126. }
  127. }
  128. if total == 0 {
  129. return 0, noValue, noValue
  130. }
  131. used := total - avail
  132. return int(used * 100 / total),
  133. formatSize(used * 1024),
  134. formatSize(total * 1024)
  135. }
  136. func getDiskInfo() (int, string, string) {
  137. out, err := exec.Command("df", "-h", "/").Output()
  138. if err != nil {
  139. return 0, noValue, noValue
  140. }
  141. lines := strings.Split(string(out), "\n")
  142. if len(lines) < 2 {
  143. return 0, noValue, noValue
  144. }
  145. f := strings.Fields(lines[1])
  146. if len(f) < 5 {
  147. return 0, noValue, noValue
  148. }
  149. percent, err := strconv.Atoi(strings.TrimSuffix(f[4], "%"))
  150. if err != nil {
  151. percent = 0
  152. }
  153. return percent, f[2], f[1]
  154. }
  155. func formatSize(size uint64) string {
  156. const (
  157. KB = 1024
  158. MB = KB * 1024
  159. GB = MB * 1024
  160. )
  161. switch {
  162. case size >= GB:
  163. return fmt.Sprintf("%.1fGB", float64(size)/GB)
  164. case size >= MB:
  165. return fmt.Sprintf("%.0fMB", float64(size)/MB)
  166. case size >= KB:
  167. return fmt.Sprintf("%.0fKB", float64(size)/KB)
  168. default:
  169. return fmt.Sprintf("%dB", size)
  170. }
  171. }
  172. func systemLoadAverageHandler(w http.ResponseWriter, r *http.Request) {
  173. data, err := os.ReadFile("/proc/loadavg")
  174. if err != nil {
  175. http.Error(w, err.Error(), http.StatusInternalServerError)
  176. return
  177. }
  178. fields := strings.Fields(string(data))
  179. if len(fields) < 3 {
  180. http.Error(w, "invalid /proc/loadavg", http.StatusInternalServerError)
  181. return
  182. }
  183. resp := struct {
  184. Load1m string `json:"load1m"`
  185. Load5m string `json:"load5m"`
  186. Load15m string `json:"load15m"`
  187. }{
  188. Load1m: formatLoad(fields[0]),
  189. Load5m: formatLoad(fields[1]),
  190. Load15m: formatLoad(fields[2]),
  191. }
  192. w.Header().Set("Content-Type", "application/json")
  193. json.NewEncoder(w).Encode(resp)
  194. }
  195. func formatLoad(v string) string {
  196. f, err := strconv.ParseFloat(v, 64)
  197. if err != nil {
  198. return noValue
  199. }
  200. return strconv.FormatFloat(f, 'f', 2, 64)
  201. }
  202. func serviceStatusHandler(w http.ResponseWriter, r *http.Request) {
  203. if r.Method != http.MethodGet {
  204. http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
  205. return
  206. }
  207. services := []struct {
  208. name string
  209. dir string
  210. }{
  211. {"yfkj-networkd.service", "/opt/yfkj/networkd.service"},
  212. {"yfkj-timesyncd.service", "/opt/yfkj/timesyncd.service"},
  213. {"yfkj-gnss.service", "/opt/yfkj/gnss.service"},
  214. {"yfkj-sshd-mqtt-bridge.service", "/opt/yfkj/sshd-mqtt-bridge.service"},
  215. {"yfkj-camera-capture.service", "/opt/yfkj/camera-capture.service"},
  216. {"yfkj-app-install.service", "/opt/yfkj/app-install.service"},
  217. {"yfkj-web-ui.service", "/opt/yfkj/web-ui.service"},
  218. {"yfkj-upgrade.service", "/opt/yfkj/upgrade.service"},
  219. }
  220. data := make(map[string]string, len(services)*2)
  221. for i, s := range services {
  222. n := i + 1
  223. status := noValue
  224. if out, err := exec.Command("systemctl", "is-active", s.name).Output(); err == nil {
  225. if strings.TrimSpace(string(out)) == "active" {
  226. status = "🟢运行中..."
  227. }
  228. }
  229. data[fmt.Sprintf("var%d", n)] = status
  230. if v, err := os.ReadFile(filepath.Join(s.dir, "version.txt")); err == nil {
  231. data[fmt.Sprintf("var%d%d", n, n)] = "v" + strings.TrimSpace(string(v))
  232. } else {
  233. data[fmt.Sprintf("var%d%d", n, n)] = noValue
  234. }
  235. }
  236. w.Header().Set("Content-Type", "application/json")
  237. json.NewEncoder(w).Encode(data)
  238. }