org.apache.hadoop.metrics2.impl.MetricsSystemImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.metrics2.impl.MetricsSystemImpl.java

Source

/**
 * 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.metrics2.impl;

import java.io.StringWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetAddress;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Timer;
import java.util.TimerTask;
import javax.management.ObjectName;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.annotations.VisibleForTesting;
import static com.google.common.base.Preconditions.*;

import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.math3.util.ArithmeticUtils;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.metrics2.MetricsInfo;
import org.apache.hadoop.metrics2.MetricsCollector;
import org.apache.hadoop.metrics2.MetricsException;
import org.apache.hadoop.metrics2.MetricsFilter;
import org.apache.hadoop.metrics2.MetricsRecordBuilder;
import org.apache.hadoop.metrics2.MetricsSink;
import org.apache.hadoop.metrics2.MetricsSource;
import org.apache.hadoop.metrics2.MetricsSystem;
import org.apache.hadoop.metrics2.MetricsTag;
import org.apache.hadoop.metrics2.annotation.Metric;
import org.apache.hadoop.metrics2.annotation.Metrics;
import org.apache.hadoop.metrics2.lib.MutableCounterLong;
import static org.apache.hadoop.metrics2.impl.MetricsConfig.*;
import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
import org.apache.hadoop.metrics2.lib.Interns;
import org.apache.hadoop.metrics2.lib.MetricsAnnotations;
import org.apache.hadoop.metrics2.lib.MetricsRegistry;
import org.apache.hadoop.metrics2.lib.MetricsSourceBuilder;
import org.apache.hadoop.metrics2.lib.MutableStat;
import org.apache.hadoop.metrics2.util.MBeans;
import org.apache.hadoop.util.StringUtils;
import org.apache.hadoop.util.Time;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A base class for metrics system singletons
 */
@InterfaceAudience.Private
@Metrics(context = "metricssystem")
public class MetricsSystemImpl extends MetricsSystem implements MetricsSource {

    static final Logger LOG = LoggerFactory.getLogger(MetricsSystemImpl.class);
    static final String MS_NAME = "MetricsSystem";
    static final String MS_STATS_NAME = MS_NAME + ",sub=Stats";
    static final String MS_STATS_DESC = "Metrics system metrics";
    static final String MS_CONTROL_NAME = MS_NAME + ",sub=Control";
    static final String MS_INIT_MODE_KEY = "hadoop.metrics.init.mode";

    enum InitMode {
        NORMAL, STANDBY
    }

    private final Map<String, MetricsSourceAdapter> sources;
    private final Map<String, MetricsSource> allSources;
    private final Map<String, MetricsSinkAdapter> sinks;
    private final Map<String, MetricsSink> allSinks;

    // The callback list is used by register(Callback callback), while
    // the callback map is used by register(String name, String desc, T sink)
    private final List<Callback> callbacks;
    private final Map<String, Callback> namedCallbacks;

    private final MetricsCollectorImpl collector;
    private final MetricsRegistry registry = new MetricsRegistry(MS_NAME);
    @Metric({ "Snapshot", "Snapshot stats" })
    MutableStat snapshotStat;
    @Metric({ "Publish", "Publishing stats" })
    MutableStat publishStat;
    @Metric("Dropped updates by all sinks")
    MutableCounterLong droppedPubAll;

    private final List<MetricsTag> injectedTags;

    // Things that are changed by init()/start()/stop()
    private String prefix;
    private MetricsFilter sourceFilter;
    private MetricsConfig config;
    private Map<String, MetricsConfig> sourceConfigs, sinkConfigs;
    private boolean monitoring = false;
    private Timer timer;
    private long period; // milliseconds
    private long logicalTime; // number of timer invocations * period
    private ObjectName mbeanName;
    private boolean publishSelfMetrics = true;
    private MetricsSourceAdapter sysSource;
    private int refCount = 0; // for mini cluster mode

    /**
     * Construct the metrics system
     * @param prefix  for the system
     */
    public MetricsSystemImpl(String prefix) {
        this.prefix = prefix;
        allSources = Maps.newHashMap();
        sources = Maps.newLinkedHashMap();
        allSinks = Maps.newHashMap();
        sinks = Maps.newLinkedHashMap();
        sourceConfigs = Maps.newHashMap();
        sinkConfigs = Maps.newHashMap();
        callbacks = Lists.newArrayList();
        namedCallbacks = Maps.newHashMap();
        injectedTags = Lists.newArrayList();
        collector = new MetricsCollectorImpl();
        if (prefix != null) {
            // prefix could be null for default ctor, which requires init later
            initSystemMBean();
        }
    }

