/* * Copyright 2012 NGDATA nv * * Licensed 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.lilyproject.clientmetrics; import javax.management.MBeanServerConnection; import javax.management.MalformedObjectNameException; import javax.management.ObjectInstance; import javax.management.ObjectName; import javax.management.openmbean.CompositeDataSupport; import java.io.IOException; import java.io.PrintStream; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import org.apache.hadoop.hbase.ClusterStatus; import org.apache.hadoop.hbase.HServerLoad; import org.apache.hadoop.hbase.MasterNotRunningException; import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.client.HBaseAdmin; import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.HTableInterface; import org.apache.hadoop.hbase.client.ResultScanner; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter; /** * Various utility methods to pull interesting data from HBase. */ public class HBaseMetrics { private HBaseAdmin hbaseAdmin; private JmxConnections jmxConnections = new JmxConnections(); private static final String HBASE_JMX_PORT = "10102"; private ObjectName regionServerStats = new ObjectName("hadoop:service=RegionServer,name=RegionServerStatistics"); private ObjectName operationSystem = new ObjectName("java.lang:type=OperatingSystem"); private ObjectName memory = new ObjectName("java.lang:type=Memory"); private ObjectName threading = new ObjectName("java.lang:type=Threading"); private boolean gcMetricsInitialized = false; private Map<String, ObjectName> garbageCollectionNames = new HashMap<String, ObjectName>(); public HBaseMetrics(HBaseAdmin hbaseAdmin) throws MasterNotRunningException, MalformedObjectNameException { this.hbaseAdmin = hbaseAdmin; } public void close() { jmxConnections.close(); } public void reportMetrics(Metrics metrics) throws Exception { ClusterStatus clusterStatus = hbaseAdmin.getClusterStatus(); for (ServerName serverName : clusterStatus.getServers()) { int hbaseLoad = clusterStatus.getLoad(serverName).getLoad(); MBeanServerConnection connection = jmxConnections.getConnector(serverName.getHostname(), HBASE_JMX_PORT) .getMBeanServerConnection(); initGarbageCollMetrics(connection); Integer blockCacheHitRatio = (Integer)connection.getAttribute(regionServerStats, "blockCacheHitRatio"); Double sysLoadAvg = (Double)connection.getAttribute(operationSystem, "SystemLoadAverage"); Integer threadCount = (Integer)connection.getAttribute(threading, "ThreadCount"); CompositeDataSupport heapMemUsage = (CompositeDataSupport)connection.getAttribute(memory, "HeapMemoryUsage"); double usedHeapMB = ((double)(Long)heapMemUsage.get("used")) / 1024d / 1024d; // The dash at the beginning of the name is a hint towards the MetricsReportTool that the avg/min/max // is the same (there is only one value per interval) // The usage of the @ symbol is also a hint for MetricsReportTool: it will group all values with the // same text before the @ symbol. metrics.increment("-hbaseLoad@" + serverName, hbaseLoad); metrics.increment("-blockCacheHitRatio@" + serverName, blockCacheHitRatio); metrics.increment("-sysLoadAvg@" + serverName, sysLoadAvg); metrics.increment("-hbaseUsedHeap@" + serverName, usedHeapMB); metrics.increment("-hbaseThreadCount@" + serverName, threadCount); for (Map.Entry<String, ObjectName> entry : garbageCollectionNames.entrySet()) { long collectionCount = (Long)connection.getAttribute(entry.getValue(), "CollectionCount"); long collectionTime = (Long)connection.getAttribute(entry.getValue(), "CollectionTime"); metrics.increment("-" + entry.getKey() + "CollCount@" + serverName, collectionCount); metrics.increment("-" + entry.getKey() + "CollTime@" + serverName, collectionTime); } } } private void initGarbageCollMetrics(MBeanServerConnection connection) throws Exception { if (!gcMetricsInitialized) { gcMetricsInitialized = true; Set<ObjectInstance> mbeans = connection.queryMBeans(new ObjectName("java.lang:type=GarbageCollector,name=*"), null); for (ObjectInstance instance : mbeans) { garbageCollectionNames.put(instance.getObjectName().getKeyProperty("name"), instance.getObjectName()); } } } public void reportRequestCountMetric(Metrics metrics) throws Exception { ClusterStatus clusterStatus = hbaseAdmin.getClusterStatus(); for (ServerName serverName : clusterStatus.getServers()) { int requestCount = clusterStatus.getLoad(serverName).getNumberOfRequests(); metrics.increment("-hbaseRequestCount@" + serverName.getHostname(), requestCount); } } public Collection<TableInfo> getHBaseTableInfo(ClusterStatus clusterStatus) throws IOException { SortedMap<String, TableInfo> tableInfos = new TreeMap<String, TableInfo>(); for (ServerName serverName : clusterStatus.getServers()) { for (HServerLoad.RegionLoad regionLoad : clusterStatus.getLoad(serverName).getRegionsLoad().values()) { String regionName = regionLoad.getNameAsString(); String tableName = getTableNameFromRegionName(regionName); TableInfo table = tableInfos.get(tableName); if (table == null) { table = new TableInfo(); table.name = tableName; tableInfos.put(tableName, table); } table.stores = Math.max(table.stores, regionLoad.getStores()); table.regions++; table.storefiles += regionLoad.getStorefiles(); table.storefilesMB += regionLoad.getStorefileSizeMB(); table.memStoreMB += regionLoad.getMemStoreSizeMB(); } } for (Map.Entry<String, TableInfo> entry : tableInfos.entrySet()) { HTableInterface table = new HTable(hbaseAdmin.getConfiguration(), entry.getKey()); Scan scan = new Scan(); scan.setCaching(100); scan.setCacheBlocks(false); scan.setFilter(new FirstKeyOnlyFilter()); ResultScanner scanner = table.getScanner(scan); int rowCount = 0; while (scanner.next() != null && rowCount <= 5000) { rowCount++; } entry.getValue().rows = rowCount; scanner.close(); table.close(); } return tableInfos.values(); } private String getTableNameFromRegionName(String regionName) { int commaPos = regionName.indexOf(','); String tableName = regionName.substring(0, commaPos); return tableName; } public static class TableInfo { String name; int stores; int regions; int storefiles; int storefilesMB; int memStoreMB; int rows; } public void outputHBaseState(PrintStream stream) throws IOException { ClusterStatus clusterStatus = hbaseAdmin.getClusterStatus(); Collection<TableInfo> tableInfos = getHBaseTableInfo(clusterStatus); Table table = new Table(stream); table.addColumn(20, "Table name", "s"); table.addColumn(-1, "# stores", "d"); table.addColumn(-1, "# regions", "d"); table.addColumn(-1, "# storesfiles", "d"); table.addColumn(-1, "storefiles MB", "d"); table.addColumn(-1, "memstore MB", "d"); table.addColumn(-1, "# rows", "s", false, 0); table.finishDefinition(); table.fullSepLine(); table.crossColumn("HBase version: " + clusterStatus.getHBaseVersion()); table.crossColumn("# regions in transition: " + clusterStatus.getRegionsInTransition().size()); table.crossColumn("The information below is from summarizing HBaseAdmin.ClusterStatus (= only online regions)"); table.columnSepLine(); table.titles(); table.columnSepLine(); for (TableInfo tableInfo : tableInfos) { String rowCount = tableInfo.rows < 5000 ? String.valueOf(tableInfo.rows) : "> 5000"; table.columns(tableInfo.name, tableInfo.stores, tableInfo.regions, tableInfo.storefiles, tableInfo.storefilesMB, tableInfo.memStoreMB, rowCount); } table.columnSepLine(); } public void outputRegionServersInfo(PrintStream ps) throws Exception { Table table = new Table(ps); table.addColumn(30, "Regionserver", "s"); table.addColumn(6, "Arch", "s"); table.addColumn(-1, "# CPU", "d"); table.addColumn(15, "OS Version", "s"); table.addColumn(-1, "Physical mem", "f"); table.addColumn(-1, "JVM version", "s"); table.addColumn(-1, "Max heap", "f"); table.addColumn(-1, "Used heap", "f"); table.finishDefinition(); table.fullSepLine(); table.crossColumn("Information on the HBase region servers"); table.columnSepLine(); table.titles(); table.columnSepLine(); ObjectName runtime = new ObjectName("java.lang:type=Runtime"); ClusterStatus clusterStatus = hbaseAdmin.getClusterStatus(); for (ServerName serverName : clusterStatus.getServers()) { String hostname = serverName.getHostname(); MBeanServerConnection connection = jmxConnections.getConnector(hostname, HBASE_JMX_PORT).getMBeanServerConnection(); String arch = (String)connection.getAttribute(operationSystem, "Arch"); Integer processors = (Integer)connection.getAttribute(operationSystem, "AvailableProcessors"); String osVersion = (String)connection.getAttribute(operationSystem, "Version"); long physicalMem = (Long)connection.getAttribute(operationSystem, "TotalPhysicalMemorySize"); double physicalMemMB = ((double)physicalMem) / 1024d / 1024d; String vmVersion = (String)connection.getAttribute(runtime, "VmVersion"); CompositeDataSupport heapMemUsage = (CompositeDataSupport)connection.getAttribute(memory, "HeapMemoryUsage"); double maxHeapMB = ((double)(Long)heapMemUsage.get("max")) / 1024d / 1024d; double usedHeapMB = ((double)(Long)heapMemUsage.get("used")) / 1024d / 1024d; table.columns(hostname, arch, processors, osVersion, physicalMemMB, vmVersion, maxHeapMB, usedHeapMB); } table.columnSepLine(); } public void outputRegionCountByServer(PrintStream ps) throws IOException { Table table = new Table(ps); table.addColumn(30, "Regionserver", "s"); table.addColumn(30, "Table name", "s"); table.addColumn(-1, "Region count", "d"); table.finishDefinition(); table.fullSepLine(); table.crossColumn("Number of regions for each table on each region server"); table.columnSepLine(); table.titles(); table.columnSepLine(); ClusterStatus clusterStatus = hbaseAdmin.getClusterStatus(); for (ServerName serverName : clusterStatus.getServers()) { Map<String, Integer> regionCounts = new HashMap<String, Integer>(); for (HServerLoad.RegionLoad regionLoad : clusterStatus.getLoad(serverName).getRegionsLoad().values()) { String tableName = getTableNameFromRegionName(regionLoad.getNameAsString()); if (regionCounts.containsKey(tableName)) { regionCounts.put(tableName, regionCounts.get(tableName) + 1); } else { regionCounts.put(tableName, 1); } } String hostName = serverName.getHostname(); for (Map.Entry<String, Integer> entry : regionCounts.entrySet()) { table.columns(hostName, entry.getKey(), entry.getValue()); } } table.columnSepLine(); } }