fix: 完善Agent系统信息上报

- 持续上报系统元数据并动态更新运行时长
- 兼容旧版心跳补齐资产运行时长
- 修复监控指标当前值重复显示单位
This commit is contained in:
Logan.Li committed 2026-07-25 17:14:55 +08:00
1 parent 0c7c63017c
commit 699560a270
4 files changed
+77 -15

No files matched your search

+6 -10
View File
@@ -116,12 +116,11 @@ func main() {
sysInfo := collector.CollectSystem()
logger.Printf("系统信息: OS=%q Kernel=%q", sysInfo.OSVersion, sysInfo.Kernel)
agentStartedAt := time.Now()
localIP := detectLocalIP()
logger.Printf("探测本机 IP: %s", localIP)
sysReportCount := 0
taskExecutor := task.New(logger)
taskClient := task.NewClient(httpCli, cfg.Hostname, logger)
@@ -135,20 +134,17 @@ func main() {
var probeConfigs []model.ProbeConfig
collectAndReport := func() {
currentUptime := sysInfo.UptimeSeconds + int64(time.Since(agentStartedAt).Seconds())
report := &model.Report{
Hostname: cfg.Hostname,
IP: localIP,
Timestamp: time.Now().UTC().Format(time.RFC3339),
AgentVersion: version,
}
if sysReportCount < 3 && (sysInfo.OSVersion != "" || sysInfo.Kernel != "") {
report.System = &model.SystemStat{
System: &model.SystemStat{
OSVersion: sysInfo.OSVersion,
Kernel: sysInfo.Kernel,
Uptime: sysInfo.UptimeSeconds,
}
sysReportCount++
Uptime: currentUptime,
},
}
if stat, err := cpuCollector.Collect(); err != nil {
@@ -211,7 +207,7 @@ func main() {
runtime.ReadMemStats(&memStats)
report.Heartbeat = model.AgentHeartbeat{
Version: version,
UptimeSec: sysInfo.UptimeSeconds,
UptimeSec: currentUptime,
SelfCPU: 0,
SelfRSSMB: float64(memStats.Alloc) / 1024.0 / 1024.0,
ReportSeq: reportSeq,
+21 -2
View File
@@ -150,6 +150,10 @@ func (h *AgentHandler) ReceiveReport(c *gin.Context) {
updates["uptime"] = req.System.Uptime
}
}
// 兼容旧版 Agent:系统信息缺席时使用心跳中的运行时长补齐资产元数据。 // Logan.Li
if _, ok := updates["uptime"]; !ok && req.Heartbeat.UptimeSec > 0 {
updates["uptime"] = req.Heartbeat.UptimeSec
}
var result *gorm.DB
if assetMatched {
result = h.db.Model(&model.Server{}).Where("id = ?", matchedServer.ID).Updates(updates)
@@ -162,19 +166,32 @@ func (h *AgentHandler) ReceiveReport(c *gin.Context) {
// 避免 Agent 每次上报(10s)都新建记录导致资产越来越多。
if result.Error == nil && result.RowsAffected == 0 {
osName := ""
kernel := ""
uptime := req.Heartbeat.UptimeSec
if req.System != nil {
osName = req.System.OSVersion
kernel = req.System.Kernel
if req.System.Uptime > 0 {
uptime = req.System.Uptime
}
}
var existing model.Server
if h.db.Where("hostname = ?", req.Hostname).Take(&existing).Error == nil {
// 已自动注册过 → 仅更新实时指标,不重复创建
h.db.Model(&existing).Updates(map[string]interface{}{
existingUpdates := map[string]interface{}{
"ip": matchIP, "os": osName, "cpu": req.CPU.CoreCount,
"memory": memMB, "disk": diskGB, "status": "running",
"agent_status": "online", "agent_version": req.AgentVersion,
"cpu_usage": req.CPU.UsagePercent, "memory_usage": req.Memory.UsagePercent,
"disk_usage": diskAvg,
})
}
if kernel != "" {
existingUpdates["kernel"] = kernel
}
if uptime > 0 {
existingUpdates["uptime"] = uptime
}
h.db.Model(&existing).Updates(existingUpdates)
} else {
// 真正的新主机 → 注册,hostname 无唯一索引时不使用 OnConflict。 // Logan.Li
newServer := model.Server{
@@ -190,6 +207,8 @@ func (h *AgentHandler) ReceiveReport(c *gin.Context) {
Status: "running",
AgentStatus: "online",
AgentVersion: req.AgentVersion,
Kernel: kernel,
Uptime: uptime,
CPUUsage: req.CPU.UsagePercent,
MemoryUsage: req.Memory.UsagePercent,
DiskUsage: diskAvg,
+45
View File
@@ -79,6 +79,51 @@ func TestReceiveReportMatchesExistingAssetByIP(t *testing.T) {
}
}
func TestReceiveReportUsesHeartbeatUptimeWithoutSystemPayload(t *testing.T) {
gin.SetMode(gin.TestMode)
db, err := gorm.Open(sqlite.Open("file:agent_report_heartbeat_uptime?mode=memory&cache=shared"), &gorm.Config{
Logger: gormlogger.Default.LogMode(gormlogger.Silent),
})
if err != nil {
t.Fatalf("打开测试数据库失败: %v", err)
}
if err := db.AutoMigrate(&model.Server{}, &model.AgentReport{}, &model.HealthProbe{}); err != nil {
t.Fatalf("迁移测试表失败: %v", err)
}
server := model.Server{Hostname: "c-monitor", IP: "172.19.16.113", Status: "running"}
if err := db.Create(&server).Error; err != nil {
t.Fatalf("创建测试资产失败: %v", err)
}
body := []byte(`{
"hostname":"c-monitor",
"ip":"172.19.16.113",
"agent_version":"v2.1.0",
"cpu":{"usage_percent":1.5,"core_count":4},
"memory":{"total_gb":15.1,"used_gb":1.8,"available_gb":13.3,"usage_percent":11.9},
"heartbeat":{"uptime_sec":7200}
}`)
req := httptest.NewRequest(http.MethodPost, "/api/v1/agent/report", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
recorder := httptest.NewRecorder()
c, _ := gin.CreateTestContext(recorder)
c.Request = req
NewAgentHandler(db, nil, nil).ReceiveReport(c)
if recorder.Code != http.StatusOK {
t.Fatalf("上报返回码 = %d, body=%s", recorder.Code, recorder.Body.String())
}
var gotServer model.Server
if err := db.Take(&gotServer, server.ID).Error; err != nil {
t.Fatalf("读取资产失败: %v", err)
}
if gotServer.Uptime != 7200 {
t.Fatalf("心跳运行时长未写入资产: got=%d want=7200", gotServer.Uptime)
}
}
func TestGetLatestReportsUsesUTCTimeBoundary(t *testing.T) {
gin.SetMode(gin.TestMode)
db, err := gorm.Open(sqlite.Open("file:agent_latest_utc?mode=memory&cache=shared"), &gorm.Config{
@@ -33,6 +33,8 @@ export function MetricChartCard({
const [zoomed, setZoomed] = useState(false);
const chartData = data.map((d) => ({ ...d, value: d[aggType] }));
const shortTitle = title.replace(/\(.+\)/, '').trim();
// 当前值可能已包含 %、GB、KB/s 等单位,避免卡片重复拼接。 // Logan.Li
const displayUnit = /[%a-zA-Z/]/.test(value) ? '' : unit;
return (
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
@@ -52,7 +54,7 @@ export function MetricChartCard({
</div>
<div className="mb-2 pl-10">
<span className="text-xl font-bold text-[var(--dg-text-primary)] tabular-nums">{value}</span>
{unit && <span className="text-xs text-[var(--dg-text-muted)] ml-1">{unit}</span>}
{displayUnit && <span className="text-xs text-[var(--dg-text-muted)] ml-1">{displayUnit}</span>}
</div>
<ZoomableAreaChart
ref={chartRef}
@@ -62,7 +64,7 @@ export function MetricChartCard({
unit={unit}
chartId={safeChartId(title)}
title={title}
subtitle={`${t('monitor.metricChart.currentPrefix')} ${value}${unit ? ` ${unit}` : ''}`}
subtitle={`${t('monitor.metricChart.currentPrefix')} ${value}${displayUnit ? ` ${displayUnit}` : ''}`}
height={188}
toolbarPlacement="header"
onZoomedChange={setZoomed}
@@ -71,4 +73,4 @@ export function MetricChartCard({
</CardContent>
</Card>
);
}
}