mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2026-05-08 06:52:10 +08:00
170 lines
3.3 KiB
Go
170 lines
3.3 KiB
Go
package analytic
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/0xJacky/Nginx-UI/internal/transport"
|
|
"github.com/0xJacky/Nginx-UI/internal/upstream"
|
|
"github.com/0xJacky/Nginx-UI/internal/version"
|
|
"github.com/0xJacky/Nginx-UI/model"
|
|
"github.com/shirou/gopsutil/v4/load"
|
|
"github.com/shirou/gopsutil/v4/net"
|
|
"github.com/uozi-tech/cosy"
|
|
"github.com/uozi-tech/cosy/logger"
|
|
)
|
|
|
|
type NodeInfo struct {
|
|
NodeRuntimeInfo version.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"`
|
|
UpstreamStatusMap map[string]*upstream.Status `json:"upstream_status_map"`
|
|
}
|
|
|
|
type Node struct {
|
|
*model.Node
|
|
NodeStat
|
|
NodeInfo
|
|
}
|
|
|
|
var nodeMapMu sync.RWMutex
|
|
|
|
type TNodeMap map[uint64]*Node
|
|
|
|
var NodeMap TNodeMap
|
|
|
|
func init() {
|
|
NodeMap = make(TNodeMap)
|
|
}
|
|
|
|
func cloneNode(n *Node) *Node {
|
|
if n == nil {
|
|
return nil
|
|
}
|
|
|
|
cloned := *n
|
|
|
|
if n.Node != nil {
|
|
nodeCopy := *n.Node
|
|
cloned.Node = &nodeCopy
|
|
}
|
|
|
|
if n.UpstreamStatusMap != nil {
|
|
upstreams := make(map[string]*upstream.Status, len(n.UpstreamStatusMap))
|
|
for key, status := range n.UpstreamStatusMap {
|
|
if status == nil {
|
|
upstreams[key] = nil
|
|
continue
|
|
}
|
|
statusCopy := *status
|
|
upstreams[key] = &statusCopy
|
|
}
|
|
cloned.UpstreamStatusMap = upstreams
|
|
}
|
|
|
|
return &cloned
|
|
}
|
|
|
|
func SnapshotNodeMap() TNodeMap {
|
|
nodeMapMu.RLock()
|
|
defer nodeMapMu.RUnlock()
|
|
|
|
snapshot := make(TNodeMap, len(NodeMap))
|
|
for id, node := range NodeMap {
|
|
snapshot[id] = cloneNode(node)
|
|
}
|
|
|
|
return snapshot
|
|
}
|
|
|
|
func GetNode(node *model.Node) (n *Node) {
|
|
if node == nil {
|
|
// this should never happen
|
|
logger.Error("node is nil")
|
|
return
|
|
}
|
|
if !node.Enabled {
|
|
return &Node{
|
|
Node: node,
|
|
}
|
|
}
|
|
nodeMapMu.RLock()
|
|
cached, ok := NodeMap[node.ID]
|
|
nodeMapMu.RUnlock()
|
|
if !ok || cached == nil {
|
|
return &Node{
|
|
Node: node,
|
|
}
|
|
}
|
|
|
|
cloned := cloneNode(cached)
|
|
if cloned == nil {
|
|
return &Node{
|
|
Node: node,
|
|
}
|
|
}
|
|
cloned.Node = node
|
|
return cloned
|
|
}
|
|
|
|
func InitNode(node *model.Node) (n *Node, err error) {
|
|
n = &Node{
|
|
Node: node,
|
|
}
|
|
|
|
u, err := url.JoinPath(node.URL, "/api/node")
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
t, err := transport.NewTransport()
|
|
if err != nil {
|
|
return
|
|
}
|
|
client := http.Client{
|
|
Transport: t,
|
|
}
|
|
|
|
req, err := http.NewRequest("GET", u, nil)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
req.Header.Set("X-Node-Secret", node.Token)
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
bytes, _ := io.ReadAll(resp.Body)
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return n, cosy.WrapErrorWithParams(ErrNodeAnalyticsFailed, string(bytes))
|
|
}
|
|
|
|
err = json.Unmarshal(bytes, &n.NodeInfo)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|