mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-12 19:05:55 +02:00
enhance: record node analytic
This commit is contained in:
parent
aee3b87076
commit
228a36b079
13 changed files with 203 additions and 4978 deletions
|
@ -1 +1,3 @@
|
||||||
nodeLinker: node-modules
|
nodeLinker: node-modules
|
||||||
|
|
||||||
|
yarnPath: .yarn/releases/yarn-3.5.1.cjs
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
"less": "^4.1.3",
|
"less": "^4.1.3",
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.0.4",
|
||||||
"unplugin-vue-components": "^0.24.1",
|
"unplugin-vue-components": "^0.24.1",
|
||||||
"vite": "^4.3.5",
|
"vite": "^4.3.7",
|
||||||
"vite-plugin-html": "^3.2.0",
|
"vite-plugin-html": "^3.2.0",
|
||||||
"vite-svg-loader": "^4.0.0",
|
"vite-svg-loader": "^4.0.0",
|
||||||
"vue-tsc": "^1.6.1"
|
"vue-tsc": "^1.6.1"
|
||||||
|
|
|
@ -40,7 +40,7 @@ const fixed_percent = computed(() => {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1000px) {
|
@media (max-width: 1000px) and (min-width: 600px) {
|
||||||
.dot {
|
.dot {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"version":"1.9.9","build_id":120,"total_build":190}
|
{"version":"1.9.9","build_id":127,"total_build":197}
|
|
@ -24,13 +24,12 @@ const node_map = computed(() => {
|
||||||
return o
|
return o
|
||||||
})
|
})
|
||||||
|
|
||||||
environment.get_list().then(r => {
|
|
||||||
data.value = r.data
|
|
||||||
})
|
|
||||||
|
|
||||||
let websocket: ReconnectingWebSocket | WebSocket
|
let websocket: ReconnectingWebSocket | WebSocket
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
environment.get_list().then(r => {
|
||||||
|
data.value = r.data
|
||||||
|
})
|
||||||
websocket = ws('/api/analytic/nodes')
|
websocket = ws('/api/analytic/nodes')
|
||||||
websocket.onmessage = m => {
|
websocket.onmessage = m => {
|
||||||
const nodes = JSON.parse(m.data)
|
const nodes = JSON.parse(m.data)
|
||||||
|
@ -113,6 +112,14 @@ const visible = computed(() => {
|
||||||
|
|
||||||
.runtime-meta {
|
.runtime-meta {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
display: block;
|
||||||
|
margin-top: 5px;
|
||||||
|
span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
|
@ -10,50 +10,44 @@ const props = defineProps(['item'])
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="hardware-monitor">
|
<div class="hardware-monitor">
|
||||||
<a-row>
|
<div class="hardware-monitor-item longer">
|
||||||
<a-col :xs="12" :md="10">
|
<div>
|
||||||
<div class="hardware-monitor-item longer">
|
<line-chart-outlined/>
|
||||||
<div>
|
<span class="load-avg-describe">1min:</span>{{ ' ' + item.avg_load?.load1?.toFixed(2) }} ·
|
||||||
<line-chart-outlined/>
|
<span class="load-avg-describe">5min:</span>{{ item.avg_load?.load5?.toFixed(2) }} ·
|
||||||
<span class="load-avg-describe">1min:</span>{{ ' ' + item.avg_load?.load1?.toFixed(2) }} ·
|
<span class="load-avg-describe">15min:</span>{{ item.avg_load?.load15?.toFixed(2) }}
|
||||||
<span class="load-avg-describe">5min:</span>{{ item.avg_load?.load5?.toFixed(2) }} ·
|
</div>
|
||||||
<span class="load-avg-describe">15min:</span>{{ item.avg_load?.load15?.toFixed(2) }}
|
<div>
|
||||||
</div>
|
<arrow-up-outlined/>
|
||||||
<div>
|
{{ bytesToSize(item?.network?.bytesSent) }}
|
||||||
<arrow-up-outlined/>
|
<arrow-down-outlined/>
|
||||||
{{ bytesToSize(item?.network?.bytesSent) }}
|
{{ bytesToSize(item?.network?.bytesRecv) }}
|
||||||
<arrow-down-outlined/>
|
</div>
|
||||||
{{ bytesToSize(item?.network?.bytesRecv) }}
|
</div>
|
||||||
</div>
|
<div class="hardware-monitor-item">
|
||||||
</div>
|
<usage-progress-line :percent="item.cpu_percent">
|
||||||
</a-col>
|
<template #icon>
|
||||||
<a-col :xs="12" :md="14">
|
<Icon :component="cpu"/>
|
||||||
<div class="hardware-monitor-item">
|
</template>
|
||||||
<usage-progress-line :percent="item.cpu_percent">
|
<span>{{ item.cpu_num }} CPU</span>
|
||||||
<template #icon>
|
</usage-progress-line>
|
||||||
<Icon :component="cpu"/>
|
</div>
|
||||||
</template>
|
<div class="hardware-monitor-item">
|
||||||
<span>{{ item.cpu_num }} CPU</span>
|
<usage-progress-line :percent="item.memory_percent">
|
||||||
</usage-progress-line>
|
<template #icon>
|
||||||
</div>
|
<Icon :component="memory"/>
|
||||||
<div class="hardware-monitor-item">
|
</template>
|
||||||
<usage-progress-line :percent="item.memory_percent">
|
<span>{{ item.memory_total }}</span>
|
||||||
<template #icon>
|
</usage-progress-line>
|
||||||
<Icon :component="memory"/>
|
</div>
|
||||||
</template>
|
<div class="hardware-monitor-item">
|
||||||
<span>{{ item.memory_total }}</span>
|
<usage-progress-line :percent="item.disk_percent">
|
||||||
</usage-progress-line>
|
<template #icon>
|
||||||
</div>
|
<database-outlined/>
|
||||||
<div class="hardware-monitor-item">
|
</template>
|
||||||
<usage-progress-line :percent="item.disk_percent">
|
<span>{{ item.disk_total }}</span>
|
||||||
<template #icon>
|
</usage-progress-line>
|
||||||
<database-outlined/>
|
</div>
|
||||||
</template>
|
|
||||||
<span>{{ item.disk_total }}</span>
|
|
||||||
</usage-progress-line>
|
|
||||||
</div>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -61,13 +55,16 @@ const props = defineProps(['item'])
|
||||||
.hardware-monitor {
|
.hardware-monitor {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
:deep(.ant-col) {
|
@media (max-width: 900px) {
|
||||||
display: flex;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hardware-monitor-item {
|
.hardware-monitor-item {
|
||||||
width: 150px;
|
width: 150px;
|
||||||
margin-right: 30px;
|
margin-right: 30px;
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.longer {
|
.longer {
|
||||||
|
@ -76,7 +73,7 @@ const props = defineProps(['item'])
|
||||||
}
|
}
|
||||||
|
|
||||||
.load-avg-describe {
|
.load-avg-describe {
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) and (min-width: 600px) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"version":"1.9.9","build_id":120,"total_build":190}
|
{"version":"1.9.9","build_id":127,"total_build":197}
|
4865
frontend/yarn.lock
4865
frontend/yarn.lock
File diff suppressed because it is too large
Load diff
|
@ -229,56 +229,8 @@ func GetIntroAnalytic(c *gin.Context) {
|
||||||
defer ws.Close()
|
defer ws.Close()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
memory, err := getMemoryStat()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cpuTimesBefore, _ := cpu.Times(false)
|
|
||||||
time.Sleep(1000 * time.Millisecond)
|
|
||||||
cpuTimesAfter, _ := cpu.Times(false)
|
|
||||||
threadNum := runtime.GOMAXPROCS(0)
|
|
||||||
cpuUserUsage := (cpuTimesAfter[0].User - cpuTimesBefore[0].User) / (float64(1000*threadNum) / 1000)
|
|
||||||
cpuSystemUsage := (cpuTimesAfter[0].System - cpuTimesBefore[0].System) / (float64(1000*threadNum) / 1000)
|
|
||||||
|
|
||||||
loadAvg, err := load.Avg()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
diskStat, err := getDiskStat()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
netIO, err := net.IOCounters(false)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var network net.IOCountersStat
|
|
||||||
if len(netIO) > 0 {
|
|
||||||
network = netIO[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
data := analytic.Node{
|
|
||||||
AvgLoad: loadAvg,
|
|
||||||
CPUPercent: math.Min((cpuUserUsage+cpuSystemUsage)*100, 100),
|
|
||||||
MemoryPercent: memory.Pressure,
|
|
||||||
DiskPercent: diskStat.Percentage,
|
|
||||||
Network: network,
|
|
||||||
}
|
|
||||||
|
|
||||||
// write
|
// write
|
||||||
err = ws.WriteJSON(data)
|
err = ws.WriteJSON(analytic.GetNodeAnalyticIntro())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
break
|
break
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/0xJacky/Nginx-UI/server/internal/analytic"
|
||||||
"github.com/0xJacky/Nginx-UI/server/service"
|
"github.com/0xJacky/Nginx-UI/server/service"
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
@ -28,12 +29,17 @@ func GetCurrentNode(c *gin.Context) {
|
||||||
ver, _ := service.GetCurrentVersion()
|
ver, _ := service.GetCurrentVersion()
|
||||||
diskUsage, _ := disk.Usage(".")
|
diskUsage, _ := disk.Usage(".")
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
intro := analytic.GetNodeAnalyticIntro()
|
||||||
"request_node_secret": c.MustGet("NodeSecret"),
|
|
||||||
"node_runtime_info": runtimeInfo,
|
nodeInfo := service.NodeInfo{
|
||||||
"cpu_num": len(cpuInfo),
|
RequestNodeSecret: c.MustGet("NodeSecret").(string),
|
||||||
"memory_total": memory.Total,
|
NodeRuntimeInfo: runtimeInfo,
|
||||||
"disk_total": humanize.Bytes(diskUsage.Total),
|
CPUNum: len(cpuInfo),
|
||||||
"version": ver.Version,
|
MemoryTotal: memory.Total,
|
||||||
})
|
DiskTotal: humanize.Bytes(diskUsage.Total),
|
||||||
|
Version: ver.Version,
|
||||||
|
Node: intro,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, nodeInfo)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,13 @@ import (
|
||||||
"github.com/0xJacky/Nginx-UI/server/query"
|
"github.com/0xJacky/Nginx-UI/server/query"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/opentracing/opentracing-go/log"
|
"github.com/opentracing/opentracing-go/log"
|
||||||
|
"github.com/shirou/gopsutil/v3/cpu"
|
||||||
"github.com/shirou/gopsutil/v3/load"
|
"github.com/shirou/gopsutil/v3/load"
|
||||||
"github.com/shirou/gopsutil/v3/net"
|
"github.com/shirou/gopsutil/v3/net"
|
||||||
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,6 +27,9 @@ type Node struct {
|
||||||
Network net.IOCountersStat `json:"network"`
|
Network net.IOCountersStat `json:"network"`
|
||||||
Status bool `json:"status"`
|
Status bool `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mutex sync.Mutex
|
||||||
|
|
||||||
type TNodeMap map[int]*Node
|
type TNodeMap map[int]*Node
|
||||||
|
|
||||||
var NodeMap TNodeMap
|
var NodeMap TNodeMap
|
||||||
|
@ -85,8 +92,9 @@ func nodeAnalyticRecord(env *model.Environment) (err error) {
|
||||||
nodeAnalytic.Name = env.Name
|
nodeAnalytic.Name = env.Name
|
||||||
// set online
|
// set online
|
||||||
nodeAnalytic.Status = true
|
nodeAnalytic.Status = true
|
||||||
|
mutex.Lock()
|
||||||
NodeMap[env.ID] = &nodeAnalytic
|
NodeMap[env.ID] = &nodeAnalytic
|
||||||
|
mutex.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,3 +121,53 @@ func RetrieveNodesStatus() {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetNodeAnalyticIntro() (data Node) {
|
||||||
|
memory, err := GetMemoryStat()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cpuTimesBefore, _ := cpu.Times(false)
|
||||||
|
time.Sleep(1000 * time.Millisecond)
|
||||||
|
cpuTimesAfter, _ := cpu.Times(false)
|
||||||
|
threadNum := runtime.GOMAXPROCS(0)
|
||||||
|
cpuUserUsage := (cpuTimesAfter[0].User - cpuTimesBefore[0].User) / (float64(1000*threadNum) / 1000)
|
||||||
|
cpuSystemUsage := (cpuTimesAfter[0].System - cpuTimesBefore[0].System) / (float64(1000*threadNum) / 1000)
|
||||||
|
|
||||||
|
loadAvg, err := load.Avg()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
diskStat, err := GetDiskStat()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
netIO, err := net.IOCounters(false)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var network net.IOCountersStat
|
||||||
|
if len(netIO) > 0 {
|
||||||
|
network = netIO[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return Node{
|
||||||
|
AvgLoad: loadAvg,
|
||||||
|
CPUPercent: math.Min((cpuUserUsage+cpuSystemUsage)*100, 100),
|
||||||
|
MemoryPercent: memory.Pressure,
|
||||||
|
DiskPercent: diskStat.Percentage,
|
||||||
|
Network: network,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
66
server/internal/analytic/stat.go
Normal file
66
server/internal/analytic/stat.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package analytic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/shirou/gopsutil/v3/disk"
|
||||||
|
"github.com/shirou/gopsutil/v3/mem"
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MemStat struct {
|
||||||
|
Total string `json:"total"`
|
||||||
|
Used string `json:"used"`
|
||||||
|
Cached string `json:"cached"`
|
||||||
|
Free string `json:"free"`
|
||||||
|
SwapUsed string `json:"swap_used"`
|
||||||
|
SwapTotal string `json:"swap_total"`
|
||||||
|
SwapCached string `json:"swap_cached"`
|
||||||
|
SwapPercent float64 `json:"swap_percent"`
|
||||||
|
Pressure float64 `json:"pressure"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiskStat struct {
|
||||||
|
Total string `json:"total"`
|
||||||
|
Used string `json:"used"`
|
||||||
|
Percentage float64 `json:"percentage"`
|
||||||
|
Writes Usage `json:"writes"`
|
||||||
|
Reads Usage `json:"reads"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMemoryStat() (MemStat, error) {
|
||||||
|
memoryStat, err := mem.VirtualMemory()
|
||||||
|
if err != nil {
|
||||||
|
return MemStat{}, errors.Wrap(err, "error analytic getMemoryStat")
|
||||||
|
}
|
||||||
|
return MemStat{
|
||||||
|
Total: humanize.Bytes(memoryStat.Total),
|
||||||
|
Used: humanize.Bytes(memoryStat.Used),
|
||||||
|
Cached: humanize.Bytes(memoryStat.Cached),
|
||||||
|
Free: humanize.Bytes(memoryStat.Free),
|
||||||
|
SwapUsed: humanize.Bytes(memoryStat.SwapTotal - memoryStat.SwapFree),
|
||||||
|
SwapTotal: humanize.Bytes(memoryStat.SwapTotal),
|
||||||
|
SwapCached: humanize.Bytes(memoryStat.SwapCached),
|
||||||
|
SwapPercent: cast.ToFloat64(fmt.Sprintf("%.2f",
|
||||||
|
100*float64(memoryStat.SwapTotal-memoryStat.SwapFree)/math.Max(float64(memoryStat.SwapTotal), 1))),
|
||||||
|
Pressure: cast.ToFloat64(fmt.Sprintf("%.2f", memoryStat.UsedPercent)),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDiskStat() (DiskStat, error) {
|
||||||
|
diskUsage, err := disk.Usage(".")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return DiskStat{}, errors.Wrap(err, "error analytic getDiskStat")
|
||||||
|
}
|
||||||
|
|
||||||
|
return DiskStat{
|
||||||
|
Used: humanize.Bytes(diskUsage.Used),
|
||||||
|
Total: humanize.Bytes(diskUsage.Total),
|
||||||
|
Percentage: cast.ToFloat64(fmt.Sprintf("%.2f", diskUsage.UsedPercent)),
|
||||||
|
Writes: DiskWriteRecord[len(DiskWriteRecord)-1],
|
||||||
|
Reads: DiskReadRecord[len(DiskReadRecord)-1],
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package service
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"github.com/0xJacky/Nginx-UI/server/internal/analytic"
|
||||||
"github.com/0xJacky/Nginx-UI/server/internal/logger"
|
"github.com/0xJacky/Nginx-UI/server/internal/logger"
|
||||||
"github.com/0xJacky/Nginx-UI/server/model"
|
"github.com/0xJacky/Nginx-UI/server/model"
|
||||||
"github.com/0xJacky/Nginx-UI/server/query"
|
"github.com/0xJacky/Nginx-UI/server/query"
|
||||||
|
@ -49,6 +50,7 @@ type NodeInfo struct {
|
||||||
MemoryTotal string `json:"memory_total"`
|
MemoryTotal string `json:"memory_total"`
|
||||||
DiskTotal string `json:"disk_total"`
|
DiskTotal string `json:"disk_total"`
|
||||||
ResponseAt time.Time `json:"response_at"`
|
ResponseAt time.Time `json:"response_at"`
|
||||||
|
analytic.Node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (env *Environment) GetNode() (node NodeInfo, status bool) {
|
func (env *Environment) GetNode() (node NodeInfo, status bool) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue