001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.master.http;
019
020import java.io.IOException;
021import java.lang.reflect.Type;
022import java.util.Collections;
023import java.util.List;
024import java.util.concurrent.ExecutionException;
025import java.util.stream.Collectors;
026import org.apache.commons.lang3.builder.EqualsBuilder;
027import org.apache.commons.lang3.builder.HashCodeBuilder;
028import org.apache.commons.lang3.builder.ToStringBuilder;
029import org.apache.commons.lang3.builder.ToStringStyle;
030import org.apache.hadoop.conf.Configuration;
031import org.apache.hadoop.hbase.ClusterMetrics;
032import org.apache.hadoop.hbase.HBaseConfiguration;
033import org.apache.hadoop.hbase.RegionMetrics;
034import org.apache.hadoop.hbase.ServerMetrics;
035import org.apache.hadoop.hbase.ServerName;
036import org.apache.hadoop.hbase.Size;
037import org.apache.hadoop.hbase.TableName;
038import org.apache.hadoop.hbase.client.Admin;
039import org.apache.hadoop.hbase.client.AsyncAdmin;
040import org.apache.hadoop.hbase.client.AsyncConnection;
041import org.apache.hadoop.hbase.client.ConnectionFactory;
042import org.apache.hadoop.hbase.client.RegionInfo;
043import org.apache.hadoop.hbase.util.AbstractHBaseTool;
044import org.apache.hadoop.hbase.util.Bytes;
045import org.apache.yetus.audience.InterfaceAudience;
046import org.slf4j.Logger;
047import org.slf4j.LoggerFactory;
048
049import org.apache.hbase.thirdparty.com.google.gson.FieldNamingPolicy;
050import org.apache.hbase.thirdparty.com.google.gson.Gson;
051import org.apache.hbase.thirdparty.com.google.gson.GsonBuilder;
052import org.apache.hbase.thirdparty.com.google.gson.JsonElement;
053import org.apache.hbase.thirdparty.com.google.gson.JsonObject;
054import org.apache.hbase.thirdparty.com.google.gson.JsonPrimitive;
055import org.apache.hbase.thirdparty.com.google.gson.JsonSerializationContext;
056import org.apache.hbase.thirdparty.com.google.gson.JsonSerializer;
057import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine;
058
059/**
060 * Support class for the "Region Visualizer" rendered out of
061 * {@code src/main/jamon/org/apache/hadoop/hbase/tmpl/master/RegionVisualizerTmpl.jamon}
062 */
063@InterfaceAudience.Private
064public class RegionVisualizer extends AbstractHBaseTool {
065  private static final Logger LOG = LoggerFactory.getLogger(RegionVisualizer.class);
066
067  private final Admin admin;
068  private final AsyncAdmin asyncAdmin;
069  private final Gson gson;
070
071  public RegionVisualizer() {
072    admin = null;
073    asyncAdmin = null;
074    gson = null;
075  }
076
077  public RegionVisualizer(final Admin admin) {
078    this.admin = admin;
079    this.asyncAdmin = null;
080    this.gson = buildGson();
081  }
082
083  public RegionVisualizer(final AsyncAdmin asyncAdmin) {
084    this.admin = null;
085    this.asyncAdmin = asyncAdmin;
086    this.gson = buildGson();
087  }
088
089  public String renderRegionDetails() {
090    return gson.toJson(clusterStatusToRegionDetails());
091  }
092
093  public static void main(final String[] argv) {
094    new RegionVisualizer().doStaticMain(argv);
095  }
096
097  static Gson buildGson() {
098    return new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
099      .enableComplexMapKeySerialization()
100      .registerTypeAdapter(byte[].class, new ByteArraySerializer())
101      .registerTypeAdapter(Size.class, new SizeAsBytesSerializer())
102      .registerTypeAdapter(RegionDetails.class, new RegionDetailsSerializer()).create();
103  }
104
105  private ClusterMetrics getClusterMetrics()
106    throws ExecutionException, InterruptedException, IOException {
107    if (admin != null) {
108      return admin.getClusterMetrics();
109    }
110    if (asyncAdmin != null) {
111      return asyncAdmin.getClusterMetrics().get();
112    }
113    throw new RuntimeException("should not happen");
114  }
115
116  private List<RegionDetails> clusterStatusToRegionDetails() {
117    final ClusterMetrics clusterMetrics;
118    try {
119      clusterMetrics = getClusterMetrics();
120    } catch (Exception e) {
121      LOG.warn("Failed to retrieve cluster metrics.", e);
122      return Collections.emptyList();
123    }
124
125    return clusterMetrics.getLiveServerMetrics().entrySet().stream().flatMap(serverEntry -> {
126      final ServerName serverName = serverEntry.getKey();
127      final ServerMetrics serverMetrics = serverEntry.getValue();
128      return serverMetrics.getRegionMetrics().values().stream().map(regionMetrics -> {
129        final TableName tableName = RegionInfo.getTable(regionMetrics.getRegionName());
130        return new RegionDetails(serverName, tableName, regionMetrics);
131      });
132    }).collect(Collectors.toList());
133  }
134
135  @Override
136  protected void addOptions() {
137
138  }
139
140  @Override
141  protected void processOptions(CommandLine cmd) {
142
143  }
144
145  @Override
146  protected int doWork() throws Exception {
147    final Configuration conf = HBaseConfiguration.create(getConf());
148    final AsyncConnection conn = ConnectionFactory.createAsyncConnection(conf).get();
149    final RegionVisualizer viz = new RegionVisualizer(conn.getAdmin());
150    System.out.println(viz.renderRegionDetails());
151    return 0;
152  }
153
154  private static final class ByteArraySerializer implements JsonSerializer<byte[]> {
155
156    @Override
157    public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
158      return new JsonPrimitive(Bytes.toString(src));
159    }
160  }
161
162  /**
163   * Simplify representation of a {@link Size} instance by converting to bytes.
164   */
165  private static final class SizeAsBytesSerializer implements JsonSerializer<Size> {
166
167    @Override
168    public JsonElement serialize(Size src, Type typeOfSrc, JsonSerializationContext context) {
169      return new JsonPrimitive(src.get(Size.Unit.BYTE));
170    }
171  }
172
173  /**
174   * "Flatten" the serialized representation of a {@link RegionDetails}.
175   */
176  private static final class RegionDetailsSerializer implements JsonSerializer<RegionDetails> {
177    @Override
178    public JsonElement serialize(RegionDetails src, Type typeOfSrc,
179      JsonSerializationContext context) {
180      final JsonObject jobj = (JsonObject) context.serialize(src.getRegionMetrics());
181      jobj.addProperty("server_name", src.getServerName().toShortString());
182      jobj.addProperty("table_name", src.getTableName().getNameAsString());
183      return jobj;
184    }
185  }
186
187  /**
188   * POJO carrying detailed information about a region for use in visualizations. Intended to be
189   * serialized to JSON and consumed from JavaScript.
190   */
191  public static final class RegionDetails {
192    private final ServerName serverName;
193    private final TableName tableName;
194    private final RegionMetrics regionMetrics;
195
196    RegionDetails(final ServerName serverName, final TableName tableName,
197      final RegionMetrics regionMetrics) {
198      this.serverName = serverName;
199      this.tableName = tableName;
200      this.regionMetrics = regionMetrics;
201    }
202
203    public ServerName getServerName() {
204      return serverName;
205    }
206
207    public TableName getTableName() {
208      return tableName;
209    }
210
211    public RegionMetrics getRegionMetrics() {
212      return regionMetrics;
213    }
214
215    @Override
216    public String toString() {
217      return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
218        .append("serverName", serverName).append("tableName", tableName)
219        .append("regionMetrics", regionMetrics).toString();
220    }
221
222    @Override
223    public boolean equals(Object o) {
224      if (this == o) {
225        return true;
226      }
227
228      if (o == null || getClass() != o.getClass()) {
229        return false;
230      }
231
232      RegionDetails that = (RegionDetails) o;
233
234      return new EqualsBuilder().append(serverName, that.serverName)
235        .append(tableName, that.tableName).append(regionMetrics, that.regionMetrics).isEquals();
236    }
237
238    @Override
239    public int hashCode() {
240      return new HashCodeBuilder(17, 37).append(serverName).append(tableName).append(regionMetrics)
241        .toHashCode();
242    }
243  }
244}