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 nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.5.1.cjs

View file

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

View file

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

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

View file

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

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() 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

View file

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

View file

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

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