View Javadoc

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