diff --git a/dev/io.openliberty.io.smallrye.metrics/.classpath b/dev/io.openliberty.io.smallrye.metrics/.classpath
index 63890cf77e2..46c7e9f763a 100644
--- a/dev/io.openliberty.io.smallrye.metrics/.classpath
+++ b/dev/io.openliberty.io.smallrye.metrics/.classpath
@@ -1,6 +1,11 @@
-
+
+
+
+
+
+
diff --git a/dev/io.openliberty.io.smallrye.metrics/bnd.bnd b/dev/io.openliberty.io.smallrye.metrics/bnd.bnd
index 17b8a88e4ac..41c5e12d919 100644
--- a/dev/io.openliberty.io.smallrye.metrics/bnd.bnd
+++ b/dev/io.openliberty.io.smallrye.metrics/bnd.bnd
@@ -1,5 +1,5 @@
#*******************************************************************************
-# Copyright (c) 2022, 2023 IBM Corporation and others.
+# Copyright (c) 2022, 2024 IBM Corporation and others.
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License 2.0
# which accompanies this distribution, and is available at
@@ -33,6 +33,9 @@ WS-TraceGroup: METRICS
publish.wlp.jar.suffix: lib
-buildpath: \
- io.smallrye:smallrye-metrics;version=5.1.0
+ io.smallrye:smallrye-metrics;version=5.1.0,\
+ io.openliberty.org.eclipse.microprofile.metrics.5.0;version=latest,\
+ io.openliberty.io.micrometer;version=latest,\
+ io.openliberty.org.eclipse.microprofile.config.3.0;version=latest
instrument.disabled: true
\ No newline at end of file
diff --git a/dev/io.openliberty.io.smallrye.metrics/src/io/smallrye/metrics/legacyapi/LegacyMetricRegistryAdapter.java b/dev/io.openliberty.io.smallrye.metrics/src/io/smallrye/metrics/legacyapi/LegacyMetricRegistryAdapter.java
new file mode 100644
index 00000000000..bee41a2a17e
--- /dev/null
+++ b/dev/io.openliberty.io.smallrye.metrics/src/io/smallrye/metrics/legacyapi/LegacyMetricRegistryAdapter.java
@@ -0,0 +1,1105 @@
+/*
+ * Copyright 2020, 2024 Red Hat, Inc, and individual contributors
+ *
+ * Licensed 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.
+ */
+/*
+ * Liberty changes are enclosed by LIBERTY CHANGE START and LIBERTY CHANGE END
+ */
+package io.smallrye.metrics.legacyapi;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.function.ToDoubleFunction;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.eclipse.microprofile.metrics.Counter;
+import org.eclipse.microprofile.metrics.Gauge;
+import org.eclipse.microprofile.metrics.Histogram;
+import org.eclipse.microprofile.metrics.Metadata;
+import org.eclipse.microprofile.metrics.Metric;
+import org.eclipse.microprofile.metrics.MetricFilter;
+import org.eclipse.microprofile.metrics.MetricID;
+import org.eclipse.microprofile.metrics.MetricRegistry;
+import org.eclipse.microprofile.metrics.Tag;
+import org.eclipse.microprofile.metrics.Timer;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Metrics;
+import io.micrometer.core.instrument.Tags;
+import io.smallrye.metrics.setup.ApplicationNameResolver;
+import io.smallrye.metrics.setup.config.MetricsConfigurationManager;
+
+public class LegacyMetricRegistryAdapter implements MetricRegistry {
+
+ private static final String CLASS_NAME = LegacyMetricRegistryAdapter.class.getName();
+ private static final Logger LOGGER = Logger.getLogger(CLASS_NAME);
+
+ private final String scope;
+ private final MeterRegistry registry;
+
+ public static final String MP_APPLICATION_NAME_TAG = "mp_app";
+
+ public static final String MP_SCOPE_TAG = "mp_scope";
+
+ protected static final String MP_APPLICATION_NAME_VAR = "mp.metrics.appName";
+
+ protected static final String MP_DEFAULT_APPLICATION_NAME_VAR = "mp.metrics.defaultAppName";
+ protected volatile static io.micrometer.core.instrument.Tag DEFAULT_APP_NAME_TAG = null;
+
+ /*
+ * Set by user on the server-level with MP Config property mp.metrics.defaultAppName
+ */
+ private final String defaultAppNameValue;
+
+ private final Map constructedMeters = new ConcurrentHashMap<>();
+ private final Map metadataMap = new ConcurrentHashMap<>();
+
+ protected final ConcurrentHashMap applicationMPConfigAppNameTagCache;
+
+ // LIBERTY CHANGE START
+ /*
+ * Liberty change. Previously, the second param type of ConcurrentLinkedQueue lead to memory leak.
+ */
+ protected final ConcurrentHashMap> applicationMap;
+ // LIBERTY CHANGE END
+
+ protected final ApplicationNameResolver appNameResolver;
+
+ protected final boolean isAppnameResolverPresent;
+
+ private MemberToMetricMappings memberToMetricMappings;
+
+ protected static io.micrometer.core.instrument.Tag[] SERVER_LEVEL_MPCONFIG_APPLICATION_NAME_TAG = null;
+
+ protected static final String GLOBAL_TAG_MALFORMED_EXCEPTION = "Malformed list of Global Tags. Tag names "
+ + "must match the following regex [a-zA-Z_][a-zA-Z0-9_]*." + " Global Tag values must not be empty."
+ + " Global Tag values MUST escape equal signs `=` and commas `,`" + " with a backslash `\\` ";
+
+ protected static final String GLOBAL_TAGS_VARIABLE = "mp.metrics.tags";
+
+ /**
+ * This static Tag[] represents the server level global tags retrieved from MP Config for
+ * mp.metrics.tags. This value will be 'null' when not initialized. If during initialization and no
+ * global tag has been resolved this will be to an array of size 0. Using an array of size 0 is to
+ * represent that an attempt on start up was made to resolve the value, but none was found. This
+ * prevents later instantiations of MetricRegistry to avoid attempting to resolve the MP Config
+ * value for the slight performance boon.
+ *
+ * This server level value will not change at all throughout the life time of the server as it is
+ * defined by env vars or sys props.
+ */
+ protected static io.micrometer.core.instrument.Tag[] SERVER_LEVEL_MPCONFIG_GLOBAL_TAGS = null;
+
+ public MeterRegistry getPrometheusMeterRegistry() {
+ return registry;
+ }
+
+ /**
+ * Associates a metric's MetricID to a specific application if an application name can be resolved.
+ *
+ * @param metricDescriptor MetricDescriptor of metric
+ */
+ public void addNameToApplicationMap(MetricDescriptor metricDescriptor) {
+ if (isAppnameResolverPresent)
+ addNameToApplicationMap(metricDescriptor.toMetricID(), appNameResolver.getApplicationName());
+ }
+
+ /**
+ * Associates a metric's MetricID to a specific application if an application name can be resolved.
+ *
+ * @param MetricID MetricID of metric
+ */
+ public void addNameToApplicationMap(MetricID MetricID) {
+ if (isAppnameResolverPresent)
+ addNameToApplicationMap(MetricID, appNameResolver.getApplicationName());
+ }
+
+ /**
+ * Adds the MetricID to an application map given the application name.
+ * This map is not a complete list of metrics owned by an application,
+ * produced metrics are managed in the MetricsExtension
+ *
+ * @param metricID metric ID of metric that was added
+ * @param appName applicationName
+ */
+ public void addNameToApplicationMap(MetricID metricID, String appName) {
+ final String METHOD_NAME = "addNameToApplicationMap";
+
+ /*
+ * - Base metrics (or vendor metrics)
+ * - Vendor does not support multi-application deployment and/or
+ * no AppnameResolver was provided.
+ */
+ if (appName == null)
+ return;
+
+ // LIBERTY CHANGE START
+ /*
+ * Liberty change. Previously, the second param type of ConcurrentLinkedQueue lead to memory leak.
+ */
+
+ Set set = applicationMap.get(appName);
+ if (set == null) {
+ Set newSet = Collections.newSetFromMap(new ConcurrentHashMap());
+ set = applicationMap.putIfAbsent(appName, newSet);
+ if (set == null)
+ set = newSet;
+ }
+ set.add(metricID);
+ // LIBERTY CHANGE END
+ if (LOGGER.isLoggable(Level.FINER)) {
+ LOGGER.logp(Level.FINER, CLASS_NAME, METHOD_NAME,
+ String.format("Mapped MetricID [id= %s] to application \"%s\"", metricID, appName));
+ }
+ }
+
+ public void unRegisterApplicationMetrics() {
+ unRegisterApplicationMetrics(appNameResolver.getApplicationName());
+ }
+
+ public void unRegisterApplicationMetrics(String appName) {
+
+ /*
+ * - Base metrics (or vendor metrics)
+ * - Vendor does not support multi-application deployment and/or
+ * no AppnameResolver was provided.
+ */
+ if (appName == null) {
+ return;
+ }
+ // LIBERTY CHANGE START
+ /*
+ * Liberty change. Previously, the second Parameter type of ConcurrentLinkedQueue lead to memory leak.
+ */
+ Set set = applicationMap.remove(appName);
+ // LIBERTY CHANGE END
+ if (set != null) {
+ for (MetricID metricID : set) {
+ remove(metricID);
+ }
+ }
+
+ MetricsConfigurationManager.getInstance().removeConfiguration(appName);
+
+ }
+
+ public LegacyMetricRegistryAdapter(String scope, MeterRegistry registry, ApplicationNameResolver appNameResolver) {
+ /*
+ * Note: if ApplicationNameResolver is passed through as Java Reflection Proxy object,
+ * can only be checked if its is "null".
+ * Trying any other operations would lead to an Exception (i.e. equals())
+ */
+ if (appNameResolver == null) {
+ this.appNameResolver = ApplicationNameResolver.DEFAULT;
+ isAppnameResolverPresent = false;
+ } else {
+ this.appNameResolver = appNameResolver;
+ isAppnameResolverPresent = true;
+ }
+
+ this.scope = scope;
+ this.registry = registry;
+
+ applicationMPConfigAppNameTagCache = new ConcurrentHashMap();
+
+ // LIBERTY CHANGE START
+ /*
+ * Liberty change. Previously, the second param type of ConcurrentLinkedQueue lead to memory leak.
+ */
+ applicationMap = new ConcurrentHashMap>();
+ // LIBERTY CHANGE END
+
+ defaultAppNameValue = resolveMPConfigDefaultAppNameTag();
+
+ resolveMPConfigGlobalTagsByServer();
+
+ if (scope != BASE_SCOPE && scope != VENDOR_SCOPE) {
+ memberToMetricMappings = new MemberToMetricMappings();
+ }
+ }
+
+ private synchronized io.micrometer.core.instrument.Tag[] resolveMPConfigGlobalTagsByServer() {
+
+ final String METHOD_NAME = "resolveMPConfigGlobalTagsByServer";
+ if (SERVER_LEVEL_MPCONFIG_GLOBAL_TAGS == null) {
+
+ // Using MP Config to retreive the mp.metrics.tags Config value
+ Optional globalTags = ConfigProvider.getConfig().getOptionalValue(GLOBAL_TAGS_VARIABLE,
+ String.class);
+
+ if (globalTags.isPresent()) {
+ if (LOGGER.isLoggable(Level.FINE)) {
+ LOGGER.logp(Level.FINE, CLASS_NAME, METHOD_NAME, String.format(
+ "MicroProfile Config value for \"%s\" resolved to be: %s", GLOBAL_TAGS_VARIABLE,
+ globalTags.get()));
+ }
+ }
+
+ // evaluate if there exists tag values or set tag[0] to be null for no value;
+ SERVER_LEVEL_MPCONFIG_GLOBAL_TAGS = (globalTags.isPresent()) ? parseGlobalTags(globalTags.get()) : new io.micrometer.core.instrument.Tag[0];
+ }
+ return (SERVER_LEVEL_MPCONFIG_GLOBAL_TAGS.length == 0) ? null : SERVER_LEVEL_MPCONFIG_GLOBAL_TAGS;
+ }
+
+ /**
+ * This will return server level global tag i.e defined in env var or sys props
+ *
+ * Will return null if no MP Config value is set for the mp.metrics.tags on the server level
+ *
+ * @return Tag[] The server wide global tag; can return null
+ */
+ private static io.micrometer.core.instrument.Tag[] parseGlobalTags(String globalTags) {
+ if (globalTags == null || globalTags.length() == 0) {
+ return null;
+ }
+ String[] kvPairs = globalTags.split("(? tagMap = new HashMap();
+ tagMap.put(mpConfigAppTag.getKey(), mpConfigAppTag.getValue());
+
+ /*
+ * Application Metric tags are put into the map second
+ * this will over write any conflicting tags. This is similar
+ * to the old behaviour when MetricID auto-resolved MP Config tags
+ * it would resolve MP COnfig tags first then add application tags
+ */
+ for (io.micrometer.core.instrument.Tag tag : tags) {
+ tagMap.put(tag.getKey(), tag.getValue());
+ }
+
+ Tags result = Tags.empty();
+ for (Entry entry : tagMap.entrySet()) {
+ result = result.and(entry.getKey(), entry.getValue());
+ }
+
+ tags = result;
+
+ }
+ return tags;
+
+ }
+
+ private String resolveMPConfigDefaultAppNameTag() {
+
+ Optional configVal = ConfigProvider.getConfig().getOptionalValue(MP_DEFAULT_APPLICATION_NAME_VAR,
+ String.class);
+
+ return (configVal.isPresent()) ? configVal.get().trim() : null;
+ }
+
+ /**
+ * This method will retrieve cached tag values for the mp.metrics.appName or resolve it and cache it
+ *
+ * @return The application level MP Config mp.metrics.appName tag of the application; Or if it exists the server level
+ * value; Or null
+ */
+ private synchronized io.micrometer.core.instrument.Tag resolveMPConfigAppNameTag() {
+
+ String appName = appNameResolver.getApplicationName();
+
+ /*
+ * If appName is null then we aren't running in an application context.
+ * This is possible when resolving metrics for BASE or VENDOR.
+ *
+ * Since we're using a ConcurrentHashMap, can't store a null key and don't want
+ * to risk making up a key a user might use as their appName. So we'll call two methods
+ * that are similar. resolveAppTagByServer() will, however, store to a static array.
+ *
+ */
+ io.micrometer.core.instrument.Tag tag = (appName == null) ? resolveMPConfigAppNameTagByServer() : resolveMPConfigAppNameTagByApplication(appName);
+
+ return (tag != null) ? tag : (defaultAppNameValue != null) ? io.micrometer.core.instrument.Tag.of(MP_APPLICATION_NAME_TAG, defaultAppNameValue) : null;
+ }
+
+ /**
+ * This will return server level application tag
+ * i.e defined in env var or sys props
+ *
+ * Will return null if no MP Config value is set
+ * for the mp.metrics.appName on the server level
+ *
+ * @return Tag The server wide application tag; can return null
+ */
+ private synchronized io.micrometer.core.instrument.Tag resolveMPConfigAppNameTagByServer() {
+ if (SERVER_LEVEL_MPCONFIG_APPLICATION_NAME_TAG == null) {
+ SERVER_LEVEL_MPCONFIG_APPLICATION_NAME_TAG = new io.micrometer.core.instrument.Tag[1];
+
+ //Using MP Config to retrieve the mp.metrics.appName Config value
+ Optional applicationName = ConfigProvider.getConfig().getOptionalValue(MP_APPLICATION_NAME_VAR,
+ String.class);
+
+ //Evaluate if there exists a tag value or set tag[0] to be null for no value;
+ SERVER_LEVEL_MPCONFIG_APPLICATION_NAME_TAG[0] = (applicationName.isPresent()) ? io.micrometer.core.instrument.Tag.of(MP_APPLICATION_NAME_TAG,
+ applicationName.get()) : null;
+ }
+ return SERVER_LEVEL_MPCONFIG_APPLICATION_NAME_TAG[0];
+ }
+
+ /**
+ * This will return the MP Config value for
+ * mp.metrics.appName for the application
+ * that the current TCCL is running for
+ *
+ * @param appName the application name to look up from cache
+ * @return Tag The mp.metrics.appName MP Config value associated to the appName; can return null if non exists
+ */
+ private synchronized io.micrometer.core.instrument.Tag resolveMPConfigAppNameTagByApplication(String appName) {
+ //Return cached value
+ if (!applicationMPConfigAppNameTagCache.containsKey(appName)) {
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+
+ //Using MP Config to retreive the mp.metrics.appName Config value
+ Optional applicationName = ConfigProvider.getConfig(classLoader).getOptionalValue(MP_APPLICATION_NAME_VAR,
+ String.class);
+
+ /*
+ * Evaluate if there exists a tag value. If there is not then we must create an "invalid" Tag to represent no value
+ * resolved.
+ * This is used later to return a null value.
+ * This is due the use of ConcurrentHashMap and we cannot set a null key.
+ */
+ io.micrometer.core.instrument.Tag appTag = (applicationName.isPresent()) ? io.micrometer.core.instrument.Tag.of(MP_APPLICATION_NAME_TAG,
+ applicationName.get()) : io.micrometer.core.instrument.Tag.of("null",
+ "null");
+
+ //Cache the value
+ applicationMPConfigAppNameTagCache.put(appName, appTag);
+ }
+
+ //Perhaps we don't really need a concurrent hashmap.. so we can avoid this.
+ io.micrometer.core.instrument.Tag returnTag;
+ return ((returnTag = applicationMPConfigAppNameTagCache.get(appName)).getKey().equals("null")) ? null : returnTag;
+ }
+
+ public LegacyMetricRegistryAdapter(String scope, MeterRegistry registry) {
+ this(scope, registry, ApplicationNameResolver.DEFAULT);
+ }
+
+ @Override
+ public Counter counter(String name) {
+ return internalCounter(internalGetMetadata(name),
+ new MetricDescriptor(name, withAppTags()));
+ }
+
+ @Override
+ public Counter counter(String name, Tag... tags) {
+ /*
+ * Verify tags before internalGetMetadata().
+ * The call withAppTags() can throw an IAE.
+ * Don't want to have had created metadata
+ * and have it put into the map before that.
+ */
+ Tags unifiedTags = withAppTags(tags);
+ return internalCounter(internalGetMetadata(name),
+ new MetricDescriptor(name, unifiedTags));
+ }
+
+ @Override
+ public Counter counter(MetricID metricID) {
+ String name = metricID.getName();
+ return internalCounter(internalGetMetadata(name),
+ new MetricDescriptor(name, withAppTags(metricID.getTagsAsArray())));
+ }
+
+ @Override
+ public Counter counter(Metadata metadata) {
+ return internalCounter(internalGetMetadata(metadata),
+ new MetricDescriptor(metadata.getName(), withAppTags()));
+ }
+
+ @Override
+ public Counter counter(Metadata metadata, Tag... tags) {
+ /*
+ * Verify tags before internalGetMetadata().
+ * The call withAppTags() can throw an IAE.
+ * Don't want to have had created metadata
+ * and have it put into the map before that.
+ */
+ Tags unifiedTags = withAppTags(tags);
+ return internalCounter(internalGetMetadata(metadata),
+ new MetricDescriptor(metadata.getName(), unifiedTags));
+ }
+
+ private void validateTagNamesMatch(MetricDescriptor id) {
+ /*
+ * Check that if there are metrics registered with same metric name that the tag
+ * set is the same. Specifically that the tag key values are the same. Values
+ * can differ.
+ */
+ for (MetricDescriptor md : constructedMeters.keySet()) {
+ if (md.name().equals(id.name) && !id.isTagNamesMatch(md.tags())) {
+ throw new IllegalArgumentException(String.format(
+ "The set of tags names provided do not match those of an existing metric with the same name. Provided = %s Existing = %s ",
+ id.tags.toString(), md.tags.toString()));
+ }
+ }
+ }
+
+ CounterAdapter internalCounter(MpMetadata metadata, MetricDescriptor id) {
+
+ validateTagNamesMatch(id);
+
+ CounterAdapter result = checkCast(CounterAdapter.class, metadata,
+ constructedMeters.computeIfAbsent(id, k -> new CounterAdapter()));
+ addNameToApplicationMap(id);
+
+ return result.register(metadata, id, registry, scope, resolveMPConfigGlobalTagsByServer());
+ }
+
+ /**
+ * This is specifically used for runtimes which may need use of a functional counter.
+ * For example, the runtime may want to implement a vendor specific counter metric which
+ * relies on values obtained from a Mbeans or MXbeans.
+ *
+ * @param object type
+ * @param metadata metadata of metric
+ * @param obj object to apply ToDoubleFunction
+ * @param func ToDoubleFunction
+ * @param tags tags of metric
+ * @return The functional counter
+ */
+ public Counter counter(Metadata metadata, T obj, ToDoubleFunction func, Tag... tags) {
+ /*
+ * Verify tags before internalGetMetadata().
+ * The call withAppTags() can throw an IAE.
+ * Don't want to have had created metadata
+ * and have it put into the map before that.
+ */
+ Tags unifiedTags = withAppTags(tags);
+ return internalCounter(internalGetMetadata(metadata), obj, func,
+ new MetricDescriptor(metadata.getName(), unifiedTags));
+ }
+
+ FunctionCounterAdapter internalCounter(MpMetadata metadata, T obj, ToDoubleFunction func, MetricDescriptor id) {
+
+ validateTagNamesMatch(id);
+
+ FunctionCounterAdapter result = checkCast(FunctionCounterAdapter.class, metadata,
+ constructedMeters.computeIfAbsent(id, k -> new FunctionCounterAdapter(obj, func)));
+ addNameToApplicationMap(id);
+ return result.register(metadata, id, registry, scope, resolveMPConfigGlobalTagsByServer());
+ }
+
+ public Gauge gauge(String name, T o, ToDoubleFunction f) {
+ return internalGauge(internalGetMetadata(name),
+ new MetricDescriptor(name, withAppTags()), o, f);
+ }
+
+ public Gauge gauge(String name, T o, ToDoubleFunction f, Tag... tags) {
+ /*
+ * Verify tags before internalGetMetadata().
+ * The call withAppTags() can throw an IAE.
+ * Don't want to have had created metadata
+ * and have it put into the map before that.
+ */
+ Tags unifiedTags = withAppTags(tags);
+ return internalGauge(internalGetMetadata(name),
+ new MetricDescriptor(name, unifiedTags), o, f);
+ }
+
+ @Override
+ public Gauge gauge(String name, T o, Function f, Tag... tags) {
+ /*
+ * Verify tags before internalGetMetadata().
+ * The call withAppTags() can throw an IAE.
+ * Don't want to have had created metadata
+ * and have it put into the map before that.
+ */
+ Tags unifiedTags = withAppTags(tags);
+ return internalGauge(internalGetMetadata(name),
+ new MetricDescriptor(name, unifiedTags), o, f);
+ }
+
+ @Override
+ public Gauge gauge(MetricID metricID, T o, Function f) {
+ String name = metricID.getName();
+ return internalGauge(internalGetMetadata(name),
+ new MetricDescriptor(name, withAppTags()), o, f);
+ }
+
+ @Override
+ public Gauge gauge(Metadata metadata, T o, Function f, Tag... tags) {
+ String name = metadata.getName();
+ /*
+ * Verify tags before internalGetMetadata().
+ * The call withAppTags() can throw an IAE.
+ * Don't want to have had created metadata
+ * and have it put into the map before that.
+ */
+ Tags unifiedTags = withAppTags(tags);
+ return internalGauge(internalGetMetadata(metadata),
+ new MetricDescriptor(name, unifiedTags), o, f);
+ }
+
+ @SuppressWarnings("unchecked")
+ GaugeAdapter internalGauge(MpMetadata metadata, MetricDescriptor id, T obj, ToDoubleFunction f) {
+ validateTagNamesMatch(id);
+ GaugeAdapter.DoubleFunctionGauge result = checkCast(GaugeAdapter.DoubleFunctionGauge.class, metadata,
+ constructedMeters.computeIfAbsent(id, k -> new GaugeAdapter.DoubleFunctionGauge<>(obj, f)));
+ addNameToApplicationMap(id);
+ return result.register(metadata, id, registry, scope, resolveMPConfigGlobalTagsByServer());
+ }
+
+ @SuppressWarnings("unchecked")
+ GaugeAdapter internalGauge(MpMetadata metadata, MetricDescriptor id, T obj, Function f) {
+ validateTagNamesMatch(id);
+ GaugeAdapter.FunctionGauge result = checkCast(GaugeAdapter.FunctionGauge.class, metadata,
+ constructedMeters.computeIfAbsent(id, k -> new GaugeAdapter.FunctionGauge<>(obj, f)));
+ addNameToApplicationMap(id);
+ return result.register(metadata, id, registry, scope, resolveMPConfigGlobalTagsByServer());
+ }
+
+ public Gauge gauge(String name, Supplier f) {
+ return internalGauge(internalGetMetadata(name),
+ new MetricDescriptor(name, withAppTags()), f);
+ }
+
+ @Override
+ public Gauge gauge(String name, Supplier f, Tag... tags) {
+ /*
+ * Verify tags before internalGetMetadata().
+ * The call withAppTags() can throw an IAE.
+ * Don't want to have had created metadata
+ * and have it put into the map before that.
+ */
+ Tags unifiedTags = withAppTags(tags);
+ return internalGauge(internalGetMetadata(name),
+ new MetricDescriptor(name, unifiedTags), f);
+ }
+
+ @Override
+ public Gauge gauge(MetricID metricID, Supplier f) {
+ String name = metricID.getName();
+ return internalGauge(internalGetMetadata(name),
+ new MetricDescriptor(name, withAppTags()), f);
+ }
+
+ @Override
+ public Gauge gauge(Metadata metadata, Supplier f, Tag... tags) {
+ String name = metadata.getName();
+ /*
+ * Verify tags before internalGetMetadata().
+ * The call withAppTags() can throw an IAE.
+ * Don't want to have had created metadata
+ * and have it put into the map before that.
+ */
+ Tags unifiedTags = withAppTags(tags);
+ return internalGauge(internalGetMetadata(metadata),
+ new MetricDescriptor(name, unifiedTags), f);
+ }
+
+ @SuppressWarnings("unchecked")
+ GaugeAdapter internalGauge(MpMetadata metadata, MetricDescriptor id, Supplier f) {
+ validateTagNamesMatch(id);
+ GaugeAdapter result = checkCast(GaugeAdapter.NumberSupplierGauge.class, metadata,
+ constructedMeters.computeIfAbsent(id, k -> new GaugeAdapter.NumberSupplierGauge(f)));
+ addNameToApplicationMap(id);
+ return result.register(metadata, id, registry, scope, resolveMPConfigGlobalTagsByServer());
+ }
+
+ void bindAnnotatedGauge(AnnotatedGaugeAdapter adapter) {
+ MetricDescriptor id = new MetricDescriptor(adapter.name(), adapter.tags());
+ AnnotatedGaugeAdapter oops = checkCast(AnnotatedGaugeAdapter.class, adapter.getMetadata(),
+ constructedMeters.putIfAbsent(id, adapter));
+ if (oops == null) {
+ metadataMap.put(adapter.name(), adapter.getMetadata());
+ adapter.register(id, registry);
+ } else {
+ throw new IllegalArgumentException(String.format("Gauge %s already exists. (existing='%s', new='%s')",
+ adapter.getId(), oops.getTargetName(), adapter.getTargetName()));
+ }
+ }
+
+ @Override
+ public Histogram histogram(String name) {
+ return internalHistogram(internalGetMetadata(name),
+ new MetricDescriptor(name, withAppTags()));
+ }
+
+ @Override
+ public Histogram histogram(String name, Tag... tags) {
+ /*
+ * Verify tags before internalGetMetadata().
+ * The call withAppTags() can throw an IAE.
+ * Don't want to have had created metadata
+ * and have it put into the map before that.
+ */
+ Tags unifiedTags = withAppTags(tags);
+ return internalHistogram(internalGetMetadata(name),
+ new MetricDescriptor(name, unifiedTags));
+ }
+
+ @Override
+ public Histogram histogram(MetricID metricID) {
+ String name = metricID.getName();
+ return internalHistogram(internalGetMetadata(name),
+ new MetricDescriptor(name, withAppTags(metricID.getTagsAsArray())));
+ }
+
+ @Override
+ public Histogram histogram(Metadata metadata) {
+ return internalHistogram(internalGetMetadata(metadata),
+ new MetricDescriptor(metadata.getName(), withAppTags()));
+ }
+
+ @Override
+ public Histogram histogram(Metadata metadata, Tag... tags) {
+ /*
+ * Verify tags before internalGetMetadata().
+ * The call withAppTags() can throw an IAE.
+ * Don't want to have had created metadata
+ * and have it put into the map before that.
+ */
+ Tags unifiedTags = withAppTags(tags);
+ return internalHistogram(internalGetMetadata(metadata),
+ new MetricDescriptor(metadata.getName(), unifiedTags));
+ }
+
+ HistogramAdapter internalHistogram(MpMetadata metadata, MetricDescriptor id) {
+ validateTagNamesMatch(id);
+ HistogramAdapter result = checkCast(HistogramAdapter.class, metadata,
+ constructedMeters.computeIfAbsent(id, k -> new HistogramAdapter()));
+ addNameToApplicationMap(id);
+ return result.register(metadata, id, scope, resolveMPConfigGlobalTagsByServer());
+ }
+
+ @Override
+ public Timer timer(String name) {
+ return internalTimer(internalGetMetadataTimers(name),
+ new MetricDescriptor(name, withAppTags()));
+ }
+
+ @Override
+ public Timer timer(String name, Tag... tags) {
+ /*
+ * Verify tags before internalGetMetadata().
+ * The call withAppTags() can throw an IAE.
+ * Don't want to have had created metadata
+ * and have it put into the map before that.
+ */
+ Tags unifiedTags = withAppTags(tags);
+ return internalTimer(internalGetMetadataTimers(name),
+ new MetricDescriptor(name, unifiedTags));
+ }
+
+ @Override
+ public Timer timer(MetricID metricID) {
+ String name = metricID.getName();
+ return internalTimer(internalGetMetadataTimers(name),
+ new MetricDescriptor(name, withAppTags(metricID.getTagsAsArray())));
+ }
+
+ @Override
+ public Timer timer(Metadata metadata) {
+ return internalTimer(internalGetMetadataTimers(metadata),
+ new MetricDescriptor(metadata.getName(), withAppTags()));
+ }
+
+ @Override
+ public Timer timer(Metadata metadata, Tag... tags) {
+ /*
+ * Verify tags before internalGetMetadata().
+ * The call withAppTags() can throw an IAE.
+ * Don't want to have had created metadata
+ * and have it put into the map before that.
+ */
+ Tags unifiedTags = withAppTags(tags);
+ return internalTimer(internalGetMetadataTimers(metadata),
+ new MetricDescriptor(metadata.getName(), unifiedTags));
+ }
+
+ TimerAdapter internalTimer(MpMetadata metadata, MetricDescriptor id) {
+ validateTagNamesMatch(id);
+ TimerAdapter result = checkCast(TimerAdapter.class, metadata,
+ constructedMeters.computeIfAbsent(id, k -> new TimerAdapter(registry)));
+ addNameToApplicationMap(id);
+ return result.register(metadata, id, scope, resolveMPConfigGlobalTagsByServer());
+ }
+
+ @Override
+ public Metric getMetric(MetricID metricID) {
+ return constructedMeters.get(new MetricDescriptor(metricID.getName(), withAppTags(metricID.getTagsAsArray())));
+ }
+
+ @Override
+ public T getMetric(MetricID metricID, Class asType) {
+ return asType.cast(constructedMeters.get(new MetricDescriptor(metricID.getName(), withAppTags(metricID.getTagsAsArray()))));
+ }
+
+ @Override
+ public Counter getCounter(MetricID metricID) {
+ return (Counter) constructedMeters.get(new MetricDescriptor(metricID.getName(), withAppTags(metricID.getTagsAsArray())));
+ }
+
+ @Override
+ public Gauge> getGauge(MetricID metricID) {
+ return (Gauge>) constructedMeters.get(new MetricDescriptor(metricID.getName(), withAppTags(metricID.getTagsAsArray())));
+ }
+
+ @Override
+ public Histogram getHistogram(MetricID metricID) {
+ return (Histogram) constructedMeters.get(new MetricDescriptor(metricID.getName(), withAppTags(metricID.getTagsAsArray())));
+ }
+
+ @Override
+ public Timer getTimer(MetricID metricID) {
+ return (Timer) constructedMeters.get(new MetricDescriptor(metricID.getName(), withAppTags(metricID.getTagsAsArray())));
+ }
+
+ @Override
+ public Metadata getMetadata(String name) {
+ return metadataMap.get(name);
+ }
+
+ @Override
+ public boolean remove(String name) {
+
+ boolean isRemoveSuccess = false;
+ for (Map.Entry e : constructedMeters.entrySet()) {
+ if (e.getKey().name().equals(name)) {
+ isRemoveSuccess = internalRemove(e.getKey());
+ }
+ }
+ return isRemoveSuccess;
+
+ }
+
+ @Override
+ public boolean remove(MetricID metricID) {
+ return internalRemove(new MetricDescriptor(metricID));
+ }
+
+ @Override
+ public void removeMatching(MetricFilter metricFilter) {
+ for (Map.Entry e : constructedMeters.entrySet()) {
+ MetricID mid = e.getKey().toMetricID();
+ if (metricFilter.matches(mid, e.getValue())) {
+ internalRemove(e.getKey());
+ }
+ }
+ }
+
+ boolean internalRemove(MetricDescriptor match) {
+ final String METHOD_NAME = "internalRemove";
+
+ MeterHolder holder = constructedMeters.remove(match);
+
+ if (holder != null) {
+ if (LOGGER.isLoggable(Level.FINE)) {
+ LOGGER.logp(Level.FINE, CLASS_NAME, METHOD_NAME,
+ String.format("Removed metric with [id: %s]", match.toMetricID().toString()));
+ }
+
+ io.micrometer.core.instrument.Meter meter = Metrics.globalRegistry.remove(holder.getMeter());
+ if (meter != null) {
+ if (LOGGER.isLoggable(Level.FINE)) {
+ LOGGER.logp(Level.FINE, CLASS_NAME, METHOD_NAME, String.format("Removed from the Micrometer global registry a meter with MeterId [id= %s]",
+ meter.getId()));
+ }
+ } else {
+ if (LOGGER.isLoggable(Level.FINE)) {
+ LOGGER.logp(Level.FINE, CLASS_NAME, METHOD_NAME, String.format(
+ "Attempted to remove a meter with the corresponding MetricID [id= %s] from the Micrometer global registry, but does not exist.",
+ match.toMetricID()));
+ }
+ }
+
+ // Remove associated metadata if this is the last MP Metric left with that name
+ if (constructedMeters.keySet().stream().noneMatch(id -> id.name.equals(match.name))) {
+ metadataMap.remove(match.name);
+ if (LOGGER.isLoggable(Level.FINE)) {
+ LOGGER.logp(Level.FINE, CLASS_NAME, METHOD_NAME,
+ String.format("Removed metadata for [name: %s]", match.name));
+ }
+ }
+ }
+ return holder != null;
+ }
+
+ @Override
+ public SortedSet getNames() {
+ return new TreeSet<>(metadataMap.keySet());
+ }
+
+ @Override
+ public SortedSet getMetricIDs() {
+ SortedSet out = new TreeSet<>();
+ for (MetricDescriptor key : constructedMeters.keySet()) {
+ out.add(key.toMetricID());
+ }
+ return out;
+ }
+
+ @Override
+ public SortedMap getGauges() {
+ return getGauges(MetricFilter.ALL);
+ }
+
+ @Override
+ public SortedMap getGauges(MetricFilter metricFilter) {
+ return getMetrics(Gauge.class, metricFilter);
+ }
+
+ @Override
+ public SortedMap getCounters() {
+ return getCounters(MetricFilter.ALL);
+ }
+
+ @Override
+ public SortedMap getCounters(MetricFilter metricFilter) {
+ return getMetrics(Counter.class, metricFilter);
+ }
+
+ @Override
+ public SortedMap getHistograms() {
+ return getHistograms(MetricFilter.ALL);
+ }
+
+ @Override
+ public SortedMap getHistograms(MetricFilter metricFilter) {
+ return getMetrics(Histogram.class, metricFilter);
+ }
+
+ @Override
+ public SortedMap getTimers() {
+ return getTimers(MetricFilter.ALL);
+ }
+
+ @Override
+ public SortedMap getTimers(MetricFilter metricFilter) {
+ return getMetrics(Timer.class, metricFilter);
+ }
+
+ @Override
+ public SortedMap getMetrics(MetricFilter filter) {
+ SortedMap out = new TreeMap<>();
+ for (Map.Entry e : constructedMeters.entrySet()) {
+ MetricID mid = e.getKey().toMetricID();
+ if (filter.matches(mid, e.getValue())) {
+ out.put(e.getKey().toMetricID(), e.getValue());
+ }
+ }
+ return out;
+ }
+
+ @Override
+ public SortedMap getMetrics(Class ofType, MetricFilter filter) {
+ SortedMap out = new TreeMap<>();
+ for (Map.Entry e : constructedMeters.entrySet()) {
+ if (ofType.isAssignableFrom(e.getValue().getClass())) {
+ MetricID mid = e.getKey().toMetricID();
+ if (filter.matches(mid, e.getValue())) {
+ out.put(e.getKey().toMetricID(), (T) e.getValue());
+ }
+ }
+ }
+ return out;
+ }
+
+ @Override
+ public Map getMetrics() {
+ SortedMap out = new TreeMap<>();
+ for (Map.Entry e : constructedMeters.entrySet()) {
+ out.put(e.getKey().toMetricID(), e.getValue());
+ }
+ return out;
+ }
+
+ @Override
+ public Map getMetadata() {
+ return Collections.unmodifiableMap(metadataMap);
+ }
+
+ @Override
+ public String getScope() {
+ return scope;
+ }
+
+ /**
+ * Must be called before any internalGetMetadata calls
+ * We may throw an IllegalArgumentException. So we don't
+ * want metadata to be registered if it was not necessary.
+ *
+ * @param tags Tags to be combined with
+ * @return tags combined with global tags and mp_app if available
+ */
+ public Tags withAppTags(Tag... tags) {
+
+ Tags out = Tags.empty();
+
+ if (tags != null) {
+ for (Tag t : tags) {
+ /*
+ * Need to check if tags being passed in are
+ * 'mp_scope' or 'mp_app'; throw IAE as per spec
+ *
+ * mp_scope is provided to micrometer registry
+ * during metric/meter registration in the adapters
+ *
+ * mp_app is resolved with the resolveMPConfigAppNameTag()
+ * logic
+ */
+ if (t.getTagName().equals(MP_APPLICATION_NAME_TAG)
+ || t.getTagName().equals(MP_SCOPE_TAG)) {
+ throw new IllegalArgumentException("Can not use "
+ + "reserved tag names: \"mp_scope\" "
+ + "or \"mp_app\"");
+ }
+ out = out.and(t.getTagName(), t.getTagValue());
+ }
+ }
+
+ out = combineApplicationTagsWithMPConfigAppNameTag(out);
+
+ return out;
+ }
+
+ public Tag[] scopeTagsLegacy() {
+ return new Tag[] { new Tag("scope", this.scope) };
+ }
+
+ private MpMetadata internalGetMetadata(String name) {
+
+ MpMetadata result = metadataMap.computeIfAbsent(name, k -> new MpMetadata(name));
+
+ /*
+ * Check that metadata of metric being registered/retrieved matches existing
+ * existing metadata (if it exists)
+ */
+ if (!result.equals(MpMetadata.sanitize(new MpMetadata(name)))) {
+ throw new IllegalArgumentException(String.format("Existing metadata (%s) does not match with supplied metadata (%s)",
+ result.toString(), new MpMetadata(name).toString()));
+ }
+
+ return result;
+ }
+
+ /*
+ * Temporary work around due to https://github.com/eclipse/microprofile-metrics/issues/760
+ */
+ private MpMetadata internalGetMetadata(Metadata metadata) {
+ MpMetadata result = metadataMap.computeIfAbsent(metadata.getName(), k -> MpMetadata.sanitize(metadata));
+
+ /*
+ * Check that metadata of metric being registered/retrieved matches existing
+ * existing metadata (if it exists)
+ */
+ if (!result.equals(MpMetadata.sanitize(metadata))) {
+ throw new IllegalArgumentException(String.format("Existing metadata (%s) does not match with supplied metadata (%s)",
+ result.toString(), metadata.toString()));
+ }
+
+ return result;
+ }
+
+ /*
+ * Temporary work around due to https://github.com/eclipse/microprofile-metrics/issues/760
+ */
+ private MpMetadata internalGetMetadataTimers(String name) {
+
+ MpMetadata result = metadataMap.computeIfAbsent(name, k -> new MpMetadata(name));
+
+ /*
+ * Check that metadata of metric being registered/retrieved matches existing
+ * existing metadata (if it exists)
+ */
+ if (!result.equalsTimers(MpMetadata.sanitize(new MpMetadata(name)))) {
+ throw new IllegalArgumentException(String.format("Existing metadata (%s) does not match with supplied metadata (%s)",
+ result.toString(), new MpMetadata(name).toString()));
+ }
+
+ return result;
+ }
+
+ private MpMetadata internalGetMetadataTimers(Metadata metadata) {
+ MpMetadata result = metadataMap.computeIfAbsent(metadata.getName(), k -> MpMetadata.sanitize(metadata));
+
+ /*
+ * Check that metadata of metric being registered/retrieved matches existing
+ * existing metadata (if it exists)
+ */
+ if (!result.equalsTimers(MpMetadata.sanitize(metadata))) {
+ throw new IllegalArgumentException(String.format("Existing metadata (%s) does not match with supplied metadata (%s)",
+ result.toString(), metadata.toString()));
+ }
+
+ return result;
+ }
+
+ T checkCast(Class type, MpMetadata metadata, MeterHolder o) {
+ try {
+ return type.cast(o);
+ } catch (ClassCastException cce) {
+ throw new IllegalStateException(String.format("Metric (%s) already defined using a different type (%s)",
+ metadata.name, o.getMeter().getId().getType()), cce);
+ }
+ }
+
+ public MemberToMetricMappings getMemberToMetricMappings() {
+ return memberToMetricMappings;
+ }
+
+}