PageRenderTime 26ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/atlassian-micrometer/src/main/java/com/atlassian/util/profiling/micrometer/util/QualifiedCompatibleHierarchicalNameMapper.java

https://bitbucket.org/atlassian/atlassian-profiling
Java | 171 lines | 126 code | 18 blank | 27 comment | 3 complexity | 2be8a61fcd5b0bb9658392695c78eded MD5 | raw file
  1. package com.atlassian.util.profiling.micrometer.util;
  2. import com.atlassian.annotations.VisibleForTesting;
  3. import io.micrometer.core.instrument.Meter;
  4. import io.micrometer.core.instrument.Statistic;
  5. import io.micrometer.core.instrument.Tag;
  6. import io.micrometer.core.instrument.config.NamingConvention;
  7. import io.micrometer.core.instrument.util.HierarchicalNameMapper;
  8. import javax.annotation.Nonnull;
  9. import java.util.ArrayList;
  10. import java.util.Arrays;
  11. import java.util.List;
  12. import java.util.Objects;
  13. import java.util.Optional;
  14. import java.util.stream.Collectors;
  15. import static java.util.Objects.requireNonNull;
  16. /**
  17. * Compatible with Java qualified names in metric tag keys and values. Will split a metric name into categories on '.'
  18. * <p>
  19. * e.g.
  20. * <pre>{@code
  21. * try (Ticker ignored = Metrics.startTimer("db.ao.executeInTransaction",
  22. * MetricTag.of("pluginKey", "com.atlassian.audit")) {
  23. * // measured code here
  24. * }
  25. * }</pre>
  26. * would appear as follows in JConsole:
  27. * <pre>{@code
  28. * + com.atlassian.refapp
  29. * + metrics
  30. * + pluginKey
  31. * + com.atlassian.audit
  32. * + db
  33. * + ao
  34. * executeInTransaction
  35. * }</pre>
  36. * <p>
  37. * <strong>Note:</strong> should be used with {@link UnescapedObjectNameFactory}
  38. *
  39. * @since 3.5.0
  40. */
  41. public class QualifiedCompatibleHierarchicalNameMapper implements HierarchicalNameMapper {
  42. @VisibleForTesting
  43. static final String CATEGORY_BASE_KEY = "category";
  44. @VisibleForTesting
  45. static final String NAME_KEY = "name";
  46. @VisibleForTesting
  47. static final String TAG_KEY_KEY = "tagKey";
  48. @VisibleForTesting
  49. static final String TAG_VALUE_KEY = "tagValue";
  50. @VisibleForTesting
  51. static final String TYPE_KEY = "type";
  52. @VisibleForTesting
  53. static final String METRICS_PROPERTY = "metrics";
  54. @VisibleForTesting
  55. static final String KEY_PROPERTY_PAIR_DELIMITER = ",";
  56. @VisibleForTesting
  57. static final String NAME_GROUPING_DELIMITER = ".";
  58. @VisibleForTesting
  59. static final String STATISTIC_TAG_KEY = "statistic";
  60. static final String CURRENT = "current";
  61. private static final String KEY_PROPERTY_SEPARATOR = "=";
  62. @VisibleForTesting
  63. static final int STARTING_COUNT = 0;
  64. @Nonnull
  65. @Override
  66. public String toHierarchicalName(
  67. @Nonnull final Meter.Id meterId,
  68. @Nonnull final NamingConvention namingConvention
  69. ) {
  70. requireNonNull(meterId, "meterId");
  71. requireNonNull(namingConvention, "namingConvention");
  72. final List<Tag> tags = new ArrayList<>(meterId.getConventionTags(namingConvention));
  73. final StringBuilder metricName = new StringBuilder(meterId.getConventionName(NamingConvention.identity));
  74. // -- Moves the statistic tag (that is rudely inserted by micrometer in the middle of them) into the metric name
  75. final Optional<Tag> statisticTag = findInsertedStatisticTag(tags);
  76. if (statisticTag.isPresent()) {
  77. tags.remove(statisticTag.get());
  78. metricName.append(NAME_GROUPING_DELIMITER).append(statisticTag.get().getValue());
  79. }
  80. // -- End of moving the tag into the name
  81. final String hierarchicalName = buildTagsString(tags)
  82. + buildCategoriesAndNameString(metricName.toString());
  83. final String safeHierarchicalName = hierarchicalName
  84. .replaceAll(" ", "_")
  85. .replaceAll(":", "_");
  86. return safeHierarchicalName;
  87. }
  88. public static String buildSiblingLongRunningTimerMetricName(final String metricName) {
  89. return metricName + NAME_GROUPING_DELIMITER + CURRENT;
  90. }
  91. private static Optional<Tag> findInsertedStatisticTag(final List<Tag> tags) {
  92. return tags
  93. .stream()
  94. .filter(tag -> STATISTIC_TAG_KEY.equals(tag.getKey())
  95. && Arrays
  96. .stream(Statistic.class.getEnumConstants())
  97. .map(Statistic::getTagValueRepresentation)
  98. .anyMatch(statisticTagValue -> Objects.equals(tag.getValue(), statisticTagValue)))
  99. .findFirst();
  100. }
  101. private static String buildTagsString(List<Tag> tags) {
  102. StringBuilder tagStringBuilder = new StringBuilder();
  103. int tagCount = STARTING_COUNT;
  104. for (Tag tag : tags) {
  105. tagStringBuilder
  106. .append(buildNumberedKeyProperty(TAG_KEY_KEY, tag.getKey(), tagCount))
  107. .append(buildNumberedKeyProperty(TAG_VALUE_KEY, tag.getValue(), tagCount));
  108. tagCount++;
  109. }
  110. return tagStringBuilder.toString();
  111. }
  112. private static String buildCategoriesAndNameString(String metricName) {
  113. final List<String> jmxCategories = Arrays.stream(metricName.split("\\" + NAME_GROUPING_DELIMITER)).collect(Collectors.toList());
  114. final String jmxName = jmxCategories.remove(jmxCategories.size() - 1);
  115. final StringBuilder jmxStringBuilder = new StringBuilder();
  116. jmxStringBuilder.append(buildIntermediateKeyProperty(TYPE_KEY, METRICS_PROPERTY));
  117. int categoriesCount = STARTING_COUNT;
  118. for (String category : jmxCategories) {
  119. jmxStringBuilder.append(buildNumberedKeyProperty(CATEGORY_BASE_KEY, category, categoriesCount));
  120. categoriesCount++;
  121. }
  122. jmxStringBuilder.append(buildKeyProperty(NAME_KEY, jmxName));
  123. return jmxStringBuilder.toString();
  124. }
  125. /**
  126. * Pads an integer with a zero, so it's at least a two character string.
  127. * <p>
  128. * Saves a dependency on apache commons
  129. *
  130. * @param integerToPad integer to be padded
  131. * @return a string with the padding
  132. */
  133. private static String twoDigitMinimumLeftPad(int integerToPad) {
  134. return String.format("%02d", integerToPad);
  135. }
  136. @VisibleForTesting
  137. static String buildNumberedKeyProperty(final String key, final String value, final int number) {
  138. return buildIntermediateKeyProperty(key + twoDigitMinimumLeftPad(number), value);
  139. }
  140. private static String buildIntermediateKeyProperty(final String key, final String value) {
  141. return buildKeyProperty(key, value) + KEY_PROPERTY_PAIR_DELIMITER;
  142. }
  143. @VisibleForTesting
  144. static String buildKeyProperty(final String key, final String value) {
  145. return key + KEY_PROPERTY_SEPARATOR + value;
  146. }
  147. }