    /**
     * Construct the system but not initializing (read config etc.) it.
     */
    public MetricsSystemImpl() {
        this(null);
    }

    /**
     * Initialized the metrics system with a prefix.
     * @param prefix  the system will look for configs with the prefix
     * @return the metrics system object itself
     */
    @Override
    public synchronized MetricsSystem init(String prefix) {
        if (monitoring && !DefaultMetricsSystem.inMiniClusterMode()) {
            LOG.warn(this.prefix + " metrics system already initialized!");
            return this;
        }
        this.prefix = checkNotNull(prefix, "prefix");
        ++refCount;
        if (monitoring) {
            // in mini cluster mode
            LOG.info(this.prefix + " metrics system started (again)");
            return this;
        }
        switch (initMode()) {
        case NORMAL:
            try {
                start();
            } catch (MetricsConfigException e) {
                // Configuration errors (e.g., typos) should not be fatal.
                // We can always start the metrics system later via JMX.
                LOG.warn("Metrics system not started: " + e.getMessage());
                LOG.debug("Stacktrace: ", e);
            }
            break;
        case STANDBY:
            LOG.info(prefix + " metrics system started in standby mode");
        }
        initSystemMBean();
        return this;
    }

    @Override
    public synchronized void start() {
        checkNotNull(prefix, "prefix");
        if (monitoring) {
            LOG.warn(prefix + " metrics system already started!", new MetricsException("Illegal start"));
            return;
        }
        for (Callback cb : callbacks)
            cb.preStart();
        for (Callback cb : namedCallbacks.values())
            cb.preStart();
        configure(prefix);
        startTimer();
        monitoring = true;
        LOG.info(prefix + " metrics system started");
        for (Callback cb : callbacks)
            cb.postStart();
        for (Callback cb : namedCallbacks.values())
            cb.postStart();
    }

    @Override
    public synchronized void stop() {
        if (!monitoring && !DefaultMetricsSystem.inMiniClusterMode()) {
            LOG.warn(prefix + " metrics system not yet started!", new MetricsException("Illegal stop"));
            return;
        }
        if (!monitoring) {
            // in mini cluster mode
            LOG.info(prefix + " metrics system stopped (again)");
            return;
        }
        for (Callback cb : callbacks)
            cb.preStop();
        for (Callback cb : namedCallbacks.values())
            cb.preStop();
        LOG.info("Stopping " + prefix + " metrics system...");
        stopTimer();
        stopSources();
        stopSinks();
        clearConfigs();
        monitoring = false;
        LOG.info(prefix + " metrics system stopped.");
        for (Callback cb : callbacks)
            cb.postStop();
        for (Callback cb : namedCallbacks.values())
            cb.postStop();
    }

    @Override
    public synchronized <T> T register(String name, String desc, T source) {
        MetricsSourceBuilder sb = MetricsAnnotations.newSourceBuilder(source);
        final MetricsSource s = sb.build();
        MetricsInfo si = sb.info();
        String name2 = name == null ? si.name() : name;
        final String finalDesc = desc == null ? si.description() : desc;
        final String finalName = // be friendly to non-metrics tests
                DefaultMetricsSystem.sourceName(name2, !monitoring);
        allSources.put(finalName, s);
        LOG.debug(finalName + ", " + finalDesc);
        if (monitoring) {
            registerSource(finalName, finalDesc, s);
        }
        // We want to re-register the source to pick up new config when the
        // metrics system restarts.
        register(finalName, new AbstractCallback() {
            @Override
            public void postStart() {
                registerSource(finalName, finalDesc, s);
            }
        });
        return source;
    }

    @Override
    public synchronized void unregisterSource(String name) {
        if (sources.containsKey(name)) {
            sources.get(name).stop();
            sources.remove(name);
        }
        if (allSources.containsKey(name)) {
            allSources.remove(name);
        }
        if (namedCallbacks.containsKey(name)) {
            namedCallbacks.remove(name);
        }
        DefaultMetricsSystem.removeSourceName(name);
    }

