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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,31 +1,43 @@
package analytic package analytic
import ( import (
"crypto/tls"
"encoding/json" "encoding/json"
"github.com/0xJacky/Nginx-UI/server/internal/logger" "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/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/load"
"github.com/shirou/gopsutil/v3/net" "github.com/shirou/gopsutil/v3/net"
"math" "io"
"net/http" "net/http"
"runtime" "net/url"
"sync" "sync"
"time" "time"
) )
type Node struct { type NodeInfo struct {
EnvironmentID int `json:"environment_id,omitempty"` NodeRuntimeInfo upgrader.RuntimeInfo `json:"node_runtime_info"`
Name string `json:"name,omitempty"` 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"` AvgLoad *load.AvgStat `json:"avg_load"`
CPUPercent float64 `json:"cpu_percent"` CPUPercent float64 `json:"cpu_percent"`
MemoryPercent float64 `json:"memory_percent"` MemoryPercent float64 `json:"memory_percent"`
DiskPercent float64 `json:"disk_percent"` DiskPercent float64 `json:"disk_percent"`
Network net.IOCountersStat `json:"network"` Network net.IOCountersStat `json:"network"`
Status bool `json:"status"` 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 var mutex sync.Mutex
@ -38,136 +50,66 @@ func init() {
NodeMap = make(TNodeMap) NodeMap = make(TNodeMap)
} }
func nodeAnalyticLive(env *model.Environment, errChan chan error) { func GetNode(env *model.Environment) (n *Node) {
for { if env == nil {
err := nodeAnalyticRecord(env) logger.Error("env is nil")
return
if err != nil { }
// set node offline n, ok := NodeMap[env.ID]
if NodeMap[env.ID] != nil { if !ok {
NodeMap[env.ID].Status = false n = &Node{
} Environment: env,
log.Error(err)
errChan <- err
// wait 5s then reconnect
time.Sleep(5 * time.Second)
} }
} }
return n
} }
func nodeAnalyticRecord(env *model.Environment) (err error) { func InitNode(env *model.Environment) (n *Node) {
url, err := env.GetWebSocketURL("/api/analytic/intro") n = &Node{
Environment: env,
}
u, err := url.JoinPath(env.URL, "/api/node")
if err != nil { if err != nil {
logger.Error(err)
return return
} }
header := http.Header{}
header.Set("X-Node-Secret", env.Token)
c, _, err := websocket.DefaultDialer.Dial(url, header)
if err != nil { 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 return
} }
defer c.Close() defer resp.Body.Close()
bytes, _ := io.ReadAll(resp.Body)
for { if resp.StatusCode != 200 {
_, message, err := c.ReadMessage() logger.Error(string(bytes))
if err != nil { return
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,
} }
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 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 ( import (
"bufio" "bufio"

View file

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

View file

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