/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.accumulo.server.monitor.servlets;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilePermission;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.accumulo.core.conf.AccumuloConfiguration;
import org.apache.accumulo.core.conf.Property;
import org.apache.accumulo.core.master.thrift.MasterMonitorInfo;
import org.apache.accumulo.core.util.CachedConfiguration;
import org.apache.accumulo.core.util.Duration;
import org.apache.accumulo.core.util.Pair;
import org.apache.accumulo.server.monitor.Monitor;
import org.apache.accumulo.server.monitor.ZooKeeperStatus;
import org.apache.accumulo.server.monitor.ZooKeeperStatus.ZooKeeperState;
import org.apache.accumulo.server.monitor.util.celltypes.NumberType;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.ContentSummary;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hdfs.DistributedFileSystem;
import org.apache.hadoop.hdfs.protocol.DatanodeInfo;
import org.apache.hadoop.hdfs.protocol.FSConstants.DatanodeReportType;
import org.apache.hadoop.mapred.ClusterStatus;
import org.apache.hadoop.mapred.JobClient;
import org.apache.hadoop.mapred.JobTracker;
public class DefaultServlet extends BasicServlet {
private static final long serialVersionUID = 1L;
@Override
protected String getTitle(HttpServletRequest req) {
return req.getRequestURI().startsWith("/docs") ? "Documentation" : "Accumulo Overview";
}
private void getResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String path = req.getRequestURI();
if (path.endsWith(".jpg"))
resp.setContentType("image/jpeg");
if (path.endsWith(".html"))
resp.setContentType("text/html");
path = path.substring(1);
InputStream data = BasicServlet.class.getClassLoader().getResourceAsStream(path);
if (data != null) {
byte[] buffer = new byte[1024];
int n;
ServletOutputStream out = resp.getOutputStream();
while ((n = data.read(buffer)) > 0)
out.write(buffer, 0, n);
}
}
private void getDocResource(HttpServletRequest req, final HttpServletResponse resp) throws IOException {
final String path = req.getRequestURI();
if (path.endsWith(".html"))
resp.setContentType("text/html");
// Allow user to only read any file in docs directory
final String aHome = System.getenv("ACCUMULO_HOME");
PermissionCollection pc = new Permissions();
pc.add(new FilePermission(aHome + "/docs/-", "read"));
AccessControlContext acc = new AccessControlContext(new ProtectionDomain[] {new ProtectionDomain(null, pc)});
IOException e = AccessController.doPrivileged(new PrivilegedAction<IOException>() {
@Override
public IOException run() {
try {
File file = new File(aHome + path);
InputStream data = new FileInputStream(file.getAbsolutePath());
byte[] buffer = new byte[1024];
int n;
ServletOutputStream out = resp.getOutputStream();
while ((n = data.read(buffer)) > 0)
out.write(buffer, 0, n);
return null;
} catch (IOException e) {
return e;
}
}
}, acc);
if (e != null)
throw e;
}
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (req.getRequestURI().startsWith("/web"))
getResource(req, resp);
else if (req.getRequestURI().equals("/docs") || req.getRequestURI().equals("/docs/apidocs"))
super.doGet(req, resp);
else if (req.getRequestURI().startsWith("/docs"))
getDocResource(req, resp);
else if (req.getRequestURI().startsWith("/monitor"))
resp.sendRedirect("/master");
else if (req.getRequestURI().startsWith("/errors"))
resp.sendRedirect("/problems");
else
super.doGet(req, resp);
}
public static final int GRAPH_WIDTH = 450;
public static final int GRAPH_HEIGHT = 150;
@SuppressWarnings("unchecked")
private static void plotData(StringBuilder sb, String title, @SuppressWarnings("rawtypes") List data, boolean points) {
sb.append("<div class=\"plotHeading\">");
sb.append(title);
sb.append("</div>");
sb.append("</br>");
String id = "c" + title.hashCode();
sb.append("<div id=\"" + id + "\" style=\"width:" + GRAPH_WIDTH + "px;height:" + GRAPH_HEIGHT + "px;\"></div>\n");
sb.append("<script type=\"text/javascript\">\n");
sb.append("$(function () {\n");
sb.append(" var d1 = [");
String sep = "";
for (Pair<Long,? extends Number> point : (List<Pair<Long,? extends Number>>) data) {
if (point.getSecond() == null)
continue;
String y;
if (point.getSecond() instanceof Double)
y = String.format("%1.2f", point.getSecond());
else
y = point.getSecond().toString();
sb.append(sep);
sep = ",";
sb.append("[" + point.getFirst() + "," + y + "]");
}
String opts = "lines: { show: true }";
if (points)
opts = "points: { show: true, radius: 1 }";
sb.append(" ];\n");
sb.append(" $.plot($(\"#" + id + "\"), [{ data: d1, " + opts
+ ", color:\"red\" }], {yaxis:{}, xaxis:{mode:\"time\",minTickSize: [1, \"minute\"],timeformat: \"%H:%M\", ticks:3}});");
sb.append(" });\n");
sb.append("</script>\n");
}
@Override
protected void pageBody(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) throws IOException {
if (req.getRequestURI().equals("/docs") || req.getRequestURI().equals("/docs/apidocs")) {
sb.append("<object data='").append(req.getRequestURI()).append("/index.html' type='text/html' width='100%' height='100%'></object>");
return;
}
sb.append("<table class='noborder'>\n");
sb.append("<tr>\n");
sb.append("<td class='noborder'>\n");
doAccumuloTable(sb);
sb.append("</td>\n");
sb.append("<td class='noborder'>\n");
doHdfsTable(sb);
sb.append("</td>\n");
sb.append("<td class='noborder'>\n");
doJobTrackerTable(sb);
sb.append("</td>\n");
sb.append("<td class='noborder'>\n");
doZooKeeperTable(sb);
sb.append("</td>\n");
sb.append("</tr></table>\n");
sb.append("<br/>\n");
sb.append("<p/><table class=\"noborder\">\n");
sb.append("<tr><td>\n");
plotData(sb, "Ingest (Entries/s)", Monitor.getIngestRateOverTime(), false);
sb.append("</td><td>\n");
plotData(sb, "Scan (Entries/s)", Monitor.getQueryRateOverTime(), false);
sb.append("</td></tr>\n");
sb.append("<tr><td>\n");
plotData(sb, "Ingest (MB/s)", Monitor.getIngestByteRateOverTime(), false);
sb.append("</td><td>\n");
plotData(sb, "Scan (MB/s)", Monitor.getQueryByteRateOverTime(), false);
sb.append("</td></tr>\n");
sb.append("<tr><td>\n");
plotData(sb, "Load Average", Monitor.getLoadOverTime(), false);
sb.append("</td><td>\n");
plotData(sb, "Scan Sessions", Monitor.getLookupsOverTime(), false);
sb.append("</td></tr>\n");
sb.append("<tr><td>\n");
plotData(sb, "Minor Compactions", Monitor.getMinorCompactionsOverTime(), false);
sb.append("</td><td>\n");
plotData(sb, "Major Compactions", Monitor.getMajorCompactionsOverTime(), false);
sb.append("</td></tr>\n");
sb.append("</table>\n");
}
private void doAccumuloTable(StringBuilder sb) throws IOException {
// Accumulo
Configuration conf = CachedConfiguration.getInstance();
DistributedFileSystem fs = (DistributedFileSystem) FileSystem.get(conf);
MasterMonitorInfo info = Monitor.getMmi();
sb.append("<table>\n");
sb.append("<tr><th colspan='2'><a href='/master'>Accumulo Master</a></th></tr>\n");
if (info == null) {
sb.append("<tr><td colspan='2'><span class='error'>Master is Down</span></td></tr>\n");
} else {
String consumed = "Unknown";
String diskUsed = "Unknown";
try {
ContentSummary all = fs.getContentSummary(new Path("/"));
ContentSummary acu = fs.getContentSummary(new Path(AccumuloConfiguration.getSystemConfiguration().get(Property.INSTANCE_DFS_DIR)));
consumed = String.format("%.2f%%", acu.getLength() / (all.getLength() / 100.));
diskUsed = bytes(acu.getSpaceConsumed());
} catch (Exception e) {
log.debug(e, e);
}
boolean highlight = false;
tableRow(sb, (highlight = !highlight), "Disk Used", diskUsed);
tableRow(sb, (highlight = !highlight), "% of Used DFS", consumed);
tableRow(sb, (highlight = !highlight), "<a href='/tservers'>Tablet Servers</a>", NumberType.commas(info.tServerInfo.size()));
tableRow(sb, (highlight = !highlight), "Tablets", NumberType.commas(Monitor.getTotalTabletCount()));
tableRow(sb, (highlight = !highlight), "Entries", NumberType.commas(Monitor.getTotalEntries()));
tableRow(sb, (highlight = !highlight), "Lookups", NumberType.commas(Monitor.getTotalLookups()));
tableRow(sb, (highlight = !highlight), "Uptime", Duration.format(System.currentTimeMillis() - Monitor.getStartTime()));
}
sb.append("</table>\n");
}
private void doHdfsTable(StringBuilder sb) throws IOException {
// HDFS
Configuration conf = CachedConfiguration.getInstance();
DistributedFileSystem fs = (DistributedFileSystem) FileSystem.get(conf);
String httpAddress = conf.get("dfs.http.address");
String port = httpAddress.split(":")[1];
String href = "http://" + fs.getUri().getHost() + ":" + port;
sb.append("<table>\n");
sb.append("<tr><th colspan='2'><a href='" + href + "'>NameNode</a></th></tr>\n");
try {
boolean highlight = false;
tableRow(sb, (highlight = !highlight), "Unreplicated Capacity", bytes(fs.getRawCapacity()));
tableRow(sb, (highlight = !highlight), "% Used", NumberType.commas(fs.getRawUsed() * 100. / fs.getRawCapacity(), 0, 90, 0, 100) + "%");
tableRow(sb, (highlight = !highlight), "Corrupt Blocks", NumberType.commas(fs.getCorruptBlocksCount(), 0, 0));
DatanodeInfo[] liveNodes = fs.getClient().datanodeReport(DatanodeReportType.LIVE);
DatanodeInfo[] deadNodes = fs.getClient().datanodeReport(DatanodeReportType.DEAD);
tableRow(sb, (highlight = !highlight), "Live Data Nodes", NumberType.commas(liveNodes.length));
tableRow(sb, (highlight = !highlight), "Dead Data Nodes", NumberType.commas(deadNodes.length));
long count = 0;
for (DatanodeInfo stat : liveNodes)
count += stat.getXceiverCount();
tableRow(sb, (highlight = !highlight), "Xceivers", NumberType.commas(count));
} catch (Exception ex) {
sb.append("<tr><td colspan='2'><span class='error'>Name Node is Down</span></td></tr>\n");
}
sb.append("</table>\n");
}
private void doJobTrackerTable(StringBuilder sb) {
// Job Tracker
Configuration conf = CachedConfiguration.getInstance();
sb.append("<table>\n");
try {
InetSocketAddress address = JobTracker.getAddress(conf);
@SuppressWarnings("deprecation")
JobClient jc = new JobClient(new org.apache.hadoop.mapred.JobConf(conf));
String httpAddress = conf.get("mapred.job.tracker.http.address");
String port = httpAddress.split(":")[1];
String href = "http://" + address.getHostName() + ":" + port;
sb.append("<tr><th colspan='2'><a href='" + href + "'>JobTracker</a></th></tr>\n");
boolean highlight = false;
tableRow(sb, (highlight = !highlight), "Running Jobs", jc.jobsToComplete().length);
ClusterStatus status = jc.getClusterStatus();
tableRow(sb, (highlight = !highlight), "Map Tasks", status.getMapTasks() + "/" + status.getMaxMapTasks());
tableRow(sb, (highlight = !highlight), "Reduce Tasks", status.getReduceTasks() + "/" + status.getMaxReduceTasks());
tableRow(sb, (highlight = !highlight), "Trackers", status.getTaskTrackers());
tableRow(sb, (highlight = !highlight), "Blacklisted", status.getBlacklistedTrackers());
} catch (Exception ex) {
sb.append("<tr><td colspan='2'><span class='error'>Job Tracker is Down</span></td></tr>\n");
}
sb.append("</table>\n");
}
private void doZooKeeperTable(StringBuilder sb) throws IOException {
// Zookeepers
sb.append("<table>\n");
sb.append("<tr><th colspan='3'>Zookeeper</th></tr>\n");
sb.append("<tr><th>Server</th><th>Mode</th><th>Clients</th></tr>\n");
boolean highlight = false;
for (ZooKeeperState k : ZooKeeperStatus.getZooKeeperStatus()) {
if (k.clients >= 0) {
tableRow(sb, (highlight = !highlight), k.keeper, k.mode, k.clients);
} else {
tableRow(sb, false, k.keeper, "<span class='error'>Down</span>", "");
}
}
sb.append("</table>\n");
}
private static final String BYTES[] = {"", "K", "M", "G", "T", "P", "E", "Z"};
private static String bytes(long big) {
return NumberType.bigNumber(big, BYTES, 1024);
}
public static void tableRow(StringBuilder sb, boolean highlight, Object... cells) {
sb.append(highlight ? "<tr class='highlight'>" : "<tr>");
for (int i = 0; i < cells.length; ++i) {
Object cell = cells[i];
String cellValue = cell == null ? "" : String.valueOf(cell).trim();
sb.append("<td class='").append(i < cells.length - 1 ? "left" : "right").append("'>").append(cellValue.isEmpty() ? "-" : cellValue).append("</td>");
}
sb.append("</tr>\n");
}
}