001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019
020package org.apache.hadoop.hbase.coprocessor;
021
022import java.io.IOException;
023import java.nio.charset.StandardCharsets;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Optional;
027import java.util.Set;
028
029import org.apache.hadoop.hbase.Cell;
030import org.apache.hadoop.hbase.CoprocessorEnvironment;
031import org.apache.hadoop.hbase.TableName;
032import org.apache.hadoop.hbase.client.Delete;
033import org.apache.hadoop.hbase.client.Durability;
034import org.apache.hadoop.hbase.client.Get;
035import org.apache.hadoop.hbase.client.Put;
036import org.apache.hadoop.hbase.client.Row;
037import org.apache.hadoop.hbase.ipc.RpcServer;
038import org.apache.hadoop.hbase.metrics.MetricRegistry;
039import org.apache.hadoop.hbase.util.LossyCounting;
040import org.apache.hadoop.hbase.wal.WALEdit;
041import org.apache.yetus.audience.InterfaceAudience;
042
043import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap;
044
045
046/**
047 * A coprocessor that collects metrics from meta table.
048 * <p>
049 * These metrics will be available through the regular Hadoop metrics2 sinks (ganglia, opentsdb,
050 * etc) as well as JMX output.
051 * </p>
052 * @see MetaTableMetrics
053 */
054
055@InterfaceAudience.Private
056public class MetaTableMetrics implements RegionCoprocessor {
057
058  private ExampleRegionObserverMeta observer;
059  private MetricRegistry registry;
060  private LossyCounting clientMetricsLossyCounting, regionMetricsLossyCounting;
061  private boolean active = false;
062  private Set<String> metrics = new HashSet<String>();
063
064  enum MetaTableOps {
065    GET, PUT, DELETE;
066  }
067
068  private ImmutableMap<Class, MetaTableOps> opsNameMap =
069      ImmutableMap.<Class, MetaTableOps>builder()
070              .put(Put.class, MetaTableOps.PUT)
071              .put(Get.class, MetaTableOps.GET)
072              .put(Delete.class, MetaTableOps.DELETE)
073              .build();
074
075  class ExampleRegionObserverMeta implements RegionCoprocessor, RegionObserver {
076
077    @Override
078    public Optional<RegionObserver> getRegionObserver() {
079      return Optional.of(this);
080    }
081
082    @Override
083    public void preGetOp(ObserverContext<RegionCoprocessorEnvironment> e, Get get,
084        List<Cell> results) throws IOException {
085      registerAndMarkMetrics(e, get);
086    }
087
088    @Override
089    public void prePut(ObserverContext<RegionCoprocessorEnvironment> e, Put put, WALEdit edit,
090        Durability durability) throws IOException {
091      registerAndMarkMetrics(e, put);
092    }
093
094    @Override
095    public void preDelete(ObserverContext<RegionCoprocessorEnvironment> e, Delete delete,
096        WALEdit edit, Durability durability) throws IOException {
097      registerAndMarkMetrics(e, delete);
098    }
099
100    private void registerAndMarkMetrics(ObserverContext<RegionCoprocessorEnvironment> e, Row row){
101      if (!active || !isMetaTableOp(e)) {
102        return;
103      }
104      tableMetricRegisterAndMark(row);
105      clientMetricRegisterAndMark();
106      regionMetricRegisterAndMark(row);
107      opMetricRegisterAndMark(row);
108      opWithClientMetricRegisterAndMark(row);
109    }
110
111    /**
112     * Get table name from Ops such as: get, put, delete.
113     * @param op such as get, put or delete.
114     */
115    private String getTableNameFromOp(Row op) {
116      String tableName = null;
117      String tableRowKey = new String(((Row) op).getRow(), StandardCharsets.UTF_8);
118      if (tableRowKey.isEmpty()) {
119        return null;
120      }
121      tableName = tableRowKey.split(",").length > 0 ? tableRowKey.split(",")[0] : null;
122      return tableName;
123    }
124
125    /**
126     * Get regionId from Ops such as: get, put, delete.
127     * @param op  such as get, put or delete.
128     */
129    private String getRegionIdFromOp(Row op) {
130      String regionId = null;
131      String tableRowKey = new String(((Row) op).getRow(), StandardCharsets.UTF_8);
132      if (tableRowKey.isEmpty()) {
133        return null;
134      }
135      regionId = tableRowKey.split(",").length > 2 ? tableRowKey.split(",")[2] : null;
136      return regionId;
137    }
138
139    private boolean isMetaTableOp(ObserverContext<RegionCoprocessorEnvironment> e) {
140      return TableName.META_TABLE_NAME
141          .equals(e.getEnvironment().getRegionInfo().getTable());
142    }
143
144    private void clientMetricRegisterAndMark() {
145      // Mark client metric
146      String clientIP = RpcServer.getRemoteIp() != null ? RpcServer.getRemoteIp().toString() : null;
147      if (clientIP == null || clientIP.isEmpty()) {
148        return;
149      }
150      String clientRequestMeter = clientRequestMeterName(clientIP);
151      clientMetricsLossyCounting.add(clientRequestMeter);
152      registerAndMarkMeter(clientRequestMeter);
153    }
154
155    private void tableMetricRegisterAndMark(Row op) {
156      // Mark table metric
157      String tableName = getTableNameFromOp(op);
158      if (tableName == null || tableName.isEmpty()) {
159        return;
160      }
161      String tableRequestMeter = tableMeterName(tableName);
162      registerAndMarkMeter(tableRequestMeter);
163    }
164
165    private void regionMetricRegisterAndMark(Row op) {
166      // Mark region metric
167      String regionId = getRegionIdFromOp(op);
168      if (regionId == null || regionId.isEmpty()) {
169        return;
170      }
171      String regionRequestMeter = regionMeterName(regionId);
172      regionMetricsLossyCounting.add(regionRequestMeter);
173      registerAndMarkMeter(regionRequestMeter);
174    }
175
176    private void opMetricRegisterAndMark(Row op) {
177      // Mark access type ["get", "put", "delete"] metric
178      String opMeterName = opMeterName(op);
179      if (opMeterName == null || opMeterName.isEmpty()) {
180        return;
181      }
182      registerAndMarkMeter(opMeterName);
183    }
184
185    private void opWithClientMetricRegisterAndMark(Object op) {
186      // // Mark client + access type metric
187      String opWithClientMeterName = opWithClientMeterName(op);
188      if (opWithClientMeterName == null || opWithClientMeterName.isEmpty()) {
189        return;
190      }
191      registerAndMarkMeter(opWithClientMeterName);
192    }
193
194    // Helper function to register and mark meter if not present
195    private void registerAndMarkMeter(String requestMeter) {
196      if (requestMeter.isEmpty()) {
197        return;
198      }
199      if(!registry.get(requestMeter).isPresent()){
200        metrics.add(requestMeter);
201      }
202      registry.meter(requestMeter).mark();
203    }
204
205    private String opWithClientMeterName(Object op) {
206      // Extract meter name containing the client IP
207      String clientIP = RpcServer.getRemoteIp() != null ? RpcServer.getRemoteIp().toString() : "";
208      if (clientIP.isEmpty()) {
209        return "";
210      }
211      MetaTableOps ops = opsNameMap.get(op.getClass());
212      String opWithClientMeterName = "";
213      switch (ops) {
214        case GET:
215          opWithClientMeterName = String.format("MetaTable_client_%s_get_request", clientIP);
216          break;
217        case PUT:
218          opWithClientMeterName = String.format("MetaTable_client_%s_put_request", clientIP);
219          break;
220        case DELETE:
221          opWithClientMeterName = String.format("MetaTable_client_%s_delete_request", clientIP);
222          break;
223        default:
224          break;
225      }
226      return opWithClientMeterName;
227    }
228
229    private String opMeterName(Object op) {
230      // Extract meter name containing the access type
231      MetaTableOps ops = opsNameMap.get(op.getClass());
232      String opMeterName = "";
233      switch (ops) {
234        case GET:
235          opMeterName = "MetaTable_get_request";
236          break;
237        case PUT:
238          opMeterName = "MetaTable_put_request";
239          break;
240        case DELETE:
241          opMeterName = "MetaTable_delete_request";
242          break;
243        default:
244          break;
245      }
246      return opMeterName;
247    }
248
249    private String tableMeterName(String tableName) {
250      // Extract meter name containing the table name
251      return String.format("MetaTable_table_%s_request", tableName);
252    }
253
254    private String clientRequestMeterName(String clientIP) {
255      // Extract meter name containing the client IP
256      if (clientIP.isEmpty()) {
257        return "";
258      }
259      return String.format("MetaTable_client_%s_lossy_request", clientIP);
260    }
261
262    private String regionMeterName(String regionId) {
263      // Extract meter name containing the region ID
264      return String.format("MetaTable_region_%s_lossy_request", regionId);
265    }
266  }
267
268  @Override
269  public Optional<RegionObserver> getRegionObserver() {
270    return Optional.of(observer);
271  }
272
273  @Override
274  public void start(CoprocessorEnvironment env) throws IOException {
275    observer = new ExampleRegionObserverMeta();
276    if (env instanceof RegionCoprocessorEnvironment
277        && ((RegionCoprocessorEnvironment) env).getRegionInfo().getTable() != null
278        && ((RegionCoprocessorEnvironment) env).getRegionInfo().getTable()
279          .equals(TableName.META_TABLE_NAME)) {
280      RegionCoprocessorEnvironment regionCoprocessorEnv = (RegionCoprocessorEnvironment) env;
281      registry = regionCoprocessorEnv.getMetricRegistryForRegionServer();
282      LossyCounting.LossyCountingListener listener = new LossyCounting.LossyCountingListener(){
283        @Override public void sweep(String key) {
284          registry.remove(key);
285          metrics.remove(key);
286        }
287      };
288      clientMetricsLossyCounting = new LossyCounting("clientMetaMetrics",listener);
289      regionMetricsLossyCounting = new LossyCounting("regionMetaMetrics",listener);
290      // only be active mode when this region holds meta table.
291      active = true;
292    }
293  }
294
295  @Override
296  public void stop(CoprocessorEnvironment env) throws IOException {
297    // since meta region can move around, clear stale metrics when stop.
298    for(String metric:metrics){
299      registry.remove(metric);
300    }
301  }
302}