mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-13 03:15:48 +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
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.5.1.cjs
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
"less": "^4.1.3",
|
||||
"typescript": "^5.0.4",
|
||||
"unplugin-vue-components": "^0.24.1",
|
||||
"vite": "^4.3.5",
|
||||
"vite": "^4.3.7",
|
||||
"vite-plugin-html": "^3.2.0",
|
||||
"vite-svg-loader": "^4.0.0",
|
||||
"vue-tsc": "^1.6.1"
|
||||
|
|
|
@ -40,7 +40,7 @@ const fixed_percent = computed(() => {
|
|||
margin-right: 5px;
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
@media (max-width: 1000px) and (min-width: 600px) {
|
||||
.dot {
|
||||
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
|
||||
})
|
||||
|
||||
environment.get_list().then(r => {
|
||||
data.value = r.data
|
||||
})
|
||||
|
||||
let websocket: ReconnectingWebSocket | WebSocket
|
||||
|
||||
onMounted(() => {
|
||||
environment.get_list().then(r => {
|
||||
data.value = r.data
|
||||
})
|
||||
websocket = ws('/api/analytic/nodes')
|
||||
websocket.onmessage = m => {
|
||||
const nodes = JSON.parse(m.data)
|
||||
|
@ -113,6 +112,14 @@ const visible = computed(() => {
|
|||
|
||||
.runtime-meta {
|
||||
display: inline-flex;
|
||||
@media (max-width: 700px) {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
font-weight: 400;
|
||||
|
|
|
@ -10,50 +10,44 @@ const props = defineProps(['item'])
|
|||
|
||||
<template>
|
||||
<div class="hardware-monitor">
|
||||
<a-row>
|
||||
<a-col :xs="12" :md="10">
|
||||
<div class="hardware-monitor-item longer">
|
||||
<div>
|
||||
<line-chart-outlined/>
|
||||
<span class="load-avg-describe">1min:</span>{{ ' ' + item.avg_load?.load1?.toFixed(2) }} ·
|
||||
<span class="load-avg-describe">5min:</span>{{ item.avg_load?.load5?.toFixed(2) }} ·
|
||||
<span class="load-avg-describe">15min:</span>{{ item.avg_load?.load15?.toFixed(2) }}
|
||||
</div>
|
||||
<div>
|
||||
<arrow-up-outlined/>
|
||||
{{ bytesToSize(item?.network?.bytesSent) }}
|
||||
<arrow-down-outlined/>
|
||||
{{ bytesToSize(item?.network?.bytesRecv) }}
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :xs="12" :md="14">
|
||||
<div class="hardware-monitor-item">
|
||||
<usage-progress-line :percent="item.cpu_percent">
|
||||
<template #icon>
|
||||
<Icon :component="cpu"/>
|
||||
</template>
|
||||
<span>{{ item.cpu_num }} CPU</span>
|
||||
</usage-progress-line>
|
||||
</div>
|
||||
<div class="hardware-monitor-item">
|
||||
<usage-progress-line :percent="item.memory_percent">
|
||||
<template #icon>
|
||||
<Icon :component="memory"/>
|
||||
</template>
|
||||
<span>{{ item.memory_total }}</span>
|
||||
</usage-progress-line>
|
||||
</div>
|
||||
<div class="hardware-monitor-item">
|
||||
<usage-progress-line :percent="item.disk_percent">
|
||||
<template #icon>
|
||||
<database-outlined/>
|
||||
</template>
|
||||
<span>{{ item.disk_total }}</span>
|
||||
</usage-progress-line>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="hardware-monitor-item longer">
|
||||
<div>
|
||||
<line-chart-outlined/>
|
||||
<span class="load-avg-describe">1min:</span>{{ ' ' + item.avg_load?.load1?.toFixed(2) }} ·
|
||||
<span class="load-avg-describe">5min:</span>{{ item.avg_load?.load5?.toFixed(2) }} ·
|
||||
<span class="load-avg-describe">15min:</span>{{ item.avg_load?.load15?.toFixed(2) }}
|
||||
</div>
|
||||
<div>
|
||||
<arrow-up-outlined/>
|
||||
{{ bytesToSize(item?.network?.bytesSent) }}
|
||||
<arrow-down-outlined/>
|
||||
{{ bytesToSize(item?.network?.bytesRecv) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="hardware-monitor-item">
|
||||
<usage-progress-line :percent="item.cpu_percent">
|
||||
<template #icon>
|
||||
<Icon :component="cpu"/>
|
||||
</template>
|
||||
<span>{{ item.cpu_num }} CPU</span>
|
||||
</usage-progress-line>
|
||||
</div>
|
||||
<div class="hardware-monitor-item">
|
||||
<usage-progress-line :percent="item.memory_percent">
|
||||
<template #icon>
|
||||
<Icon :component="memory"/>
|
||||
</template>
|
||||
<span>{{ item.memory_total }}</span>
|
||||
</usage-progress-line>
|
||||
</div>
|
||||
<div class="hardware-monitor-item">
|
||||
<usage-progress-line :percent="item.disk_percent">
|
||||
<template #icon>
|
||||
<database-outlined/>
|
||||
</template>
|
||||
<span>{{ item.disk_total }}</span>
|
||||
</usage-progress-line>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -61,13 +55,16 @@ const props = defineProps(['item'])
|
|||
.hardware-monitor {
|
||||
display: flex;
|
||||
|
||||
:deep(.ant-col) {
|
||||
display: flex;
|
||||
@media (max-width: 900px) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hardware-monitor-item {
|
||||
width: 150px;
|
||||
margin-right: 30px;
|
||||
@media (max-width: 900px) {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.longer {
|
||||
|
@ -76,7 +73,7 @@ const props = defineProps(['item'])
|
|||
}
|
||||
|
||||
.load-avg-describe {
|
||||
@media (max-width: 1200px) {
|
||||
@media (max-width: 1200px) and (min-width: 600px) {
|
||||
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()
|
||||
|
||||
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
|
||||
err = ws.WriteJSON(data)
|
||||
err = ws.WriteJSON(analytic.GetNodeAnalyticIntro())
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
break
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/server/internal/analytic"
|
||||
"github.com/0xJacky/Nginx-UI/server/service"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
@ -28,12 +29,17 @@ func GetCurrentNode(c *gin.Context) {
|
|||
ver, _ := service.GetCurrentVersion()
|
||||
diskUsage, _ := disk.Usage(".")
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"request_node_secret": c.MustGet("NodeSecret"),
|
||||
"node_runtime_info": runtimeInfo,
|
||||
"cpu_num": len(cpuInfo),
|
||||
"memory_total": memory.Total,
|
||||
"disk_total": humanize.Bytes(diskUsage.Total),
|
||||
"version": ver.Version,
|
||||
})
|
||||
intro := analytic.GetNodeAnalyticIntro()
|
||||
|
||||
nodeInfo := service.NodeInfo{
|
||||
RequestNodeSecret: c.MustGet("NodeSecret").(string),
|
||||
NodeRuntimeInfo: runtimeInfo,
|
||||
CPUNum: len(cpuInfo),
|
||||
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/gorilla/websocket"
|
||||
"github.com/opentracing/opentracing-go/log"
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
"math"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -23,6 +27,9 @@ type Node struct {
|
|||
Network net.IOCountersStat `json:"network"`
|
||||
Status bool `json:"status"`
|
||||
}
|
||||
|
||||
var mutex sync.Mutex
|
||||
|
||||
type TNodeMap map[int]*Node
|
||||
|
||||
var NodeMap TNodeMap
|
||||
|
@ -85,8 +92,9 @@ func nodeAnalyticRecord(env *model.Environment) (err error) {
|
|||
nodeAnalytic.Name = env.Name
|
||||
// set online
|
||||
nodeAnalytic.Status = true
|
||||
|
||||
mutex.Lock()
|
||||
NodeMap[env.ID] = &nodeAnalytic
|
||||
mutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,3 +121,53 @@ func RetrieveNodesStatus() {
|
|||
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 (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"github.com/0xJacky/Nginx-UI/server/internal/analytic"
|
||||
"github.com/0xJacky/Nginx-UI/server/internal/logger"
|
||||
"github.com/0xJacky/Nginx-UI/server/model"
|
||||
"github.com/0xJacky/Nginx-UI/server/query"
|
||||
|
@ -49,6 +50,7 @@ type NodeInfo struct {
|
|||
MemoryTotal string `json:"memory_total"`
|
||||
DiskTotal string `json:"disk_total"`
|
||||
ResponseAt time.Time `json:"response_at"`
|
||||
analytic.Node
|
||||
}
|
||||
|
||||
func (env *Environment) GetNode() (node NodeInfo, status bool) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue