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  
19  package org.apache.hadoop.hbase.coprocessor;
20  
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertFalse;
23  import static org.junit.Assert.assertNotEquals;
24  import static org.junit.Assert.assertNull;
25  import static org.junit.Assert.assertTrue;
26  
27  import java.io.IOException;
28  import java.util.List;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.conf.Configuration;
33  import org.apache.hadoop.hbase.Cell;
34  import org.apache.hadoop.hbase.CoprocessorEnvironment;
35  import org.apache.hadoop.hbase.HBaseTestingUtility;
36  import org.apache.hadoop.hbase.HColumnDescriptor;
37  import org.apache.hadoop.hbase.HRegionInfo;
38  import org.apache.hadoop.hbase.HRegionLocation;
39  import org.apache.hadoop.hbase.HTableDescriptor;
40  import org.apache.hadoop.hbase.TableName;
41  import org.apache.hadoop.hbase.Waiter.Predicate;
42  import org.apache.hadoop.hbase.client.Admin;
43  import org.apache.hadoop.hbase.client.Connection;
44  import org.apache.hadoop.hbase.client.ConnectionFactory;
45  import org.apache.hadoop.hbase.client.Get;
46  import org.apache.hadoop.hbase.client.Mutation;
47  import org.apache.hadoop.hbase.client.Put;
48  import org.apache.hadoop.hbase.client.RegionLocator;
49  import org.apache.hadoop.hbase.client.Table;
50  import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
51  import org.apache.hadoop.hbase.metrics.Counter;
52  import org.apache.hadoop.hbase.metrics.Metric;
53  import org.apache.hadoop.hbase.metrics.MetricRegistries;
54  import org.apache.hadoop.hbase.metrics.MetricRegistry;
55  import org.apache.hadoop.hbase.metrics.MetricRegistryInfo;
56  import org.apache.hadoop.hbase.metrics.Timer;
57  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
58  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos;
59  import org.apache.hadoop.hbase.protobuf.generated.MultiRowMutationProtos.MultiRowMutationService;
60  import org.apache.hadoop.hbase.protobuf.generated.MultiRowMutationProtos.MutateRowsRequest;
61  import org.apache.hadoop.hbase.protobuf.generated.MultiRowMutationProtos.MutateRowsResponse;
62  import org.apache.hadoop.hbase.regionserver.HRegionServer;
63  import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
64  import org.apache.hadoop.hbase.testclassification.CoprocessorTests;
65  import org.apache.hadoop.hbase.testclassification.MediumTests;
66  import org.apache.hadoop.hbase.util.Bytes;
67  import org.junit.AfterClass;
68  import org.junit.Before;
69  import org.junit.BeforeClass;
70  import org.junit.Test;
71  import org.junit.experimental.categories.Category;
72  
73  import com.google.common.base.Optional;
74  import com.google.common.collect.Lists;
75  import com.google.protobuf.RpcCallback;
76  import com.google.protobuf.RpcController;
77  import com.google.protobuf.ServiceException;
78  
79  /**
80   * Testing of coprocessor metrics end-to-end.
81   */
82  @Category({CoprocessorTests.class, MediumTests.class})
83  public class TestCoprocessorMetrics {
84  
85    private static final Log LOG = LogFactory.getLog(TestCoprocessorMetrics.class);
86    private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
87  
88    private static final byte[] foo = Bytes.toBytes("foo");
89    private static final byte[] bar = Bytes.toBytes("bar");
90    /**
91     * MasterObserver that has a Timer metric for create table operation.
92     */
93    public static class CustomMasterObserver extends BaseMasterObserver {
94      private Timer createTableTimer;
95      private long start = Long.MIN_VALUE;
96  
97      @Override
98      public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
99                                 HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
100       super.preCreateTable(ctx, desc, regions);
101 
102       // we rely on the fact that there is only 1 instance of our MasterObserver
103       this.start = System.currentTimeMillis();
104     }
105 
106     @Override
107     public void postCreateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
108                                 HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
109       super.postCreateTable(ctx, desc, regions);
110       if (this.start > 0) {
111         long time = System.currentTimeMillis() - start;
112         LOG.info("Create table took: " + time);
113         createTableTimer.updateMillis(time);
114       }
115     }
116 
117     @Override
118     public void start(CoprocessorEnvironment env) throws IOException {
119       super.start(env);
120       if (env instanceof MasterCoprocessorEnvironment) {
121         MetricRegistry registry =
122             ((MasterCoprocessorEnvironment) env).getMetricRegistryForMaster();
123 
124         createTableTimer  = registry.timer("CreateTable");
125       }
126     }
127   }
128 
129   /**
130    * RegionServerObserver that has a Counter for rollWAL requests.
131    */
132   public static class CustomRegionServerObserver extends BaseRegionServerObserver {
133     /** This is the Counter metric object to keep track of the current count across invocations */
134     private Counter rollWALCounter;
135     @Override
136     public void postRollWALWriterRequest(ObserverContext<RegionServerCoprocessorEnvironment> ctx)
137         throws IOException {
138       // Increment the Counter whenever the coprocessor is called
139       rollWALCounter.increment();
140       super.postRollWALWriterRequest(ctx);
141     }
142 
143     @Override
144     public void start(CoprocessorEnvironment env) throws IOException {
145       super.start(env);
146       if (env instanceof RegionServerCoprocessorEnvironment) {
147         MetricRegistry registry =
148             ((RegionServerCoprocessorEnvironment) env).getMetricRegistryForRegionServer();
149 
150         if (rollWALCounter == null) {
151           rollWALCounter = registry.counter("rollWALRequests");
152         }
153       }
154     }
155   }
156 
157   /**
158    * WALObserver that has a Counter for walEdits written.
159    */
160   public static class CustomWALObserver extends BaseWALObserver {
161     private Counter walEditsCount;
162 
163     @Override
164     public void postWALWrite(ObserverContext<? extends WALCoprocessorEnvironment> ctx,
165                              HRegionInfo info, org.apache.hadoop.hbase.wal.WALKey logKey,
166                              WALEdit logEdit) throws IOException {
167       super.postWALWrite(ctx, info, logKey, logEdit);
168       walEditsCount.increment();
169     }
170 
171     @Override
172     public void start(CoprocessorEnvironment env) throws IOException {
173       super.start(env);
174       if (env instanceof WALCoprocessorEnvironment) {
175         MetricRegistry registry =
176             ((WALCoprocessorEnvironment) env).getMetricRegistryForRegionServer();
177 
178         if (walEditsCount == null) {
179           walEditsCount = registry.counter("walEditsCount");
180         }
181       }
182     }
183   }
184 
185   /**
186    * RegionObserver that has a Counter for preGet()
187    */
188   public static class CustomRegionObserver extends BaseRegionObserver {
189     private Counter preGetCounter;
190 
191     @Override
192     public void preGetOp(ObserverContext<RegionCoprocessorEnvironment> e, Get get,
193                          List<Cell> results) throws IOException {
194       super.preGetOp(e, get, results);
195       preGetCounter.increment();
196     }
197 
198     @Override
199     public void start(CoprocessorEnvironment env) throws IOException {
200       super.start(env);
201 
202       if (env instanceof RegionCoprocessorEnvironment) {
203         MetricRegistry registry =
204             ((RegionCoprocessorEnvironment) env).getMetricRegistryForRegionServer();
205 
206         if (preGetCounter == null) {
207           preGetCounter = registry.counter("preGetRequests");
208         }
209       }
210     }
211   }
212 
213   public static class CustomRegionObserver2 extends CustomRegionObserver {
214   }
215 
216   /**
217    * RegionEndpoint to test metrics from endpoint calls
218    */
219   public static class CustomRegionEndpoint extends MultiRowMutationEndpoint {
220 
221     private Timer endpointExecution;
222 
223     @Override
224     public void mutateRows(RpcController controller, MutateRowsRequest request,
225                            RpcCallback<MutateRowsResponse> done) {
226       long start = System.nanoTime();
227       super.mutateRows(controller, request, done);
228       endpointExecution.updateNanos(System.nanoTime() - start);
229     }
230 
231     @Override
232     public void start(CoprocessorEnvironment env) throws IOException {
233       super.start(env);
234 
235       if (env instanceof RegionCoprocessorEnvironment) {
236         MetricRegistry registry =
237             ((RegionCoprocessorEnvironment) env).getMetricRegistryForRegionServer();
238 
239         if (endpointExecution == null) {
240           endpointExecution = registry.timer("EndpointExecution");
241         }
242       }
243     }
244   }
245 
246   @BeforeClass
247   public static void setupBeforeClass() throws Exception {
248     Configuration conf = UTIL.getConfiguration();
249     // inject master, regionserver and WAL coprocessors
250     conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
251         CustomMasterObserver.class.getName());
252     conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY,
253         CustomRegionServerObserver.class.getName());
254     conf.set(CoprocessorHost.WAL_COPROCESSOR_CONF_KEY,
255         CustomWALObserver.class.getName());
256     conf.setBoolean(CoprocessorHost.ABORT_ON_ERROR_KEY, true);
257     UTIL.startMiniCluster();
258   }
259 
260   @AfterClass
261   public static void teardownAfterClass() throws Exception {
262     UTIL.shutdownMiniCluster();
263   }
264 
265   @Before
266   public void setup() throws IOException {
267     try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
268          Admin admin = connection.getAdmin()) {
269       for (HTableDescriptor htd : admin.listTables()) {
270         UTIL.deleteTable(htd.getTableName());
271       }
272     }
273   }
274 
275   @Test
276   public void testMasterObserver() throws IOException {
277     // Find out the MetricRegistry used by the CP using the global registries
278     MetricRegistryInfo info = MetricsCoprocessor.createRegistryInfoForMasterCoprocessor(
279         CustomMasterObserver.class.getName());
280     Optional<MetricRegistry> registry =  MetricRegistries.global().get(info);
281     assertTrue(registry.isPresent());
282 
283     Optional<Metric> metric = registry.get().get("CreateTable");
284     assertTrue(metric.isPresent());
285 
286     try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
287          Admin admin = connection.getAdmin()) {
288 
289       Timer createTableTimer = (Timer)metric.get();
290       long prevCount = createTableTimer.getHistogram().getCount();
291       LOG.info("Creating table");
292       admin.createTable(
293           new HTableDescriptor("testMasterObserver")
294               .addFamily(new HColumnDescriptor("foo")));
295 
296       assertEquals(1, createTableTimer.getHistogram().getCount() - prevCount);
297     }
298   }
299 
300   @Test
301   public void testRegionServerObserver() throws IOException {
302     try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
303          Admin admin = connection.getAdmin()) {
304       LOG.info("Rolling WALs");
305       admin.rollWALWriter(UTIL.getMiniHBaseCluster().getServerHoldingMeta());
306     }
307 
308     // Find out the MetricRegistry used by the CP using the global registries
309     MetricRegistryInfo info = MetricsCoprocessor.createRegistryInfoForRSCoprocessor(
310         CustomRegionServerObserver.class.getName());
311 
312     Optional<MetricRegistry> registry =  MetricRegistries.global().get(info);
313     assertTrue(registry.isPresent());
314 
315     Optional<Metric> metric = registry.get().get("rollWALRequests");
316     assertTrue(metric.isPresent());
317 
318     Counter rollWalRequests = (Counter)metric.get();
319     assertEquals(1, rollWalRequests.getCount());
320   }
321 
322   @Test
323   public void testWALObserver() throws IOException {
324     // Find out the MetricRegistry used by the CP using the global registries
325     MetricRegistryInfo info = MetricsCoprocessor.createRegistryInfoForWALCoprocessor(
326         CustomWALObserver.class.getName());
327 
328     Optional<MetricRegistry> registry =  MetricRegistries.global().get(info);
329     assertTrue(registry.isPresent());
330 
331     Optional<Metric> metric = registry.get().get("walEditsCount");
332     assertTrue(metric.isPresent());
333 
334     try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
335          Admin admin = connection.getAdmin()) {
336       admin.createTable(
337           new HTableDescriptor("testWALObserver")
338               .addFamily(new HColumnDescriptor("foo")));
339 
340       Counter rollWalRequests = (Counter)metric.get();
341       long prevCount = rollWalRequests.getCount();
342       assertTrue(prevCount > 0);
343 
344       try (Table table = connection.getTable(TableName.valueOf("testWALObserver"))) {
345         table.put(new Put(foo).addColumn(foo, foo, foo));
346       }
347 
348       assertEquals(1, rollWalRequests.getCount() - prevCount);
349     }
350   }
351 
352   /**
353    * Helper for below tests
354    */
355   private void assertPreGetRequestsCounter(Class<?> coprocClass) {
356     // Find out the MetricRegistry used by the CP using the global registries
357     MetricRegistryInfo info = MetricsCoprocessor.createRegistryInfoForRegionCoprocessor(
358         coprocClass.getName());
359 
360     Optional<MetricRegistry> registry =  MetricRegistries.global().get(info);
361     assertTrue(registry.isPresent());
362 
363     Optional<Metric> metric = registry.get().get("preGetRequests");
364     assertTrue(metric.isPresent());
365 
366     Counter preGetRequests = (Counter)metric.get();
367     assertEquals(2, preGetRequests.getCount());
368   }
369 
370   @Test
371   public void testRegionObserverSingleRegion() throws IOException {
372     TableName tableName = TableName.valueOf("testRegionObserverSingleRegion");
373     try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
374          Admin admin = connection.getAdmin()) {
375       admin.createTable(
376           new HTableDescriptor(tableName)
377               .addFamily(new HColumnDescriptor(foo))
378               // add the coprocessor for the region
379               .addCoprocessor(CustomRegionObserver.class.getName()));
380       try (Table table = connection.getTable(tableName)) {
381         table.get(new Get(foo));
382         table.get(new Get(foo)); // 2 gets
383       }
384     }
385 
386     assertPreGetRequestsCounter(CustomRegionObserver.class);
387   }
388 
389   @Test
390   public void testRegionObserverMultiRegion() throws IOException {
391     TableName tableName = TableName.valueOf("testRegionObserverMultiRegion");
392     try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
393          Admin admin = connection.getAdmin()) {
394       admin.createTable(
395           new HTableDescriptor(tableName)
396               .addFamily(new HColumnDescriptor(foo))
397               // add the coprocessor for the region
398               .addCoprocessor(CustomRegionObserver.class.getName())
399           , new byte[][]{foo}); // create with 2 regions
400       try (Table table = connection.getTable(tableName);
401            RegionLocator locator = connection.getRegionLocator(tableName)) {
402         table.get(new Get(bar));
403         table.get(new Get(foo)); // 2 gets to 2 separate regions
404         assertEquals(2, locator.getAllRegionLocations().size());
405         assertNotEquals(locator.getRegionLocation(bar).getRegionInfo(),
406             locator.getRegionLocation(foo).getRegionInfo());
407       }
408     }
409 
410     assertPreGetRequestsCounter(CustomRegionObserver.class);
411   }
412 
413   @Test
414   public void testRegionObserverMultiTable() throws IOException {
415     TableName tableName1 = TableName.valueOf("testRegionObserverMultiTable1");
416     TableName tableName2 = TableName.valueOf("testRegionObserverMultiTable2");
417     try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
418          Admin admin = connection.getAdmin()) {
419       admin.createTable(
420           new HTableDescriptor(tableName1)
421               .addFamily(new HColumnDescriptor(foo))
422               // add the coprocessor for the region
423               .addCoprocessor(CustomRegionObserver.class.getName()));
424       admin.createTable(
425           new HTableDescriptor(tableName2)
426               .addFamily(new HColumnDescriptor(foo))
427               // add the coprocessor for the region
428               .addCoprocessor(CustomRegionObserver.class.getName()));
429       try (Table table1 = connection.getTable(tableName1);
430            Table table2 = connection.getTable(tableName2);) {
431         table1.get(new Get(bar));
432         table2.get(new Get(foo)); // 2 gets to 2 separate tables
433       }
434     }
435     assertPreGetRequestsCounter(CustomRegionObserver.class);
436   }
437 
438   @Test
439   public void testRegionObserverMultiCoprocessor() throws IOException {
440     TableName tableName = TableName.valueOf("testRegionObserverMultiCoprocessor");
441     try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
442          Admin admin = connection.getAdmin()) {
443       admin.createTable(
444           new HTableDescriptor(tableName)
445               .addFamily(new HColumnDescriptor(foo))
446               // add the coprocessor for the region. We add two different coprocessors
447               .addCoprocessor(CustomRegionObserver.class.getName())
448               .addCoprocessor(CustomRegionObserver2.class.getName()));
449       try (Table table = connection.getTable(tableName)) {
450         table.get(new Get(foo));
451         table.get(new Get(foo)); // 2 gets
452       }
453     }
454 
455     // we will have two counters coming from two coprocs, in two different MetricRegistries
456     assertPreGetRequestsCounter(CustomRegionObserver.class);
457     assertPreGetRequestsCounter(CustomRegionObserver2.class);
458   }
459 
460   @Test
461   public void testRegionObserverAfterRegionClosed() throws IOException {
462     TableName tableName = TableName.valueOf("testRegionObserverAfterRegionClosed");
463     try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
464          Admin admin = connection.getAdmin()) {
465       admin.createTable(
466           new HTableDescriptor(tableName)
467               .addFamily(new HColumnDescriptor(foo))
468               // add the coprocessor for the region
469               .addCoprocessor(CustomRegionObserver.class.getName())
470           , new byte[][]{foo}); // create with 2 regions
471       try (Table table = connection.getTable(tableName)) {
472         table.get(new Get(foo));
473         table.get(new Get(foo)); // 2 gets
474       }
475 
476       assertPreGetRequestsCounter(CustomRegionObserver.class);
477 
478       // close one of the regions
479       try (RegionLocator locator = connection.getRegionLocator(tableName)) {
480         final HRegionLocation loc = locator.getRegionLocation(foo);
481         admin.closeRegion(loc.getServerName(), loc.getRegionInfo());
482 
483         final HRegionServer server = UTIL.getMiniHBaseCluster().getRegionServer(loc.getServerName());
484         UTIL.waitFor(30000,new Predicate<IOException>() {
485           @Override
486           public boolean evaluate() throws IOException {
487             return server.getOnlineRegion(loc.getRegionInfo().getRegionName()) == null;
488           }
489         });
490         assertNull(server.getOnlineRegion(loc.getRegionInfo().getRegionName()));
491       }
492 
493       // with only 1 region remaining, we should still be able to find the Counter
494       assertPreGetRequestsCounter(CustomRegionObserver.class);
495 
496       // close the table
497       admin.disableTable(tableName);
498 
499       MetricRegistryInfo info = MetricsCoprocessor.createRegistryInfoForRegionCoprocessor(
500           CustomRegionObserver.class.getName());
501 
502       // ensure that MetricRegistry is deleted
503       Optional<MetricRegistry> registry =  MetricRegistries.global().get(info);
504       assertFalse(registry.isPresent());
505     }
506   }
507 
508   @Test
509   public void testRegionObserverEndpoint() throws IOException, ServiceException {
510     TableName tableName = TableName.valueOf("testRegionObserverEndpoint");
511     try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
512          Admin admin = connection.getAdmin()) {
513       admin.createTable(
514           new HTableDescriptor(tableName)
515               .addFamily(new HColumnDescriptor(foo))
516               // add the coprocessor for the region
517               .addCoprocessor(CustomRegionEndpoint.class.getName()));
518 
519       try (Table table = connection.getTable(tableName)) {
520         List<Put> mutations = Lists.newArrayList(new Put(foo), new Put(bar));
521         MutateRowsRequest.Builder mrmBuilder = MutateRowsRequest.newBuilder();
522 
523         for (Mutation mutation : mutations) {
524           mrmBuilder.addMutationRequest(ProtobufUtil.toMutation(
525               ClientProtos.MutationProto.MutationType.PUT, mutation));
526         }
527 
528         CoprocessorRpcChannel channel = table.coprocessorService(bar);
529         MultiRowMutationService.BlockingInterface service =
530             MultiRowMutationService.newBlockingStub(channel);
531         MutateRowsRequest mrm = mrmBuilder.build();
532         service.mutateRows(null, mrm);
533       }
534     }
535 
536     // Find out the MetricRegistry used by the CP using the global registries
537     MetricRegistryInfo info = MetricsCoprocessor.createRegistryInfoForRegionCoprocessor(
538         CustomRegionEndpoint.class.getName());
539 
540     Optional<MetricRegistry> registry =  MetricRegistries.global().get(info);
541     assertTrue(registry.isPresent());
542 
543     Optional<Metric> metric = registry.get().get("EndpointExecution");
544     assertTrue(metric.isPresent());
545 
546     Timer endpointExecutions = (Timer)metric.get();
547     assertEquals(1, endpointExecutions.getHistogram().getCount());
548   }
549 }