    synchronized void registerSource(String name, String desc, MetricsSource source) {
        checkNotNull(config, "config");
        MetricsConfig conf = sourceConfigs.get(name);
        MetricsSourceAdapter sa = new MetricsSourceAdapter(prefix, name, desc, source, injectedTags, period,
                conf != null ? conf : config.subset(SOURCE_KEY));
        sources.put(name, sa);
        sa.start();
        LOG.debug("Registered source " + name);
    }

    @Override
    public synchronized <T extends MetricsSink> T register(final String name, final String description,
            final T sink) {
        LOG.debug(name + ", " + description);
        if (allSinks.containsKey(name)) {
            LOG.warn("Sink " + name + " already exists!");
            return sink;
        }
        allSinks.put(name, sink);
        if (config != null) {
            registerSink(name, description, sink);
        }
        // We want to re-register the sink to pick up new config
        // when the metrics system restarts.
        register(name, new AbstractCallback() {
            @Override
            public void postStart() {
                register(name, description, sink);
            }
        });
        return sink;
    }

    synchronized void registerSink(String name, String desc, MetricsSink sink) {
        checkNotNull(config, "config");
        MetricsConfig conf = sinkConfigs.get(name);
        MetricsSinkAdapter sa = conf != null ? newSink(name, desc, sink, conf)
                : newSink(name, desc, sink, config.subset(SINK_KEY));
        sinks.put(name, sa);
        sa.start();
        LOG.info("Registered sink " + name);
    }

    @Override
    public synchronized void register(final Callback callback) {
        callbacks.add((Callback) getProxyForCallback(callback));
    }

    private synchronized void register(String name, final Callback callback) {
        namedCallbacks.put(name, (Callback) getProxyForCallback(callback));
    }

