From 17cfd6902bbe4acfd53264d385557326f1427ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Fri, 4 Sep 2026 23:48:21 +0800 Subject: [PATCH] =?UTF-8?q?task-184:=20task.heartbeat.age=20=E5=BF=83?= =?UTF-8?q?=E8=B7=B3=E5=B9=B4=E9=BE=84=20gauge=20=E5=A5=91=E7=BA=A6?= =?UTF-8?q?=EF=BC=88=E6=96=B0=E9=B2=9C/=E9=99=88=E6=97=A7/=E8=B4=9F?= =?UTF-8?q?=E6=97=B6=E9=92=9F=E4=BF=9D=E6=8A=A4/=E6=9C=AA=E5=86=99?= =?UTF-8?q?=E6=97=A0=20meter=EF=BC=89+=208=20=E6=9D=A1=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metrics/TaskHeartbeatAgeMetricsTest.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 backend-java/src/test/java/com/nanri/aiimage/metrics/TaskHeartbeatAgeMetricsTest.java diff --git a/backend-java/src/test/java/com/nanri/aiimage/metrics/TaskHeartbeatAgeMetricsTest.java b/backend-java/src/test/java/com/nanri/aiimage/metrics/TaskHeartbeatAgeMetricsTest.java new file mode 100644 index 00000000..1615b720 --- /dev/null +++ b/backend-java/src/test/java/com/nanri/aiimage/metrics/TaskHeartbeatAgeMetricsTest.java @@ -0,0 +1,79 @@ +package com.nanri.aiimage.metrics; + +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * task-184:task.heartbeat.age 心跳年龄 gauge 契约(spec 11 §1)。 + * 年龄 = 距最后心跳的毫秒数,由 TaskHeartbeatService/巡检写入;新鲜/陈旧可观测; + * 刚心跳≈0;时钟负保护;未设置不产生 meter。 + */ +class TaskHeartbeatAgeMetricsTest { + + private final SimpleMeterRegistry registry = new SimpleMeterRegistry(); + private final TaskObservabilityMetrics metrics = new TaskObservabilityMetrics("server-110", registry); + + private io.micrometer.core.instrument.Gauge gauge(String moduleType) { + return registry.get("aiimage.task.heartbeat.age").tag("moduleType", moduleType).gauge(); + } + + @Test + void ageComputedFreshAndStale() { + metrics.taskHeartbeatAge("similarasin", 200L); + assertEquals(200.0, gauge("similarasin").value(), "新鲜心跳年龄应小"); + metrics.taskHeartbeatAge("similarasin", 3_600_000L); + assertEquals(3_600_000.0, gauge("similarasin").value(), "陈旧心跳年龄应大"); + } + + @Test + void ageJustHeartbeatIsZero() { + metrics.taskHeartbeatAge("similarasin", 0L); + assertEquals(0.0, gauge("similarasin").value()); + } + + @Test + void ageClockNegativeGuard() { + metrics.taskHeartbeatAge("similarasin", -100L); + assertEquals(0.0, gauge("similarasin").value(), "时钟回拨导致的负年龄按 0 处理"); + } + + @Test + void ageUpdatesOnEachWrite() { + metrics.taskHeartbeatAge("similarasin", 1_000L); + metrics.taskHeartbeatAge("similarasin", 2_000L); + assertEquals(2_000.0, gauge("similarasin").value()); + } + + @Test + void ageSplitByModule() { + metrics.taskHeartbeatAge("similarasin", 500L); + metrics.taskHeartbeatAge("publish", 900L); + assertEquals(500.0, gauge("similarasin").value()); + assertEquals(900.0, gauge("publish").value()); + } + + @Test + void noHeartbeatYetNoGauge() { + assertNull(registry.find("aiimage.task.heartbeat.age") + .tag("moduleType", "withdraw").gauge(), + "从未写入心跳年龄时不应产生 gauge"); + } + + @Test + void ageNameFrozen() { + metrics.taskHeartbeatAge("similarasin", 10L); + assertTrue(registry.getMeters().stream() + .anyMatch(m -> m.getId().getName().equals("aiimage.task.heartbeat.age"))); + } + + @Test + void ageCarriesInstanceLabel() { + metrics.taskHeartbeatAge("similarasin", 10L); + assertEquals("server-110", + registry.get("aiimage.task.heartbeat.age").gauge().getId().getTag("instanceId")); + } +}