blob: 0af89cb247c5a32dae09d6335f08b7eb5c947d09 [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 java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CoprocessorEnvironment;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Durability;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Row;
import org.apache.hadoop.hbase.ipc.RpcServer;
import org.apache.hadoop.hbase.metrics.MetricRegistry;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.LossyCounting;
import org.apache.hadoop.hbase.wal.WALEdit;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap;
/**
* A coprocessor that collects metrics from meta table.
* <p>
* These metrics will be available through the regular Hadoop metrics2 sinks (ganglia, opentsdb,
* etc) as well as JMX output.
* </p>
* @see MetaTableMetrics
*/
@InterfaceAudience.Private
public class MetaTableMetrics implements RegionCoprocessor {
private ExampleRegionObserverMeta observer;
private MetricRegistry registry;
private LossyCounting clientMetricsLossyCounting, regionMetricsLossyCounting;
private boolean active = false;
private Set<String> metrics = ConcurrentHashMap.newKeySet();
enum MetaTableOps {
GET, PUT, DELETE,
}
private ImmutableMap<Class<? extends Row>, MetaTableOps> opsNameMap =
ImmutableMap.<Class<? extends Row>, MetaTableOps>builder()
.put(Put.class, MetaTableOps.PUT)
.put(Get.class, MetaTableOps.GET)
.put(Delete.class, MetaTableOps.DELETE)
.build();
class ExampleRegionObserverMeta implements RegionCoprocessor, RegionObserver {
@Override
public Optional<RegionObserver> getRegionObserver() {
return Optional.of(this);
}
@Override
public void preGetOp(ObserverContext<RegionCoprocessorEnvironment> e, Get get,
List<Cell> results) throws IOException {
registerAndMarkMetrics(e, get);
}
@Override
public void prePut(ObserverContext<RegionCoprocessorEnvironment> e, Put put, WALEdit edit,
Durability durability) throws IOException {
registerAndMarkMetrics(e, put);
}
@Override
public void preDelete(ObserverContext<RegionCoprocessorEnvironment> e, Delete delete,
WALEdit edit, Durability durability) {
registerAndMarkMetrics(e, delete);
}
private void registerAndMarkMetrics(ObserverContext<RegionCoprocessorEnvironment> e, Row row){
if (!active || !isMetaTableOp(e)) {
return;
}
tableMetricRegisterAndMark(row);
clientMetricRegisterAndMark();
regionMetricRegisterAndMark(row);
opMetricRegisterAndMark(row);
opWithClientMetricRegisterAndMark(row);
}
/**
* Get table name from Ops such as: get, put, delete.
* @param op such as get, put or delete.
*/
private String getTableNameFromOp(Row op) {
final String tableRowKey = Bytes.toString(op.getRow());
if (StringUtils.isEmpty(tableRowKey)) {
return null;
}
final String[] splits = tableRowKey.split(",");
return splits.length > 0 ? splits[0] : null;
}
/**
* Get regionId from Ops such as: get, put, delete.
* @param op such as get, put or delete.
*/
private String getRegionIdFromOp(Row op) {
final String tableRowKey = Bytes.toString(op.getRow());
if (StringUtils.isEmpty(tableRowKey)) {
return null;
}
final String[] splits = tableRowKey.split(",");
return splits.length > 2 ? splits[2] : null;
}
private boolean isMetaTableOp(ObserverContext<RegionCoprocessorEnvironment> e) {
return TableName.META_TABLE_NAME
.equals(e.getEnvironment().getRegionInfo().getTable());
}
private void clientMetricRegisterAndMark() {
// Mark client metric
String clientIP = RpcServer.getRemoteIp() != null ? RpcServer.getRemoteIp().toString() : null;
if (clientIP == null || clientIP.isEmpty()) {
return;
}
String clientRequestMeter = clientRequestMeterName(clientIP);
clientMetricsLossyCounting.add(clientRequestMeter);
registerAndMarkMeter(clientRequestMeter);
}
private void tableMetricRegisterAndMark(Row op) {
// Mark table metric
String tableName = getTableNameFromOp(op);
if (tableName == null || tableName.isEmpty()) {
return;
}
String tableRequestMeter = tableMeterName(tableName);
registerAndMarkMeter(tableRequestMeter);
}
private void regionMetricRegisterAndMark(Row op) {
// Mark region metric
String regionId = getRegionIdFromOp(op);
if (regionId == null || regionId.isEmpty()) {
return;
}
String regionRequestMeter = regionMeterName(regionId);
regionMetricsLossyCounting.add(regionRequestMeter);
registerAndMarkMeter(regionRequestMeter);
}
private void opMetricRegisterAndMark(Row op) {
// Mark access type ["get", "put", "delete"] metric
String opMeterName = opMeterName(op);
if (opMeterName == null || opMeterName.isEmpty()) {
return;
}
registerAndMarkMeter(opMeterName);
}
private void opWithClientMetricRegisterAndMark(Object op) {
// // Mark client + access type metric
String opWithClientMeterName = opWithClientMeterName(op);
if (opWithClientMeterName == null || opWithClientMeterName.isEmpty()) {
return;
}
registerAndMarkMeter(opWithClientMeterName);
}
// Helper function to register and mark meter if not present
private void registerAndMarkMeter(String requestMeter) {
if (requestMeter.isEmpty()) {
return;
}
if(!registry.get(requestMeter).isPresent()){
metrics.add(requestMeter);
}
registry.meter(requestMeter).mark();
}
private String opWithClientMeterName(Object op) {
// Extract meter name containing the client IP
String clientIP = RpcServer.getRemoteIp() != null ? RpcServer.getRemoteIp().toString() : "";
if (clientIP.isEmpty()) {
return "";
}
MetaTableOps ops = opsNameMap.get(op.getClass());
String opWithClientMeterName = "";
switch (ops) {
case GET:
opWithClientMeterName = String.format("MetaTable_client_%s_get_request", clientIP);
break;
case PUT:
opWithClientMeterName = String.format("MetaTable_client_%s_put_request", clientIP);
break;
case DELETE:
opWithClientMeterName = String.format("MetaTable_client_%s_delete_request", clientIP);
break;
default:
break;
}
return opWithClientMeterName;
}
private String opMeterName(Object op) {
// Extract meter name containing the access type
MetaTableOps ops = opsNameMap.get(op.getClass());
String opMeterName = "";
switch (ops) {
case GET:
opMeterName = "MetaTable_get_request";
break;
case PUT:
opMeterName = "MetaTable_put_request";
break;
case DELETE:
opMeterName = "MetaTable_delete_request";
break;
default:
break;
}
return opMeterName;
}
private String tableMeterName(String tableName) {
// Extract meter name containing the table name
return String.format("MetaTable_table_%s_request", tableName);
}
private String clientRequestMeterName(String clientIP) {
// Extract meter name containing the client IP
if (clientIP.isEmpty()) {
return "";
}
return String.format("MetaTable_client_%s_lossy_request", clientIP);
}
private String regionMeterName(String regionId) {
// Extract meter name containing the region ID
return String.format("MetaTable_region_%s_lossy_request", regionId);
}
}
@Override
public Optional<RegionObserver> getRegionObserver() {
return Optional.of(observer);
}
@Override
public void start(CoprocessorEnvironment env) throws IOException {
observer = new ExampleRegionObserverMeta();
if (env instanceof RegionCoprocessorEnvironment
&& ((RegionCoprocessorEnvironment) env).getRegionInfo().getTable() != null
&& ((RegionCoprocessorEnvironment) env).getRegionInfo().getTable()
.equals(TableName.META_TABLE_NAME)) {
RegionCoprocessorEnvironment regionCoprocessorEnv = (RegionCoprocessorEnvironment) env;
registry = regionCoprocessorEnv.getMetricRegistryForRegionServer();
LossyCounting.LossyCountingListener listener = key -> {
registry.remove(key);
metrics.remove(key);
};
final Configuration conf = regionCoprocessorEnv.getConfiguration();
clientMetricsLossyCounting = new LossyCounting("clientMetaMetrics", conf, listener);
regionMetricsLossyCounting = new LossyCounting("regionMetaMetrics", conf, listener);
// only be active mode when this region holds meta table.
active = true;
}
}
@Override
public void stop(CoprocessorEnvironment env) throws IOException {
// since meta region can move around, clear stale metrics when stop.
for(String metric:metrics){
registry.remove(metric);
}
}
}