View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  package org.apache.hadoop.hbase.coprocessor;
21  
22  import static org.junit.Assert.assertTrue;
23  import static org.junit.Assert.fail;
24  
25  import java.io.IOException;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.Hashtable;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Random;
35  import java.util.Set;
36  
37  import javax.management.MBeanAttributeInfo;
38  import javax.management.MBeanInfo;
39  import javax.management.MBeanServerConnection;
40  import javax.management.ObjectInstance;
41  import javax.management.ObjectName;
42  import javax.management.remote.JMXConnector;
43  import javax.management.remote.JMXConnectorFactory;
44  
45  import org.apache.hadoop.conf.Configuration;
46  import org.apache.hadoop.hbase.HBaseTestingUtility;
47  import org.apache.hadoop.hbase.JMXListener;
48  import org.apache.hadoop.hbase.TableName;
49  import org.apache.hadoop.hbase.Waiter.Predicate;
50  import org.apache.hadoop.hbase.client.Get;
51  import org.apache.hadoop.hbase.client.Put;
52  import org.apache.hadoop.hbase.client.Table;
53  import org.apache.hadoop.hbase.testclassification.CoprocessorTests;
54  import org.apache.hadoop.hbase.testclassification.MediumTests;
55  import org.apache.hadoop.hbase.util.Bytes;
56  import org.hamcrest.CustomTypeSafeMatcher;
57  import org.hamcrest.Matcher;
58  import org.hamcrest.core.AllOf;
59  import org.junit.AfterClass;
60  import org.junit.BeforeClass;
61  import org.junit.Test;
62  import org.junit.experimental.categories.Category;
63  import org.slf4j.Logger;
64  import org.slf4j.LoggerFactory;
65  
66  
67  @Category({ CoprocessorTests.class, MediumTests.class })
68  public class TestMetaTableMetrics {
69  
70    private static final Logger LOG = LoggerFactory.getLogger(TestMetaTableMetrics.class);
71  
72    private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
73    private static final TableName NAME1 = TableName.valueOf("TestExampleMetaTableMetricsOne");
74    private static final byte[] FAMILY = Bytes.toBytes("f");
75    private static final byte[] QUALIFIER = Bytes.toBytes("q");
76    private static final int NUM_ROWS = 5;
77    private static final String value = "foo";
78    private static final String METRICS_ATTRIBUTE_NAME_PREFIX = "MetaTable_";
79    private static final List<String> METRICS_ATTRIBUTE_NAME_POSTFIXES =
80      Arrays.asList("_count", "_mean_rate", "_1min_rate", "_5min_rate", "_15min_rate");
81    private static int connectorPort = 61120;
82  
83    private final byte[] cf = Bytes.toBytes("info");
84    private final byte[] col = Bytes.toBytes("any");
85    private byte[] tablename;
86    private final int nthreads = 20;
87  
88    @BeforeClass
89    public static void setupBeforeClass() throws Exception {
90  
91      Configuration conf = UTIL.getConfiguration();
92      // Set system coprocessor so it can be applied to meta regions
93      UTIL.getConfiguration().set("hbase.coprocessor.region.classes",
94        MetaTableMetrics.class.getName());
95      conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, JMXListener.class.getName());
96      Random rand = new Random();
97      for (int i = 0; i < 10; i++) {
98        do {
99          int sign = i % 2 == 0 ? 1 : -1;
100         connectorPort += sign * rand.nextInt(100);
101       } while (!HBaseTestingUtility.available(connectorPort));
102       try {
103         conf.setInt("regionserver.rmi.registry.port", connectorPort);
104         UTIL.startMiniCluster(1);
105         break;
106       } catch (Exception e) {
107         LOG.debug("Encountered exception when starting cluster. Trying port {}", connectorPort, e);
108         try {
109           // this is to avoid "IllegalStateException: A mini-cluster is already running"
110           UTIL.shutdownMiniCluster();
111         } catch (Exception ex) {
112           LOG.debug("Encountered exception shutting down cluster", ex);
113         }
114       }
115     }
116   }
117 
118   @AfterClass
119   public static void tearDown() throws Exception {
120     UTIL.shutdownMiniCluster();
121   }
122 
123   // Verifies that meta table metrics exist in jmx. In case of one table (one region) with a single
124   // client: 9 metrics
125   // are generated and for each metrics, there should be 5 JMX attributes produced. e.g. for one
126   // table, there should
127   // be 5 MetaTable_table_<TableName>_request attributes, such as:
128   // - MetaTable_table_TestExampleMetaTableMetricsOne_request_count
129   // - MetaTable_table_TestExampleMetaTableMetricsOne_request_mean_rate
130   // - MetaTable_table_TestExampleMetaTableMetricsOne_request_1min_rate
131   // - MetaTable_table_TestExampleMetaTableMetricsOne_request_5min_rate
132   // - MetaTable_table_TestExampleMetaTableMetricsOne_request_15min_rate
133   @Test
134   public void testMetaTableMetricsInJmx() throws Exception {
135     UTIL.createTable(NAME1, new byte[][]{FAMILY});
136     writeData(NAME1);
137     readingSingleRowFromTheMetaTable();
138     UTIL.deleteTable(NAME1);
139 
140     UTIL.waitFor(30000, 2000, true, new Predicate<IOException>() {
141 
142       @Override
143       public boolean evaluate() throws IOException {
144         Map<String, Double> jmxMetrics = readMetaTableJmxMetrics();
145         boolean allMetricsFound = AllOf.allOf(
146           containsPositiveJmxAttributesFor("MetaTable_get_request"),
147           containsPositiveJmxAttributesFor("MetaTable_put_request"),
148           containsPositiveJmxAttributesFor("MetaTable_delete_request"),
149           containsPositiveJmxAttributesFor("MetaTable_region_.+_lossy_request"),
150           containsPositiveJmxAttributesFor("MetaTable_table_" + NAME1 + "_request"),
151           containsPositiveJmxAttributesFor("MetaTable_client_.+_put_request"),
152           containsPositiveJmxAttributesFor("MetaTable_client_.+_get_request"),
153           containsPositiveJmxAttributesFor("MetaTable_client_.+_delete_request"),
154           containsPositiveJmxAttributesFor("MetaTable_client_.+_lossy_request")
155         ).matches(jmxMetrics);
156 
157         if (allMetricsFound) {
158           LOG.info("all the meta table metrics found with positive values: {}", jmxMetrics);
159         } else {
160           LOG.warn("couldn't find all the meta table metrics with positive values: {}", jmxMetrics);
161         }
162         return allMetricsFound;
163       }
164     });
165 
166   }
167 
168   @Test
169   public void testConcurrentAccess() {
170     try {
171       tablename = Bytes.toBytes("hbase:meta");
172       int numRows = 3000;
173       int numRowsInTableBefore = UTIL.countRows(TableName.valueOf(tablename));
174       putData(numRows);
175       Thread.sleep(2000);
176       int numRowsInTableAfter = UTIL.countRows(TableName.valueOf(tablename));
177       assertTrue(numRowsInTableAfter >= numRowsInTableBefore + numRows);
178       getData(numRows);
179     } catch (InterruptedException e) {
180       LOG.info("Caught InterruptedException while testConcurrentAccess: {}", e.getMessage());
181       fail();
182     } catch (IOException e) {
183       LOG.info("Caught IOException while testConcurrentAccess: {}", e.getMessage());
184       fail();
185     }
186   }
187 
188   private void writeData(TableName tableName) throws IOException {
189     try (Table t = UTIL.getConnection().getTable(tableName)) {
190       List<Put> puts = new ArrayList<>(NUM_ROWS);
191       for (int i = 0; i < NUM_ROWS; i++) {
192         Put p = new Put(Bytes.toBytes(i + 1));
193         p.addColumn(FAMILY, QUALIFIER, Bytes.toBytes(value));
194         puts.add(p);
195       }
196       t.put(puts);
197     }
198   }
199 
200   private void readingSingleRowFromTheMetaTable() throws IOException {
201     TableName metaTableName = TableName.valueOf(Bytes.toBytes("hbase:meta"));
202     try (Table metaTable = UTIL.getConnection().getTable(metaTableName)) {
203       Get get = new Get(Bytes.toBytes(1));
204       metaTable.get(get);
205     }
206   }
207 
208   private Matcher<Map<String, Double>> containsPositiveJmxAttributesFor(final String regexp) {
209     return new CustomTypeSafeMatcher<Map<String, Double>>(
210       "failed to find all the 5 positive JMX attributes for: " + regexp) {
211 
212       @Override
213       protected boolean matchesSafely(final Map<String, Double> values) {
214         for (String key : values.keySet()) {
215           for (String metricsNamePostfix : METRICS_ATTRIBUTE_NAME_POSTFIXES) {
216             if (key.matches(regexp + metricsNamePostfix) && values.get(key) > 0) {
217               return true;
218             }
219           }
220         }
221         return false;
222       }
223     };
224   }
225 
226   /**
227    * Read the attributes from Hadoop->HBase->RegionServer->MetaTableMetrics in JMX
228    * @throws IOException when fails to retrieve jmx metrics.
229    */
230   private Map<String, Double> readMetaTableJmxMetrics() throws IOException {
231     JMXConnector connector = null;
232     ObjectName target = null;
233     MBeanServerConnection mb = null;
234     try {
235       connector =
236         JMXConnectorFactory.connect(JMXListener.buildJMXServiceURL(connectorPort, connectorPort));
237       mb = connector.getMBeanServerConnection();
238 
239       @SuppressWarnings("JdkObsolete")
240       Hashtable<String, String> pairs = new Hashtable<>();
241       pairs.put("service", "HBase");
242       pairs.put("name", "RegionServer");
243       pairs.put("sub",
244                 "Coprocessor.Region.CP_org.apache.hadoop.hbase.coprocessor.MetaTableMetrics");
245       target = new ObjectName("Hadoop", pairs);
246       MBeanInfo beanInfo = mb.getMBeanInfo(target);
247 
248       Map<String, Double> existingAttrs = new HashMap<>();
249       for (MBeanAttributeInfo attrInfo : beanInfo.getAttributes()) {
250         Object value = mb.getAttribute(target, attrInfo.getName());
251         if (attrInfo.getName().startsWith(METRICS_ATTRIBUTE_NAME_PREFIX)
252           && value instanceof Number) {
253           existingAttrs.put(attrInfo.getName(), Double.parseDouble(value.toString()));
254         }
255       }
256       LOG.info("MBean Found: {}", target);
257       return existingAttrs;
258     } catch (Exception e) {
259       LOG.warn("Failed to get Meta Table Metrics bean (will retry later): {}", target, e);
260       if (mb != null) {
261         Set<ObjectInstance> instances = mb.queryMBeans(null, null);
262         Iterator<ObjectInstance> iterator = instances.iterator();
263         LOG.debug("All the MBeans we found:");
264         while (iterator.hasNext()) {
265           ObjectInstance instance = iterator.next();
266           LOG.debug("Class and object name: {} [{}]", instance.getClassName(),
267                     instance.getObjectName());
268         }
269       }
270     } finally {
271       if (connector != null) {
272         try {
273           connector.close();
274         } catch (Exception e) {
275           e.printStackTrace();
276         }
277       }
278     }
279     return Collections.emptyMap();
280   }
281 
282   private void putData(int nrows) throws InterruptedException {
283     LOG.info("Putting {} rows in hbase:meta", nrows);
284     Thread[] threads = new Thread[nthreads];
285     for (int i = 1; i <= nthreads; i++) {
286       threads[i - 1] = new PutThread(1, nrows);
287     }
288     startThreadsAndWaitToJoin(threads);
289   }
290 
291   private void getData(int nrows) throws InterruptedException {
292     LOG.info("Getting {} rows from hbase:meta", nrows);
293     Thread[] threads = new Thread[nthreads];
294     for (int i = 1; i <= nthreads; i++) {
295       threads[i - 1] = new GetThread(1, nrows);
296     }
297     startThreadsAndWaitToJoin(threads);
298   }
299 
300   private void startThreadsAndWaitToJoin(Thread[] threads) throws InterruptedException {
301     for (int i = 1; i <= nthreads; i++) {
302       threads[i - 1].start();
303     }
304     for (int i = 1; i <= nthreads; i++) {
305       threads[i - 1].join();
306     }
307   }
308 
309   private class PutThread extends Thread {
310     int start;
311     int end;
312 
313     PutThread(int start, int end) {
314       this.start = start;
315       this.end = end;
316     }
317 
318     @Override
319     public void run() {
320       try (Table table = UTIL.getConnection().getTable(TableName.valueOf(tablename))) {
321         for (int i = start; i <= end; i++) {
322           Put p = new Put(Bytes.toBytes(String.format("tableName,rowKey%d,region%d", i, i)));
323           p.addColumn(cf, col, Bytes.toBytes("Value" + i));
324           table.put(p);
325         }
326       } catch (IOException e) {
327         LOG.warn("Caught IOException while PutThread operation", e);
328       }
329     }
330   }
331 
332   private class GetThread extends Thread {
333     int start;
334     int end;
335 
336     GetThread(int start, int end) {
337       this.start = start;
338       this.end = end;
339     }
340 
341     @Override
342     public void run() {
343       try (Table table = UTIL.getConnection().getTable(TableName.valueOf(tablename))) {
344         for (int i = start; i <= end; i++) {
345           Get get = new Get(Bytes.toBytes(String.format("tableName,rowKey%d,region%d", i, i)));
346           table.get(get);
347         }
348       } catch (IOException e) {
349         LOG.warn("Caught IOException while GetThread operation", e);
350       }
351     }
352   }
353 
354 }