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
041/**
042 * This class acts as an adapter to export the MetricRegistry's in the global registry. Each
043 * MetricRegistry will be registered or unregistered from the metric2 system. The collection will be
044 * performed via the MetricsSourceAdapter and the MetricRegistry will collected like a BaseSource
045 * instance for a group of metrics (like WAL, RPC, etc) with the MetricRegistryInfo's JMX context.
046 * <p>
047 * Developer note: Unlike the current metrics2 based approach, the new metrics approach
048 * (hbase-metrics-api and hbase-metrics modules) work by having different MetricRegistries that are
049 * initialized and used from the code that lives in their respective modules (hbase-server, etc).
050 * There is no need to define BaseSource classes and do a lot of indirection. The MetricRegistry'es
051 * will be in the global MetricRegistriesImpl, and this class will iterate over
052 * MetricRegistries.global() and register adapters to the metrics2 subsystem. These adapters then
053 * report the actual values by delegating to
054 * {@link HBaseMetrics2HadoopMetricsAdapter#snapshotAllMetrics(MetricRegistry, MetricsCollector)}.
055 * We do not initialize the Hadoop Metrics2 system assuming that other BaseSources already do so
056 * (see BaseSourceImpl). Once the last BaseSource is moved to the new system, the metric2
057 * initialization should be moved here.
058 * </p>
059 */
060@InterfaceAudience.Private
061public final class GlobalMetricRegistriesAdapter {
062
063  private static final Logger LOG = LoggerFactory.getLogger(GlobalMetricRegistriesAdapter.class);
064
065  private class MetricsSourceAdapter implements MetricsSource {
066    private final MetricRegistryInfo info;
067
068    MetricsSourceAdapter(MetricRegistryInfo info) {
069      this.info = info;
070    }
071
072    @Override
073    public void getMetrics(MetricsCollector collector, boolean all) {
074      Optional<MetricRegistry> registryOpt = MetricRegistries.global().get(info);
075      registryOpt
076        .ifPresent(metricRegistry -> metricsAdapter.snapshotAllMetrics(metricRegistry, collector));
077    }
078  }
079
080  private final MetricsExecutor executor;
081  private final AtomicBoolean stopped;
082  private final DefaultMetricsSystemHelper helper;
083  private final HBaseMetrics2HadoopMetricsAdapter metricsAdapter;
084  private final HashMap<MetricRegistryInfo, MetricsSourceAdapter> registeredSources;
085
086  private GlobalMetricRegistriesAdapter() {
087    this.executor = new MetricsExecutorImpl();
088    this.stopped = new AtomicBoolean(false);
089    this.metricsAdapter = new HBaseMetrics2HadoopMetricsAdapter();
090    this.registeredSources = new HashMap<>();
091    this.helper = new DefaultMetricsSystemHelper();
092    executor.getExecutor().scheduleAtFixedRate(() -> this.doRun(), 10, 10, TimeUnit.SECONDS);
093  }
094
095  /**
096   * Make sure that this global MetricSource for hbase-metrics module based metrics are initialized.
097   * This should be called only once.
098   */
099  public static GlobalMetricRegistriesAdapter init() {
100    return new GlobalMetricRegistriesAdapter();
101  }
102
103  public void stop() {
104    stopped.set(true);
105  }
106
107  private void doRun() {
108    if (stopped.get()) {
109      executor.stop();
110      return;
111    }
112    if (LOG.isTraceEnabled()) {
113      LOG.trace("doRun called: " + registeredSources);
114    }
115
116    Collection<MetricRegistry> registries = MetricRegistries.global().getMetricRegistries();
117    for (MetricRegistry registry : registries) {
118      MetricRegistryInfo info = registry.getMetricRegistryInfo();
119
120      LOG.trace("MetricRegistryInfo : {}", info.getMetricsName());
121      if (info.isExistingSource()) {
122        // If there is an already existing BaseSource for this MetricRegistry, skip it here. These
123        // types of registries are there only due to existing BaseSource implementations in the
124        // source code (like MetricsRegionServer, etc). This is to make sure that we can transition
125        // iteratively to the new hbase-metrics system. These type of MetricRegistry metrics will be
126        // exported from the BaseSource.getMetrics() call directly because there is already a
127        // MetricRecordBuilder there (see MetricsRegionServerSourceImpl).
128        continue;
129      }
130
131      if (!registeredSources.containsKey(info)) {
132        if (LOG.isDebugEnabled()) {
133          LOG.debug("Registering adapter for the MetricRegistry: {}", info.getMetricsJmxContext());
134        }
135        // register this as a MetricSource under different JMX Context'es.
136        MetricsSourceAdapter adapter = new MetricsSourceAdapter(info);
137        if (LOG.isDebugEnabled()) {
138          LOG.debug("Registering {} {}", info.getMetricsJmxContext(), info.getMetricsDescription());
139        }
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.isEmpty()) {
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}