You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@accumulo.apache.org by ct...@apache.org on 2013/09/06 03:48:48 UTC
[20/53] [abbrv] ACCUMULO-656,ACCUMULO-658 Update package names
http://git-wip-us.apache.org/repos/asf/accumulo/blob/f00c8fd5/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/BasicServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/BasicServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/BasicServlet.java
new file mode 100644
index 0000000..186bd43
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/BasicServlet.java
@@ -0,0 +1,269 @@
+/*
+ * 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.monitor.servlets;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.Date;
+import java.util.TimerTask;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.Constants;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.monitor.LogService;
+import org.apache.accumulo.monitor.Monitor;
+import org.apache.accumulo.server.client.HdfsZooInstance;
+import org.apache.accumulo.server.util.time.SimpleTimer;
+import org.apache.log4j.Logger;
+
+abstract public class BasicServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+ protected static final Logger log = Logger.getLogger(BasicServlet.class);
+ static String cachedInstanceName = null;
+ private static String bannerText;
+ private static String bannerColor;
+ private static String bannerBackground;
+
+ abstract protected String getTitle(HttpServletRequest req);
+
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ StringBuilder sb = new StringBuilder();
+ try {
+ Monitor.fetchData();
+ bannerText = sanitize(Monitor.getSystemConfiguration().get(Property.MONITOR_BANNER_TEXT));
+ bannerColor = Monitor.getSystemConfiguration().get(Property.MONITOR_BANNER_COLOR).replace("'", "'");
+ bannerBackground = Monitor.getSystemConfiguration().get(Property.MONITOR_BANNER_BACKGROUND).replace("'", "'");
+ pageStart(req, resp, sb);
+ pageBody(req, resp, sb);
+ pageEnd(req, resp, sb);
+ } catch (Throwable t) {
+ log.error("Error building page " + req.getRequestURI(), t);
+ sb.append("\n<pre>\n");
+ StringWriter sw = new StringWriter();
+ t.printStackTrace(new PrintWriter(sw));
+ sb.append(sanitize(sw.getBuffer().toString()));
+ sb.append("</pre>\n");
+ } finally {
+ resp.getWriter().print(sb);
+ resp.getWriter().flush();
+ }
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ doGet(req, resp);
+ }
+
+ private static final String DEFAULT_CONTENT_TYPE = "text/html";
+
+ public static final Cookie getCookie(HttpServletRequest req, String name) {
+ if (req.getCookies() != null)
+ for (Cookie c : req.getCookies())
+ if (c.getName().equals(name))
+ return c;
+ return null;
+ }
+
+ protected void pageStart(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) throws Exception {
+ resp.setContentType(DEFAULT_CONTENT_TYPE);
+ int refresh = -1;
+ Cookie c = getCookie(req, "page.refresh.rate");
+ if (c != null && c.getValue() != null) {
+ try {
+ refresh = Integer.parseInt(c.getValue());
+ } catch (NumberFormatException e) {
+ // ignore improperly formatted user cookie
+ }
+ }
+ synchronized (BasicServlet.class) {
+ // Learn our instance name asynchronously so we don't hang up if zookeeper is down
+ if (cachedInstanceName == null) {
+ SimpleTimer.getInstance().schedule(new TimerTask() {
+ @Override
+ public void run() {
+ synchronized (BasicServlet.class) {
+ if (cachedInstanceName == null) {
+ cachedInstanceName = HdfsZooInstance.getInstance().getInstanceName();
+ }
+ }
+ }
+ }, 1000);
+ }
+ }
+
+ // BEGIN PAGE
+ sb.append("<!--\n");
+ sb.append(" Licensed to the Apache Software Foundation (ASF) under one or more\n");
+ sb.append(" contributor license agreements. See the NOTICE file distributed with\n");
+ sb.append(" this work for additional information regarding copyright ownership.\n");
+ sb.append(" The ASF licenses this file to You under the Apache License, Version 2.0\n");
+ sb.append(" (the \"License\"); you may not use this file except in compliance with\n");
+ sb.append(" the License. You may obtain a copy of the License at\n");
+ sb.append("\n");
+ sb.append(" http://www.apache.org/licenses/LICENSE-2.0\n");
+ sb.append("\n");
+ sb.append(" Unless required by applicable law or agreed to in writing, software\n");
+ sb.append(" distributed under the License is distributed on an \"AS IS\" BASIS,\n");
+ sb.append(" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n");
+ sb.append(" See the License for the specific language governing permissions and\n");
+ sb.append(" limitations under the License.\n");
+ sb.append("-->\n");
+ sb.append("<html>\n");
+
+ // BEGIN HEADER
+ sb.append("<head>\n");
+ sb.append("<title>").append(getTitle(req)).append(" - Accumulo ").append(Constants.VERSION).append("</title>\n");
+ if ((refresh > 0) && (req.getRequestURI().startsWith("/docs") == false) && (req.getRequestURI().startsWith("/vis") == false)
+ && (req.getRequestURI().startsWith("/shell") == false))
+ sb.append("<meta http-equiv='refresh' content='" + refresh + "' />\n");
+ sb.append("<meta http-equiv='Content-Type' content='").append(DEFAULT_CONTENT_TYPE).append("' />\n");
+ sb.append("<meta http-equiv='Content-Script-Type' content='text/javascript' />\n");
+ sb.append("<meta http-equiv='Content-Style-Type' content='text/css' />\n");
+ sb.append("<link rel='shortcut icon' type='image/jpg' href='/web/favicon.png' />\n");
+ sb.append("<link rel='stylesheet' type='text/css' href='/web/screen.css' media='screen' />\n");
+ sb.append("<script src='/web/functions.js' type='text/javascript'></script>\n");
+
+ sb.append("<!--[if lte IE 8]><script language=\"javascript\" type=\"text/javascript\" src=\"/web/flot/excanvas.min.js\"></script><![endif]-->\n");
+ sb.append("<script language=\"javascript\" type=\"text/javascript\" src=\"/web/flot/jquery.js\"></script>\n");
+ sb.append("<script language=\"javascript\" type=\"text/javascript\" src=\"/web/flot/jquery.flot.js\"></script>\n");
+
+ sb.append("</head>\n");
+
+ // BEGIN BODY OPENING
+ sb.append("\n<body ").append(getBodyAttributes()).append(">\n");
+ sb.append("<div id='content-wrapper'>\n");
+ sb.append("<div id='content'>\n");
+ sb.append("<div id='header'>");
+ if (!bannerText.isEmpty()) {
+ sb.append("<div id='banner' style='color:").append(bannerColor).append(";background:").append(bannerBackground).append("'>").append(bannerText)
+ .append("</div>\n");
+ }
+ sb.append("<div id='headertitle'>");
+ sb.append("<h1>").append(getTitle(req)).append("</h1></div>\n");
+ sb.append("<div id='subheader'>Instance Name: ").append(cachedInstanceName).append(" Version: ").append(Constants.VERSION)
+ .append("\n");
+ sb.append("<br><span class='smalltext'>Instance ID: ").append(HdfsZooInstance.getInstance().getInstanceID()).append("</span>\n");
+ sb.append("<br><span class='smalltext'>").append(new Date().toString().replace(" ", " ")).append("</span>");
+ sb.append("</div>\n"); // end <div id='subheader'>
+ sb.append("</div>\n"); // end <div id='header'>
+
+ // BEGIN LEFT SIDE
+ sb.append("<div id='nav'>\n");
+ sb.append("<span id='nav-title'><a href='/'>Overview</a></span><br />\n");
+ sb.append("<hr />\n");
+ sb.append("<a href='/master'>Master Server</a><br />\n");
+ sb.append("<a href='/tservers'>Tablet Servers</a><br />\n");
+ sb.append("<a href='/vis'>Server Activity</a><br />\n");
+ sb.append("<a href='/gc'>Garbage Collector</a><br />\n");
+ sb.append("<a href='/tables'>Tables</a><br />\n");
+ sb.append("<a href='/trace/summary?minutes=10'>Recent Traces</a><br />\n");
+ sb.append("<a href='/docs'>Documentation</a><br />\n");
+ int numLogs = LogService.getInstance().getEvents().size();
+ if (numLogs > 0)
+ sb.append("<span class='error'><a href='/log'>Recent Logs <span class='smalltext'>(" + numLogs + ")</a></span></span><br />\n");
+ int numProblems = Monitor.getProblemSummary().entrySet().size();
+ if (numProblems > 0)
+ sb.append("<span class='error'><a href='/problems'>Table Problems <span class='smalltext'>(" + numProblems + ")</a></span></span><br />\n");
+ sb.append("<hr />\n");
+ sb.append("<a href='/xml'>XML</a><br />\n");
+ sb.append("<a href='/json'>JSON</a><hr />\n");
+ if (Monitor.isUsingSsl())
+ sb.append("<a href='/shell'>Shell</a><hr />\n");
+ sb.append("<div class='smalltext'>[<a href='").append("/op?action=refresh&value=").append(refresh < 1 ? "5" : "-1");
+ sb.append("&redir=").append(currentPage(req)).append("'>");
+ sb.append(refresh < 1 ? "en" : "dis").append("able auto-refresh</a>]</div>\n");
+ sb.append("</div>\n"); // end <div id='nav'>
+
+ sb.append("<div id='main'");
+ if (bannerText.isEmpty())
+ sb.append(" style='bottom:0'");
+ sb.append(">\n");
+ sb.append("<!-- BEGIN MAIN BODY CONTENT -->\n\n");
+ }
+
+ protected void pageBody(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) throws Exception {
+ sb.append("This page intentionally left blank.");
+ }
+
+ protected void pageEnd(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) throws Exception {
+ sb.append("\n<!-- END MAIN BODY CONTENT -->\n");
+ sb.append("</div>\n"); // end <div id='main'>
+
+ // BEGIN FOOTER
+ sb.append("</div>\n"); // end <div id='content'>
+ sb.append("</div>\n"); // end <div id='content-wrapper'>
+ if (!bannerText.isEmpty()) {
+ sb.append("<div id='footer' style='color:").append(bannerColor).append(";background:").append(bannerBackground).append("'>").append(bannerText)
+ .append("</div>\n");
+ }
+ sb.append("</body>\n");
+ sb.append("</html>\n");
+ }
+
+ /**
+ * Allow the concrete servlet implementation to provide attributes on the body HTML tag, such as 'onload', which can be used to call Javascript methods on
+ * page load. By default, nothing is specified.
+ */
+ protected String getBodyAttributes() {
+ return "";
+ }
+
+ public static String encode(String s) {
+ try {
+ return URLEncoder.encode(s, Constants.UTF8.name());
+ } catch (UnsupportedEncodingException e) {
+ Logger.getLogger(BasicServlet.class).fatal(Constants.UTF8.name() + " is not a recognized encoding", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static String decode(String s) {
+ try {
+ return URLDecoder.decode(s, Constants.UTF8.name());
+ } catch (UnsupportedEncodingException e) {
+ Logger.getLogger(BasicServlet.class).fatal(Constants.UTF8.name() + " is not a recognized encoding", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static String sanitize(String xml) {
+ return xml.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
+ }
+
+ public static String currentPage(HttpServletRequest req) {
+ String redir = req.getRequestURI();
+ if (req.getQueryString() != null)
+ redir += "?" + req.getQueryString();
+ return encode(redir);
+ }
+
+ protected static void banner(StringBuilder sb, String klass, String text) {
+ sb.append("<br />\n<h2 class='").append(klass).append("'>").append(text).append("</h2>\n");
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/accumulo/blob/f00c8fd5/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/DefaultServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/DefaultServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/DefaultServlet.java
new file mode 100644
index 0000000..9280c1e
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/DefaultServlet.java
@@ -0,0 +1,374 @@
+/*
+ * 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.monitor.servlets;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.io.InputStream;
+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.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.file.FileUtil;
+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.NumUtil;
+import org.apache.accumulo.core.util.Pair;
+import org.apache.accumulo.monitor.Monitor;
+import org.apache.accumulo.monitor.ZooKeeperStatus;
+import org.apache.accumulo.monitor.ZooKeeperStatus.ZooKeeperState;
+import org.apache.accumulo.monitor.util.celltypes.NumberType;
+import org.apache.accumulo.server.conf.ServerConfiguration;
+import org.apache.accumulo.server.trace.TraceFileSystem;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.ContentSummary;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+
+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 {
+ try {
+ 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);
+ ServletOutputStream out = resp.getOutputStream();
+ try {
+ if (data != null) {
+ byte[] buffer = new byte[1024];
+ int n;
+ while ((n = data.read(buffer)) > 0)
+ out.write(buffer, 0, n);
+ } else {
+ out.write(("could not get resource " + path + "").getBytes());
+ }
+ } finally {
+ if (data != null)
+ data.close();
+ }
+ } catch (Throwable t) {
+ log.error(t, t);
+ throw new IOException(t);
+ }
+ }
+
+ 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() {
+ InputStream data = null;
+ try {
+ File file = new File(aHome + path);
+ 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;
+ } finally {
+ if (data != null) {
+ try {
+ data.close();
+ } catch (IOException ex) {
+ log.error(ex, ex);
+ }
+ }
+ }
+ }
+ }, 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;
+
+ private static void plotData(StringBuilder sb, String title, @SuppressWarnings("rawtypes") List data, boolean points) {
+ plotData(sb, title, points, new ArrayList<String>(), data);
+ }
+
+ @SuppressWarnings("rawtypes")
+ private static void plotData(StringBuilder sb, String title, boolean points, List<String> labels, List... series) {
+ 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");
+
+ for (int i = 0; i < series.length; i++) {
+
+ @SuppressWarnings("unchecked")
+ List<Pair<Long,? extends Number>> data = series[i];
+ sb.append(" var d" + i + " = [");
+
+ String sep = "";
+ for (Pair<Long,? extends Number> point : 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("[" + utc2local(point.getFirst()) + "," + y + "]");
+ }
+ sb.append(" ];\n");
+ }
+
+ String opts = "lines: { show: true }";
+ if (points)
+ opts = "points: { show: true, radius: 1 }";
+
+ sb.append(" $.plot($(\"#" + id + "\"),");
+ String sep = "";
+
+ String colors[] = new String[] {"red", "blue", "green", "black"};
+
+ sb.append("[");
+ for (int i = 0; i < series.length; i++) {
+ sb.append(sep);
+ sep = ",";
+ sb.append("{ ");
+ if (labels.size() > 0) {
+ sb.append("label: \"" + labels.get(i) + "\", ");
+ }
+ sb.append("data: d" + i + ", " + opts + ", color:\"" + colors[i] + "\" }");
+ }
+ sb.append("], ");
+ sb.append("{yaxis:{}, xaxis:{mode:\"time\",minTickSize: [1, \"minute\"],timeformat: \"%H:%M<br />" + getShortTZName() + "\", ticks:3}});");
+ sb.append(" });\n");
+ sb.append("</script>\n");
+ }
+
+ /**
+ * Shows the current time zone (based on the current time) short name
+ */
+ private static String getShortTZName() {
+ TimeZone tz = TimeZone.getDefault();
+ return tz.getDisplayName(tz.inDaylightTime(new Date()), TimeZone.SHORT);
+ }
+
+ /**
+ * Converts a unix timestamp in UTC to one that is relative to the local timezone
+ */
+ private static Long utc2local(Long utcMillis) {
+ Calendar currentCalendar = Calendar.getInstance(); // default timezone
+ currentCalendar.setTimeInMillis(utcMillis + currentCalendar.getTimeZone().getOffset(utcMillis));
+ return currentCalendar.getTime().getTime();
+ }
+
+ @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");
+ 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)", false, Arrays.asList("Read", "Returned"), Monitor.getScanRateOverTime(), Monitor.getQueryRateOverTime());
+ 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, "Seeks", 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("<tr><td>\n");
+ plotData(sb, "Index Cache Hit Rate", Monitor.getIndexCacheHitRateOverTime(), true);
+ sb.append("</td><td>\n");
+ plotData(sb, "Data Cache Hit Rate", Monitor.getDataCacheHitRateOverTime(), true);
+ sb.append("</td></tr>\n");
+
+ sb.append("</table>\n");
+ }
+
+ private void doAccumuloTable(StringBuilder sb) throws IOException {
+ // Accumulo
+ Configuration conf = CachedConfiguration.getInstance();
+ FileSystem fs = TraceFileSystem.wrap(FileUtil.getFileSystem(conf, ServerConfiguration.getSiteConfiguration()));
+ 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 {
+ Path path = new Path(Monitor.getSystemConfiguration().get(Property.INSTANCE_DFS_DIR));
+ log.debug("Reading the content summary for " + path);
+ try {
+ ContentSummary acu = fs.getContentSummary(path);
+ ContentSummary rootSummary = fs.getContentSummary(new Path("/"));
+ consumed = String.format("%.2f%%", acu.getSpaceConsumed() * 100. / rootSummary.getSpaceConsumed());
+ diskUsed = bytes(acu.getSpaceConsumed());
+ } catch (Exception ex) {
+ log.trace("Unable to get disk usage information from hdfs", ex);
+ }
+
+ boolean highlight = false;
+ tableRow(sb, (highlight = !highlight), "Disk Used", diskUsed);
+ if (fs.getUsed() != 0)
+ tableRow(sb, (highlight = !highlight), "% of Used DFS", consumed);
+ tableRow(sb, (highlight = !highlight), "<a href='/tables'>Tables</a>", NumberType.commas(Monitor.getTotalTables()));
+ tableRow(sb, (highlight = !highlight), "<a href='/tservers'>Tablet Servers</a>", NumberType.commas(info.tServerInfo.size(), 1, Long.MAX_VALUE));
+ tableRow(sb, (highlight = !highlight), "<a href='/tservers'>Dead Tablet Servers</a>", NumberType.commas(info.deadTabletServers.size(), 0, 0));
+ tableRow(sb, (highlight = !highlight), "Tablets", NumberType.commas(Monitor.getTotalTabletCount(), 1, Long.MAX_VALUE));
+ 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()));
+ } catch (Exception e) {
+ log.debug(e, e);
+ }
+ }
+ 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 String bytes(long big) {
+ return NumUtil.bigNumberForSize(big);
+ }
+
+ 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");
+ }
+}
http://git-wip-us.apache.org/repos/asf/accumulo/blob/f00c8fd5/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/GcStatusServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/GcStatusServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/GcStatusServlet.java
new file mode 100644
index 0000000..0f0db9e
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/GcStatusServlet.java
@@ -0,0 +1,72 @@
+/*
+ * 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.monitor.servlets;
+
+import java.text.SimpleDateFormat;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.gc.thrift.GCStatus;
+import org.apache.accumulo.monitor.Monitor;
+import org.apache.accumulo.monitor.util.Table;
+import org.apache.accumulo.monitor.util.celltypes.DateTimeType;
+import org.apache.accumulo.monitor.util.celltypes.DurationType;
+import org.apache.accumulo.monitor.util.celltypes.NumberType;
+
+public class GcStatusServlet extends BasicServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected String getTitle(HttpServletRequest req) {
+ return "Garbage Collector Status";
+ }
+
+ @Override
+ protected void pageBody(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) {
+ GCStatus status = Monitor.getGcStatus();
+
+ if (status != null) {
+ Table gcActivity = new Table("gcActivity", "Collection Activity");
+ gcActivity.addSortableColumn("Activity");
+ gcActivity.addSortableColumn("Finished", new DateTimeType(new SimpleDateFormat("MMM dd, yyyy kk:mm")), null);
+ gcActivity.addSortableColumn("Candidates", new NumberType<Long>(), null);
+ gcActivity.addSortableColumn("Deleted", new NumberType<Long>(), null);
+ gcActivity.addSortableColumn("In Use", new NumberType<Long>(), null);
+ gcActivity.addSortableColumn("Errors", new NumberType<Long>(0l, 1l), null);
+ gcActivity.addSortableColumn("Duration", new DurationType(), null);
+
+ if (status.last.finished > 0)
+ gcActivity.addRow("File Collection, Last Cycle", status.last.finished, status.last.candidates, status.last.deleted, status.last.inUse,
+ status.last.errors, status.last.finished - status.last.started);
+ if (status.current.started > 0)
+ gcActivity.addRow("File Collection, Running", status.current.finished, status.current.candidates, status.current.deleted,
+ status.current.inUse, status.current.errors, System.currentTimeMillis() - status.current.started);
+ if (status.lastLog.finished > 0)
+ gcActivity.addRow("WAL Collection, Last Cycle", status.lastLog.finished, status.lastLog.candidates, status.lastLog.deleted,
+ status.lastLog.inUse, status.lastLog.errors, status.lastLog.finished - status.lastLog.started);
+ if (status.currentLog.started > 0)
+ gcActivity.addRow("WAL Collection, Running", status.currentLog.finished, status.currentLog.candidates, status.currentLog.deleted,
+ status.currentLog.inUse, status.currentLog.errors, System.currentTimeMillis() - status.currentLog.started);
+ gcActivity.generate(req, sb);
+ } else {
+ banner(sb, "error", "Collector is Unavailable");
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/accumulo/blob/f00c8fd5/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/JSONServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/JSONServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/JSONServlet.java
new file mode 100644
index 0000000..8980435
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/JSONServlet.java
@@ -0,0 +1,103 @@
+/*
+ * 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.monitor.servlets;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.master.thrift.DeadServer;
+import org.apache.accumulo.core.master.thrift.TableInfo;
+import org.apache.accumulo.core.master.thrift.TabletServerStatus;
+import org.apache.accumulo.monitor.Monitor;
+import org.apache.accumulo.monitor.util.celltypes.TServerLinkType;
+
+import com.google.gson.Gson;
+
+public class JSONServlet extends BasicServlet {
+ private static final long serialVersionUID = 1L;
+
+ private Gson gson = new Gson();
+
+ @Override
+ protected String getTitle(HttpServletRequest req) {
+ return "JSON Report";
+ }
+
+ @Override
+ protected void pageStart(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) {
+ resp.setContentType("application/json");
+ }
+
+ private static Map<String,Object> addServer(String ip, String hostname, double osload, double ingest, double query, double ingestMB, double queryMB,
+ int scans, double scansessions, long holdtime) {
+ Map<String,Object> map = new HashMap<String,Object>();
+ map.put("ip", ip);
+ map.put("hostname", hostname);
+ map.put("osload", osload);
+ map.put("ingest", ingest);
+ map.put("query", query);
+ map.put("ingestMB", ingestMB);
+ map.put("queryMB", queryMB);
+ map.put("scans", scans);
+ map.put("scanssessions", scansessions);
+ map.put("holdtime", holdtime);
+ return map;
+ }
+
+ @Override
+ protected void pageBody(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) {
+ if (Monitor.getMmi() == null || Monitor.getMmi().tableMap == null) {
+ return;
+ }
+
+ Map<String,Object> results = new HashMap<String,Object>();
+ List<Map<String,Object>> servers = new ArrayList<Map<String,Object>>();
+
+ for (TabletServerStatus status : Monitor.getMmi().tServerInfo) {
+ TableInfo summary = Monitor.summarizeTableStats(status);
+ servers.add(addServer(status.name, TServerLinkType.displayName(status.name), status.osLoad, summary.ingestRate, summary.queryRate,
+ summary.ingestByteRate / 1000000.0, summary.queryByteRate / 1000000.0, summary.scans.running + summary.scans.queued, Monitor.getLookupRate(),
+ status.holdTime));
+ }
+
+ for (Entry<String,Byte> entry : Monitor.getMmi().badTServers.entrySet()) {
+ Map<String,Object> badServer = new HashMap<String,Object>();
+ badServer.put("ip", entry.getKey());
+ badServer.put("bad", true);
+ servers.add(badServer);
+ }
+
+ for (DeadServer dead : Monitor.getMmi().deadTabletServers) {
+ Map<String,Object> deadServer = new HashMap<String,Object>();
+ deadServer.put("ip", dead.server);
+ deadServer.put("dead", true);
+ servers.add(deadServer);
+ }
+
+ results.put("servers", servers);
+ sb.append(gson.toJson(results));
+ }
+
+ @Override
+ protected void pageEnd(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) {}
+}
http://git-wip-us.apache.org/repos/asf/accumulo/blob/f00c8fd5/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/LogServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/LogServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/LogServlet.java
new file mode 100644
index 0000000..513c06a
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/LogServlet.java
@@ -0,0 +1,106 @@
+/*
+ * 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.monitor.servlets;
+
+import java.text.SimpleDateFormat;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.monitor.DedupedLogEvent;
+import org.apache.accumulo.monitor.LogService;
+import org.apache.accumulo.monitor.util.Table;
+import org.apache.accumulo.monitor.util.celltypes.DateTimeType;
+import org.apache.accumulo.monitor.util.celltypes.StringType;
+import org.apache.log4j.Level;
+import org.apache.log4j.spi.LoggingEvent;
+
+public class LogServlet extends BasicServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected String getTitle(HttpServletRequest req) {
+ return "Recent Logs";
+ }
+
+ @Override
+ protected void pageBody(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) {
+ boolean clear = true;
+ SimpleDateFormat fmt = new SimpleDateFormat("dd HH:mm:ss,SSSS");
+ Table logTable = new Table("logTable", "Recent Logs");
+ logTable.addSortableColumn("Time", new DateTimeType(fmt), null);
+ logTable.addSortableColumn("Application");
+ logTable.addSortableColumn("Count");
+ logTable.addSortableColumn("Level", new LogLevelType(), null);
+ logTable.addSortableColumn("Message");
+ for (DedupedLogEvent dev : LogService.getInstance().getEvents()) {
+ clear = false;
+ LoggingEvent ev = dev.getEvent();
+ Object application = ev.getMDC("application");
+ if (application == null)
+ application = "";
+ String msg = ev.getMessage().toString();
+ StringBuilder text = new StringBuilder();
+ for (int i = 0; i < msg.length(); i++) {
+ char c = msg.charAt(i);
+ switch (Character.getType(c)) {
+ case Character.UNASSIGNED:
+ case Character.LINE_SEPARATOR:
+ case Character.NON_SPACING_MARK:
+ case Character.PRIVATE_USE:
+ c = '?';
+ default:
+ text.append(c);
+ }
+
+ }
+ msg = text.toString();
+ if (ev.getThrowableStrRep() != null)
+ for (String line : ev.getThrowableStrRep())
+ msg += "\n\t" + line;
+ msg = sanitize(msg.trim());
+ msg = "<pre class='logevent'>" + msg + "</pre>";
+ logTable.addRow(ev.getTimeStamp(), application, dev.getCount(), ev.getLevel(), msg);
+ }
+ if (!clear)
+ logTable.setSubCaption("<a href='/op?action=clearLog&redir=" + currentPage(req) + "'>Clear All Events</a>");
+ logTable.generate(req, sb);
+ if (!clear)
+ sb.append("<div class='center'><a href='/op?action=clearLog&redir=").append(currentPage(req)).append("'>Clear All Events</a></div>\n");
+ }
+
+ private static class LogLevelType extends StringType<Level> {
+ @Override
+ public String alignment() {
+ return "center";
+ }
+
+ @Override
+ public String format(Object obj) {
+ if (obj == null)
+ return "-";
+ Level l = (Level) obj;
+ if (l.equals(Level.ERROR) || l.equals(Level.FATAL))
+ return "<div class='error'>" + l.toString() + "</div>";
+ else if (l.equals(Level.WARN))
+ return "<div class='warning'>" + l.toString() + "</div>";
+ else
+ return l.toString();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/accumulo/blob/f00c8fd5/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/MasterServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/MasterServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/MasterServlet.java
new file mode 100644
index 0000000..aad670d
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/MasterServlet.java
@@ -0,0 +1,199 @@
+/*
+ * 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.monitor.servlets;
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.client.impl.Tables;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.master.thrift.DeadServer;
+import org.apache.accumulo.core.master.thrift.MasterMonitorInfo;
+import org.apache.accumulo.core.master.thrift.MasterState;
+import org.apache.accumulo.core.master.thrift.RecoveryStatus;
+import org.apache.accumulo.core.master.thrift.TabletServerStatus;
+import org.apache.accumulo.core.util.AddressUtil;
+import org.apache.accumulo.core.util.StringUtil;
+import org.apache.accumulo.monitor.DedupedLogEvent;
+import org.apache.accumulo.monitor.LogService;
+import org.apache.accumulo.monitor.Monitor;
+import org.apache.accumulo.monitor.util.Table;
+import org.apache.accumulo.monitor.util.TableRow;
+import org.apache.accumulo.monitor.util.celltypes.DurationType;
+import org.apache.accumulo.monitor.util.celltypes.NumberType;
+import org.apache.accumulo.monitor.util.celltypes.ProgressChartType;
+import org.apache.accumulo.monitor.util.celltypes.StringType;
+import org.apache.accumulo.server.client.HdfsZooInstance;
+import org.apache.log4j.Level;
+
+public class MasterServlet extends BasicServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected String getTitle(HttpServletRequest req) {
+ List<String> masters = Monitor.getInstance().getMasterLocations();
+ return "Master Server" + (masters.size() == 0 ? "" : ":" + AddressUtil.parseAddress(masters.get(0)).getHostName());
+ }
+
+ @Override
+ protected void pageBody(HttpServletRequest req, HttpServletResponse response, StringBuilder sb) throws IOException {
+ Map<String,String> tidToNameMap = Tables.getIdToNameMap(HdfsZooInstance.getInstance());
+
+ doLogEventBanner(sb);
+ TablesServlet.doProblemsBanner(sb);
+ doMasterStatus(req, sb);
+ doRecoveryList(req, sb);
+ TablesServlet.doTableList(req, sb, tidToNameMap);
+ }
+
+ private void doLogEventBanner(StringBuilder sb) {
+ if (LogService.getInstance().getEvents().size() > 0) {
+ int error = 0, warning = 0, total = 0;
+ for (DedupedLogEvent dev : LogService.getInstance().getEvents()) {
+ switch (dev.getEvent().getLevel().toInt()) {
+ case Level.FATAL_INT:
+ case Level.ERROR_INT:
+ error++;
+ break;
+ case Level.WARN_INT:
+ warning++;
+ break;
+ }
+ total++;
+ }
+ banner(sb, error > 0 ? "error" : "warning", String.format("<a href='/log'>Log Events: %d Error%s, %d Warning%s, %d Total</a>", error, error == 1 ? ""
+ : "s", warning, warning == 1 ? "" : "s", total));
+ }
+ }
+
+ private void doMasterStatus(HttpServletRequest req, StringBuilder sb) throws IOException {
+
+ if (Monitor.getMmi() != null) {
+ String gcStatus = "Waiting";
+ if (Monitor.getGcStatus() != null) {
+ long start = 0;
+ String label = "";
+ if (Monitor.getGcStatus().current.started != 0 || Monitor.getGcStatus().currentLog.started != 0) {
+ start = Math.max(Monitor.getGcStatus().current.started, Monitor.getGcStatus().currentLog.started);
+ label = "Running";
+ } else if (Monitor.getGcStatus().lastLog.finished != 0) {
+ start = Monitor.getGcStatus().lastLog.finished;
+ }
+ if (start != 0) {
+ long diff = System.currentTimeMillis() - start;
+ gcStatus = label + " " + DateFormat.getInstance().format(new Date(start));
+ gcStatus = gcStatus.replace(" ", " ");
+ long normalDelay = Monitor.getSystemConfiguration().getTimeInMillis(Property.GC_CYCLE_DELAY);
+ if (diff > normalDelay * 2)
+ gcStatus = "<span class='warning'>" + gcStatus + "</span>";
+ }
+ } else {
+ gcStatus = "<span class='error'>Down</span>";
+ }
+ if (Monitor.getMmi().state != MasterState.NORMAL) {
+ sb.append("<span class='warning'>Master State: " + Monitor.getMmi().state.name() + " Goal: " + Monitor.getMmi().goalState.name() + "</span>\n");
+ }
+ if (Monitor.getMmi().serversShuttingDown != null && Monitor.getMmi().serversShuttingDown.size() > 0 && Monitor.getMmi().state == MasterState.NORMAL) {
+ sb.append("<span class='warning'>Servers being stopped: " + StringUtil.join(Monitor.getMmi().serversShuttingDown, ", ") + "</span>\n");
+ }
+
+ int guessHighLoad = ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors();
+ List<String> slaves = new ArrayList<String>();
+ for (TabletServerStatus up : Monitor.getMmi().tServerInfo) {
+ slaves.add(up.name);
+ }
+ for (DeadServer down : Monitor.getMmi().deadTabletServers) {
+ slaves.add(down.server);
+ }
+ List<String> masters = Monitor.getInstance().getMasterLocations();
+
+ Table masterStatus = new Table("masterStatus", "Master Status");
+ masterStatus.addSortableColumn("Master", new StringType<String>(), "The hostname of the master server");
+ masterStatus.addSortableColumn("# Online<br />Tablet Servers", new PreciseNumberType((int) (slaves.size() * 0.8 + 1.0), slaves.size(),
+ (int) (slaves.size() * 0.6 + 1.0), slaves.size()), "Number of tablet servers currently available");
+ masterStatus.addSortableColumn("# Total<br />Tablet Servers", new PreciseNumberType(), "The total number of tablet servers configured");
+ masterStatus.addSortableColumn("Last GC", null, "The last time files were cleaned-up from HDFS.");
+ masterStatus.addSortableColumn("# Tablets", new NumberType<Integer>(0, Integer.MAX_VALUE, 2, Integer.MAX_VALUE), null);
+ masterStatus.addSortableColumn("# Unassigned<br />Tablets", new NumberType<Integer>(0, 0), null);
+ masterStatus.addSortableColumn("Entries", new NumberType<Long>(), "The total number of key/value pairs in Accumulo");
+ masterStatus.addSortableColumn("Ingest", new NumberType<Long>(), "The number of Key/Value pairs inserted, per second. "
+ + " Note that deleted records are \"inserted\" and will make the ingest " + "rate increase in the near-term.");
+ masterStatus.addSortableColumn("Entries<br />Read", new NumberType<Long>(),
+ "The total number of Key/Value pairs read on the server side. Not all may be returned because of filtering.");
+ masterStatus.addSortableColumn("Entries<br />Returned", new NumberType<Long>(), "The total number of Key/Value pairs returned as a result of scans.");
+ masterStatus.addSortableColumn("Hold Time", new DurationType(0l, 0l), "The maximum amount of time that ingest has been held "
+ + "across all servers due to a lack of memory to store the records");
+ masterStatus.addSortableColumn("OS Load", new NumberType<Double>(0., guessHighLoad * 1., 0., guessHighLoad * 3.),
+ "The one-minute load average on the computer that runs the monitor web server.");
+ TableRow row = masterStatus.prepareRow();
+ row.add(masters.size() == 0 ? "<div class='error'>Down</div>" : AddressUtil.parseAddress(masters.get(0)).getHostName());
+ row.add(Monitor.getMmi().tServerInfo.size());
+ row.add(slaves.size());
+ row.add("<a href='/gc'>" + gcStatus + "</a>");
+ row.add(Monitor.getTotalTabletCount());
+ row.add(Monitor.getMmi().unassignedTablets);
+ row.add(Monitor.getTotalEntries());
+ row.add(Math.round(Monitor.getTotalIngestRate()));
+ row.add(Math.round(Monitor.getTotalScanRate()));
+ row.add(Math.round(Monitor.getTotalQueryRate()));
+ row.add(Monitor.getTotalHoldTime());
+ row.add(ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage());
+ masterStatus.addRow(row);
+ masterStatus.generate(req, sb);
+
+ } else
+ banner(sb, "error", "Master Server Not Running");
+ }
+
+ private void doRecoveryList(HttpServletRequest req, StringBuilder sb) {
+ MasterMonitorInfo mmi = Monitor.getMmi();
+ if (mmi != null) {
+ Table recoveryTable = new Table("logRecovery", "Log Recovery");
+ recoveryTable.setSubCaption("Some tablets were unloaded in an unsafe manner. Write-ahead logs are being recovered.");
+ recoveryTable.addSortableColumn("Server");
+ recoveryTable.addSortableColumn("Log");
+ recoveryTable.addSortableColumn("Time", new DurationType(), null);
+ recoveryTable.addSortableColumn("Copy/Sort", new ProgressChartType(), null);
+ int rows = 0;
+ for (TabletServerStatus server : mmi.tServerInfo) {
+ if (server.logSorts != null) {
+ for (RecoveryStatus recovery : server.logSorts) {
+ TableRow row = recoveryTable.prepareRow();
+ row.add(AddressUtil.parseAddress(server.name).getHostName());
+ row.add(recovery.name);
+ row.add((long) recovery.runtime);
+ row.add(recovery.progress);
+ recoveryTable.addRow(row);
+ rows++;
+ }
+ }
+ }
+ if (rows > 0)
+ recoveryTable.generate(req, sb);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/accumulo/blob/f00c8fd5/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/OperationServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/OperationServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/OperationServlet.java
new file mode 100644
index 0000000..17c6a80
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/OperationServlet.java
@@ -0,0 +1,172 @@
+/*
+ * 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.monitor.servlets;
+
+import java.io.IOException;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.Constants;
+import org.apache.accumulo.core.client.Instance;
+import org.apache.accumulo.core.zookeeper.ZooUtil;
+import org.apache.accumulo.monitor.LogService;
+import org.apache.accumulo.server.client.HdfsZooInstance;
+import org.apache.accumulo.server.master.state.DeadServerList;
+import org.apache.accumulo.server.problems.ProblemReports;
+import org.apache.accumulo.server.problems.ProblemType;
+import org.apache.log4j.Logger;
+
+public class OperationServlet extends BasicServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected String getTitle(HttpServletRequest req) {
+ return "Operations";
+ }
+
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ String redir = null;
+ try {
+ String operation = req.getParameter("action");
+ redir = req.getParameter("redir");
+ if (redir != null)
+ redir = decode(redir);
+
+ if (operation != null) {
+ for (Class<?> subclass : OperationServlet.class.getClasses()) {
+ Object t;
+ try {
+ t = subclass.newInstance();
+ } catch (Exception e) {
+ continue;
+ }
+ if (t instanceof WebOperation) {
+ WebOperation op = (WebOperation) t;
+ if (op.getClass().getSimpleName().equalsIgnoreCase(operation + "Operation")) {
+ op.execute(req, resp, log);
+ break;
+ }
+ }
+ }
+ }
+ } catch (Throwable t) {
+ log.error(t, t);
+ } finally {
+ try {
+ if (redir != null)
+ resp.sendRedirect(redir);
+ else
+ resp.sendRedirect("/");
+ resp.flushBuffer();
+ } catch (Throwable t) {
+ log.error(t, t);
+ }
+ }
+ }
+
+ private static interface WebOperation {
+ public void execute(HttpServletRequest req, HttpServletResponse resp, Logger log) throws Exception;
+ }
+
+ public static class RefreshOperation implements WebOperation {
+ @Override
+ public void execute(HttpServletRequest req, HttpServletResponse resp, Logger log) {
+ String value = req.getParameter("value");
+ resp.addCookie(new Cookie("page.refresh.rate", value == null ? "5" : value));
+ }
+ }
+
+ public static class ClearLogOperation implements WebOperation {
+ @Override
+ public void execute(HttpServletRequest req, HttpServletResponse resp, Logger log) {
+ LogService.getInstance().clear();
+ }
+ }
+
+ public static class ClearTableProblemsOperation implements WebOperation {
+ @Override
+ public void execute(HttpServletRequest req, HttpServletResponse resp, Logger log) {
+ String table = req.getParameter("table");
+ try {
+ ProblemReports.getInstance().deleteProblemReports(table);
+ } catch (Exception e) {
+ log.error("Failed to delete problem reports for table " + table, e);
+ }
+ }
+ }
+
+ public static class ClearProblemOperation implements WebOperation {
+ @Override
+ public void execute(HttpServletRequest req, HttpServletResponse resp, Logger log) {
+ String table = req.getParameter("table");
+ String resource = req.getParameter("resource");
+ if (resource != null)
+ resource = decode(resource);
+ String ptype = req.getParameter("ptype");
+ if (ptype != null)
+ ptype = decode(ptype);
+ try {
+ ProblemReports.getInstance().deleteProblemReport(table, ProblemType.valueOf(ptype), resource);
+ } catch (Exception e) {
+ log.error("Failed to delete problem reports for table " + table, e);
+ }
+ }
+ }
+
+ public static class SortTableOperation implements WebOperation {
+ @Override
+ public void execute(HttpServletRequest req, HttpServletResponse resp, Logger log) throws IOException {
+ String page = req.getParameter("page");
+ String table = req.getParameter("table");
+ String asc = req.getParameter("asc");
+ String col = req.getParameter("col");
+ if (table == null || page == null || (asc == null && col == null))
+ return;
+ if (asc == null)
+ resp.addCookie(new Cookie("tableSort." + page + "." + table + "." + "sortCol", col));
+ else
+ resp.addCookie(new Cookie("tableSort." + page + "." + table + "." + "sortAsc", asc));
+ }
+ }
+
+ public static class ToggleLegendOperation implements WebOperation {
+ @Override
+ public void execute(HttpServletRequest req, HttpServletResponse resp, Logger log) throws Exception {
+ String page = req.getParameter("page");
+ String table = req.getParameter("table");
+ String show = req.getParameter("show");
+ if (table == null || page == null || show == null)
+ return;
+ resp.addCookie(new Cookie("tableLegend." + page + "." + table + "." + "show", show));
+ }
+ }
+
+ public static class ClearDeadServerOperation implements WebOperation {
+ @Override
+ public void execute(HttpServletRequest req, HttpServletResponse resp, Logger log) {
+ String server = decode(req.getParameter("server"));
+ Instance inst = HdfsZooInstance.getInstance();
+ // a dead server should have a uniq address: a logger or tserver
+ DeadServerList obit = new DeadServerList(ZooUtil.getRoot(inst) + Constants.ZDEADTSERVERS);
+ obit.delete(server);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/accumulo/blob/f00c8fd5/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/PreciseNumberType.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/PreciseNumberType.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/PreciseNumberType.java
new file mode 100644
index 0000000..66f97e1
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/PreciseNumberType.java
@@ -0,0 +1,32 @@
+/*
+ * 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.monitor.servlets;
+
+import org.apache.accumulo.monitor.util.celltypes.NumberType;
+
+public class PreciseNumberType extends NumberType<Integer> {
+
+ public PreciseNumberType(int warnMin, int warnMax, int errMin, int errMax) {
+ super(warnMin, warnMax, errMin, errMax);
+ }
+
+ public PreciseNumberType() {}
+
+ public static String bigNumber(long big, String[] SUFFIXES, long base) {
+ return String.format("%,d", big);
+ }
+}
http://git-wip-us.apache.org/repos/asf/accumulo/blob/f00c8fd5/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ProblemServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ProblemServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ProblemServlet.java
new file mode 100644
index 0000000..d20328e
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ProblemServlet.java
@@ -0,0 +1,193 @@
+/*
+ * 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.monitor.servlets;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.client.impl.Tables;
+import org.apache.accumulo.monitor.Monitor;
+import org.apache.accumulo.monitor.util.Table;
+import org.apache.accumulo.monitor.util.TableRow;
+import org.apache.accumulo.monitor.util.celltypes.CellType;
+import org.apache.accumulo.monitor.util.celltypes.DateTimeType;
+import org.apache.accumulo.monitor.util.celltypes.NumberType;
+import org.apache.accumulo.monitor.util.celltypes.StringType;
+import org.apache.accumulo.server.client.HdfsZooInstance;
+import org.apache.accumulo.server.problems.ProblemReport;
+import org.apache.accumulo.server.problems.ProblemReports;
+import org.apache.accumulo.server.problems.ProblemType;
+
+public class ProblemServlet extends BasicServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected String getTitle(HttpServletRequest req) {
+ return "Per-Table Problem Report";
+ }
+
+ @Override
+ protected void pageBody(final HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) {
+ Map<String,String> tidToNameMap = Tables.getIdToNameMap(HdfsZooInstance.getInstance());
+ doProblemSummary(req, sb, tidToNameMap);
+ doProblemDetails(req, sb, req.getParameter("table"), tidToNameMap);
+ }
+
+ private static void doProblemSummary(final HttpServletRequest req, StringBuilder sb, final Map<String,String> tidToNameMap) {
+ if (Monitor.getProblemSummary().isEmpty() && Monitor.getProblemException() == null)
+ return;
+
+ Table problemSummary = new Table("problemSummary", "Problem Summary", "error");
+ problemSummary.addSortableColumn("Table", new TableProblemLinkType(tidToNameMap), null);
+ for (ProblemType type : ProblemType.values())
+ problemSummary.addSortableColumn(type.name(), new NumberType<Integer>(), null);
+ problemSummary.addUnsortableColumn("Operations", new ClearTableProblemsLinkType(req, tidToNameMap), null);
+
+ if (Monitor.getProblemException() != null) {
+ StringBuilder cell = new StringBuilder();
+ cell.append("<b>Failed to obtain problem reports</b> : " + Monitor.getProblemException().getMessage());
+ Throwable cause = Monitor.getProblemException().getCause();
+ while (cause != null) {
+ if (cause.getMessage() != null)
+ cell.append("<br />\n caused by : " + cause.getMessage());
+ cause = cause.getCause();
+ }
+ problemSummary.setSubCaption(cell.toString());
+ } else {
+ for (Entry<String,Map<ProblemType,Integer>> entry : Monitor.getProblemSummary().entrySet()) {
+ TableRow row = problemSummary.prepareRow();
+ row.add(entry.getKey());
+ for (ProblemType pt : ProblemType.values()) {
+ Integer pcount = entry.getValue().get(pt);
+ row.add(pcount == null ? 0 : pcount);
+ }
+ row.add(entry.getKey());
+ problemSummary.addRow(row);
+ }
+ }
+ problemSummary.generate(req, sb);
+ }
+
+ private static void doProblemDetails(final HttpServletRequest req, StringBuilder sb, String tableId, Map<String,String> tidToNameMap) {
+
+ if (Monitor.getProblemException() != null)
+ return;
+
+ ArrayList<ProblemReport> problemReports = new ArrayList<ProblemReport>();
+ Iterator<ProblemReport> iter = tableId == null ? ProblemReports.getInstance().iterator() : ProblemReports.getInstance().iterator(tableId);
+ while (iter.hasNext())
+ problemReports.add(iter.next());
+ final SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss zzz");
+ Table problemTable = new Table("problemDetails", "Problem Details", "error");
+ problemTable.setSubCaption("Problems identified with tables.");
+ problemTable.addSortableColumn("Table", new TableProblemLinkType(tidToNameMap), null);
+ problemTable.addSortableColumn("Problem Type");
+ problemTable.addSortableColumn("Server");
+ problemTable.addSortableColumn("Time", new DateTimeType(sdf), null);
+ problemTable.addSortableColumn("Resource");
+ problemTable.addSortableColumn("Exception");
+ problemTable.addUnsortableColumn("Operations", new ClearProblemLinkType(req), null);
+ for (ProblemReport pr : problemReports) {
+
+ TableRow row = problemTable.prepareRow();
+ row.add(pr.getTableName());
+ row.add(pr.getProblemType().name());
+ row.add(pr.getServer());
+ row.add(pr.getTime());
+ row.add(pr.getResource());
+ row.add(pr.getException());
+ row.add(pr);
+ problemTable.addRow(row);
+ }
+ problemTable.generate(req, sb);
+ }
+
+ private static class TableProblemLinkType extends StringType<String> {
+ private Map<String,String> tidToNameMap;
+
+ public TableProblemLinkType(Map<String,String> tidToNameMap) {
+ this.tidToNameMap = tidToNameMap;
+ }
+
+ @Override
+ public String format(Object obj) {
+ if (obj == null)
+ return "-";
+ String table = String.valueOf(obj);
+ return String.format("<a href='/problems?table=%s'>%s</a>", table, Tables.getPrintableTableNameFromId(tidToNameMap, table));
+ }
+ }
+
+ private static class ClearTableProblemsLinkType extends StringType<String> {
+ private HttpServletRequest req;
+ private Map<String,String> tidToNameMap;
+
+ public ClearTableProblemsLinkType(HttpServletRequest req, Map<String,String> tidToNameMap) {
+ this.req = req;
+ this.tidToNameMap = tidToNameMap;
+ }
+
+ @Override
+ public String alignment() {
+ return "right";
+ }
+
+ @Override
+ public String format(Object obj) {
+ if (obj == null)
+ return "-";
+ String table = String.valueOf(obj);
+ return String.format("<a href='/op?table=%s&action=clearTableProblems&redir=%s'>clear ALL %s problems</a>", table, currentPage(req),
+ Tables.getPrintableTableNameFromId(tidToNameMap, table));
+ }
+ }
+
+ private static class ClearProblemLinkType extends CellType<ProblemReport> {
+ private HttpServletRequest req;
+
+ public ClearProblemLinkType(HttpServletRequest req) {
+ this.req = req;
+ }
+
+ @Override
+ public String alignment() {
+ return "right";
+ }
+
+ @Override
+ public String format(Object obj) {
+ if (obj == null)
+ return "-";
+ ProblemReport p = (ProblemReport) obj;
+ return String.format("<a href='/op?table=%s&action=clearProblem&redir=%s&resource=%s&ptype=%s'>clear this problem</a>", p.getTableName(),
+ currentPage(req), encode(p.getResource()), encode(p.getProblemType().name()));
+ }
+
+ @Override
+ public int compare(ProblemReport o1, ProblemReport o2) {
+ return 0;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/accumulo/blob/f00c8fd5/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ShellServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ShellServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ShellServlet.java
new file mode 100644
index 0000000..881b294
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ShellServlet.java
@@ -0,0 +1,331 @@
+/*
+ * 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.monitor.servlets;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import jline.console.ConsoleReader;
+
+import org.apache.accumulo.core.util.shell.Shell;
+
+public class ShellServlet extends BasicServlet {
+ private static final long serialVersionUID = 1L;
+ private Map<String,ShellExecutionThread> userShells = new HashMap<String,ShellExecutionThread>();
+ private ExecutorService service = Executors.newCachedThreadPool();
+
+ @Override
+ protected String getTitle(HttpServletRequest req) {
+ return "Shell";
+ }
+
+ @Override
+ protected void pageBody(HttpServletRequest req, HttpServletResponse response, StringBuilder sb) throws IOException {
+ HttpSession session = req.getSession(true);
+ String user = (String) session.getAttribute("user");
+ if (user == null) {
+ // user attribute is null, check to see if username and password are passed as parameters
+ user = req.getParameter("user");
+ String pass = req.getParameter("pass");
+ String mock = req.getParameter("mock");
+ if (user == null || pass == null) {
+ // username or password are null, re-authenticate
+ sb.append(authenticationForm(req.getRequestURI()));
+ return;
+ }
+ try {
+ // get a new shell for this user
+ ShellExecutionThread shellThread = new ShellExecutionThread(user, pass, mock);
+ service.submit(shellThread);
+ userShells.put(session.getId(), shellThread);
+ } catch (IOException e) {
+ // error validating user, reauthenticate
+ sb.append("<div id='loginError'>Invalid user/password</div>" + authenticationForm(req.getRequestURI()));
+ return;
+ }
+ session.setAttribute("user", user);
+ }
+ if (!userShells.containsKey(session.getId())) {
+ // no existing shell for this user, re-authenticate
+ sb.append(authenticationForm(req.getRequestURI()));
+ return;
+ }
+ ShellExecutionThread shellThread = userShells.get(session.getId());
+ shellThread.getOutput();
+ shellThread.printInfo();
+ sb.append("<div id='shell'>\n");
+ sb.append("<pre id='shellResponse'>").append(shellThread.getOutput()).append("</pre>\n");
+ sb.append("<form><span id='shellPrompt'>").append(shellThread.getPrompt());
+ sb.append("</span><input type='text' name='cmd' id='cmd' onkeydown='return handleKeyDown(event.keyCode);'>\n");
+ sb.append("</form>\n</div>\n");
+ sb.append("<script type='text/javascript'>\n");
+ sb.append("var url = '").append(req.getRequestURL().toString()).append("';\n");
+ sb.append("var xmlhttp = new XMLHttpRequest();\n");
+ sb.append("var hsize = 1000;\n");
+ sb.append("var hindex = 0;\n");
+ sb.append("var history = new Array();\n");
+ sb.append("\n");
+ sb.append("function handleKeyDown(keyCode) {\n");
+ sb.append(" if (keyCode==13) {\n");
+ sb.append(" submitCmd(document.getElementById('cmd').value);\n");
+ sb.append(" hindex = history.length;\n");
+ sb.append(" return false;\n");
+ sb.append(" } else if (keyCode==38) {\n");
+ sb.append(" hindex = hindex==0 ? history.length : hindex - 1;\n");
+ sb.append(" if (hindex == history.length)\n");
+ sb.append(" document.getElementById('cmd').value = '';\n");
+ sb.append(" else\n");
+ sb.append(" document.getElementById('cmd').value = history[hindex];\n");
+ sb.append(" return false;\n");
+ sb.append(" } else if (keyCode==40) {\n");
+ sb.append(" hindex = hindex==history.length ? history.length : hindex + 1;\n");
+ sb.append(" if (hindex == history.length)\n");
+ sb.append(" document.getElementById('cmd').value = '';\n");
+ sb.append(" else\n");
+ sb.append(" document.getElementById('cmd').value = history[hindex];\n");
+ sb.append(" return false;\n");
+ sb.append(" }\n");
+ sb.append(" return true;\n");
+ sb.append("}\n");
+ sb.append("\n");
+ sb.append("function submitCmd(cmd) {\n");
+ sb.append(" if (cmd=='history') {\n");
+ sb.append(" document.getElementById('shellResponse').innerHTML += document.getElementById('shellPrompt').innerHTML+cmd+'\\n';\n");
+ sb.append(" document.getElementById('shellResponse').innerHTML += history.join('\\n');\n");
+ sb.append(" return\n");
+ sb.append(" }\n");
+ sb.append(" xmlhttp.open('POST',url+'?cmd='+cmd,false);\n");
+ sb.append(" xmlhttp.send();\n");
+ sb.append(" var text = xmlhttp.responseText;\n");
+ sb.append(" var index = text.lastIndexOf('\\n');\n");
+ sb.append(" if (index >= 0) {\n");
+ sb.append(" if (index > 0 && document.getElementById('cmd').type == 'text') {\n");
+ sb.append(" if (history.length == hsize)\n");
+ sb.append(" history.shift()\n");
+ sb.append(" history.push(cmd)\n");
+ sb.append(" }\n");
+ sb.append(" if (text.charAt(text.length-1)=='*') {\n");
+ sb.append(" document.getElementById('cmd').type = 'password';\n");
+ sb.append(" text = text.substring(0,xmlhttp.responseText.length-2);\n");
+ sb.append(" } else {\n");
+ sb.append(" document.getElementById('cmd').type = 'text';\n");
+ sb.append(" }\n");
+ sb.append(" document.getElementById('shellResponse').innerHTML += text.substring(0,index+1);\n");
+ sb.append(" document.getElementById('shellPrompt').innerHTML = text.substring(index+1);\n");
+ sb.append(" document.getElementById('cmd').value = '';\n");
+ sb.append(" document.getElementById('shell').scrollTop = document.getElementById('cmd').offsetTop;\n");
+ sb.append(" } else {\n");
+ sb.append(" window.location = url;\n");
+ sb.append(" }\n");
+ sb.append("}\n");
+ sb.append("</script>\n");
+ sb.append("<script type='text/javascript'>window.onload = function() { document.getElementById('cmd').select(); }</script>\n");
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ HttpSession session = req.getSession(true);
+ String user = (String) session.getAttribute("user");
+ if (user == null || !userShells.containsKey(session.getId())) {
+ // no existing shell for user, re-authenticate
+ doGet(req, resp);
+ return;
+ }
+ ShellExecutionThread shellThread = userShells.get(session.getId());
+ String cmd = req.getParameter("cmd");
+ if (cmd == null) {
+ // the command is null, just print prompt
+ resp.getWriter().append(shellThread.getPrompt());
+ resp.getWriter().flush();
+ return;
+ }
+ shellThread.addInputString(cmd);
+ shellThread.waitUntilReady();
+ if (shellThread.isDone()) {
+ // the command was exit, invalidate session
+ userShells.remove(session.getId());
+ session.invalidate();
+ return;
+ }
+ // get the shell's output
+ StringBuilder sb = new StringBuilder();
+ sb.append(shellThread.getOutput().replace("<", "<").replace(">", ">"));
+ if (sb.length() == 0 || !(sb.charAt(sb.length() - 1) == '\n'))
+ sb.append("\n");
+ // check if shell is waiting for input
+ if (!shellThread.isWaitingForInput())
+ sb.append(shellThread.getPrompt());
+ // check if shell is waiting for password input
+ if (shellThread.isMasking())
+ sb.append("*");
+ resp.getWriter().append(sb.toString());
+ resp.getWriter().flush();
+ }
+
+ private String authenticationForm(String requestURI) {
+ return "<div id='login'><form method=POST action='" + requestURI + "'>"
+ + "<table><tr><td>Mock: </td><td><input type='checkbox' name='mock' value='mock'></td></tr>"
+ + "<tr><td>Username: </td><td><input type='text' name='user'></td></tr>"
+ + "<tr><td>Password: </td><td><input type='password' name='pass'></td><td><input type='submit' value='Enter'></td></tr></table></form></div>";
+ }
+
+ private static class StringBuilderOutputStream extends OutputStream {
+ StringBuilder sb = new StringBuilder();
+
+ @Override
+ public void write(int b) throws IOException {
+ sb.append((char) (0xff & b));
+ }
+
+ public String get() {
+ return sb.toString();
+ }
+
+ public void clear() {
+ sb.setLength(0);
+ }
+ }
+
+ private static class ShellExecutionThread extends InputStream implements Runnable {
+ private Shell shell;
+ StringBuilderOutputStream output;
+ private String cmd;
+ private int cmdIndex;
+ private boolean done;
+ private boolean readWait;
+
+ private ShellExecutionThread(String username, String password, String mock) throws IOException {
+ this.done = false;
+ this.cmd = null;
+ this.cmdIndex = 0;
+ this.readWait = false;
+ this.output = new StringBuilderOutputStream();
+ ConsoleReader reader = new ConsoleReader(this, output);
+ this.shell = new Shell(reader, new PrintWriter(output));
+ shell.setLogErrorsToConsole();
+ if (mock != null) {
+ if (shell.config("--fake", "-u", username, "-p", password))
+ throw new IOException("mock shell config error");
+ } else if (shell.config("-u", username, "-p", password)) {
+ throw new IOException("shell config error");
+ }
+ }
+
+ @Override
+ public synchronized int read() throws IOException {
+ if (cmd == null) {
+ readWait = true;
+ this.notifyAll();
+ }
+ while (cmd == null) {
+ try {
+ this.wait();
+ } catch (InterruptedException e) {}
+ }
+ readWait = false;
+ int c;
+ if (cmdIndex == cmd.length())
+ c = '\n';
+ else
+ c = cmd.charAt(cmdIndex);
+ cmdIndex++;
+ if (cmdIndex > cmd.length()) {
+ cmd = null;
+ cmdIndex = 0;
+ this.notifyAll();
+ }
+ return c;
+ }
+
+ @Override
+ public synchronized void run() {
+ Thread.currentThread().setName("shell thread");
+ while (!shell.hasExited()) {
+ while (cmd == null) {
+ try {
+ this.wait();
+ } catch (InterruptedException e) {}
+ }
+ String tcmd = cmd;
+ cmd = null;
+ cmdIndex = 0;
+ try {
+ shell.execCommand(tcmd, false, true);
+ } catch (IOException e) {}
+ this.notifyAll();
+ }
+ done = true;
+ this.notifyAll();
+ }
+
+ public synchronized void addInputString(String s) {
+ if (done)
+ throw new IllegalStateException("adding string to exited shell");
+ if (cmd == null) {
+ cmd = s;
+ } else {
+ throw new IllegalStateException("adding string to shell not waiting for input");
+ }
+ this.notifyAll();
+ }
+
+ public synchronized void waitUntilReady() {
+ while (cmd != null) {
+ try {
+ this.wait();
+ } catch (InterruptedException e) {}
+ }
+ }
+
+ public synchronized String getOutput() {
+ String s = output.get();
+ output.clear();
+ return s;
+ }
+
+ public String getPrompt() {
+ return shell.getDefaultPrompt();
+ }
+
+ public void printInfo() throws IOException {
+ shell.printInfo();
+ }
+
+ public boolean isMasking() {
+ return shell.isMasking();
+ }
+
+ public synchronized boolean isWaitingForInput() {
+ return readWait;
+ }
+
+ public boolean isDone() {
+ return done;
+ }
+ }
+}