/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.hadoop.hive.common.metrics.metrics2;
import com.codahale.metrics.ConsoleReporter;
import com.codahale.metrics.Counter;
import com.codahale.metrics.ExponentiallyDecayingReservoir;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.JmxReporter;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.MetricSet;
import com.codahale.metrics.Timer;
import com.codahale.metrics.json.MetricsModule;
import com.codahale.metrics.jvm.BufferPoolMetricSet;
import com.codahale.metrics.jvm.ClassLoadingGaugeSet;
import com.codahale.metrics.jvm.GarbageCollectorMetricSet;
import com.codahale.metrics.jvm.MemoryUsageGaugeSet;
import com.codahale.metrics.jvm.ThreadStatesGaugeSet;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.joshelser.dropwizard.metrics.hadoop.HadoopMetrics2Reporter;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.hive.common.metrics.common.MetricsConstant;
import org.apache.hadoop.hive.common.metrics.common.MetricsScope;
import org.apache.hadoop.hive.common.metrics.common.MetricsVariable;
import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.management.ManagementFactory;
import java.net.URI;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Codahale-backed Metrics implementation.
*/
public class CodahaleMetrics implements org.apache.hadoop.hive.common.metrics.common.Metrics {
public static final Logger LOGGER = LoggerFactory.getLogger(CodahaleMetrics.class);
public final MetricRegistry metricRegistry = new MetricRegistry();
private final Lock timersLock = new ReentrantLock();
private final Lock countersLock = new ReentrantLock();
private final Lock gaugesLock = new ReentrantLock();
private final Lock metersLock = new ReentrantLock();
private LoadingCache<String, Timer> timers;
private LoadingCache<String, Counter> counters;
private LoadingCache<String, Meter> meters;
private ConcurrentHashMap<String, Gauge> gauges;
private HiveConf conf;
private final Set<Closeable> reporters = new HashSet<Closeable>();
private final ThreadLocal<HashMap<String, CodahaleMetricsScope>> threadLocalScopes
= new ThreadLocal<HashMap<String, CodahaleMetricsScope>>() {
@Override
protected HashMap<String, CodahaleMetricsScope> initialValue() {
return new HashMap<String, CodahaleMetricsScope>();
}
};
public class CodahaleMetricsScope implements MetricsScope {
private final String name;
private final Timer timer;
private Timer.Context timerContext;
private boolean isOpen = false;
/**
* Instantiates a named scope - intended to only be called by Metrics, so locally scoped.
* @param name - name of the variable
*/
private CodahaleMetricsScope(String name) {
this.name = name;
this.timer = CodahaleMetrics.this.getTimer(name);
open();
}
/**
* Opens scope, and makes note of the time started, increments run counter
*
*/
public void open() {
if (!isOpen) {
isOpen = true;
this.timerContext = timer.time();
CodahaleMetrics.this.incrementCounter(MetricsConstant.ACTIVE_CALLS + name);
} else {
LOGGER.warn("Scope named " + name + " is not closed, cannot be opened.");
}
}
/**
* Closes scope, and records the time taken
*/
public void close() {
if (isOpen) {
timerContext.close();
CodahaleMetrics.this.decrementCounter(MetricsConstant.ACTIVE_CALLS + name);
} else {
LOGGER.warn("Scope named " + name + " is not open, cannot be closed.");
}
isOpen = false;
}
}
public CodahaleMetrics(HiveConf conf) {
this.conf = conf;
//Codahale artifacts are lazily-created.
timers = CacheBuilder.newBuilder().build(
new CacheLoader<String, com.codahale.metrics.Timer>() {
@Override
public com.codahale.metrics.Timer load(String key) {
Timer timer = new Timer(new ExponentiallyDecayingReservoir());
metricRegistry.register(key, timer);
return timer;
}
}
);
counters = CacheBuilder.newBuilder().build(
new CacheLoader<String, Counter>() {
@Override
public Counter load(String key) {
Counter counter = new Counter();
metricRegistry.register(key, counter);
return counter;
}
}
);
meters = CacheBuilder.newBuilder().build(
new CacheLoader<String, Meter>() {
@Override
public Meter load(String key) {
Meter meter = new Meter();
metricRegistry.register(key, meter);
return meter;
}
}
);
gauges = new ConcurrentHashMap<String, Gauge>();
//register JVM metrics
registerAll("gc", new GarbageCollectorMetricSet());
registerAll("buffers", new BufferPoolMetricSet(ManagementFactory.getPlatformMBeanServer()));
registerAll("memory", new MemoryUsageGaugeSet());
registerAll("threads", new ThreadStatesGaugeSet());
registerAll("classLoading", new ClassLoadingGaugeSet());
//initialize reporters
initReporting();
}
@Override
public void close() throws Exception {
if (reporters != null) {
for (Closeable reporter : reporters) {
reporter.close();
}
}
for (Map.Entry<String, Metric> metric : metricRegistry.getMetrics().entrySet()) {
metricRegistry.remove(metric.getKey());
}
timers.invalidateAll();
counters.invalidateAll();
meters.invalidateAll();
}
@Override
public void startStoredScope(String name) {
if (threadLocalScopes.get().containsKey(name)) {
threadLocalScopes.get().get(name).open();
} else {
threadLocalScopes.get().put(name, new CodahaleMetricsScope(name));
}
}
@Override
public void endStoredScope(String name) {
if (threadLocalScopes.get().containsKey(name)) {
threadLocalScopes.get().get(name).close();
threadLocalScopes.get().remove(name);
}
}
public MetricsScope getStoredScope(String name) throws IllegalArgumentException {
if (threadLocalScopes.get().containsKey(name)) {
return threadLocalScopes.get().get(name);
} else {
throw new IllegalArgumentException("No metrics scope named " + name);
}
}
public MetricsScope createScope(String name) {
return new CodahaleMetricsScope(name);
}
public void endScope(MetricsScope scope) {
((CodahaleMetricsScope) scope).close();
}
@Override
public Long incrementCounter(String name) {
return incrementCounter(name, 1L);
}
@Override
public Long incrementCounter(String name, long increment) {
String key = name;
try {
countersLock.lock();
counters.get(key).inc(increment);
return counters.get(key).getCount();
} catch(ExecutionException ee) {
throw new IllegalStateException("Error retrieving counter from the metric registry ", ee);
} finally {
countersLock.unlock();
}
}
@Override
public Long decrementCounter(String name) {
return decrementCounter(name, 1L);
}
@Override
public Long decrementCounter(String name, long decrement) {
String key = name;
try {
countersLock.lock();
counters.get(key).dec(decrement);
return counters.get(key).getCount();
} catch(ExecutionException ee) {
throw new IllegalStateException("Error retrieving counter from the metric registry ", ee);
} finally {
countersLock.unlock();
}
}
@Override
public void addGauge(String name, final MetricsVariable variable) {
Gauge gauge = new Gauge() {
@Override
public Object getValue() {
return variable.getValue();
}
};
addGaugeInternal(name, gauge);
}
@Override
public void addRatio(String name, MetricsVariable<Integer> numerator,
MetricsVariable<Integer> denominator) {
Preconditions.checkArgument(numerator != null, "Numerator must not be null");
Preconditions.checkArgument(denominator != null, "Denominator must not be null");
MetricVariableRatioGauge gauge = new MetricVariableRatioGauge(numerator, denominator);
addGaugeInternal(name, gauge);
}
private void addGaugeInternal(String name, Gauge gauge) {
try {
gaugesLock.lock();
gauges.put(name, gauge);
// Metrics throws an Exception if we don't do this when the key already exists
if (metricRegistry.getGauges().containsKey(name)) {
LOGGER.warn("A Gauge with name [" + name + "] already exists. "
+ " The old gauge will be overwritten, but this is not recommended");
metricRegistry.remove(name);
}
metricRegistry.register(name, gauge);
} finally {
gaugesLock.unlock();
}
}
@Override
public void markMeter(String name) {
String key = name;
try {
metersLock.lock();
Meter meter = meters.get(name);
meter.mark();
} catch (ExecutionException e) {
throw new IllegalStateException("Error retrieving meter " + name
+ " from the metric registry ", e);
} finally {
metersLock.unlock();
}
}
// This method is necessary to synchronize lazy-creation to the timers.
private Timer getTimer(String name) {
String key = name;
try {
timersLock.lock();
Timer timer = timers.get(key);
return timer;
} catch (ExecutionException e) {
throw new IllegalStateException("Error retrieving timer " + name
+ " from the metric registry ", e);
} finally {
timersLock.unlock();
}
}
private void registerAll(String prefix, MetricSet metricSet) {
for (Map.Entry<String, Metric> entry : metricSet.getMetrics().entrySet()) {
if (entry.getValue() instanceof MetricSet) {
registerAll(prefix + "." + entry.getKey(), (MetricSet) entry.getValue());
} else {
metricRegistry.register(prefix + "." + entry.getKey(), entry.getValue());
}
}
}
@VisibleForTesting
public MetricRegistry getMetricRegistry() {
return metricRegistry;
}
@VisibleForTesting
public String dumpJson() throws Exception {
ObjectMapper jsonMapper = new ObjectMapper().registerModule(
new MetricsModule(TimeUnit.MILLISECONDS, TimeUnit.MILLISECONDS, false));
return jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(metricRegistry);
}
/**
* Initializes reporters from HIVE_CODAHALE_METRICS_REPORTER_CLASSES or HIVE_METRICS_REPORTER if the former is not defined.
* Note: if both confs are defined, only HIVE_CODAHALE_METRICS_REPORTER_CLASSES will be used.
*/
private void initReporting() {
if (!(initCodahaleMetricsReporterClasses() || initMetricsReporter())) {
LOGGER.warn("Unable to initialize metrics reporting");
}
if (reporters.isEmpty()) {
// log a warning incase no reporters were successfully added
LOGGER.warn("No reporters configured for codahale metrics!");
}
}
/**
* Initializes reporting using HIVE_CODAHALE_METRICS_REPORTER_CLASSES.
* @return whether initialization was successful or not
*/
private boolean initCodahaleMetricsReporterClasses() {
List<String> reporterClasses = Lists.newArrayList(Splitter.on(",").trimResults().
omitEmptyStrings().split(conf.getVar(HiveConf.ConfVars.HIVE_CODAHALE_METRICS_REPORTER_CLASSES)));
if (reporterClasses.isEmpty()) {
return false;
}
for (String reporterClass : reporterClasses) {
Class name = null;
try {
name = conf.getClassByName(reporterClass);
} catch (ClassNotFoundException e) {
LOGGER.error("Unable to instantiate metrics reporter class " + reporterClass +
" from conf HIVE_CODAHALE_METRICS_REPORTER_CLASSES", e);
throw new IllegalArgumentException(e);
}
try {
Constructor constructor = name.getConstructor(MetricRegistry.class, HiveConf.class);
CodahaleReporter reporter = (CodahaleReporter) constructor.newInstance(metricRegistry, conf);
reporter.start();
reporters.add(reporter);
} catch (NoSuchMethodException | InstantiationException |
IllegalAccessException | InvocationTargetException e) {
LOGGER.error("Unable to instantiate using constructor(MetricRegistry, HiveConf) for"
+ " reporter " + reporterClass + " from conf HIVE_CODAHALE_METRICS_REPORTER_CLASSES",
e);
throw new IllegalArgumentException(e);
}
}
return true;
}
/**
* Initializes reporting using HIVE_METRICS+REPORTER.
* @return whether initialization was successful or not
*/
private boolean initMetricsReporter() {
List<String> metricsReporterNames = Lists.newArrayList(Splitter.on(",").trimResults().
omitEmptyStrings().split(conf.getVar(HiveConf.ConfVars.HIVE_METRICS_REPORTER)));
if (metricsReporterNames.isEmpty()) {
return false;
}
MetricsReporting reporter = null;
for (String metricsReportingName : metricsReporterNames) {
try {
reporter = MetricsReporting.valueOf(metricsReportingName.trim().toUpperCase());
} catch (IllegalArgumentException e) {
LOGGER.error("Invalid reporter name " + metricsReportingName, e);
throw e;
}
CodahaleReporter codahaleReporter = null;
switch (reporter) {
case CONSOLE:
codahaleReporter = new ConsoleMetricsReporter(metricRegistry, conf);
break;
case JMX:
codahaleReporter = new JmxMetricsReporter(metricRegistry, conf);
break;
case JSON_FILE:
codahaleReporter = new JsonFileMetricsReporter(metricRegistry, conf);
break;
case HADOOP2:
codahaleReporter = new Metrics2Reporter(metricRegistry, conf);
break;
default:
LOGGER.warn("Unhandled reporter " + reporter + " provided.");
}
if (codahaleReporter != null) {
codahaleReporter.start();
reporters.add(codahaleReporter);
}
}
return true;
}
}