View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional information regarding
4    * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
7    * law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
8    * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
9    * for the specific language governing permissions and limitations under the License.
10   */
11  
12  package org.apache.hadoop.hbase.coprocessor;
13  
14  import java.io.IOException;
15  import java.nio.charset.StandardCharsets;
16  import java.util.List;
17  import java.util.Map;
18  import java.util.Set;
19  import java.util.concurrent.ConcurrentHashMap;
20  import org.apache.hadoop.hbase.Cell;
21  import org.apache.hadoop.hbase.CoprocessorEnvironment;
22  import org.apache.hadoop.hbase.TableName;
23  import org.apache.hadoop.hbase.classification.InterfaceAudience;
24  import org.apache.hadoop.hbase.client.Delete;
25  import org.apache.hadoop.hbase.client.Durability;
26  import org.apache.hadoop.hbase.client.Get;
27  import org.apache.hadoop.hbase.client.Put;
28  import org.apache.hadoop.hbase.client.Row;
29  import org.apache.hadoop.hbase.ipc.RpcServer;
30  import org.apache.hadoop.hbase.metrics.Meter;
31  import org.apache.hadoop.hbase.metrics.Metric;
32  import org.apache.hadoop.hbase.metrics.MetricRegistry;
33  import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
34  import org.apache.hadoop.hbase.util.LossyCounting;
35  
36  import com.google.common.base.Optional;
37  import com.google.common.collect.ImmutableMap;
38  /**
39   * A coprocessor that collects metrics from meta table.
40   * <p>
41   * These metrics will be available through the regular Hadoop metrics2 sinks (ganglia, opentsdb,
42   * etc) as well as JMX output.
43   * </p>
44   * @see MetaTableMetrics
45   */
46  
47  @InterfaceAudience.Private
48  public class MetaTableMetrics extends BaseRegionObserver {
49  
50    private Map<String, Optional<Metric>> requestsMap;
51    private RegionCoprocessorEnvironment regionCoprocessorEnv;
52    private LossyCounting clientMetricsLossyCounting;
53    private boolean active = false;
54  
55    enum MetaTableOps {
56      GET, PUT, DELETE;
57    }
58  
59    private ImmutableMap<Class, MetaTableOps> opsNameMap =
60        ImmutableMap.<Class, MetaTableOps>builder()
61                .put(Put.class, MetaTableOps.PUT)
62                .put(Get.class, MetaTableOps.GET)
63                .put(Delete.class, MetaTableOps.DELETE)
64                .build();
65  
66    @Override
67    public void preGetOp(ObserverContext<RegionCoprocessorEnvironment> e, Get get,
68        List<Cell> results) throws IOException {
69      if (!active || !isMetaTableOp(e)) {
70        return;
71      }
72      tableMetricRegisterAndMark(e, get);
73      clientMetricRegisterAndMark(e);
74      regionMetricRegisterAndMark(e, get);
75      opMetricRegisterAndMark(e, get);
76      opWithClientMetricRegisterAndMark(e, get);
77    }
78  
79    @Override
80    public void prePut(ObserverContext<RegionCoprocessorEnvironment> e, Put put, WALEdit edit,
81        Durability durability) throws IOException {
82      if (!active || !isMetaTableOp(e)) {
83        return;
84      }
85      tableMetricRegisterAndMark(e, put);
86      clientMetricRegisterAndMark(e);
87      regionMetricRegisterAndMark(e, put);
88      opMetricRegisterAndMark(e, put);
89      opWithClientMetricRegisterAndMark(e, put);
90    }
91  
92    @Override
93    public void preDelete(ObserverContext<RegionCoprocessorEnvironment> e, Delete delete,
94        WALEdit edit, Durability durability) throws IOException {
95      if (!active || !isMetaTableOp(e)) {
96        return;
97      }
98      tableMetricRegisterAndMark(e, delete);
99      clientMetricRegisterAndMark(e);
100     regionMetricRegisterAndMark(e, delete);
101     opMetricRegisterAndMark(e, delete);
102     opWithClientMetricRegisterAndMark(e, delete);
103   }
104 
105   private void markMeterIfPresent(String requestMeter) {
106     if (requestMeter.isEmpty()) {
107       return;
108     }
109     Metric metric =
110         requestsMap.get(requestMeter).isPresent() ? requestsMap.get(requestMeter).get() : null;
111     if (metric != null) {
112       ((Meter) metric).mark();
113     }
114   }
115 
116   private void registerMeterIfNotPresent(ObserverContext<RegionCoprocessorEnvironment> e,
117       String requestMeter) {
118     if (requestMeter.isEmpty()) {
119       return;
120     }
121     if (!requestsMap.containsKey(requestMeter)) {
122       MetricRegistry registry = regionCoprocessorEnv.getMetricRegistryForRegionServer();
123       registry.meter(requestMeter);
124       requestsMap.put(requestMeter, registry.get(requestMeter));
125     }
126   }
127 
128   /**
129    * Registers and counts lossyCount for Meters that kept by lossy counting.
130    * By using lossy count to maintain meters, at most 7 / e meters will be kept  (e is error rate)
131    * e.g. when e is 0.02 by default, at most 50 Clients request metrics will be kept
132    *      also, all kept elements have frequency higher than e * N. (N is total count)
133    * @param e Region coprocessor environment
134    * @param requestMeter meter to be registered
135    * @param lossyCounting lossyCounting object for one type of meters.
136    */
137   private void registerLossyCountingMeterIfNotPresent(
138       ObserverContext<RegionCoprocessorEnvironment> e,
139       String requestMeter, LossyCounting lossyCounting) {
140     if (requestMeter.isEmpty()) {
141       return;
142     }
143     Set<String> metersToBeRemoved = lossyCounting.addByOne(requestMeter);
144     if(!requestsMap.containsKey(requestMeter) && metersToBeRemoved.contains(requestMeter)){
145       for(String meter: metersToBeRemoved) {
146         //cleanup requestsMap according swept data from lossy count;
147         requestsMap.remove(meter);
148         MetricRegistry registry = regionCoprocessorEnv.getMetricRegistryForRegionServer();
149         registry.remove(meter);
150       }
151       // newly added meter is swept by lossy counting cleanup. No need to put it into requestsMap.
152       return;
153     }
154 
155     if (!requestsMap.containsKey(requestMeter)) {
156       MetricRegistry registry = regionCoprocessorEnv.getMetricRegistryForRegionServer();
157       registry.meter(requestMeter);
158       requestsMap.put(requestMeter, registry.get(requestMeter));
159     }
160   }
161 
162   /**
163    * Get table name from Ops such as: get, put, delete.
164    * @param op such as get, put or delete.
165    */
166   private String getTableNameFromOp(Row op) {
167     String tableName = null;
168     String tableRowKey = new String(((Row) op).getRow(), StandardCharsets.UTF_8);
169     if (tableRowKey.isEmpty()) {
170       return null;
171     }
172     tableName = tableRowKey.split(",").length > 0 ? tableRowKey.split(",")[0] : null;
173     return tableName;
174   }
175 
176   /**
177    * Get regionId from Ops such as: get, put, delete.
178    * @param op  such as get, put or delete.
179    */
180   private String getRegionIdFromOp(Row op) {
181     String regionId = null;
182     String tableRowKey = new String(((Row) op).getRow(), StandardCharsets.UTF_8);
183     if (tableRowKey.isEmpty()) {
184       return null;
185     }
186     regionId = tableRowKey.split(",").length > 2 ? tableRowKey.split(",")[2] : null;
187     return regionId;
188   }
189 
190   private boolean isMetaTableOp(ObserverContext<RegionCoprocessorEnvironment> e) {
191     return TableName.META_TABLE_NAME.toString()
192         .equals(new String(e.getEnvironment().getRegionInfo().getTable().getName(),
193             StandardCharsets.UTF_8));
194   }
195 
196   private void clientMetricRegisterAndMark(ObserverContext<RegionCoprocessorEnvironment> e) {
197     String clientIP = RpcServer.getRemoteIp() != null ? RpcServer.getRemoteIp().toString() : "";
198 
199     String clientRequestMeter = clientRequestMeterName(clientIP);
200     registerLossyCountingMeterIfNotPresent(e, clientRequestMeter, clientMetricsLossyCounting);
201     markMeterIfPresent(clientRequestMeter);
202   }
203 
204   private void tableMetricRegisterAndMark(ObserverContext<RegionCoprocessorEnvironment> e,
205       Row op) {
206     // Mark the meta table meter whenever the coprocessor is called
207     String tableName = getTableNameFromOp(op);
208     String tableRequestMeter = tableMeterName(tableName);
209     registerMeterIfNotPresent(e, tableRequestMeter);
210     markMeterIfPresent(tableRequestMeter);
211   }
212 
213   private void regionMetricRegisterAndMark(ObserverContext<RegionCoprocessorEnvironment> e,
214       Row op) {
215     // Mark the meta table meter whenever the coprocessor is called
216     String regionId = getRegionIdFromOp(op);
217     String regionRequestMeter = regionMeterName(regionId);
218     registerMeterIfNotPresent(e, regionRequestMeter);
219     markMeterIfPresent(regionRequestMeter);
220   }
221 
222   private void opMetricRegisterAndMark(ObserverContext<RegionCoprocessorEnvironment> e,
223       Row op) {
224     String opMeterName = opMeterName(op);
225     registerMeterIfNotPresent(e, opMeterName);
226     markMeterIfPresent(opMeterName);
227   }
228 
229   private void opWithClientMetricRegisterAndMark(ObserverContext<RegionCoprocessorEnvironment> e,
230       Object op) {
231     String opWithClientMeterName = opWithClientMeterName(op);
232     registerMeterIfNotPresent(e, opWithClientMeterName);
233     markMeterIfPresent(opWithClientMeterName);
234   }
235 
236   private String opWithClientMeterName(Object op) {
237     String clientIP = RpcServer.getRemoteIp() != null ? RpcServer.getRemoteIp().toString() : "";
238     if (clientIP.isEmpty()) {
239       return "";
240     }
241     MetaTableOps ops = opsNameMap.get(op.getClass());
242     String opWithClientMeterName = "";
243     switch (ops) {
244       case GET:
245         opWithClientMeterName = String.format("MetaTable_client_%s_get_request", clientIP);
246         break;
247       case PUT:
248         opWithClientMeterName = String.format("MetaTable_client_%s_put_request", clientIP);
249         break;
250       case DELETE:
251         opWithClientMeterName = String.format("MetaTable_client_%s_delete_request", clientIP);
252         break;
253       default:
254         break;
255     }
256     return opWithClientMeterName;
257   }
258 
259   private String opMeterName(Object op) {
260     MetaTableOps ops = opsNameMap.get(op.getClass());
261     String opMeterName = "";
262     switch (ops) {
263       case GET:
264         opMeterName = "MetaTable_get_request";
265         break;
266       case PUT:
267         opMeterName = "MetaTable_put_request";
268         break;
269       case DELETE:
270         opMeterName = "MetaTable_delete_request";
271         break;
272       default:
273         break;
274     }
275     return opMeterName;
276   }
277 
278   private String tableMeterName(String tableName) {
279     return String.format("MetaTable_table_%s_request", tableName);
280   }
281 
282   private String clientRequestMeterName(String clientIP) {
283     if (clientIP.isEmpty()) {
284       return "";
285     }
286     return String.format("MetaTable_client_%s_request", clientIP);
287   }
288 
289   private String regionMeterName(String regionId) {
290     return String.format("MetaTable_region_%s_request", regionId);
291   }
292 
293   @Override
294   public void start(CoprocessorEnvironment env) throws IOException {
295     if (env instanceof RegionCoprocessorEnvironment
296         && ((RegionCoprocessorEnvironment) env).getRegionInfo().getTable() != null
297         && ((RegionCoprocessorEnvironment) env).getRegionInfo().getTable()
298           .equals(TableName.META_TABLE_NAME)) {
299       regionCoprocessorEnv = (RegionCoprocessorEnvironment) env;
300       requestsMap = new ConcurrentHashMap<>();
301       clientMetricsLossyCounting = new LossyCounting();
302       // only be active mode when this region holds meta table.
303       active = true;
304     }
305   }
306 
307   @Override
308   public void stop(CoprocessorEnvironment env) throws IOException {
309     // since meta region can move around, clear stale metrics when stop.
310     if (requestsMap != null) {
311       for (String meterName : requestsMap.keySet()) {
312         MetricRegistry registry = regionCoprocessorEnv.getMetricRegistryForRegionServer();
313         registry.remove(meterName);
314       }
315     }
316   }
317 
318 }