001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.metrics.impl;
019
020import java.util.Collection;
021import java.util.HashMap;
022import java.util.Iterator;
023import java.util.Map.Entry;
024import java.util.Optional;
025import java.util.concurrent.TimeUnit;
026import java.util.concurrent.atomic.AtomicBoolean;
027import org.apache.hadoop.hbase.metrics.MetricRegistries;
028import org.apache.hadoop.hbase.metrics.MetricRegistry;
029import org.apache.hadoop.hbase.metrics.MetricRegistryInfo;
030import org.apache.hadoop.metrics2.MetricsCollector;
031import org.apache.hadoop.metrics2.MetricsExecutor;
032import org.apache.hadoop.metrics2.MetricsSource;
033import org.apache.hadoop.metrics2.impl.JmxCacheBuster;
034import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
035import org.apache.hadoop.metrics2.lib.DefaultMetricsSystemHelper;
036import org.apache.hadoop.metrics2.lib.MetricsExecutorImpl;
037import org.apache.yetus.audience.InterfaceAudience;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
042
043/**
044 * This class acts as an adapter to export the MetricRegistry's in the global registry. Each
045 * MetricRegistry will be registered or unregistered from the metric2 system. The collection will
046 * be performed via the MetricsSourceAdapter and the MetricRegistry will collected like a
047 * BaseSource instance for a group of metrics  (like WAL, RPC, etc) with the MetricRegistryInfo's
048 * JMX context.
049 *
050 * <p>Developer note:
051 * Unlike the current metrics2 based approach, the new metrics approach
052 * (hbase-metrics-api and hbase-metrics modules) work by having different MetricRegistries that are
053 * initialized and used from the code that lives in their respective modules (hbase-server, etc).
054 * There is no need to define BaseSource classes and do a lot of indirection. The MetricRegistry'es
055 * will be in the global MetricRegistriesImpl, and this class will iterate over
056 * MetricRegistries.global() and register adapters to the metrics2 subsystem. These adapters then
057 * report the actual values by delegating to
058 * {@link HBaseMetrics2HadoopMetricsAdapter#snapshotAllMetrics(MetricRegistry, MetricsCollector)}.
059 *
060 * We do not initialize the Hadoop Metrics2 system assuming that other BaseSources already do so
061 * (see BaseSourceImpl). Once the last BaseSource is moved to the new system, the metric2
062 * initialization should be moved here.
063 * </p>
064 */
065@InterfaceAudience.Private
066public final class GlobalMetricRegistriesAdapter {
067
068  private static final Logger LOG = LoggerFactory.getLogger(GlobalMetricRegistriesAdapter.class);
069
070  private class MetricsSourceAdapter implements MetricsSource {
071    private final MetricRegistry registry;
072    MetricsSourceAdapter(MetricRegistry registry) {
073      this.registry = registry;
074    }
075
076    @Override
077    public void getMetrics(MetricsCollector collector, boolean all) {
078      metricsAdapter.snapshotAllMetrics(registry, collector);
079    }
080  }
081
082  private final MetricsExecutor executor;
083  private final AtomicBoolean stopped;
084  private final DefaultMetricsSystemHelper helper;
085  private final HBaseMetrics2HadoopMetricsAdapter metricsAdapter;
086  private final HashMap<MetricRegistryInfo, MetricsSourceAdapter> registeredSources;
087
088  private GlobalMetricRegistriesAdapter() {
089    this.executor = new MetricsExecutorImpl();
090    this.stopped = new AtomicBoolean(false);
091    this.metricsAdapter = new HBaseMetrics2HadoopMetricsAdapter();
092    this.registeredSources = new HashMap<>();
093    this.helper = new DefaultMetricsSystemHelper();
094    executor.getExecutor().scheduleAtFixedRate(() -> this.doRun(), 10, 10, TimeUnit.SECONDS);
095  }
096
097  /**
098   * Make sure that this global MetricSource for hbase-metrics module based metrics are initialized.
099   * This should be called only once.
100   */
101  public static GlobalMetricRegistriesAdapter init() {
102    return new GlobalMetricRegistriesAdapter();
103  }
104
105  @VisibleForTesting
106  public void stop() {
107    stopped.set(true);
108  }
109
110  private void doRun() {
111    if (stopped.get()) {
112      executor.stop();
113      return;
114    }
115    if (LOG.isTraceEnabled()) {
116      LOG.trace("doRun called: " + registeredSources);
117    }
118
119    Collection<MetricRegistry> registries = MetricRegistries.global().getMetricRegistries();
120    for (MetricRegistry registry : registries) {
121      MetricRegistryInfo info = registry.getMetricRegistryInfo();
122
123      if (info.isExistingSource()) {
124        // If there is an already existing BaseSource for this MetricRegistry, skip it here. These
125        // types of registries are there only due to existing BaseSource implementations in the
126        // source code (like MetricsRegionServer, etc). This is to make sure that we can transition
127        // iteratively to the new hbase-metrics system. These type of MetricRegistry metrics will be
128        // exported from the BaseSource.getMetrics() call directly because there is already a
129        // MetricRecordBuilder there (see MetricsRegionServerSourceImpl).
130        continue;
131      }
132
133      if (!registeredSources.containsKey(info)) {
134        if (LOG.isDebugEnabled()) {
135          LOG.debug("Registering adapter for the MetricRegistry: " + info.getMetricsJmxContext());
136        }
137        // register this as a MetricSource under different JMX Context'es.
138        MetricsSourceAdapter adapter = new MetricsSourceAdapter(registry);
139        LOG.info("Registering " + info.getMetricsJmxContext() + " " + info.getMetricsDescription());
140        DefaultMetricsSystem.instance().register(info.getMetricsJmxContext(),
141            info.getMetricsDescription(), adapter);
142        registeredSources.put(info, adapter);
143        // next collection will collect the newly registered MetricSource. Doing this here leads to
144        // ConcurrentModificationException.
145      }
146    }
147
148    boolean removed = false;
149    // Remove registered sources if it is removed from the global registry
150    for (Iterator<Entry<MetricRegistryInfo, MetricsSourceAdapter>> it =
151         registeredSources.entrySet().iterator(); it.hasNext();) {
152      Entry<MetricRegistryInfo, MetricsSourceAdapter> entry = it.next();
153      MetricRegistryInfo info = entry.getKey();
154      Optional<MetricRegistry> found = MetricRegistries.global().get(info);
155      if (!found.isPresent()) {
156        if (LOG.isDebugEnabled()) {
157          LOG.debug("Removing adapter for the MetricRegistry: " + info.getMetricsJmxContext());
158        }
159        synchronized(DefaultMetricsSystem.instance()) {
160          DefaultMetricsSystem.instance().unregisterSource(info.getMetricsJmxContext());
161          helper.removeSourceName(info.getMetricsJmxContext());
162          helper.removeObjectName(info.getMetricsJmxContext());
163          it.remove();
164          removed = true;
165        }
166      }
167    }
168    if (removed) {
169      JmxCacheBuster.clearJmxCache();
170    }
171  }
172}