From 7967f454ec843f8176cb57a2d1d90ecbe5276723 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:53:20 +0800 Subject: [PATCH] =?UTF-8?q?task-188:=20=E6=8C=87=E6=A0=87=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=E8=A7=84=E8=8C=83=E5=A5=91=E7=BA=A6=EF=BC=88moduleTyp?= =?UTF-8?q?e/instanceId=20=E5=BF=85=E5=B8=A6=E3=80=81null=20=E5=BD=92?= =?UTF-8?q?=E4=B8=80=20unknown=E3=80=81=E6=97=A0=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=BA=A7=E6=A0=87=E7=AD=BE=E3=80=81=E7=A3=81=E7=9B=98=E6=8C=89?= =?UTF-8?q?=20path=E3=80=81=E6=A0=87=E7=AD=BE=E5=90=8D=E5=86=BB=E7=BB=93?= =?UTF-8?q?=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 --- .../aiimage/metrics/MetricTagSpecTest.java | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 backend-java/src/test/java/com/nanri/aiimage/metrics/MetricTagSpecTest.java diff --git a/backend-java/src/test/java/com/nanri/aiimage/metrics/MetricTagSpecTest.java b/backend-java/src/test/java/com/nanri/aiimage/metrics/MetricTagSpecTest.java new file mode 100644 index 00000000..3f348100 --- /dev/null +++ b/backend-java/src/test/java/com/nanri/aiimage/metrics/MetricTagSpecTest.java @@ -0,0 +1,112 @@ +package com.nanri.aiimage.metrics; + +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * task-188:指标标签规范契约(spec 11 §1)。 + * moduleType/instanceId 必带(任务/文件/上传族);null/blank 归一 unknown;不记录用户级标签; + * 值清洗;磁盘按 path 标签。标签名冻结。 + */ +class MetricTagSpecTest { + + private final SimpleMeterRegistry registry = new SimpleMeterRegistry(); + private final TaskObservabilityMetrics metrics = new TaskObservabilityMetrics("server-110", registry); + + private void seedAllFamilies() { + metrics.taskCreated("similarasin"); + metrics.taskRunningDelta("similarasin", 1); + metrics.taskTerminal("similarasin", "success"); + metrics.taskTerminal("similarasin", "failed"); + metrics.taskDuration("similarasin", 10L); + metrics.taskHeartbeatAge("similarasin", 10L); + metrics.fileJobState("similarasin", "running"); + metrics.fileJobDuration("similarasin", 10L); + metrics.upload("similarasin", 1L, 1L, true); + } + + @Test + void taskFamilyCarriesModuleAndInstanceLabels() { + seedAllFamilies(); + for (String name : List.of("aiimage.task.created", "aiimage.task.running", "aiimage.task.success", + "aiimage.task.duration", "aiimage.task.heartbeat.age")) { + assertTrue(hasTagKey(name, "moduleType"), name + " 应带 moduleType"); + assertTrue(hasTagKey(name, "instanceId"), name + " 应带 instanceId"); + } + } + + @Test + void fileJobAndUploadCarryModuleAndInstanceLabels() { + seedAllFamilies(); + for (String name : List.of("aiimage.file-job.running", "aiimage.file-job.duration", + "aiimage.upload.duration", "aiimage.upload.size")) { + assertTrue(hasTagKey(name, "moduleType"), name + " 应带 moduleType"); + assertTrue(hasTagKey(name, "instanceId"), name + " 应带 instanceId"); + } + } + + @Test + void nullModuleNormalizedToUnknown() { + metrics.taskCreated(null); + metrics.taskCreated(" "); + assertEquals("unknown", + registry.get("aiimage.task.created").counter().getId().getTag("moduleType")); + } + + @Test + void nullInstanceNormalizedToUnknown() { + TaskObservabilityMetrics noInstance = new TaskObservabilityMetrics(null, registry); + noInstance.taskCreated("brand"); + assertEquals("unknown", + registry.get("aiimage.task.created").counter().getId().getTag("instanceId")); + } + + @Test + void noUserLevelLabels() { + seedAllFamilies(); + for (Meter meter : registry.getMeters()) { + for (Tag tag : meter.getId().getTags()) { + assertFalse(List.of("userId", "user_id", "uid", "operatorId", "operator_id").contains(tag.getKey()), + "指标不应带用户级标签: " + tag.getKey()); + } + } + } + + @Test + void labelValueTrimmed() { + metrics.taskCreated(" publish "); + assertEquals("publish", + registry.get("aiimage.task.created").counter().getId().getTag("moduleType")); + } + + @Test + void tagKeysFrozen() { + metrics.taskCreated("brand"); + Meter meter = registry.get("aiimage.task.created").counter(); + List keys = meter.getId().getTags().stream().map(Tag::getKey).sorted().toList(); + assertEquals(List.of("instanceId", "moduleType"), keys, "任务族标签应只有 moduleType/instanceId"); + } + + @Test + void diskFamilyUsesPathLabel() { + metrics.disk("/tmp/x", 10L, 1L); + assertTrue(registry.get("aiimage.tmpdisk.used").gauge().getId().getTags().stream() + .anyMatch(t -> t.getKey().equals("path") && t.getValue().equals("/tmp/x"))); + assertFalse(registry.get("aiimage.tmpdisk.used").gauge().getId().getTags().stream() + .anyMatch(t -> t.getKey().equals("instanceId")), "磁盘指标按 path,不必带 instanceId"); + } + + private boolean hasTagKey(String name, String key) { + return registry.find(name).meters().stream() + .flatMap(m -> m.getId().getTags().stream()) + .anyMatch(t -> t.getKey().equals(key)); + } +}