task-188: 指标标签规范契约(moduleType/instanceId 必带、null 归一 unknown、无用户级标签、磁盘按 path、标签名冻结)+ 8 条测试

This commit is contained in:
2026-09-04 23:53:20 +08:00
parent 7b73a2cf6e
commit 7967f454ec
@@ -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<String> 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));
}
}