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()
|
sysInfo := collector.CollectSystem()
|
||||||
logger.Printf("系统信息: OS=%q Kernel=%q", sysInfo.OSVersion, sysInfo.Kernel)
|
logger.Printf("系统信息: OS=%q Kernel=%q", sysInfo.OSVersion, sysInfo.Kernel)
|
||||||
|
agentStartedAt := time.Now()
|
||||||
|
|
||||||
localIP := detectLocalIP()
|
localIP := detectLocalIP()
|
||||||
logger.Printf("探测本机 IP: %s", localIP)
|
logger.Printf("探测本机 IP: %s", localIP)
|
||||||
|
|
||||||
sysReportCount := 0
|
|
||||||
|
|
||||||
taskExecutor := task.New(logger)
|
taskExecutor := task.New(logger)
|
||||||
taskClient := task.NewClient(httpCli, cfg.Hostname, logger)
|
taskClient := task.NewClient(httpCli, cfg.Hostname, logger)
|
||||||
|
|
||||||
@@ -135,20 +134,17 @@ func main() {
|
|||||||
var probeConfigs []model.ProbeConfig
|
var probeConfigs []model.ProbeConfig
|
||||||
|
|
||||||
collectAndReport := func() {
|
collectAndReport := func() {
|
||||||
|
currentUptime := sysInfo.UptimeSeconds + int64(time.Since(agentStartedAt).Seconds())
|
||||||
report := &model.Report{
|
report := &model.Report{
|
||||||
Hostname: cfg.Hostname,
|
Hostname: cfg.Hostname,
|
||||||
IP: localIP,
|
IP: localIP,
|
||||||
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
||||||
AgentVersion: version,
|
AgentVersion: version,
|
||||||
}
|
System: &model.SystemStat{
|
||||||
|
|
||||||
if sysReportCount < 3 && (sysInfo.OSVersion != "" || sysInfo.Kernel != "") {
|
|
||||||
report.System = &model.SystemStat{
|
|
||||||
OSVersion: sysInfo.OSVersion,
|
OSVersion: sysInfo.OSVersion,
|
||||||
Kernel: sysInfo.Kernel,
|
Kernel: sysInfo.Kernel,
|
||||||
Uptime: sysInfo.UptimeSeconds,
|
Uptime: currentUptime,
|
||||||
}
|
},
|
||||||
sysReportCount++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if stat, err := cpuCollector.Collect(); err != nil {
|
if stat, err := cpuCollector.Collect(); err != nil {
|
||||||
@@ -211,7 +207,7 @@ func main() {
|
|||||||
runtime.ReadMemStats(&memStats)
|
runtime.ReadMemStats(&memStats)
|
||||||
report.Heartbeat = model.AgentHeartbeat{
|
report.Heartbeat = model.AgentHeartbeat{
|
||||||
Version: version,
|
Version: version,
|
||||||
UptimeSec: sysInfo.UptimeSeconds,
|
UptimeSec: currentUptime,
|
||||||
SelfCPU: 0,
|
SelfCPU: 0,
|
||||||
SelfRSSMB: float64(memStats.Alloc) / 1024.0 / 1024.0,
|
SelfRSSMB: float64(memStats.Alloc) / 1024.0 / 1024.0,
|
||||||
ReportSeq: reportSeq,
|
ReportSeq: reportSeq,
|
||||||
|
|||||||
@@ -150,6 +150,10 @@ func (h *AgentHandler) ReceiveReport(c *gin.Context) {
|
|||||||
updates["uptime"] = req.System.Uptime
|
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
|
var result *gorm.DB
|
||||||
if assetMatched {
|
if assetMatched {
|
||||||
result = h.db.Model(&model.Server{}).Where("id = ?", matchedServer.ID).Updates(updates)
|
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)都新建记录导致资产越来越多。
|
// 避免 Agent 每次上报(10s)都新建记录导致资产越来越多。
|
||||||
if result.Error == nil && result.RowsAffected == 0 {
|
if result.Error == nil && result.RowsAffected == 0 {
|
||||||
osName := ""
|
osName := ""
|
||||||
|
kernel := ""
|
||||||
|
uptime := req.Heartbeat.UptimeSec
|
||||||
if req.System != nil {
|
if req.System != nil {
|
||||||
osName = req.System.OSVersion
|
osName = req.System.OSVersion
|
||||||
|
kernel = req.System.Kernel
|
||||||
|
if req.System.Uptime > 0 {
|
||||||
|
uptime = req.System.Uptime
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var existing model.Server
|
var existing model.Server
|
||||||
if h.db.Where("hostname = ?", req.Hostname).Take(&existing).Error == nil {
|
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,
|
"ip": matchIP, "os": osName, "cpu": req.CPU.CoreCount,
|
||||||
"memory": memMB, "disk": diskGB, "status": "running",
|
"memory": memMB, "disk": diskGB, "status": "running",
|
||||||
"agent_status": "online", "agent_version": req.AgentVersion,
|
"agent_status": "online", "agent_version": req.AgentVersion,
|
||||||
"cpu_usage": req.CPU.UsagePercent, "memory_usage": req.Memory.UsagePercent,
|
"cpu_usage": req.CPU.UsagePercent, "memory_usage": req.Memory.UsagePercent,
|
||||||
"disk_usage": diskAvg,
|
"disk_usage": diskAvg,
|
||||||
})
|
}
|
||||||
|
if kernel != "" {
|
||||||
|
existingUpdates["kernel"] = kernel
|
||||||
|
}
|
||||||
|
if uptime > 0 {
|
||||||
|
existingUpdates["uptime"] = uptime
|
||||||
|
}
|
||||||
|
h.db.Model(&existing).Updates(existingUpdates)
|
||||||
} else {
|
} else {
|
||||||
// 真正的新主机 → 注册,hostname 无唯一索引时不使用 OnConflict。 // Logan.Li
|
// 真正的新主机 → 注册,hostname 无唯一索引时不使用 OnConflict。 // Logan.Li
|
||||||
newServer := model.Server{
|
newServer := model.Server{
|
||||||
@@ -190,6 +207,8 @@ func (h *AgentHandler) ReceiveReport(c *gin.Context) {
|
|||||||
Status: "running",
|
Status: "running",
|
||||||
AgentStatus: "online",
|
AgentStatus: "online",
|
||||||
AgentVersion: req.AgentVersion,
|
AgentVersion: req.AgentVersion,
|
||||||
|
Kernel: kernel,
|
||||||
|
Uptime: uptime,
|
||||||
CPUUsage: req.CPU.UsagePercent,
|
CPUUsage: req.CPU.UsagePercent,
|
||||||
MemoryUsage: req.Memory.UsagePercent,
|
MemoryUsage: req.Memory.UsagePercent,
|
||||||
DiskUsage: diskAvg,
|
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) {
|
func TestGetLatestReportsUsesUTCTimeBoundary(t *testing.T) {
|
||||||
gin.SetMode(gin.TestMode)
|
gin.SetMode(gin.TestMode)
|
||||||
db, err := gorm.Open(sqlite.Open("file:agent_latest_utc?mode=memory&cache=shared"), &gorm.Config{
|
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 [zoomed, setZoomed] = useState(false);
|
||||||
const chartData = data.map((d) => ({ ...d, value: d[aggType] }));
|
const chartData = data.map((d) => ({ ...d, value: d[aggType] }));
|
||||||
const shortTitle = title.replace(/\(.+\)/, '').trim();
|
const shortTitle = title.replace(/\(.+\)/, '').trim();
|
||||||
|
// 当前值可能已包含 %、GB、KB/s 等单位,避免卡片重复拼接。 // Logan.Li
|
||||||
|
const displayUnit = /[%a-zA-Z/]/.test(value) ? '' : unit;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||||
@@ -52,7 +54,7 @@ export function MetricChartCard({
|
|||||||
</div>
|
</div>
|
||||||
<div className="mb-2 pl-10">
|
<div className="mb-2 pl-10">
|
||||||
<span className="text-xl font-bold text-[var(--dg-text-primary)] tabular-nums">{value}</span>
|
<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>
|
</div>
|
||||||
<ZoomableAreaChart
|
<ZoomableAreaChart
|
||||||
ref={chartRef}
|
ref={chartRef}
|
||||||
@@ -62,7 +64,7 @@ export function MetricChartCard({
|
|||||||
unit={unit}
|
unit={unit}
|
||||||
chartId={safeChartId(title)}
|
chartId={safeChartId(title)}
|
||||||
title={title}
|
title={title}
|
||||||
subtitle={`${t('monitor.metricChart.currentPrefix')} ${value}${unit ? ` ${unit}` : ''}`}
|
subtitle={`${t('monitor.metricChart.currentPrefix')} ${value}${displayUnit ? ` ${displayUnit}` : ''}`}
|
||||||
height={188}
|
height={188}
|
||||||
toolbarPlacement="header"
|
toolbarPlacement="header"
|
||||||
onZoomedChange={setZoomed}
|
onZoomedChange={setZoomed}
|
||||||
@@ -71,4 +73,4 @@ export function MetricChartCard({
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user