diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/metrics/WikiMetrics.java b/mateclaw-server/src/main/java/vip/mate/wiki/metrics/WikiMetrics.java new file mode 100644 index 00000000..34655afb --- /dev/null +++ b/mateclaw-server/src/main/java/vip/mate/wiki/metrics/WikiMetrics.java @@ -0,0 +1,189 @@ +package vip.mate.wiki.metrics; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.Timer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Centralized wiki metrics gateway. + * + *
All wiki-domain metrics flow through this Bean so we can: + *
Mirrors the decorator pattern used in {@code vip.mate.memory.spi.decorator.MetricsMemoryProvider} + * but is service-flavored rather than decorator-flavored: callers inject this bean and + * push metrics through explicit methods rather than wrapping a delegate. + * + *
Method families: + *
{@code
+ * try (var sample = metrics.startTimer("wiki.compile.stage", "stage", "summary")) {
+ * // work...
+ * }
+ * }
+ */
+ public WikiTimerSample startTimer(String name, String... keyValues) {
+ return new WikiTimerSample(this, name, keyValues, System.nanoTime());
+ }
+
+ /** Used by {@link WikiTimerSample#close()} — package-private. */
+ void recordTimer(String name, Tags tags, long durationNanos) {
+ if (registry == null) {
+ return;
+ }
+ getTimer(name, tags).record(Duration.ofNanos(durationNanos));
+ }
+
+ // ==================== Internal cache to avoid registering same meter twice ====================
+
+ private Counter getCounter(String name, Tags tags) {
+ return counterCache.computeIfAbsent(meterKey(name, tags),
+ k -> registry.counter(name, tags));
+ }
+
+ private Timer getTimer(String name, Tags tags) {
+ return timerCache.computeIfAbsent(meterKey(name, tags),
+ k -> registry.timer(name, tags));
+ }
+
+ private static String meterKey(String name, Tags tags) {
+ StringBuilder sb = new StringBuilder(name);
+ for (Tag t : tags) {
+ sb.append('|').append(t.getKey()).append('=').append(t.getValue());
+ }
+ return sb.toString();
+ }
+}
diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/metrics/WikiTimerSample.java b/mateclaw-server/src/main/java/vip/mate/wiki/metrics/WikiTimerSample.java
new file mode 100644
index 00000000..d6e470a5
--- /dev/null
+++ b/mateclaw-server/src/main/java/vip/mate/wiki/metrics/WikiTimerSample.java
@@ -0,0 +1,43 @@
+package vip.mate.wiki.metrics;
+
+import io.micrometer.core.instrument.Tag;
+import io.micrometer.core.instrument.Tags;
+
+/**
+ * AutoCloseable timer sample for try-with-resources usage.
+ *
+ * Created by {@link WikiMetrics#startTimer(String, String...)}; closing the + * sample records the elapsed nanoseconds against the named timer with the + * given key-value tags. Closing twice is a no-op — safe inside try/finally + * blocks that may also have an explicit {@code close()} call. + * + * @author MateClaw Team + */ +public class WikiTimerSample implements AutoCloseable { + + private final WikiMetrics metrics; + private final String name; + private final Tags tags; + private final long startNanos; + private boolean closed; + + WikiTimerSample(WikiMetrics metrics, String name, String[] keyValues, long startNanos) { + this.metrics = metrics; + this.name = name; + Tags built = Tags.empty(); + for (int i = 0; i + 1 < keyValues.length; i += 2) { + built = built.and(Tag.of(keyValues[i], keyValues[i + 1])); + } + this.tags = built; + this.startNanos = startNanos; + } + + @Override + public void close() { + if (closed) { + return; + } + closed = true; + metrics.recordTimer(name, tags, System.nanoTime() - startNanos); + } +}