This is an automated email from the ASF dual-hosted git repository.

oscerd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 1e84bfd33251 CAMEL-23848: Camel-PQC - add optional Micrometer 
metrics/observability (#24550)
1e84bfd33251 is described below

commit 1e84bfd3325143e31e2be1ad7985d61379296b5b
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Jul 9 11:59:43 2026 +0200

    CAMEL-23848: Camel-PQC - add optional Micrometer metrics/observability 
(#24550)
    
    CAMEL-23848: Camel-PQC add optional Micrometer metrics
    
    Add optional Micrometer metrics/observability for the PQC producer. When
    Micrometer is on the classpath and a MeterRegistry is available in the Camel
    registry, the producer records:
    - camel.pqc.operations (counter) tagged by operation, algorithm and outcome
    - camel.pqc.stateful.key.remaining (gauge) tagged by algorithm, for stateful
      signature keys (XMSS/XMSSMT/LMS/HSS)
    
    Micrometer is an optional dependency: all io.micrometer references are 
isolated
    in PQCMicrometerMetrics, which is only loaded once a class-resolver check
    confirms Micrometer is present, so there is no hard dependency and no
    NoClassDefFoundError when it is absent.
    
    Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
---
 components/camel-pqc/pom.xml                       |   7 +
 .../camel-pqc/src/main/docs/pqc-component.adoc     |  24 +++
 .../apache/camel/component/pqc/PQCProducer.java    | 185 +++++++++++++--------
 .../camel/component/pqc/metrics/PQCMetrics.java    |  63 +++++++
 .../pqc/metrics/PQCMicrometerMetrics.java          |  88 ++++++++++
 .../apache/camel/component/pqc/PQCMetricsTest.java | 119 +++++++++++++
 6 files changed, 420 insertions(+), 66 deletions(-)

diff --git a/components/camel-pqc/pom.xml b/components/camel-pqc/pom.xml
index 2f17156c32a8..4003805dbd65 100644
--- a/components/camel-pqc/pom.xml
+++ b/components/camel-pqc/pom.xml
@@ -58,6 +58,13 @@
             <version>${jackson2-version}</version>
         </dependency>
 
+        <!-- Micrometer for optional PQC metrics/observability -->
+        <dependency>
+            <groupId>io.micrometer</groupId>
+            <artifactId>micrometer-core</artifactId>
+            <optional>true</optional>
+        </dependency>
+
         <!-- Spring Vault for HashicorpVaultKeyLifecycleManager (optional) -->
         <dependency>
             <groupId>org.springframework.vault</groupId>
diff --git a/components/camel-pqc/src/main/docs/pqc-component.adoc 
b/components/camel-pqc/src/main/docs/pqc-component.adoc
index d032860e2696..f5c0edb443eb 100644
--- a/components/camel-pqc/src/main/docs/pqc-component.adoc
+++ b/components/camel-pqc/src/main/docs/pqc-component.adoc
@@ -101,6 +101,30 @@ See xref:others:pqc-hybrid.adoc[PQC Hybrid Cryptography] 
for configuration detai
 
 See xref:others:pqc-key-lifecycle.adoc[PQC Key Lifecycle Management] for full 
details.
 
+== Metrics
+
+When https://micrometer.io[Micrometer] is on the classpath and a 
`MeterRegistry` bean is available in the Camel
+registry, the PQC producer records metrics automatically. Micrometer is an 
*optional* dependency: without it (or without
+a `MeterRegistry`) no metrics are emitted and there is no runtime overhead.
+
+[cols="2,1,4",options="header"]
+|===
+| Meter | Type | Description
+
+| `camel.pqc.operations`
+| Counter
+| Number of PQC operations performed, tagged by `operation` (sign, verify, 
generateSecretKeyEncapsulation,
+  extractSecretKeyFromEncapsulation, ...), `algorithm`, and `outcome` 
(`success` / `failure`).
+
+| `camel.pqc.stateful.key.remaining`
+| Gauge
+| Remaining signatures for a stateful signature key (XMSS, XMSSMT, LMS, HSS), 
tagged by `algorithm`. Registered only for
+  producers configured with a stateful signature algorithm.
+|===
+
+To enable the metrics, add `camel-micrometer` (or Micrometer directly) to your 
application and bind a `MeterRegistry` in
+the registry - for example a `SimpleMeterRegistry`, or the registry provided 
by Spring Boot or Quarkus.
+
 == Sub-Pages
 
 The PQC component documentation is split into focused sub-pages:
diff --git 
a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCProducer.java
 
b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCProducer.java
index 646a502a85fb..aae685a542da 100644
--- 
a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCProducer.java
+++ 
b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/PQCProducer.java
@@ -28,6 +28,7 @@ import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;
 
+import org.apache.camel.CamelContext;
 import org.apache.camel.Endpoint;
 import org.apache.camel.Exchange;
 import org.apache.camel.InvalidPayloadException;
@@ -36,6 +37,8 @@ import org.apache.camel.component.pqc.crypto.hybrid.HybridKEM;
 import org.apache.camel.component.pqc.crypto.hybrid.HybridSignature;
 import org.apache.camel.component.pqc.lifecycle.KeyLifecycleManager;
 import org.apache.camel.component.pqc.lifecycle.KeyMetadata;
+import org.apache.camel.component.pqc.metrics.PQCMetrics;
+import org.apache.camel.component.pqc.metrics.PQCMicrometerMetrics;
 import org.apache.camel.component.pqc.stateful.StatefulKeyState;
 import org.apache.camel.health.HealthCheck;
 import org.apache.camel.health.HealthCheckHelper;
@@ -117,6 +120,9 @@ public class PQCProducer extends DefaultProducer {
     private HealthCheck producerHealthCheck;
     private WritableHealthCheckRepository healthCheckRepository;
 
+    // Metrics (optional; a no-op unless Micrometer and a MeterRegistry are 
available)
+    private PQCMetrics metrics = PQCMetrics.NOOP;
+
     public PQCProducer(Endpoint endpoint) {
         super(endpoint);
     }
@@ -133,72 +139,78 @@ public class PQCProducer extends DefaultProducer {
     public void process(Exchange exchange) throws Exception {
         PQCOperations operation = determineOperation(exchange);
         enforceKeyStatus(exchange, operation);
-        switch (operation) {
-            case sign:
-                signature(exchange);
-                break;
-            case verify:
-                verification(exchange);
-                break;
-            case hybridSign:
-                hybridSignature(exchange);
-                break;
-            case hybridVerify:
-                hybridVerification(exchange);
-                break;
-            case generateSecretKeyEncapsulation:
-                generateEncapsulation(exchange);
-                break;
-            case extractSecretKeyEncapsulation:
-                extractEncapsulation(exchange);
-                break;
-            case extractSecretKeyFromEncapsulation:
-                extractSecretKeyFromEncapsulation(exchange);
-                break;
-            case hybridGenerateSecretKeyEncapsulation:
-                hybridGenerateEncapsulation(exchange);
-                break;
-            case hybridExtractSecretKeyEncapsulation:
-                hybridExtractEncapsulation(exchange);
-                break;
-            case hybridExtractSecretKeyFromEncapsulation:
-                hybridExtractSecretKeyFromEncapsulation(exchange);
-                break;
-            case generateKeyPair:
-                lifecycleGenerateKeyPair(exchange);
-                break;
-            case exportKey:
-                lifecycleExportKey(exchange);
-                break;
-            case importKey:
-                lifecycleImportKey(exchange);
-                break;
-            case rotateKey:
-                lifecycleRotateKey(exchange);
-                break;
-            case getKeyMetadata:
-                lifecycleGetKeyMetadata(exchange);
-                break;
-            case listKeys:
-                lifecycleListKeys(exchange);
-                break;
-            case expireKey:
-                lifecycleExpireKey(exchange);
-                break;
-            case revokeKey:
-                lifecycleRevokeKey(exchange);
-                break;
-            case getRemainingSignatures:
-                statefulGetRemainingSignatures(exchange);
-                break;
-            case getKeyState:
-                statefulGetKeyState(exchange);
-                break;
-            case deleteKeyState:
-                statefulDeleteKeyState(exchange);
-                break;
-            default:
-                throw new IllegalArgumentException("Unsupported operation");
+        boolean success = false;
+        try {
+            switch (operation) {
+                case sign:
+                    signature(exchange);
+                    break;
+                case verify:
+                    verification(exchange);
+                    break;
+                case hybridSign:
+                    hybridSignature(exchange);
+                    break;
+                case hybridVerify:
+                    hybridVerification(exchange);
+                    break;
+                case generateSecretKeyEncapsulation:
+                    generateEncapsulation(exchange);
+                    break;
+                case extractSecretKeyEncapsulation:
+                    extractEncapsulation(exchange);
+                    break;
+                case extractSecretKeyFromEncapsulation:
+                    extractSecretKeyFromEncapsulation(exchange);
+                    break;
+                case hybridGenerateSecretKeyEncapsulation:
+                    hybridGenerateEncapsulation(exchange);
+                    break;
+                case hybridExtractSecretKeyEncapsulation:
+                    hybridExtractEncapsulation(exchange);
+                    break;
+                case hybridExtractSecretKeyFromEncapsulation:
+                    hybridExtractSecretKeyFromEncapsulation(exchange);
+                    break;
+                case generateKeyPair:
+                    lifecycleGenerateKeyPair(exchange);
+                    break;
+                case exportKey:
+                    lifecycleExportKey(exchange);
+                    break;
+                case importKey:
+                    lifecycleImportKey(exchange);
+                    break;
+                case rotateKey:
+                    lifecycleRotateKey(exchange);
+                    break;
+                case getKeyMetadata:
+                    lifecycleGetKeyMetadata(exchange);
+                    break;
+                case listKeys:
+                    lifecycleListKeys(exchange);
+                    break;
+                case expireKey:
+                    lifecycleExpireKey(exchange);
+                    break;
+                case revokeKey:
+                    lifecycleRevokeKey(exchange);
+                    break;
+                case getRemainingSignatures:
+                    statefulGetRemainingSignatures(exchange);
+                    break;
+                case getKeyState:
+                    statefulGetKeyState(exchange);
+                    break;
+                case deleteKeyState:
+                    statefulDeleteKeyState(exchange);
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unsupported 
operation");
+            }
+            success = true;
+        } finally {
+            metrics.recordOperation(operation.name(), currentAlgorithm(), 
success);
         }
     }
 
@@ -214,6 +226,35 @@ public class PQCProducer extends DefaultProducer {
         return getEndpoint().getConfiguration();
     }
 
+    private String currentAlgorithm() {
+        String alg = getConfiguration().getSignatureAlgorithm();
+        if (ObjectHelper.isEmpty(alg)) {
+            alg = getConfiguration().getKeyEncapsulationAlgorithm();
+        }
+        return ObjectHelper.isNotEmpty(alg) ? alg : "unknown";
+    }
+
+    private PQCMetrics createMetrics() {
+        CamelContext camelContext = getEndpoint().getCamelContext();
+        // Only touch Micrometer if it is actually on the classpath, keeping 
it an optional dependency
+        if 
(camelContext.getClassResolver().resolveClass("io.micrometer.core.instrument.MeterRegistry")
 == null) {
+            return PQCMetrics.NOOP;
+        }
+        try {
+            return PQCMicrometerMetrics.create(camelContext);
+        } catch (Throwable t) {
+            LOG.debug("Micrometer metrics are not available for the PQC 
producer: {}", t.getMessage());
+            return PQCMetrics.NOOP;
+        }
+    }
+
+    private static boolean isStatefulSignatureAlgorithm(String algorithm) {
+        return PQCSignatureAlgorithms.XMSS.name().equals(algorithm)
+                || PQCSignatureAlgorithms.XMSSMT.name().equals(algorithm)
+                || PQCSignatureAlgorithms.LMS.name().equals(algorithm)
+                || PQCSignatureAlgorithms.HSS.name().equals(algorithm);
+    }
+
     /**
      * Enforces key status checks before cryptographic operations when strict 
key lifecycle mode is enabled. Looks up
      * the key metadata via the configured {@link KeyLifecycleManager} using 
the {@code CamelPQCKeyId} header.
@@ -411,6 +452,16 @@ public class PQCProducer extends DefaultProducer {
             
producerHealthCheck.setEnabled(getEndpoint().getComponent().isHealthCheckProducerEnabled());
             healthCheckRepository.addHealthCheck(producerHealthCheck);
         }
+
+        // Optional Micrometer metrics (no-op unless a MeterRegistry is 
available)
+        metrics = createMetrics();
+        String signatureAlgorithm = getConfiguration().getSignatureAlgorithm();
+        if (isStatefulSignatureAlgorithm(signatureAlgorithm)) {
+            metrics.registerStatefulKeyGauge(signatureAlgorithm, () -> {
+                KeyPair kp = getRuntimeKeyPair();
+                return kp != null && kp.getPrivate() != null ? 
getStatefulKeyRemaining(kp.getPrivate()) : -1;
+            });
+        }
     }
 
     @Override
@@ -419,6 +470,8 @@ public class PQCProducer extends DefaultProducer {
             healthCheckRepository.removeHealthCheck(producerHealthCheck);
             producerHealthCheck = null;
         }
+        metrics.close();
+        metrics = PQCMetrics.NOOP;
         super.doStop();
     }
 
diff --git 
a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/metrics/PQCMetrics.java
 
b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/metrics/PQCMetrics.java
new file mode 100644
index 000000000000..60889522fcda
--- /dev/null
+++ 
b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/metrics/PQCMetrics.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.pqc.metrics;
+
+import java.util.function.LongSupplier;
+
+/**
+ * Abstraction over the optional Micrometer metrics emitted by the PQC 
producer.
+ * <p/>
+ * When Micrometer is not on the classpath, or no {@code MeterRegistry} is 
available in the Camel registry,
+ * {@link #NOOP} is used and nothing is recorded. This interface is 
deliberately free of any {@code io.micrometer} type
+ * so that {@code PQCProducer} can reference it without forcing Micrometer 
onto the classpath - the concrete
+ * {@link PQCMicrometerMetrics} implementation (which does reference 
Micrometer) is only loaded once Micrometer is known
+ * to be present.
+ */
+public interface PQCMetrics {
+
+    /**
+     * A no-op instance used when metrics are disabled.
+     */
+    PQCMetrics NOOP = new PQCMetrics() {
+    };
+
+    /**
+     * Records that an operation was performed.
+     *
+     * @param operation the operation name (for example {@code sign}, {@code 
verify},
+     *                  {@code generateSecretKeyEncapsulation})
+     * @param algorithm the PQC algorithm in use
+     * @param success   whether the operation completed successfully
+     */
+    default void recordOperation(String operation, String algorithm, boolean 
success) {
+    }
+
+    /**
+     * Registers a gauge reporting the remaining signatures of a stateful 
signature key.
+     *
+     * @param algorithm         the stateful signature algorithm (XMSS, 
XMSSMT, LMS, HSS)
+     * @param remainingSupplier supplies the current remaining-signature count 
(negative when unavailable)
+     */
+    default void registerStatefulKeyGauge(String algorithm, LongSupplier 
remainingSupplier) {
+    }
+
+    /**
+     * Removes any meters registered by this instance.
+     */
+    default void close() {
+    }
+}
diff --git 
a/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/metrics/PQCMicrometerMetrics.java
 
b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/metrics/PQCMicrometerMetrics.java
new file mode 100644
index 000000000000..63d20d3642a5
--- /dev/null
+++ 
b/components/camel-pqc/src/main/java/org/apache/camel/component/pqc/metrics/PQCMicrometerMetrics.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.pqc.metrics;
+
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.LongSupplier;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.MeterRegistry;
+import org.apache.camel.CamelContext;
+
+/**
+ * Micrometer-backed {@link PQCMetrics}. All {@code io.micrometer} references 
live in this class so that it is only
+ * loaded when Micrometer is actually present on the classpath (see {@code 
PQCProducer#createMetrics}).
+ */
+public class PQCMicrometerMetrics implements PQCMetrics {
+
+    public static final String METRIC_OPERATIONS = "camel.pqc.operations";
+    public static final String METRIC_STATEFUL_REMAINING = 
"camel.pqc.stateful.key.remaining";
+
+    private final MeterRegistry registry;
+    private final ConcurrentMap<String, Counter> counters = new 
ConcurrentHashMap<>();
+    private volatile Gauge statefulKeyGauge;
+
+    public PQCMicrometerMetrics(MeterRegistry registry) {
+        this.registry = registry;
+    }
+
+    /**
+     * Creates a Micrometer-backed metrics instance if a {@code MeterRegistry} 
is available in the Camel registry,
+     * otherwise returns {@link PQCMetrics#NOOP}.
+     */
+    public static PQCMetrics create(CamelContext camelContext) {
+        Set<MeterRegistry> registries = 
camelContext.getRegistry().findByType(MeterRegistry.class);
+        if (registries.isEmpty()) {
+            return PQCMetrics.NOOP;
+        }
+        return new PQCMicrometerMetrics(registries.iterator().next());
+    }
+
+    @Override
+    public void recordOperation(String operation, String algorithm, boolean 
success) {
+        String outcome = success ? "success" : "failure";
+        String key = operation + '|' + algorithm + '|' + outcome;
+        counters.computeIfAbsent(key, k -> Counter.builder(METRIC_OPERATIONS)
+                .tag("operation", operation)
+                .tag("algorithm", algorithm)
+                .tag("outcome", outcome)
+                .description("Number of PQC operations performed")
+                .register(registry))
+                .increment();
+    }
+
+    @Override
+    public void registerStatefulKeyGauge(String algorithm, LongSupplier 
remainingSupplier) {
+        statefulKeyGauge = Gauge.builder(METRIC_STATEFUL_REMAINING, 
remainingSupplier, LongSupplier::getAsLong)
+                .tag("algorithm", algorithm)
+                .description("Remaining signatures for the stateful PQC 
signature key")
+                .register(registry);
+    }
+
+    @Override
+    public void close() {
+        counters.values().forEach(registry::remove);
+        counters.clear();
+        if (statefulKeyGauge != null) {
+            registry.remove(statefulKeyGauge);
+            statefulKeyGauge = null;
+        }
+    }
+}
diff --git 
a/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCMetricsTest.java
 
b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCMetricsTest.java
new file mode 100644
index 000000000000..72748f057e90
--- /dev/null
+++ 
b/components/camel-pqc/src/test/java/org/apache/camel/component/pqc/PQCMetricsTest.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.pqc;
+
+import java.security.Security;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.EndpointInject;
+import org.apache.camel.Produce;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.component.pqc.metrics.PQCMicrometerMetrics;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class PQCMetricsTest extends CamelTestSupport {
+
+    @BindToRegistry
+    private final MeterRegistry meterRegistry = new SimpleMeterRegistry();
+
+    @EndpointInject("mock:sign")
+    protected MockEndpoint resultSign;
+
+    @Produce("direct:sign")
+    protected ProducerTemplate templateSign;
+
+    @BeforeAll
+    public static void startup() {
+        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
+            Security.addProvider(new BouncyCastleProvider());
+        }
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:sign")
+                        .to("pqc:sign?operation=sign&signatureAlgorithm=MLDSA")
+                        .to("mock:sign")
+                        
.to("pqc:verify?operation=verify&signatureAlgorithm=MLDSA");
+            }
+        };
+    }
+
+    @Test
+    void testProducerRecordsOperationMetrics() throws Exception {
+        resultSign.expectedMessageCount(1);
+        templateSign.sendBody("Hello PQC");
+        resultSign.assertIsSatisfied();
+
+        Counter sign = 
meterRegistry.find(PQCMicrometerMetrics.METRIC_OPERATIONS)
+                .tag("operation", "sign").tag("algorithm", 
"MLDSA").tag("outcome", "success").counter();
+        assertNotNull(sign, "sign success counter should be registered");
+        assertTrue(sign.count() >= 1.0);
+
+        Counter verify = 
meterRegistry.find(PQCMicrometerMetrics.METRIC_OPERATIONS)
+                .tag("operation", "verify").tag("outcome", 
"success").counter();
+        assertNotNull(verify, "verify success counter should be registered");
+        assertTrue(verify.count() >= 1.0);
+    }
+
+    @Test
+    void testMicrometerMetricsDirectly() {
+        SimpleMeterRegistry reg = new SimpleMeterRegistry();
+        PQCMicrometerMetrics m = new PQCMicrometerMetrics(reg);
+
+        m.recordOperation("sign", "MLDSA", true);
+        m.recordOperation("sign", "MLDSA", true);
+        m.recordOperation("sign", "MLDSA", false);
+        m.registerStatefulKeyGauge("XMSS", () -> 42L);
+
+        Counter ok = reg.find(PQCMicrometerMetrics.METRIC_OPERATIONS)
+                .tag("operation", "sign").tag("outcome", "success").counter();
+        assertNotNull(ok);
+        assertEquals(2.0, ok.count());
+
+        Counter fail = reg.find(PQCMicrometerMetrics.METRIC_OPERATIONS)
+                .tag("operation", "sign").tag("outcome", "failure").counter();
+        assertNotNull(fail);
+        assertEquals(1.0, fail.count());
+
+        Gauge gauge = 
reg.find(PQCMicrometerMetrics.METRIC_STATEFUL_REMAINING).tag("algorithm", 
"XMSS").gauge();
+        assertNotNull(gauge);
+        assertEquals(42.0, gauge.value());
+
+        // close() removes the meters it registered
+        m.close();
+        assertNull(reg.find(PQCMicrometerMetrics.METRIC_OPERATIONS).counter());
+        
assertNull(reg.find(PQCMicrometerMetrics.METRIC_STATEFUL_REMAINING).gauge());
+    }
+}

Reply via email to