/** * Copyright (C) 2014-2016 LinkedIn Corp. (pinot-core@linkedin.com) * * 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.linkedin.pinot.common.metrics; import com.yammer.metrics.core.Timer; import java.lang.reflect.Constructor; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.TimeUnit; import com.yammer.metrics.Metrics; import com.yammer.metrics.core.Counter; import com.yammer.metrics.core.Gauge; import com.yammer.metrics.core.Histogram; import com.yammer.metrics.core.Meter; import com.yammer.metrics.core.Metered; import com.yammer.metrics.core.MetricName; import com.yammer.metrics.core.MetricsRegistry; import com.yammer.metrics.core.Sampling; import com.yammer.metrics.core.Stoppable; import org.apache.commons.configuration.Configuration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MetricsHelper { private static final Logger LOGGER = LoggerFactory.getLogger(MetricsHelper.class); private static Map<MetricsRegistry, Object> metricsRegistryMap = new WeakHashMap<MetricsRegistry, Object>(); private static Map<MetricsRegistryRegistrationListener, Object> metricsRegistryRegistrationListenersMap = new WeakHashMap<MetricsRegistryRegistrationListener, Object>(); /** * Initializes the metrics system by initializing the registry registration listeners present in the configuration. * * @param configuration The subset of the configuration containing the metrics-related keys */ public static void initializeMetrics(Configuration configuration) { synchronized (MetricsHelper.class) { String[] listenerClassNames = configuration.getStringArray("metricsRegistryRegistrationListeners"); if (listenerClassNames.length < 1) { listenerClassNames = new String[] { JmxReporterMetricsRegistryRegistrationListener.class.getName() }; } // Build each listener using their default constructor and add them for (String listenerClassName : listenerClassNames) { try { Class<? extends MetricsRegistryRegistrationListener> clazz = (Class<? extends MetricsRegistryRegistrationListener>) Class.forName(listenerClassName); Constructor<? extends MetricsRegistryRegistrationListener> defaultConstructor = clazz.getDeclaredConstructor(); MetricsRegistryRegistrationListener listener = defaultConstructor.newInstance(); addMetricsRegistryRegistrationListener(listener); } catch (Exception e) { LOGGER.warn("Caught exception while initializing MetricsRegistryRegistrationListener " + listenerClassName, e); } } } } /** * Adds a metrics registry registration listener. When adding a metrics registry registration listener, events are * fired to add all previously registered metrics registries to the newly added metrics registry registration * listener. * * @param listener The listener to add */ public static void addMetricsRegistryRegistrationListener(MetricsRegistryRegistrationListener listener) { synchronized (MetricsHelper.class) { metricsRegistryRegistrationListenersMap.put(listener, null); // Fire events to register all previously registered metrics registries Set<MetricsRegistry> metricsRegistries = metricsRegistryMap.keySet(); for (MetricsRegistry metricsRegistry : metricsRegistries) { listener.onMetricsRegistryRegistered(metricsRegistry); } } } /** * Registers the metrics registry with the metrics helper. * * @param registry The registry to register */ public static void registerMetricsRegistry(MetricsRegistry registry) { synchronized (MetricsHelper.class) { metricsRegistryMap.put(registry, null); // Fire event to all registered listeners Set<MetricsRegistryRegistrationListener> metricsRegistryRegistrationListeners = metricsRegistryRegistrationListenersMap.keySet(); for (MetricsRegistryRegistrationListener metricsRegistryRegistrationListener : metricsRegistryRegistrationListeners) { metricsRegistryRegistrationListener.onMetricsRegistryRegistered(registry); } } } /** * * Return an existing meter if * (a) A meter already exist with the same metric name. * Otherwise, creates a new meter and registers * * @param registry MetricsRegistry * @param name metric name * @param eventType Event Type * @param unit TimeUnit for rate determination * @return Meter */ public static Meter newMeter(MetricsRegistry registry, MetricName name, String eventType, TimeUnit unit) { if (registry != null) { return registry.newMeter(name, eventType, unit); } else { return Metrics.newMeter(name, eventType, unit); } } /** * * Return an existing aggregated meter if registry is not null and a aggregated meter already exist * with the same metric name. Otherwise, creates a new aggregated meter and registers (if registry not null) * * @param registry MetricsRegistry * @param name metric name * @return AggregatedMeter */ public static <T extends Metered & Stoppable> AggregatedMeter<T> newAggregatedMeter( AggregatedMetricsRegistry registry, MetricName name) { if (registry != null) { return registry.newAggregatedMeter(name); } else { return new AggregatedMeter<T>(); //not registered } } /** * * Return an existing counter if * (a) A counter already exist with the same metric name. * Otherwise, creates a new meter and registers * * @param registry MetricsRegistry * @param name metric name * @return Counter */ public static Counter newCounter(MetricsRegistry registry, MetricName name) { if (registry != null) { return registry.newCounter(name); } else { return Metrics.newCounter(name); } } /** * * Return an existing aggregated counter if registry is not null and a aggregated counter already exist * with the same metric name. Otherwise, creates a new aggregated counter and registers (if registry not null) * * @param registry MetricsRegistry * @param name metric name * @return AggregatedCounter */ public static AggregatedCounter newAggregatedCounter(AggregatedMetricsRegistry registry, MetricName name) { if (registry != null) { return registry.newAggregatedCounter(name); } else { return new AggregatedCounter(); } } /** * * Return an existing histogram if * (a) A histogram already exist with the same metric name. * Otherwise, creates a new meter and registers * * @param registry MetricsRegistry * @param name metric name * @param biased (true if uniform distribution, otherwise exponential weighted) * @return histogram */ public static Histogram newHistogram(MetricsRegistry registry, MetricName name, boolean biased) { if (registry != null) { return registry.newHistogram(name, biased); } else { return Metrics.newHistogram(name, biased); } } /** * * Return an existing aggregated histogram if registry is not null and a aggregated histogram already exist * with the same metric name. Otherwise, creates a new aggregated histogram and registers (if registry not null) * * @param registry MetricsRegistry * @param name metric name * @return AggregatedHistogram */ public static <T extends Sampling> AggregatedHistogram<T> newAggregatedHistogram(AggregatedMetricsRegistry registry, MetricName name) { if (registry != null) { return registry.newAggregatedHistogram(name); } else { return new AggregatedHistogram<T>(); } } /** * * Return an existing gauge if * (a) A gauge already exist with the same metric name. * Otherwise, creates a new meter and registers * * @param registry MetricsRegistry * @param name metric name * @param gauge Underlying gauge to be tracked * @return gauge */ public static <T> Gauge<T> newGauge(MetricsRegistry registry, MetricName name, Gauge<T> gauge) { if (registry != null) { return registry.newGauge(name, gauge); } else { return Metrics.newGauge(name, gauge); } } /** * * Return an existing aggregated long gauge if registry is not null and a aggregated long gauge already exist * with the same metric name. Otherwise, creates a new aggregated long gauge and registers (if registry not null) * * @param registry MetricsRegistry * @param name metric name * @return AggregatedLongGauge */ public static <T extends Number, V extends Gauge<T>> AggregatedLongGauge<T, V> newAggregatedLongGauge( AggregatedMetricsRegistry registry, MetricName name) { if (registry != null) { return registry.newAggregatedLongGauge(name); } else { return new AggregatedLongGauge<T, V>(); } } /** * * Return an existing timer if * (a) A timer already exist with the same metric name. * Otherwise, creates a new timer and registers * * @param registry MetricsRegistry * @param name metric name * @param durationUnit TimeUnit for duration * @param rateUnit TimeUnit for rate determination * @return Timer */ public static Timer newTimer(MetricsRegistry registry, MetricName name, TimeUnit durationUnit, TimeUnit rateUnit) { if (registry != null) { return registry.newTimer(name, durationUnit, rateUnit); } else { return Metrics.newTimer(name, durationUnit, rateUnit); } } /** * Useful for measuring elapsed times. * * Usage : * <pre> * {@code * TimerContext tc = MtericsHelper.startTimer(); * .... * Your code to be measured * .... * tc.stop(); * long elapsedTimeMs = tc.getLatencyMs(); * * } * </pre> * @return */ public static TimerContext startTimer() { return new TimerContext(); } /** * * TimerContext to measure elapsed time * */ public static class TimerContext { private final long _startTimeNanos; private long _stopTimeNanos; private boolean _isDone; public TimerContext() { _startTimeNanos = System.nanoTime(); _isDone = false; } public void stop() { _isDone = true; _stopTimeNanos = System.nanoTime(); } /** * * @return */ public long getLatencyMs() { if (!_isDone) { stop(); } return (_stopTimeNanos - _startTimeNanos) / 1000000L; } } }