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