|
@@ -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 nodeAnalyticRecord(env *model.Environment) (err error) {
|
|
|
- url, err := env.GetWebSocketURL("/api/analytic/intro")
|
|
|
-
|
|
|
- if err != nil {
|
|
|
+func GetNode(env *model.Environment) (n *Node) {
|
|
|
+ if env == nil {
|
|
|
+ logger.Error("env is nil")
|
|
|
return
|
|
|
}
|
|
|
-
|
|
|
- header := http.Header{}
|
|
|
-
|
|
|
- header.Set("X-Node-Secret", env.Token)
|
|
|
-
|
|
|
- c, _, err := websocket.DefaultDialer.Dial(url, header)
|
|
|
- if err != nil {
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- defer c.Close()
|
|
|
-
|
|
|
- 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
|
|
|
+ n, ok := NodeMap[env.ID]
|
|
|
+ if !ok {
|
|
|
+ n = &Node{
|
|
|
+ Environment: env,
|
|
|
}
|
|
|
-
|
|
|
- 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)
|
|
|
+ return n
|
|
|
+}
|
|
|
|
|
|
- env := query.Environment
|
|
|
+func InitNode(env *model.Environment) (n *Node) {
|
|
|
+ n = &Node{
|
|
|
+ Environment: env,
|
|
|
+ }
|
|
|
|
|
|
- envs, err := env.Find()
|
|
|
+ u, err := url.JoinPath(env.URL, "/api/node")
|
|
|
|
|
|
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
|
|
|
}
|
|
|
+ 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)
|
|
|
|
|
|
- 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()
|
|
|
+ resp, err := client.Do(req)
|
|
|
|
|
|
if err != nil {
|
|
|
logger.Error(err)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- diskStat, err := GetDiskStat()
|
|
|
+ defer resp.Body.Close()
|
|
|
+ bytes, _ := io.ReadAll(resp.Body)
|
|
|
|
|
|
- if err != nil {
|
|
|
- logger.Error(err)
|
|
|
+ if resp.StatusCode != 200 {
|
|
|
+ logger.Error(string(bytes))
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- netIO, err := net.IOCounters(false)
|
|
|
-
|
|
|
+ logger.Debug(string(bytes))
|
|
|
+ err = json.Unmarshal(bytes, &n.NodeInfo)
|
|
|
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,
|
|
|
- }
|
|
|
+ return
|
|
|
}
|