This is an automated email from the ASF dual-hosted git repository. luigidemasi pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit 5695b943f2e2980fb4ed096095f965fcd6e93091 Author: Luigi De Masi <[email protected]> AuthorDate: Fri Nov 13 15:31:30 2026 +0100 CAMEL-25648: Refine Semantic Micrometer collection metrics Expose the monotonically increasing collection error total as a function counter and document its Prometheus name. Verify the scrape type, count, stop cleanup and restart reset while preserving payload-free diagnostics. Reuse the fixed result-type allowlist across events. Co-authored-by: OpenAI Codex <[email protected]> Signed-off-by: Luigi De Masi <[email protected]> --- .../camel/catalog/docs/semantic-micrometer.adoc | 3 ++- .../src/main/docs/semantic-micrometer.adoc | 3 ++- .../micrometer/MicrometerSemanticMetrics.java | 6 ++++-- .../micrometer/MicrometerSemanticMetricsTest.java | 21 ++++++++++++++++++--- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-micrometer.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-micrometer.adoc index c3d175dab600..465388c1ed83 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-micrometer.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-micrometer.adoc @@ -70,7 +70,8 @@ start. The sum can decrease; do not treat it as a monotonic counter. `+Inf` bucket. Bounds describe numeric values, not severity or policy decisions. |`camel.semantic.collection.loss` |Counter by fixed `reason`: `population_limit`, `answer_limit`, `invalid_event`, or `unsupported_measurement`. Loss is excluded from the corresponding result distribution. -|`camel.semantic.collection.errors` |Number of metric collection failures since collector start. +|`camel.semantic.collection.errors` |Function counter of metric collection failures since collector start. +Prometheus exposes `camel_semantic_collection_errors_total`; use `rate()` or `increase()` to account for restarts. |=== Assessment meters have `camelContext`, `expert`, `definition`, `operation`, `origin` and `result.type` tags. diff --git a/components/camel-ai/camel-semantic-micrometer/src/main/docs/semantic-micrometer.adoc b/components/camel-ai/camel-semantic-micrometer/src/main/docs/semantic-micrometer.adoc index c3d175dab600..465388c1ed83 100644 --- a/components/camel-ai/camel-semantic-micrometer/src/main/docs/semantic-micrometer.adoc +++ b/components/camel-ai/camel-semantic-micrometer/src/main/docs/semantic-micrometer.adoc @@ -70,7 +70,8 @@ start. The sum can decrease; do not treat it as a monotonic counter. `+Inf` bucket. Bounds describe numeric values, not severity or policy decisions. |`camel.semantic.collection.loss` |Counter by fixed `reason`: `population_limit`, `answer_limit`, `invalid_event`, or `unsupported_measurement`. Loss is excluded from the corresponding result distribution. -|`camel.semantic.collection.errors` |Number of metric collection failures since collector start. +|`camel.semantic.collection.errors` |Function counter of metric collection failures since collector start. +Prometheus exposes `camel_semantic_collection_errors_total`; use `rate()` or `increase()` to account for restarts. |=== Assessment meters have `camelContext`, `expert`, `definition`, `operation`, `origin` and `result.type` tags. diff --git a/components/camel-ai/camel-semantic-micrometer/src/main/java/org/apache/camel/semantic/micrometer/MicrometerSemanticMetrics.java b/components/camel-ai/camel-semantic-micrometer/src/main/java/org/apache/camel/semantic/micrometer/MicrometerSemanticMetrics.java index 589e1e8bf3ac..0ac707265e45 100644 --- a/components/camel-ai/camel-semantic-micrometer/src/main/java/org/apache/camel/semantic/micrometer/MicrometerSemanticMetrics.java +++ b/components/camel-ai/camel-semantic-micrometer/src/main/java/org/apache/camel/semantic/micrometer/MicrometerSemanticMetrics.java @@ -27,6 +27,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.FunctionCounter; import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.MeterRegistry; @@ -54,6 +55,7 @@ public final class MicrometerSemanticMetrics extends EventNotifierSupport { private static final int MAX_POPULATIONS = 100; private static final int MAX_ANSWERS = 64; private static final Set<String> STATUSES = Set.of("success", "failed", "not_executed"); + private static final Set<String> RESULT_TYPES = Set.of("", "BOOLEAN", "CHOICE", "SCORE", "CLASSIFICATION"); private static final Set<String> UNITS = Set.of("1", "ratio", "bytes", "seconds", "milliseconds", "nanoseconds", "count", "requests", "assessments"); private static final Set<String> DIMENSIONS = Set.of("expert", "definition", "operation", "provider", @@ -164,7 +166,7 @@ public final class MicrometerSemanticMetrics extends EventNotifierSupport { return; } String type = data.get("semantics") instanceof Map<?, ?> semantics ? text(semantics, "resultType") : ""; - if (!Set.of("", "BOOLEAN", "CHOICE", "SCORE", "CLASSIFICATION").contains(type)) { + if (!RESULT_TYPES.contains(type)) { invalidEvents.increment(); return; } @@ -246,7 +248,7 @@ public final class MicrometerSemanticMetrics extends EventNotifierSupport { existing = new HashSet<>(registry.getMeters()); contextTags = Tags.of(CAMEL_CONTEXT_TAG, getCamelContext().getName()); errors.set(0); - own(Gauge.builder("camel.semantic.collection.errors", errors, AtomicLong::doubleValue) + own(FunctionCounter.builder("camel.semantic.collection.errors", errors, AtomicLong::doubleValue) .description("Metric collection failures since this collector started").tags(contextTags).register(registry)); populationLoss = counter("collection.loss", contextTags.and("reason", "population_limit")); answerLoss = counter("collection.loss", contextTags.and("reason", "answer_limit")); diff --git a/components/camel-ai/camel-semantic-micrometer/src/test/java/org/apache/camel/semantic/micrometer/MicrometerSemanticMetricsTest.java b/components/camel-ai/camel-semantic-micrometer/src/test/java/org/apache/camel/semantic/micrometer/MicrometerSemanticMetricsTest.java index 7abf5f1e0adc..1743bccaa500 100644 --- a/components/camel-ai/camel-semantic-micrometer/src/test/java/org/apache/camel/semantic/micrometer/MicrometerSemanticMetricsTest.java +++ b/components/camel-ai/camel-semantic-micrometer/src/test/java/org/apache/camel/semantic/micrometer/MicrometerSemanticMetricsTest.java @@ -157,7 +157,7 @@ class MicrometerSemanticMetricsTest { context.start(); send(context, event(ResultType.SCORE, Map.of("value", 0.4), "success", 1)); send(context, event(ResultType.SCORE, Map.of("value", 1.6), "success", 1)); - assertThat(registry.get("camel.semantic.collection.errors").gauge().value()).isEqualTo(1); + assertThat(registry.get("camel.semantic.collection.errors").functionCounter().count()).isEqualTo(1); assertThat(registry.get("camel.semantic.score.observations").counter().count()).isEqualTo(1); assertThat(registry.get("camel.semantic.score.sum").gauge().value()).isEqualTo(1.6); assertThat(registry.get("camel.semantic.score.distribution").tag("le", "+Inf").counter().count()).isEqualTo(1); @@ -325,7 +325,7 @@ class MicrometerSemanticMetricsTest { appender.start(); logger.addAppender(appender); logger.setLevel(Level.WARN); - var registry = new SimpleMeterRegistry(); + var registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); try (AutoCloseable closeRegistry = registry::close; var context = new DefaultCamelContext()) { registry.config().meterFilter(new MeterFilter() { @Override @@ -339,15 +339,30 @@ class MicrometerSemanticMetricsTest { var metrics = new MicrometerSemanticMetrics(registry); context.getManagementStrategy().addEventNotifier(metrics); context.start(); + var errors = registry.get("camel.semantic.collection.errors").functionCounter(); + assertThat(errors.count()).isZero(); metrics.notify(event(ResultType.BOOLEAN, Map.of("value", true), "success", 1)); metrics.notify(event(ResultType.BOOLEAN, Map.of("value", false), "success", 1)); - assertThat(registry.get("camel.semantic.collection.errors").gauge().value()).isEqualTo(2); + assertThat(errors.count()).isEqualTo(2); + assertThat(registry.scrape()).contains("# TYPE camel_semantic_collection_errors_total counter"); + assertThat(registry.scrape().lines() + .filter(line -> line.startsWith("camel_semantic_collection_errors_total{"))) + .singleElement().asString().matches(".*camelContext=\"[^\"]+\".*} 2(?:\\.0)?"); assertThat(messages).singleElement().satisfies(message -> { assertThat(message.getLevel()).isEqualTo(Level.WARN); assertThat(message.getMessage().getFormattedMessage()).contains("java.lang.IllegalStateException") .doesNotContain("private-provider-error", "private-input"); assertThat(message.getThrown()).isNull(); }); + context.stop(); + assertThat(registry.find("camel.semantic.collection.errors").meters()).isEmpty(); + context.start(); + var restartedErrors = registry.get("camel.semantic.collection.errors").functionCounter(); + assertThat(restartedErrors).isNotSameAs(errors); + assertThat(restartedErrors.count()).isZero(); + metrics.notify(event(ResultType.BOOLEAN, Map.of("value", true), "success", 1)); + assertThat(restartedErrors.count()).isEqualTo(1); + assertThat(messages).hasSize(2); } finally { logger.setLevel(previous); logger.removeAppender(appender);
