analytic.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. package api
  2. import (
  3. "fmt"
  4. "github.com/0xJacky/Nginx-UI/server/internal/analytic"
  5. "github.com/0xJacky/Nginx-UI/server/internal/logger"
  6. "github.com/pkg/errors"
  7. "github.com/shirou/gopsutil/v3/cpu"
  8. "github.com/shirou/gopsutil/v3/disk"
  9. "github.com/shirou/gopsutil/v3/host"
  10. "github.com/shirou/gopsutil/v3/load"
  11. "github.com/shirou/gopsutil/v3/mem"
  12. "github.com/shirou/gopsutil/v3/net"
  13. "github.com/spf13/cast"
  14. "math"
  15. "net/http"
  16. "runtime"
  17. "time"
  18. "github.com/dustin/go-humanize"
  19. "github.com/gin-gonic/gin"
  20. "github.com/gorilla/websocket"
  21. )
  22. type CPUStat struct {
  23. User float64 `json:"user"`
  24. System float64 `json:"system"`
  25. Idle float64 `json:"idle"`
  26. Total float64 `json:"total"`
  27. }
  28. type MemStat struct {
  29. Total string `json:"total"`
  30. Used string `json:"used"`
  31. Cached string `json:"cached"`
  32. Free string `json:"free"`
  33. SwapUsed string `json:"swap_used"`
  34. SwapTotal string `json:"swap_total"`
  35. SwapCached string `json:"swap_cached"`
  36. SwapPercent float64 `json:"swap_percent"`
  37. Pressure float64 `json:"pressure"`
  38. }
  39. type DiskStat struct {
  40. Total string `json:"total"`
  41. Used string `json:"used"`
  42. Percentage float64 `json:"percentage"`
  43. Writes analytic.Usage `json:"writes"`
  44. Reads analytic.Usage `json:"reads"`
  45. }
  46. type Stat struct {
  47. Uptime uint64 `json:"uptime"`
  48. LoadAvg *load.AvgStat `json:"loadavg"`
  49. CPU CPUStat `json:"cpu"`
  50. Memory MemStat `json:"memory"`
  51. Disk DiskStat `json:"disk"`
  52. Network net.IOCountersStat `json:"network"`
  53. }
  54. func getMemoryStat() (MemStat, error) {
  55. memoryStat, err := mem.VirtualMemory()
  56. if err != nil {
  57. return MemStat{}, errors.Wrap(err, "error analytic getMemoryStat")
  58. }
  59. return MemStat{
  60. Total: humanize.Bytes(memoryStat.Total),
  61. Used: humanize.Bytes(memoryStat.Used),
  62. Cached: humanize.Bytes(memoryStat.Cached),
  63. Free: humanize.Bytes(memoryStat.Free),
  64. SwapUsed: humanize.Bytes(memoryStat.SwapTotal - memoryStat.SwapFree),
  65. SwapTotal: humanize.Bytes(memoryStat.SwapTotal),
  66. SwapCached: humanize.Bytes(memoryStat.SwapCached),
  67. SwapPercent: cast.ToFloat64(fmt.Sprintf("%.2f",
  68. 100*float64(memoryStat.SwapTotal-memoryStat.SwapFree)/math.Max(float64(memoryStat.SwapTotal), 1))),
  69. Pressure: cast.ToFloat64(fmt.Sprintf("%.2f", memoryStat.UsedPercent)),
  70. }, nil
  71. }
  72. func getDiskStat() (DiskStat, error) {
  73. diskUsage, err := disk.Usage(".")
  74. if err != nil {
  75. return DiskStat{}, errors.Wrap(err, "error analytic getDiskStat")
  76. }
  77. return DiskStat{
  78. Used: humanize.Bytes(diskUsage.Used),
  79. Total: humanize.Bytes(diskUsage.Total),
  80. Percentage: cast.ToFloat64(fmt.Sprintf("%.2f", diskUsage.UsedPercent)),
  81. Writes: analytic.DiskWriteRecord[len(analytic.DiskWriteRecord)-1],
  82. Reads: analytic.DiskReadRecord[len(analytic.DiskReadRecord)-1],
  83. }, nil
  84. }
  85. func Analytic(c *gin.Context) {
  86. var upGrader = websocket.Upgrader{
  87. CheckOrigin: func(r *http.Request) bool {
  88. return true
  89. },
  90. }
  91. // upgrade http to websocket
  92. ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
  93. if err != nil {
  94. logger.Error(err)
  95. return
  96. }
  97. defer ws.Close()
  98. var stat Stat
  99. for {
  100. stat.Memory, err = getMemoryStat()
  101. if err != nil {
  102. logger.Error(err)
  103. return
  104. }
  105. cpuTimesBefore, _ := cpu.Times(false)
  106. time.Sleep(1000 * time.Millisecond)
  107. cpuTimesAfter, _ := cpu.Times(false)
  108. threadNum := runtime.GOMAXPROCS(0)
  109. cpuUserUsage := (cpuTimesAfter[0].User - cpuTimesBefore[0].User) / (float64(1000*threadNum) / 1000)
  110. cpuSystemUsage := (cpuTimesAfter[0].System - cpuTimesBefore[0].System) / (float64(1000*threadNum) / 1000)
  111. stat.CPU = CPUStat{
  112. User: cast.ToFloat64(fmt.Sprintf("%.2f", cpuUserUsage*100)),
  113. System: cast.ToFloat64(fmt.Sprintf("%.2f", cpuSystemUsage*100)),
  114. Idle: cast.ToFloat64(fmt.Sprintf("%.2f", (1-cpuUserUsage-cpuSystemUsage)*100)),
  115. Total: cast.ToFloat64(fmt.Sprintf("%.2f", (cpuUserUsage+cpuSystemUsage)*100)),
  116. }
  117. stat.Uptime, _ = host.Uptime()
  118. stat.LoadAvg, _ = load.Avg()
  119. stat.Disk, err = getDiskStat()
  120. if err != nil {
  121. logger.Error(err)
  122. return
  123. }
  124. network, _ := net.IOCounters(false)
  125. if len(network) > 0 {
  126. stat.Network = network[0]
  127. }
  128. // write
  129. err = ws.WriteJSON(stat)
  130. if err != nil {
  131. logger.Error(err)
  132. break
  133. }
  134. time.Sleep(800 * time.Microsecond)
  135. }
  136. }
  137. func GetAnalyticInit(c *gin.Context) {
  138. cpuInfo, _ := cpu.Info()
  139. network, _ := net.IOCounters(false)
  140. memory, err := getMemoryStat()
  141. if err != nil {
  142. logger.Error(err)
  143. return
  144. }
  145. diskStat, err := getDiskStat()
  146. if err != nil {
  147. logger.Error(err)
  148. return
  149. }
  150. var _net net.IOCountersStat
  151. if len(network) > 0 {
  152. _net = network[0]
  153. }
  154. hostInfo, _ := host.Info()
  155. switch hostInfo.Platform {
  156. case "ubuntu":
  157. hostInfo.Platform = "Ubuntu"
  158. case "centos":
  159. hostInfo.Platform = "CentOS"
  160. }
  161. loadAvg, _ := load.Avg()
  162. c.JSON(http.StatusOK, gin.H{
  163. "host": hostInfo,
  164. "cpu": gin.H{
  165. "info": cpuInfo,
  166. "user": analytic.CpuUserRecord,
  167. "total": analytic.CpuTotalRecord,
  168. },
  169. "network": gin.H{
  170. "init": _net,
  171. "bytesRecv": analytic.NetRecvRecord,
  172. "bytesSent": analytic.NetSentRecord,
  173. },
  174. "disk_io": gin.H{
  175. "writes": analytic.DiskWriteRecord,
  176. "reads": analytic.DiskReadRecord,
  177. },
  178. "memory": memory,
  179. "disk": diskStat,
  180. "loadavg": loadAvg,
  181. })
  182. }
  183. func GetIntroAnalytic(c *gin.Context) {
  184. var upGrader = websocket.Upgrader{
  185. CheckOrigin: func(r *http.Request) bool {
  186. return true
  187. },
  188. }
  189. // upgrade http to websocket
  190. ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
  191. if err != nil {
  192. logger.Error(err)
  193. return
  194. }
  195. defer ws.Close()
  196. for {
  197. memory, err := getMemoryStat()
  198. if err != nil {
  199. logger.Error(err)
  200. return
  201. }
  202. cpuTimesBefore, _ := cpu.Times(false)
  203. time.Sleep(1000 * time.Millisecond)
  204. cpuTimesAfter, _ := cpu.Times(false)
  205. threadNum := runtime.GOMAXPROCS(0)
  206. cpuUserUsage := (cpuTimesAfter[0].User - cpuTimesBefore[0].User) / (float64(1000*threadNum) / 1000)
  207. cpuSystemUsage := (cpuTimesAfter[0].System - cpuTimesBefore[0].System) / (float64(1000*threadNum) / 1000)
  208. loadAvg, err := load.Avg()
  209. if err != nil {
  210. logger.Error(err)
  211. return
  212. }
  213. diskStat, err := getDiskStat()
  214. if err != nil {
  215. logger.Error(err)
  216. return
  217. }
  218. netIO, err := net.IOCounters(false)
  219. if err != nil {
  220. logger.Error(err)
  221. return
  222. }
  223. var network net.IOCountersStat
  224. if len(netIO) > 0 {
  225. network = netIO[0]
  226. }
  227. data := analytic.Node{
  228. AvgLoad: loadAvg,
  229. CPUPercent: math.Min((cpuUserUsage+cpuSystemUsage)*100, 100),
  230. MemoryPercent: memory.Pressure,
  231. DiskPercent: diskStat.Percentage,
  232. Network: network,
  233. }
  234. // write
  235. err = ws.WriteJSON(data)
  236. if err != nil {
  237. logger.Error(err)
  238. break
  239. }
  240. time.Sleep(5 * time.Second)
  241. }
  242. }
  243. func GetNodesAnalytic(c *gin.Context) {
  244. var upGrader = websocket.Upgrader{
  245. CheckOrigin: func(r *http.Request) bool {
  246. return true
  247. },
  248. }
  249. // upgrade http to websocket
  250. ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
  251. if err != nil {
  252. logger.Error(err)
  253. return
  254. }
  255. defer ws.Close()
  256. for {
  257. // write
  258. err = ws.WriteJSON(analytic.NodeMap)
  259. if err != nil {
  260. logger.Error(err)
  261. break
  262. }
  263. time.Sleep(5 * time.Second)
  264. }
  265. }