    private Object getProxyForCallback(final Callback callback) {
        return Proxy.newProxyInstance(callback.getClass().getClassLoader(), new Class<?>[] { Callback.class },
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        try {
                            return method.invoke(callback, args);
                        } catch (Exception e) {
                            // These are not considered fatal.
                            LOG.warn("Caught exception in callback " + method.getName(), e);
                        }
                        return null;
                    }
                });
    }

    @Override
    public synchronized void startMetricsMBeans() {
        for (MetricsSourceAdapter sa : sources.values()) {
            sa.startMBeans();
        }
    }

    @Override
    public synchronized void stopMetricsMBeans() {
        for (MetricsSourceAdapter sa : sources.values()) {
            sa.stopMBeans();
        }
    }

    @Override
    public synchronized String currentConfig() {
        PropertiesConfiguration saver = new PropertiesConfiguration();
        StringWriter writer = new StringWriter();
        saver.copy(config);
        try {
            saver.write(writer);
        } catch (Exception e) {
            throw new MetricsConfigException("Error stringify config", e);
        }
        return writer.toString();
    }

    private synchronized void startTimer() {
        if (timer != null) {
            LOG.warn(prefix + " metrics system timer already started!");
            return;
        }
        logicalTime = 0;
        long millis = period;
        timer = new Timer("Timer for '" + prefix + "' metrics system", true);
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                try {
                    onTimerEvent();
                } catch (Exception e) {
                    LOG.warn("Error invoking metrics timer", e);
                }
            }
        }, millis, millis);
        LOG.info("Scheduled Metric snapshot period at " + (period / 1000) + " second(s).");
    }

    synchronized void onTimerEvent() {
        logicalTime += period;
        if (sinks.size() > 0) {
            publishMetrics(sampleMetrics(), false);
        }
    }

    /**
     * Requests an immediate publish of all metrics from sources to sinks.
     */
    @Override
    public synchronized void publishMetricsNow() {
        if (sinks.size() > 0) {
            publishMetrics(sampleMetrics(), true);
        }
    }

    /**
     * Sample all the sources for a snapshot of metrics/tags
     * @return  the metrics buffer containing the snapshot
     */
    @VisibleForTesting
    public synchronized MetricsBuffer sampleMetrics() {
        collector.clear();
        MetricsBufferBuilder bufferBuilder = new MetricsBufferBuilder();

        for (Entry<String, MetricsSourceAdapter> entry : sources.entrySet()) {
            if (sourceFilter == null || sourceFilter.accepts(entry.getKey())) {
                snapshotMetrics(entry.getValue(), bufferBuilder);
            }
        }
        if (publishSelfMetrics) {
            snapshotMetrics(sysSource, bufferBuilder);
        }
        MetricsBuffer buffer = bufferBuilder.get();
        return buffer;
    }

    private void snapshotMetrics(MetricsSourceAdapter sa, MetricsBufferBuilder bufferBuilder) {
        long startTime = Time.monotonicNow();
        bufferBuilder.add(sa.name(), sa.getMetrics(collector, true));
        collector.clear();
        snapshotStat.add(Time.monotonicNow() - startTime);
        LOG.debug("Snapshotted source " + sa.name());
    }

    /**
     * Publish a metrics snapshot to all the sinks
     * @param buffer  the metrics snapshot to publish
     * @param immediate  indicates that we should publish metrics immediately
     *                   instead of using a separate thread.
     */
    synchronized void publishMetrics(MetricsBuffer buffer, boolean immediate) {
        int dropped = 0;
        for (MetricsSinkAdapter sa : sinks.values()) {
            long startTime = Time.monotonicNow();
            boolean result;
            if (immediate) {
                result = sa.putMetricsImmediate(buffer);
            } else {
                result = sa.putMetrics(buffer, logicalTime);
            }
            dropped += result ? 0 : 1;
            publishStat.add(Time.monotonicNow() - startTime);
        }
        droppedPubAll.incr(dropped);
    }

    private synchronized void stopTimer() {
        if (timer == null) {
            LOG.warn(prefix + " metrics system timer already stopped!");
            return;
        }
        timer.cancel();
        timer = null;
    }

    private synchronized void stopSources() {
        for (Entry<String, MetricsSourceAdapter> entry : sources.entrySet()) {
            MetricsSourceAdapter sa = entry.getValue();
            LOG.debug("Stopping metrics source " + entry.getKey() + ": class=" + sa.source().getClass());
            sa.stop();
        }
        sysSource.stop();
        sources.clear();
    }

    private synchronized void stopSinks() {
        for (Entry<String, MetricsSinkAdapter> entry : sinks.entrySet()) {
            MetricsSinkAdapter sa = entry.getValue();
            LOG.debug("Stopping metrics sink " + entry.getKey() + ": class=" + sa.sink().getClass());
            sa.stop();
        }
        sinks.clear();
    }

    private synchronized void configure(String prefix) {
        config = MetricsConfig.create(prefix);
        configureSinks();
        configureSources();
        configureSystem();
    }

    private synchronized void configureSystem() {
        injectedTags.add(Interns.tag(MsInfo.Hostname, getHostname()));
    }

    private synchronized void configureSinks() {
        sinkConfigs = config.getInstanceConfigs(SINK_KEY);
        long confPeriodMillis = 0;
        for (Entry<String, MetricsConfig> entry : sinkConfigs.entrySet()) {
            MetricsConfig conf = entry.getValue();
            int sinkPeriod = conf.getInt(PERIOD_KEY, PERIOD_DEFAULT);
            // Support configuring periodMillis for testing.
            long sinkPeriodMillis = conf.getLong(PERIOD_MILLIS_KEY, sinkPeriod * 1000);
            confPeriodMillis = confPeriodMillis == 0 ? sinkPeriodMillis
                    : ArithmeticUtils.gcd(confPeriodMillis, sinkPeriodMillis);
            String clsName = conf.getClassName("");
            if (clsName == null)
                continue; // sink can be registered later on
            String sinkName = entry.getKey();
            try {
                MetricsSinkAdapter sa = newSink(sinkName, conf.getString(DESC_KEY, sinkName), conf);
                sa.start();
                sinks.put(sinkName, sa);
            } catch (Exception e) {
                LOG.warn("Error creating sink '" + sinkName + "'", e);
            }
        }
        long periodSec = config.getInt(PERIOD_KEY, PERIOD_DEFAULT);
        period = confPeriodMillis > 0 ? confPeriodMillis : config.getLong(PERIOD_MILLIS_KEY, periodSec * 1000);
    }

    static MetricsSinkAdapter newSink(String name, String desc, MetricsSink sink, MetricsConfig conf) {
        return new MetricsSinkAdapter(name, desc, sink, conf.getString(CONTEXT_KEY),
                conf.getFilter(SOURCE_FILTER_KEY), conf.getFilter(RECORD_FILTER_KEY),
                conf.getFilter(METRIC_FILTER_KEY), conf.getInt(PERIOD_KEY, PERIOD_DEFAULT) * 1000,
                conf.getInt(QUEUE_CAPACITY_KEY, QUEUE_CAPACITY_DEFAULT),
                conf.getInt(RETRY_DELAY_KEY, RETRY_DELAY_DEFAULT),
                conf.getFloat(RETRY_BACKOFF_KEY, RETRY_BACKOFF_DEFAULT),
                conf.getInt(RETRY_COUNT_KEY, RETRY_COUNT_DEFAULT));
    }

    static MetricsSinkAdapter newSink(String name, String desc, MetricsConfig conf) {
        return newSink(name, desc, (MetricsSink) conf.getPlugin(""), conf);
    }

    private void configureSources() {
        sourceFilter = config.getFilter(PREFIX_DEFAULT + SOURCE_FILTER_KEY);
        sourceConfigs = config.getInstanceConfigs(SOURCE_KEY);
        registerSystemSource();
    }

    private void clearConfigs() {
        sinkConfigs.clear();
        sourceConfigs.clear();
        injectedTags.clear();
        config = null;
    }

    static String getHostname() {
        try {
            return InetAddress.getLocalHost().getHostName();
        } catch (Exception e) {
            LOG.error("Error getting localhost name. Using 'localhost'...", e);
        }
        return "localhost";
    }

    private void registerSystemSource() {
        MetricsConfig sysConf = sourceConfigs.get(MS_NAME);
        sysSource = new MetricsSourceAdapter(prefix, MS_STATS_NAME, MS_STATS_DESC,
                MetricsAnnotations.makeSource(this), injectedTags, period,
                sysConf == null ? config.subset(SOURCE_KEY) : sysConf);
        sysSource.start();
    }

    @Override
    public synchronized void getMetrics(MetricsCollector builder, boolean all) {
        MetricsRecordBuilder rb = builder.addRecord(MS_NAME).addGauge(MsInfo.NumActiveSources, sources.size())
                .addGauge(MsInfo.NumAllSources, allSources.size()).addGauge(MsInfo.NumActiveSinks, sinks.size())
                .addGauge(MsInfo.NumAllSinks, allSinks.size());

        for (MetricsSinkAdapter sa : sinks.values()) {
            sa.snapshot(rb, all);
        }
        registry.snapshot(rb, all);
    }

    private void initSystemMBean() {
        checkNotNull(prefix, "prefix should not be null here!");
        if (mbeanName == null) {
            mbeanName = MBeans.register(prefix, MS_CONTROL_NAME, this);
        }
    }

    @Override
    public synchronized boolean shutdown() {
        LOG.debug("refCount=" + refCount);
        if (refCount <= 0) {
            LOG.debug("Redundant shutdown", new Throwable());
            return true; // already shutdown
        }
        if (--refCount > 0)
            return false;
        if (monitoring) {
            try {
                stop();
            } catch (Exception e) {
                LOG.warn("Error stopping the metrics system", e);
            }
        }
        allSources.clear();
        allSinks.clear();
        callbacks.clear();
        namedCallbacks.clear();
        if (mbeanName != null) {
            MBeans.unregister(mbeanName);
            mbeanName = null;
        }
        LOG.info(prefix + " metrics system shutdown complete.");
        return true;
    }

    @Override
    public MetricsSource getSource(String name) {
        return allSources.get(name);
    }

    @VisibleForTesting
    MetricsSourceAdapter getSourceAdapter(String name) {
        return sources.get(name);
    }

    @VisibleForTesting
    public MetricsSinkAdapter getSinkAdapter(String name) {
        return sinks.get(name);
    }

    private InitMode initMode() {
        LOG.debug("from system property: " + System.getProperty(MS_INIT_MODE_KEY));
        LOG.debug("from environment variable: " + System.getenv(MS_INIT_MODE_KEY));
        String m = System.getProperty(MS_INIT_MODE_KEY);
        String m2 = m == null ? System.getenv(MS_INIT_MODE_KEY) : m;
        return InitMode.valueOf(StringUtils.toUpperCase((m2 == null ? InitMode.NORMAL.name() : m2)));
    }
}