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 java.io.IOException; 015import java.nio.charset.StandardCharsets; 016import java.util.List; 017import java.util.Map; 018import java.util.Optional; 019import java.util.Set; 020import java.util.concurrent.ConcurrentHashMap; 021import org.apache.hadoop.hbase.Cell; 022import org.apache.hadoop.hbase.CoprocessorEnvironment; 023import org.apache.hadoop.hbase.TableName; 024import org.apache.hadoop.hbase.client.Delete; 025import org.apache.hadoop.hbase.client.Durability; 026import org.apache.hadoop.hbase.client.Get; 027import org.apache.hadoop.hbase.client.Put; 028import org.apache.hadoop.hbase.client.Row; 029import org.apache.hadoop.hbase.ipc.RpcServer; 030import org.apache.hadoop.hbase.metrics.Meter; 031import org.apache.hadoop.hbase.metrics.Metric; 032import org.apache.hadoop.hbase.metrics.MetricRegistry; 033import org.apache.hadoop.hbase.util.LossyCounting; 034import org.apache.hadoop.hbase.wal.WALEdit; 035import org.apache.yetus.audience.InterfaceAudience; 036import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap; 037 038 039/** 040 * A coprocessor that collects metrics from meta table. 041 * <p> 042 * These metrics will be available through the regular Hadoop metrics2 sinks (ganglia, opentsdb, 043 * etc) as well as JMX output. 044 * </p> 045 * @see MetaTableMetrics 046 */ 047 048@InterfaceAudience.Private 049public class MetaTableMetrics implements RegionCoprocessor { 050 051 private ExampleRegionObserverMeta observer; 052 private Map<String, Optional<Metric>> requestsMap; 053 private RegionCoprocessorEnvironment regionCoprocessorEnv; 054 private LossyCounting clientMetricsLossyCounting; 055 private boolean active = false; 056 057 enum MetaTableOps { 058 GET, PUT, DELETE; 059 } 060 061 private ImmutableMap<Class, MetaTableOps> opsNameMap = 062 ImmutableMap.<Class, MetaTableOps>builder() 063 .put(Put.class, MetaTableOps.PUT) 064 .put(Get.class, MetaTableOps.GET) 065 .put(Delete.class, MetaTableOps.DELETE) 066 .build(); 067 068 class ExampleRegionObserverMeta implements RegionCoprocessor, RegionObserver { 069 070 @Override 071 public Optional<RegionObserver> getRegionObserver() { 072 return Optional.of(this); 073 } 074 075 @Override 076 public void preGetOp(ObserverContext<RegionCoprocessorEnvironment> e, Get get, 077 List<Cell> results) throws IOException { 078 if (!active || !isMetaTableOp(e)) { 079 return; 080 } 081 tableMetricRegisterAndMark(e, get); 082 clientMetricRegisterAndMark(e); 083 regionMetricRegisterAndMark(e, get); 084 opMetricRegisterAndMark(e, get); 085 opWithClientMetricRegisterAndMark(e, get); 086 } 087 088 @Override 089 public void prePut(ObserverContext<RegionCoprocessorEnvironment> e, Put put, WALEdit edit, 090 Durability durability) throws IOException { 091 if (!active || !isMetaTableOp(e)) { 092 return; 093 } 094 tableMetricRegisterAndMark(e, put); 095 clientMetricRegisterAndMark(e); 096 regionMetricRegisterAndMark(e, put); 097 opMetricRegisterAndMark(e, put); 098 opWithClientMetricRegisterAndMark(e, put); 099 } 100 101 @Override 102 public void preDelete(ObserverContext<RegionCoprocessorEnvironment> e, Delete delete, 103 WALEdit edit, Durability durability) throws IOException { 104 if (!active || !isMetaTableOp(e)) { 105 return; 106 } 107 tableMetricRegisterAndMark(e, delete); 108 clientMetricRegisterAndMark(e); 109 regionMetricRegisterAndMark(e, delete); 110 opMetricRegisterAndMark(e, delete); 111 opWithClientMetricRegisterAndMark(e, delete); 112 } 113 114 private void markMeterIfPresent(String requestMeter) { 115 if (requestMeter.isEmpty()) { 116 return; 117 } 118 Metric metric = 119 requestsMap.get(requestMeter).isPresent() ? requestsMap.get(requestMeter).get() : null; 120 if (metric != null) { 121 ((Meter) metric).mark(); 122 } 123 } 124 125 private void registerMeterIfNotPresent(ObserverContext<RegionCoprocessorEnvironment> e, 126 String requestMeter) { 127 if (requestMeter.isEmpty()) { 128 return; 129 } 130 if (!requestsMap.containsKey(requestMeter)) { 131 MetricRegistry registry = regionCoprocessorEnv.getMetricRegistryForRegionServer(); 132 registry.meter(requestMeter); 133 requestsMap.put(requestMeter, registry.get(requestMeter)); 134 } 135 } 136 137 /** 138 * Registers and counts lossyCount for Meters that kept by lossy counting. 139 * By using lossy count to maintain meters, at most 7 / e meters will be kept (e is error rate) 140 * e.g. when e is 0.02 by default, at most 50 Clients request metrics will be kept 141 * also, all kept elements have frequency higher than e * N. (N is total count) 142 * @param e Region coprocessor environment 143 * @param requestMeter meter to be registered 144 * @param lossyCounting lossyCounting object for one type of meters. 145 */ 146 private void registerLossyCountingMeterIfNotPresent( 147 ObserverContext<RegionCoprocessorEnvironment> e, 148 String requestMeter, LossyCounting lossyCounting) { 149 if (requestMeter.isEmpty()) { 150 return; 151 } 152 Set<String> metersToBeRemoved = lossyCounting.addByOne(requestMeter); 153 if(!requestsMap.containsKey(requestMeter) && metersToBeRemoved.contains(requestMeter)){ 154 for(String meter: metersToBeRemoved) { 155 //cleanup requestsMap according swept data from lossy count; 156 requestsMap.remove(meter); 157 MetricRegistry registry = regionCoprocessorEnv.getMetricRegistryForRegionServer(); 158 registry.remove(meter); 159 } 160 // newly added meter is swept by lossy counting cleanup. No need to put it into requestsMap. 161 return; 162 } 163 164 if (!requestsMap.containsKey(requestMeter)) { 165 MetricRegistry registry = regionCoprocessorEnv.getMetricRegistryForRegionServer(); 166 registry.meter(requestMeter); 167 requestsMap.put(requestMeter, registry.get(requestMeter)); 168 } 169 } 170 171 /** 172 * Get table name from Ops such as: get, put, delete. 173 * @param op such as get, put or delete. 174 */ 175 private String getTableNameFromOp(Row op) { 176 String tableName = null; 177 String tableRowKey = new String(((Row) op).getRow(), StandardCharsets.UTF_8); 178 if (tableRowKey.isEmpty()) { 179 return null; 180 } 181 tableName = tableRowKey.split(",").length > 0 ? tableRowKey.split(",")[0] : null; 182 return tableName; 183 } 184 185 /** 186 * Get regionId from Ops such as: get, put, delete. 187 * @param op such as get, put or delete. 188 */ 189 private String getRegionIdFromOp(Row op) { 190 String regionId = null; 191 String tableRowKey = new String(((Row) op).getRow(), StandardCharsets.UTF_8); 192 if (tableRowKey.isEmpty()) { 193 return null; 194 } 195 regionId = tableRowKey.split(",").length > 2 ? tableRowKey.split(",")[2] : null; 196 return regionId; 197 } 198 199 private boolean isMetaTableOp(ObserverContext<RegionCoprocessorEnvironment> e) { 200 return TableName.META_TABLE_NAME.toString() 201 .equals(new String(e.getEnvironment().getRegionInfo().getTable().getName(), 202 StandardCharsets.UTF_8)); 203 } 204 205 private void clientMetricRegisterAndMark(ObserverContext<RegionCoprocessorEnvironment> e) { 206 String clientIP = RpcServer.getRemoteIp() != null ? RpcServer.getRemoteIp().toString() : ""; 207 208 String clientRequestMeter = clientRequestMeterName(clientIP); 209 registerLossyCountingMeterIfNotPresent(e, clientRequestMeter, clientMetricsLossyCounting); 210 markMeterIfPresent(clientRequestMeter); 211 } 212 213 private void tableMetricRegisterAndMark(ObserverContext<RegionCoprocessorEnvironment> e, 214 Row op) { 215 // Mark the meta table meter whenever the coprocessor is called 216 String tableName = getTableNameFromOp(op); 217 String tableRequestMeter = tableMeterName(tableName); 218 registerMeterIfNotPresent(e, tableRequestMeter); 219 markMeterIfPresent(tableRequestMeter); 220 } 221 222 private void regionMetricRegisterAndMark(ObserverContext<RegionCoprocessorEnvironment> e, 223 Row op) { 224 // Mark the meta table meter whenever the coprocessor is called 225 String regionId = getRegionIdFromOp(op); 226 String regionRequestMeter = regionMeterName(regionId); 227 registerMeterIfNotPresent(e, regionRequestMeter); 228 markMeterIfPresent(regionRequestMeter); 229 } 230 231 private void opMetricRegisterAndMark(ObserverContext<RegionCoprocessorEnvironment> e, 232 Row op) { 233 String opMeterName = opMeterName(op); 234 registerMeterIfNotPresent(e, opMeterName); 235 markMeterIfPresent(opMeterName); 236 } 237 238 private void opWithClientMetricRegisterAndMark(ObserverContext<RegionCoprocessorEnvironment> e, 239 Object op) { 240 String opWithClientMeterName = opWithClientMeterName(op); 241 registerMeterIfNotPresent(e, opWithClientMeterName); 242 markMeterIfPresent(opWithClientMeterName); 243 } 244 245 private String opWithClientMeterName(Object op) { 246 String clientIP = RpcServer.getRemoteIp() != null ? RpcServer.getRemoteIp().toString() : ""; 247 if (clientIP.isEmpty()) { 248 return ""; 249 } 250 MetaTableOps ops = opsNameMap.get(op.getClass()); 251 String opWithClientMeterName = ""; 252 switch (ops) { 253 case GET: 254 opWithClientMeterName = String.format("MetaTable_client_%s_get_request", clientIP); 255 break; 256 case PUT: 257 opWithClientMeterName = String.format("MetaTable_client_%s_put_request", clientIP); 258 break; 259 case DELETE: 260 opWithClientMeterName = String.format("MetaTable_client_%s_delete_request", clientIP); 261 break; 262 default: 263 break; 264 } 265 return opWithClientMeterName; 266 } 267 268 private String opMeterName(Object op) { 269 MetaTableOps ops = opsNameMap.get(op.getClass()); 270 String opMeterName = ""; 271 switch (ops) { 272 case GET: 273 opMeterName = "MetaTable_get_request"; 274 break; 275 case PUT: 276 opMeterName = "MetaTable_put_request"; 277 break; 278 case DELETE: 279 opMeterName = "MetaTable_delete_request"; 280 break; 281 default: 282 break; 283 } 284 return opMeterName; 285 } 286 287 private String tableMeterName(String tableName) { 288 return String.format("MetaTable_table_%s_request", tableName); 289 } 290 291 private String clientRequestMeterName(String clientIP) { 292 if (clientIP.isEmpty()) { 293 return ""; 294 } 295 return String.format("MetaTable_client_%s_request", clientIP); 296 } 297 298 private String regionMeterName(String regionId) { 299 return String.format("MetaTable_region_%s_request", regionId); 300 } 301 } 302 303 @Override 304 public Optional<RegionObserver> getRegionObserver() { 305 return Optional.of(observer); 306 } 307 308 @Override 309 public void start(CoprocessorEnvironment env) throws IOException { 310 if (env instanceof RegionCoprocessorEnvironment 311 && ((RegionCoprocessorEnvironment) env).getRegionInfo().getTable() != null 312 && ((RegionCoprocessorEnvironment) env).getRegionInfo().getTable().getName() != null 313 && new String(((RegionCoprocessorEnvironment) env).getRegionInfo().getTable().getName(), 314 StandardCharsets.UTF_8).equals(TableName.META_TABLE_NAME.toString())) { 315 regionCoprocessorEnv = (RegionCoprocessorEnvironment) env; 316 observer = new ExampleRegionObserverMeta(); 317 requestsMap = new ConcurrentHashMap<>(); 318 clientMetricsLossyCounting = new LossyCounting(); 319 // only be active mode when this region holds meta table. 320 active = true; 321 } else { 322 observer = new ExampleRegionObserverMeta(); 323 } 324 } 325 326 @Override 327 public void stop(CoprocessorEnvironment e) throws IOException { 328 // since meta region can move around, clear stale metrics when stop. 329 for (String meterName : requestsMap.keySet()) { 330 MetricRegistry registry = regionCoprocessorEnv.getMetricRegistryForRegionServer(); 331 registry.remove(meterName); 332 } 333 } 334 335}