View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.metrics.impl;
19  
20  import com.google.common.base.Optional;
21  import java.lang.reflect.InvocationTargetException;
22  import java.lang.reflect.Method;
23  import java.util.Collection;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.Map;
27  import java.util.Map.Entry;
28  import java.util.concurrent.TimeUnit;
29  import java.util.concurrent.atomic.AtomicBoolean;
30  import org.apache.commons.lang.reflect.FieldUtils;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.hadoop.hbase.metrics.MetricRegistries;
34  import org.apache.hadoop.hbase.metrics.MetricRegistry;
35  import org.apache.hadoop.hbase.metrics.MetricRegistryInfo;
36  import org.apache.hadoop.metrics2.MetricsCollector;
37  import org.apache.hadoop.metrics2.MetricsExecutor;
38  import org.apache.hadoop.metrics2.MetricsSource;
39  import org.apache.hadoop.metrics2.MetricsSystem;
40  import org.apache.hadoop.metrics2.impl.JmxCacheBuster;
41  import org.apache.hadoop.metrics2.impl.MetricsSystemImpl;
42  import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
43  import org.apache.hadoop.metrics2.lib.DefaultMetricsSystemHelper;
44  import org.apache.hadoop.metrics2.lib.MetricsExecutorImpl;
45  
46  /**
47   * This class acts as an adapter to export the MetricRegistry's in the global registry. Each
48   * MetricRegistry will be registered or unregistered from the metric2 system. The collection will
49   * be performed via the MetricsSourceAdapter and the MetricRegistry will collected like a
50   * BaseSource instance for a group of metrics  (like WAL, RPC, etc) with the MetricRegistryInfo's
51   * JMX context.
52   *
53   * <p>Developer note:
54   * Unlike the current metrics2 based approach, the new metrics approach
55   * (hbase-metrics-api and hbase-metrics modules) work by having different MetricRegistries that are
56   * initialized and used from the code that lives in their respective modules (hbase-server, etc).
57   * There is no need to define BaseSource classes and do a lot of indirection. The MetricRegistry'es
58   * will be in the global MetricRegistriesImpl, and this class will iterate over
59   * MetricRegistries.global() and register adapters to the metrics2 subsystem. These adapters then
60   * report the actual values by delegating to
61   * {@link HBaseMetrics2HadoopMetricsAdapter#snapshotAllMetrics(MetricRegistry, MetricsCollector)}.
62   *
63   * We do not initialize the Hadoop Metrics2 system assuming that other BaseSources already do so
64   * (see BaseSourceImpl). Once the last BaseSource is moved to the new system, the metric2
65   * initialization should be moved here.
66   * </p>
67   */
68  public class GlobalMetricRegistriesAdapter {
69  
70    private static final Log LOG = LogFactory.getLog(GlobalMetricRegistriesAdapter.class);
71  
72    private class MetricsSourceAdapter implements MetricsSource {
73      private final MetricRegistry registry;
74      MetricsSourceAdapter(MetricRegistry registry) {
75        this.registry = registry;
76      }
77  
78      @Override
79      public void getMetrics(MetricsCollector collector, boolean all) {
80        metricsAdapter.snapshotAllMetrics(registry, collector);
81      }
82    }
83  
84    private final MetricsExecutor executor;
85    private final AtomicBoolean stopped;
86    private final DefaultMetricsSystemHelper helper;
87    private final HBaseMetrics2HadoopMetricsAdapter metricsAdapter;
88    private final HashMap<MetricRegistryInfo, MetricsSourceAdapter> registeredSources;
89  
90    private GlobalMetricRegistriesAdapter() {
91      this.executor = new MetricsExecutorImpl();
92      this.stopped = new AtomicBoolean(false);
93      this.metricsAdapter = new HBaseMetrics2HadoopMetricsAdapter();
94      this.registeredSources = new HashMap<>();
95      this.helper = new DefaultMetricsSystemHelper();
96      executor.getExecutor().scheduleAtFixedRate(new Runnable(){
97        @Override
98        public void run() {
99          doRun();
100 
101       }}, 10, 10, TimeUnit.SECONDS);
102   }
103 
104   /**
105    * Make sure that this global MetricSource for hbase-metrics module based metrics are initialized.
106    * This should be called only once.
107    */
108   public static GlobalMetricRegistriesAdapter init() {
109     return new GlobalMetricRegistriesAdapter();
110   }
111 
112   public void stop() {
113     stopped.set(true);
114   }
115 
116   private void doRun() {
117     if (stopped.get()) {
118       executor.stop();
119       return;
120     }
121     if (LOG.isTraceEnabled()) {
122       LOG.trace("doRun called: " + registeredSources);
123     }
124 
125     Collection<MetricRegistry> registries = MetricRegistries.global().getMetricRegistries();
126     for (MetricRegistry registry : registries) {
127       MetricRegistryInfo info = registry.getMetricRegistryInfo();
128 
129       if (info.isExistingSource()) {
130         // If there is an already existing BaseSource for this MetricRegistry, skip it here. These
131         // types of registries are there only due to existing BaseSource implementations in the
132         // source code (like MetricsRegionServer, etc). This is to make sure that we can transition
133         // iteratively to the new hbase-metrics system. These type of MetricRegistry metrics will be
134         // exported from the BaseSource.getMetrics() call directly because there is already a
135         // MetricRecordBuilder there (see MetricsRegionServerSourceImpl).
136         continue;
137       }
138 
139       if (!registeredSources.containsKey(info)) {
140         if (LOG.isDebugEnabled()) {
141           LOG.debug("Registering adapter for the MetricRegistry: " + info.getMetricsJmxContext());
142         }
143         // register this as a MetricSource under different JMX Context'es.
144         MetricsSourceAdapter adapter = new MetricsSourceAdapter(registry);
145         LOG.info("Registering " + info.getMetricsJmxContext() + " " + info.getMetricsDescription());
146         DefaultMetricsSystem.instance().register(info.getMetricsJmxContext(),
147             info.getMetricsDescription(), adapter);
148         registeredSources.put(info, adapter);
149         // next collection will collect the newly registered MetricSource. Doing this here leads to
150         // ConcurrentModificationException.
151       }
152     }
153 
154     boolean removed = false;
155     // Remove registered sources if it is removed from the global registry
156     for (Iterator<Entry<MetricRegistryInfo, MetricsSourceAdapter>> it =
157          registeredSources.entrySet().iterator(); it.hasNext();) {
158       Entry<MetricRegistryInfo, MetricsSourceAdapter> entry = it.next();
159       MetricRegistryInfo info = entry.getKey();
160       Optional<MetricRegistry> found = MetricRegistries.global().get(info);
161       if (!found.isPresent()) {
162         if (LOG.isDebugEnabled()) {
163           LOG.debug("Removing adapter for the MetricRegistry: " + info.getMetricsJmxContext());
164         }
165         synchronized(DefaultMetricsSystem.instance()) {
166           unregisterSource(info);
167           helper.removeSourceName(info.getMetricsJmxContext());
168           helper.removeObjectName(info.getMetricsJmxContext());
169           it.remove();
170           removed = true;
171         }
172       }
173     }
174     if (removed) {
175       JmxCacheBuster.clearJmxCache();
176     }
177   }
178 
179   /**
180    * Use reflection to unregister the Hadoop metric source, since MetricsSystem#unregisterSource()
181    * is only available in Hadoop 2.6+ (HADOOP-10839)
182    */
183   protected void unregisterSource(MetricRegistryInfo info) {
184     // unregisterSource is only available in Hadoop 2.6+ (HADOOP-10839). Don't unregister for now
185     MetricsSystem metricsSystem = DefaultMetricsSystem.instance();
186     if (metricsSystem instanceof MetricsSystemImpl) {
187       try {
188         // it's actually a Map<String, MetricsSourceAdapter> , but MetricsSourceAdapter isn't
189         // accessible
190         @SuppressWarnings("unchecked")
191         Map<String, Object> sources =
192             (Map<String, Object>) FieldUtils.readField(metricsSystem, "sources", true);
193         String sourceName = info.getMetricsJmxContext();
194         if (sources.containsKey(sourceName)) {
195           Object sourceAdapter = sources.get(sourceName);
196           Method method = null;
197           try {
198             method = sourceAdapter.getClass().getDeclaredMethod("stop");
199           } catch (NoSuchMethodException e) {
200             LOG.info("Stop method not found on MetricsSourceAdapter");
201           } catch (SecurityException e) {
202             LOG.info("Don't have access to call stop method not found on MetricsSourceAdapter", e);
203           }
204           if (method != null) {
205             method.setAccessible(true);
206             try {
207               method.invoke(sourceAdapter);
208             } catch (IllegalArgumentException | InvocationTargetException e) {
209               LOG.warn("Couldn't invoke stop on metrics source adapter: " + sourceName);
210               e.printStackTrace();
211             }
212           }
213           sources.remove(sourceName);
214 
215         }
216         @SuppressWarnings("unchecked")
217         Map<String, MetricsSource> allSources =
218             (Map<String, MetricsSource>) FieldUtils.readField(metricsSystem, "allSources", true);
219         if (allSources.containsKey(sourceName)) {
220           allSources.remove(sourceName);
221         }
222       } catch (IllegalAccessException e) {
223         LOG.warn("Error unregistering metric source " + info.getMetricsJmxContext());
224       }
225     }
226   }
227 }