// ================================================================================================= // Copyright 2013 Twitter, Inc. // ------------------------------------------------------------------------------------------------- // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this work except in compliance with the License. // You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.metrics; import java.util.Map; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.twitter.jsr166e.LongAdder; /** * Root metric registry. */ public final class Metrics implements MetricRegistry, MetricProvider { private static final Metrics ROOT = new Metrics(); private final Map<String, Gauge<?>> gauges = Maps.newConcurrentMap(); private final Map<String, LongAdder> counters = Maps.newConcurrentMap(); private final Map<String, HistogramInterface> histograms = Maps.newConcurrentMap(); private Metrics() { } private void checkNameCollision(String key) { if (gauges.containsKey(key) || counters.containsKey(key) || counters.containsKey(key)) { throw new MetricCollisionException( "A metric with the name " + key + " has already been defined"); } } /** * Create a new Metrics detached from the static root. * * @return the detached metric registry. */ public static Metrics createDetached() { return new Metrics(); } /** * Returns a handle to the root metric registry. * * @return Root metric registry. */ public static Metrics root() { return ROOT; } @Override public MetricRegistry scope(String name) { return new ScopedRegistry(this, name); } @Override public <T extends Number> void register(Gauge<T> gauge) { registerGauge(gauge); } @Override public synchronized <T extends Number> Gauge<T> registerGauge(Gauge<T> gauge) { String key = gauge.getName(); checkNameCollision(key); gauges.put(key, gauge); return gauge; } @Override public synchronized boolean unregister(Gauge<?> gauge) { String key = gauge.getName(); return gauges.remove(key) != null; } @Override public Counter registerCounter(String name) { return createCounter(name); } @Override public synchronized Counter createCounter(final String name) { final LongAdder adder = new LongAdder(); checkNameCollision(name); counters.put(name, adder); return new Counter() { public String getName() { return name; } public void increment() { adder.increment(); } public void add(long n) { adder.add(n); } }; } @Override public synchronized boolean unregister(Counter counter) { String key = counter.getName(); return counters.remove(key) != null; } @Override public HistogramInterface createHistogram(String name) { return registerHistogram(new Histogram(name)); } @Override public synchronized HistogramInterface registerHistogram(HistogramInterface histogram) { String key = histogram.getName(); checkNameCollision(key); histograms.put(key, histogram); return histogram; } @Override public synchronized boolean unregister(HistogramInterface histogram) { String key = histogram.getName(); return histograms.remove(key) != null; } @Override public synchronized boolean unregister(String metricName) { return gauges.remove(metricName) != null || counters.remove(metricName) != null || histograms.remove(metricName) != null; } @Override public Map<String, Number> sampleGauges() { ImmutableMap.Builder<String, Number> samples = ImmutableMap.builder(); for (Map.Entry<String, Gauge<?>> metric : gauges.entrySet()) { Gauge<?> gauge = metric.getValue(); samples.put(metric.getKey(), gauge.read()); } return samples.build(); } @Override public Map<String, Number> sampleCounters() { ImmutableMap.Builder<String, Number> samples = ImmutableMap.builder(); for (Map.Entry<String, LongAdder> metric : counters.entrySet()) { samples.put(metric.getKey(), metric.getValue().sum()); } return samples.build(); } @Override public Map<String, Snapshot> sampleHistograms() { ImmutableMap.Builder<String, Snapshot> samples = ImmutableMap.builder(); for (HistogramInterface h: histograms.values()) { Snapshot snapshot = h.snapshot(); samples.put(h.getName(), snapshot); } return samples.build(); } @Override public Map<String, Number> sample() { ImmutableMap.Builder<String, Number> samples = ImmutableMap.builder(); samples.putAll(sampleGauges()); samples.putAll(sampleCounters()); for (Map.Entry<String, Snapshot> entry : sampleHistograms().entrySet()) { String name = entry.getKey(); Snapshot snapshot = entry.getValue(); samples.put(named(name, "count"), snapshot.count()); samples.put(named(name, "sum"), snapshot.sum()); samples.put(named(name, "avg"), snapshot.avg()); samples.put(named(name, "min"), snapshot.min()); samples.put(named(name, "max"), snapshot.max()); samples.put(named(name, "stddev"), snapshot.stddev()); for (Percentile p: snapshot.percentiles()) { String percentileName = named(name, gaugeName(p.getQuantile())); samples.put(percentileName, p.getValue()); } } return samples.build(); } private static String named(String histogramName, String statName) { return histogramName + ScopedRegistry.DEFAULT_SCOPE_DELIMITER + statName; } private static String gaugeName(double quantile) { String gname = "p" + (int) (quantile * 10000); if (3 < gname.length() && "00".equals(gname.substring(3))) { gname = gname.substring(0, 3); } return gname; } }