All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tangosol.internal.metrics.MetricSupport Maven / Gradle / Ivy

There is a newer version: 24.09
Show newest version
/*
 * Copyright (c) 2000, 2022, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * https://oss.oracle.com/licenses/upl.
 */
package com.tangosol.internal.metrics;

import com.oracle.coherence.common.base.Logger;

import com.tangosol.coherence.config.Config;
import com.tangosol.coherence.discovery.Discovery;

import com.tangosol.internal.net.metrics.MetricsHttpHelper;
import com.tangosol.net.CacheFactory;
import com.tangosol.net.Cluster;
import com.tangosol.net.Member;

import com.tangosol.net.management.MBeanHelper;
import com.tangosol.net.management.MBeanServerProxy;
import com.tangosol.net.management.Registry;

import com.tangosol.net.management.annotation.MetricsLabels;
import com.tangosol.net.management.annotation.MetricsScope;
import com.tangosol.net.management.annotation.MetricsTag;
import com.tangosol.net.management.annotation.MetricsValue;

import com.tangosol.net.metrics.MBeanMetric;
import com.tangosol.net.metrics.MBeanMetric.Identifier;
import com.tangosol.net.metrics.MetricsRegistryAdapter;

import com.tangosol.util.Base;

import javax.management.Descriptor;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenMBeanAttributeInfo;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import javax.management.openmbean.TabularData;
import javax.management.openmbean.TabularDataSupport;
import javax.management.openmbean.TabularType;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryType;
import java.lang.management.MemoryUsage;
import java.math.BigDecimal;
import java.math.BigInteger;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;

import java.util.function.Function;
import java.util.function.Supplier;

/**
 * A helper class to provide support for registering and un-registering
 * Coherence metrics based on MBeans.
 *
 * @author jk  2019.06.19
 * @since 12.2.1.4
 */
