View Javadoc

1   /**
2    * Copyright 2010 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  
21  package org.apache.hadoop.hbase.regionserver.metrics;
22  
23  import java.lang.reflect.Field;
24  import java.lang.reflect.Method;
25  import java.util.Map;
26  import java.util.Map.Entry;
27  import java.util.concurrent.atomic.AtomicInteger;
28  import java.util.concurrent.atomic.AtomicLong;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.hbase.regionserver.HRegionServer;
33  import org.apache.hadoop.hbase.util.Pair;
34  import org.apache.hadoop.metrics.MetricsContext;
35  import org.apache.hadoop.metrics.MetricsRecord;
36  import org.apache.hadoop.metrics.MetricsUtil;
37  import org.apache.hadoop.metrics.Updater;
38  import org.apache.hadoop.metrics.util.MetricsBase;
39  import org.apache.hadoop.metrics.util.MetricsLongValue;
40  import org.apache.hadoop.metrics.util.MetricsRegistry;
41  import org.apache.hadoop.metrics.util.MetricsTimeVaryingRate;
42  
43  /**
44   *
45   * This class is for maintaining  the various RPC statistics
46   * and publishing them through the metrics interfaces.
47   * This also registers the JMX MBean for RPC.
48   * <p>
49   * This class has a number of metrics variables that are publicly accessible;
50   * these variables (objects) have methods to update their values;
51   * for example: rpcQueueTime.inc(time)
52   *
53   */
54  public class RegionServerDynamicMetrics implements Updater {
55    private static final String UNABLE_TO_CLEAR = "Unable to clear RegionServerDynamicMetrics";
56    
57    private MetricsRecord metricsRecord;
58    private MetricsContext context;
59    private final RegionServerDynamicStatistics rsDynamicStatistics;
60    private Method updateMbeanInfoIfMetricsListChanged = null;
61    private HRegionServer regionServer;
62    private static final Log LOG =
63      LogFactory.getLog(RegionServerDynamicStatistics.class);
64    
65    private boolean reflectionInitialized = false;
66    private boolean needsUpdateMessage = false;
67    private Field recordMetricMapField;
68    private Field registryMetricMapField;
69  
70    /**
71     * The metrics variables are public:
72     *  - they can be set directly by calling their set/inc methods
73     *  -they can also be read directly - e.g. JMX does this.
74     */
75    public final MetricsRegistry registry = new MetricsRegistry();
76  
77    private RegionServerDynamicMetrics(HRegionServer regionServer) {
78      this.context = MetricsUtil.getContext("hbase");
79      this.metricsRecord = MetricsUtil.createRecord(
80                              this.context,
81                              "RegionServerDynamicStatistics");
82      context.registerUpdater(this);
83      this.rsDynamicStatistics = new RegionServerDynamicStatistics(this.registry);
84      this.regionServer = regionServer;
85      try {
86        updateMbeanInfoIfMetricsListChanged =
87          this.rsDynamicStatistics.getClass().getSuperclass()
88          .getDeclaredMethod("updateMbeanInfoIfMetricsListChanged",
89              new Class[]{});
90        updateMbeanInfoIfMetricsListChanged.setAccessible(true);
91      } catch (Exception e) {
92        LOG.error(e);
93      }
94    }
95  
96    public static RegionServerDynamicMetrics newInstance(HRegionServer regionServer) {
97      RegionServerDynamicMetrics metrics =
98        new RegionServerDynamicMetrics(regionServer);
99      return metrics;
100   }
101 
102   public synchronized void setNumericMetric(String name, long amt) {
103     MetricsLongValue m = (MetricsLongValue)registry.get(name);
104     if (m == null) {
105       m = new MetricsLongValue(name, this.registry);
106       this.needsUpdateMessage = true;
107     }
108     m.set(amt);
109   }
110 
111   public synchronized void incrTimeVaryingMetric(
112       String name,
113       long amt,
114       int numOps) {
115     MetricsTimeVaryingRate m = (MetricsTimeVaryingRate)registry.get(name);
116     if (m == null) {
117       m = new MetricsTimeVaryingRate(name, this.registry);
118       this.needsUpdateMessage = true;
119     }
120     if (numOps > 0) {
121       m.inc(numOps, amt);
122     }
123   }
124   
125   /**
126    * Clear all metrics this exposes. 
127    * Uses reflection to clear them from hadoop metrics side as well.
128    */
129   @SuppressWarnings("rawtypes")
130   public void clear() {
131     this.needsUpdateMessage = true;
132     // If this is the first clear use reflection to get the two maps that hold copies of our 
133     // metrics on the hadoop metrics side. We have to use reflection because there is not 
134     // remove metrics on the hadoop side. If we can't get them then clearing old metrics 
135     // is not possible and bailing out early is our best option.
136     if (!this.reflectionInitialized) {
137       this.reflectionInitialized = true;
138       try {
139         this.recordMetricMapField = this.metricsRecord.getClass().getDeclaredField("metricTable");
140         this.recordMetricMapField.setAccessible(true);
141       } catch (SecurityException e) {
142         LOG.debug(UNABLE_TO_CLEAR);
143         return;
144       } catch (NoSuchFieldException e) {
145         LOG.debug(UNABLE_TO_CLEAR);
146         return;
147       }
148 
149       try {
150         this.registryMetricMapField = this.registry.getClass().getDeclaredField("metricsList");
151         this.registryMetricMapField.setAccessible(true);
152       } catch (SecurityException e) {
153         LOG.debug(UNABLE_TO_CLEAR);
154         return;
155       } catch (NoSuchFieldException e) {
156         LOG.debug(UNABLE_TO_CLEAR);
157         return;
158       } 
159     }
160 
161     
162     //If we found both fields then try and clear the maps.
163     if (this.recordMetricMapField != null && this.registryMetricMapField != null) {
164       try {
165         Map recordMap = (Map) this.recordMetricMapField.get(this.metricsRecord);
166         recordMap.clear();
167         Map registryMap = (Map) this.registryMetricMapField.get(this.registry);
168         registryMap.clear();
169       } catch (IllegalArgumentException e) {
170         LOG.debug(UNABLE_TO_CLEAR);
171       } catch (IllegalAccessException e) {
172         LOG.debug(UNABLE_TO_CLEAR);
173       }
174     } else {
175       LOG.debug(UNABLE_TO_CLEAR);
176     }
177   }
178 
179   /**
180    * Push the metrics to the monitoring subsystem on doUpdate() call.
181    * @param context ctx
182    */
183   public void doUpdates(MetricsContext context) {
184     /* get dynamically created numeric metrics, and push the metrics */
185     for (Entry<String, AtomicLong> entry : RegionMetricsStorage.getNumericMetrics().entrySet()) {
186       this.setNumericMetric(entry.getKey(), entry.getValue().getAndSet(0));
187     }
188 
189     /* export estimated size of all response queues */
190     if (regionServer != null) {
191       long responseQueueSize = regionServer.getResponseQueueSize();
192       this.setNumericMetric("responseQueuesSize", responseQueueSize);
193     }
194 
195     /* get dynamically created numeric metrics, and push the metrics.
196      * These ones aren't to be reset; they are cumulative. */
197     for (Entry<String, AtomicLong> entry : RegionMetricsStorage.getNumericPersistentMetrics().entrySet()) {
198       this.setNumericMetric(entry.getKey(), entry.getValue().get());
199     }
200     /* get dynamically created time varying metrics, and push the metrics */
201     for (Entry<String, Pair<AtomicLong, AtomicInteger>> entry :
202         RegionMetricsStorage.getTimeVaryingMetrics().entrySet()) {
203       Pair<AtomicLong, AtomicInteger> value = entry.getValue();
204       this.incrTimeVaryingMetric(entry.getKey(),
205           value.getFirst().getAndSet(0),
206           value.getSecond().getAndSet(0));
207     }
208 
209     // If there are new metrics sending this message to jmx tells it to update everything.
210     // This is not ideal we should just move to metrics2 that has full support for dynamic metrics.
211     if (needsUpdateMessage) {
212       try {
213         if (updateMbeanInfoIfMetricsListChanged != null) {
214           updateMbeanInfoIfMetricsListChanged.invoke(this.rsDynamicStatistics,
215               new Object[]{});
216         }
217       } catch (Exception e) {
218         LOG.error(e);
219       }
220       needsUpdateMessage = false;
221     }
222 
223 
224     synchronized (registry) {
225       // Iterate through the registry to propagate the different rpc metrics.
226       for (String metricName : registry.getKeyList() ) {
227         MetricsBase value = registry.get(metricName);
228         value.pushMetric(metricsRecord);
229       }
230     }
231     metricsRecord.update();
232   }
233 
234   public void shutdown() {
235     if (rsDynamicStatistics != null)
236       rsDynamicStatistics.shutdown();
237   }
238 }