enhance: record node analytic

This commit is contained in:
0xJacky 2023-05-18 10:50:28 +08:00
parent aee3b87076
commit 228a36b079
No known key found for this signature in database
GPG key ID: B6E4A6E4A561BAF0
13 changed files with 203 additions and 4978 deletions

View file

@ -1 +1,3 @@
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.5.1.cjs

View file

@ -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"

View file

@ -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;
}

View file

@ -1 +1 @@
{"version":"1.9.9","build_id":120,"total_build":190}
{"version":"1.9.9","build_id":127,"total_build":197}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -1 +1 @@
{"version":"1.9.9","build_id":120,"total_build":190}
{"version":"1.9.9","build_id":127,"total_build":197}

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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)
}

View file

@ -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,
}
}

View 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
}

View file

@ -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) {