/atlassian-micrometer/src/main/java/com/atlassian/util/profiling/micrometer/util/QualifiedCompatibleHierarchicalNameMapper.java
Java | 171 lines | 126 code | 18 blank | 27 comment | 3 complexity | 2be8a61fcd5b0bb9658392695c78eded MD5 | raw file
- package com.atlassian.util.profiling.micrometer.util;
- import com.atlassian.annotations.VisibleForTesting;
- import io.micrometer.core.instrument.Meter;
- import io.micrometer.core.instrument.Statistic;
- import io.micrometer.core.instrument.Tag;
- import io.micrometer.core.instrument.config.NamingConvention;
- import io.micrometer.core.instrument.util.HierarchicalNameMapper;
- import javax.annotation.Nonnull;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.List;
- import java.util.Objects;
- import java.util.Optional;
- import java.util.stream.Collectors;
- import static java.util.Objects.requireNonNull;
- /**
- * Compatible with Java qualified names in metric tag keys and values. Will split a metric name into categories on '.'
- * <p>
- * e.g.
- * <pre>{@code
- * try (Ticker ignored = Metrics.startTimer("db.ao.executeInTransaction",
- * MetricTag.of("pluginKey", "com.atlassian.audit")) {
- * // measured code here
- * }
- * }</pre>
- * would appear as follows in JConsole:
- * <pre>{@code
- * + com.atlassian.refapp
- * + metrics
- * + pluginKey
- * + com.atlassian.audit
- * + db
- * + ao
- * executeInTransaction
- * }</pre>
- * <p>
- * <strong>Note:</strong> should be used with {@link UnescapedObjectNameFactory}
- *
- * @since 3.5.0
- */
- public class QualifiedCompatibleHierarchicalNameMapper implements HierarchicalNameMapper {
- @VisibleForTesting
- static final String CATEGORY_BASE_KEY = "category";
- @VisibleForTesting
- static final String NAME_KEY = "name";
- @VisibleForTesting
- static final String TAG_KEY_KEY = "tagKey";
- @VisibleForTesting
- static final String TAG_VALUE_KEY = "tagValue";
- @VisibleForTesting
- static final String TYPE_KEY = "type";
- @VisibleForTesting
- static final String METRICS_PROPERTY = "metrics";
- @VisibleForTesting
- static final String KEY_PROPERTY_PAIR_DELIMITER = ",";
- @VisibleForTesting
- static final String NAME_GROUPING_DELIMITER = ".";
- @VisibleForTesting
- static final String STATISTIC_TAG_KEY = "statistic";
- static final String CURRENT = "current";
- private static final String KEY_PROPERTY_SEPARATOR = "=";
- @VisibleForTesting
- static final int STARTING_COUNT = 0;
- @Nonnull
- @Override
- public String toHierarchicalName(
- @Nonnull final Meter.Id meterId,
- @Nonnull final NamingConvention namingConvention
- ) {
- requireNonNull(meterId, "meterId");
- requireNonNull(namingConvention, "namingConvention");
- final List<Tag> tags = new ArrayList<>(meterId.getConventionTags(namingConvention));
- final StringBuilder metricName = new StringBuilder(meterId.getConventionName(NamingConvention.identity));
- // -- Moves the statistic tag (that is rudely inserted by micrometer in the middle of them) into the metric name
- final Optional<Tag> statisticTag = findInsertedStatisticTag(tags);
- if (statisticTag.isPresent()) {
- tags.remove(statisticTag.get());
- metricName.append(NAME_GROUPING_DELIMITER).append(statisticTag.get().getValue());
- }
- // -- End of moving the tag into the name
- final String hierarchicalName = buildTagsString(tags)
- + buildCategoriesAndNameString(metricName.toString());
- final String safeHierarchicalName = hierarchicalName
- .replaceAll(" ", "_")
- .replaceAll(":", "_");
- return safeHierarchicalName;
- }
- public static String buildSiblingLongRunningTimerMetricName(final String metricName) {
- return metricName + NAME_GROUPING_DELIMITER + CURRENT;
- }
- private static Optional<Tag> findInsertedStatisticTag(final List<Tag> tags) {
- return tags
- .stream()
- .filter(tag -> STATISTIC_TAG_KEY.equals(tag.getKey())
- && Arrays
- .stream(Statistic.class.getEnumConstants())
- .map(Statistic::getTagValueRepresentation)
- .anyMatch(statisticTagValue -> Objects.equals(tag.getValue(), statisticTagValue)))
- .findFirst();
- }
- private static String buildTagsString(List<Tag> tags) {
- StringBuilder tagStringBuilder = new StringBuilder();
- int tagCount = STARTING_COUNT;
- for (Tag tag : tags) {
- tagStringBuilder
- .append(buildNumberedKeyProperty(TAG_KEY_KEY, tag.getKey(), tagCount))
- .append(buildNumberedKeyProperty(TAG_VALUE_KEY, tag.getValue(), tagCount));
- tagCount++;
- }
- return tagStringBuilder.toString();
- }
- private static String buildCategoriesAndNameString(String metricName) {
- final List<String> jmxCategories = Arrays.stream(metricName.split("\\" + NAME_GROUPING_DELIMITER)).collect(Collectors.toList());
- final String jmxName = jmxCategories.remove(jmxCategories.size() - 1);
- final StringBuilder jmxStringBuilder = new StringBuilder();
- jmxStringBuilder.append(buildIntermediateKeyProperty(TYPE_KEY, METRICS_PROPERTY));
- int categoriesCount = STARTING_COUNT;
- for (String category : jmxCategories) {
- jmxStringBuilder.append(buildNumberedKeyProperty(CATEGORY_BASE_KEY, category, categoriesCount));
- categoriesCount++;
- }
- jmxStringBuilder.append(buildKeyProperty(NAME_KEY, jmxName));
- return jmxStringBuilder.toString();
- }
- /**
- * Pads an integer with a zero, so it's at least a two character string.
- * <p>
- * Saves a dependency on apache commons
- *
- * @param integerToPad integer to be padded
- * @return a string with the padding
- */
- private static String twoDigitMinimumLeftPad(int integerToPad) {
- return String.format("%02d", integerToPad);
- }
- @VisibleForTesting
- static String buildNumberedKeyProperty(final String key, final String value, final int number) {
- return buildIntermediateKeyProperty(key + twoDigitMinimumLeftPad(number), value);
- }
- private static String buildIntermediateKeyProperty(final String key, final String value) {
- return buildKeyProperty(key, value) + KEY_PROPERTY_PAIR_DELIMITER;
- }
- @VisibleForTesting
- static String buildKeyProperty(final String key, final String value) {
- return key + KEY_PROPERTY_SEPARATOR + value;
- }
- }