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:
Muse
2026-10-22 11:33:56 +00:00
parent 84d7b85be7
commit 3536d4174e
10 changed files with 604 additions and 6 deletions
+28 -4
View File
@@ -14,6 +14,8 @@ import (
"net/http"
"os"
"path/filepath"
"sync"
"sync/atomic"
"time"
"github.com/mhsanaei/3x-ui/v3/internal/config"
@@ -64,6 +66,15 @@ type Agent struct {
xrayAPI xray.XrayAPI
apiPort int
baselineLoaded bool
// Heartbeat telemetry state (P13): last measured master-request RTT
// and the mtime-keyed xray version cache.
lastRTTms atomic.Int64
hasRTT atomic.Bool
xrayVerMu sync.Mutex
xrayVer string
xrayVerFor string // binary path the cached version was read from
xrayVerMod time.Time
}
// New creates a new agent instance
@@ -110,7 +121,7 @@ func (a *Agent) Register() error {
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+a.config.Token)
resp, err := a.httpClient.Do(req)
resp, err := a.do(req)
if err != nil {
return fmt.Errorf("register failed: %w", err)
}
@@ -241,12 +252,25 @@ func (a *Agent) Stop() {
close(a.stopCh)
}
// do performs one master request and records its round-trip time as the
// node->master latency sample reported by the next heartbeat (P13).
func (a *Agent) do(req *http.Request) (*http.Response, error) {
start := time.Now()
resp, err := a.httpClient.Do(req)
if err == nil {
a.lastRTTms.Store(time.Since(start).Milliseconds())
a.hasRTT.Store(true)
}
return resp, err
}
// sendHeartbeat sends a heartbeat to the master
func (a *Agent) sendHeartbeat() error {
url := fmt.Sprintf("%s/panel/api/agent/heartbeat", a.config.MasterURL)
payload := map[string]interface{}{
"token": a.config.Token,
"timestamp": time.Now().Unix(),
"telemetry": a.collectTelemetry(),
}
body, _ := json.Marshal(payload)
req, err := http.NewRequest("POST", url, bytes.NewReader(body))
@@ -256,7 +280,7 @@ func (a *Agent) sendHeartbeat() error {
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+a.config.Token)
resp, err := a.httpClient.Do(req)
resp, err := a.do(req)
if err != nil {
return err
}
@@ -276,7 +300,7 @@ func (a *Agent) fetchAndApplyConfig() error {
}
req.Header.Set("Authorization", "Bearer "+a.config.Token)
resp, err := a.httpClient.Do(req)
resp, err := a.do(req)
if err != nil {
return err
}
@@ -509,7 +533,7 @@ func (a *Agent) postTrafficReport(report trafficReport) error {
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+a.config.Token)
resp, err := a.httpClient.Do(req)
resp, err := a.do(req)
if err != nil {
return err
}