blob: d7ac38a3595b8031551298f846b875a1a9a2bc99 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.coprocessor;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CoprocessorEnvironment;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.HRegionLocation;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.Mutation;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.RegionLocator;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
import org.apache.hadoop.hbase.metrics.Counter;
import org.apache.hadoop.hbase.metrics.Metric;
import org.apache.hadoop.hbase.metrics.MetricRegistries;
import org.apache.hadoop.hbase.metrics.MetricRegistry;
import org.apache.hadoop.hbase.metrics.MetricRegistryInfo;
import org.apache.hadoop.hbase.metrics.Timer;
import org.apache.hadoop.hbase.regionserver.HRegionServer;
import org.apache.hadoop.hbase.testclassification.CoprocessorTests;
import org.apache.hadoop.hbase.testclassification.MediumTests;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.wal.WALEdit;
import org.apache.hadoop.hbase.wal.WALKey;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.TestName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
import org.apache.hbase.thirdparty.com.google.protobuf.RpcCallback;
import org.apache.hbase.thirdparty.com.google.protobuf.RpcController;
import org.apache.hbase.thirdparty.com.google.protobuf.ServiceException;
import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos;
import org.apache.hadoop.hbase.shaded.protobuf.generated.MultiRowMutationProtos.MultiRowMutationService;
import org.apache.hadoop.hbase.shaded.protobuf.generated.MultiRowMutationProtos.MutateRowsRequest;
import org.apache.hadoop.hbase.shaded.protobuf.generated.MultiRowMutationProtos.MutateRowsResponse;
/**
* Testing of coprocessor metrics end-to-end.
*/
@Category({CoprocessorTests.class, MediumTests.class})
public class TestCoprocessorMetrics {
@ClassRule
public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(TestCoprocessorMetrics.class);
private static final Logger LOG = LoggerFactory.getLogger(TestCoprocessorMetrics.class);
private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
private static final byte[] foo = Bytes.toBytes("foo");
private static final byte[] bar = Bytes.toBytes("bar");
@Rule
public TestName name = new TestName();
/**
* MasterObserver that has a Timer metric for create table operation.
*/
public static class CustomMasterObserver implements MasterCoprocessor, MasterObserver {
private Timer createTableTimer;
private long start = Long.MIN_VALUE;
@Override
public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
TableDescriptor desc, RegionInfo[] regions) throws IOException {
// we rely on the fact that there is only 1 instance of our MasterObserver
this.start = System.currentTimeMillis();
}
@Override
public void postCreateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
TableDescriptor desc, RegionInfo[] regions) throws IOException {
if (this.start > 0) {
long time = System.currentTimeMillis() - start;
LOG.info("Create table took: " + time);
createTableTimer.updateMillis(time);
}
}
@Override
public void start(CoprocessorEnvironment env) throws IOException {
if (env instanceof MasterCoprocessorEnvironment) {
MetricRegistry registry =
((MasterCoprocessorEnvironment) env).getMetricRegistryForMaster();
createTableTimer = registry.timer("CreateTable");
}
}
@Override
public Optional<MasterObserver> getMasterObserver() {
return Optional.of(this);
}
}
/**
* RegionServerObserver that has a Counter for rollWAL requests.
*/
public static class CustomRegionServerObserver implements RegionServerCoprocessor,
RegionServerObserver {
/** This is the Counter metric object to keep track of the current count across invocations */
private Counter rollWALCounter;
@Override public Optional<RegionServerObserver> getRegionServerObserver() {
return Optional.of(this);
}
@Override
public void postRollWALWriterRequest(ObserverContext<RegionServerCoprocessorEnvironment> ctx)
throws IOException {
// Increment the Counter whenever the coprocessor is called
rollWALCounter.increment();
}
@Override
public void start(CoprocessorEnvironment env) throws IOException {
if (env instanceof RegionServerCoprocessorEnvironment) {
MetricRegistry registry =
((RegionServerCoprocessorEnvironment) env).getMetricRegistryForRegionServer();
if (rollWALCounter == null) {
rollWALCounter = registry.counter("rollWALRequests");
}
}
}
}
/**
* WALObserver that has a Counter for walEdits written.
*/
public static class CustomWALObserver implements WALCoprocessor, WALObserver {
private Counter walEditsCount;
@Override
public void postWALWrite(ObserverContext<? extends WALCoprocessorEnvironment> ctx,
RegionInfo info, WALKey logKey,
WALEdit logEdit) throws IOException {
walEditsCount.increment();
}
@Override
public void start(CoprocessorEnvironment env) throws IOException {
if (env instanceof WALCoprocessorEnvironment) {
MetricRegistry registry =
((WALCoprocessorEnvironment) env).getMetricRegistryForRegionServer();
if (walEditsCount == null) {
walEditsCount = registry.counter("walEditsCount");
}
}
}
@Override public Optional<WALObserver> getWALObserver() {
return Optional.of(this);
}
}
/**
* RegionObserver that has a Counter for preGet()
*/
public static class CustomRegionObserver implements RegionCoprocessor, RegionObserver {
private Counter preGetCounter;
@Override
public void preGetOp(ObserverContext<RegionCoprocessorEnvironment> e, Get get,
List<Cell> results) throws IOException {
preGetCounter.increment();
}
@Override
public Optional<RegionObserver> getRegionObserver() {
return Optional.of(this);
}
@Override
public void start(CoprocessorEnvironment env) throws IOException {
if (env instanceof RegionCoprocessorEnvironment) {
MetricRegistry registry =
((RegionCoprocessorEnvironment) env).getMetricRegistryForRegionServer();
if (preGetCounter == null) {
preGetCounter = registry.counter("preGetRequests");
}
}
}
}
public static class CustomRegionObserver2 extends CustomRegionObserver {
}
/**
* RegionEndpoint to test metrics from endpoint calls
*/
public static class CustomRegionEndpoint extends MultiRowMutationEndpoint {
private Timer endpointExecution;
@Override
public void mutateRows(RpcController controller, MutateRowsRequest request,
RpcCallback<MutateRowsResponse> done) {
long start = System.nanoTime();
super.mutateRows(controller, request, done);
endpointExecution.updateNanos(System.nanoTime() - start);
}
@Override
public void start(CoprocessorEnvironment env) throws IOException {
super.start(env);
if (env instanceof RegionCoprocessorEnvironment) {
MetricRegistry registry =
((RegionCoprocessorEnvironment) env).getMetricRegistryForRegionServer();
if (endpointExecution == null) {
endpointExecution = registry.timer("EndpointExecution");
}
}
}
}
@BeforeClass
public static void setupBeforeClass() throws Exception {
Configuration conf = UTIL.getConfiguration();
// inject master, regionserver and WAL coprocessors
conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
CustomMasterObserver.class.getName());
conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY,
CustomRegionServerObserver.class.getName());
conf.set(CoprocessorHost.WAL_COPROCESSOR_CONF_KEY,
CustomWALObserver.class.getName());
conf.setBoolean(CoprocessorHost.ABORT_ON_ERROR_KEY, true);
UTIL.startMiniCluster();
}
@AfterClass
public static void teardownAfterClass() throws Exception {
UTIL.shutdownMiniCluster();
}
@Before
public void setup() throws IOException {
try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
Admin admin = connection.getAdmin()) {
for (TableDescriptor htd : admin.listTableDescriptors()) {
UTIL.deleteTable(htd.getTableName());
}
}
}
@Test
public void testMasterObserver() throws IOException {
// Find out the MetricRegistry used by the CP using the global registries
MetricRegistryInfo info = MetricsCoprocessor.createRegistryInfoForMasterCoprocessor(
CustomMasterObserver.class.getName());
Optional<MetricRegistry> registry = MetricRegistries.global().get(info);
assertTrue(registry.isPresent());
Optional<Metric> metric = registry.get().get("CreateTable");
assertTrue(metric.isPresent());
try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
Admin admin = connection.getAdmin()) {
Timer createTableTimer = (Timer)metric.get();
long prevCount = createTableTimer.getHistogram().getCount();
LOG.info("Creating table");
TableDescriptorBuilder tableDescriptorBuilder =
TableDescriptorBuilder.newBuilder(TableName.valueOf(name.getMethodName()));
ColumnFamilyDescriptor columnFamilyDescriptor =
ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("foo")).build();
tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor);
admin.createTable(tableDescriptorBuilder.build());
assertEquals(1, createTableTimer.getHistogram().getCount() - prevCount);
}
}
@Test
public void testRegionServerObserver() throws IOException {
try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
Admin admin = connection.getAdmin()) {
LOG.info("Rolling WALs");
admin.rollWALWriter(UTIL.getMiniHBaseCluster().getServerHoldingMeta());
}
// Find out the MetricRegistry used by the CP using the global registries
MetricRegistryInfo info = MetricsCoprocessor.createRegistryInfoForRSCoprocessor(
CustomRegionServerObserver.class.getName());
Optional<MetricRegistry> registry = MetricRegistries.global().get(info);
assertTrue(registry.isPresent());
Optional<Metric> metric = registry.get().get("rollWALRequests");
assertTrue(metric.isPresent());
Counter rollWalRequests = (Counter)metric.get();
assertEquals(1, rollWalRequests.getCount());
}
@Test
public void testWALObserver() throws IOException {
// Find out the MetricRegistry used by the CP using the global registries
MetricRegistryInfo info = MetricsCoprocessor.createRegistryInfoForWALCoprocessor(
CustomWALObserver.class.getName());
Optional<MetricRegistry> registry = MetricRegistries.global().get(info);
assertTrue(registry.isPresent());
Optional<Metric> metric = registry.get().get("walEditsCount");
assertTrue(metric.isPresent());
try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
Admin admin = connection.getAdmin()) {
TableDescriptorBuilder tableDescriptorBuilder =
TableDescriptorBuilder.newBuilder(TableName.valueOf(name.getMethodName()));
ColumnFamilyDescriptor columnFamilyDescriptor =
ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("foo")).build();
tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor);
admin.createTable(tableDescriptorBuilder.build());
Counter rollWalRequests = (Counter)metric.get();
long prevCount = rollWalRequests.getCount();
assertTrue(prevCount > 0);
try (Table table = connection.getTable(TableName.valueOf(name.getMethodName()))) {
table.put(new Put(foo).addColumn(foo, foo, foo));
}
assertEquals(1, rollWalRequests.getCount() - prevCount);
}
}
/**
* Helper for below tests
*/
private void assertPreGetRequestsCounter(Class<?> coprocClass) {
// Find out the MetricRegistry used by the CP using the global registries
MetricRegistryInfo info = MetricsCoprocessor.createRegistryInfoForRegionCoprocessor(
coprocClass.getName());
Optional<MetricRegistry> registry = MetricRegistries.global().get(info);
assertTrue(registry.isPresent());
Optional<Metric> metric = registry.get().get("preGetRequests");
assertTrue(metric.isPresent());
Counter preGetRequests = (Counter)metric.get();
assertEquals(2, preGetRequests.getCount());
}
@Test
public void testRegionObserverSingleRegion() throws IOException {
final TableName tableName = TableName.valueOf(name.getMethodName());
try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
Admin admin = connection.getAdmin()) {
admin.createTable(TableDescriptorBuilder.newBuilder(tableName)
.setColumnFamily(ColumnFamilyDescriptorBuilder.of(foo))
// add the coprocessor for the region
.setCoprocessor(CustomRegionObserver.class.getName()).build());
try (Table table = connection.getTable(tableName)) {
table.get(new Get(foo));
table.get(new Get(foo)); // 2 gets
}
}
assertPreGetRequestsCounter(CustomRegionObserver.class);
}
@Test
public void testRegionObserverMultiRegion() throws IOException {
final TableName tableName = TableName.valueOf(name.getMethodName());
try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
Admin admin = connection.getAdmin()) {
admin.createTable(TableDescriptorBuilder.newBuilder(tableName)
.setColumnFamily(ColumnFamilyDescriptorBuilder.of(foo))
// add the coprocessor for the region
.setCoprocessor(CustomRegionObserver.class.getName()).build(), new byte[][] { foo });
// create with 2 regions
try (Table table = connection.getTable(tableName);
RegionLocator locator = connection.getRegionLocator(tableName)) {
table.get(new Get(bar));
table.get(new Get(foo)); // 2 gets to 2 separate regions
assertEquals(2, locator.getAllRegionLocations().size());
assertNotEquals(locator.getRegionLocation(bar).getRegion(),
locator.getRegionLocation(foo).getRegion());
}
}
assertPreGetRequestsCounter(CustomRegionObserver.class);
}
@Test
public void testRegionObserverMultiTable() throws IOException {
final TableName tableName1 = TableName.valueOf(name.getMethodName() + "1");
final TableName tableName2 = TableName.valueOf(name.getMethodName() + "2");
try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
Admin admin = connection.getAdmin()) {
admin.createTable(TableDescriptorBuilder.newBuilder(tableName1)
.setColumnFamily(ColumnFamilyDescriptorBuilder.of(foo))
// add the coprocessor for the region
.setCoprocessor(CustomRegionObserver.class.getName()).build());
admin.createTable(TableDescriptorBuilder.newBuilder(tableName2)
.setColumnFamily(ColumnFamilyDescriptorBuilder.of(foo))
// add the coprocessor for the region
.setCoprocessor(CustomRegionObserver.class.getName()).build());
try (Table table1 = connection.getTable(tableName1);
Table table2 = connection.getTable(tableName2)) {
table1.get(new Get(bar));
table2.get(new Get(foo)); // 2 gets to 2 separate tables
}
}
assertPreGetRequestsCounter(CustomRegionObserver.class);
}
@Test
public void testRegionObserverMultiCoprocessor() throws IOException {
final TableName tableName = TableName.valueOf(name.getMethodName());
try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
Admin admin = connection.getAdmin()) {
admin.createTable(TableDescriptorBuilder.newBuilder(tableName)
.setColumnFamily(ColumnFamilyDescriptorBuilder.of(foo))
// add the coprocessor for the region. We add two different coprocessors
.setCoprocessor(CustomRegionObserver.class.getName())
.setCoprocessor(CustomRegionObserver2.class.getName()).build());
try (Table table = connection.getTable(tableName)) {
table.get(new Get(foo));
table.get(new Get(foo)); // 2 gets
}
}
// we will have two counters coming from two coprocs, in two different MetricRegistries
assertPreGetRequestsCounter(CustomRegionObserver.class);
assertPreGetRequestsCounter(CustomRegionObserver2.class);
}
@Test
public void testRegionObserverAfterRegionClosed() throws IOException {
final TableName tableName = TableName.valueOf(name.getMethodName());
try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
Admin admin = connection.getAdmin()) {
admin.createTable(TableDescriptorBuilder.newBuilder(tableName)
.setColumnFamily(ColumnFamilyDescriptorBuilder.of(foo))
// add the coprocessor for the region
.setCoprocessor(CustomRegionObserver.class.getName()).build(), new byte[][] { foo });
// create with 2 regions
try (Table table = connection.getTable(tableName)) {
table.get(new Get(foo));
table.get(new Get(foo)); // 2 gets
}
assertPreGetRequestsCounter(CustomRegionObserver.class);
// close one of the regions
try (RegionLocator locator = connection.getRegionLocator(tableName)) {
HRegionLocation loc = locator.getRegionLocation(foo);
admin.unassign(loc.getRegion().getEncodedNameAsBytes(), true);
HRegionServer server = UTIL.getMiniHBaseCluster().getRegionServer(loc.getServerName());
UTIL.waitFor(30000,
() -> server.getOnlineRegion(loc.getRegion().getRegionName()) == null);
assertNull(server.getOnlineRegion(loc.getRegion().getRegionName()));
}
// with only 1 region remaining, we should still be able to find the Counter
assertPreGetRequestsCounter(CustomRegionObserver.class);
// close the table
admin.disableTable(tableName);
MetricRegistryInfo info = MetricsCoprocessor.createRegistryInfoForRegionCoprocessor(
CustomRegionObserver.class.getName());
// ensure that MetricRegistry is deleted
Optional<MetricRegistry> registry = MetricRegistries.global().get(info);
assertFalse(registry.isPresent());
}
}
@Test
public void testRegionObserverEndpoint() throws IOException, ServiceException {
final TableName tableName = TableName.valueOf(name.getMethodName());
try (Connection connection = ConnectionFactory.createConnection(UTIL.getConfiguration());
Admin admin = connection.getAdmin()) {
admin.createTable(TableDescriptorBuilder.newBuilder(tableName)
.setColumnFamily(ColumnFamilyDescriptorBuilder.of(foo))
// add the coprocessor for the region
.setCoprocessor(CustomRegionEndpoint.class.getName()).build());
try (Table table = connection.getTable(tableName)) {
List<Mutation> mutations = Lists.newArrayList(new Put(foo), new Put(bar));
MutateRowsRequest.Builder mrmBuilder = MutateRowsRequest.newBuilder();
for (Mutation mutation : mutations) {
mrmBuilder.addMutationRequest(ProtobufUtil.toMutation(
ClientProtos.MutationProto.MutationType.PUT, mutation));
}
CoprocessorRpcChannel channel = table.coprocessorService(bar);
MultiRowMutationService.BlockingInterface service =
MultiRowMutationService.newBlockingStub(channel);
MutateRowsRequest mrm = mrmBuilder.build();
service.mutateRows(null, mrm);
}
}
// Find out the MetricRegistry used by the CP using the global registries
MetricRegistryInfo info = MetricsCoprocessor.createRegistryInfoForRegionCoprocessor(
CustomRegionEndpoint.class.getName());
Optional<MetricRegistry> registry = MetricRegistries.global().get(info);
assertTrue(registry.isPresent());
Optional<Metric> metric = registry.get().get("EndpointExecution");
assertTrue(metric.isPresent());
Timer endpointExecutions = (Timer)metric.get();
assertEquals(1, endpointExecutions.getHistogram().getCount());
}
}