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);

Reply via email to