public class MetricSupport
    {
    // ----- constructors ---------------------------------------------------

    /**
     * Create a {@link MetricSupport} that obtains a {@link MBeanServerProxy}
     * from the supplied {@link Registry}.
     * 

* The {@link ServiceLoader} is used to discover and load instances of * {@link MetricsRegistryAdapter} that will be notified when Coherence * metrics are registered or removed. */ // Called from TDE Gateway @SuppressWarnings("unused") public MetricSupport() { this(() -> CacheFactory.getCluster().getManagement()); } /** * Create a {@link MetricSupport} instance that uses the specified * {@link Registry} supplier and adapters. * * @param supplier the {@link Registry} supplier * @param adapters the list of {@link MetricsRegistryAdapter}s */ MetricSupport(Supplier supplier, List adapters) { this(supplier, () -> adapters); } /** * Create a {@link MetricSupport} instance that uses the specified * {@link Registry} supplier and adapters. * * @param supplier the {@link Registry} supplier * @param adaptersSupplier the supplier providing a {@link List} of {@link MetricsRegistryAdapter}s */ MetricSupport(Supplier supplier, Supplier> adaptersSupplier) { f_suppRegistry = supplier; f_suppMBeanServerProxy = () -> f_suppRegistry.get().getMBeanServerProxy().local(); f_listRegistry = adaptersSupplier.get(); f_fHasRegistries = !f_listRegistry.isEmpty(); f_mapMetric = new HashMap<>(); } /** * Create a {@link MetricSupport} instance that uses the specified * {@link Registry} supplier. * * @param supplier the {@link Registry} supplier */ MetricSupport(Supplier supplier) { this(supplier, () -> { List list = new ArrayList<>(); if (Config.getBoolean(MetricsHttpHelper.PROP_METRICS_ENABLED, false)) { // add the default Coherence metrics registry list.add(new DefaultMetricRegistry.Adapter()); } ClassLoader[] classLoaders = new ClassLoader[] { Base.getContextClassLoader(), MetricsRegistryAdapter.class.getClassLoader() // fallback if context classloader fails }; for (int i = 0, len = classLoaders.length; i < len; i++) { ClassLoader loader = classLoaders[i]; try { ServiceLoader serviceLoader = ServiceLoader.load(MetricsRegistryAdapter.class, loader); for (MetricsRegistryAdapter metricsRegistry : serviceLoader) { list.add(metricsRegistry); } break; } catch (Throwable t) { list.clear(); if (Logger.isEnabled(Logger.WARNING)) { String msg = "Error loading MetricRegistryAdapter using the %s classloader:"; if (i == 0) { Logger.warn(String.format(msg, "context"), t); Logger.warn("Attempting to load adapters using the fallback classloader."); } else { Logger.warn(String.format(msg, "fallback"), t); Logger.warn("Metrics failed to initialize."); } } } } return list; }); } // ----- MetricsSupport methods ----------------------------------------- /** * Determine whether there are any {@link MetricsRegistryAdapter} instances. * * @return {@code true} if there are {@link MetricsRegistryAdapter} loaded */ public boolean hasRegistries() { return f_fHasRegistries; } /** * Register Coherence metrics for the specified local MBean name. *

* If the MBean name has already been registered then it will be ignored. * * @param sMBeanName the name of the local MBean to create and register * metrics for. */ public void register(String sMBeanName) { if (f_fHasRegistries) { // We skip the Cluster MBean because when it is registered the cluster is not started // so we will be unable to obtain values such as cluster name, member, role etc. We // will register the cluster when we see the Node MBean. if (sMBeanName.startsWith(Registry.CLUSTER_TYPE) || sMBeanName.startsWith(Registry.MANAGEMENT_TYPE) || sMBeanName.startsWith(Discovery.DISCOVERY_TYPE) || sMBeanName.startsWith(Registry.HEALTH_TYPE)) { return; } MBeanServerProxy proxy = f_suppMBeanServerProxy.get(); if (sMBeanName.startsWith(Registry.NODE_TYPE)) { registerInternal(f_suppRegistry.get().ensureGlobalName(Registry.CLUSTER_TYPE), proxy); } registerInternal(sMBeanName, proxy); } } /** * Remove all Coherence metrics for the specified local MBean name. *

* If the MBean name is not already registered it will be ignored. * * @param sMBeanName the name of the local MBean */ public synchronized void remove(String sMBeanName) { if (f_fHasRegistries && f_setRegistered.remove(sMBeanName)) { Set setMetric = f_mapMetric.remove(createObjectName(sMBeanName)); if (setMetric != null) { for (MBeanMetric metric : setMetric) { for (MetricsRegistryAdapter adapter : f_listRegistry) { try { adapter.remove(metric.getIdentifier()); } catch (Throwable e) { Logger.warn("Caught exception removing metrics for " + sMBeanName + " from " + adapter + ": " + e.getLocalizedMessage()); } } } } } } // ----- helper methods ------------------------------------------------- /** * Register metrics from the specified MBean. *

* If the MBean has already been registered it will be ignored * * @param sMBeanName the name of the MBean * @param proxy the {@link MBeanServerProxy} to use to obtain MBean information */ private synchronized void registerInternal(String sMBeanName, MBeanServerProxy proxy) { if (f_setRegistered.contains(sMBeanName)) { return; } if (sMBeanName.startsWith("type=Platform") && sMBeanName.contains("subType=MemoryPool")) { ensureMemoryMetrics(f_listRegistry); } f_setRegistered.add(sMBeanName); MBeanInfo mBeanInfo = proxy.getMBeanInfo(sMBeanName); if (mBeanInfo == null) { Logger.warn("MetricSupport.registerInternal(), mBeanInfo is null on MBean: " + sMBeanName + ", proxy: " + proxy); } Set setMetric = getMetrics(sMBeanName, mBeanInfo, proxy); if (setMetric.size() > 0) { f_mapMetric.put(createObjectName(sMBeanName), setMetric); for (MetricsRegistryAdapter adapter : f_listRegistry) { for (MBeanMetric metric : setMetric) { try { adapter.register(metric); } catch (Throwable e) { Logger.warn("Caught exception registering metric " + metric.getIdentifier() + " with " + adapter + ": " + e.getLocalizedMessage()); } } } } } /** * Create an ObjectName from the MBean name. * * @param sMBeanName the MBean name * * @return an {@link ObjectName} */ private ObjectName createObjectName(String sMBeanName) { try { sMBeanName = MBeanHelper.ensureDomain(sMBeanName); return new ObjectName(MBeanHelper.quoteCanonical(sMBeanName)); } catch (MalformedObjectNameException e) { throw Base.ensureRuntimeException(e); } } /** * Returns the metrics published by the specified MBean. * * @param sMBeanName the MBean name * @param mBeanInfo the MBeanInfo for the MBean * @param proxy the {@link MBeanServerProxy} to use to obtain MBean values * * @return the {@link Set} of metrics for the MBean, or an empty {@link Set} * if the MBean has no metrics */ Set getMetrics(String sMBeanName, MBeanInfo mBeanInfo, MBeanServerProxy proxy) { MBeanAttributeInfo[] aAttributeInfo = mBeanInfo.getAttributes(); ObjectName objectName = createObjectName(sMBeanName); Map mapMetricValues = new HashMap<>(); Map mapTagAttributes = new HashMap<>(); if (isPlatformMBean(objectName)) { getMetricsForPlatformMBean(aAttributeInfo, objectName, mapMetricValues, mapTagAttributes); } else { getMetricsForCoherenceMBean(aAttributeInfo, mapMetricValues, mapTagAttributes); } Set setMetric = new HashSet<>(); if (!mapMetricValues.isEmpty()) { Map mapTag = createMetricTags(sMBeanName, proxy, objectName, mapTagAttributes); for (Map.Entry entry : mapMetricValues.entrySet()) { List list = getMetricsForAttribute(sMBeanName, objectName, mBeanInfo, entry.getValue(), mapTag, proxy); setMetric.addAll(list); } } return setMetric; } /** * Obtain the metrics for a Coherence MBean. * * @param aAttributeInfo the array of the MBean's MBeanAttributeInfo instances * @param mapMetricValues the {@link Map} of metric value attributes to populate * @param mapTagAttributes the {@link Map} of metric tag attributes to populate */ private void getMetricsForCoherenceMBean(MBeanAttributeInfo[] aAttributeInfo, Map mapMetricValues, Map mapTagAttributes) { for (MBeanAttributeInfo attributeInfo : aAttributeInfo) { if (ATTRIBUTE_FILTER.evaluate(attributeInfo)) { String sTagName = getMetric(MetricsTag.DESCRIPTOR_KEY, attributeInfo); if (sTagName != null) { if (sTagName.equals(MetricsTag.DEFAULT)) { sTagName = attributeInfo.getName(); } sTagName = sTagName.replaceAll(REGEX_TAG, UNDER_SCORE); if (sTagName.equals("senior_member_id")) { // skip - this was a hack tag in the original metrics continue; } mapTagAttributes.put(sTagName, attributeInfo.getName()); } else { mapMetricValues.put(attributeInfo.getName(), attributeInfo); } } } } /** * Obtain the metrics for a Coherence Platform MBean. * * @param aAttributeInfo the array of the MBean's MBeanAttributeInfo instances * @param objectName the MBean's {@link ObjectName} * @param mapMetricValues the {@link Map} of metric value attributes to populate * @param mapTagAttributes the {@link Map} of metric tag attributes to populate */ private void getMetricsForPlatformMBean(MBeanAttributeInfo[] aAttributeInfo, ObjectName objectName, Map mapMetricValues, Map mapTagAttributes) { if (f_setPlatfromMBeans.stream().anyMatch(pattern -> pattern.apply(objectName))) { for (MBeanAttributeInfo attributeInfo : aAttributeInfo) { String sName = attributeInfo.getName(); String sType = attributeInfo.getType(); if (!sType.startsWith("[") && !LIST_SKIP_ATTRIBUTES.contains(sName)) { if (LIST_TAG_TYPES.contains(sType)) { mapTagAttributes.put(sName, sName); } else { mapMetricValues.put(sName, attributeInfo); } } } } } /** * Obtain the metrics for an attribute. *

* Some attributes (for example {@link CompositeData} types) may produce * multiple metrics. * * @param sMBeanName the MBean name * @param objectName the MBean's {@link ObjectName} * @param mBeanInfo the MBean's {@link MBeanInfo} * @param attributeInfo the {@link MBeanAttributeInfo} of the attribute * @param mapTag the map of tags to apply to the metrics * * @return the {@link List} of metrics from the attribute or an empty list of the attribute * does not produce any metrics */ private List getMetricsForAttribute(String sMBeanName, ObjectName objectName, MBeanInfo mBeanInfo, MBeanAttributeInfo attributeInfo, Map mapTag, MBeanServerProxy proxy) { List listMetric = new ArrayList<>(); String sAttribType = attributeInfo.getType(); MBeanMetric.Scope scope = getRegistryType(mBeanInfo, objectName); String sAttributeName = attributeInfo.getName(); Descriptor descriptor = attributeInfo.getDescriptor(); String[] asLabels = descriptor == null ? null : (String[]) descriptor.getFieldValue(MetricsLabels.DESCRIPTOR_KEY); if (attributeInfo instanceof OpenMBeanAttributeInfo) { OpenMBeanAttributeInfo openInfo = (OpenMBeanAttributeInfo) attributeInfo; OpenType openType = openInfo.getOpenType(); if (openType instanceof CompositeType) { Function function = mbsp -> (CompositeData) mbsp.getAttribute(sMBeanName, sAttributeName); listMetric.addAll(getMetricsForCompositeType(sMBeanName, objectName, attributeInfo, mapTag, scope, function, (CompositeType) openType)); } else if (openType instanceof SimpleType) { MBeanMetric metric = createSimpleMetric(sMBeanName, objectName, attributeInfo, mapTag, asLabels, scope, sAttributeName); // If the metric value is not set then do not create a metric from it. // This stops us creating metrics for meaningless attributes, for example // creating metrics for service persistence attributes on a Proxy service. if (shouldInclude(metric)) { listMetric.add(metric); } } else if (openType instanceof TabularType) { listMetric.addAll(getMetricsForTabularType(sMBeanName, objectName, attributeInfo, mapTag, scope, (TabularType) openType, proxy)); } } else if (TabularDataSupport.class.getName().equals(sAttribType)) { // not currently supported } else { MBeanMetric metric = createSimpleMetric(sMBeanName, objectName, attributeInfo, mapTag, asLabels, scope, sAttributeName); // If the metric value is not set then do not create a metric from it. // This stops us creating metrics for meaningless attributes, for example // creating metrics for service persistence attributes on a Proxy service. if (shouldInclude(metric)) { listMetric.add(metric); } } return listMetric; } private MBeanMetric createSimpleMetric(String sMBeanName, ObjectName objectName, MBeanAttributeInfo attributeInfo, Map mapTag, String[] asLabels, MBeanMetric.Scope scope, String sAttributeName) { String sMetricName = createMetricName(objectName, attributeInfo); String sDescription = attributeInfo.getDescription(); Map mapMetricTag; if (asLabels == null || asLabels.length == 0) { mapMetricTag = mapTag; } else { mapMetricTag = new HashMap<>(mapTag); for (int i = 0; i < asLabels.length; i++) { String sKey = asLabels[i++]; if (i < asLabels.length) { mapMetricTag.put(sKey, asLabels[i]); } } } return createSimpleMetric(sMBeanName, sMetricName, sAttributeName, scope, mapMetricTag, sDescription); } /** * Determine whether to include a metric based on whether it has a value. * Some Coherence MBeans have values that do not apply and are hence set * to a "not set" value, such as -1. * * @param metric the metric to test * * @return {@code true} if the metric should be created */ private boolean shouldInclude(MBeanMetric metric) { Object oValue = metric.getValue(); if (oValue != null) { return true; } // for some weird reason cache metrics may be -1 initially ??? return metric.getName().startsWith("Coherence.Cache.") && !metric.getName().startsWith("Coherence.Cache.Store") && !metric.getName().startsWith("Coherence.Cache.Queue"); } /** * Obtain the metrics for an attribute. *

* Some attributes (for example {@link CompositeData} types) may produce * multiple metrics. * * @param sMBeanName the MBean name * @param objectName the MBean's {@link ObjectName} * @param attributeInfo the {@link MBeanAttributeInfo} of the attribute * @param mapTag the map of tags to apply to the metrics * @param scope the scope of the metric * @param tabularType the table type * @param proxy the {@link MBeanServerProxy} * * @return the {@link List} of metrics from the attribute or an empty list of the attribute * does not produce any metrics */ @SuppressWarnings("unchecked") private List getMetricsForTabularType(String sMBeanName, ObjectName objectName, MBeanAttributeInfo attributeInfo, Map mapTag, MBeanMetric.Scope scope, TabularType tabularType, MBeanServerProxy proxy) { List listMetrics = new ArrayList<>(); String sAttributeName = attributeInfo.getName(); TabularData tabularData = (TabularData) proxy.getAttribute(sMBeanName, sAttributeName); List listTagNames = tabularType.getIndexNames(); Set> setListTagValues = (Set>) tabularData.keySet(); Descriptor descriptor = attributeInfo.getDescriptor(); String[] asMetricColumns = (String[]) descriptor.getFieldValue("metrics.columns"); for (String sColumn : asMetricColumns) { String sScope = (String) descriptor.getFieldValue(sColumn + '.' + MetricsScope.KEY); String sValue = (String) descriptor.getFieldValue(sColumn + '.' + MetricsValue.DESCRIPTOR_KEY); String[] asLabels = (String[]) descriptor.getFieldValue(sColumn + '.' + MetricsLabels.DESCRIPTOR_KEY); String sSuffix = sValue == null || sValue.isBlank() ? sColumn : sValue; if (sScope != null && !sScope.isBlank()) { scope = MBeanMetric.Scope.valueOf(sScope); } for (List listTag : setListTagValues) { Map mapTableTags = new LinkedHashMap<>(mapTag); for (int i = 0; i < listTag.size(); i++) { addTag(mapTableTags, listTagNames.get(i), listTag.get(i)); } if (asLabels != null) { for (int i = 0; i < asLabels.length; i++) { addTag(mapTableTags, asLabels[i++], asLabels[i]); } } Object[] aoKey = listTag.toArray(new Object[0]); Function fn = mbsp -> ((TabularData) mbsp.getAttribute(sMBeanName, sAttributeName)).get(aoKey); String sMetricName = createMetricName(objectName, attributeInfo) + '.' + sSuffix; MBeanMetric metric = createCompositeMetric(sMBeanName, sMetricName, fn, sColumn, scope, mapTableTags, ""); listMetrics.add(metric); } } return listMetrics; } /** * Obtain the metrics for an attribute. *

* Some attributes (for example {@link CompositeData} types) may produce * multiple metrics. * * @param sMBeanName the MBean name * @param objectName the MBean's {@link ObjectName} * @param attributeInfo the {@link MBeanAttributeInfo} of the attribute * @param mapTag the map of tags to apply to the metrics * @param scope the scope of the metric * @param supplierParent the function that supplied the parent {@link CompositeData} * to use when obtaining the metric values * @param openType the {@link CompositeType} of the metric * * @return the {@link List} of metrics from the attribute or an empty list of the attribute * does not produce any metrics */ private List getMetricsForCompositeType(String sMBeanName, ObjectName objectName, MBeanAttributeInfo attributeInfo, Map mapTag, MBeanMetric.Scope scope, Function supplierParent, CompositeType openType) { List list = new ArrayList<>(); for (String sKey : openType.keySet()) { OpenType type = openType.getType(sKey); if (type instanceof SimpleType) { if (METRIC_ATTRIBUTE_TYPES.contains(type.getTypeName())) { String sMetricName = createMetricName(objectName, attributeInfo) + '.' + sKey; String sHelp = attributeInfo.getDescription(); list.add(createCompositeMetric(sMBeanName, sMetricName, supplierParent, sKey, scope, mapTag, sHelp)); } } else if (type instanceof TabularData) { // ToDo: this is where we'd need to do work to support Heap after GC } else if (type instanceof CompositeData) { Function supplier = mbs -> { CompositeData data = supplierParent.apply(mbs); return data == null ? null : (CompositeData) data.get(sKey); }; list.addAll(getMetricsForCompositeType(sMBeanName, objectName, attributeInfo, mapTag, scope, supplier, (CompositeType) type)); } } return list; } /** * Create a simple MBean metric. * * @param sMBeanName the MBean name * @param sMetricName the metric name * @param sAttribute the MBean attribute name * @param scope the metric scope * @param mapTag the metric's tags * @param sHelp the metric's help text * * @return a simple MBean metric */ private MBeanMetric createSimpleMetric(String sMBeanName, String sMetricName, String sAttribute, MBeanMetric.Scope scope, Map mapTag, String sHelp) { MBeanMetric.Identifier identifier = new MBeanMetric.Identifier(scope, sMetricName, mapTag); return new SimpleMetric(identifier, sMBeanName, sAttribute, sHelp, f_suppMBeanServerProxy); } /** * Create a MBean metric wrapping a value from a {@link CompositeData} attribute. * * @param sMBeanName the MBean name * @param sMetricName the metric name * @param supplierParent the function that supplied the parent {@link CompositeData} * to use when obtaining the metric values * @param scope the metric scope * @param mapTag the metric's tags * @param sHelp the metric's help text * * @return a {@link CompositeData} MBean metric */ private MBeanMetric createCompositeMetric(String sMBeanName, String sMetricName, Function supplierParent, String sKey, MBeanMetric.Scope scope, Map mapTag, String sHelp) { MBeanMetric.Identifier identifier = new MBeanMetric.Identifier(scope, sMetricName, mapTag); return new CompositeMetric(identifier, sMBeanName, supplierParent, sKey, sHelp, f_suppMBeanServerProxy); } private MBeanMetric.Scope getRegistryType(MBeanInfo mBeanInfo, ObjectName objectName) { Object sType = mBeanInfo.getDescriptor().getFieldValue(MetricsScope.KEY); if (sType == null) { return isPlatformMBean(objectName) ? MBeanMetric.Scope.VENDOR : MBeanMetric.Scope.APPLICATION; } try { return MBeanMetric.Scope.valueOf(String.valueOf(sType)); } catch (IllegalArgumentException e) { return MBeanMetric.Scope.APPLICATION; } } /** * Create the name to use for a metric. * * @param objectName the MBean {@link ObjectName} * @param attributeInfo the {@link MBeanAttributeInfo} * * @return the name to use for the metric */ private String createMetricName(ObjectName objectName, MBeanAttributeInfo attributeInfo) { String sMetricName = getMetric(MetricsValue.DESCRIPTOR_KEY, attributeInfo); String sPrefix = s_mapMetricPrefix.entrySet() .stream() .filter(e -> e.getKey().apply(objectName)) .map(Map.Entry::getValue) .findFirst() .orElse(null); if (sPrefix == null) { sPrefix = "Coherence." + objectName.getKeyProperty("type"); } if (sMetricName == null || sMetricName.isEmpty() || sMetricName.equals(MetricsValue.DEFAULT)) { sMetricName = attributeInfo.getName(); } if (!sPrefix.endsWith(".")) { sPrefix = sPrefix + "."; } return sPrefix + sMetricName; } /** * Create the {@link Map} of metric tags from the {@link ObjectName} properties. * * @param sMBeanName the MBean name * @param proxy the {@link MBeanServerProxy} to use to obtain MBean attribute values * @param objectName the {@link ObjectName} to use to create the tags * @param mapTagAttributes the {@link Map} of MBean attributes to use as tags * * @return the {@link Map} of metric tags */ private Map createMetricTags(String sMBeanName, MBeanServerProxy proxy, ObjectName objectName, Map mapTagAttributes) { Map mapTag = new HashMap<>(); Cluster cluster = CacheFactory.getCluster(); Member member = cluster.getLocalMember(); // these metric tags uniquely // identify metrics per cluster member if (cluster.isRunning()) { // Note: a call to getClusterName will ensure the cluster which // is not desired when registering services that do not require // a running cluster (extend clients & LocalCache service) mapTag.put(GLOBAL_TAG_CLUSTER, cluster.getClusterName()); } if (!sMBeanName.startsWith(Registry.CLUSTER_TYPE) && objectName.getKeyProperty("responsibility") == null) { mapTag.put(GLOBAL_TAG_SITE, member.getSiteName()); mapTag.put(GLOBAL_TAG_MACHINE, member.getMachineName()); mapTag.put(GLOBAL_TAG_MEMBER, member.getMemberName()); mapTag.put(GLOBAL_TAG_ROLE, member.getRoleName()); } objectName.getKeyPropertyList() .entrySet() .stream() .filter(e -> !OBJECT_NAME_TAG_EXCLUDES.contains(e.getKey())) .forEach(e -> addTag(mapTag, e.getKey(), e.getValue())); for (Map.Entry entry : mapTagAttributes.entrySet()) { Object oTag = proxy.getAttribute(sMBeanName, entry.getValue()); addTag(mapTag, entry.getKey(), oTag); } return mapTag; } /** * Add a tag to the map of tags. *

* This method performs any conversion of the tag name before adding it. * * @param mapTag the {@link Map} of tags to add to * @param sKey the tag key, which may be mutated before adding to the map * @param oValue the tag value */ private void addTag(Map mapTag, String sKey, Object oValue) { // Only create tags that have values that are not Coherence "not set" values, // such as null, -1 "n/a" etc. if (isValueSet(oValue)) { sKey = ensureStartsWithLowercase(sKey); String sValue = String.valueOf(oValue).replaceAll(QUOTE, ESC_QUOTE); if ("service".equals(sKey)) { // COH-19269: transform ObjectName key property "service" to "coherence_service" to avoid clash // with prometheus ServiceMonitor using metric label service. Prometheus ServiceMonitor was // renaming service to exported_service. sKey = "coherence_service"; } // If the tag map already contains the key add the "tag_" prefix // but only if the tag with the same name AND value doesn't already exist if (mapTag.containsKey(sKey) && !sValue.equals(mapTag.get(sKey))) { sKey = "tag_" + sKey; } mapTag.put(sKey, sValue); } } /** * Ensures that the first letter of a string is lower case. * * @param s string to check * * @return specified string, with a first character converted to * lower case, if necessary */ private static String ensureStartsWithLowercase(String s) { return Character.isLowerCase(s.charAt(0)) ? s : Character.toLowerCase(s.charAt(0)) + s.substring(1); } /** * Return true if oValue set to a non-default value. * Optimization to avoid returning metric tag or metric value for an unset value. * A null value is always considered not set. For {@link String}, n/a and * Not configured are considered not set. For {@link Long}, the value -1 * is considered not set. * * @param oValue MBean attribute value * * @return true if value set to a non-default value for its type */ private static boolean isValueSet(Object oValue) { if (oValue == null) { return false; } if (oValue instanceof Number) { return ((Number) oValue).longValue() != -1L; } else if (oValue instanceof String) { String sValue = (String) oValue; return sValue.length() > 0 && !sValue.equals("Not configured") && !sValue.equalsIgnoreCase("n/a"); } return true; } /** * Return the metric name. * * @param sMetricFieldName either "metric.tag" or "metric.value" * @param attrInfo MBean attribute information * * @return metrics.tag name from MBeanAttributeInfo @descriptor annotation. */ private static String getMetric(String sMetricFieldName, MBeanAttributeInfo attrInfo) { Descriptor descriptor = attrInfo.getDescriptor(); Object oMetricsFieldValue = descriptor == null ? null : descriptor.getFieldValue(sMetricFieldName); return oMetricsFieldValue instanceof String ? (String) oMetricsFieldValue : null; } /** * Initialise the static set of patterns to use to match platform MBeans * that should be used for metrics. * * @return the set of {@link ObjectName} patterns to use to match platform MBeans */ private static Set getPlatformPatterns() { try { Set set = new HashSet<>(); set.add(new ObjectName("*:type=Platform,Domain=java.lang,subType=Memory,*")); set.add(new ObjectName("*:type=Platform,Domain=java.lang,subType=OperatingSystem,*")); set.add(new ObjectName("*:type=Platform,Domain=java.lang,subType=GarbageCollector,*")); return set; } catch (MalformedObjectNameException e) { throw Base.ensureRuntimeException(e); } } /** * Create a map of {@link ObjectName} patterns to metric name prefixes. *

* This is used to figure out the prefix to use for a metric from a * given MBean, for example all metrics from MBeans matching the object * name "*:type=Node,*" are prefixed with "coherence_node_". * * @return a map of {@link ObjectName} patterns to metric name prefixes */ private static Map getMetricNamePrefixMap() { Map map = new HashMap<>(); map.put(createPattern(FEDERATION_ORIGIN_TYPE), "Coherence.Federation.Origin."); map.put(createPattern(FEDERATION_DESTINATION_TYPE), "Coherence.Federation.Destination."); map.put(createPattern(PLATFORM_MEMORY_TYPE), "Coherence.Memory."); map.put(createPattern(PLATFORM_OS_TYPE), "Coherence.OS."); map.put(createPattern(PLATFORM_GC), "Coherence.GC."); return map; } private static ObjectName createPattern(String sType) { try { return new ObjectName(String.format("*:%s,*", sType)); } catch (MalformedObjectNameException e) { throw Base.ensureRuntimeException(e); } } /** * Determine whether the {@link ObjectName} is a Coherence Platform MBean * * @param objectName the {@link ObjectName} to test * * @return {@code true} if the {@link ObjectName} is a Platform MBean */ private boolean isPlatformMBean(ObjectName objectName) { return ("Platform".equals(objectName.getKeyProperty("type")) && "java.lang".equals(objectName.getKeyProperty("Domain"))); } /** * Obtains the MemoryPoolMXBeans and creates after GC usage metrics for each * of them. This makes it much easier to capture these metrics than trying * to obtain them directly from the Coherence Platform MBeans. * * @param listRegistry the metric registries to register the metrics with */ private synchronized void ensureMemoryMetrics(List listRegistry) { if (f_fMemoryRegistered || listRegistry == null || listRegistry.isEmpty()) { return; } f_fMemoryRegistered = true; try { for (MemoryPoolMXBean bean : ManagementFactory.getMemoryPoolMXBeans()) { if (bean.getType() == MemoryType.HEAP) { String sMBeanName = bean.getObjectName().getCanonicalName(); String sDescr = "Memory pool heap usage after GC "; MBeanServerProxy proxy = f_suppMBeanServerProxy.get(); Map mapTagAttributes = new HashMap<>(); Map mapTag = createMetricTags(sMBeanName, proxy, bean.getObjectName(), mapTagAttributes); mapTag.put("name", bean.getName()); String sNameUsed = METRIC_PREFIX_HEAP_AFTER_GC + "Used"; Identifier idUsed = new Identifier(MBeanMetric.Scope.VENDOR, sNameUsed, mapTag); String sNameMax = METRIC_PREFIX_HEAP_AFTER_GC + "Max"; Identifier idMax = new Identifier(MBeanMetric.Scope.VENDOR, sNameMax, mapTag); String sNameCommitted = METRIC_PREFIX_HEAP_AFTER_GC + "Committed"; Identifier idCommitted = new Identifier(MBeanMetric.Scope.VENDOR, sNameCommitted, mapTag); String sNameInitial = METRIC_PREFIX_HEAP_AFTER_GC + "Initial"; Identifier idInit = new Identifier(MBeanMetric.Scope.VENDOR,sNameInitial, mapTag); MemoryAfterGCMetric metricUsed = new MemoryAfterGCMetric(idUsed, sDescr + "(used)", bean, MemoryUsage::getUsed); MemoryAfterGCMetric metricMax = new MemoryAfterGCMetric(idMax, sDescr + "(max)", bean, MemoryUsage::getMax); MemoryAfterGCMetric metricCommitted = new MemoryAfterGCMetric(idCommitted, sDescr + "(committed)", bean, MemoryUsage::getCommitted); MemoryAfterGCMetric metricInit = new MemoryAfterGCMetric(idInit, sDescr + "(initial)", bean, MemoryUsage::getInit); for (MetricsRegistryAdapter adapter : listRegistry) { try { adapter.register(metricUsed); adapter.register(metricMax); adapter.register(metricCommitted); adapter.register(metricInit); } catch (Throwable t) { CacheFactory.err("Failed to register MemoryPool metrics with adapter " + adapter); CacheFactory.err(t); } } } } } catch (Throwable t) { CacheFactory.err("Failed to register MemoryPool metrics"); CacheFactory.err(t); } } // ----- inner class: BaseMetric ---------------------------------------- /** * A base class for a {@link MBeanMetric} that wraps * a Coherence MBean attribute. */ abstract class BaseMetric extends BaseMBeanMetric { BaseMetric(Identifier identifier, String sMBeanName, String sDescription) { super(identifier, sMBeanName, sDescription); } // ----- BaseMetric methods ----------------------------------------- /** * Obtain the MBean attribute's current value. * * @return the current value of the MBean attribute */ abstract Object getAttributeValue(); // ----- CoherenceMetrics methods ----------------------------------- @Override public Object getValue() { try { Object oValue = getAttributeValue(); // special case handling: // transform of Coherence MBean attribute of type Date to Prometheus metric timestamp. // The timestamp is an int64 (milliseconds since epoch, i.e. 1970-01-01 00:00:00 UTC, // excluding leap seconds). if (oValue instanceof Date) { oValue = ((Date) oValue).getTime(); } return MetricSupport.isValueSet(oValue) ? oValue : null; } catch (IllegalArgumentException e) { // thrown if MBean no longer exists try { remove(getMBeanName()); } catch (Exception ex) { // ignored } return null; } catch (Exception e) { CacheFactory.err("Caught exception getting metric value for " + getIdentifier()); CacheFactory.err(e); return null; } } } // ----- inner class: SimpleMetric -------------------------------------- /** * An implementation of a {@link MBeanMetric} that wraps * a simple MBean attribute. */ class SimpleMetric extends BaseMetric { /** * Create a metric that wraps an attribute of a MBean. * * @param identifier the metric {@link MBeanMetric.Identifier} * @param sMbean the MBean name for this metric * @param sAttribute the name of the MBean attribute represented by this metric * @param sDescription the description of this metric * @param supplier the supplier of the {@link MBeanServerProxy} to use to obtain * this metric's value */ SimpleMetric(Identifier identifier, String sMbean, String sAttribute, String sDescription, Supplier supplier) { super(identifier, sMbean, sDescription); f_sAttribute = sAttribute; f_suppMBeanServerProxy = supplier; } // ----- BaseMetric methods ----------------------------------------- @Override Object getAttributeValue() { return f_suppMBeanServerProxy.get().getAttribute(getMBeanName(), f_sAttribute); } // ----- data members ----------------------------------------------- /** * The supplier to use to obtain an {@link MBeanServerProxy}. */ private final Supplier f_suppMBeanServerProxy; /** * The MBean attribute name. */ private final String f_sAttribute; } // ----- inner class: CompositeMetric ----------------------------------- /** * An implementation of a {@link MBeanMetric} that wraps * a MBean composite data attribute. */ class CompositeMetric extends BaseMetric { /** * Create a metric that wraps an composite attribute of a MBean. * * @param identifier the metric {@link MBeanMetric.Identifier} * @param sMbean the MBean name for this metric * @param supplierParent the function to use to obtain the parent {@link CompositeData} * @param sDescription the description of this metric * @param supplier the supplier of the {@link MBeanServerProxy} to use to obtain * this metric's value */ CompositeMetric(Identifier identifier, String sMbean, Function supplierParent, String sKey, String sDescription, Supplier supplier) { super(identifier, sMbean, sDescription); f_supplierParent = supplierParent; f_sKey = sKey; f_suppMBeanServerProxy = supplier; } // ----- BaseMetric methods ----------------------------------------- @Override Object getAttributeValue() { CompositeData data = f_supplierParent.apply(f_suppMBeanServerProxy.get()); return data == null ? null : data.get(f_sKey); } // ----- data members ----------------------------------------------- /** * The supplier to use to obtain an {@link MBeanServerProxy}. */ private final Supplier f_suppMBeanServerProxy; /** * The supplier of the parent attribute. */ private final Function f_supplierParent; /** * The key of the value in the {@link CompositeData} that this metric represents. */ private String f_sKey; } // ----- inner class: MemoryCollectionUsageMetric ----------------------- class MemoryAfterGCMetric extends BaseMetric { public MemoryAfterGCMetric(Identifier id, String sDescr, MemoryPoolMXBean bean, Function fn) { super(id, bean.getObjectName().getCanonicalName(), sDescr); f_bean = bean; f_function = fn; } @Override Object getAttributeValue() { MemoryUsage usage = f_bean.getCollectionUsage(); return usage == null ? null : f_function.apply(usage); } private final MemoryPoolMXBean f_bean; private final Function f_function; } // ----- constants ------------------------------------------------------ /** * Filter out MBean attributes that do not map to a metric's tag or value. */ private static final MetricsMBeanAttributeFilter ATTRIBUTE_FILTER = new MetricsMBeanAttributeFilter(); /** * MBean object name query for java.lang domain of type Platform for subType Memory. */ private static final String PLATFORM_MEMORY_TYPE = "type=Platform,Domain=java.lang,subType=Memory"; /** * MBean object name query for java.lang domain of type Platform for subType OperatingSystem. */ private static final String PLATFORM_OS_TYPE = "type=Platform,Domain=java.lang,subType=OperatingSystem"; /** * MBean object name query for java.lang domain of type Platform for subType GarbageCollection. */ private static final String PLATFORM_GC = "type=Platform,Domain=java.lang,subType=GarbageCollector"; /** * The prefix for heap after GC metric names. */ private static final String METRIC_PREFIX_HEAP_AFTER_GC = "Coherence.Memory.HeapAfterGC."; /** * MBean object name query for Federation OriginMBean. */ private static final String FEDERATION_ORIGIN_TYPE = Registry.FEDERATION_TYPE + ",subType=Origin"; /** * MBean object name query for Federation DestinationMBean. */ private static final String FEDERATION_DESTINATION_TYPE = Registry.FEDERATION_TYPE + ",subType=Destination"; /** * ObjectName key properties to exclude as metrics labels. * Most keys were redundant since the metrics value name contains this info. * UUID was excluded due to large size. */ public static final Set OBJECT_NAME_TAG_EXCLUDES = new HashSet<>(Arrays.asList("type","subType","responsibility", "UUID", "Domain")); /** * A list of attribute types that may be used as tags. */ private static final List LIST_TAG_TYPES = Arrays.asList("String", "boolean", "java.lang.String", "java.lang.Boolean"); /** * A set of attribute types that may be used as values. */ private static final Set METRIC_ATTRIBUTE_TYPES = new HashSet<>(Arrays.asList( "long", "int", "double", "float", Long.class.getName(), Integer.class.getName(), Double.class.getName(), Float.class.getName(), BigDecimal.class.getName(), BigInteger.class.getName() )); /** * A set of attribute names never used as metric values. */ private static final List LIST_SKIP_ATTRIBUTES = Arrays.asList( //"LastGcInfo", // skip overly verbose tabular data for LastGcInfo BeforeGc and AfterGc. "ObjectName", // skip MBean attribute ObjectName "Verbose", "Valid" ); private static final String REGEX_TAG = "[^a-zA-Z0-9]"; private static final String UNDER_SCORE = "_"; private static final String ESC_QUOTE = "\\\\\""; private static final String QUOTE = "\""; /** * The {@link ObjectName} patterns used to recognise Coherence Platform MBeans. */ private static final Set f_setPlatfromMBeans = getPlatformPatterns(); /** * A map of {@link ObjectName} patterns to metric name prefixes. */ private static final Map s_mapMetricPrefix = getMetricNamePrefixMap(); /** * The tag key used for the cluster name on all metrics. */ public static final String GLOBAL_TAG_CLUSTER = "cluster"; /** * The tag key used for the site name on all metrics. */ public static final String GLOBAL_TAG_SITE = "site"; /** * The tag key used for the machine name on all metrics. */ public static final String GLOBAL_TAG_MACHINE = "machine"; /** * The tag key used for the member name on all metrics. */ public static final String GLOBAL_TAG_MEMBER = "member"; /** * The tag key used for the role name on all metrics. */ public static final String GLOBAL_TAG_ROLE = "role"; // ----- data members --------------------------------------------------- /** * The {@link List} of {@link MetricsRegistryAdapter} to register metrics with. */ private final List f_listRegistry; /** * A flag indicating whether there are any {@link MetricsRegistryAdapter} instances. */ private final boolean f_fHasRegistries; /** * A {@link Map} of {@link ObjectName} to the metrics registered for that name. */ private final Map> f_mapMetric; /** * The supplier to use to obtain an {@link MBeanServerProxy}. */ private final Supplier f_suppMBeanServerProxy; /** * The supplier to use to obtain a {@link Registry}. */ private final Supplier f_suppRegistry; /** * The currently registered MBeans. */ private final Set f_setRegistered = new HashSet<>(); /** * Flag indicating whether memory metrics have been registered. */ private boolean f_fMemoryRegistered; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy