fix: 完善Agent系统信息上报
- 持续上报系统元数据并动态更新运行时长 - 兼容旧版心跳补齐资产运行时长 - 修复监控指标当前值重复显示单位
This commit is contained in:
1 parent
0c7c63017c
commit
699560a270
4 files changed
+77
-15
No files matched your search
+6
-10
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user