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/11/01 01:56:11 UTC

[32/54] [partial] ACCUMULO-658, ACCUMULO-656 Split server into separate modules

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/TServersServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/TServersServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/TServersServlet.java
new file mode 100644
index 0000000..681e696
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/TServersServlet.java
@@ -0,0 +1,369 @@
+/*
+ * 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.lang.management.ManagementFactory;
+import java.security.MessageDigest;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map.Entry;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.data.KeyExtent;
+import org.apache.accumulo.core.master.thrift.DeadServer;
+import org.apache.accumulo.core.master.thrift.MasterMonitorInfo;
+import org.apache.accumulo.core.master.thrift.TableInfo;
+import org.apache.accumulo.core.master.thrift.TabletServerStatus;
+import org.apache.accumulo.core.tabletserver.thrift.ActionStats;
+import org.apache.accumulo.core.tabletserver.thrift.TabletClientService;
+import org.apache.accumulo.core.tabletserver.thrift.TabletStats;
+import org.apache.accumulo.core.util.Duration;
+import org.apache.accumulo.core.util.ThriftUtil;
+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.CompactionsType;
+import org.apache.accumulo.monitor.util.celltypes.DateTimeType;
+import org.apache.accumulo.monitor.util.celltypes.DurationType;
+import org.apache.accumulo.monitor.util.celltypes.NumberType;
+import org.apache.accumulo.monitor.util.celltypes.PercentageType;
+import org.apache.accumulo.monitor.util.celltypes.ProgressChartType;
+import org.apache.accumulo.monitor.util.celltypes.TServerLinkType;
+import org.apache.accumulo.monitor.util.celltypes.TableLinkType;
+import org.apache.accumulo.server.master.state.TabletServerState;
+import org.apache.accumulo.server.security.SystemCredentials;
+import org.apache.accumulo.server.util.ActionStatsUpdator;
+import org.apache.accumulo.server.util.TableInfoUtil;
+import org.apache.accumulo.trace.instrument.Tracer;
+import org.apache.commons.codec.binary.Base64;
+
+import com.google.common.net.HostAndPort;
+
+public class TServersServlet extends BasicServlet {
+  
+  private static final long serialVersionUID = 1L;
+  private static final TabletServerStatus NO_STATUS = new TabletServerStatus();
+  
+  static class SecondType extends NumberType<Double> {
+    
+    @Override
+    public String format(Object obj) {
+      if (obj == null)
+        return "&mdash;";
+      return Duration.format((long) (1000.0 * (Double) obj));
+    }
+    
+  }
+  
+  @Override
+  protected String getTitle(HttpServletRequest req) {
+    return "Tablet Server Status";
+  }
+  
+  @Override
+  protected void pageBody(HttpServletRequest req, HttpServletResponse response, StringBuilder sb) throws Exception {
+    String tserverAddress = req.getParameter("s");
+    
+    // Check to make sure tserver is a known address
+    boolean tserverExists = false;
+    if (tserverAddress != null && tserverAddress.isEmpty() == false) {
+      for (TabletServerStatus ts : Monitor.getMmi().getTServerInfo()) {
+        if (tserverAddress.equals(ts.getName())) {
+          tserverExists = true;
+          break;
+        }
+      }
+    }
+    
+    if (tserverAddress == null || tserverAddress.isEmpty() || tserverExists == false) {
+      doBadTserverList(req, sb);
+      
+      doDeadTserverList(req, sb);
+      
+      ArrayList<TabletServerStatus> tservers = new ArrayList<TabletServerStatus>();
+      if (Monitor.getMmi() != null)
+        tservers.addAll(Monitor.getMmi().tServerInfo);
+      
+      Table tServerList = new Table("tservers", "Tablet&nbsp;Servers");
+      tServerList.setSubCaption("Click on the <span style='color: #0000ff;'>server address</span> to view detailed performance statistics for that server.");
+      
+      doTserverList(req, sb, tservers, null, tServerList);
+      return;
+    }
+    
+    double totalElapsedForAll = 0;
+    double splitStdDev = 0;
+    double minorStdDev = 0;
+    double minorQueueStdDev = 0;
+    double majorStdDev = 0;
+    double majorQueueStdDev = 0;
+    double currentMinorAvg = 0;
+    double currentMajorAvg = 0;
+    double currentMinorStdDev = 0;
+    double currentMajorStdDev = 0;
+    TabletStats total = new TabletStats(null, new ActionStats(), new ActionStats(), new ActionStats(), 0, 0, 0, 0);
+    
+    HostAndPort address = HostAndPort.fromString(tserverAddress);
+    TabletStats historical = new TabletStats(null, new ActionStats(), new ActionStats(), new ActionStats(), 0, 0, 0, 0);
+    List<TabletStats> tsStats = new ArrayList<TabletStats>();
+    try {
+      TabletClientService.Client client = ThriftUtil.getClient(new TabletClientService.Client.Factory(), address, Monitor.getSystemConfiguration());
+      try {
+        for (String tableId : Monitor.getMmi().tableMap.keySet()) {
+          tsStats.addAll(client.getTabletStats(Tracer.traceInfo(), SystemCredentials.get().toThrift(Monitor.getInstance()), tableId));
+        }
+        historical = client.getHistoricalStats(Tracer.traceInfo(), SystemCredentials.get().toThrift(Monitor.getInstance()));
+      } finally {
+        ThriftUtil.returnClient(client);
+      }
+    } catch (Exception e) {
+      banner(sb, "error", "No Such Tablet ServerAvailable");
+      log.error(e, e);
+      return;
+    }
+    
+    Table perTabletResults = new Table("perTabletResults", "Detailed&nbsp;Current&nbsp;Operations");
+    perTabletResults.setSubCaption("Per-tablet&nbsp;Details");
+    perTabletResults.addSortableColumn("Table", new TableLinkType(), null);
+    perTabletResults.addSortableColumn("Tablet");
+    perTabletResults.addSortableColumn("Entries", new NumberType<Long>(), null);
+    perTabletResults.addSortableColumn("Ingest", new NumberType<Long>(), null);
+    perTabletResults.addSortableColumn("Query", new NumberType<Long>(), null);
+    perTabletResults.addSortableColumn("Minor&nbsp;Avg", new SecondType(), null);
+    perTabletResults.addSortableColumn("Minor&nbsp;Std&nbsp;Dev", new SecondType(), null);
+    perTabletResults.addSortableColumn("Minor&nbsp;Avg&nbsp;e/s", new NumberType<Double>(), null);
+    perTabletResults.addSortableColumn("Major&nbsp;Avg", new SecondType(), null);
+    perTabletResults.addSortableColumn("Major&nbsp;Std&nbsp;Dev", new SecondType(), null);
+    perTabletResults.addSortableColumn("Major&nbsp;Avg&nbsp;e/s", new NumberType<Double>(), null);
+    
+    for (TabletStats info : tsStats) {
+      if (info.extent == null) {
+        historical = info;
+        continue;
+      }
+      total.numEntries += info.numEntries;
+      ActionStatsUpdator.update(total.minors, info.minors);
+      ActionStatsUpdator.update(total.majors, info.majors);
+      
+      KeyExtent extent = new KeyExtent(info.extent);
+      String tableId = extent.getTableId().toString();
+      MessageDigest digester = MessageDigest.getInstance("MD5");
+      if (extent.getEndRow() != null && extent.getEndRow().getLength() > 0) {
+        digester.update(extent.getEndRow().getBytes(), 0, extent.getEndRow().getLength());
+      }
+      String obscuredExtent = new String(Base64.encodeBase64(digester.digest()));
+      String displayExtent = String.format("<code>[%s]</code>", obscuredExtent);
+      
+      TableRow row = perTabletResults.prepareRow();
+      row.add(tableId);
+      row.add(displayExtent);
+      row.add(info.numEntries);
+      row.add(info.ingestRate);
+      row.add(info.queryRate);
+      row.add(info.minors.num != 0 ? info.minors.elapsed / info.minors.num : null);
+      row.add(stddev(info.minors.elapsed, info.minors.num, info.minors.sumDev));
+      row.add(info.minors.elapsed != 0 ? info.minors.count / info.minors.elapsed : null);
+      row.add(info.majors.num != 0 ? info.majors.elapsed / info.majors.num : null);
+      row.add(stddev(info.majors.elapsed, info.majors.num, info.majors.sumDev));
+      row.add(info.majors.elapsed != 0 ? info.majors.count / info.majors.elapsed : null);
+      perTabletResults.addRow(row);
+    }
+    
+    // Calculate current averages oldServer adding in historical data
+    if (total.minors.num != 0)
+      currentMinorAvg = (long) (total.minors.elapsed / total.minors.num);
+    if (total.minors.elapsed != 0 && total.minors.num != 0)
+      currentMinorStdDev = stddev(total.minors.elapsed, total.minors.num, total.minors.sumDev);
+    if (total.majors.num != 0)
+      currentMajorAvg = total.majors.elapsed / total.majors.num;
+    if (total.majors.elapsed != 0 && total.majors.num != 0 && total.majors.elapsed > total.majors.num)
+      currentMajorStdDev = stddev(total.majors.elapsed, total.majors.num, total.majors.sumDev);
+    
+    // After these += operations, these variables are now total for current
+    // tablets and historical tablets
+    ActionStatsUpdator.update(total.minors, historical.minors);
+    ActionStatsUpdator.update(total.majors, historical.majors);
+    totalElapsedForAll += total.majors.elapsed + historical.splits.elapsed + total.minors.elapsed;
+    
+    minorStdDev = stddev(total.minors.elapsed, total.minors.num, total.minors.sumDev);
+    minorQueueStdDev = stddev(total.minors.queueTime, total.minors.num, total.minors.queueSumDev);
+    majorStdDev = stddev(total.majors.elapsed, total.majors.num, total.majors.sumDev);
+    majorQueueStdDev = stddev(total.majors.queueTime, total.majors.num, total.majors.queueSumDev);
+    splitStdDev = stddev(historical.splits.num, historical.splits.elapsed, historical.splits.sumDev);
+    
+    doDetailTable(req, sb, address, tsStats.size(), total, historical);
+    doAllTimeTable(req, sb, total, historical, majorQueueStdDev, minorQueueStdDev, totalElapsedForAll, splitStdDev, majorStdDev, minorStdDev);
+    doCurrentTabletOps(req, sb, currentMinorAvg, currentMinorStdDev, currentMajorAvg, currentMajorStdDev);
+    perTabletResults.generate(req, sb);
+  }
+  
+  private void doCurrentTabletOps(HttpServletRequest req, StringBuilder sb, double currentMinorAvg, double currentMinorStdDev, double currentMajorAvg,
+      double currentMajorStdDev) {
+    Table currentTabletOps = new Table("currentTabletOps", "Current&nbsp;Tablet&nbsp;Operation&nbsp;Results");
+    currentTabletOps.addSortableColumn("Minor&nbsp;Average", new SecondType(), null);
+    currentTabletOps.addSortableColumn("Minor&nbsp;Std&nbsp;Dev", new SecondType(), null);
+    currentTabletOps.addSortableColumn("Major&nbsp;Avg", new SecondType(), null);
+    currentTabletOps.addSortableColumn("Major&nbsp;Std&nbsp;Dev", new SecondType(), null);
+    currentTabletOps.addRow(currentMinorAvg, currentMinorStdDev, currentMajorAvg, currentMajorStdDev);
+    currentTabletOps.generate(req, sb);
+  }
+  
+  private void doAllTimeTable(HttpServletRequest req, StringBuilder sb, TabletStats total, TabletStats historical, double majorQueueStdDev,
+      double minorQueueStdDev, double totalElapsedForAll, double splitStdDev, double majorStdDev, double minorStdDev) {
+    
+    Table opHistoryDetails = new Table("opHistoryDetails", "All-Time&nbsp;Tablet&nbsp;Operation&nbsp;Results");
+    opHistoryDetails.addSortableColumn("Operation");
+    opHistoryDetails.addSortableColumn("Success", new NumberType<Integer>(), null);
+    opHistoryDetails.addSortableColumn("Failure", new NumberType<Integer>(), null);
+    opHistoryDetails.addSortableColumn("Average<br />Queue&nbsp;Time", new SecondType(), null);
+    opHistoryDetails.addSortableColumn("Std.&nbsp;Dev.<br />Queue&nbsp;Time", new SecondType(), null);
+    opHistoryDetails.addSortableColumn("Average<br />Time", new SecondType(), null);
+    opHistoryDetails.addSortableColumn("Std.&nbsp;Dev.<br />Time", new SecondType(), null);
+    opHistoryDetails.addSortableColumn("Percentage&nbsp;Time&nbsp;Spent", new ProgressChartType(totalElapsedForAll), null);
+    
+    opHistoryDetails.addRow("Split", historical.splits.num, historical.splits.fail, null, null,
+        historical.splits.num != 0 ? (historical.splits.elapsed / historical.splits.num) : null, splitStdDev, historical.splits.elapsed);
+    opHistoryDetails.addRow("Major&nbsp;Compaction", total.majors.num, total.majors.fail, total.majors.num != 0 ? (total.majors.queueTime / total.majors.num)
+        : null, majorQueueStdDev, total.majors.num != 0 ? (total.majors.elapsed / total.majors.num) : null, majorStdDev, total.majors.elapsed);
+    opHistoryDetails.addRow("Minor&nbsp;Compaction", total.minors.num, total.minors.fail, total.minors.num != 0 ? (total.minors.queueTime / total.minors.num)
+        : null, minorQueueStdDev, total.minors.num != 0 ? (total.minors.elapsed / total.minors.num) : null, minorStdDev, total.minors.elapsed);
+    opHistoryDetails.generate(req, sb);
+  }
+  
+  private void doDetailTable(HttpServletRequest req, StringBuilder sb, HostAndPort address, int numTablets, TabletStats total, TabletStats historical) {
+    Table detailTable = new Table("tServerDetail", "Details");
+    detailTable.setSubCaption(address.getHostText() + ":" + address.getPort());
+    detailTable.addSortableColumn("Hosted&nbsp;Tablets", new NumberType<Integer>(), null);
+    detailTable.addSortableColumn("Entries", new NumberType<Long>(), null);
+    detailTable.addSortableColumn("Minor&nbsp;Compacting", new NumberType<Integer>(), null);
+    detailTable.addSortableColumn("Major&nbsp;Compacting", new NumberType<Integer>(), null);
+    detailTable.addSortableColumn("Splitting", new NumberType<Integer>(), null);
+    detailTable.addRow(numTablets, total.numEntries, total.minors.status, total.majors.status, historical.splits.status);
+    detailTable.generate(req, sb);
+  }
+  
+  /*
+   * omg there's so much undocumented stuff going on here. First, sumDev is a partial standard deviation computation. It is the (clue 1) sum of the squares of
+   * (clue 2) seconds of elapsed time.
+   */
+  private static double stddev(double elapsed, double num, double sumDev) {
+    if (num != 0) {
+      double average = elapsed / num;
+      return Math.sqrt((sumDev / num) - (average * average));
+    }
+    return 0;
+  }
+  
+  private void doBadTserverList(HttpServletRequest req, StringBuilder sb) {
+    if (Monitor.getMmi() != null && !Monitor.getMmi().badTServers.isEmpty()) {
+      Table badTServerList = new Table("badtservers", "Non-Functioning&nbsp;Tablet&nbsp;Servers", "error");
+      badTServerList.setSubCaption("The following tablet servers reported a status other than Online.");
+      badTServerList.addSortableColumn("Tablet&nbsp;Server");
+      badTServerList.addSortableColumn("Tablet&nbsp;Server&nbsp;Status");
+      for (Entry<String,Byte> badserver : Monitor.getMmi().badTServers.entrySet())
+        badTServerList.addRow(badserver.getKey(), TabletServerState.getStateById(badserver.getValue()).name());
+      badTServerList.generate(req, sb);
+    }
+  }
+  
+  private void doDeadTserverList(HttpServletRequest req, StringBuilder sb) {
+    MasterMonitorInfo mmi = Monitor.getMmi();
+    if (mmi != null) {
+      List<DeadServer> obit = mmi.deadTabletServers;
+      Table deadTServerList = new Table("deaddtservers", "Dead&nbsp;Tablet&nbsp;Servers", "error");
+      deadTServerList.setSubCaption("The following tablet servers are no longer reachable.");
+      doDeadServerTable(req, sb, deadTServerList, obit);
+    }
+  }
+  
+  public static void doDeadServerTable(HttpServletRequest req, StringBuilder sb, Table deadTServerList, List<DeadServer> obit) {
+    if (obit != null && !obit.isEmpty()) {
+      deadTServerList.addSortableColumn("Server");
+      deadTServerList.addSortableColumn("Last&nbsp;Updated", new DateTimeType(DateFormat.MEDIUM, DateFormat.SHORT), null);
+      deadTServerList.addSortableColumn("Event");
+      deadTServerList.addUnsortableColumn("Clear");
+      for (DeadServer dead : obit)
+        deadTServerList.addRow(TServerLinkType.displayName(dead.server), dead.lastStatus, dead.status, "<a href='/op?action=clearDeadServer&redir="
+            + currentPage(req) + "&server=" + encode(dead.server) + "'>clear</a>");
+      deadTServerList.generate(req, sb);
+    }
+  }
+  
+  static void doTserverList(HttpServletRequest req, StringBuilder sb, List<TabletServerStatus> tservers, String tableId, Table tServerList) {
+    int guessHighLoad = ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors();
+    long now = System.currentTimeMillis();
+    
+    double avgLastContact = 0.;
+    for (TabletServerStatus status : tservers) {
+      avgLastContact += (now - status.lastContact);
+    }
+    final long MINUTES = 3 * 60 * 1000;
+    tServerList.addSortableColumn("Server", new TServerLinkType(), null);
+    tServerList.addSortableColumn("Hosted&nbsp;Tablets", new NumberType<Integer>(0, Integer.MAX_VALUE), null);
+    tServerList.addSortableColumn("Last&nbsp;Contact", new DurationType(0l, (long) Math.min(avgLastContact * 4, MINUTES)), null);
+    tServerList.addSortableColumn("Entries", new NumberType<Long>(), "The number of key/value pairs.");
+    tServerList.addSortableColumn("Ingest", new NumberType<Long>(), "The number of key/value pairs inserted. (Note that deletes are also 'inserted')");
+    tServerList.addSortableColumn("Query", new NumberType<Long>(), "The number of key/value pairs returned to clients. (Not the number of scans)");
+    tServerList.addSortableColumn("Hold&nbsp;Time", new DurationType(), "The amount of time ingest is suspended waiting for data to be written to disk.");
+    tServerList.addSortableColumn("Running<br />Scans", new CompactionsType("scans"), "The number of scans running and queued on this tablet server.");
+    tServerList
+        .addSortableColumn(
+            "Minor<br />Compactions",
+            new CompactionsType("minor"),
+            "The number of minor compactions running and (queued waiting for resources). Minor compactions are the operations where entries are flushed from memory to disk.");
+    tServerList.addSortableColumn("Major<br />Compactions", new CompactionsType("major"),
+        "The number of major compactions running and (queued waiting for resources). "
+            + "Major compactions are the operations where many smaller files are grouped into a larger file, eliminating duplicates and cleaning up deletes.");
+    tServerList.addSortableColumn("Index Cache<br />Hit Rate", new PercentageType(), "The recent index cache hit rate.");
+    tServerList.addSortableColumn("Data Cache<br />Hit Rate", new PercentageType(), "The recent data cache hit rate.");
+    tServerList.addSortableColumn("OS&nbsp;Load", new NumberType<Double>(0., guessHighLoad * 1., 0., guessHighLoad * 3.),
+        "The Unix one minute load average. The average number of processes in the run queue over a one minute interval.");
+    
+    log.debug("tableId: " + tableId);
+    for (TabletServerStatus status : tservers) {
+      if (status == null)
+        status = NO_STATUS;
+      TableInfo summary = TableInfoUtil.summarizeTableStats(status);
+      if (tableId != null)
+        summary = status.tableMap.get(tableId);
+      if (summary == null)
+        continue;
+      TableRow row = tServerList.prepareRow();
+      row.add(status); // add for server name
+      row.add(summary.tablets);
+      row.add(now - status.lastContact);
+      row.add(summary.recs);
+      row.add(summary.ingestRate);
+      row.add(summary.queryRate);
+      row.add(status.holdTime);
+      row.add(summary); // add for scans
+      row.add(summary); // add for minor compactions
+      row.add(summary); // add for major compactions
+      double indexCacheHitRate = status.indexCacheHits / (double) Math.max(status.indexCacheRequest, 1);
+      row.add(indexCacheHitRate);
+      double dataCacheHitRate = status.dataCacheHits / (double) Math.max(status.dataCacheRequest, 1);
+      row.add(dataCacheHitRate);
+      row.add(status.osLoad);
+      tServerList.addRow(row);
+    }
+    tServerList.generate(req, sb);
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/TablesServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/TablesServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/TablesServlet.java
new file mode 100644
index 0000000..428880e
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/TablesServlet.java
@@ -0,0 +1,190 @@
+/*
+ * 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.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.client.Instance;
+import org.apache.accumulo.core.client.impl.Tables;
+import org.apache.accumulo.core.data.KeyExtent;
+import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.master.thrift.TableInfo;
+import org.apache.accumulo.core.master.thrift.TabletServerStatus;
+import org.apache.accumulo.core.metadata.MetadataTable;
+import org.apache.accumulo.core.metadata.RootTable;
+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.CompactionsType;
+import org.apache.accumulo.monitor.util.celltypes.DurationType;
+import org.apache.accumulo.monitor.util.celltypes.NumberType;
+import org.apache.accumulo.monitor.util.celltypes.TableLinkType;
+import org.apache.accumulo.monitor.util.celltypes.TableStateType;
+import org.apache.accumulo.server.client.HdfsZooInstance;
+import org.apache.accumulo.server.master.state.MetaDataTableScanner;
+import org.apache.accumulo.server.master.state.TabletLocationState;
+import org.apache.accumulo.server.security.SystemCredentials;
+import org.apache.accumulo.server.tables.TableManager;
+import org.apache.accumulo.server.util.TableInfoUtil;
+import org.apache.hadoop.io.Text;
+
+public class TablesServlet extends BasicServlet {
+  
+  private static final long serialVersionUID = 1L;
+  
+  @Override
+  protected String getTitle(HttpServletRequest req) {
+    return "Table Status";
+  }
+  
+  @Override
+  protected void pageBody(HttpServletRequest req, HttpServletResponse response, StringBuilder sb) throws Exception {
+    Map<String,String> tidToNameMap = Tables.getIdToNameMap(HdfsZooInstance.getInstance());
+    String tableId = req.getParameter("t");
+    
+    doProblemsBanner(sb);
+    
+    if (tableId == null || tableId.isEmpty() || tidToNameMap.containsKey(tableId) == false) {
+      doTableList(req, sb, tidToNameMap);
+      return;
+    }
+    
+    doTableDetails(req, sb, tidToNameMap, tableId);
+  }
+  
+  static void doProblemsBanner(StringBuilder sb) {
+    int numProblems = Monitor.getProblemSummary().entrySet().size();
+    if (numProblems > 0)
+      banner(sb, "error", String.format("<a href='/problems'>Table Problems: %d Total</a>", numProblems));
+  }
+  
+  static void doTableList(HttpServletRequest req, StringBuilder sb, Map<String,String> tidToNameMap) {
+    Table tableList = new Table("tableList", "Table&nbsp;List");
+    tableList.addSortableColumn("Table&nbsp;Name", new TableLinkType(), null);
+    tableList.addSortableColumn("State", new TableStateType(), null);
+    tableList.addSortableColumn("#&nbsp;Tablets", new NumberType<Integer>(), "Tables are broken down into ranges of rows called tablets.");
+    tableList.addSortableColumn("#&nbsp;Offline<br />Tablets", new NumberType<Integer>(0, 0), "Tablets unavailable for query or ingest.  "
+        + "May be a transient condition when tablets are moved for balancing.");
+    tableList.addSortableColumn("Entries", new NumberType<Long>(), "Key/value pairs over each instance, table or tablet.");
+    tableList.addSortableColumn("Entries<br />In&nbsp;Memory", new NumberType<Long>(),
+        "The total number of key/value pairs stored in memory and not yet written to disk");
+    tableList.addSortableColumn("Ingest", new NumberType<Long>(), "The number of Key/Value pairs inserted.  Note that deletes are 'inserted'.");
+    tableList.addSortableColumn("Entries<br/>Read", new NumberType<Long>(),
+        "The number of Key/Value pairs read on the server side.  Not all key values read may be returned to client because of filtering.");
+    tableList.addSortableColumn("Entries<br/>Returned", new NumberType<Long>(),
+        "The number of Key/Value pairs returned to clients during queries.  This is <b>not</b> the number of scans.");
+    tableList.addSortableColumn("Hold&nbsp;Time", new DurationType(0l, 0l),
+        "The amount of time that ingest operations are suspended while waiting for data to be written to disk.");
+    tableList.addSortableColumn("Running<br />Scans", new CompactionsType("scans"),
+        "Information about the scans threads.  Shows how many threads are running and how much work is queued for the threads.");
+    tableList.addSortableColumn("Minor<br />Compactions", new CompactionsType("minor"), "Flushing memory to disk is called a \"minor compaction.\" "
+        + "Multiple tablets can be minor compacted simultaneously, but " + "" + "sometimes they must wait for resources to be available.  These "
+        + "tablets that are waiting for compaction are \"queued\" and are " + "indicated using parentheses. So <tt> 2 (3)</tt> indicates there are "
+        + "two compactions running and three queued waiting for resources.");
+    tableList.addSortableColumn("Major<br />Compactions", new CompactionsType("major"),
+        "Gathering up many small files and rewriting them as one larger file is called a 'Major Compaction'. "
+            + "Major Compactions are performed as a consequence of new files created from Minor Compactions and Bulk Load operations.  "
+            + "They reduce the number of files used during queries.");
+    SortedMap<String,TableInfo> tableStats = new TreeMap<String,TableInfo>();
+    
+    if (Monitor.getMmi() != null && Monitor.getMmi().tableMap != null)
+      for (Entry<String,TableInfo> te : Monitor.getMmi().tableMap.entrySet())
+        tableStats.put(Tables.getPrintableTableNameFromId(tidToNameMap, te.getKey()), te.getValue());
+    
+    Map<String,Double> compactingByTable = TableInfoUtil.summarizeTableStats(Monitor.getMmi());
+    TableManager tableManager = TableManager.getInstance();
+    
+    for (Entry<String,String> tableName_tableId : Tables.getNameToIdMap(HdfsZooInstance.getInstance()).entrySet()) {
+      String tableName = tableName_tableId.getKey();
+      String tableId = tableName_tableId.getValue();
+      TableInfo tableInfo = tableStats.get(tableName);
+      Double holdTime = compactingByTable.get(tableId);
+      if (holdTime == null)
+        holdTime = new Double(0.);
+      TableRow row = tableList.prepareRow();
+      row.add(tableId);
+      row.add(tableManager.getTableState(tableId));
+      row.add(tableInfo == null ? null : tableInfo.tablets);
+      row.add(tableInfo == null ? null : tableInfo.tablets - tableInfo.onlineTablets);
+      row.add(tableInfo == null ? null : tableInfo.recs);
+      row.add(tableInfo == null ? null : tableInfo.recsInMemory);
+      row.add(tableInfo == null ? null : tableInfo.ingestRate);
+      row.add(tableInfo == null ? null : tableInfo.scanRate);
+      row.add(tableInfo == null ? null : tableInfo.queryRate);
+      row.add(holdTime.longValue());
+      row.add(tableInfo);
+      row.add(tableInfo);
+      row.add(tableInfo);
+      tableList.addRow(row);
+    }
+    
+    tableList.generate(req, sb);
+  }
+  
+  private void doTableDetails(HttpServletRequest req, StringBuilder sb, Map<String,String> tidToNameMap, String tableId) {
+    String displayName = Tables.getPrintableTableNameFromId(tidToNameMap, tableId);
+    Instance instance = HdfsZooInstance.getInstance();
+    TreeSet<String> locs = new TreeSet<String>();
+    if (RootTable.ID.equals(tableId)) {
+      locs.add(instance.getRootTabletLocation());
+    } else {
+      String systemTableName = MetadataTable.ID.equals(tableId) ? RootTable.NAME : MetadataTable.NAME;
+      MetaDataTableScanner scanner = new MetaDataTableScanner(instance, SystemCredentials.get(), new Range(KeyExtent.getMetadataEntry(new Text(tableId),
+          new Text()), KeyExtent.getMetadataEntry(new Text(tableId), null)), systemTableName);
+      
+      while (scanner.hasNext()) {
+        TabletLocationState state = scanner.next();
+        if (state.current != null) {
+          try {
+            locs.add(state.current.hostPort());
+          } catch (Exception ex) {
+            log.error(ex, ex);
+          }
+        }
+      }
+      scanner.close();
+    }
+    
+    log.debug("Locs: " + locs);
+    
+    List<TabletServerStatus> tservers = new ArrayList<TabletServerStatus>();
+    if (Monitor.getMmi() != null) {
+      for (TabletServerStatus tss : Monitor.getMmi().tServerInfo) {
+        try {
+          log.debug("tss: " + tss.name);
+          if (tss.name != null && locs.contains(tss.name))
+            tservers.add(tss);
+        } catch (Exception ex) {
+          log.error(ex, ex);
+        }
+      }
+    }
+    
+    Table tableDetails = new Table("participatingTServers", "Participating&nbsp;Tablet&nbsp;Servers");
+    tableDetails.setSubCaption(displayName);
+    TServersServlet.doTserverList(req, sb, tservers, tableId, tableDetails);
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/VisServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/VisServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/VisServlet.java
new file mode 100644
index 0000000..5d2d2db
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/VisServlet.java
@@ -0,0 +1,236 @@
+/*
+ * 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.util.ArrayList;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.master.thrift.TabletServerStatus;
+import org.apache.accumulo.monitor.Monitor;
+
+public class VisServlet extends BasicServlet {
+  private static final int concurrentScans = Monitor.getSystemConfiguration().getCount(Property.TSERV_READ_AHEAD_MAXCONCURRENT);
+  
+  private static final long serialVersionUID = 1L;
+  
+  public enum StatType {
+    osload(ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors(), true, 100, "OS Load"),
+    ingest(1000, true, 1, "Ingest Entries"),
+    query(10000, true, 1, "Scan Entries"),
+    ingestMB(10, true, 10, "Ingest MB"),
+    queryMB(5, true, 10, "Scan MB"),
+    scans(concurrentScans * 2, false, 1, "Running Scans"),
+    scansessions(50, true, 10, "Scan Sessions"),
+    holdtime(60000, false, 1, "Hold Time"),
+    allavg(1, false, 100, "Overall Avg", true),
+    allmax(1, false, 100, "Overall Max", true);
+    
+    private int max;
+    private boolean adjustMax;
+    private float significance;
+    private String description;
+    private boolean derived;
+    
+    /**
+     * @param max
+     *          initial estimate of largest possible value for this stat
+     * @param adjustMax
+     *          indicates whether max should be adjusted based on observed values
+     * @param significance
+     *          values will be converted by floor(significance*value)/significance
+     * @param description
+     *          as appears in selection box
+     */
+    private StatType(int max, boolean adjustMax, float significance, String description) {
+      this(max, adjustMax, significance, description, false);
+    }
+    
+    private StatType(int max, boolean adjustMax, float significance, String description, boolean derived) {
+      this.max = max;
+      this.adjustMax = adjustMax;
+      this.significance = significance;
+      this.description = description;
+      this.derived = derived;
+    }
+    
+    public int getMax() {
+      return max;
+    }
+    
+    public boolean getAdjustMax() {
+      return adjustMax;
+    }
+    
+    public float getSignificance() {
+      return significance;
+    }
+    
+    public String getDescription() {
+      return description;
+    }
+    
+    public boolean isDerived() {
+      return derived;
+    }
+    
+    public static int numDerived() {
+      int count = 0;
+      for (StatType st : StatType.values())
+        if (st.isDerived())
+          count++;
+      return count;
+    }
+  }
+  
+  public static class VisualizationConfig {
+    boolean useCircles = true;
+    StatType motion = StatType.allmax;
+    StatType color = StatType.allavg;
+    int spacing = 40;
+    String url;
+  }
+  
+  @Override
+  protected String getTitle(HttpServletRequest req) {
+    return "Server Activity";
+  }
+  
+  @Override
+  protected void pageBody(HttpServletRequest req, HttpServletResponse response, StringBuilder sb) throws IOException {
+    StringBuffer urlsb = req.getRequestURL();
+    urlsb.setLength(urlsb.lastIndexOf("/") + 1);
+    VisualizationConfig cfg = new VisualizationConfig();
+    cfg.url = urlsb.toString();
+    
+    String s = req.getParameter("shape");
+    if (s != null && (s.equals("square") || s.equals("squares"))) {
+      cfg.useCircles = false;
+    }
+    
+    s = req.getParameter("motion");
+    if (s != null) {
+      try {
+        cfg.motion = StatType.valueOf(s);
+      } catch (Exception e) {}
+    }
+    
+    s = req.getParameter("color");
+    if (s != null) {
+      try {
+        cfg.color = StatType.valueOf(s);
+      } catch (Exception e) {}
+    }
+    
+    String size = req.getParameter("size");
+    if (size != null) {
+      if (size.equals("10"))
+        cfg.spacing = 10;
+      else if (size.equals("20"))
+        cfg.spacing = 20;
+      else if (size.equals("80"))
+        cfg.spacing = 80;
+    }
+    
+    ArrayList<TabletServerStatus> tservers = new ArrayList<TabletServerStatus>();
+    if (Monitor.getMmi() != null)
+      tservers.addAll(Monitor.getMmi().tServerInfo);
+    
+    if (tservers.size() == 0)
+      return;
+    
+    int width = (int) Math.ceil(Math.sqrt(tservers.size())) * cfg.spacing;
+    int height = (int) Math.ceil(tservers.size() / width) * cfg.spacing;
+    doSettings(sb, cfg, width < 640 ? 640 : width, height < 640 ? 640 : height);
+    doScript(sb, cfg, tservers);
+  }
+  
+  private void doSettings(StringBuilder sb, VisualizationConfig cfg, int width, int height) {
+    sb.append("<div class='left'>\n");
+    sb.append("<div id='parameters' class='nowrap'>\n");
+    // shape select box
+    sb.append("<span class='viscontrol'>Shape: <select id='shape' onchange='setShape(this)'><option>Circles</option><option")
+        .append(!cfg.useCircles ? " selected='true'" : "").append(">Squares</option></select></span>\n");
+    // size select box
+    sb.append("&nbsp;&nbsp<span class='viscontrol'>Size: <select id='size' onchange='setSize(this)'><option")
+        .append(cfg.spacing == 10 ? " selected='true'" : "").append(">10</option><option").append(cfg.spacing == 20 ? " selected='true'" : "")
+        .append(">20</option><option").append(cfg.spacing == 40 ? " selected='true'" : "").append(">40</option><option")
+        .append(cfg.spacing == 80 ? " selected='true'" : "").append(">80</option></select></span>\n");
+    // motion select box
+    sb.append("&nbsp;&nbsp<span class='viscontrol'>Motion: <select id='motion' onchange='setMotion(this)'>");
+    sb.append("<option selected='true'></option>");
+    addOptions(sb, null);
+    sb.append("</select></span>\n");
+    // color select box
+    sb.append("&nbsp;&nbsp<span class='viscontrol'>Color: <select id='color' onchange='setColor(this)'>");
+    addOptions(sb, cfg.color);
+    sb.append("</select></span>\n");
+    sb.append("&nbsp;&nbsp<span class='viscontrol'>(hover for info, click for details)</span>");
+    sb.append("</div>\n\n");
+    sb.append("<div id='hoverable'>\n");
+    // floating info box
+    sb.append("<div id='vishoverinfo'></div>\n\n");
+    // canvas
+    sb.append("<br><canvas id='visCanvas' width='").append(width).append("' height='").append(height).append("'>Browser does not support canvas.</canvas>\n\n");
+    sb.append("</div>\n");
+    sb.append("</div>\n\n");
+  }
+  
+  private void addOptions(StringBuilder sb, StatType selectedStatType) {
+    for (StatType st : StatType.values()) {
+      sb.append("<option").append(st.equals(selectedStatType) ? " selected='true'>" : ">").append(st.getDescription()).append("</option>");
+    }
+  }
+  
+  private void doScript(StringBuilder sb, VisualizationConfig cfg, ArrayList<TabletServerStatus> tservers) {
+    // initialization of some javascript variables
+    sb.append("<script type='text/javascript'>\n");
+    sb.append("var numCores = " + ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors() + ";\n");
+    sb.append("var jsonurl = '" + cfg.url + "json';\n");
+    sb.append("var visurl = '" + cfg.url + "vis';\n");
+    sb.append("var serverurl = '" + cfg.url + "tservers?s=';\n\n");
+    sb.append("// observable stats that can be connected to motion or color\n");
+    sb.append("var statNames = {");
+    for (StatType st : StatType.values())
+      sb.append("'").append(st).append("': ").append(st.derived).append(",");
+    sb.setLength(sb.length() - 1);
+    sb.append("};\n");
+    sb.append("var maxStatValues = {");
+    for (StatType st : StatType.values())
+      sb.append("'").append(st).append("': ").append(st.getMax()).append(", ");
+    sb.setLength(sb.length() - 2);
+    sb.append("}; // initial values that are system-dependent may increase based on observed values\n");
+    sb.append("var adjustMax = {");
+    for (StatType st : StatType.values())
+      sb.append("'").append(st).append("': ").append(st.getAdjustMax()).append(", ");
+    sb.setLength(sb.length() - 2);
+    sb.append("}; // whether to allow increases in the max based on observed values\n");
+    sb.append("var significance = {");
+    for (StatType st : StatType.values())
+      sb.append("'").append(st).append("': ").append(st.getSignificance()).append(", ");
+    sb.setLength(sb.length() - 2);
+    sb.append("}; // values will be converted by floor(this*value)/this\n");
+    sb.append("var numNormalStats = ").append(StatType.values().length - StatType.numDerived()).append(";\n");
+    sb.append("</script>\n");
+    
+    sb.append("<script src='web/vis.js' type='text/javascript'></script>");
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/XMLServlet.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/XMLServlet.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/XMLServlet.java
new file mode 100644
index 0000000..0cbf957
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/XMLServlet.java
@@ -0,0 +1,179 @@
+/*
+ * 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.Map.Entry;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+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.client.impl.Tables;
+import org.apache.accumulo.core.master.thrift.Compacting;
+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 org.apache.accumulo.server.client.HdfsZooInstance;
+import org.apache.accumulo.server.master.state.TabletServerState;
+import org.apache.accumulo.server.util.TableInfoUtil;
+
+public class XMLServlet extends BasicServlet {
+  private static final long serialVersionUID = 1L;
+  
+  @Override
+  protected String getTitle(HttpServletRequest req) {
+    return "XML Report";
+  }
+  
+  @Override
+  protected void pageStart(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) {
+    resp.setContentType("text/xml;charset=" + Constants.UTF8.name());
+    sb.append("<?xml version=\"1.0\" encoding=\"" + Constants.UTF8.name() + "\"?>\n");
+    sb.append("<stats>\n");
+  }
+  
+  @Override
+  protected void pageBody(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) {
+    double totalIngest = 0.;
+    double totalQuery = 0.;
+    double disk = 0.0;
+    long totalEntries = 0L;
+    
+    sb.append("\n<servers>\n");
+    if (Monitor.getMmi() == null || Monitor.getMmi().tableMap == null) {
+      sb.append("</servers>\n");
+      return;
+    }
+    SortedMap<String,TableInfo> tableStats = new TreeMap<String,TableInfo>(Monitor.getMmi().tableMap);
+    
+    for (TabletServerStatus status : Monitor.getMmi().tServerInfo) {
+      
+      sb.append("\n<server id='").append(status.name).append("'>\n");
+      sb.append("<hostname>").append(TServerLinkType.displayName(status.name)).append("</hostname>");
+      sb.append("<lastContact>").append(System.currentTimeMillis() - status.lastContact).append("</lastContact>\n");
+      sb.append("<osload>").append(status.osLoad).append("</osload>\n");
+      
+      TableInfo summary = TableInfoUtil.summarizeTableStats(status);
+      sb.append("<compactions>\n");
+      sb.append("<major>").append("<running>").append(summary.majors.running).append("</running>").append("<queued>").append(summary.majors.queued)
+          .append("</queued>").append("</major>\n");
+      sb.append("<minor>").append("<running>").append(summary.minors.running).append("</running>").append("<queued>").append(summary.minors.queued)
+          .append("</queued>").append("</minor>\n");
+      sb.append("</compactions>\n");
+      
+      sb.append("<tablets>").append(summary.tablets).append("</tablets>\n");
+      
+      sb.append("<ingest>").append(summary.ingestRate).append("</ingest>\n");
+      sb.append("<query>").append(summary.queryRate).append("</query>\n");
+      sb.append("<ingestMB>").append(summary.ingestByteRate / 1000000.0).append("</ingestMB>\n");
+      sb.append("<queryMB>").append(summary.queryByteRate / 1000000.0).append("</queryMB>\n");
+      sb.append("<scans>").append(summary.scans.running + summary.scans.queued).append("</scans>");
+      sb.append("<scansessions>").append(Monitor.getLookupRate()).append("</scansessions>\n");
+      sb.append("<holdtime>").append(status.holdTime).append("</holdtime>\n");
+      totalIngest += summary.ingestRate;
+      totalQuery += summary.queryRate;
+      totalEntries += summary.recs;
+      sb.append("</server>\n");
+    }
+    sb.append("\n</servers>\n");
+    
+    sb.append("\n<masterGoalState>" + Monitor.getMmi().goalState + "</masterGoalState>\n");
+    sb.append("\n<masterState>" + Monitor.getMmi().state + "</masterState>\n");
+    
+    sb.append("\n<badTabletServers>\n");
+    for (Entry<String,Byte> entry : Monitor.getMmi().badTServers.entrySet()) {
+      sb.append(String.format("<badTabletServer id='%s' status='%s'/>\n", entry.getKey(), TabletServerState.getStateById(entry.getValue())));
+    }
+    sb.append("\n</badTabletServers>\n");
+    
+    sb.append("\n<tabletServersShuttingDown>\n");
+    for (String server : Monitor.getMmi().serversShuttingDown) {
+      sb.append(String.format("<server id='%s'/>\n", server));
+    }
+    sb.append("\n</tabletServersShuttingDown>\n");
+    
+    sb.append(String.format("\n<unassignedTablets>%d</unassignedTablets>\n", Monitor.getMmi().unassignedTablets));
+    
+    sb.append("\n<deadTabletServers>\n");
+    for (DeadServer dead : Monitor.getMmi().deadTabletServers) {
+      sb.append(String.format("<deadTabletServer id='%s' lastChange='%d' status='%s'/>\n", dead.server, dead.lastStatus, dead.status));
+    }
+    sb.append("\n</deadTabletServers>\n");
+    
+    sb.append("\n<deadLoggers>\n");
+    for (DeadServer dead : Monitor.getMmi().deadTabletServers) {
+      sb.append(String.format("<deadLogger id='%s' lastChange='%d' status='%s'/>\n", dead.server, dead.lastStatus, dead.status));
+    }
+    sb.append("\n</deadLoggers>\n");
+    
+    sb.append("\n<tables>\n");
+    Instance instance = HdfsZooInstance.getInstance();
+    for (Entry<String,TableInfo> entry : tableStats.entrySet()) {
+      TableInfo tableInfo = entry.getValue();
+      
+      sb.append("\n<table>\n");
+      String tableId = entry.getKey();
+      String tableName = "unknown";
+      String tableState = "unknown";
+      try {
+        tableName = Tables.getTableName(instance, tableId);
+        tableState = Tables.getTableState(instance, tableId).toString();
+      } catch (Exception ex) {
+        log.warn(ex, ex);
+      }
+      sb.append("<tablename>").append(tableName).append("</tablename>\n");
+      sb.append("<tableId>").append(tableId).append("</tableId>\n");
+      sb.append("<tableState>").append(tableState).append("</tableState>\n");
+      sb.append("<tablets>").append(tableInfo.tablets).append("</tablets>\n");
+      sb.append("<onlineTablets>").append(tableInfo.onlineTablets).append("</onlineTablets>\n");
+      sb.append("<recs>").append(tableInfo.recs).append("</recs>\n");
+      sb.append("<recsInMemory>").append(tableInfo.recsInMemory).append("</recsInMemory>\n");
+      sb.append("<ingest>").append(tableInfo.ingestRate).append("</ingest>\n");
+      sb.append("<ingestByteRate>").append(tableInfo.ingestByteRate).append("</ingestByteRate>\n");
+      sb.append("<query>").append(tableInfo.queryRate).append("</query>\n");
+      sb.append("<queryByteRate>").append(tableInfo.queryRate).append("</queryByteRate>\n");
+      int running = 0;
+      int queued = 0;
+      Compacting compacting = entry.getValue().majors;
+      if (compacting != null) {
+        running = compacting.running;
+        queued = compacting.queued;
+      }
+      sb.append("<majorCompactions>").append("<running>").append(running).append("</running>").append("<queued>").append(queued).append("</queued>")
+          .append("</majorCompactions>\n");
+      sb.append("</table>\n");
+    }
+    sb.append("\n</tables>\n");
+    
+    sb.append("\n<totals>\n");
+    sb.append("<ingestrate>").append(totalIngest).append("</ingestrate>\n");
+    sb.append("<queryrate>").append(totalQuery).append("</queryrate>\n");
+    sb.append("<diskrate>").append(disk).append("</diskrate>\n");
+    sb.append("<numentries>").append(totalEntries).append("</numentries>\n");
+    sb.append("</totals>\n");
+  }
+  
+  @Override
+  protected void pageEnd(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) {
+    sb.append("\n</stats>\n");
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/Basic.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/Basic.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/Basic.java
new file mode 100644
index 0000000..d85efa5
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/Basic.java
@@ -0,0 +1,104 @@
+/*
+ * 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.trace;
+
+import java.util.Date;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.Connector;
+import org.apache.accumulo.core.client.Scanner;
+import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.client.security.tokens.AuthenticationToken;
+import org.apache.accumulo.core.client.security.tokens.AuthenticationToken.Properties;
+import org.apache.accumulo.core.client.security.tokens.PasswordToken;
+import org.apache.accumulo.core.conf.AccumuloConfiguration;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.trace.TraceFormatter;
+import org.apache.accumulo.monitor.Monitor;
+import org.apache.accumulo.monitor.servlets.BasicServlet;
+import org.apache.accumulo.server.client.HdfsZooInstance;
+
+abstract class Basic extends BasicServlet {
+
+  private static final long serialVersionUID = 1L;
+
+  public static String getStringParameter(HttpServletRequest req, String name, String defaultValue) {
+    String result = req.getParameter(name);
+    if (result == null) {
+      return defaultValue;
+    }
+    return result;
+  }
+
+  public static int getIntParameter(HttpServletRequest req, String name, int defaultMinutes) {
+    String valueString = req.getParameter(name);
+    if (valueString == null)
+      return defaultMinutes;
+    int result = 0;
+    try {
+      result = Integer.parseInt(valueString);
+    } catch (NumberFormatException ex) {
+      return defaultMinutes;
+    }
+    return result;
+  }
+
+  public static String dateString(long millis) {
+    return TraceFormatter.formatDate(new Date(millis));
+  }
+
+  protected Scanner getScanner(StringBuilder sb) throws AccumuloException, AccumuloSecurityException {
+    AccumuloConfiguration conf = Monitor.getSystemConfiguration();
+    String principal = conf.get(Property.TRACE_USER);
+    AuthenticationToken at;
+    Map<String,String> loginMap = conf.getAllPropertiesWithPrefix(Property.TRACE_TOKEN_PROPERTY_PREFIX);
+    if (loginMap.isEmpty()) {
+      Property p = Property.TRACE_PASSWORD;
+      at = new PasswordToken(conf.get(p).getBytes());
+    } else {
+      Properties props = new Properties();
+      int prefixLength = Property.TRACE_TOKEN_PROPERTY_PREFIX.getKey().length() + 1;
+      for (Entry<String,String> entry : loginMap.entrySet()) {
+        props.put(entry.getKey().substring(prefixLength), entry.getValue());
+      }
+
+      AuthenticationToken token = Property.createInstanceFromPropertyName(conf, Property.TRACE_TOKEN_TYPE, AuthenticationToken.class, new PasswordToken());
+      token.init(props);
+      at = token;
+    }
+
+    String table = conf.get(Property.TRACE_TABLE);
+    try {
+      Connector conn = HdfsZooInstance.getInstance().getConnector(principal, at);
+      if (!conn.tableOperations().exists(table)) {
+        return new NullScanner();
+      }
+      Scanner scanner = conn.createScanner(table, conn.securityOperations().getUserAuthorizations(principal));
+      return scanner;
+    } catch (AccumuloSecurityException ex) {
+      sb.append("<h2>Unable to read trace table: check trace username and password configuration.</h2>\n");
+      return null;
+    } catch (TableNotFoundException ex) {
+      return new NullScanner();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/ListType.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/ListType.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/ListType.java
new file mode 100644
index 0000000..e29c5d7
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/ListType.java
@@ -0,0 +1,76 @@
+/*
+ * 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.trace;
+
+import java.util.Map.Entry;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.client.Scanner;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.trace.TraceFormatter;
+import org.apache.accumulo.monitor.util.Table;
+import org.apache.accumulo.monitor.util.celltypes.DurationType;
+import org.apache.accumulo.monitor.util.celltypes.StringType;
+import org.apache.accumulo.trace.thrift.RemoteSpan;
+import org.apache.hadoop.io.Text;
+
+public class ListType extends Basic {
+  
+  private static final long serialVersionUID = 1L;
+  
+  String getType(HttpServletRequest req) {
+    return getStringParameter(req, "type", "<Unknown>");
+  }
+  
+  int getMinutes(HttpServletRequest req) {
+    return getIntParameter(req, "minutes", Summary.DEFAULT_MINUTES);
+  }
+  
+  @Override
+  public void pageBody(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) throws Exception {
+    String type = getType(req);
+    int minutes = getMinutes(req);
+    long endTime = System.currentTimeMillis();
+    long startTime = endTime - minutes * 60 * 1000;
+    Scanner scanner = getScanner(sb);
+    if (scanner == null) {
+      return;
+    }
+    Range range = new Range(new Text("start:" + Long.toHexString(startTime)), new Text("start:" + Long.toHexString(endTime)));
+    scanner.setRange(range);
+    Table trace = new Table("trace", "Traces for " + getType(req));
+    trace.addSortableColumn("Start", new ShowTraceLinkType(), "Start Time");
+    trace.addSortableColumn("ms", new DurationType(), "Span time");
+    trace.addUnsortableColumn("Source", new StringType<String>(), "Service and location");
+    for (Entry<Key,Value> entry : scanner) {
+      RemoteSpan span = TraceFormatter.getRemoteSpan(entry);
+      if (span.description.equals(type)) {
+        trace.addRow(span, new Long(span.stop - span.start), span.svc + ":" + span.sender);
+      }
+    }
+    trace.generate(req, sb);
+  }
+  
+  @Override
+  public String getTitle(HttpServletRequest req) {
+    return "Traces for " + getType(req) + " for the last " + getMinutes(req) + " minutes";
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/NullKeyValueIterator.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/NullKeyValueIterator.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/NullKeyValueIterator.java
new file mode 100644
index 0000000..26cfb07
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/NullKeyValueIterator.java
@@ -0,0 +1,39 @@
+/*
+ * 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.trace;
+
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Value;
+
+public class NullKeyValueIterator implements Iterator<Entry<Key,Value>> {
+  
+  @Override
+  public boolean hasNext() {
+    return false;
+  }
+  
+  @Override
+  public Entry<Key,Value> next() {
+    return null;
+  }
+  
+  @Override
+  public void remove() {}
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/NullScanner.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/NullScanner.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/NullScanner.java
new file mode 100644
index 0000000..bf35557
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/NullScanner.java
@@ -0,0 +1,116 @@
+/*
+ * 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.trace;
+
+import java.util.Iterator;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.accumulo.core.client.IteratorSetting;
+import org.apache.accumulo.core.client.Scanner;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.Value;
+import org.apache.hadoop.io.Text;
+
+public class NullScanner implements Scanner {
+  
+  @Override
+  public void addScanIterator(IteratorSetting cfg) {}
+  
+  @Override
+  public void updateScanIteratorOption(String iteratorName, String key, String value) {}
+  
+  @Override
+  public void fetchColumnFamily(Text col) {}
+  
+  @Override
+  public void fetchColumn(Text colFam, Text colQual) {}
+  
+  @Override
+  public void clearColumns() {}
+  
+  @Override
+  public void clearScanIterators() {}
+  
+  @Deprecated
+  @Override
+  public void setTimeOut(int timeOut) {}
+  
+  @Deprecated
+  @Override
+  public int getTimeOut() {
+    return 0;
+  }
+  
+  @Override
+  public void setRange(Range range) {}
+  
+  @Override
+  public Range getRange() {
+    return null;
+  }
+  
+  @Override
+  public void setBatchSize(int size) {
+    
+  }
+  
+  @Override
+  public int getBatchSize() {
+    return 0;
+  }
+  
+  @Override
+  public void enableIsolation() {
+    
+  }
+  
+  @Override
+  public void disableIsolation() {
+    
+  }
+  
+  @Override
+  public Iterator<Entry<Key,Value>> iterator() {
+    return new NullKeyValueIterator();
+  }
+  
+  @Override
+  public void removeScanIterator(String iteratorName) {}
+  
+  @Override
+  public void setTimeout(long timeOut, TimeUnit timeUnit) {}
+  
+  @Override
+  public long getTimeout(TimeUnit timeUnit) {
+    return 0;
+  }
+  
+  @Override
+  public void close() {}
+
+  @Override
+  public long getReadaheadThreshold() {
+    return 0l;
+  }
+
+  @Override
+  public void setReadaheadThreshold(long batches) {
+    
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/ShowTrace.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/ShowTrace.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/ShowTrace.java
new file mode 100644
index 0000000..a476201
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/ShowTrace.java
@@ -0,0 +1,159 @@
+/*
+ * 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.trace;
+
+import static java.lang.Math.min;
+
+import java.util.Collection;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.client.Scanner;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.trace.SpanTree;
+import org.apache.accumulo.core.trace.SpanTreeVisitor;
+import org.apache.accumulo.core.trace.TraceDump;
+import org.apache.accumulo.core.trace.TraceFormatter;
+import org.apache.accumulo.monitor.servlets.BasicServlet;
+import org.apache.accumulo.trace.thrift.RemoteSpan;
+import org.apache.hadoop.io.Text;
+
+public class ShowTrace extends Basic {
+  
+  private static final long serialVersionUID = 1L;
+  private static final String checkboxIdSuffix = "_checkbox";
+  private static final String pageLoadFunctionName = "pageload";
+  
+  String getTraceId(HttpServletRequest req) {
+    return getStringParameter(req, "id", null);
+  }
+  
+  @Override
+  public String getTitle(HttpServletRequest req) {
+    String id = getTraceId(req);
+    if (id == null)
+      return "No trace id specified";
+    return "Trace ID " + id;
+  }
+  
+  @Override
+  public void pageBody(HttpServletRequest req, HttpServletResponse resp, final StringBuilder sb) throws Exception {
+    String id = getTraceId(req);
+    if (id == null) {
+      return;
+    }
+    Scanner scanner = getScanner(sb);
+    if (scanner == null) {
+      return;
+    }
+    Range range = new Range(new Text(id));
+    scanner.setRange(range);
+    SpanTree tree = new SpanTree();
+    long start = Long.MAX_VALUE;
+    for (Entry<Key,Value> entry : scanner) {
+      RemoteSpan span = TraceFormatter.getRemoteSpan(entry);
+      tree.addNode(span);
+      start = min(start, span.start);
+    }
+    sb.append("<style>\n");
+    sb.append(" td.right { text-align: right }\n");
+    sb.append(" table.indent { position: relative; left: 10% }\n");
+    sb.append(" td.left { text-align: left }\n");
+    sb.append("</style>\n");
+    sb.append("<script language='javascript'>\n");
+    sb.append("function toggle(id) {\n");
+    sb.append(" var elt = document.getElementById(id);\n");
+    sb.append(" if (elt.style.display=='none') {\n");
+    sb.append("    elt.style.display='table-row';\n");
+    sb.append(" } else { \n");
+    sb.append("    elt.style.display='none';\n ");
+    sb.append(" }\n");
+    sb.append("}\n");
+    
+    sb.append("function ").append(pageLoadFunctionName).append("() {\n");
+    sb.append("  var checkboxes = document.getElementsByTagName('input');\n");
+    sb.append("  for (var i = 0; i < checkboxes.length; i++) {\n");
+    sb.append("    if (checkboxes[i].checked) {\n");
+    sb.append("      var idSuffixOffset = checkboxes[i].id.indexOf('").append(checkboxIdSuffix).append("');\n");
+    sb.append("      var id = checkboxes[i].id.substring(0, idSuffixOffset);\n");
+    sb.append("      document.getElementById(id).style.display='table-row';\n");
+    sb.append("    }\n");
+    sb.append("  }\n");
+    sb.append("}\n");
+    
+    sb.append("</script>\n");
+    sb.append("<div>");
+    sb.append("<table><caption>");
+    sb.append(String.format("<span class='table-caption'>Trace %s started at<br>%s</span></caption>", id, dateString(start)));
+    sb.append("<tr><th>Time</th><th>Start</th><th>Service@Location</th><th>Name</th><th>Addl Data</th></tr>");
+    
+    final long finalStart = start;
+    Set<Long> visited = tree.visit(new SpanTreeVisitor() {
+      @Override
+      public void visit(int level, RemoteSpan parent, RemoteSpan node, Collection<RemoteSpan> children) {
+        sb.append("<tr>\n");
+        sb.append(String.format("<td class='right'>%d+</td><td class='left'>%d</td>%n", node.stop - node.start, node.start - finalStart));
+        sb.append(String.format("<td style='text-indent: %dpx'>%s@%s</td>%n", level * 5, node.svc, node.sender));
+        sb.append("<td>" + node.description + "</td>");
+        boolean hasData = node.data != null && !node.data.isEmpty();
+        if (hasData) {
+          String hexSpanId = Long.toHexString(node.spanId);
+          sb.append("<td><input type='checkbox' id=\"");
+          sb.append(hexSpanId);
+          sb.append(checkboxIdSuffix);
+          sb.append("\" onclick='toggle(\"" + Long.toHexString(node.spanId) + "\")'></td>\n");
+        } else {
+          sb.append("<td></td>\n");
+        }
+        sb.append("</tr>\n");
+        sb.append("<tr id='" + Long.toHexString(node.spanId) + "' style='display:none'>");
+        sb.append("<td colspan='5'>\n");
+        if (hasData) {
+          sb.append("  <table class='indent,noborder'>\n");
+          for (Entry<String,String> entry : node.data.entrySet()) {
+            sb.append("  <tr><td>" + BasicServlet.sanitize(entry.getKey()) + "</td>");
+            sb.append("<td>" + BasicServlet.sanitize(entry.getValue()) + "</td></tr>\n");
+          }
+          sb.append("  </table>");
+        }
+        sb.append("</td>\n");
+        sb.append("</tr>\n");
+      }
+    });
+    tree.nodes.keySet().removeAll(visited);
+    if (!tree.nodes.isEmpty()) {
+      sb.append("<span type='warn'>Warning: the following spans are not rooted!</span>\n");
+      sb.append("<ul>\n");
+      for (RemoteSpan span : TraceDump.sortByStart(tree.nodes.values())) {
+        sb.append(String.format("<li>%s %s %s</li>\n", Long.toHexString(span.spanId), Long.toHexString(span.parentId), span.description));
+      }
+      sb.append("</ul>\n");
+    }
+    sb.append("</table>\n");
+    sb.append("</div>\n");
+  }
+  
+  @Override
+  protected String getBodyAttributes() {
+    return " onload=\"" + pageLoadFunctionName + "()\" ";
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/ShowTraceLinkType.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/ShowTraceLinkType.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/ShowTraceLinkType.java
new file mode 100644
index 0000000..98b7d6e
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/ShowTraceLinkType.java
@@ -0,0 +1,47 @@
+/*
+ * 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.trace;
+
+import java.util.Date;
+
+import org.apache.accumulo.core.trace.TraceFormatter;
+import org.apache.accumulo.monitor.util.celltypes.StringType;
+import org.apache.accumulo.trace.thrift.RemoteSpan;
+
+/**
+ * 
+ */
+public class ShowTraceLinkType extends StringType<RemoteSpan> {
+  @Override
+  public String format(Object obj) {
+    if (obj == null)
+      return "-";
+    RemoteSpan span = (RemoteSpan) obj;
+    return String.format("<a href='/trace/show?id=%s'>%s</a>", Long.toHexString(span.traceId), TraceFormatter.formatDate(new Date(span.start)));
+  }
+  
+  @Override
+  public int compare(RemoteSpan o1, RemoteSpan o2) {
+    if (o1 == null && o2 == null)
+      return 0;
+    else if (o1 == null)
+      return -1;
+    else if (o2 == null)
+      return 1;
+    return o1.start < o2.start ? -1 : (o1.start == o2.start ? 0 : 1);
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/598821cd/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/Summary.java
----------------------------------------------------------------------
diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/Summary.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/Summary.java
new file mode 100644
index 0000000..b444c0c
--- /dev/null
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/trace/Summary.java
@@ -0,0 +1,166 @@
+/*
+ * 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.trace;
+
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.accumulo.core.client.Scanner;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.trace.TraceFormatter;
+import org.apache.accumulo.monitor.servlets.BasicServlet;
+import org.apache.accumulo.monitor.util.Table;
+import org.apache.accumulo.monitor.util.celltypes.DurationType;
+import org.apache.accumulo.monitor.util.celltypes.NumberType;
+import org.apache.accumulo.monitor.util.celltypes.StringType;
+import org.apache.accumulo.trace.thrift.RemoteSpan;
+import org.apache.hadoop.io.Text;
+
+public class Summary extends Basic {
+  
+  private static final long serialVersionUID = 1L;
+  public static final int DEFAULT_MINUTES = 10;
+  
+  int getMinutes(HttpServletRequest req) {
+    return getIntParameter(req, "minutes", DEFAULT_MINUTES);
+  }
+  
+  @Override
+  public String getTitle(HttpServletRequest req) {
+    return "Traces for the last " + getMinutes(req) + " minutes";
+  }
+  
+  static private class Stats {
+    int count;
+    long min = Long.MAX_VALUE;
+    long max = Long.MIN_VALUE;
+    long total = 0l;
+    long histogram[] = new long[] {0, 0, 0, 0, 0, 0};
+    
+    void addSpan(RemoteSpan span) {
+      count++;
+      long ms = span.stop - span.start;
+      total += ms;
+      min = Math.min(min, ms);
+      max = Math.max(max, ms);
+      int index = 0;
+      while (ms >= 10 && index < histogram.length) {
+        ms /= 10;
+        index++;
+      }
+      histogram[index]++;
+    }
+    
+    long average() {
+      return total / count;
+    }
+  }
+  
+  private static class ShowTypeLink extends StringType<String> {
+    
+    int minutes;
+    
+    public ShowTypeLink(int minutes) {
+      this.minutes = minutes;
+    }
+    
+    @Override
+    public String format(Object obj) {
+      if (obj == null)
+        return "-";
+      String type = obj.toString();
+      String encodedType = BasicServlet.encode(type);
+      return String.format("<a href='/trace/listType?type=%s&minutes=%d'>%s</a>", encodedType, minutes, type);
+    }
+  }
+  
+  static private class HistogramType extends StringType<Stats> {
+    @Override
+    public String format(Object obj) {
+      Stats stat = (Stats) obj;
+      StringBuilder sb = new StringBuilder();
+      sb.append("<table>");
+      sb.append("<tr>");
+      for (long count : stat.histogram) {
+        if (count > 0)
+          sb.append(String.format("<td style='width:5em'>%d</td>", count));
+        else
+          sb.append("<td style='width:5em'>-</td>");
+      }
+      sb.append("</tr></table>");
+      return sb.toString();
+    }
+    
+    @Override
+    public int compare(Stats o1, Stats o2) {
+      for (int i = 0; i < o1.histogram.length; i++) {
+        long diff = o1.histogram[i] - o2.histogram[i];
+        if (diff < 0)
+          return -1;
+        if (diff > 0)
+          return 1;
+      }
+      return 0;
+    }
+  }
+  
+  @Override
+  public void pageBody(HttpServletRequest req, HttpServletResponse resp, StringBuilder sb) throws Exception {
+    int minutes = getMinutes(req);
+    long endTime = System.currentTimeMillis();
+    long startTime = endTime - minutes * 60 * 1000;
+    
+    Scanner scanner = getScanner(sb);
+    if (scanner == null) {
+      return;
+    }
+    Range range = new Range(new Text("start:" + Long.toHexString(startTime)), new Text("start:" + Long.toHexString(endTime)));
+    scanner.setRange(range);
+    Map<String,Stats> summary = new TreeMap<String,Stats>();
+    for (Entry<Key,Value> entry : scanner) {
+      RemoteSpan span = TraceFormatter.getRemoteSpan(entry);
+      Stats stats = summary.get(span.description);
+      if (stats == null) {
+        summary.put(span.description, stats = new Stats());
+      }
+      stats.addSpan(span);
+    }
+    Table trace = new Table("traceSummary", "All Traces");
+    trace.addSortableColumn("Type", new ShowTypeLink(minutes), "Trace Type");
+    trace.addSortableColumn("Total", new NumberType<Integer>(), "Number of spans of this type");
+    trace.addSortableColumn("min", new DurationType(), "Shortest span duration");
+    trace.addSortableColumn("max", new DurationType(), "Longest span duration");
+    trace.addSortableColumn("avg", new DurationType(), "Average span duration");
+    trace
+        .addSortableColumn(
+            "Histogram",
+            new HistogramType(),
+            "Counts of spans of different duration. Columns start at milliseconds, and each column is ten times longer: tens of milliseconds, seconds, tens of seconds, etc.");
+    
+    for (Entry<String,Stats> entry : summary.entrySet()) {
+      Stats stat = entry.getValue();
+      trace.addRow(entry.getKey(), stat.count, stat.min, stat.max, stat.average(), stat);
+    }
+    trace.generate(req, sb);
+  }
+}