You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2023/12/19 12:29:37 UTC

(camel) branch main updated: CAMEL-20249: camel-micrometer - Should remove meters when stopping to avoid having old unused meters when reloading routes or redeploying apps (#12486)

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

davsclaus 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 f9f850b6b2f CAMEL-20249: camel-micrometer - Should remove meters when stopping to avoid having old unused meters when reloading routes or redeploying apps (#12486)
f9f850b6b2f is described below

commit f9f850b6b2f2bc57110019a521eadd36edafbb92
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Tue Dec 19 13:29:30 2023 +0100

    CAMEL-20249: camel-micrometer - Should remove meters when stopping to avoid having old unused meters when reloading routes or redeploying apps (#12486)
    
    CAMEL-20249: camel-micrometer - Should remove meters when stopping to avoid having old unused meters when reloading routes or redeploying apps (#12486)
---
 .../main/camel-main-configuration-metadata.json    |  1 +
 .../prometheus/MicrometerPrometheusConfigurer.java |  6 ++
 .../prometheus/MicrometerPrometheus.java           | 64 ++++++++++++++++++++--
 .../src/main/docs/micrometer-component.adoc        |  5 +-
 .../component/micrometer/MicrometerConsole.java    |  2 +-
 .../component/micrometer/MicrometerConstants.java  |  8 +--
 .../MicrometerExchangeEventNotifier.java           | 34 +++++++++++-
 ...rometerExchangeEventNotifierNamingStrategy.java | 23 ++++++--
 .../MicrometerRouteEventNotifier.java              | 31 ++++++++++-
 ...MicrometerRouteEventNotifierNamingStrategy.java | 13 +++++
 .../routepolicy/MicrometerRoutePolicy.java         | 33 +++++++++++
 .../MetricsConfigurationPropertiesConfigurer.java  |  6 ++
 .../camel-main-configuration-metadata.json         |  1 +
 core/camel-main/src/main/docs/main.adoc            |  3 +-
 .../camel/main/MetricsConfigurationProperties.java | 21 +++++++
 .../jbang/core/commands/process/ListMetric.java    | 59 ++++++++++++++++----
 16 files changed, 277 insertions(+), 33 deletions(-)

diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
index d7d8b717315..3a1734b8152 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
@@ -173,6 +173,7 @@
     { "name": "camel.lra.localParticipantContextPath", "description": "The context-path for the local participant. Is default \/lra-participant", "sourceType": "org.apache.camel.main.LraConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "\/lra-participant" },
     { "name": "camel.lra.localParticipantUrl", "description": "The URL for the local participant", "sourceType": "org.apache.camel.main.LraConfigurationProperties", "type": "string", "javaType": "java.lang.String" },
     { "name": "camel.metrics.binders", "description": "Additional Micrometer binders to include such as jvm-memory, processor, jvm-thread, and so forth. Multiple binders can be separated by comma. The following binders currently is available from Micrometer: class-loader, commons-object-pool2, file-descriptor, hystrix-metrics-binder, jvm-compilation, jvm-gc, jvm-heap-pressure, jvm-info, jvm-memory, jvm-thread, log4j2, logback, processor, uptime", "sourceType": "org.apache.camel.main.Metr [...]
+    { "name": "camel.metrics.clearOnReload", "description": "Clear the captured metrics data when Camel is reloading routes such as when using Camel JBang.", "sourceType": "org.apache.camel.main.MetricsConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": true },
     { "name": "camel.metrics.enabled", "description": "To enable Micrometer metrics.", "sourceType": "org.apache.camel.main.MetricsConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" },
     { "name": "camel.metrics.enableExchangeEventNotifier", "description": "Set whether to enable the MicrometerExchangeEventNotifier for capturing metrics on exchange processing times.", "sourceType": "org.apache.camel.main.MetricsConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": true },
     { "name": "camel.metrics.enableMessageHistory", "description": "Set whether to enable the MicrometerMessageHistoryFactory for capturing metrics on individual route node processing times. Depending on the number of configured route nodes, there is the potential to create a large volume of metrics. Therefore, this option is disabled by default.", "sourceType": "org.apache.camel.main.MetricsConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" },
diff --git a/components/camel-micrometer-prometheus/src/generated/java/org/apache/camel/component/micrometer/prometheus/MicrometerPrometheusConfigurer.java b/components/camel-micrometer-prometheus/src/generated/java/org/apache/camel/component/micrometer/prometheus/MicrometerPrometheusConfigurer.java
index 479105554d7..3466314c9cd 100644
--- a/components/camel-micrometer-prometheus/src/generated/java/org/apache/camel/component/micrometer/prometheus/MicrometerPrometheusConfigurer.java
+++ b/components/camel-micrometer-prometheus/src/generated/java/org/apache/camel/component/micrometer/prometheus/MicrometerPrometheusConfigurer.java
@@ -25,6 +25,8 @@ public class MicrometerPrometheusConfigurer extends org.apache.camel.support.com
         case "Binders": target.setBinders(property(camelContext, java.lang.String.class, value)); return true;
         case "camelcontext":
         case "CamelContext": target.setCamelContext(property(camelContext, org.apache.camel.CamelContext.class, value)); return true;
+        case "clearonreload":
+        case "ClearOnReload": target.setClearOnReload(property(camelContext, boolean.class, value)); return true;
         case "enableexchangeeventnotifier":
         case "EnableExchangeEventNotifier": target.setEnableExchangeEventNotifier(property(camelContext, boolean.class, value)); return true;
         case "enablemessagehistory":
@@ -48,6 +50,8 @@ public class MicrometerPrometheusConfigurer extends org.apache.camel.support.com
         case "Binders": return java.lang.String.class;
         case "camelcontext":
         case "CamelContext": return org.apache.camel.CamelContext.class;
+        case "clearonreload":
+        case "ClearOnReload": return boolean.class;
         case "enableexchangeeventnotifier":
         case "EnableExchangeEventNotifier": return boolean.class;
         case "enablemessagehistory":
@@ -72,6 +76,8 @@ public class MicrometerPrometheusConfigurer extends org.apache.camel.support.com
         case "Binders": return target.getBinders();
         case "camelcontext":
         case "CamelContext": return target.getCamelContext();
+        case "clearonreload":
+        case "ClearOnReload": return target.isClearOnReload();
         case "enableexchangeeventnotifier":
         case "EnableExchangeEventNotifier": return target.isEnableExchangeEventNotifier();
         case "enablemessagehistory":
diff --git a/components/camel-micrometer-prometheus/src/main/java/org/apache/camel/component/micrometer/prometheus/MicrometerPrometheus.java b/components/camel-micrometer-prometheus/src/main/java/org/apache/camel/component/micrometer/prometheus/MicrometerPrometheus.java
index d8cab4db2e4..63289d4b668 100644
--- a/components/camel-micrometer-prometheus/src/main/java/org/apache/camel/component/micrometer/prometheus/MicrometerPrometheus.java
+++ b/components/camel-micrometer-prometheus/src/main/java/org/apache/camel/component/micrometer/prometheus/MicrometerPrometheus.java
@@ -18,11 +18,13 @@ package org.apache.camel.component.micrometer.prometheus;
 
 import java.io.Closeable;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.StringJoiner;
 
+import io.micrometer.core.instrument.Meter;
 import io.micrometer.core.instrument.MeterRegistry;
 import io.micrometer.core.instrument.binder.MeterBinder;
 import io.micrometer.prometheus.PrometheusConfig;
@@ -49,12 +51,14 @@ import org.apache.camel.component.micrometer.routepolicy.MicrometerRoutePolicyNa
 import org.apache.camel.component.platform.http.PlatformHttpComponent;
 import org.apache.camel.component.platform.http.main.MainHttpServer;
 import org.apache.camel.component.platform.http.vertx.VertxPlatformHttpRouter;
+import org.apache.camel.spi.CamelEvent;
 import org.apache.camel.spi.CamelMetricsService;
 import org.apache.camel.spi.Configurer;
 import org.apache.camel.spi.ManagementStrategy;
 import org.apache.camel.spi.Metadata;
 import org.apache.camel.spi.Registry;
 import org.apache.camel.spi.annotations.JdkService;
+import org.apache.camel.support.SimpleEventNotifierSupport;
 import org.apache.camel.support.service.ServiceSupport;
 import org.apache.camel.util.IOHelper;
 import org.apache.camel.util.ObjectHelper;
@@ -86,6 +90,8 @@ public class MicrometerPrometheus extends ServiceSupport implements CamelMetrics
     private boolean enableExchangeEventNotifier = true;
     @Metadata(defaultValue = "true")
     private boolean enableRouteEventNotifier = true;
+    @Metadata(defaultValue = "true")
+    private boolean clearOnReload = true;
     @Metadata(defaultValue = "0.0.4", enums = "0.0.4,1.0.0")
     private String textFormatVersion = "0.0.4";
     @Metadata
@@ -107,7 +113,7 @@ public class MicrometerPrometheus extends ServiceSupport implements CamelMetrics
 
     /**
      * Controls the name style to use for metrics.
-     *
+     * <p>
      * Default = uses micrometer naming convention. Legacy = uses the classic naming style (camelCase)
      */
     public void setNamingStrategy(String namingStrategy) {
@@ -132,7 +138,7 @@ public class MicrometerPrometheus extends ServiceSupport implements CamelMetrics
     /**
      * Set whether to enable the MicrometerMessageHistoryFactory for capturing metrics on individual route node
      * processing times.
-     *
+     * <p>
      * Depending on the number of configured route nodes, there is the potential to create a large volume of metrics.
      * Therefore, this option is disabled by default.
      */
@@ -163,13 +169,24 @@ public class MicrometerPrometheus extends ServiceSupport implements CamelMetrics
         this.enableRouteEventNotifier = enableRouteEventNotifier;
     }
 
+    public boolean isClearOnReload() {
+        return clearOnReload;
+    }
+
+    /**
+     * Clear the captured metrics data when Camel is reloading routes such as when using Camel JBang.
+     */
+    public void setClearOnReload(boolean clearOnReload) {
+        this.clearOnReload = clearOnReload;
+    }
+
     public String getTextFormatVersion() {
         return textFormatVersion;
     }
 
     /**
      * The text-format version to use with Prometheus scraping.
-     *
+     * <p>
      * 0.0.4 = text/plain; version=0.0.4; charset=utf-8 1.0.0 = application/openmetrics-text; version=1.0.0;
      * charset=utf-8
      */
@@ -184,7 +201,7 @@ public class MicrometerPrometheus extends ServiceSupport implements CamelMetrics
     /**
      * Additional Micrometer binders to include such as jvm-memory, processor, jvm-thread, and so forth. Multiple
      * binders can be separated by comma.
-     *
+     * <p>
      * The following binders currently is available from Micrometer: class-loader, commons-object-pool2,
      * file-descriptor, hystrix-metrics-binder, jvm-compilation, jvm-gc, jvm-heap-pressure, jvm-info, jvm-memory,
      * jvm-thread, log4j2, logback, processor, uptime
@@ -257,6 +274,45 @@ public class MicrometerPrometheus extends ServiceSupport implements CamelMetrics
             factory.setMeterRegistry(meterRegistry);
             camelContext.setMessageHistoryFactory(factory);
         }
+
+        if (clearOnReload) {
+            camelContext.getManagementStrategy().addEventNotifier(new SimpleEventNotifierSupport() {
+
+                @Override
+                public boolean isEnabled(CamelEvent event) {
+                    return event instanceof CamelEvent.RouteReloadedEvent;
+                }
+
+                @Override
+                public void notify(CamelEvent event) throws Exception {
+                    // when reloading then there may be more routes in the same batch, so we only want
+                    // to log the summary at the end
+                    if (event instanceof CamelEvent.RouteReloadedEvent) {
+                        CamelEvent.RouteReloadedEvent re = (CamelEvent.RouteReloadedEvent) event;
+                        if (re.getIndex() >= re.getTotal()) {
+                            LOG.info("Resetting Micrometer Registry after reloading routes");
+
+                            // remove all meters (not counters) that are from Camel and associated routes via routeId as tag
+                            List<Meter> toRemove = new ArrayList<>();
+                            for (Meter m : meterRegistry.getMeters()) {
+                                String n = m.getId().getName();
+                                boolean camel = n.startsWith("camel_") || n.startsWith("camel.");
+                                boolean keep = n.startsWith("camel.exchanges.") || n.startsWith("camel_exchanges_");
+                                // remove camel but keep those special camel.exchanges. counters
+                                boolean remove = camel && !keep;
+                                if (remove) {
+                                    String t = m.getId().getTag("routeId");
+                                    if (t != null) {
+                                        toRemove.add(m);
+                                    }
+                                }
+                            }
+                            toRemove.forEach(meterRegistry::remove);
+                        }
+                    }
+                }
+            });
+        }
     }
 
     @Override
diff --git a/components/camel-micrometer/src/main/docs/micrometer-component.adoc b/components/camel-micrometer/src/main/docs/micrometer-component.adoc
index 4326d9173b4..539862d0af9 100644
--- a/components/camel-micrometer/src/main/docs/micrometer-component.adoc
+++ b/components/camel-micrometer/src/main/docs/micrometer-component.adoc
@@ -83,8 +83,9 @@ Some Camel specific metrics are available out of the box.
 |=====================================================
 |Name |Type |Description
 |camel.message.history|timer |Sample of performance of each node in the route when message history is enabled
-|camel.routes.added |gauge |Number of routes added
-|camel.routes.running |gauge |Number of routes runnning
+|camel.routes.added |gauge |Number of routes in total
+|camel.routes.reloaded |gauge |Number of routes that has been reloaded
+|camel.routes.running |gauge |Number of routes currently running
 |camel.exchanges.inflight |gauge |Route inflight messages
 |camel.exchanges.total |counter |Total number of processed exchanges
 |camel.exchanges.succeeded |counter |Number of successfully completed exchange
diff --git a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/MicrometerConsole.java b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/MicrometerConsole.java
index 4c87e4ada99..6cbff3403a3 100644
--- a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/MicrometerConsole.java
+++ b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/MicrometerConsole.java
@@ -45,7 +45,7 @@ public class MicrometerConsole extends AbstractDevConsole {
         StringBuilder sb = new StringBuilder();
 
         MeterRegistry mr = lookupMeterRegistry();
-        sb.append(String.format("MeterRegistry: %s\n", mr.getClass().getName()));
+        sb.append(String.format("MeterRegistry: %s\n\n", mr.getClass().getName()));
 
         int i = 0;
         for (Meter m : mr.getMeters()) {
diff --git a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/MicrometerConstants.java b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/MicrometerConstants.java
index 67dd47b0b45..d9008beafe4 100644
--- a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/MicrometerConstants.java
+++ b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/MicrometerConstants.java
@@ -41,6 +41,7 @@ public final class MicrometerConstants {
     public static final String HEADER_METRIC_TAGS = HEADER_PREFIX + "Tags";
 
     public static final String DEFAULT_CAMEL_MESSAGE_HISTORY_METER_NAME = "camel.message.history";
+
     public static final String DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_FAILED_METER_NAME = "camel.exchanges.failed";
     public static final String DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_SUCCEEDED_METER_NAME = "camel.exchanges.succeeded";
     public static final String DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_TOTAL_METER_NAME = "camel.exchanges.total";
@@ -48,15 +49,16 @@ public final class MicrometerConstants {
             = "camel.exchanges.failures.handled";
     public static final String DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_EXTERNAL_REDELIVERIES_METER_NAME
             = "camel.exchanges.external.redeliveries";
+    public static final String DEFAULT_CAMEL_ROUTES_EXCHANGES_INFLIGHT = "camel.exchanges.inflight";
+
     public static final String DEFAULT_CAMEL_ROUTE_POLICY_METER_NAME = "camel.route.policy";
     public static final String DEFAULT_CAMEL_ROUTE_POLICY_LONGMETER_NAME = "camel.route.policy.long.task";
     public static final String DEFAULT_CAMEL_EXCHANGE_EVENT_METER_NAME = "camel.exchange.event.notifier";
     public static final String DEFAULT_CAMEL_ROUTES_ADDED = "camel.routes.added";
     public static final String DEFAULT_CAMEL_ROUTES_RUNNING = "camel.routes.running";
-    public static final String DEFAULT_CAMEL_ROUTES_EXCHANGES_INFLIGHT = "camel.exchanges.inflight";
+    public static final String DEFAULT_CAMEL_ROUTES_RELOADED = "camel.routes.reloaded";
 
     public static final String ROUTE_ID_TAG = "routeId";
-    public static final String ROUTE_DESCRIPTION_TAG = "routeDescription";
     public static final String NODE_ID_TAG = "nodeId";
     public static final String FAILED_TAG = "failed";
     public static final String CAMEL_CONTEXT_TAG = "camelContext";
@@ -67,8 +69,6 @@ public final class MicrometerConstants {
     public static final String ENDPOINT_NAME = "endpointName";
 
     public static final Predicate<Meter.Id> CAMEL_METERS = id -> id.getTag(CAMEL_CONTEXT_TAG) != null;
-    public static final Predicate<Meter.Id> TIMERS = id -> id.getType() == Meter.Type.TIMER;
-    public static final Predicate<Meter.Id> DISTRIBUTION_SUMMARIES = id -> id.getType() == Meter.Type.DISTRIBUTION_SUMMARY;
     public static final Predicate<Meter.Id> ALWAYS = id -> true;
 
     private MicrometerConstants() {
diff --git a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifier.java b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifier.java
index d4fc998b69f..61ce90e9526 100644
--- a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifier.java
+++ b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifier.java
@@ -16,10 +16,13 @@
  */
 package org.apache.camel.component.micrometer.eventnotifier;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 
 import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.Meter;
 import io.micrometer.core.instrument.Tags;
 import io.micrometer.core.instrument.Timer;
 import org.apache.camel.Exchange;
@@ -30,9 +33,12 @@ import org.apache.camel.spi.CamelEvent.ExchangeEvent;
 import org.apache.camel.spi.CamelEvent.ExchangeFailedEvent;
 import org.apache.camel.spi.CamelEvent.ExchangeSentEvent;
 import org.apache.camel.spi.InflightRepository;
+import org.apache.camel.support.SimpleEventNotifierSupport;
 
 public class MicrometerExchangeEventNotifier extends AbstractMicrometerEventNotifier<ExchangeEvent> {
     private InflightRepository inflightRepository;
+
+    private final Map<String, Meter> meterMap = new HashMap<>();
     private Predicate<Exchange> ignoreExchanges = exchange -> false;
     private MicrometerExchangeEventNotifierNamingStrategy namingStrategy
             = MicrometerExchangeEventNotifierNamingStrategy.DEFAULT;
@@ -59,8 +65,31 @@ public class MicrometerExchangeEventNotifier extends AbstractMicrometerEventNoti
 
     @Override
     protected void doStart() throws Exception {
-        inflightRepository = getCamelContext().getInflightRepository();
         super.doStart();
+
+        inflightRepository = getCamelContext().getInflightRepository();
+
+        // need to be able to remove meter if a route is removed
+        getCamelContext().getManagementStrategy().addEventNotifier(new SimpleEventNotifierSupport() {
+            @Override
+            public void notify(CamelEvent event) throws Exception {
+                if (event instanceof CamelEvent.RouteRemovedEvent rre) {
+                    String id = rre.getRoute().getRouteId();
+                    Meter meter = meterMap.remove(id);
+                    if (meter != null) {
+                        getMeterRegistry().remove(meter);
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    protected void doStop() throws Exception {
+        super.doStop();
+
+        meterMap.values().forEach(m -> getMeterRegistry().remove(m));
+        meterMap.clear();
     }
 
     @Override
@@ -82,10 +111,11 @@ public class MicrometerExchangeEventNotifier extends AbstractMicrometerEventNoti
         if (exchange.getFromRouteId() != null && exchange.getFromEndpoint() != null) {
             String name = namingStrategy.getInflightExchangesName(exchange, exchange.getFromEndpoint());
             Tags tags = namingStrategy.getInflightExchangesTags(exchangeEvent, exchange.getFromEndpoint());
-            Gauge.builder(name, () -> getInflightExchangesInRoute(exchangeEvent))
+            Meter meter = Gauge.builder(name, () -> getInflightExchangesInRoute(exchangeEvent))
                     .description("Route inflight messages")
                     .tags(tags)
                     .register(getMeterRegistry());
+            meterMap.put(exchange.getFromRouteId(), meter);
         }
     }
 
diff --git a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifierNamingStrategy.java b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifierNamingStrategy.java
index fdaa1c91053..065c7c1c38e 100644
--- a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifierNamingStrategy.java
+++ b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifierNamingStrategy.java
@@ -69,12 +69,23 @@ public interface MicrometerExchangeEventNotifierNamingStrategy {
             // use sanitized uri to not reveal sensitive information
             uri = endpoint.toString();
         }
-        return Tags.of(
-                CAMEL_CONTEXT_TAG, event.getExchange().getContext().getName(),
-                SERVICE_NAME, MicrometerEventNotifierService.class.getSimpleName(),
-                EVENT_TYPE_TAG, event.getClass().getSimpleName(),
-                ENDPOINT_NAME, uri,
-                FAILED_TAG, Boolean.toString(event.getExchange().isFailed()));
+        String routeId = event.getExchange().getFromRouteId();
+        if (routeId != null) {
+            return Tags.of(
+                    CAMEL_CONTEXT_TAG, event.getExchange().getContext().getName(),
+                    SERVICE_NAME, MicrometerEventNotifierService.class.getSimpleName(),
+                    EVENT_TYPE_TAG, event.getClass().getSimpleName(),
+                    ROUTE_ID_TAG, routeId,
+                    ENDPOINT_NAME, uri,
+                    FAILED_TAG, Boolean.toString(event.getExchange().isFailed()));
+        } else {
+            return Tags.of(
+                    CAMEL_CONTEXT_TAG, event.getExchange().getContext().getName(),
+                    SERVICE_NAME, MicrometerEventNotifierService.class.getSimpleName(),
+                    EVENT_TYPE_TAG, event.getClass().getSimpleName(),
+                    ENDPOINT_NAME, uri,
+                    FAILED_TAG, Boolean.toString(event.getExchange().isFailed()));
+        }
     }
 
     default Tags getInflightExchangesTags(ExchangeEvent event, Endpoint endpoint) {
diff --git a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerRouteEventNotifier.java b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerRouteEventNotifier.java
index c5617dd819b..6c638e031ce 100644
--- a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerRouteEventNotifier.java
+++ b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerRouteEventNotifier.java
@@ -22,6 +22,7 @@ import io.micrometer.core.instrument.Gauge;
 import org.apache.camel.spi.CamelEvent;
 import org.apache.camel.spi.CamelEvent.RouteAddedEvent;
 import org.apache.camel.spi.CamelEvent.RouteEvent;
+import org.apache.camel.spi.CamelEvent.RouteReloadedEvent;
 import org.apache.camel.spi.CamelEvent.RouteRemovedEvent;
 import org.apache.camel.spi.CamelEvent.RouteStartedEvent;
 import org.apache.camel.spi.CamelEvent.RouteStoppedEvent;
@@ -30,6 +31,10 @@ public class MicrometerRouteEventNotifier extends AbstractMicrometerEventNotifie
 
     private final AtomicLong routesAdded = new AtomicLong();
     private final AtomicLong routesRunning = new AtomicLong();
+    private final AtomicLong routesReloaded = new AtomicLong();
+    private Gauge gaugeAdded;
+    private Gauge gaugeRunning;
+    private Gauge gaugeReloaded;
     private MicrometerRouteEventNotifierNamingStrategy namingStrategy = MicrometerRouteEventNotifierNamingStrategy.DEFAULT;
 
     public MicrometerRouteEventNotifier() {
@@ -47,16 +52,36 @@ public class MicrometerRouteEventNotifier extends AbstractMicrometerEventNotifie
     @Override
     protected void doStart() throws Exception {
         super.doStart();
-        Gauge.builder(namingStrategy.getRouteAddedName(), routesAdded, value -> (double) value.get())
+
+        gaugeAdded = Gauge.builder(namingStrategy.getRouteAddedName(), routesAdded, value -> (double) value.get())
+                .baseUnit("routes")
+                .tags(namingStrategy.getTags(getCamelContext()))
+                .register(getMeterRegistry());
+        gaugeRunning = Gauge.builder(namingStrategy.getRouteRunningName(), routesRunning, value -> (double) value.get())
                 .baseUnit("routes")
                 .tags(namingStrategy.getTags(getCamelContext()))
                 .register(getMeterRegistry());
-        Gauge.builder(namingStrategy.getRouteRunningName(), routesRunning, value -> (double) value.get())
+        gaugeReloaded = Gauge.builder(namingStrategy.getRouteReloadedName(), routesReloaded, value -> (double) value.get())
                 .baseUnit("routes")
                 .tags(namingStrategy.getTags(getCamelContext()))
                 .register(getMeterRegistry());
     }
 
+    @Override
+    protected void doStop() throws Exception {
+        super.doStop();
+
+        if (gaugeAdded != null) {
+            getMeterRegistry().remove(gaugeAdded);
+        }
+        if (gaugeRunning != null) {
+            getMeterRegistry().remove(gaugeRunning);
+        }
+        if (gaugeReloaded != null) {
+            getMeterRegistry().remove(gaugeReloaded);
+        }
+    }
+
     @Override
     public void notify(CamelEvent eventObject) {
         if (eventObject instanceof RouteAddedEvent) {
@@ -67,6 +92,8 @@ public class MicrometerRouteEventNotifier extends AbstractMicrometerEventNotifie
             routesRunning.incrementAndGet();
         } else if (eventObject instanceof RouteStoppedEvent) {
             routesRunning.decrementAndGet();
+        } else if (eventObject instanceof RouteReloadedEvent) {
+            routesReloaded.incrementAndGet();
         }
     }
 }
diff --git a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerRouteEventNotifierNamingStrategy.java b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerRouteEventNotifierNamingStrategy.java
index 094567ee790..693275c80ad 100644
--- a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerRouteEventNotifierNamingStrategy.java
+++ b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerRouteEventNotifierNamingStrategy.java
@@ -26,6 +26,7 @@ import org.apache.camel.spi.CamelEvent.RouteEvent;
 
 import static org.apache.camel.component.micrometer.MicrometerConstants.CAMEL_CONTEXT_TAG;
 import static org.apache.camel.component.micrometer.MicrometerConstants.DEFAULT_CAMEL_ROUTES_ADDED;
+import static org.apache.camel.component.micrometer.MicrometerConstants.DEFAULT_CAMEL_ROUTES_RELOADED;
 import static org.apache.camel.component.micrometer.MicrometerConstants.DEFAULT_CAMEL_ROUTES_RUNNING;
 import static org.apache.camel.component.micrometer.MicrometerConstants.EVENT_TYPE_TAG;
 import static org.apache.camel.component.micrometer.MicrometerConstants.SERVICE_NAME;
@@ -48,6 +49,11 @@ public interface MicrometerRouteEventNotifierNamingStrategy {
         public String getRouteRunningName() {
             return DEFAULT_CAMEL_ROUTES_RUNNING;
         }
+
+        @Override
+        public String getRouteReloadedName() {
+            return DEFAULT_CAMEL_ROUTES_RELOADED;
+        }
     };
 
     /**
@@ -64,6 +70,11 @@ public interface MicrometerRouteEventNotifierNamingStrategy {
             return formatName(DEFAULT_CAMEL_ROUTES_RUNNING);
         }
 
+        @Override
+        public String getRouteReloadedName() {
+            return formatName(DEFAULT_CAMEL_ROUTES_RELOADED);
+        }
+
         @Override
         public String formatName(String name) {
             return MicrometerUtils.legacyName(name);
@@ -78,6 +89,8 @@ public interface MicrometerRouteEventNotifierNamingStrategy {
 
     String getRouteRunningName();
 
+    String getRouteReloadedName();
+
     default Tags getTags(CamelContext camelContext) {
         return Tags.of(
                 SERVICE_NAME, MicrometerEventNotifierService.class.getSimpleName(),
diff --git a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/routepolicy/MicrometerRoutePolicy.java b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/routepolicy/MicrometerRoutePolicy.java
index a53dc85753f..264cb58d6cf 100644
--- a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/routepolicy/MicrometerRoutePolicy.java
+++ b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/routepolicy/MicrometerRoutePolicy.java
@@ -220,6 +220,7 @@ public class MicrometerRoutePolicy extends RoutePolicySupport implements NonMana
     @Override
     public void onInit(Route route) {
         super.onInit(route);
+
         if (getMeterRegistry() == null) {
             setMeterRegistry(MicrometerUtils.getOrCreateMeterRegistry(
                     route.getCamelContext().getRegistry(), METRICS_REGISTRY_NAME));
@@ -239,7 +240,10 @@ public class MicrometerRoutePolicy extends RoutePolicySupport implements NonMana
         } catch (Exception e) {
             throw RuntimeCamelException.wrapRuntimeCamelException(e);
         }
+    }
 
+    @Override
+    public void onStart(Route route) {
         // create statistics holder
         // for now we record only all the timings of a complete exchange (responses)
         // we have in-flight / total statistics already from camel-core
@@ -247,6 +251,35 @@ public class MicrometerRoutePolicy extends RoutePolicySupport implements NonMana
                 it -> new MetricsStatistics(getMeterRegistry(), it, getNamingStrategy(), configuration));
     }
 
+    @Override
+    public void onRemove(Route route) {
+        // route is removed, so remove metrics from micrometer
+        MetricsStatistics stats = statisticsMap.remove(route);
+        if (stats != null) {
+            if (stats.exchangesSucceeded != null) {
+                meterRegistry.remove(stats.exchangesSucceeded);
+            }
+            if (stats.exchangesFailed != null) {
+                meterRegistry.remove(stats.exchangesFailed);
+            }
+            if (stats.exchangesTotal != null) {
+                meterRegistry.remove(stats.exchangesTotal);
+            }
+            if (stats.externalRedeliveries != null) {
+                meterRegistry.remove(stats.externalRedeliveries);
+            }
+            if (stats.failuresHandled != null) {
+                meterRegistry.remove(stats.failuresHandled);
+            }
+            if (stats.timer != null) {
+                meterRegistry.remove(stats.timer);
+            }
+            if (stats.longTaskTimer != null) {
+                meterRegistry.remove(stats.longTaskTimer);
+            }
+        }
+    }
+
     @Override
     public void onExchangeBegin(Route route, Exchange exchange) {
         Optional.ofNullable(statisticsMap.get(route))
diff --git a/core/camel-main/src/generated/java/org/apache/camel/main/MetricsConfigurationPropertiesConfigurer.java b/core/camel-main/src/generated/java/org/apache/camel/main/MetricsConfigurationPropertiesConfigurer.java
index 9d53c533439..59fd581efe0 100644
--- a/core/camel-main/src/generated/java/org/apache/camel/main/MetricsConfigurationPropertiesConfigurer.java
+++ b/core/camel-main/src/generated/java/org/apache/camel/main/MetricsConfigurationPropertiesConfigurer.java
@@ -23,6 +23,8 @@ public class MetricsConfigurationPropertiesConfigurer extends org.apache.camel.s
         switch (ignoreCase ? name.toLowerCase() : name) {
         case "binders":
         case "Binders": target.setBinders(property(camelContext, java.lang.String.class, value)); return true;
+        case "clearonreload":
+        case "ClearOnReload": target.setClearOnReload(property(camelContext, boolean.class, value)); return true;
         case "enableexchangeeventnotifier":
         case "EnableExchangeEventNotifier": target.setEnableExchangeEventNotifier(property(camelContext, boolean.class, value)); return true;
         case "enablemessagehistory":
@@ -46,6 +48,8 @@ public class MetricsConfigurationPropertiesConfigurer extends org.apache.camel.s
         switch (ignoreCase ? name.toLowerCase() : name) {
         case "binders":
         case "Binders": return java.lang.String.class;
+        case "clearonreload":
+        case "ClearOnReload": return boolean.class;
         case "enableexchangeeventnotifier":
         case "EnableExchangeEventNotifier": return boolean.class;
         case "enablemessagehistory":
@@ -70,6 +74,8 @@ public class MetricsConfigurationPropertiesConfigurer extends org.apache.camel.s
         switch (ignoreCase ? name.toLowerCase() : name) {
         case "binders":
         case "Binders": return target.getBinders();
+        case "clearonreload":
+        case "ClearOnReload": return target.isClearOnReload();
         case "enableexchangeeventnotifier":
         case "EnableExchangeEventNotifier": return target.isEnableExchangeEventNotifier();
         case "enablemessagehistory":
diff --git a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
index d7d8b717315..3a1734b8152 100644
--- a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
+++ b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
@@ -173,6 +173,7 @@
     { "name": "camel.lra.localParticipantContextPath", "description": "The context-path for the local participant. Is default \/lra-participant", "sourceType": "org.apache.camel.main.LraConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "\/lra-participant" },
     { "name": "camel.lra.localParticipantUrl", "description": "The URL for the local participant", "sourceType": "org.apache.camel.main.LraConfigurationProperties", "type": "string", "javaType": "java.lang.String" },
     { "name": "camel.metrics.binders", "description": "Additional Micrometer binders to include such as jvm-memory, processor, jvm-thread, and so forth. Multiple binders can be separated by comma. The following binders currently is available from Micrometer: class-loader, commons-object-pool2, file-descriptor, hystrix-metrics-binder, jvm-compilation, jvm-gc, jvm-heap-pressure, jvm-info, jvm-memory, jvm-thread, log4j2, logback, processor, uptime", "sourceType": "org.apache.camel.main.Metr [...]
+    { "name": "camel.metrics.clearOnReload", "description": "Clear the captured metrics data when Camel is reloading routes such as when using Camel JBang.", "sourceType": "org.apache.camel.main.MetricsConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": true },
     { "name": "camel.metrics.enabled", "description": "To enable Micrometer metrics.", "sourceType": "org.apache.camel.main.MetricsConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" },
     { "name": "camel.metrics.enableExchangeEventNotifier", "description": "Set whether to enable the MicrometerExchangeEventNotifier for capturing metrics on exchange processing times.", "sourceType": "org.apache.camel.main.MetricsConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": true },
     { "name": "camel.metrics.enableMessageHistory", "description": "Set whether to enable the MicrometerMessageHistoryFactory for capturing metrics on individual route node processing times. Depending on the number of configured route nodes, there is the potential to create a large volume of metrics. Therefore, this option is disabled by default.", "sourceType": "org.apache.camel.main.MetricsConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" },
diff --git a/core/camel-main/src/main/docs/main.adoc b/core/camel-main/src/main/docs/main.adoc
index 7541bf137cf..87322318756 100644
--- a/core/camel-main/src/main/docs/main.adoc
+++ b/core/camel-main/src/main/docs/main.adoc
@@ -360,12 +360,13 @@ The camel.opentelemetry supports 4 options, which are listed below.
 
 
 === Camel Micrometer Metrics configurations
-The camel.metrics supports 8 options, which are listed below.
+The camel.metrics supports 9 options, which are listed below.
 
 [width="100%",cols="2,5,^1,2",options="header"]
 |===
 | Name | Description | Default | Type
 | *camel.metrics.binders* | Additional Micrometer binders to include such as jvm-memory, processor, jvm-thread, and so forth. Multiple binders can be separated by comma. The following binders currently is available from Micrometer: class-loader, commons-object-pool2, file-descriptor, hystrix-metrics-binder, jvm-compilation, jvm-gc, jvm-heap-pressure, jvm-info, jvm-memory, jvm-thread, log4j2, logback, processor, uptime |  | String
+| *camel.metrics.clearOnReload* | Clear the captured metrics data when Camel is reloading routes such as when using Camel JBang. | true | boolean
 | *camel.metrics.enabled* | To enable Micrometer metrics. | false | boolean
 | *camel.metrics.enableExchange{zwsp}EventNotifier* | Set whether to enable the MicrometerExchangeEventNotifier for capturing metrics on exchange processing times. | true | boolean
 | *camel.metrics.enableMessage{zwsp}History* | Set whether to enable the MicrometerMessageHistoryFactory for capturing metrics on individual route node processing times. Depending on the number of configured route nodes, there is the potential to create a large volume of metrics. Therefore, this option is disabled by default. | false | boolean
diff --git a/core/camel-main/src/main/java/org/apache/camel/main/MetricsConfigurationProperties.java b/core/camel-main/src/main/java/org/apache/camel/main/MetricsConfigurationProperties.java
index f5c7772149c..108c9dfc2a8 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/MetricsConfigurationProperties.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/MetricsConfigurationProperties.java
@@ -38,6 +38,8 @@ public class MetricsConfigurationProperties implements BootstrapCloseable {
     private boolean enableExchangeEventNotifier = true;
     @Metadata(defaultValue = "true")
     private boolean enableRouteEventNotifier = true;
+    @Metadata(defaultValue = "true")
+    private boolean clearOnReload = true;
     @Metadata(defaultValue = "0.0.4", enums = "0.0.4,1.0.0")
     private String textFormatVersion = "0.0.4";
     @Metadata
@@ -124,6 +126,17 @@ public class MetricsConfigurationProperties implements BootstrapCloseable {
         this.enableRouteEventNotifier = enableRouteEventNotifier;
     }
 
+    public boolean isClearOnReload() {
+        return clearOnReload;
+    }
+
+    /**
+     * Clear the captured metrics data when Camel is reloading routes such as when using Camel JBang.
+     */
+    public void setClearOnReload(boolean clearOnReload) {
+        this.clearOnReload = clearOnReload;
+    }
+
     public String getTextFormatVersion() {
         return textFormatVersion;
     }
@@ -214,6 +227,14 @@ public class MetricsConfigurationProperties implements BootstrapCloseable {
         return this;
     }
 
+    /**
+     * Clear the captured metrics data when Camel is reloading routes such as when using Camel JBang.
+     */
+    public MetricsConfigurationProperties withClearOnReload(boolean clearOnReload) {
+        this.clearOnReload = clearOnReload;
+        return this;
+    }
+
     /**
      * The text-format version to use with Prometheus scraping.
      *
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetric.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetric.java
index 6427b955a08..4b5beba36c5 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetric.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetric.java
@@ -19,6 +19,7 @@ package org.apache.camel.dsl.jbang.core.commands.process;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.StringJoiner;
 
 import com.github.freva.asciitable.AsciiTable;
 import com.github.freva.asciitable.Column;
@@ -48,6 +49,10 @@ public class ListMetric extends ProcessWatchCommand {
                         description = "Filter metric by type or name")
     String filter;
 
+    @CommandLine.Option(names = { "--tags" },
+                        description = "Show metric tags", defaultValue = "false")
+    boolean tags;
+
     @CommandLine.Option(names = { "--custom" },
                         description = "Only show custom metrics", defaultValue = "false")
     boolean custom;
@@ -95,7 +100,8 @@ public class ListMetric extends ProcessWatchCommand {
                                     row.type = "counter";
                                     row.metricName = jo.getString("name");
                                     row.metricDescription = jo.getString("description");
-                                    row.metricRouteId = extractRouteId(jo);
+                                    row.metricId = extractId(jo);
+                                    row.tags = extractTags(jo);
                                     row.count = jo.getDouble("count");
 
                                     if (custom && row.metricName.startsWith("Camel")) {
@@ -117,7 +123,8 @@ public class ListMetric extends ProcessWatchCommand {
                                     row.type = "timer";
                                     row.metricName = jo.getString("name");
                                     row.metricDescription = jo.getString("description");
-                                    row.metricRouteId = extractRouteId(jo);
+                                    row.metricId = extractId(jo);
+                                    row.tags = extractTags(jo);
                                     row.count = jo.getDouble("count");
                                     row.mean = jo.getDouble("mean");
                                     row.max = jo.getDouble("max");
@@ -142,7 +149,8 @@ public class ListMetric extends ProcessWatchCommand {
                                     row.type = "gauge";
                                     row.metricName = jo.getString("name");
                                     row.metricDescription = jo.getString("description");
-                                    row.metricRouteId = extractRouteId(jo);
+                                    row.metricId = extractId(jo);
+                                    row.tags = extractTags(jo);
                                     row.count = jo.getDouble("value");
 
                                     if (custom && row.metricName.startsWith("Camel")) {
@@ -164,7 +172,8 @@ public class ListMetric extends ProcessWatchCommand {
                                     row.type = "distribution";
                                     row.metricName = jo.getString("name");
                                     row.metricDescription = jo.getString("description");
-                                    row.metricRouteId = extractRouteId(jo);
+                                    row.metricId = extractId(jo);
+                                    row.tags = extractTags(jo);
                                     row.count = jo.getDouble("value");
                                     row.mean = jo.getDouble("mean");
                                     row.max = jo.getDouble("max");
@@ -196,8 +205,8 @@ public class ListMetric extends ProcessWatchCommand {
                     new Column().header("TYPE").dataAlign(HorizontalAlign.LEFT).with(r -> r.type),
                     new Column().header("METRIC").dataAlign(HorizontalAlign.LEFT).maxWidth(40, OverflowBehaviour.ELLIPSIS_RIGHT)
                             .with(r -> r.metricName),
-                    new Column().header("ROUTE").dataAlign(HorizontalAlign.LEFT).maxWidth(25, OverflowBehaviour.ELLIPSIS_RIGHT)
-                            .with(r -> r.metricRouteId),
+                    new Column().header("ID").dataAlign(HorizontalAlign.LEFT).maxWidth(40, OverflowBehaviour.ELLIPSIS_RIGHT)
+                            .with(r -> r.metricId),
                     new Column().header("VALUE").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
                             .with(r -> getNumber(r.count)),
                     new Column().header("MEAN").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
@@ -205,7 +214,10 @@ public class ListMetric extends ProcessWatchCommand {
                     new Column().header("MAX").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
                             .with(r -> getNumber(r.max)),
                     new Column().header("TOTAL").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
-                            .with(r -> getNumber(r.total)))));
+                            .with(r -> getNumber(r.total)),
+                    new Column().header("TAGS").visible(tags).dataAlign(HorizontalAlign.LEFT)
+                            .maxWidth(60, OverflowBehaviour.NEWLINE)
+                            .with(r -> r.tags))));
         }
 
         return 0;
@@ -237,7 +249,7 @@ public class ListMetric extends ProcessWatchCommand {
             // sort by metric/route
             answer = o1.metricName.compareToIgnoreCase(o2.metricName) * negate;
             if (answer == 0) {
-                answer = o1.metricRouteId.compareToIgnoreCase(o2.metricRouteId) * negate;
+                answer = o1.metricId.compareToIgnoreCase(o2.metricId) * negate;
             }
         }
         return answer;
@@ -253,19 +265,43 @@ public class ListMetric extends ProcessWatchCommand {
         return s;
     }
 
-    private String extractRouteId(JsonObject jo) {
+    private String extractId(JsonObject jo) {
         List<JsonObject> tags = jo.getCollection("tags");
+        String routeId = null;
+        String nodeId = null;
         if (tags != null) {
             for (JsonObject t : tags) {
                 String k = t.getString("key");
                 if ("routeId".equals(k)) {
-                    return t.getString("value");
+                    routeId = t.getString("value");
+                } else if ("nodeId".equals(k)) {
+                    nodeId = t.getString("value");
                 }
             }
         }
+        if (routeId != null && nodeId != null) {
+            return routeId + "/" + nodeId;
+        } else if (routeId != null) {
+            return routeId;
+        } else if (nodeId != null) {
+            return nodeId;
+        }
         return "";
     }
 
+    private String extractTags(JsonObject jo) {
+        StringJoiner sj = new StringJoiner(" ");
+        List<JsonObject> tags = jo.getCollection("tags");
+        if (tags != null) {
+            for (JsonObject t : tags) {
+                String k = t.getString("key");
+                String v = t.getString("value");
+                sj.add(k + "=" + v);
+            }
+        }
+        return sj.toString();
+    }
+
     private static class Row implements Cloneable {
         String pid;
         String name;
@@ -274,7 +310,8 @@ public class ListMetric extends ProcessWatchCommand {
         String type;
         String metricName;
         String metricDescription;
-        String metricRouteId;
+        String metricId;
+        String tags;
         double count;
         double mean;
         double max;