enhance: node analytic init

This commit is contained in:
0xJacky 2023-05-18 15:08:26 +08:00
parent 21cd91b163
commit ca3d19a5d7
No known key found for this signature in database
GPG key ID: B6E4A6E4A561BAF0
18 changed files with 301 additions and 264 deletions

View file

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

View file

@ -50,5 +50,6 @@
"vite-plugin-html": "^3.2.0",
"vite-svg-loader": "^4.0.0",
"vue-tsc": "^1.6.1"
}
},
"packageManager": "yarn@1.22.19"
}

View file

@ -1 +1 @@
{"version":"1.9.9","build_id":127,"total_build":197}
{"version":"1.9.9","build_id":129,"total_build":199}

View file

@ -1 +1 @@
{"version":"1.9.9","build_id":127,"total_build":197}
{"version":"1.9.9","build_id":129,"total_build":199}

View file

@ -4,7 +4,6 @@ import (
"flag"
"fmt"
"github.com/0xJacky/Nginx-UI/server"
"github.com/0xJacky/Nginx-UI/server/service"
"github.com/0xJacky/Nginx-UI/server/settings"
"github.com/gin-gonic/gin"
"github.com/jpillora/overseer"
@ -21,7 +20,7 @@ func main() {
gin.SetMode(settings.ServerSettings.RunMode)
r, err := service.GetRuntimeInfo()
r, err := server.GetRuntimeInfo()
if err != nil {
log.Fatalln(err)

View file

@ -230,13 +230,13 @@ func GetIntroAnalytic(c *gin.Context) {
for {
// write
err = ws.WriteJSON(analytic.GetNodeAnalyticIntro())
err = ws.WriteJSON(analytic.GetNodeStat())
if err != nil {
logger.Error(err)
break
}
time.Sleep(5 * time.Second)
time.Sleep(10 * time.Second)
}
}

View file

@ -1,9 +1,9 @@
package api
import (
"github.com/0xJacky/Nginx-UI/server/internal/environment"
"github.com/0xJacky/Nginx-UI/server/model"
"github.com/0xJacky/Nginx-UI/server/query"
"github.com/0xJacky/Nginx-UI/server/service"
"github.com/gin-gonic/gin"
"github.com/spf13/cast"
"net/http"
@ -24,7 +24,7 @@ func GetEnvironment(c *gin.Context) {
}
func GetEnvironmentList(c *gin.Context) {
data, err := service.RetrieveEnvironmentList()
data, err := environment.RetrieveEnvironmentList()
if err != nil {
ErrHandler(c, err)
return

View file

@ -2,7 +2,7 @@ package api
import (
"github.com/0xJacky/Nginx-UI/server/internal/analytic"
"github.com/0xJacky/Nginx-UI/server/service"
"github.com/0xJacky/Nginx-UI/server/internal/upgrader"
"github.com/dustin/go-humanize"
"github.com/gin-gonic/gin"
"github.com/shirou/gopsutil/v3/cpu"
@ -18,28 +18,28 @@ func GetCurrentNode(c *gin.Context) {
return
}
runtimeInfo, err := service.GetRuntimeInfo()
runtimeInfo, err := upgrader.GetRuntimeInfo()
if err != nil {
ErrHandler(c, err)
return
}
cpuInfo, _ := cpu.Info()
memory, _ := getMemoryStat()
ver, _ := service.GetCurrentVersion()
ver, _ := upgrader.GetCurrentVersion()
diskUsage, _ := disk.Usage(".")
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,
nodeInfo := analytic.NodeInfo{
NodeRuntimeInfo: runtimeInfo,
CPUNum: len(cpuInfo),
MemoryTotal: memory.Total,
DiskTotal: humanize.Bytes(diskUsage.Total),
Version: ver.Version,
}
c.JSON(http.StatusOK, nodeInfo)
stat := analytic.GetNodeStat()
c.JSON(http.StatusOK, analytic.Node{
NodeInfo: nodeInfo,
NodeStat: stat,
})
}

View file

@ -2,7 +2,7 @@ package api
import (
"github.com/0xJacky/Nginx-UI/server/internal/nginx"
"github.com/0xJacky/Nginx-UI/server/service"
"github.com/0xJacky/Nginx-UI/server/internal/template"
"github.com/gin-gonic/gin"
"net/http"
)
@ -45,7 +45,7 @@ func GetTemplate(c *gin.Context) {
}
func GetTemplateConfList(c *gin.Context) {
configList, err := service.GetTemplateList("conf")
configList, err := template.GetTemplateList("conf")
if err != nil {
ErrHandler(c, err)
@ -58,7 +58,7 @@ func GetTemplateConfList(c *gin.Context) {
}
func GetTemplateBlockList(c *gin.Context) {
configList, err := service.GetTemplateList("block")
configList, err := template.GetTemplateList("block")
if err != nil {
ErrHandler(c, err)
@ -72,18 +72,18 @@ func GetTemplateBlockList(c *gin.Context) {
func GetTemplateBlock(c *gin.Context) {
type resp struct {
service.ConfigInfoItem
service.ConfigDetail
template.ConfigInfoItem
template.ConfigDetail
}
var bindData map[string]service.TVariable
var bindData map[string]template.TVariable
_ = c.ShouldBindJSON(&bindData)
info := service.GetTemplateInfo("block", c.Param("name"))
info := template.GetTemplateInfo("block", c.Param("name"))
if bindData == nil {
bindData = info.Variables
}
detail, err := service.ParseTemplate("block", c.Param("name"), bindData)
detail, err := template.ParseTemplate("block", c.Param("name"), bindData)
if err != nil {
ErrHandler(c, err)
return

View file

@ -2,7 +2,7 @@ package api
import (
"github.com/0xJacky/Nginx-UI/server/internal/logger"
"github.com/0xJacky/Nginx-UI/server/service"
"github.com/0xJacky/Nginx-UI/server/internal/upgrader"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"net/http"
@ -10,19 +10,19 @@ import (
)
func GetRelease(c *gin.Context) {
data, err := service.GetRelease(c.Query("channel"))
data, err := upgrader.GetRelease(c.Query("channel"))
if err != nil {
ErrHandler(c, err)
return
}
runtimeInfo, err := service.GetRuntimeInfo()
runtimeInfo, err := upgrader.GetRuntimeInfo()
if err != nil {
ErrHandler(c, err)
return
}
type resp struct {
service.TRelease
service.RuntimeInfo
upgrader.TRelease
upgrader.RuntimeInfo
}
c.JSON(http.StatusOK, resp{
data, runtimeInfo,
@ -30,7 +30,7 @@ func GetRelease(c *gin.Context) {
}
func GetCurrentVersion(c *gin.Context) {
curVer, err := service.GetCurrentVersion()
curVer, err := upgrader.GetCurrentVersion()
if err != nil {
ErrHandler(c, err)
return
@ -70,7 +70,7 @@ func PerformCoreUpgrade(c *gin.Context) {
"message": "Initialing core upgrader",
})
u, err := service.NewUpgrader(control.Channel)
u, err := upgrader.NewUpgrader(control.Channel)
if err != nil {
_ = ws.WriteJSON(gin.H{

View file

@ -1,31 +1,43 @@
package analytic
import (
"crypto/tls"
"encoding/json"
"github.com/0xJacky/Nginx-UI/server/internal/logger"
"github.com/0xJacky/Nginx-UI/server/internal/upgrader"
"github.com/0xJacky/Nginx-UI/server/model"
"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"
"io"
"net/http"
"runtime"
"net/url"
"sync"
"time"
)
type Node struct {
EnvironmentID int `json:"environment_id,omitempty"`
Name string `json:"name,omitempty"`
type NodeInfo struct {
NodeRuntimeInfo upgrader.RuntimeInfo `json:"node_runtime_info"`
Version string `json:"version"`
CPUNum int `json:"cpu_num"`
MemoryTotal string `json:"memory_total"`
DiskTotal string `json:"disk_total"`
}
type NodeStat struct {
AvgLoad *load.AvgStat `json:"avg_load"`
CPUPercent float64 `json:"cpu_percent"`
MemoryPercent float64 `json:"memory_percent"`
DiskPercent float64 `json:"disk_percent"`
Network net.IOCountersStat `json:"network"`
Status bool `json:"status"`
ResponseAt time.Time `json:"response_at"`
}
type Node struct {
EnvironmentID int `json:"environment_id,omitempty"`
*model.Environment
NodeStat
NodeInfo
}
var mutex sync.Mutex
@ -38,136 +50,66 @@ func init() {
NodeMap = make(TNodeMap)
}
func nodeAnalyticLive(env *model.Environment, errChan chan error) {
for {
err := nodeAnalyticRecord(env)
if err != nil {
// set node offline
if NodeMap[env.ID] != nil {
NodeMap[env.ID].Status = false
}
log.Error(err)
errChan <- err
// wait 5s then reconnect
time.Sleep(5 * time.Second)
func GetNode(env *model.Environment) (n *Node) {
if env == nil {
logger.Error("env is nil")
return
}
n, ok := NodeMap[env.ID]
if !ok {
n = &Node{
Environment: env,
}
}
return n
}
func nodeAnalyticRecord(env *model.Environment) (err error) {
url, err := env.GetWebSocketURL("/api/analytic/intro")
func InitNode(env *model.Environment) (n *Node) {
n = &Node{
Environment: env,
}
u, err := url.JoinPath(env.URL, "/api/node")
if err != nil {
logger.Error(err)
return
}
header := http.Header{}
header.Set("X-Node-Secret", env.Token)
c, _, err := websocket.DefaultDialer.Dial(url, header)
if err != nil {
logger.Error(err)
return
}
client := http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
req, err := http.NewRequest("GET", u, nil)
req.Header.Set("X-Node-Secret", env.Token)
resp, err := client.Do(req)
if err != nil {
logger.Error(err)
return
}
defer c.Close()
defer resp.Body.Close()
bytes, _ := io.ReadAll(resp.Body)
for {
_, message, err := c.ReadMessage()
if err != nil {
return err
}
logger.Debugf("recv: %s %s", env.Name, message)
var nodeAnalytic Node
err = json.Unmarshal(message, &nodeAnalytic)
if err != nil {
return err
}
nodeAnalytic.EnvironmentID = env.ID
nodeAnalytic.Name = env.Name
// set online
nodeAnalytic.Status = true
mutex.Lock()
NodeMap[env.ID] = &nodeAnalytic
mutex.Unlock()
}
}
func RetrieveNodesStatus() {
NodeMap = make(TNodeMap)
env := query.Environment
envs, err := env.Find()
if err != nil {
logger.Error(err)
return
}
errChan := make(chan error)
for _, v := range envs {
go nodeAnalyticLive(v, errChan)
}
// block at here
for err = range errChan {
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,
if resp.StatusCode != 200 {
logger.Error(string(bytes))
return
}
logger.Debug(string(bytes))
err = json.Unmarshal(bytes, &n.NodeInfo)
if err != nil {
logger.Error(err)
return
}
return
}

View file

@ -0,0 +1,102 @@
package analytic
import (
"encoding/json"
"github.com/0xJacky/Nginx-UI/server/internal/logger"
"github.com/0xJacky/Nginx-UI/server/model"
"github.com/0xJacky/Nginx-UI/server/query"
"github.com/gorilla/websocket"
"github.com/opentracing/opentracing-go/log"
"net/http"
"time"
)
func RetrieveNodesStatus() {
NodeMap = make(TNodeMap)
env := query.Environment
envs, err := env.Find()
if err != nil {
logger.Error(err)
return
}
errChan := make(chan error)
for _, v := range envs {
go nodeAnalyticLive(v, errChan)
}
// block at here
for err = range errChan {
log.Error(err)
}
}
func nodeAnalyticLive(env *model.Environment, errChan chan error) {
for {
err := nodeAnalyticRecord(env)
if err != nil {
// set node offline
if NodeMap[env.ID] != nil {
mutex.Lock()
NodeMap[env.ID].Status = false
mutex.Unlock()
}
logger.Error(err)
errChan <- err
// wait 5s then reconnect
time.Sleep(5 * time.Second)
}
}
}
func nodeAnalyticRecord(env *model.Environment) (err error) {
mutex.Lock()
NodeMap[env.ID] = InitNode(env)
mutex.Unlock()
u, err := env.GetWebSocketURL("/api/analytic/intro")
if err != nil {
return
}
header := http.Header{}
header.Set("X-Node-Secret", env.Token)
c, _, err := websocket.DefaultDialer.Dial(u, header)
if err != nil {
return
}
defer c.Close()
var nodeStat NodeStat
for {
_, message, err := c.ReadMessage()
if err != nil {
return err
}
logger.Debugf("recv: %s %s", env.Name, message)
err = json.Unmarshal(message, &nodeStat)
if err != nil {
return err
}
// set online
nodeStat.Status = true
nodeStat.ResponseAt = time.Now()
mutex.Lock()
NodeMap[env.ID].NodeStat = nodeStat
mutex.Unlock()
}
}

View file

@ -0,0 +1,61 @@
package analytic
import (
"github.com/0xJacky/Nginx-UI/server/internal/logger"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/net"
"math"
"runtime"
"time"
)
func GetNodeStat() (data NodeStat) {
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 NodeStat{
AvgLoad: loadAvg,
CPUPercent: math.Min((cpuUserUsage+cpuSystemUsage)*100, 100),
MemoryPercent: memory.Pressure,
DiskPercent: diskStat.Percentage,
Network: network,
}
}

View file

@ -1 +1,23 @@
package environment
import (
"github.com/0xJacky/Nginx-UI/server/internal/analytic"
"github.com/0xJacky/Nginx-UI/server/query"
)
func RetrieveEnvironmentList() (envs []*analytic.Node, err error) {
envQuery := query.Environment
data, err := envQuery.Find()
if err != nil {
return
}
for _, v := range data {
t := analytic.GetNode(v)
envs = append(envs, t)
}
return
}

View file

@ -1,4 +1,4 @@
package service
package template
import (
"bufio"

View file

@ -1,4 +1,4 @@
package service
package upgrader
import (
"encoding/json"

View file

@ -5,6 +5,7 @@ import (
"github.com/0xJacky/Nginx-UI/server/internal/cert"
"github.com/0xJacky/Nginx-UI/server/internal/logger"
"github.com/0xJacky/Nginx-UI/server/internal/nginx"
"github.com/0xJacky/Nginx-UI/server/internal/upgrader"
"github.com/0xJacky/Nginx-UI/server/model"
"github.com/0xJacky/Nginx-UI/server/query"
"github.com/0xJacky/Nginx-UI/server/router"
@ -18,6 +19,10 @@ import (
"time"
)
func GetRuntimeInfo() (r upgrader.RuntimeInfo, err error) {
return upgrader.GetRuntimeInfo()
}
func Program(state overseer.State) {
defer logger.Sync()
// Hack: fix wrong Content Type of .js file on some OS platforms

View file

@ -1,97 +0,0 @@
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"
"io"
"net/http"
"net/url"
"time"
)
type Environment struct {
*model.Environment
Status bool `json:"status"`
NodeInfo
}
func RetrieveEnvironmentList() (envs []*Environment, err error) {
envQuery := query.Environment
data, err := envQuery.Find()
if err != nil {
return
}
for _, v := range data {
t := &Environment{
Environment: v,
}
node, status := t.GetNode()
t.Status = status
t.NodeInfo = node
envs = append(envs, t)
}
return
}
type NodeInfo struct {
RequestNodeSecret string `json:"request_node_secret"`
NodeRuntimeInfo RuntimeInfo `json:"node_runtime_info"`
Version string `json:"version"`
CPUNum int `json:"cpu_num"`
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) {
u, err := url.JoinPath(env.URL, "/api/node")
if err != nil {
logger.Error(err)
return
}
client := http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
req, err := http.NewRequest("GET", u, nil)
req.Header.Set("X-Node-Secret", env.Token)
resp, err := client.Do(req)
if err != nil {
logger.Error(err)
return
}
defer resp.Body.Close()
bytes, _ := io.ReadAll(resp.Body)
if resp.StatusCode != 200 {
logger.Error(string(bytes))
return
}
logger.Debug(string(bytes))
err = json.Unmarshal(bytes, &node)
if err != nil {
logger.Error(err)
return
}
node.ResponseAt = time.Now()
status = true
return
}