package io.dropwizard.metrics; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TreeSet; /** * A metric name with the ability to include semantic tags. * * This replaces the previous style where metric names where strictly * dot-separated strings. * * @author udoprog */ public class MetricName implements Comparable<MetricName> { public static final String SEPARATOR = "."; public static final Map<String, String> EMPTY_TAGS = Collections.unmodifiableMap(new HashMap<String, String>()); public static final MetricName EMPTY = new MetricName(); private final String key; private final Map<String, String> tags; public MetricName() { this.key = null; this.tags = EMPTY_TAGS; } public MetricName(String key) { this.key = key; this.tags = EMPTY_TAGS; } public MetricName(String key, Map<String, String> tags) { this.key = key; this.tags = checkTags(tags); } private Map<String, String> checkTags(Map<String, String> tags) { if (tags == null || tags.isEmpty()) { return EMPTY_TAGS; } return Collections.unmodifiableMap(tags); } public String getKey() { return key; } public Map<String, String> getTags() { return tags; } /** * Build the MetricName that is this with another path appended to it. * * The new MetricName inherits the tags of this one. * * @param p The extra path element to add to the new metric. * @return A new metric name relative to the original by the path specified * in p. */ public MetricName resolve(String p) { final String next; if (p != null && !p.isEmpty()) { if (key != null && !key.isEmpty()) { next = key + SEPARATOR + p; } else { next = p; } } else { next = this.key; } return new MetricName(next, tags); } /** * Add tags to a metric name and return the newly created MetricName. * * @param add Tags to add. * @return A newly created metric name with the specified tags associated with it. */ public MetricName tagged(Map<String, String> add) { final Map<String, String> tags = new HashMap<>(add); tags.putAll(this.tags); return new MetricName(key, tags); } /** * Same as {@link #tagged(Map)}, but takes a variadic list * of arguments. * * @see #tagged(Map) * @param pairs An even list of strings acting as key-value pairs. * @return A newly created metric name with the specified tags associated * with it. */ public MetricName tagged(String... pairs) { if (pairs == null) { return this; } if (pairs.length % 2 != 0) { throw new IllegalArgumentException("Argument count must be even"); } final Map<String, String> add = new HashMap<>(); for (int i = 0; i < pairs.length; i += 2) { add.put(pairs[i], pairs[i+1]); } return tagged(add); } /** * Join the specified set of metric names. * * @param parts Multiple metric names to join using the separator. * @return A newly created metric name which has the name of the specified * parts and includes all tags of all child metric names. **/ public static MetricName join(MetricName... parts) { final StringBuilder nameBuilder = new StringBuilder(); final Map<String, String> tags = new HashMap<>(); boolean first = true; for (MetricName part : parts) { final String name = part.getKey(); if (name != null && !name.isEmpty()) { if (first) { first = false; } else { nameBuilder.append(SEPARATOR); } nameBuilder.append(name); } if (!part.getTags().isEmpty()) tags.putAll(part.getTags()); } return new MetricName(nameBuilder.toString(), tags); } /** * Build a new metric name using the specific path components. * * @param parts Path of the new metric name. * @return A newly created metric name with the specified path. **/ public static MetricName build(String... parts) { if (parts == null || parts.length == 0) return MetricName.EMPTY; if (parts.length == 1) return new MetricName(parts[0], EMPTY_TAGS); return new MetricName(buildName(parts), EMPTY_TAGS); } private static String buildName(String... names) { final StringBuilder builder = new StringBuilder(); boolean first = true; for (String name : names) { if (name == null || name.isEmpty()) continue; if (first) { first = false; } else { builder.append(SEPARATOR); } builder.append(name); } return builder.toString(); } @Override public String toString() { if (tags.isEmpty()) { return key; //return key + "{}"; } return key + tags; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((key == null) ? 0 : key.hashCode()); result = prime * result + ((tags == null) ? 0 : tags.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; MetricName other = (MetricName) obj; if (key == null) { if (other.key != null) return false; } else if (!key.equals(other.key)) return false; if (!tags.equals(other.tags)) return false; return true; } @Override public int compareTo(MetricName o) { if (o == null) return -1; int c = compareName(key, o.getKey()); if (c != 0) return c; return compareTags(tags, o.getTags()); } private int compareName(String left, String right) { if (left == null && right == null) return 0; if (left == null) return 1; if (right == null) return -1; return left.compareTo(right); } private int compareTags(Map<String, String> left, Map<String, String> right) { if (left == null && right == null) return 0; if (left == null) return 1; if (right == null) return -1; final Iterable<String> keys = uniqueSortedKeys(left, right); for (final String key : keys) { final String a = left.get(key); final String b = right.get(key); if (a == null && b == null) continue; if (a == null) return -1; if (b == null) return 1; int c = a.compareTo(b); if (c != 0) return c; } return 0; } private Iterable<String> uniqueSortedKeys(Map<String, String> left, Map<String, String> right) { final Set<String> set = new TreeSet<>(left.keySet()); set.addAll(right.keySet()); return set; } }