feat(telemetry): agent 心跳遥测上报+节点列表覆写(第13条)
agent 每 60s 心跳捎带遥测: agentVersion/xrayVersion(按 mtime 缓存重读)/ uptime/最近一次主控请求 RTT/cpu%/mem%/公网 v4+v6(公网优先, ULA/私网兜底); 主控心跳处理按指针语义只存上报字段, 旧 agent 无遥测行为不变; agent_nodes 加法迁移存遥测, 节点列表对 agent 行覆写真值, panelVersion 显示 "agent <版本>", NodeView 新增 ipv4/ipv6。
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
)
|
||||
|
||||
// P13: annotateAgentTelemetry overlays the agent-reported heartbeat
|
||||
// telemetry onto agent node views only — panel version becomes
|
||||
// "agent <version>", xray version/uptime/latency/CPU/mem/IPs take the
|
||||
// reported values. Traditional rows and never-reporting agents are
|
||||
// left untouched.
|
||||
func TestAnnotateAgentTelemetry(t *testing.T) {
|
||||
setupConflictDB(t)
|
||||
db := database.GetDB()
|
||||
|
||||
agentNode := &model.Node{Id: 7401, Name: "agent-node", Address: ""}
|
||||
tradNode := &model.Node{Id: 7402, Name: "trad-node", Address: "198.51.100.2", PanelVersion: "3.4.2", XrayVersion: "26.6.27"}
|
||||
silentNode := &model.Node{Id: 7403, Name: "silent-agent-node", Address: ""}
|
||||
for _, n := range []*model.Node{agentNode, tradNode, silentNode} {
|
||||
if err := db.Create(n).Error; err != nil {
|
||||
t.Fatalf("create node %d: %v", n.Id, err)
|
||||
}
|
||||
}
|
||||
reporting := &model.AgentNode{
|
||||
TokenHash: "hash-reporting",
|
||||
Name: "agent-node", NodeID: &agentNode.Id,
|
||||
AgentVersion: "3.7.0-agent.1", XrayVersion: "26.6.27",
|
||||
UptimeSecs: 90061, LatencyMs: 12, CpuPct: 3.5, MemPct: 41.2,
|
||||
IPv4: "203.0.113.7", IPv6: "2001:db8::7", TelemetryAt: 1700000000000,
|
||||
}
|
||||
silent := &model.AgentNode{TokenHash: "hash-silent", Name: "silent-agent-node", NodeID: &silentNode.Id}
|
||||
// The traditional node has no agent row at all — by the codebase's
|
||||
// definition (any binding makes a node agent-managed) it must pass
|
||||
// through the overlay untouched.
|
||||
for _, ag := range []*model.AgentNode{reporting, silent} {
|
||||
if err := db.Create(ag).Error; err != nil {
|
||||
t.Fatalf("create agent row: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
views := toNodeViews([]*model.Node{agentNode, tradNode, silentNode})
|
||||
annotateNodeTypes(views)
|
||||
annotateAgentTelemetry(views)
|
||||
byID := map[int]*NodeView{}
|
||||
for _, v := range views {
|
||||
byID[v.Id] = v
|
||||
}
|
||||
|
||||
av := byID[7401]
|
||||
if av.NodeType != "agent" {
|
||||
t.Fatalf("node 7401 type = %q, want agent", av.NodeType)
|
||||
}
|
||||
if av.XrayVersion != "26.6.27" || av.PanelVersion != "agent 3.7.0-agent.1" ||
|
||||
av.UptimeSecs != 90061 || av.LatencyMs != 12 ||
|
||||
av.CpuPct != 3.5 || av.MemPct != 41.2 ||
|
||||
av.Ipv4 != "203.0.113.7" || av.Ipv6 != "2001:db8::7" {
|
||||
t.Fatalf("agent view not overlaid: %+v", av)
|
||||
}
|
||||
|
||||
tv := byID[7402]
|
||||
if tv.PanelVersion != "3.4.2" || tv.XrayVersion != "26.6.27" || tv.Ipv4 != "" || tv.Ipv6 != "" {
|
||||
t.Fatalf("traditional view mutated: %+v", tv)
|
||||
}
|
||||
|
||||
sv := byID[7403]
|
||||
if sv.XrayVersion != "" || sv.PanelVersion != "" || sv.Ipv4 != "" || sv.UptimeSecs != 0 {
|
||||
t.Fatalf("silent agent view should stay empty: %+v", sv)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user