001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
003 * agreements. See the NOTICE file distributed with this work for additional information regarding
004 * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
005 * "License"); you may not use this file except in compliance with the License. You may obtain a
006 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
007 * law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
008 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
009 * for the specific language governing permissions and limitations under the License.
010 */
011
012package org.apache.hadoop.hbase.coprocessor;
013
014import static org.junit.Assert.assertEquals;
015import static org.junit.Assert.assertNotNull;
016
017import java.io.IOException;
018import java.util.ArrayList;
019import java.util.HashSet;
020import java.util.Hashtable;
021import java.util.Iterator;
022import java.util.List;
023import java.util.Random;
024import java.util.Set;
025
026import javax.management.MBeanAttributeInfo;
027import javax.management.MBeanInfo;
028import javax.management.MBeanServerConnection;
029import javax.management.ObjectInstance;
030import javax.management.ObjectName;
031import javax.management.remote.JMXConnector;
032import javax.management.remote.JMXConnectorFactory;
033
034import org.apache.hadoop.conf.Configuration;
035import org.apache.hadoop.hbase.HBaseClassTestRule;
036import org.apache.hadoop.hbase.HBaseTestingUtility;
037import org.apache.hadoop.hbase.JMXListener;
038import org.apache.hadoop.hbase.TableName;
039import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
040import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
041import org.apache.hadoop.hbase.client.Put;
042import org.apache.hadoop.hbase.client.Table;
043import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
044import org.apache.hadoop.hbase.testclassification.CoprocessorTests;
045import org.apache.hadoop.hbase.testclassification.MediumTests;
046import org.apache.hadoop.hbase.util.Bytes;
047import org.apache.hadoop.hbase.util.Threads;
048import org.junit.AfterClass;
049import org.junit.BeforeClass;
050import org.junit.ClassRule;
051import org.junit.Test;
052import org.junit.experimental.categories.Category;
053import org.slf4j.Logger;
054import org.slf4j.LoggerFactory;
055
056
057@Category({ CoprocessorTests.class, MediumTests.class })
058public class TestMetaTableMetrics {
059
060  @ClassRule
061  public static final HBaseClassTestRule CLASS_RULE =
062      HBaseClassTestRule.forClass(TestMetaTableMetrics.class);
063  private static final Logger LOG = LoggerFactory.getLogger(TestMetaTableMetrics.class);
064
065  private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
066  private static final TableName NAME1 = TableName.valueOf("TestExampleMetaTableMetricsOne");
067  private static final byte[] FAMILY = Bytes.toBytes("f");
068  private static final byte[] QUALIFIER = Bytes.toBytes("q");
069  private static final ColumnFamilyDescriptor CFD =
070      ColumnFamilyDescriptorBuilder.newBuilder(FAMILY).build();
071  private static final int NUM_ROWS = 5;
072  private static final String value = "foo";
073  private static Configuration conf = null;
074  private static int connectorPort = 61120;
075
076  @BeforeClass
077  public static void setupBeforeClass() throws Exception {
078
079    conf = UTIL.getConfiguration();
080    // Set system coprocessor so it can be applied to meta regions
081    UTIL.getConfiguration().set("hbase.coprocessor.region.classes",
082      MetaTableMetrics.class.getName());
083    conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, JMXListener.class.getName());
084    Random rand = new Random();
085    for (int i = 0; i < 10; i++) {
086      do {
087        int sign = i % 2 == 0 ? 1 : -1;
088        connectorPort += sign * rand.nextInt(100);
089      } while (!HBaseTestingUtility.available(connectorPort));
090      try {
091        conf.setInt("regionserver.rmi.registry.port", connectorPort);
092        UTIL.startMiniCluster(1);
093        break;
094      } catch (Exception e) {
095        LOG.debug("Encountered exception when starting cluster. Trying port " + connectorPort, e);
096        try {
097          // this is to avoid "IllegalStateException: A mini-cluster is already running"
098          UTIL.shutdownMiniCluster();
099        } catch (Exception ex) {
100          LOG.debug("Encountered exception shutting down cluster", ex);
101        }
102      }
103    }
104    UTIL.getAdmin()
105        .createTable(TableDescriptorBuilder.newBuilder(NAME1)
106            .setColumnFamily(CFD)
107            .build());
108  }
109
110  @AfterClass
111  public static void tearDown() throws Exception {
112    UTIL.shutdownMiniCluster();
113  }
114
115  private void writeData(Table t) throws IOException {
116    List<Put> puts = new ArrayList<>(NUM_ROWS);
117    for (int i = 0; i < NUM_ROWS; i++) {
118      Put p = new Put(Bytes.toBytes(i + 1));
119      p.addColumn(FAMILY, QUALIFIER, Bytes.toBytes(value));
120      puts.add(p);
121    }
122    t.put(puts);
123  }
124
125  private Set<String> readJmxMetricsWithRetry() throws IOException {
126    final int count = 0;
127    for (int i = 0; i < 10; i++) {
128      Set<String> metrics = readJmxMetrics();
129      if (metrics != null) {
130        return metrics;
131      }
132      LOG.warn("Failed to get jmxmetrics... sleeping, retrying; " + i + " of " + count + " times");
133      Threads.sleep(1000);
134    }
135    return null;
136  }
137
138  /**
139   * Read the attributes from Hadoop->HBase->RegionServer->MetaTableMetrics in JMX
140   * @throws IOException when fails to retrieve jmx metrics.
141   */
142  // this method comes from this class: TestStochasticBalancerJmxMetrics with minor modifications.
143  private Set<String> readJmxMetrics() throws IOException {
144    JMXConnector connector = null;
145    ObjectName target = null;
146    MBeanServerConnection mb = null;
147    try {
148      connector =
149          JMXConnectorFactory.connect(JMXListener.buildJMXServiceURL(connectorPort, connectorPort));
150      mb = connector.getMBeanServerConnection();
151
152      @SuppressWarnings("JdkObsolete")
153      Hashtable<String, String> pairs = new Hashtable<>();
154      pairs.put("service", "HBase");
155      pairs.put("name", "RegionServer");
156      pairs.put("sub",
157        "Coprocessor.Region.CP_org.apache.hadoop.hbase.coprocessor"
158            + ".MetaTableMetrics");
159      target = new ObjectName("Hadoop", pairs);
160      MBeanInfo beanInfo = mb.getMBeanInfo(target);
161
162      Set<String> existingAttrs = new HashSet<>();
163      for (MBeanAttributeInfo attrInfo : beanInfo.getAttributes()) {
164        existingAttrs.add(attrInfo.getName());
165      }
166      return existingAttrs;
167    } catch (Exception e) {
168      LOG.warn("Failed to get bean." + target, e);
169      if (mb != null) {
170        Set<ObjectInstance> instances = mb.queryMBeans(null, null);
171        Iterator<ObjectInstance> iterator = instances.iterator();
172        LOG.warn("MBean Found:");
173        while (iterator.hasNext()) {
174          ObjectInstance instance = iterator.next();
175          LOG.warn("Class Name: " + instance.getClassName());
176          LOG.warn("Object Name: " + instance.getObjectName());
177        }
178      }
179    } finally {
180      if (connector != null) {
181        try {
182          connector.close();
183        } catch (Exception e) {
184          e.printStackTrace();
185        }
186      }
187    }
188    return null;
189  }
190
191  // verifies meta table metrics exist from jmx
192  // for one table, there should be 5 MetaTable_table_<TableName> metrics.
193  // such as:
194  // [Time-limited test] example.TestMetaTableMetrics(204): ==
195  //    MetaTable_table_TestExampleMetaTableMetricsOne_request_count
196  // [Time-limited test] example.TestMetaTableMetrics(204): ==
197  //    MetaTable_table_TestExampleMetaTableMetricsOne_request_mean_rate
198  // [Time-limited test] example.TestMetaTableMetrics(204): ==
199  //    MetaTable_table_TestExampleMetaTableMetricsOne_request_1min_rate
200  // [Time-limited test] example.TestMetaTableMetrics(204): ==
201  //    MetaTable_table_TestExampleMetaTableMetricsOne_request_5min_rate
202  // [Time-limited test] example.TestMetaTableMetrics(204): ==
203  // MetaTable_table_TestExampleMetaTableMetricsOne_request_15min_rate
204  @Test
205  public void test() throws IOException, InterruptedException {
206    try (Table t = UTIL.getConnection().getTable(NAME1)) {
207      writeData(t);
208      // Flush the data
209      UTIL.flush(NAME1);
210      // Issue a compaction
211      UTIL.compact(NAME1, true);
212      Thread.sleep(2000);
213    }
214    Set<String> jmxMetrics = readJmxMetricsWithRetry();
215    assertNotNull(jmxMetrics);
216    long name1TableMetricsCount =
217        jmxMetrics.stream().filter(metric -> metric.contains("MetaTable_table_" + NAME1)).count();
218    assertEquals(5L, name1TableMetricsCount);
219
220    String putWithClientMetricNameRegex = "MetaTable_client_.+_put_request.*";
221    long putWithClientMetricsCount =
222            jmxMetrics.stream().filter(metric -> metric.matches(putWithClientMetricNameRegex))
223                    .count();
224    assertEquals(5L, putWithClientMetricsCount);
225
226
227
228
229  }
230
231}