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

com.couchbase.connect.kafka.util.JmxHelper Maven / Gradle / Ivy

/*
 * Copyright 2020 Couchbase, Inc.
 *
 * 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.
 */

package com.couchbase.connect.kafka.util;

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.jmx.JmxReporter;
import com.codahale.metrics.jmx.ObjectNameFactory;
import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.util.HierarchicalNameMapper;
import io.micrometer.jmx.JmxConfig;
import io.micrometer.jmx.JmxMeterRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

import static com.couchbase.client.core.util.CbStrings.removeStart;

/**
 * Utilities for integrating Micrometer with JMX.
 * 

* As much as possible, follows * * Java Management Extensions (JMX) Best Practices. *

* Specifically: *

    *
  • All objects of the same "type" have the same set of attributes. *
  • If it's possible for a key property to have a value that requires * escaping, then *all* values of that property should be escaped. *
*

* These utilities expose the tags of multi-dimensional metrics as JMX key properties. * The intent is to let users query the JMX objects in much the same way you might filter * the metrics across different dimensions in a more sophisticated monitoring system. */ public class JmxHelper { private static final Logger LOGGER = LoggerFactory.getLogger(JmxHelper.class); private JmxHelper() { throw new AssertionError("not instantiable"); } public static JmxMeterRegistry newJmxMeterRegistry(String domain, LinkedHashMap commonKeyProperties) { MetricRegistry registry = new MetricRegistry(); String namePrefix = domain + ":" + format(commonKeyProperties); JmxReporter reporter = JmxReporter.forRegistry(registry) .createsObjectNamesWith(preformattedObjectNameFactory(namePrefix)) .build(); return new JmxMeterRegistry(JmxConfig.DEFAULT, Clock.SYSTEM, jmxNameMapper("dcp"), registry, reporter); } /** * Returns a name mapper that converts a meter ID (meter type, name and tags) * into a preformatted list of JMX key properties, suitable for direct inclusion in * a JMX ObjectName. * * @param groups list of metric name prefixes that should be interpreted as "groups" */ private static HierarchicalNameMapper jmxNameMapper(String... groups) { return (id, convention) -> { String canonicalName = id.getType().name().toLowerCase(Locale.ROOT).replace("_", "."); String type = convention.name(canonicalName, id.getType()); LinkedHashMap keyProperties = new LinkedHashMap<>(); keyProperties.put("type", type); String name = id.getName(); for (String group : groups) { String prefix = group + "."; if (id.getName().startsWith(prefix)) { name = removeStart(name, prefix); keyProperties.put("group", group); break; } } keyProperties.put("name", convention.name(name, id.getType())); // JMX object names aren't *really* hierarchical... but some browsers display them that way. // Give those browsers a hint about the best hierarchy. LinkedHashMap orderedTags = toMapWithOrderedKeys(id.getTagsAsIterable(), "remote", "opcode", "result", "exception"); // Remove useless ActionCounter tags; JMX doesn't care if // two metrics with the same name have different tag names. if ("success".equals(orderedTags.get("result"))) { orderedTags.remove("exception"); // always "none", and clutters JMX hierarchy } for (Map.Entry tag : orderedTags.entrySet()) { keyProperties.put( convention.name(tag.getKey(), id.getType()), ObjectName.quote(tag.getValue())); } return format(keyProperties); }; } private static String format(Map keyProperties) { // Assumes values that require quoting have already been quoted. return keyProperties.entrySet().stream() .map(entry -> entry.getKey() + "=" + entry.getValue()) .collect(Collectors.joining(",")); } private static LinkedHashMap toMapWithOrderedKeys(Iterable tags, String... orderedKeys) { LinkedHashMap tagMap = new LinkedHashMap<>(); for (Tag tag : tags) { tagMap.put(tag.getKey(), tag.getValue()); } return withKeysInOrder(tagMap, orderedKeys); } @SafeVarargs private static LinkedHashMap withKeysInOrder(Map map, K... orderedKeys) { LinkedHashMap temp = new LinkedHashMap<>(map); LinkedHashMap result = new LinkedHashMap<>(); for (K key : orderedKeys) { V value = temp.remove(key); if (value != null) { result.put(key, value); } } result.putAll(temp); return result; } /** * Returns a name factory that assumes metric names are well-formed JMX key property lists. * Intended for use with {@link #jmxNameMapper} * * @param objectNamePrefix the JMX domain, followed by a colon, optionally followed by * a JMX key property list to be prepended to all object names. */ private static ObjectNameFactory preformattedObjectNameFactory(String objectNamePrefix) { return (type, domain, name) -> { try { return ObjectName.getInstance(objectNamePrefix + "," + name); } catch (MalformedObjectNameException e) { LOGGER.error("failed to create ObjectName for metric '{}'", name); throw new RuntimeException(e); } }; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy