You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kudu.apache.org by wd...@apache.org on 2017/10/17 23:10:39 UTC

[1/3] kudu git commit: Rename *-path-handlers.* to *_path_handlers.*

Repository: kudu
Updated Branches:
  refs/heads/master abbfa49de -> 1c90b2f5c


http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/tserver/tserver-path-handlers.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tserver/tserver-path-handlers.cc b/src/kudu/tserver/tserver-path-handlers.cc
deleted file mode 100644
index 49b2df2..0000000
--- a/src/kudu/tserver/tserver-path-handlers.cc
+++ /dev/null
@@ -1,679 +0,0 @@
-// 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.
-
-#include "kudu/tserver/tserver-path-handlers.h"
-
-#include <algorithm>
-#include <cstddef>
-#include <cstdint>
-#include <iosfwd>
-#include <map>
-#include <memory>
-#include <sstream>
-#include <string>
-#include <type_traits>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include <boost/bind.hpp> // IWYU pragma: keep
-#include <glog/logging.h>
-
-#include "kudu/common/column_predicate.h"
-#include "kudu/common/common.pb.h"
-#include "kudu/common/encoded_key.h"
-#include "kudu/common/iterator.h"
-#include "kudu/common/iterator_stats.h"
-#include "kudu/common/partition.h"
-#include "kudu/common/scan_spec.h"
-#include "kudu/common/schema.h"
-#include "kudu/common/wire_protocol.pb.h"
-#include "kudu/consensus/consensus.pb.h"
-#include "kudu/consensus/log_anchor_registry.h"
-#include "kudu/consensus/metadata.pb.h"
-#include "kudu/consensus/opid.pb.h"
-#include "kudu/consensus/quorum_util.h"
-#include "kudu/consensus/raft_consensus.h"
-#include "kudu/gutil/map-util.h"
-#include "kudu/gutil/ref_counted.h"
-#include "kudu/gutil/stringprintf.h"
-#include "kudu/gutil/strings/human_readable.h"
-#include "kudu/gutil/strings/join.h"
-#include "kudu/gutil/strings/numbers.h"
-#include "kudu/gutil/strings/substitute.h"
-#include "kudu/server/webui_util.h"
-#include "kudu/tablet/metadata.pb.h"
-#include "kudu/tablet/tablet.h"
-#include "kudu/tablet/tablet.pb.h"
-#include "kudu/tablet/tablet_metadata.h"
-#include "kudu/tablet/tablet_replica.h"
-#include "kudu/tablet/transactions/transaction.h"
-#include "kudu/tserver/scanners.h"
-#include "kudu/tserver/tablet_server.h"
-#include "kudu/tserver/ts_tablet_manager.h"
-#include "kudu/util/easy_json.h"
-#include "kudu/util/maintenance_manager.h"
-#include "kudu/util/maintenance_manager.pb.h"
-#include "kudu/util/mem_tracker.h"
-#include "kudu/util/monotime.h"
-#include "kudu/util/pb_util.h"
-#include "kudu/util/url-coding.h"
-#include "kudu/util/web_callback_registry.h"
-
-using kudu::consensus::GetConsensusRole;
-using kudu::consensus::ConsensusStatePB;
-using kudu::consensus::RaftPeerPB;
-using kudu::consensus::TransactionStatusPB;
-using kudu::MaintenanceManagerStatusPB;
-using kudu::MaintenanceManagerStatusPB_MaintenanceOpPB;
-using kudu::MaintenanceManagerStatusPB_OpInstancePB;
-using kudu::pb_util::SecureDebugString;
-using kudu::pb_util::SecureShortDebugString;
-using kudu::tablet::Tablet;
-using kudu::tablet::TabletReplica;
-using kudu::tablet::TabletStatePB;
-using kudu::tablet::TabletStatusPB;
-using kudu::tablet::Transaction;
-using std::endl;
-using std::map;
-using std::ostringstream;
-using std::shared_ptr;
-using std::string;
-using std::vector;
-using strings::Substitute;
-
-namespace kudu {
-namespace tserver {
-
-TabletServerPathHandlers::~TabletServerPathHandlers() {
-}
-
-Status TabletServerPathHandlers::Register(Webserver* server) {
-  server->RegisterPrerenderedPathHandler(
-    "/scans", "Scans",
-    boost::bind(&TabletServerPathHandlers::HandleScansPage, this, _1, _2),
-    true /* styled */, false /* is_on_nav_bar */);
-  server->RegisterPrerenderedPathHandler(
-    "/tablets", "Tablets",
-    boost::bind(&TabletServerPathHandlers::HandleTabletsPage, this, _1, _2),
-    true /* styled */, true /* is_on_nav_bar */);
-  server->RegisterPrerenderedPathHandler(
-    "/tablet", "",
-    boost::bind(&TabletServerPathHandlers::HandleTabletPage, this, _1, _2),
-    true /* styled */, false /* is_on_nav_bar */);
-  server->RegisterPrerenderedPathHandler(
-    "/transactions", "",
-    boost::bind(&TabletServerPathHandlers::HandleTransactionsPage, this, _1, _2),
-    true /* styled */, false /* is_on_nav_bar */);
-  server->RegisterPrerenderedPathHandler(
-    "/tablet-rowsetlayout-svg", "",
-    boost::bind(&TabletServerPathHandlers::HandleTabletSVGPage, this, _1, _2),
-    true /* styled */, false /* is_on_nav_bar */);
-  server->RegisterPrerenderedPathHandler(
-    "/tablet-consensus-status", "",
-    boost::bind(&TabletServerPathHandlers::HandleConsensusStatusPage, this, _1, _2),
-    true /* styled */, false /* is_on_nav_bar */);
-  server->RegisterPrerenderedPathHandler(
-    "/log-anchors", "",
-    boost::bind(&TabletServerPathHandlers::HandleLogAnchorsPage, this, _1, _2),
-    true /* styled */, false /* is_on_nav_bar */);
-  server->RegisterPrerenderedPathHandler(
-    "/dashboards", "Dashboards",
-    boost::bind(&TabletServerPathHandlers::HandleDashboardsPage, this, _1, _2),
-    true /* styled */, true /* is_on_nav_bar */);
-  server->RegisterPathHandler(
-    "/maintenance-manager", "",
-    boost::bind(&TabletServerPathHandlers::HandleMaintenanceManagerPage, this, _1, _2),
-    true /* styled */, false /* is_on_nav_bar */);
-
-  return Status::OK();
-}
-
-void TabletServerPathHandlers::HandleTransactionsPage(const Webserver::WebRequest& req,
-                                                      Webserver::PrerenderedWebResponse* resp) {
-  ostringstream* output = resp->output;
-  bool as_text = ContainsKey(req.parsed_args, "raw");
-
-  vector<scoped_refptr<TabletReplica> > replicas;
-  tserver_->tablet_manager()->GetTabletReplicas(&replicas);
-
-  string arg = FindWithDefault(req.parsed_args, "include_traces", "false");
-  Transaction::TraceType trace_type = ParseLeadingBoolValue(
-      arg.c_str(), false) ? Transaction::TRACE_TXNS : Transaction::NO_TRACE_TXNS;
-
-  if (!as_text) {
-    *output << "<h1>Transactions</h1>\n";
-    *output << "<table class='table table-striped'>\n";
-    *output << "   <thead><tr><th>Tablet id</th><th>Op Id</th>"
-      "<th>Transaction Type</th><th>"
-      "Total time in-flight</th><th>Description</th></tr></thead>\n";
-    *output << "<tbody>\n";
-  }
-
-  for (const scoped_refptr<TabletReplica>& replica : replicas) {
-    vector<TransactionStatusPB> inflight;
-
-    if (replica->tablet() == nullptr) {
-      continue;
-    }
-
-    replica->GetInFlightTransactions(trace_type, &inflight);
-    for (const TransactionStatusPB& inflight_tx : inflight) {
-      string total_time_str = Substitute("$0 us.", inflight_tx.running_for_micros());
-      string description;
-      if (trace_type == Transaction::TRACE_TXNS) {
-        description = Substitute("$0, Trace: $1",
-                                  inflight_tx.description(), inflight_tx.trace_buffer());
-      } else {
-        description = inflight_tx.description();
-      }
-
-      if (!as_text) {
-        *output << Substitute(
-          "<tr><th>$0</th><th>$1</th><th>$2</th><th>$3</th><th>$4</th></tr>\n",
-          EscapeForHtmlToString(replica->tablet_id()),
-          EscapeForHtmlToString(SecureShortDebugString(inflight_tx.op_id())),
-          OperationType_Name(inflight_tx.tx_type()),
-          total_time_str,
-          EscapeForHtmlToString(description));
-      } else {
-        *output << "Tablet: " << replica->tablet_id() << endl;
-        *output << "Op ID: " << SecureShortDebugString(inflight_tx.op_id()) << endl;
-        *output << "Type: " << OperationType_Name(inflight_tx.tx_type()) << endl;
-        *output << "Running: " << total_time_str;
-        *output << description << endl;
-        *output << endl;
-      }
-    }
-  }
-
-  if (!as_text) {
-    *output << "</tbody></table>\n";
-  }
-}
-
-namespace {
-string TabletLink(const string& id) {
-  return Substitute("<a href=\"/tablet?id=$0\">$1</a>",
-                    UrlEncodeToString(id),
-                    EscapeForHtmlToString(id));
-}
-
-} // anonymous namespace
-
-void TabletServerPathHandlers::HandleTabletsPage(const Webserver::WebRequest& /*req*/,
-                                                 Webserver::PrerenderedWebResponse* resp) {
-  ostringstream* output = resp->output;
-  vector<scoped_refptr<TabletReplica>> replicas;
-  tserver_->tablet_manager()->GetTabletReplicas(&replicas);
-
-  // Sort by (table_name, tablet_id) tuples.
-  std::sort(replicas.begin(), replicas.end(),
-            [](const scoped_refptr<TabletReplica>& rep_a,
-               const scoped_refptr<TabletReplica>& rep_b) {
-              return std::make_pair(rep_a->tablet_metadata()->table_name(), rep_a->tablet_id()) <
-                     std::make_pair(rep_b->tablet_metadata()->table_name(), rep_b->tablet_id());
-            });
-
-  // For assigning ids to table divs;
-  int i = 0;
-  auto generate_table = [this, &i](const vector<scoped_refptr<TabletReplica>>& replicas,
-                                   std::ostream* output) {
-    i++;
-
-    *output << "<h4>Summary</h4>\n";
-    map<string, int> tablet_statuses;
-    for (const scoped_refptr<TabletReplica>& replica : replicas) {
-      tablet_statuses[TabletStatePB_Name(replica->state())]++;
-    }
-    *output << "<table class='table table-striped table-hover'>\n";
-    *output << "<thead><tr><th>Status</th><th>Count</th><th>Percentage</th></tr></thead>\n";
-    *output << "<tbody>\n";
-    for (const auto& entry : tablet_statuses) {
-      double percent = replicas.size() == 0 ? 0 : (100.0 * entry.second) / replicas.size();
-      *output << Substitute("<tr><td>$0</td><td>$1</td><td>$2</td></tr>\n",
-                            entry.first,
-                            entry.second,
-                            StringPrintf("%.2f", percent));
-    }
-    *output << "</tbody>\n";
-    *output << Substitute("<tfoot><tr><td>Total</td><td>$0</td><td></td></tr></tfoot>\n",
-                          replicas.size());
-    *output << "</table>\n";
-
-    *output << "<h4>Detail</h4>";
-    *output << Substitute("<a href='#detail$0' data-toggle='collapse'>(toggle)</a>\n", i);
-    *output << Substitute("<div id='detail$0' class='collapse'>\n", i);
-    *output << "<table class='table table-striped table-hover'>\n";
-    *output << "<thead><tr><th>Table name</th><th>Tablet ID</th>"
-        "<th>Partition</th><th>State</th><th>Write buffer memory usage</th>"
-        "<th>On-disk size</th><th>RaftConfig</th><th>Last status</th></tr></thead>\n";
-    *output << "<tbody>\n";
-    for (const scoped_refptr<TabletReplica>& replica : replicas) {
-      TabletStatusPB status;
-      replica->GetTabletStatusPB(&status);
-      string id = status.tablet_id();
-      string table_name = status.table_name();
-      string tablet_id_or_link;
-      if (replica->tablet() != nullptr) {
-        tablet_id_or_link = TabletLink(id);
-      } else {
-        tablet_id_or_link = EscapeForHtmlToString(id);
-      }
-      string mem_bytes = "";
-      if (replica->tablet() != nullptr) {
-        mem_bytes = HumanReadableNumBytes::ToString(
-            replica->tablet()->mem_tracker()->consumption());
-      }
-      string n_bytes = "";
-      if (status.has_estimated_on_disk_size()) {
-        n_bytes = HumanReadableNumBytes::ToString(status.estimated_on_disk_size());
-      }
-      string partition = replica->tablet_metadata()
-                                ->partition_schema()
-                                 .PartitionDebugString(replica->tablet_metadata()->partition(),
-                                                       replica->tablet_metadata()->schema());
-
-      shared_ptr<consensus::RaftConsensus> consensus = replica->shared_consensus();
-      *output << Substitute(
-          // Table name, tablet id, partition
-          "<tr><td>$0</td><td>$1</td><td>$2</td>"
-          // State, on-disk size, consensus configuration, last status
-          "<td>$3</td><td>$4</td><td>$5</td><td>$6</td><td>$7</td></tr>\n",
-          EscapeForHtmlToString(table_name), // $0
-          tablet_id_or_link, // $1
-          EscapeForHtmlToString(partition), // $2
-          EscapeForHtmlToString(replica->HumanReadableState()), mem_bytes, n_bytes, // $3, $4, $5
-          consensus ? ConsensusStatePBToHtml(consensus->ConsensusState()) : "", // $6
-          EscapeForHtmlToString(status.last_status())); // $7
-    }
-    *output << "<tbody></table>\n</div>\n";
-  };
-
-  vector<scoped_refptr<TabletReplica>> live_replicas;
-  vector<scoped_refptr<TabletReplica>> tombstoned_replicas;
-  for (const scoped_refptr<TabletReplica>& replica : replicas) {
-    if (replica->HumanReadableState() != "TABLET_DATA_TOMBSTONED") {
-      live_replicas.push_back(replica);
-    } else {
-      tombstoned_replicas.push_back(replica);
-    }
-  }
-
-  if (!live_replicas.empty()) {
-    *output << "<h3>Live Tablets</h3>\n";
-    generate_table(live_replicas, output);
-  }
-  if (!tombstoned_replicas.empty()) {
-    *output << "<h3>Tombstoned Tablets</h3>\n";
-    *output << "<p><small>Tombstoned tablets are tablets that previously "
-               "stored a replica on this server.</small></p>";
-    generate_table(tombstoned_replicas, output);
-  }
-}
-
-namespace {
-
-bool CompareByMemberType(const RaftPeerPB& a, const RaftPeerPB& b) {
-  if (!a.has_member_type()) return false;
-  if (!b.has_member_type()) return true;
-  return a.member_type() < b.member_type();
-}
-
-} // anonymous namespace
-
-string TabletServerPathHandlers::ConsensusStatePBToHtml(const ConsensusStatePB& cstate) const {
-  ostringstream html;
-
-  html << "<ul>\n";
-  std::vector<RaftPeerPB> sorted_peers;
-  sorted_peers.assign(cstate.committed_config().peers().begin(),
-                      cstate.committed_config().peers().end());
-  std::sort(sorted_peers.begin(), sorted_peers.end(), &CompareByMemberType);
-  for (const RaftPeerPB& peer : sorted_peers) {
-    string peer_addr_or_uuid =
-        peer.has_last_known_addr() ? Substitute("$0:$1",
-                                                peer.last_known_addr().host(),
-                                                peer.last_known_addr().port())
-                                   : peer.permanent_uuid();
-    peer_addr_or_uuid = EscapeForHtmlToString(peer_addr_or_uuid);
-    string role_name = RaftPeerPB::Role_Name(GetConsensusRole(peer.permanent_uuid(), cstate));
-    string formatted = Substitute("$0: $1", role_name, peer_addr_or_uuid);
-    // Make the local peer bold.
-    if (peer.permanent_uuid() == tserver_->instance_pb().permanent_uuid()) {
-      formatted = Substitute("<b>$0</b>", formatted);
-    }
-
-    html << Substitute(" <li>$0</li>\n", formatted);
-  }
-  html << "</ul>\n";
-  return html.str();
-}
-
-namespace {
-
-bool GetTabletID(const Webserver::WebRequest& req,
-                 string* id,
-                 Webserver::PrerenderedWebResponse* resp) {
-  if (!FindCopy(req.parsed_args, "id", id)) {
-    resp->status_code = HttpStatusCode::BadRequest;
-    *resp->output << "Tablet missing 'id' argument";
-    return false;
-  }
-  return true;
-}
-
-bool GetTabletReplica(TabletServer* tserver, const Webserver::WebRequest& /*req*/,
-                      scoped_refptr<TabletReplica>* replica, const string& tablet_id,
-                      Webserver::PrerenderedWebResponse* resp) {
-  if (!tserver->tablet_manager()->LookupTablet(tablet_id, replica)) {
-    resp->status_code = HttpStatusCode::NotFound;
-    *resp->output << "Tablet " << EscapeForHtmlToString(tablet_id) << " not found";
-    return false;
-  }
-  return true;
-}
-
-bool TabletBootstrapping(const scoped_refptr<TabletReplica>& replica, const string& tablet_id,
-                         Webserver::PrerenderedWebResponse* resp) {
-  if (replica->state() == tablet::BOOTSTRAPPING) {
-    resp->status_code = HttpStatusCode::ServiceUnavailable;
-    *resp->output << "Tablet " << EscapeForHtmlToString(tablet_id) << " is still bootstrapping";
-    return true;
-  }
-  return false;
-}
-
-// Returns true if the tablet_id was properly specified, the
-// tablet is found, and is in a non-bootstrapping state.
-bool LoadTablet(TabletServer* tserver,
-                const Webserver::WebRequest& req,
-                string* tablet_id, scoped_refptr<TabletReplica>* replica,
-                Webserver::PrerenderedWebResponse* resp) {
-  return GetTabletID(req, tablet_id, resp) &&
-      GetTabletReplica(tserver, req, replica, *tablet_id, resp) &&
-      !TabletBootstrapping(*replica, *tablet_id, resp);
-}
-
-} // anonymous namespace
-
-void TabletServerPathHandlers::HandleTabletPage(const Webserver::WebRequest& req,
-                                                Webserver::PrerenderedWebResponse* resp) {
-  ostringstream* output = resp->output;
-  string tablet_id;
-  scoped_refptr<TabletReplica> replica;
-  if (!LoadTablet(tserver_, req, &tablet_id, &replica, resp)) return;
-
-  string table_name = replica->tablet_metadata()->table_name();
-  RaftPeerPB::Role role = RaftPeerPB::UNKNOWN_ROLE;
-  auto consensus = replica->consensus();
-  if (consensus) {
-    role = consensus->role();
-  }
-
-  *output << "<h1>Tablet " << EscapeForHtmlToString(tablet_id)
-          << " (" << replica->HumanReadableState()
-          << "/" << RaftPeerPB::Role_Name(role) << ")</h1>\n";
-  *output << "<h3>Table " << EscapeForHtmlToString(table_name) << "</h3>";
-
-  // Output schema in tabular format.
-  *output << "<h2>Schema</h2>\n";
-  const Schema& schema = replica->tablet_metadata()->schema();
-  HtmlOutputSchemaTable(schema, output);
-
-  *output << "<h2>Other Tablet Info Pages</h2>" << endl;
-
-  // List of links to various tablet-specific info pages
-  *output << "<ul>";
-
-  // Link to output svg of current DiskRowSet layout over keyspace.
-  *output << "<li>" << Substitute("<a href=\"/tablet-rowsetlayout-svg?id=$0\">$1</a>",
-                                  UrlEncodeToString(tablet_id),
-                                  "Rowset Layout Diagram")
-          << "</li>" << endl;
-
-  // Link to consensus status page.
-  *output << "<li>" << Substitute("<a href=\"/tablet-consensus-status?id=$0\">$1</a>",
-                                  UrlEncodeToString(tablet_id),
-                                  "Consensus Status")
-          << "</li>" << endl;
-
-  // Log anchors info page.
-  *output << "<li>" << Substitute("<a href=\"/log-anchors?id=$0\">$1</a>",
-                                  UrlEncodeToString(tablet_id),
-                                  "Tablet Log Anchors")
-          << "</li>" << endl;
-
-  // End list
-  *output << "</ul>\n";
-}
-
-void TabletServerPathHandlers::HandleTabletSVGPage(const Webserver::WebRequest& req,
-                                                   Webserver::PrerenderedWebResponse* resp) {
-  ostringstream* output = resp->output;
-  string id;
-  scoped_refptr<TabletReplica> replica;
-  if (!LoadTablet(tserver_, req, &id, &replica, resp)) return;
-  shared_ptr<Tablet> tablet = replica->shared_tablet();
-  if (!tablet) {
-    *output << "Tablet " << EscapeForHtmlToString(id) << " not running";
-    return;
-  }
-
-  *output << "<h1>Rowset Layout Diagram for Tablet "
-          << TabletLink(id) << "</h1>\n";
-  tablet->PrintRSLayout(output);
-
-}
-
-void TabletServerPathHandlers::HandleLogAnchorsPage(const Webserver::WebRequest& req,
-                                                    Webserver::PrerenderedWebResponse* resp) {
-  ostringstream* output = resp->output;
-  string tablet_id;
-  scoped_refptr<TabletReplica> replica;
-  if (!LoadTablet(tserver_, req, &tablet_id, &replica, resp)) return;
-
-  *output << "<h1>Log Anchors for Tablet " << EscapeForHtmlToString(tablet_id) << "</h1>"
-          << std::endl;
-
-  string dump = replica->log_anchor_registry()->DumpAnchorInfo();
-  *output << "<pre>" << EscapeForHtmlToString(dump) << "</pre>" << std::endl;
-}
-
-void TabletServerPathHandlers::HandleConsensusStatusPage(const Webserver::WebRequest& req,
-                                                         Webserver::PrerenderedWebResponse* resp) {
-  ostringstream* output = resp->output;
-  string id;
-  scoped_refptr<TabletReplica> replica;
-  if (!LoadTablet(tserver_, req, &id, &replica, resp)) return;
-  shared_ptr<consensus::RaftConsensus> consensus = replica->shared_consensus();
-  if (!consensus) {
-    *output << "Tablet " << EscapeForHtmlToString(id) << " not initialized";
-    return;
-  }
-  consensus->DumpStatusHtml(*output);
-}
-
-void TabletServerPathHandlers::HandleScansPage(const Webserver::WebRequest& /*req*/,
-                                               Webserver::PrerenderedWebResponse* resp) {
-  std::ostringstream* output = resp->output;
-  *output << "<h1>Scans</h1>\n";
-  *output << "<table class='table table-striped'>\n";
-  *output << "<thead><tr><th>Tablet id</th><th>Scanner id</th><th>Total time in-flight</th>"
-      "<th>Time since last update</th><th>Requestor</th><th>Iterator Stats</th>"
-      "<th>Pushed down key predicates</th><th>Other predicates</th></tr></thead>\n";
-  *output << "<tbody>\n";
-
-  vector<SharedScanner> scanners;
-  tserver_->scanner_manager()->ListScanners(&scanners);
-  for (const SharedScanner& scanner : scanners) {
-    *output << ScannerToHtml(*scanner);
-  }
-  *output << "</tbody></table>";
-}
-
-string TabletServerPathHandlers::ScannerToHtml(const Scanner& scanner) const {
-  std::ostringstream html;
-  uint64_t time_in_flight_us =
-      (MonoTime::Now() - scanner.start_time()).ToMicroseconds();
-  uint64_t time_since_last_access_us =
-      scanner.TimeSinceLastAccess(MonoTime::Now()).ToMicroseconds();
-
-  html << Substitute("<tr><td>$0</td><td>$1</td><td>$2 us.</td><td>$3 us.</td><td>$4</td>",
-                     EscapeForHtmlToString(scanner.tablet_id()), // $0
-                     EscapeForHtmlToString(scanner.id()), // $1
-                     time_in_flight_us, time_since_last_access_us, // $2, $3
-                     EscapeForHtmlToString(scanner.requestor_string())); // $4
-
-
-  if (!scanner.IsInitialized()) {
-    html << "<td colspan=\"3\">&lt;not yet initialized&gt;</td></tr>";
-    return html.str();
-  }
-
-  const Schema* projection = &scanner.iter()->schema();
-
-  vector<IteratorStats> stats;
-  scanner.GetIteratorStats(&stats);
-  CHECK_EQ(stats.size(), projection->num_columns());
-  html << Substitute("<td>$0</td>", IteratorStatsToHtml(*projection, stats));
-  scoped_refptr<TabletReplica> tablet_replica;
-  if (!tserver_->tablet_manager()->LookupTablet(scanner.tablet_id(), &tablet_replica)) {
-    html << Substitute("<td colspan=\"2\"><b>Tablet $0 is no longer valid.</b></td></tr>\n",
-                       scanner.tablet_id());
-  } else {
-    string range_pred_str;
-    vector<string> other_preds;
-    const ScanSpec& spec = scanner.spec();
-    if (spec.lower_bound_key() || spec.exclusive_upper_bound_key()) {
-      range_pred_str = EncodedKey::RangeToString(spec.lower_bound_key(),
-                                                 spec.exclusive_upper_bound_key());
-    }
-    for (const auto& col_pred : scanner.spec().predicates()) {
-      int32_t col_idx = projection->find_column(col_pred.first);
-      if (col_idx == Schema::kColumnNotFound) {
-        other_preds.emplace_back("unknown column");
-      } else {
-        other_preds.push_back(col_pred.second.ToString());
-      }
-    }
-    string other_pred_str = JoinStrings(other_preds, "\n");
-    html << Substitute("<td>$0</td><td>$1</td></tr>\n",
-                       EscapeForHtmlToString(range_pred_str),
-                       EscapeForHtmlToString(other_pred_str));
-  }
-  return html.str();
-}
-
-string TabletServerPathHandlers::IteratorStatsToHtml(const Schema& projection,
-                                                     const vector<IteratorStats>& stats) const {
-  std::ostringstream html;
-  html << "<table>\n";
-  html << "<tr><th>Column</th>"
-       << "<th>Blocks read from disk</th>"
-       << "<th>Bytes read from disk</th>"
-       << "<th>Cells read from disk</th>"
-       << "</tr>\n";
-  for (size_t idx = 0; idx < stats.size(); idx++) {
-    // We use 'title' attributes so that if the user hovers over the value, they get a
-    // human-readable tooltip.
-    html << Substitute("<tr>"
-                       "<td>$0</td>"
-                       "<td title=\"$1\">$2</td>"
-                       "<td title=\"$3\">$4</td>"
-                       "<td title=\"$5\">$6</td>"
-                       "</tr>\n",
-                       EscapeForHtmlToString(projection.column(idx).name()), // $0
-                       HumanReadableInt::ToString(stats[idx].data_blocks_read_from_disk), // $1
-                       stats[idx].data_blocks_read_from_disk, // $2
-                       HumanReadableNumBytes::ToString(stats[idx].bytes_read_from_disk), // $3
-                       stats[idx].bytes_read_from_disk, // $4
-                       HumanReadableInt::ToString(stats[idx].cells_read_from_disk), // $5
-                       stats[idx].cells_read_from_disk); // $6
-  }
-  html << "</table>\n";
-  return html.str();
-}
-
-void TabletServerPathHandlers::HandleDashboardsPage(const Webserver::WebRequest& /*req*/,
-                                                    Webserver::PrerenderedWebResponse* resp) {
-  ostringstream* output = resp->output;
-  *output << "<h3>Dashboards</h3>\n";
-  *output << "<table class='table table-striped'>\n";
-  *output << "  <thead><tr><th>Dashboard</th><th>Description</th></tr></thead>\n";
-  *output << "  <tbody\n";
-  *output << GetDashboardLine("scans", "Scans", "List of scanners that are currently running.");
-  *output << GetDashboardLine("transactions", "Transactions", "List of transactions that are "
-                                                              "currently running.");
-  *output << GetDashboardLine("maintenance-manager", "Maintenance Manager",
-                              "List of operations that are currently running and those "
-                              "that are registered.");
-  *output << "</tbody></table>\n";
-}
-
-string TabletServerPathHandlers::GetDashboardLine(const std::string& link,
-                                                  const std::string& text,
-                                                  const std::string& desc) {
-  return Substitute("  <tr><td><a href=\"$0\">$1</a></td><td>$2</td></tr>\n",
-                    EscapeForHtmlToString(link),
-                    EscapeForHtmlToString(text),
-                    EscapeForHtmlToString(desc));
-}
-
-void TabletServerPathHandlers::HandleMaintenanceManagerPage(const Webserver::WebRequest& req,
-                                                            Webserver::WebResponse* resp) {
-  EasyJson* output = resp->output;
-  MaintenanceManager* manager = tserver_->maintenance_manager();
-  MaintenanceManagerStatusPB pb;
-  manager->GetMaintenanceManagerStatusDump(&pb);
-  if (ContainsKey(req.parsed_args, "raw")) {
-    (*output)["raw"] = SecureDebugString(pb);
-    return;
-  }
-
-  EasyJson running_ops = output->Set("running_operations", EasyJson::kArray);
-  for (const auto& op_pb : pb.registered_operations()) {
-    if (op_pb.running() > 0) {
-      EasyJson running_op = running_ops.PushBack(EasyJson::kObject);
-      running_op["name"] = op_pb.name();
-      running_op["instances_running"] = op_pb.running();
-    }
-  }
-
-  EasyJson completed_ops = output->Set("completed_operations", EasyJson::kArray);
-  for (const auto& op_pb : pb.completed_operations()) {
-    EasyJson completed_op = completed_ops.PushBack(EasyJson::kObject);
-    completed_op["name"] = op_pb.name();
-    completed_op["duration"] =
-      HumanReadableElapsedTime::ToShortString(op_pb.duration_millis() / 1000.0);
-    completed_op["time_since_start"] =
-      HumanReadableElapsedTime::ToShortString(op_pb.millis_since_start() / 1000.0);
-  }
-
-  EasyJson registered_ops = output->Set("registered_operations", EasyJson::kArray);
-  for (const auto& op_pb : pb.registered_operations()) {
-    EasyJson registered_op = registered_ops.PushBack(EasyJson::kObject);
-    registered_op["name"] = op_pb.name();
-    registered_op["runnable"] = op_pb.runnable();
-    registered_op["ram_anchored"] = HumanReadableNumBytes::ToString(op_pb.ram_anchored_bytes());
-    registered_op["logs_retained"] = HumanReadableNumBytes::ToString(op_pb.logs_retained_bytes());
-    registered_op["perf"] = op_pb.perf_improvement();
-  }
-}
-
-} // namespace tserver
-} // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/tserver/tserver-path-handlers.h
----------------------------------------------------------------------
diff --git a/src/kudu/tserver/tserver-path-handlers.h b/src/kudu/tserver/tserver-path-handlers.h
deleted file mode 100644
index 8c00a6f..0000000
--- a/src/kudu/tserver/tserver-path-handlers.h
+++ /dev/null
@@ -1,84 +0,0 @@
-// 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.
-#ifndef KUDU_TSERVER_TSERVER_PATH_HANDLERS_H
-#define KUDU_TSERVER_TSERVER_PATH_HANDLERS_H
-
-#include <string>
-#include <vector>
-
-#include "kudu/gutil/macros.h"
-#include "kudu/server/webserver.h"
-#include "kudu/util/status.h"
-
-namespace kudu {
-
-class Schema;
-struct IteratorStats;
-
-namespace consensus {
-class ConsensusStatePB;
-} // namespace consensus
-
-namespace tserver {
-
-class TabletServer;
-class Scanner;
-
-class TabletServerPathHandlers {
- public:
-  explicit TabletServerPathHandlers(TabletServer* tserver)
-    : tserver_(tserver) {
-  }
-
-  ~TabletServerPathHandlers();
-
-  Status Register(Webserver* server);
-
- private:
-  void HandleScansPage(const Webserver::WebRequest& req,
-                       Webserver::PrerenderedWebResponse* resp);
-  void HandleTabletsPage(const Webserver::WebRequest& req,
-                         Webserver::PrerenderedWebResponse* resp);
-  void HandleTabletPage(const Webserver::WebRequest& req,
-                        Webserver::PrerenderedWebResponse* resp);
-  void HandleTransactionsPage(const Webserver::WebRequest& req,
-                              Webserver::PrerenderedWebResponse* resp);
-  void HandleTabletSVGPage(const Webserver::WebRequest& req,
-                           Webserver::PrerenderedWebResponse* resp);
-  void HandleLogAnchorsPage(const Webserver::WebRequest& req,
-                            Webserver::PrerenderedWebResponse* resp);
-  void HandleConsensusStatusPage(const Webserver::WebRequest& req,
-                                 Webserver::PrerenderedWebResponse* resp);
-  void HandleDashboardsPage(const Webserver::WebRequest& req,
-                            Webserver::PrerenderedWebResponse* resp);
-  void HandleMaintenanceManagerPage(const Webserver::WebRequest& req,
-                                    Webserver::WebResponse* resp);
-  std::string ConsensusStatePBToHtml(const consensus::ConsensusStatePB& cstate) const;
-  std::string ScannerToHtml(const Scanner& scanner) const;
-  std::string IteratorStatsToHtml(const Schema& projection,
-                                  const std::vector<IteratorStats>& stats) const;
-  std::string GetDashboardLine(const std::string& link,
-                               const std::string& text, const std::string& desc);
-
-  TabletServer* tserver_;
-
-  DISALLOW_COPY_AND_ASSIGN(TabletServerPathHandlers);
-};
-
-} // namespace tserver
-} // namespace kudu
-#endif /* KUDU_TSERVER_TSERVER_PATH_HANDLERS_H */

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/tserver/tserver_path_handlers.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tserver/tserver_path_handlers.cc b/src/kudu/tserver/tserver_path_handlers.cc
new file mode 100644
index 0000000..c982d41
--- /dev/null
+++ b/src/kudu/tserver/tserver_path_handlers.cc
@@ -0,0 +1,677 @@
+// 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.
+
+#include "kudu/tserver/tserver_path_handlers.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <iosfwd>
+#include <map>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <type_traits>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include <boost/bind.hpp> // IWYU pragma: keep
+#include <glog/logging.h>
+
+#include "kudu/common/column_predicate.h"
+#include "kudu/common/common.pb.h"
+#include "kudu/common/encoded_key.h"
+#include "kudu/common/iterator.h"
+#include "kudu/common/iterator_stats.h"
+#include "kudu/common/partition.h"
+#include "kudu/common/scan_spec.h"
+#include "kudu/common/schema.h"
+#include "kudu/common/wire_protocol.pb.h"
+#include "kudu/consensus/consensus.pb.h"
+#include "kudu/consensus/log_anchor_registry.h"
+#include "kudu/consensus/metadata.pb.h"
+#include "kudu/consensus/opid.pb.h"
+#include "kudu/consensus/quorum_util.h"
+#include "kudu/consensus/raft_consensus.h"
+#include "kudu/gutil/map-util.h"
+#include "kudu/gutil/ref_counted.h"
+#include "kudu/gutil/stringprintf.h"
+#include "kudu/gutil/strings/human_readable.h"
+#include "kudu/gutil/strings/join.h"
+#include "kudu/gutil/strings/numbers.h"
+#include "kudu/gutil/strings/substitute.h"
+#include "kudu/server/webui_util.h"
+#include "kudu/tablet/metadata.pb.h"
+#include "kudu/tablet/tablet.h"
+#include "kudu/tablet/tablet.pb.h"
+#include "kudu/tablet/tablet_metadata.h"
+#include "kudu/tablet/tablet_replica.h"
+#include "kudu/tablet/transactions/transaction.h"
+#include "kudu/tserver/scanners.h"
+#include "kudu/tserver/tablet_server.h"
+#include "kudu/tserver/ts_tablet_manager.h"
+#include "kudu/util/easy_json.h"
+#include "kudu/util/maintenance_manager.h"
+#include "kudu/util/maintenance_manager.pb.h"
+#include "kudu/util/mem_tracker.h"
+#include "kudu/util/monotime.h"
+#include "kudu/util/pb_util.h"
+#include "kudu/util/url-coding.h"
+#include "kudu/util/web_callback_registry.h"
+
+using kudu::consensus::GetConsensusRole;
+using kudu::consensus::ConsensusStatePB;
+using kudu::consensus::RaftPeerPB;
+using kudu::consensus::TransactionStatusPB;
+using kudu::MaintenanceManagerStatusPB;
+using kudu::pb_util::SecureDebugString;
+using kudu::pb_util::SecureShortDebugString;
+using kudu::tablet::Tablet;
+using kudu::tablet::TabletReplica;
+using kudu::tablet::TabletStatePB;
+using kudu::tablet::TabletStatusPB;
+using kudu::tablet::Transaction;
+using std::endl;
+using std::map;
+using std::ostringstream;
+using std::shared_ptr;
+using std::string;
+using std::vector;
+using strings::Substitute;
+
+namespace kudu {
+namespace tserver {
+
+TabletServerPathHandlers::~TabletServerPathHandlers() {
+}
+
+Status TabletServerPathHandlers::Register(Webserver* server) {
+  server->RegisterPrerenderedPathHandler(
+    "/scans", "Scans",
+    boost::bind(&TabletServerPathHandlers::HandleScansPage, this, _1, _2),
+    true /* styled */, false /* is_on_nav_bar */);
+  server->RegisterPrerenderedPathHandler(
+    "/tablets", "Tablets",
+    boost::bind(&TabletServerPathHandlers::HandleTabletsPage, this, _1, _2),
+    true /* styled */, true /* is_on_nav_bar */);
+  server->RegisterPrerenderedPathHandler(
+    "/tablet", "",
+    boost::bind(&TabletServerPathHandlers::HandleTabletPage, this, _1, _2),
+    true /* styled */, false /* is_on_nav_bar */);
+  server->RegisterPrerenderedPathHandler(
+    "/transactions", "",
+    boost::bind(&TabletServerPathHandlers::HandleTransactionsPage, this, _1, _2),
+    true /* styled */, false /* is_on_nav_bar */);
+  server->RegisterPrerenderedPathHandler(
+    "/tablet-rowsetlayout-svg", "",
+    boost::bind(&TabletServerPathHandlers::HandleTabletSVGPage, this, _1, _2),
+    true /* styled */, false /* is_on_nav_bar */);
+  server->RegisterPrerenderedPathHandler(
+    "/tablet-consensus-status", "",
+    boost::bind(&TabletServerPathHandlers::HandleConsensusStatusPage, this, _1, _2),
+    true /* styled */, false /* is_on_nav_bar */);
+  server->RegisterPrerenderedPathHandler(
+    "/log-anchors", "",
+    boost::bind(&TabletServerPathHandlers::HandleLogAnchorsPage, this, _1, _2),
+    true /* styled */, false /* is_on_nav_bar */);
+  server->RegisterPrerenderedPathHandler(
+    "/dashboards", "Dashboards",
+    boost::bind(&TabletServerPathHandlers::HandleDashboardsPage, this, _1, _2),
+    true /* styled */, true /* is_on_nav_bar */);
+  server->RegisterPathHandler(
+    "/maintenance-manager", "",
+    boost::bind(&TabletServerPathHandlers::HandleMaintenanceManagerPage, this, _1, _2),
+    true /* styled */, false /* is_on_nav_bar */);
+
+  return Status::OK();
+}
+
+void TabletServerPathHandlers::HandleTransactionsPage(const Webserver::WebRequest& req,
+                                                      Webserver::PrerenderedWebResponse* resp) {
+  ostringstream* output = resp->output;
+  bool as_text = ContainsKey(req.parsed_args, "raw");
+
+  vector<scoped_refptr<TabletReplica> > replicas;
+  tserver_->tablet_manager()->GetTabletReplicas(&replicas);
+
+  string arg = FindWithDefault(req.parsed_args, "include_traces", "false");
+  Transaction::TraceType trace_type = ParseLeadingBoolValue(
+      arg.c_str(), false) ? Transaction::TRACE_TXNS : Transaction::NO_TRACE_TXNS;
+
+  if (!as_text) {
+    *output << "<h1>Transactions</h1>\n";
+    *output << "<table class='table table-striped'>\n";
+    *output << "   <thead><tr><th>Tablet id</th><th>Op Id</th>"
+      "<th>Transaction Type</th><th>"
+      "Total time in-flight</th><th>Description</th></tr></thead>\n";
+    *output << "<tbody>\n";
+  }
+
+  for (const scoped_refptr<TabletReplica>& replica : replicas) {
+    vector<TransactionStatusPB> inflight;
+
+    if (replica->tablet() == nullptr) {
+      continue;
+    }
+
+    replica->GetInFlightTransactions(trace_type, &inflight);
+    for (const TransactionStatusPB& inflight_tx : inflight) {
+      string total_time_str = Substitute("$0 us.", inflight_tx.running_for_micros());
+      string description;
+      if (trace_type == Transaction::TRACE_TXNS) {
+        description = Substitute("$0, Trace: $1",
+                                  inflight_tx.description(), inflight_tx.trace_buffer());
+      } else {
+        description = inflight_tx.description();
+      }
+
+      if (!as_text) {
+        *output << Substitute(
+          "<tr><th>$0</th><th>$1</th><th>$2</th><th>$3</th><th>$4</th></tr>\n",
+          EscapeForHtmlToString(replica->tablet_id()),
+          EscapeForHtmlToString(SecureShortDebugString(inflight_tx.op_id())),
+          OperationType_Name(inflight_tx.tx_type()),
+          total_time_str,
+          EscapeForHtmlToString(description));
+      } else {
+        *output << "Tablet: " << replica->tablet_id() << endl;
+        *output << "Op ID: " << SecureShortDebugString(inflight_tx.op_id()) << endl;
+        *output << "Type: " << OperationType_Name(inflight_tx.tx_type()) << endl;
+        *output << "Running: " << total_time_str;
+        *output << description << endl;
+        *output << endl;
+      }
+    }
+  }
+
+  if (!as_text) {
+    *output << "</tbody></table>\n";
+  }
+}
+
+namespace {
+string TabletLink(const string& id) {
+  return Substitute("<a href=\"/tablet?id=$0\">$1</a>",
+                    UrlEncodeToString(id),
+                    EscapeForHtmlToString(id));
+}
+
+} // anonymous namespace
+
+void TabletServerPathHandlers::HandleTabletsPage(const Webserver::WebRequest& /*req*/,
+                                                 Webserver::PrerenderedWebResponse* resp) {
+  ostringstream* output = resp->output;
+  vector<scoped_refptr<TabletReplica>> replicas;
+  tserver_->tablet_manager()->GetTabletReplicas(&replicas);
+
+  // Sort by (table_name, tablet_id) tuples.
+  std::sort(replicas.begin(), replicas.end(),
+            [](const scoped_refptr<TabletReplica>& rep_a,
+               const scoped_refptr<TabletReplica>& rep_b) {
+              return std::make_pair(rep_a->tablet_metadata()->table_name(), rep_a->tablet_id()) <
+                     std::make_pair(rep_b->tablet_metadata()->table_name(), rep_b->tablet_id());
+            });
+
+  // For assigning ids to table divs;
+  int i = 0;
+  auto generate_table = [this, &i](const vector<scoped_refptr<TabletReplica>>& replicas,
+                                   std::ostream* output) {
+    i++;
+
+    *output << "<h4>Summary</h4>\n";
+    map<string, int> tablet_statuses;
+    for (const scoped_refptr<TabletReplica>& replica : replicas) {
+      tablet_statuses[TabletStatePB_Name(replica->state())]++;
+    }
+    *output << "<table class='table table-striped table-hover'>\n";
+    *output << "<thead><tr><th>Status</th><th>Count</th><th>Percentage</th></tr></thead>\n";
+    *output << "<tbody>\n";
+    for (const auto& entry : tablet_statuses) {
+      double percent = replicas.empty() ? 0 : (100.0 * entry.second) / replicas.size();
+      *output << Substitute("<tr><td>$0</td><td>$1</td><td>$2</td></tr>\n",
+                            entry.first,
+                            entry.second,
+                            StringPrintf("%.2f", percent));
+    }
+    *output << "</tbody>\n";
+    *output << Substitute("<tfoot><tr><td>Total</td><td>$0</td><td></td></tr></tfoot>\n",
+                          replicas.size());
+    *output << "</table>\n";
+
+    *output << "<h4>Detail</h4>";
+    *output << Substitute("<a href='#detail$0' data-toggle='collapse'>(toggle)</a>\n", i);
+    *output << Substitute("<div id='detail$0' class='collapse'>\n", i);
+    *output << "<table class='table table-striped table-hover'>\n";
+    *output << "<thead><tr><th>Table name</th><th>Tablet ID</th>"
+        "<th>Partition</th><th>State</th><th>Write buffer memory usage</th>"
+        "<th>On-disk size</th><th>RaftConfig</th><th>Last status</th></tr></thead>\n";
+    *output << "<tbody>\n";
+    for (const scoped_refptr<TabletReplica>& replica : replicas) {
+      TabletStatusPB status;
+      replica->GetTabletStatusPB(&status);
+      string id = status.tablet_id();
+      string table_name = status.table_name();
+      string tablet_id_or_link;
+      if (replica->tablet() != nullptr) {
+        tablet_id_or_link = TabletLink(id);
+      } else {
+        tablet_id_or_link = EscapeForHtmlToString(id);
+      }
+      string mem_bytes = "";
+      if (replica->tablet() != nullptr) {
+        mem_bytes = HumanReadableNumBytes::ToString(
+            replica->tablet()->mem_tracker()->consumption());
+      }
+      string n_bytes = "";
+      if (status.has_estimated_on_disk_size()) {
+        n_bytes = HumanReadableNumBytes::ToString(status.estimated_on_disk_size());
+      }
+      string partition = replica->tablet_metadata()
+                                ->partition_schema()
+                                 .PartitionDebugString(replica->tablet_metadata()->partition(),
+                                                       replica->tablet_metadata()->schema());
+
+      shared_ptr<consensus::RaftConsensus> consensus = replica->shared_consensus();
+      *output << Substitute(
+          // Table name, tablet id, partition
+          "<tr><td>$0</td><td>$1</td><td>$2</td>"
+          // State, on-disk size, consensus configuration, last status
+          "<td>$3</td><td>$4</td><td>$5</td><td>$6</td><td>$7</td></tr>\n",
+          EscapeForHtmlToString(table_name), // $0
+          tablet_id_or_link, // $1
+          EscapeForHtmlToString(partition), // $2
+          EscapeForHtmlToString(replica->HumanReadableState()), mem_bytes, n_bytes, // $3, $4, $5
+          consensus ? ConsensusStatePBToHtml(consensus->ConsensusState()) : "", // $6
+          EscapeForHtmlToString(status.last_status())); // $7
+    }
+    *output << "<tbody></table>\n</div>\n";
+  };
+
+  vector<scoped_refptr<TabletReplica>> live_replicas;
+  vector<scoped_refptr<TabletReplica>> tombstoned_replicas;
+  for (const scoped_refptr<TabletReplica>& replica : replicas) {
+    if (replica->HumanReadableState() != "TABLET_DATA_TOMBSTONED") {
+      live_replicas.push_back(replica);
+    } else {
+      tombstoned_replicas.push_back(replica);
+    }
+  }
+
+  if (!live_replicas.empty()) {
+    *output << "<h3>Live Tablets</h3>\n";
+    generate_table(live_replicas, output);
+  }
+  if (!tombstoned_replicas.empty()) {
+    *output << "<h3>Tombstoned Tablets</h3>\n";
+    *output << "<p><small>Tombstoned tablets are tablets that previously "
+               "stored a replica on this server.</small></p>";
+    generate_table(tombstoned_replicas, output);
+  }
+}
+
+namespace {
+
+bool CompareByMemberType(const RaftPeerPB& a, const RaftPeerPB& b) {
+  if (!a.has_member_type()) return false;
+  if (!b.has_member_type()) return true;
+  return a.member_type() < b.member_type();
+}
+
+} // anonymous namespace
+
+string TabletServerPathHandlers::ConsensusStatePBToHtml(const ConsensusStatePB& cstate) const {
+  ostringstream html;
+
+  html << "<ul>\n";
+  std::vector<RaftPeerPB> sorted_peers;
+  sorted_peers.assign(cstate.committed_config().peers().begin(),
+                      cstate.committed_config().peers().end());
+  std::sort(sorted_peers.begin(), sorted_peers.end(), &CompareByMemberType);
+  for (const RaftPeerPB& peer : sorted_peers) {
+    string peer_addr_or_uuid =
+        peer.has_last_known_addr() ? Substitute("$0:$1",
+                                                peer.last_known_addr().host(),
+                                                peer.last_known_addr().port())
+                                   : peer.permanent_uuid();
+    peer_addr_or_uuid = EscapeForHtmlToString(peer_addr_or_uuid);
+    string role_name = RaftPeerPB::Role_Name(GetConsensusRole(peer.permanent_uuid(), cstate));
+    string formatted = Substitute("$0: $1", role_name, peer_addr_or_uuid);
+    // Make the local peer bold.
+    if (peer.permanent_uuid() == tserver_->instance_pb().permanent_uuid()) {
+      formatted = Substitute("<b>$0</b>", formatted);
+    }
+
+    html << Substitute(" <li>$0</li>\n", formatted);
+  }
+  html << "</ul>\n";
+  return html.str();
+}
+
+namespace {
+
+bool GetTabletID(const Webserver::WebRequest& req,
+                 string* id,
+                 Webserver::PrerenderedWebResponse* resp) {
+  if (!FindCopy(req.parsed_args, "id", id)) {
+    resp->status_code = HttpStatusCode::BadRequest;
+    *resp->output << "Tablet missing 'id' argument";
+    return false;
+  }
+  return true;
+}
+
+bool GetTabletReplica(TabletServer* tserver, const Webserver::WebRequest& /*req*/,
+                      scoped_refptr<TabletReplica>* replica, const string& tablet_id,
+                      Webserver::PrerenderedWebResponse* resp) {
+  if (!tserver->tablet_manager()->LookupTablet(tablet_id, replica)) {
+    resp->status_code = HttpStatusCode::NotFound;
+    *resp->output << "Tablet " << EscapeForHtmlToString(tablet_id) << " not found";
+    return false;
+  }
+  return true;
+}
+
+bool TabletBootstrapping(const scoped_refptr<TabletReplica>& replica, const string& tablet_id,
+                         Webserver::PrerenderedWebResponse* resp) {
+  if (replica->state() == tablet::BOOTSTRAPPING) {
+    resp->status_code = HttpStatusCode::ServiceUnavailable;
+    *resp->output << "Tablet " << EscapeForHtmlToString(tablet_id) << " is still bootstrapping";
+    return true;
+  }
+  return false;
+}
+
+// Returns true if the tablet_id was properly specified, the
+// tablet is found, and is in a non-bootstrapping state.
+bool LoadTablet(TabletServer* tserver,
+                const Webserver::WebRequest& req,
+                string* tablet_id, scoped_refptr<TabletReplica>* replica,
+                Webserver::PrerenderedWebResponse* resp) {
+  return GetTabletID(req, tablet_id, resp) &&
+      GetTabletReplica(tserver, req, replica, *tablet_id, resp) &&
+      !TabletBootstrapping(*replica, *tablet_id, resp);
+}
+
+} // anonymous namespace
+
+void TabletServerPathHandlers::HandleTabletPage(const Webserver::WebRequest& req,
+                                                Webserver::PrerenderedWebResponse* resp) {
+  ostringstream* output = resp->output;
+  string tablet_id;
+  scoped_refptr<TabletReplica> replica;
+  if (!LoadTablet(tserver_, req, &tablet_id, &replica, resp)) return;
+
+  string table_name = replica->tablet_metadata()->table_name();
+  RaftPeerPB::Role role = RaftPeerPB::UNKNOWN_ROLE;
+  auto consensus = replica->consensus();
+  if (consensus) {
+    role = consensus->role();
+  }
+
+  *output << "<h1>Tablet " << EscapeForHtmlToString(tablet_id)
+          << " (" << replica->HumanReadableState()
+          << "/" << RaftPeerPB::Role_Name(role) << ")</h1>\n";
+  *output << "<h3>Table " << EscapeForHtmlToString(table_name) << "</h3>";
+
+  // Output schema in tabular format.
+  *output << "<h2>Schema</h2>\n";
+  const Schema& schema = replica->tablet_metadata()->schema();
+  HtmlOutputSchemaTable(schema, output);
+
+  *output << "<h2>Other Tablet Info Pages</h2>" << endl;
+
+  // List of links to various tablet-specific info pages
+  *output << "<ul>";
+
+  // Link to output svg of current DiskRowSet layout over keyspace.
+  *output << "<li>" << Substitute("<a href=\"/tablet-rowsetlayout-svg?id=$0\">$1</a>",
+                                  UrlEncodeToString(tablet_id),
+                                  "Rowset Layout Diagram")
+          << "</li>" << endl;
+
+  // Link to consensus status page.
+  *output << "<li>" << Substitute("<a href=\"/tablet-consensus-status?id=$0\">$1</a>",
+                                  UrlEncodeToString(tablet_id),
+                                  "Consensus Status")
+          << "</li>" << endl;
+
+  // Log anchors info page.
+  *output << "<li>" << Substitute("<a href=\"/log-anchors?id=$0\">$1</a>",
+                                  UrlEncodeToString(tablet_id),
+                                  "Tablet Log Anchors")
+          << "</li>" << endl;
+
+  // End list
+  *output << "</ul>\n";
+}
+
+void TabletServerPathHandlers::HandleTabletSVGPage(const Webserver::WebRequest& req,
+                                                   Webserver::PrerenderedWebResponse* resp) {
+  ostringstream* output = resp->output;
+  string id;
+  scoped_refptr<TabletReplica> replica;
+  if (!LoadTablet(tserver_, req, &id, &replica, resp)) return;
+  shared_ptr<Tablet> tablet = replica->shared_tablet();
+  if (!tablet) {
+    *output << "Tablet " << EscapeForHtmlToString(id) << " not running";
+    return;
+  }
+
+  *output << "<h1>Rowset Layout Diagram for Tablet "
+          << TabletLink(id) << "</h1>\n";
+  tablet->PrintRSLayout(output);
+
+}
+
+void TabletServerPathHandlers::HandleLogAnchorsPage(const Webserver::WebRequest& req,
+                                                    Webserver::PrerenderedWebResponse* resp) {
+  ostringstream* output = resp->output;
+  string tablet_id;
+  scoped_refptr<TabletReplica> replica;
+  if (!LoadTablet(tserver_, req, &tablet_id, &replica, resp)) return;
+
+  *output << "<h1>Log Anchors for Tablet " << EscapeForHtmlToString(tablet_id) << "</h1>"
+          << std::endl;
+
+  string dump = replica->log_anchor_registry()->DumpAnchorInfo();
+  *output << "<pre>" << EscapeForHtmlToString(dump) << "</pre>" << std::endl;
+}
+
+void TabletServerPathHandlers::HandleConsensusStatusPage(const Webserver::WebRequest& req,
+                                                         Webserver::PrerenderedWebResponse* resp) {
+  ostringstream* output = resp->output;
+  string id;
+  scoped_refptr<TabletReplica> replica;
+  if (!LoadTablet(tserver_, req, &id, &replica, resp)) return;
+  shared_ptr<consensus::RaftConsensus> consensus = replica->shared_consensus();
+  if (!consensus) {
+    *output << "Tablet " << EscapeForHtmlToString(id) << " not initialized";
+    return;
+  }
+  consensus->DumpStatusHtml(*output);
+}
+
+void TabletServerPathHandlers::HandleScansPage(const Webserver::WebRequest& /*req*/,
+                                               Webserver::PrerenderedWebResponse* resp) {
+  std::ostringstream* output = resp->output;
+  *output << "<h1>Scans</h1>\n";
+  *output << "<table class='table table-striped'>\n";
+  *output << "<thead><tr><th>Tablet id</th><th>Scanner id</th><th>Total time in-flight</th>"
+      "<th>Time since last update</th><th>Requestor</th><th>Iterator Stats</th>"
+      "<th>Pushed down key predicates</th><th>Other predicates</th></tr></thead>\n";
+  *output << "<tbody>\n";
+
+  vector<SharedScanner> scanners;
+  tserver_->scanner_manager()->ListScanners(&scanners);
+  for (const SharedScanner& scanner : scanners) {
+    *output << ScannerToHtml(*scanner);
+  }
+  *output << "</tbody></table>";
+}
+
+string TabletServerPathHandlers::ScannerToHtml(const Scanner& scanner) const {
+  std::ostringstream html;
+  uint64_t time_in_flight_us =
+      (MonoTime::Now() - scanner.start_time()).ToMicroseconds();
+  uint64_t time_since_last_access_us =
+      scanner.TimeSinceLastAccess(MonoTime::Now()).ToMicroseconds();
+
+  html << Substitute("<tr><td>$0</td><td>$1</td><td>$2 us.</td><td>$3 us.</td><td>$4</td>",
+                     EscapeForHtmlToString(scanner.tablet_id()), // $0
+                     EscapeForHtmlToString(scanner.id()), // $1
+                     time_in_flight_us, time_since_last_access_us, // $2, $3
+                     EscapeForHtmlToString(scanner.requestor_string())); // $4
+
+
+  if (!scanner.IsInitialized()) {
+    html << "<td colspan=\"3\">&lt;not yet initialized&gt;</td></tr>";
+    return html.str();
+  }
+
+  const Schema* projection = &scanner.iter()->schema();
+
+  vector<IteratorStats> stats;
+  scanner.GetIteratorStats(&stats);
+  CHECK_EQ(stats.size(), projection->num_columns());
+  html << Substitute("<td>$0</td>", IteratorStatsToHtml(*projection, stats));
+  scoped_refptr<TabletReplica> tablet_replica;
+  if (!tserver_->tablet_manager()->LookupTablet(scanner.tablet_id(), &tablet_replica)) {
+    html << Substitute("<td colspan=\"2\"><b>Tablet $0 is no longer valid.</b></td></tr>\n",
+                       scanner.tablet_id());
+  } else {
+    string range_pred_str;
+    vector<string> other_preds;
+    const ScanSpec& spec = scanner.spec();
+    if (spec.lower_bound_key() || spec.exclusive_upper_bound_key()) {
+      range_pred_str = EncodedKey::RangeToString(spec.lower_bound_key(),
+                                                 spec.exclusive_upper_bound_key());
+    }
+    for (const auto& col_pred : scanner.spec().predicates()) {
+      int32_t col_idx = projection->find_column(col_pred.first);
+      if (col_idx == Schema::kColumnNotFound) {
+        other_preds.emplace_back("unknown column");
+      } else {
+        other_preds.push_back(col_pred.second.ToString());
+      }
+    }
+    string other_pred_str = JoinStrings(other_preds, "\n");
+    html << Substitute("<td>$0</td><td>$1</td></tr>\n",
+                       EscapeForHtmlToString(range_pred_str),
+                       EscapeForHtmlToString(other_pred_str));
+  }
+  return html.str();
+}
+
+string TabletServerPathHandlers::IteratorStatsToHtml(const Schema& projection,
+                                                     const vector<IteratorStats>& stats) const {
+  std::ostringstream html;
+  html << "<table>\n";
+  html << "<tr><th>Column</th>"
+       << "<th>Blocks read from disk</th>"
+       << "<th>Bytes read from disk</th>"
+       << "<th>Cells read from disk</th>"
+       << "</tr>\n";
+  for (size_t idx = 0; idx < stats.size(); idx++) {
+    // We use 'title' attributes so that if the user hovers over the value, they get a
+    // human-readable tooltip.
+    html << Substitute("<tr>"
+                       "<td>$0</td>"
+                       "<td title=\"$1\">$2</td>"
+                       "<td title=\"$3\">$4</td>"
+                       "<td title=\"$5\">$6</td>"
+                       "</tr>\n",
+                       EscapeForHtmlToString(projection.column(idx).name()), // $0
+                       HumanReadableInt::ToString(stats[idx].data_blocks_read_from_disk), // $1
+                       stats[idx].data_blocks_read_from_disk, // $2
+                       HumanReadableNumBytes::ToString(stats[idx].bytes_read_from_disk), // $3
+                       stats[idx].bytes_read_from_disk, // $4
+                       HumanReadableInt::ToString(stats[idx].cells_read_from_disk), // $5
+                       stats[idx].cells_read_from_disk); // $6
+  }
+  html << "</table>\n";
+  return html.str();
+}
+
+void TabletServerPathHandlers::HandleDashboardsPage(const Webserver::WebRequest& /*req*/,
+                                                    Webserver::PrerenderedWebResponse* resp) {
+  ostringstream* output = resp->output;
+  *output << "<h3>Dashboards</h3>\n";
+  *output << "<table class='table table-striped'>\n";
+  *output << "  <thead><tr><th>Dashboard</th><th>Description</th></tr></thead>\n";
+  *output << "  <tbody\n";
+  *output << GetDashboardLine("scans", "Scans", "List of scanners that are currently running.");
+  *output << GetDashboardLine("transactions", "Transactions", "List of transactions that are "
+                                                              "currently running.");
+  *output << GetDashboardLine("maintenance-manager", "Maintenance Manager",
+                              "List of operations that are currently running and those "
+                              "that are registered.");
+  *output << "</tbody></table>\n";
+}
+
+string TabletServerPathHandlers::GetDashboardLine(const std::string& link,
+                                                  const std::string& text,
+                                                  const std::string& desc) {
+  return Substitute("  <tr><td><a href=\"$0\">$1</a></td><td>$2</td></tr>\n",
+                    EscapeForHtmlToString(link),
+                    EscapeForHtmlToString(text),
+                    EscapeForHtmlToString(desc));
+}
+
+void TabletServerPathHandlers::HandleMaintenanceManagerPage(const Webserver::WebRequest& req,
+                                                            Webserver::WebResponse* resp) {
+  EasyJson* output = resp->output;
+  MaintenanceManager* manager = tserver_->maintenance_manager();
+  MaintenanceManagerStatusPB pb;
+  manager->GetMaintenanceManagerStatusDump(&pb);
+  if (ContainsKey(req.parsed_args, "raw")) {
+    (*output)["raw"] = SecureDebugString(pb);
+    return;
+  }
+
+  EasyJson running_ops = output->Set("running_operations", EasyJson::kArray);
+  for (const auto& op_pb : pb.registered_operations()) {
+    if (op_pb.running() > 0) {
+      EasyJson running_op = running_ops.PushBack(EasyJson::kObject);
+      running_op["name"] = op_pb.name();
+      running_op["instances_running"] = op_pb.running();
+    }
+  }
+
+  EasyJson completed_ops = output->Set("completed_operations", EasyJson::kArray);
+  for (const auto& op_pb : pb.completed_operations()) {
+    EasyJson completed_op = completed_ops.PushBack(EasyJson::kObject);
+    completed_op["name"] = op_pb.name();
+    completed_op["duration"] =
+      HumanReadableElapsedTime::ToShortString(op_pb.duration_millis() / 1000.0);
+    completed_op["time_since_start"] =
+      HumanReadableElapsedTime::ToShortString(op_pb.millis_since_start() / 1000.0);
+  }
+
+  EasyJson registered_ops = output->Set("registered_operations", EasyJson::kArray);
+  for (const auto& op_pb : pb.registered_operations()) {
+    EasyJson registered_op = registered_ops.PushBack(EasyJson::kObject);
+    registered_op["name"] = op_pb.name();
+    registered_op["runnable"] = op_pb.runnable();
+    registered_op["ram_anchored"] = HumanReadableNumBytes::ToString(op_pb.ram_anchored_bytes());
+    registered_op["logs_retained"] = HumanReadableNumBytes::ToString(op_pb.logs_retained_bytes());
+    registered_op["perf"] = op_pb.perf_improvement();
+  }
+}
+
+} // namespace tserver
+} // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/tserver/tserver_path_handlers.h
----------------------------------------------------------------------
diff --git a/src/kudu/tserver/tserver_path_handlers.h b/src/kudu/tserver/tserver_path_handlers.h
new file mode 100644
index 0000000..8c00a6f
--- /dev/null
+++ b/src/kudu/tserver/tserver_path_handlers.h
@@ -0,0 +1,84 @@
+// 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.
+#ifndef KUDU_TSERVER_TSERVER_PATH_HANDLERS_H
+#define KUDU_TSERVER_TSERVER_PATH_HANDLERS_H
+
+#include <string>
+#include <vector>
+
+#include "kudu/gutil/macros.h"
+#include "kudu/server/webserver.h"
+#include "kudu/util/status.h"
+
+namespace kudu {
+
+class Schema;
+struct IteratorStats;
+
+namespace consensus {
+class ConsensusStatePB;
+} // namespace consensus
+
+namespace tserver {
+
+class TabletServer;
+class Scanner;
+
+class TabletServerPathHandlers {
+ public:
+  explicit TabletServerPathHandlers(TabletServer* tserver)
+    : tserver_(tserver) {
+  }
+
+  ~TabletServerPathHandlers();
+
+  Status Register(Webserver* server);
+
+ private:
+  void HandleScansPage(const Webserver::WebRequest& req,
+                       Webserver::PrerenderedWebResponse* resp);
+  void HandleTabletsPage(const Webserver::WebRequest& req,
+                         Webserver::PrerenderedWebResponse* resp);
+  void HandleTabletPage(const Webserver::WebRequest& req,
+                        Webserver::PrerenderedWebResponse* resp);
+  void HandleTransactionsPage(const Webserver::WebRequest& req,
+                              Webserver::PrerenderedWebResponse* resp);
+  void HandleTabletSVGPage(const Webserver::WebRequest& req,
+                           Webserver::PrerenderedWebResponse* resp);
+  void HandleLogAnchorsPage(const Webserver::WebRequest& req,
+                            Webserver::PrerenderedWebResponse* resp);
+  void HandleConsensusStatusPage(const Webserver::WebRequest& req,
+                                 Webserver::PrerenderedWebResponse* resp);
+  void HandleDashboardsPage(const Webserver::WebRequest& req,
+                            Webserver::PrerenderedWebResponse* resp);
+  void HandleMaintenanceManagerPage(const Webserver::WebRequest& req,
+                                    Webserver::WebResponse* resp);
+  std::string ConsensusStatePBToHtml(const consensus::ConsensusStatePB& cstate) const;
+  std::string ScannerToHtml(const Scanner& scanner) const;
+  std::string IteratorStatsToHtml(const Schema& projection,
+                                  const std::vector<IteratorStats>& stats) const;
+  std::string GetDashboardLine(const std::string& link,
+                               const std::string& text, const std::string& desc);
+
+  TabletServer* tserver_;
+
+  DISALLOW_COPY_AND_ASSIGN(TabletServerPathHandlers);
+};
+
+} // namespace tserver
+} // namespace kudu
+#endif /* KUDU_TSERVER_TSERVER_PATH_HANDLERS_H */


[3/3] kudu git commit: Rename *-path-handlers.* to *_path_handlers.*

Posted by wd...@apache.org.
Rename *-path-handlers.* to *_path_handlers.*

Our naming convention separates words with _'s, except for test files
which are often named {name of file whose content is tested}-test.cc.
This patch brings the various path handler files in line with the
convention.

Change-Id: Id7763d69ad484cf45cca6d77badd565843df2dd8
Reviewed-on: http://gerrit.cloudera.org:8080/8301
Reviewed-by: Adar Dembo <ad...@cloudera.com>
Tested-by: Kudu Jenkins


Project: http://git-wip-us.apache.org/repos/asf/kudu/repo
Commit: http://git-wip-us.apache.org/repos/asf/kudu/commit/1c90b2f5
Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/1c90b2f5
Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/1c90b2f5

Branch: refs/heads/master
Commit: 1c90b2f5c4e998d1db773ff2a2e67d8d93edfeea
Parents: abbfa49
Author: Will Berkeley <wd...@apache.org>
Authored: Tue Oct 17 10:38:47 2017 -0700
Committer: Will Berkeley <wd...@gmail.com>
Committed: Tue Oct 17 22:14:57 2017 +0000

----------------------------------------------------------------------
 src/kudu/master/CMakeLists.txt            |   2 +-
 src/kudu/master/master-path-handlers.cc   | 698 -------------------------
 src/kudu/master/master-path-handlers.h    |  82 ---
 src/kudu/master/master.cc                 |   2 +-
 src/kudu/master/master_path_handlers.cc   | 698 +++++++++++++++++++++++++
 src/kudu/master/master_path_handlers.h    |  82 +++
 src/kudu/server/CMakeLists.txt            |   6 +-
 src/kudu/server/default-path-handlers.cc  | 318 -----------
 src/kudu/server/default-path-handlers.h   |  35 --
 src/kudu/server/default_path_handlers.cc  | 319 +++++++++++
 src/kudu/server/default_path_handlers.h   |  35 ++
 src/kudu/server/pprof-path-handlers.cc    | 260 ---------
 src/kudu/server/pprof-path-handlers.h     |  27 -
 src/kudu/server/pprof_path_handlers.cc    | 260 +++++++++
 src/kudu/server/pprof_path_handlers.h     |  27 +
 src/kudu/server/server_base.cc            |   4 +-
 src/kudu/server/tracing-path-handlers.cc  | 287 ----------
 src/kudu/server/tracing-path-handlers.h   |  40 --
 src/kudu/server/tracing_path_handlers.cc  | 285 ++++++++++
 src/kudu/server/tracing_path_handlers.h   |  40 ++
 src/kudu/server/webserver-test.cc         |   2 +-
 src/kudu/tserver/CMakeLists.txt           |   2 +-
 src/kudu/tserver/tablet_server.cc         |   2 +-
 src/kudu/tserver/tserver-path-handlers.cc | 679 ------------------------
 src/kudu/tserver/tserver-path-handlers.h  |  84 ---
 src/kudu/tserver/tserver_path_handlers.cc | 677 ++++++++++++++++++++++++
 src/kudu/tserver/tserver_path_handlers.h  |  84 +++
 27 files changed, 2517 insertions(+), 2520 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/master/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/src/kudu/master/CMakeLists.txt b/src/kudu/master/CMakeLists.txt
index 53fbbde..5d5fe2c 100644
--- a/src/kudu/master/CMakeLists.txt
+++ b/src/kudu/master/CMakeLists.txt
@@ -34,10 +34,10 @@ ADD_EXPORTABLE_LIBRARY(master_proto
 
 set(MASTER_SRCS
   catalog_manager.cc
-  master-path-handlers.cc
   master.cc
   master_cert_authority.cc
   master_options.cc
+  master_path_handlers.cc
   master_service.cc
   mini_master.cc
   sys_catalog.cc

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/master/master-path-handlers.cc
----------------------------------------------------------------------
diff --git a/src/kudu/master/master-path-handlers.cc b/src/kudu/master/master-path-handlers.cc
deleted file mode 100644
index 397776b..0000000
--- a/src/kudu/master/master-path-handlers.cc
+++ /dev/null
@@ -1,698 +0,0 @@
-// 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.
-
-#include "kudu/master/master-path-handlers.h"
-
-#include <algorithm>
-#include <array>
-#include <cstdint>
-#include <iosfwd>
-#include <map>
-#include <memory>
-#include <sstream>
-#include <string>
-#include <type_traits>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include <boost/bind.hpp> // IWYU pragma: keep
-#include <glog/logging.h>
-
-#include "kudu/common/common.pb.h"
-#include "kudu/common/partition.h"
-#include "kudu/common/schema.h"
-#include "kudu/common/wire_protocol.h"
-#include "kudu/common/wire_protocol.pb.h"
-#include "kudu/consensus/metadata.pb.h"
-#include "kudu/consensus/quorum_util.h"
-#include "kudu/gutil/map-util.h"
-#include "kudu/gutil/port.h"
-#include "kudu/gutil/ref_counted.h"
-#include "kudu/gutil/stringprintf.h"
-#include "kudu/gutil/strings/join.h"
-#include "kudu/gutil/strings/numbers.h"
-#include "kudu/gutil/strings/substitute.h"
-#include "kudu/master/catalog_manager.h"
-#include "kudu/master/master.h"
-#include "kudu/master/master.pb.h"
-#include "kudu/master/master_options.h"
-#include "kudu/master/sys_catalog.h"
-#include "kudu/master/ts_descriptor.h"
-#include "kudu/master/ts_manager.h"
-#include "kudu/server/monitored_task.h"
-#include "kudu/server/webui_util.h"
-#include "kudu/util/cow_object.h"
-#include "kudu/util/easy_json.h"
-#include "kudu/util/jsonwriter.h"
-#include "kudu/util/monotime.h"
-#include "kudu/util/net/net_util.h"
-#include "kudu/util/net/sockaddr.h"
-#include "kudu/util/pb_util.h"
-#include "kudu/util/string_case.h"
-#include "kudu/util/url-coding.h"
-#include "kudu/util/web_callback_registry.h"
-
-namespace kudu {
-
-using consensus::ConsensusStatePB;
-using consensus::RaftPeerPB;
-using std::array;
-using std::map;
-using std::ostringstream;
-using std::pair;
-using std::shared_ptr;
-using std::string;
-using std::vector;
-using strings::Substitute;
-
-namespace master {
-
-MasterPathHandlers::~MasterPathHandlers() {
-}
-
-void MasterPathHandlers::HandleTabletServers(const Webserver::WebRequest& /*req*/,
-                                             Webserver::WebResponse* resp) {
-  EasyJson* output = resp->output;
-  vector<std::shared_ptr<TSDescriptor>> descs;
-  master_->ts_manager()->GetAllDescriptors(&descs);
-
-  (*output)["num_ts"] = std::to_string(descs.size());
-
-  // In mustache, when conditionally rendering a section of the template based
-  // on a key, the value becomes the context. In the subcontext, searches for
-  // keys used to display a value (e.g. {{my_key}}) recurse into the parent
-  // context-- but not when used to define a subsection (e.g. {{#my_list}}.
-  // Thus, we use a negative test {{^has_no_live_ts}} to decide if we
-  // should render the live and dead tablet server tables.
-  bool has_no_live_ts = true;
-  bool has_no_dead_ts = true;
-  output->Set("live_tservers", EasyJson::kArray);
-  output->Set("dead_tservers", EasyJson::kArray);
-  map<string, array<int, 2>> version_counts;
-  for (const std::shared_ptr<TSDescriptor>& desc : descs) {
-    string ts_key = desc->PresumedDead() ? "dead_tservers" : "live_tservers";
-    EasyJson ts_json = (*output)[ts_key].PushBack(EasyJson::kObject);
-
-    ServerRegistrationPB reg;
-    desc->GetRegistration(&reg);
-    ts_json["uuid"] = desc->permanent_uuid();
-    if (!reg.http_addresses().empty()) {
-      ts_json["target"] = Substitute("$0://$1:$2/",
-                                     reg.https_enabled() ? "https" : "http",
-                                     reg.http_addresses(0).host(),
-                                     reg.http_addresses(0).port());
-    }
-    ts_json["time_since_hb"] = StringPrintf("%.1fs", desc->TimeSinceHeartbeat().ToSeconds());
-    ts_json["registration"] = pb_util::SecureShortDebugString(reg);
-    version_counts[reg.software_version()][desc->PresumedDead() ? 1 : 0]++;
-    has_no_live_ts &= desc->PresumedDead();
-    has_no_dead_ts &= !desc->PresumedDead();
-  }
-  (*output)["has_no_live_ts"] = has_no_live_ts;
-  (*output)["has_no_dead_ts"] = has_no_dead_ts;
-
-  output->Set("version_counts", EasyJson::kArray);
-  for (const auto& entry : version_counts) {
-    EasyJson version_count_json = (*output)["version_counts"].PushBack(EasyJson::kObject);
-    version_count_json["version"] = entry.first;
-    version_count_json["live"] = Substitute("$0", entry.second[0]);
-    version_count_json["dead"] = Substitute("$0", entry.second[1]);
-  }
-}
-
-namespace {
-
-// Extracts the value of the 'redirects' parameter from 'req'; returns 0 if the
-// parameter doesn't exist or couldn't be parsed.
-int ExtractRedirectsFromRequest(const Webserver::WebRequest& req) {
-  string redirects_str;
-  int redirects = 0;
-  if (FindCopy(req.parsed_args, "redirects", &redirects_str)) {
-    if (!safe_strto32(redirects_str, &redirects)) {
-      return 0;
-    }
-  }
-  return redirects;
-}
-
-} // anonymous namespace
-
-void MasterPathHandlers::HandleCatalogManager(const Webserver::WebRequest& req,
-                                              Webserver::WebResponse* resp) {
-  EasyJson* output = resp->output;
-  CatalogManager::ScopedLeaderSharedLock l(master_->catalog_manager());
-  if (!l.catalog_status().ok()) {
-    (*output)["error"] = Substitute("Master is not ready: $0",  l.catalog_status().ToString());
-    return;
-  }
-  if (!l.leader_status().ok()) {
-    // Track redirects to prevent a redirect loop.
-    int redirects = ExtractRedirectsFromRequest(req);
-    SetupLeaderMasterRedirect("tables?", redirects, output);
-    return;
-  }
-
-  std::vector<scoped_refptr<TableInfo>> tables;
-  master_->catalog_manager()->GetAllTables(&tables);
-  int num_running_tables = 0;
-  EasyJson tables_json = output->Set("tables", EasyJson::kArray);
-  for (const scoped_refptr<TableInfo>& table : tables) {
-    TableMetadataLock l(table.get(), LockMode::READ);
-    if (!l.data().is_running()) {
-      continue;
-    }
-    num_running_tables++; // Table count excluding deleted ones
-    string state = SysTablesEntryPB_State_Name(l.data().pb.state());
-    Capitalize(&state);
-    EasyJson table_json = tables_json.PushBack(EasyJson::kObject);
-    table_json["name"] = EscapeForHtmlToString(l.data().name());
-    table_json["id"] = EscapeForHtmlToString(table->id());
-    table_json["state"] = state;
-    table_json["message"] = EscapeForHtmlToString(l.data().pb.state_msg());
-  }
-  (*output).Set<int64_t>("num_tables", num_running_tables);
-}
-
-namespace {
-
-
-// Holds info about a peer for use in the tablet detail table.
-struct TabletDetailPeerInfo {
-  string text;
-  string target;
-  string role;
-  bool is_leader;
-};
-
-int RoleToSortIndex(RaftPeerPB::Role r) {
-  switch (r) {
-    case RaftPeerPB::LEADER: return 0;
-    default: return 1 + static_cast<int>(r);
-  }
-}
-
-bool CompareByRole(const pair<TabletDetailPeerInfo, RaftPeerPB::Role>& a,
-                   const pair<TabletDetailPeerInfo, RaftPeerPB::Role>& b) {
-  return RoleToSortIndex(a.second) < RoleToSortIndex(b.second);
-}
-
-} // anonymous namespace
-
-
-void MasterPathHandlers::HandleTablePage(const Webserver::WebRequest& req,
-                                         Webserver::WebResponse* resp) {
-  EasyJson* output = resp->output;
-  // Parse argument.
-  string table_id;
-  if (!FindCopy(req.parsed_args, "id", &table_id)) {
-    resp->status_code = HttpStatusCode::BadRequest;
-    (*output)["error"] = "Missing 'id' argument";
-    return;
-  }
-
-  CatalogManager::ScopedLeaderSharedLock l(master_->catalog_manager());
-  if (!l.catalog_status().ok()) {
-    (*output)["error"] = Substitute("Master is not ready: $0", l.catalog_status().ToString());
-    return;
-  }
-  if (!l.leader_status().ok()) {
-    // It's possible to respond 307 Temporary Redirect and automatically redirect with
-    // a Location header, but this would likely confuse users about which master's web ui
-    // they are looking at. Instead, we show a link users can click to go to the leader master.
-    // We track redirects to prevent a redirect loop.
-    int redirects = ExtractRedirectsFromRequest(req);
-    SetupLeaderMasterRedirect(Substitute("table?id=$0", table_id), redirects, output);
-    return;
-  }
-
-  scoped_refptr<TableInfo> table;
-  Status s = master_->catalog_manager()->GetTableInfo(table_id, &table);
-  if (!s.ok()) {
-    resp->status_code = HttpStatusCode::ServiceUnavailable;
-    (*output)["error"] = Substitute("Master is not ready: $0", s.ToString());
-    return;
-  }
-
-  if (!table) {
-    resp->status_code = HttpStatusCode::NotFound;
-    (*output)["error"] = "Table not found";
-    return;
-  }
-
-  Schema schema;
-  PartitionSchema partition_schema;
-  vector<scoped_refptr<TabletInfo>> tablets;
-  {
-    TableMetadataLock l(table.get(), LockMode::READ);
-    (*output)["name"] = l.data().name();
-    (*output)["id"] = table_id;
-    (*output)["version"] = l.data().pb.version();
-
-    string state = SysTablesEntryPB_State_Name(l.data().pb.state());
-    Capitalize(&state);
-    (*output)["state"] = state;
-    string state_msg = l.data().pb.state_msg();
-    if (!state_msg.empty()) {
-      (*output)["state_msg"] = state_msg;
-    }
-
-    s = SchemaFromPB(l.data().pb.schema(), &schema);
-    if (!s.ok()) {
-      (*output)["error"] = Substitute("Unable to decode schema: $0", s.ToString());
-      return;
-    }
-    s = PartitionSchema::FromPB(l.data().pb.partition_schema(), schema, &partition_schema);
-    if (!s.ok()) {
-      (*output)["error"] =
-          Substitute("Unable to decode partition schema: $0", s.ToString());
-      return;
-    }
-    table->GetAllTablets(&tablets);
-  }
-
-  SchemaToJson(schema, output);
-
-  // We have to collate partition schema and tablet information in order to set
-  // up the partition schema, tablet summary, and tablet detail tables.
-  std::vector<string> range_partitions;
-  map<string, int> summary_states;
-  (*output)["detail_partition_schema_header"] = partition_schema.PartitionTableHeader(schema);
-  EasyJson tablets_detail_json = output->Set("tablets_detail", EasyJson::kArray);
-  for (const scoped_refptr<TabletInfo>& tablet : tablets) {
-    vector<pair<TabletDetailPeerInfo, RaftPeerPB::Role>> sorted_replicas;
-    TabletMetadataLock l(tablet.get(), LockMode::READ);
-
-    // Count states for tablet summary.
-    summary_states[SysTabletsEntryPB_State_Name(l.data().pb.state())]++;
-
-    // Collect details about each tablet replica.
-    if (l.data().pb.has_consensus_state()) {
-      const ConsensusStatePB& cstate = l.data().pb.consensus_state();
-      for (const auto& peer : cstate.committed_config().peers()) {
-        TabletDetailPeerInfo peer_info;
-        shared_ptr<TSDescriptor> ts_desc;
-        if (master_->ts_manager()->LookupTSByUUID(peer.permanent_uuid(), &ts_desc)) {
-          auto link_pair = TSDescToLinkPair(*ts_desc.get(), tablet->id());
-          peer_info.text = std::move(link_pair.first);
-          peer_info.target = std::move(link_pair.second);
-        } else {
-          peer_info.text = peer.permanent_uuid();
-        }
-        RaftPeerPB::Role role = GetConsensusRole(peer.permanent_uuid(), cstate);
-        peer_info.role = RaftPeerPB_Role_Name(role);
-        peer_info.is_leader = role == RaftPeerPB::LEADER;
-        sorted_replicas.emplace_back(std::make_pair(peer_info, role));
-      }
-    }
-    std::sort(sorted_replicas.begin(), sorted_replicas.end(), &CompareByRole);
-
-    // Generate a readable description of the partition of each tablet, used
-    // both for each tablet's details and the readable range partition schema.
-    Partition partition;
-    Partition::FromPB(l.data().pb.partition(), &partition);
-
-    // For each unique range partition, add a debug string to range_partitions.
-    // To ensure uniqueness, only use partitions whose hash buckets are all 0.
-    if (std::all_of(partition.hash_buckets().begin(),
-                    partition.hash_buckets().end(),
-                    [] (const int32_t& bucket) { return bucket == 0; })) {
-      range_partitions.emplace_back(
-          partition_schema.RangePartitionDebugString(partition.range_key_start(),
-                                                     partition.range_key_end(),
-                                                     schema));
-    }
-
-    // Combine the tablet details and partition info for each tablet.
-    EasyJson tablet_detail_json = tablets_detail_json.PushBack(EasyJson::kObject);
-    tablet_detail_json["id"] = tablet->id();
-    tablet_detail_json["partition_cols"] = partition_schema.PartitionTableEntry(schema, partition);
-    string state = SysTabletsEntryPB_State_Name(l.data().pb.state());
-    Capitalize(&state);
-    tablet_detail_json["state"] = state;
-    tablet_detail_json["state_msg"] = l.data().pb.state_msg();
-    EasyJson peers_json = tablet_detail_json.Set("peers", EasyJson::kArray);
-    for (const auto& e : sorted_replicas) {
-      EasyJson peer_json = peers_json.PushBack(EasyJson::kObject);
-      peer_json["text"] = e.first.text;
-      if (!e.first.target.empty()) {
-        peer_json["target"] = e.first.target;
-      }
-      peer_json["role"] = e.first.role;
-      peer_json["is_leader"] = e.first.is_leader;
-    }
-  }
-
-  (*output)["partition_schema"] = partition_schema.DisplayString(schema, range_partitions);
-
-  EasyJson summary_json = output->Set("tablets_summary", EasyJson::kArray);
-  for (const auto& entry : summary_states) {
-    EasyJson state_json = summary_json.PushBack(EasyJson::kObject);
-    state_json["state"] = entry.first;
-    state_json["count"] = entry.second;
-    double percentage = (100.0 * entry.second) / tablets.size();
-    state_json["percentage"] = tablets.empty() ? "0.0" : StringPrintf("%.2f", percentage);
-  }
-
-  // Used to make the Impala CREATE TABLE statement.
-  (*output)["master_addresses"] = MasterAddrsToCsv();
-
-  std::vector<scoped_refptr<MonitoredTask>> task_list;
-  table->GetTaskList(&task_list);
-  TaskListToJson(task_list, output);
-}
-
-void MasterPathHandlers::HandleMasters(const Webserver::WebRequest& /*req*/,
-                                       Webserver::WebResponse* resp) {
-  EasyJson* output = resp->output;
-  vector<ServerEntryPB> masters;
-  Status s = master_->ListMasters(&masters);
-  if (!s.ok()) {
-    string msg = s.CloneAndPrepend("Unable to list Masters").ToString();
-    LOG(WARNING) << msg;
-    (*output)["error"] = msg;
-    return;
-  }
-  output->Set("masters", EasyJson::kArray);
-  for (const ServerEntryPB& master : masters) {
-    EasyJson master_json = (*output)["masters"].PushBack(EasyJson::kObject);
-    if (master.has_error()) {
-      Status error = StatusFromPB(master.error());
-      master_json["error"] = error.ToString();
-      continue;
-    }
-    const ServerRegistrationPB& reg = master.registration();
-    master_json["uuid"] = master.instance_id().permanent_uuid();
-    if (!reg.http_addresses().empty()) {
-      master_json["target"] = Substitute("$0://$1:$2/",
-                                         reg.https_enabled() ? "https" : "http",
-                                         reg.http_addresses(0).host(),
-                                         reg.http_addresses(0).port());
-    }
-    master_json["role"] = master.has_role() ? RaftPeerPB_Role_Name(master.role()) : "N/A";
-    master_json["registration"] = pb_util::SecureShortDebugString(master.registration());
-  }
-}
-
-namespace {
-
-// Visitor for the catalog table which dumps tables and tablets in a JSON format. This
-// dump is interpreted by the CM agent in order to track time series entities in the SMON
-// database.
-//
-// This implementation relies on scanning the catalog table directly instead of using the
-// catalog manager APIs. This allows it to work even on a non-leader master, and avoids
-// any requirement for locking. For the purposes of metrics entity gathering, it's OK to
-// serve a slightly stale snapshot.
-//
-// It is tempting to directly dump the metadata protobufs using JsonWriter::Protobuf(...),
-// but then we would be tying ourselves to textual compatibility of the PB field names in
-// our catalog table. Instead, the implementation specifically dumps the fields that we
-// care about.
-//
-// This should be considered a "stable" protocol -- do not rename, remove, or restructure
-// without consulting with the CM team.
-class JsonDumper : public TableVisitor, public TabletVisitor {
- public:
-  explicit JsonDumper(JsonWriter* jw) : jw_(jw) {
-  }
-
-  Status VisitTable(const std::string& table_id,
-                    const SysTablesEntryPB& metadata) OVERRIDE {
-    if (metadata.state() != SysTablesEntryPB::RUNNING) {
-      return Status::OK();
-    }
-
-    jw_->StartObject();
-    jw_->String("table_id");
-    jw_->String(table_id);
-
-    jw_->String("table_name");
-    jw_->String(metadata.name());
-
-    jw_->String("state");
-    jw_->String(SysTablesEntryPB::State_Name(metadata.state()));
-
-    jw_->EndObject();
-    return Status::OK();
-  }
-
-  Status VisitTablet(const std::string& table_id,
-                     const std::string& tablet_id,
-                     const SysTabletsEntryPB& metadata) OVERRIDE {
-    if (metadata.state() != SysTabletsEntryPB::RUNNING) {
-      return Status::OK();
-    }
-
-    jw_->StartObject();
-    jw_->String("table_id");
-    jw_->String(table_id);
-
-    jw_->String("tablet_id");
-    jw_->String(tablet_id);
-
-    jw_->String("state");
-    jw_->String(SysTabletsEntryPB::State_Name(metadata.state()));
-
-    // Dump replica UUIDs
-    if (metadata.has_consensus_state()) {
-      const consensus::ConsensusStatePB& cs = metadata.consensus_state();
-      jw_->String("replicas");
-      jw_->StartArray();
-      for (const RaftPeerPB& peer : cs.committed_config().peers()) {
-        jw_->StartObject();
-        jw_->String("type");
-        jw_->String(RaftPeerPB::MemberType_Name(peer.member_type()));
-
-        jw_->String("server_uuid");
-        jw_->String(peer.permanent_uuid());
-
-        jw_->String("addr");
-        jw_->String(Substitute("$0:$1", peer.last_known_addr().host(),
-                               peer.last_known_addr().port()));
-
-        jw_->EndObject();
-      }
-      jw_->EndArray();
-
-      if (!cs.leader_uuid().empty()) {
-        jw_->String("leader");
-        jw_->String(cs.leader_uuid());
-      }
-    }
-
-    jw_->EndObject();
-    return Status::OK();
-  }
-
- private:
-  JsonWriter* jw_;
-};
-
-void JsonError(const Status& s, ostringstream* out) {
-  out->str("");
-  JsonWriter jw(out, JsonWriter::COMPACT);
-  jw.StartObject();
-  jw.String("error");
-  jw.String(s.ToString());
-  jw.EndObject();
-}
-} // anonymous namespace
-
-void MasterPathHandlers::HandleDumpEntities(const Webserver::WebRequest& /*req*/,
-                                            Webserver::PrerenderedWebResponse* resp) {
-  ostringstream* output = resp->output;
-  Status s = master_->catalog_manager()->CheckOnline();
-  if (!s.ok()) {
-    JsonError(s, output);
-    return;
-  }
-  JsonWriter jw(output, JsonWriter::COMPACT);
-  JsonDumper d(&jw);
-
-  jw.StartObject();
-
-  jw.String("tables");
-  jw.StartArray();
-  s = master_->catalog_manager()->sys_catalog()->VisitTables(&d);
-  if (!s.ok()) {
-    JsonError(s, output);
-    return;
-  }
-  jw.EndArray();
-
-  jw.String("tablets");
-  jw.StartArray();
-  s = master_->catalog_manager()->sys_catalog()->VisitTablets(&d);
-  if (!s.ok()) {
-    JsonError(s, output);
-    return;
-  }
-  jw.EndArray();
-
-  jw.String("tablet_servers");
-  jw.StartArray();
-  vector<std::shared_ptr<TSDescriptor> > descs;
-  master_->ts_manager()->GetAllDescriptors(&descs);
-  for (const std::shared_ptr<TSDescriptor>& desc : descs) {
-    jw.StartObject();
-
-    jw.String("uuid");
-    jw.String(desc->permanent_uuid());
-
-    ServerRegistrationPB reg;
-    desc->GetRegistration(&reg);
-
-    jw.String("rpc_addrs");
-    jw.StartArray();
-    for (const HostPortPB& host_port : reg.rpc_addresses()) {
-      jw.String(Substitute("$0:$1", host_port.host(), host_port.port()));
-    }
-    jw.EndArray();
-
-    jw.String("http_addrs");
-    jw.StartArray();
-    for (const HostPortPB& host_port : reg.http_addresses()) {
-      jw.String(Substitute("$0://$1:$2",
-                           reg.https_enabled() ? "https" : "http",
-                           host_port.host(), host_port.port()));
-    }
-    jw.EndArray();
-
-    jw.String("live");
-    jw.Bool(!desc->PresumedDead());
-
-    jw.String("millis_since_heartbeat");
-    jw.Int64(desc->TimeSinceHeartbeat().ToMilliseconds());
-
-    jw.String("version");
-    jw.String(reg.software_version());
-
-    jw.EndObject();
-  }
-  jw.EndArray();
-
-  jw.EndObject();
-}
-
-Status MasterPathHandlers::Register(Webserver* server) {
-  bool is_styled = true;
-  bool is_on_nav_bar = true;
-  server->RegisterPathHandler(
-      "/tablet-servers", "Tablet Servers",
-      boost::bind(&MasterPathHandlers::HandleTabletServers, this, _1, _2),
-      is_styled, is_on_nav_bar);
-  server->RegisterPathHandler(
-      "/tables", "Tables",
-      boost::bind(&MasterPathHandlers::HandleCatalogManager, this, _1, _2),
-      is_styled, is_on_nav_bar);
-  server->RegisterPathHandler(
-      "/table", "",
-      boost::bind(&MasterPathHandlers::HandleTablePage, this, _1, _2),
-      is_styled, false);
-  server->RegisterPathHandler(
-      "/masters", "Masters",
-      boost::bind(&MasterPathHandlers::HandleMasters, this, _1, _2),
-      is_styled, is_on_nav_bar);
-  server->RegisterPrerenderedPathHandler(
-      "/dump-entities", "Dump Entities",
-      boost::bind(&MasterPathHandlers::HandleDumpEntities, this, _1, _2),
-      false, false);
-  return Status::OK();
-}
-
-pair<string, string> MasterPathHandlers::TSDescToLinkPair(const TSDescriptor& desc,
-                                                          const string& tablet_id) const {
-  ServerRegistrationPB reg;
-  desc.GetRegistration(&reg);
-  if (reg.http_addresses().empty()) {
-    return std::make_pair(desc.permanent_uuid(), "");
-  }
-  string text = Substitute("$0:$1", reg.http_addresses(0).host(), reg.http_addresses(0).port());
-  string target = Substitute("$0://$1:$2/tablet?id=$3",
-                             reg.https_enabled() ? "https" : "http",
-                             reg.http_addresses(0).host(),
-                             reg.http_addresses(0).port(),
-                             tablet_id);
-  return std::make_pair(std::move(text), std::move(target));
-}
-
-string MasterPathHandlers::MasterAddrsToCsv() const {
-  if (master_->opts().IsDistributed()) {
-    vector<string> all_addresses;
-    all_addresses.reserve(master_->opts().master_addresses.size());
-    for (const HostPort& hp : master_->opts().master_addresses) {
-      all_addresses.push_back(hp.ToString());
-    }
-    return JoinElements(all_addresses, ",");
-  }
-  Sockaddr addr = master_->first_rpc_address();
-  HostPort hp;
-  Status s = HostPortFromSockaddrReplaceWildcard(addr, &hp);
-  if (s.ok()) {
-    return hp.ToString();
-  }
-  LOG(WARNING) << "Unable to determine proper local hostname: " << s.ToString();
-  return addr.ToString();
-}
-
-Status MasterPathHandlers::GetLeaderMasterHttpAddr(string* leader_http_addr) const {
-  vector<ServerEntryPB> masters;
-  RETURN_NOT_OK_PREPEND(master_->ListMasters(&masters), "unable to list masters");
-  for (const auto& master : masters) {
-    if (master.has_error()) {
-      continue;
-    }
-    if (master.role() != RaftPeerPB::LEADER) {
-      continue;
-    }
-    const ServerRegistrationPB& reg = master.registration();
-    if (reg.http_addresses().empty()) {
-      return Status::NotFound("leader master has no http address");
-    }
-    *leader_http_addr = Substitute("$0://$1:$2",
-                                   reg.https_enabled() ? "https" : "http",
-                                   reg.http_addresses(0).host(),
-                                   reg.http_addresses(0).port());
-    return Status::OK();
-  }
-  return Status::NotFound("no leader master known to this master");
-}
-
-void MasterPathHandlers::SetupLeaderMasterRedirect(const string& path,
-                                                   int redirects,
-                                                   EasyJson* output) const {
-  // Allow 3 redirects.
-  const int max_redirects = 3;
-  (*output)["error"] = "Master is not the leader.";
-  if (redirects >= max_redirects) {
-    (*output)["redirect_error"] = "Too many redirects attempting to find the leader master.";
-    return;
-  }
-  string leader_http_addr;
-  Status s = GetLeaderMasterHttpAddr(&leader_http_addr);
-  if (!s.ok()) {
-    (*output)["redirect_error"] = Substitute("Unable to redirect to leader master: $0",
-                                             s.ToString());
-    return;
-  }
-  (*output)["leader_redirect"] = Substitute("$0/$1&redirects=$2",
-                                            leader_http_addr, path, redirects + 1);
-}
-
-} // namespace master
-} // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/master/master-path-handlers.h
----------------------------------------------------------------------
diff --git a/src/kudu/master/master-path-handlers.h b/src/kudu/master/master-path-handlers.h
deleted file mode 100644
index 186e6d3..0000000
--- a/src/kudu/master/master-path-handlers.h
+++ /dev/null
@@ -1,82 +0,0 @@
-// 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.
-#ifndef KUDU_MASTER_MASTER_PATH_HANDLERS_H
-#define KUDU_MASTER_MASTER_PATH_HANDLERS_H
-
-#include <string>
-#include <utility>
-
-#include "kudu/gutil/macros.h"
-#include "kudu/server/webserver.h"
-#include "kudu/util/status.h"
-
-namespace kudu {
-class EasyJson;
-
-namespace master {
-
-class Master;
-class TSDescriptor;
-
-// Web page support for the master.
-class MasterPathHandlers {
- public:
-  explicit MasterPathHandlers(Master* master)
-    : master_(master) {
-  }
-
-  ~MasterPathHandlers();
-
-  Status Register(Webserver* server);
-
- private:
-  void HandleTabletServers(const Webserver::WebRequest& req,
-                           Webserver::WebResponse* resp);
-  void HandleCatalogManager(const Webserver::WebRequest& req,
-                            Webserver::WebResponse* resp);
-  void HandleTablePage(const Webserver::WebRequest& req,
-                       Webserver::WebResponse* resp);
-  void HandleMasters(const Webserver::WebRequest& req,
-                     Webserver::WebResponse* resp);
-  void HandleDumpEntities(const Webserver::WebRequest& req,
-                          Webserver::PrerenderedWebResponse* resp);
-
-  // Returns a pair (text, target) given a tserver's TSDescriptor and a tablet id.
-  // - text is the http host and port for the tserver, if available, or the tserver's uuid.
-  // - target is a url to the tablet page for the tablet on the tserver's webui,
-  //   or an empty string if no http address is available for the tserver.
-  std::pair<std::string, std::string> TSDescToLinkPair(const TSDescriptor& desc,
-                                                       const std::string& tablet_id) const;
-
-  // Return a CSV of master addresses suitable for display.
-  std::string MasterAddrsToCsv() const;
-
-  // If a leader master is known and has an http address, place it in leader_http_addr.
-  Status GetLeaderMasterHttpAddr(std::string* leader_http_addr) const;
-
-  // Adds the necessary properties to 'output' to set up a redirect to the leader master, or
-  // provide an error message if no redirect is possible.
-  // The redirect will link to <master web UI url>/path&redirects=(redirects + 1).
-  void SetupLeaderMasterRedirect(const std::string& path, int redirects, EasyJson* output) const;
-
-  Master* master_;
-  DISALLOW_COPY_AND_ASSIGN(MasterPathHandlers);
-};
-
-} // namespace master
-} // namespace kudu
-#endif /* KUDU_MASTER_MASTER_PATH_HANDLERS_H */

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/master/master.cc
----------------------------------------------------------------------
diff --git a/src/kudu/master/master.cc b/src/kudu/master/master.cc
index 617977f..4a88007 100644
--- a/src/kudu/master/master.cc
+++ b/src/kudu/master/master.cc
@@ -35,10 +35,10 @@
 #include "kudu/gutil/move.h"
 #include "kudu/gutil/strings/substitute.h"
 #include "kudu/master/catalog_manager.h"
-#include "kudu/master/master-path-handlers.h"
 #include "kudu/master/master.pb.h"
 #include "kudu/master/master.proxy.h"
 #include "kudu/master/master_cert_authority.h"
+#include "kudu/master/master_path_handlers.h"
 #include "kudu/master/master_service.h"
 #include "kudu/master/ts_manager.h"
 #include "kudu/rpc/messenger.h"

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/master/master_path_handlers.cc
----------------------------------------------------------------------
diff --git a/src/kudu/master/master_path_handlers.cc b/src/kudu/master/master_path_handlers.cc
new file mode 100644
index 0000000..d06ddd1
--- /dev/null
+++ b/src/kudu/master/master_path_handlers.cc
@@ -0,0 +1,698 @@
+// 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.
+
+#include "kudu/master/master_path_handlers.h"
+
+#include <algorithm>
+#include <array>
+#include <cstdint>
+#include <iosfwd>
+#include <map>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <type_traits>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include <boost/bind.hpp> // IWYU pragma: keep
+#include <glog/logging.h>
+
+#include "kudu/common/common.pb.h"
+#include "kudu/common/partition.h"
+#include "kudu/common/schema.h"
+#include "kudu/common/wire_protocol.h"
+#include "kudu/common/wire_protocol.pb.h"
+#include "kudu/consensus/metadata.pb.h"
+#include "kudu/consensus/quorum_util.h"
+#include "kudu/gutil/map-util.h"
+#include "kudu/gutil/port.h"
+#include "kudu/gutil/ref_counted.h"
+#include "kudu/gutil/stringprintf.h"
+#include "kudu/gutil/strings/join.h"
+#include "kudu/gutil/strings/numbers.h"
+#include "kudu/gutil/strings/substitute.h"
+#include "kudu/master/catalog_manager.h"
+#include "kudu/master/master.h"
+#include "kudu/master/master.pb.h"
+#include "kudu/master/master_options.h"
+#include "kudu/master/sys_catalog.h"
+#include "kudu/master/ts_descriptor.h"
+#include "kudu/master/ts_manager.h"
+#include "kudu/server/monitored_task.h"
+#include "kudu/server/webui_util.h"
+#include "kudu/util/cow_object.h"
+#include "kudu/util/easy_json.h"
+#include "kudu/util/jsonwriter.h"
+#include "kudu/util/monotime.h"
+#include "kudu/util/net/net_util.h"
+#include "kudu/util/net/sockaddr.h"
+#include "kudu/util/pb_util.h"
+#include "kudu/util/string_case.h"
+#include "kudu/util/url-coding.h"
+#include "kudu/util/web_callback_registry.h"
+
+namespace kudu {
+
+using consensus::ConsensusStatePB;
+using consensus::RaftPeerPB;
+using std::array;
+using std::map;
+using std::ostringstream;
+using std::pair;
+using std::shared_ptr;
+using std::string;
+using std::vector;
+using strings::Substitute;
+
+namespace master {
+
+MasterPathHandlers::~MasterPathHandlers() {
+}
+
+void MasterPathHandlers::HandleTabletServers(const Webserver::WebRequest& /*req*/,
+                                             Webserver::WebResponse* resp) {
+  EasyJson* output = resp->output;
+  vector<std::shared_ptr<TSDescriptor>> descs;
+  master_->ts_manager()->GetAllDescriptors(&descs);
+
+  (*output)["num_ts"] = std::to_string(descs.size());
+
+  // In mustache, when conditionally rendering a section of the template based
+  // on a key, the value becomes the context. In the subcontext, searches for
+  // keys used to display a value (e.g. {{my_key}}) recurse into the parent
+  // context-- but not when used to define a subsection (e.g. {{#my_list}}.
+  // Thus, we use a negative test {{^has_no_live_ts}} to decide if we
+  // should render the live and dead tablet server tables.
+  bool has_no_live_ts = true;
+  bool has_no_dead_ts = true;
+  output->Set("live_tservers", EasyJson::kArray);
+  output->Set("dead_tservers", EasyJson::kArray);
+  map<string, array<int, 2>> version_counts;
+  for (const std::shared_ptr<TSDescriptor>& desc : descs) {
+    string ts_key = desc->PresumedDead() ? "dead_tservers" : "live_tservers";
+    EasyJson ts_json = (*output)[ts_key].PushBack(EasyJson::kObject);
+
+    ServerRegistrationPB reg;
+    desc->GetRegistration(&reg);
+    ts_json["uuid"] = desc->permanent_uuid();
+    if (!reg.http_addresses().empty()) {
+      ts_json["target"] = Substitute("$0://$1:$2/",
+                                     reg.https_enabled() ? "https" : "http",
+                                     reg.http_addresses(0).host(),
+                                     reg.http_addresses(0).port());
+    }
+    ts_json["time_since_hb"] = StringPrintf("%.1fs", desc->TimeSinceHeartbeat().ToSeconds());
+    ts_json["registration"] = pb_util::SecureShortDebugString(reg);
+    version_counts[reg.software_version()][desc->PresumedDead() ? 1 : 0]++;
+    has_no_live_ts &= desc->PresumedDead();
+    has_no_dead_ts &= !desc->PresumedDead();
+  }
+  (*output)["has_no_live_ts"] = has_no_live_ts;
+  (*output)["has_no_dead_ts"] = has_no_dead_ts;
+
+  output->Set("version_counts", EasyJson::kArray);
+  for (const auto& entry : version_counts) {
+    EasyJson version_count_json = (*output)["version_counts"].PushBack(EasyJson::kObject);
+    version_count_json["version"] = entry.first;
+    version_count_json["live"] = Substitute("$0", entry.second[0]);
+    version_count_json["dead"] = Substitute("$0", entry.second[1]);
+  }
+}
+
+namespace {
+
+// Extracts the value of the 'redirects' parameter from 'req'; returns 0 if the
+// parameter doesn't exist or couldn't be parsed.
+int ExtractRedirectsFromRequest(const Webserver::WebRequest& req) {
+  string redirects_str;
+  int redirects = 0;
+  if (FindCopy(req.parsed_args, "redirects", &redirects_str)) {
+    if (!safe_strto32(redirects_str, &redirects)) {
+      return 0;
+    }
+  }
+  return redirects;
+}
+
+} // anonymous namespace
+
+void MasterPathHandlers::HandleCatalogManager(const Webserver::WebRequest& req,
+                                              Webserver::WebResponse* resp) {
+  EasyJson* output = resp->output;
+  CatalogManager::ScopedLeaderSharedLock l(master_->catalog_manager());
+  if (!l.catalog_status().ok()) {
+    (*output)["error"] = Substitute("Master is not ready: $0",  l.catalog_status().ToString());
+    return;
+  }
+  if (!l.leader_status().ok()) {
+    // Track redirects to prevent a redirect loop.
+    int redirects = ExtractRedirectsFromRequest(req);
+    SetupLeaderMasterRedirect("tables?", redirects, output);
+    return;
+  }
+
+  std::vector<scoped_refptr<TableInfo>> tables;
+  master_->catalog_manager()->GetAllTables(&tables);
+  int num_running_tables = 0;
+  EasyJson tables_json = output->Set("tables", EasyJson::kArray);
+  for (const scoped_refptr<TableInfo>& table : tables) {
+    TableMetadataLock l(table.get(), LockMode::READ);
+    if (!l.data().is_running()) {
+      continue;
+    }
+    num_running_tables++; // Table count excluding deleted ones
+    string state = SysTablesEntryPB_State_Name(l.data().pb.state());
+    Capitalize(&state);
+    EasyJson table_json = tables_json.PushBack(EasyJson::kObject);
+    table_json["name"] = EscapeForHtmlToString(l.data().name());
+    table_json["id"] = EscapeForHtmlToString(table->id());
+    table_json["state"] = state;
+    table_json["message"] = EscapeForHtmlToString(l.data().pb.state_msg());
+  }
+  (*output).Set<int64_t>("num_tables", num_running_tables);
+}
+
+namespace {
+
+
+// Holds info about a peer for use in the tablet detail table.
+struct TabletDetailPeerInfo {
+  string text;
+  string target;
+  string role;
+  bool is_leader;
+};
+
+int RoleToSortIndex(RaftPeerPB::Role r) {
+  switch (r) {
+    case RaftPeerPB::LEADER: return 0;
+    default: return 1 + static_cast<int>(r);
+  }
+}
+
+bool CompareByRole(const pair<TabletDetailPeerInfo, RaftPeerPB::Role>& a,
+                   const pair<TabletDetailPeerInfo, RaftPeerPB::Role>& b) {
+  return RoleToSortIndex(a.second) < RoleToSortIndex(b.second);
+}
+
+} // anonymous namespace
+
+
+void MasterPathHandlers::HandleTablePage(const Webserver::WebRequest& req,
+                                         Webserver::WebResponse* resp) {
+  EasyJson* output = resp->output;
+  // Parse argument.
+  string table_id;
+  if (!FindCopy(req.parsed_args, "id", &table_id)) {
+    resp->status_code = HttpStatusCode::BadRequest;
+    (*output)["error"] = "Missing 'id' argument";
+    return;
+  }
+
+  CatalogManager::ScopedLeaderSharedLock l(master_->catalog_manager());
+  if (!l.catalog_status().ok()) {
+    (*output)["error"] = Substitute("Master is not ready: $0", l.catalog_status().ToString());
+    return;
+  }
+  if (!l.leader_status().ok()) {
+    // It's possible to respond 307 Temporary Redirect and automatically redirect with
+    // a Location header, but this would likely confuse users about which master's web ui
+    // they are looking at. Instead, we show a link users can click to go to the leader master.
+    // We track redirects to prevent a redirect loop.
+    int redirects = ExtractRedirectsFromRequest(req);
+    SetupLeaderMasterRedirect(Substitute("table?id=$0", table_id), redirects, output);
+    return;
+  }
+
+  scoped_refptr<TableInfo> table;
+  Status s = master_->catalog_manager()->GetTableInfo(table_id, &table);
+  if (!s.ok()) {
+    resp->status_code = HttpStatusCode::ServiceUnavailable;
+    (*output)["error"] = Substitute("Master is not ready: $0", s.ToString());
+    return;
+  }
+
+  if (!table) {
+    resp->status_code = HttpStatusCode::NotFound;
+    (*output)["error"] = "Table not found";
+    return;
+  }
+
+  Schema schema;
+  PartitionSchema partition_schema;
+  vector<scoped_refptr<TabletInfo>> tablets;
+  {
+    TableMetadataLock l(table.get(), LockMode::READ);
+    (*output)["name"] = l.data().name();
+    (*output)["id"] = table_id;
+    (*output)["version"] = l.data().pb.version();
+
+    string state = SysTablesEntryPB_State_Name(l.data().pb.state());
+    Capitalize(&state);
+    (*output)["state"] = state;
+    string state_msg = l.data().pb.state_msg();
+    if (!state_msg.empty()) {
+      (*output)["state_msg"] = state_msg;
+    }
+
+    s = SchemaFromPB(l.data().pb.schema(), &schema);
+    if (!s.ok()) {
+      (*output)["error"] = Substitute("Unable to decode schema: $0", s.ToString());
+      return;
+    }
+    s = PartitionSchema::FromPB(l.data().pb.partition_schema(), schema, &partition_schema);
+    if (!s.ok()) {
+      (*output)["error"] =
+          Substitute("Unable to decode partition schema: $0", s.ToString());
+      return;
+    }
+    table->GetAllTablets(&tablets);
+  }
+
+  SchemaToJson(schema, output);
+
+  // We have to collate partition schema and tablet information in order to set
+  // up the partition schema, tablet summary, and tablet detail tables.
+  std::vector<string> range_partitions;
+  map<string, int> summary_states;
+  (*output)["detail_partition_schema_header"] = partition_schema.PartitionTableHeader(schema);
+  EasyJson tablets_detail_json = output->Set("tablets_detail", EasyJson::kArray);
+  for (const scoped_refptr<TabletInfo>& tablet : tablets) {
+    vector<pair<TabletDetailPeerInfo, RaftPeerPB::Role>> sorted_replicas;
+    TabletMetadataLock l(tablet.get(), LockMode::READ);
+
+    // Count states for tablet summary.
+    summary_states[SysTabletsEntryPB_State_Name(l.data().pb.state())]++;
+
+    // Collect details about each tablet replica.
+    if (l.data().pb.has_consensus_state()) {
+      const ConsensusStatePB& cstate = l.data().pb.consensus_state();
+      for (const auto& peer : cstate.committed_config().peers()) {
+        TabletDetailPeerInfo peer_info;
+        shared_ptr<TSDescriptor> ts_desc;
+        if (master_->ts_manager()->LookupTSByUUID(peer.permanent_uuid(), &ts_desc)) {
+          auto link_pair = TSDescToLinkPair(*ts_desc.get(), tablet->id());
+          peer_info.text = std::move(link_pair.first);
+          peer_info.target = std::move(link_pair.second);
+        } else {
+          peer_info.text = peer.permanent_uuid();
+        }
+        RaftPeerPB::Role role = GetConsensusRole(peer.permanent_uuid(), cstate);
+        peer_info.role = RaftPeerPB_Role_Name(role);
+        peer_info.is_leader = role == RaftPeerPB::LEADER;
+        sorted_replicas.emplace_back(std::make_pair(peer_info, role));
+      }
+    }
+    std::sort(sorted_replicas.begin(), sorted_replicas.end(), &CompareByRole);
+
+    // Generate a readable description of the partition of each tablet, used
+    // both for each tablet's details and the readable range partition schema.
+    Partition partition;
+    Partition::FromPB(l.data().pb.partition(), &partition);
+
+    // For each unique range partition, add a debug string to range_partitions.
+    // To ensure uniqueness, only use partitions whose hash buckets are all 0.
+    if (std::all_of(partition.hash_buckets().begin(),
+                    partition.hash_buckets().end(),
+                    [] (const int32_t& bucket) { return bucket == 0; })) {
+      range_partitions.emplace_back(
+          partition_schema.RangePartitionDebugString(partition.range_key_start(),
+                                                     partition.range_key_end(),
+                                                     schema));
+    }
+
+    // Combine the tablet details and partition info for each tablet.
+    EasyJson tablet_detail_json = tablets_detail_json.PushBack(EasyJson::kObject);
+    tablet_detail_json["id"] = tablet->id();
+    tablet_detail_json["partition_cols"] = partition_schema.PartitionTableEntry(schema, partition);
+    string state = SysTabletsEntryPB_State_Name(l.data().pb.state());
+    Capitalize(&state);
+    tablet_detail_json["state"] = state;
+    tablet_detail_json["state_msg"] = l.data().pb.state_msg();
+    EasyJson peers_json = tablet_detail_json.Set("peers", EasyJson::kArray);
+    for (const auto& e : sorted_replicas) {
+      EasyJson peer_json = peers_json.PushBack(EasyJson::kObject);
+      peer_json["text"] = e.first.text;
+      if (!e.first.target.empty()) {
+        peer_json["target"] = e.first.target;
+      }
+      peer_json["role"] = e.first.role;
+      peer_json["is_leader"] = e.first.is_leader;
+    }
+  }
+
+  (*output)["partition_schema"] = partition_schema.DisplayString(schema, range_partitions);
+
+  EasyJson summary_json = output->Set("tablets_summary", EasyJson::kArray);
+  for (const auto& entry : summary_states) {
+    EasyJson state_json = summary_json.PushBack(EasyJson::kObject);
+    state_json["state"] = entry.first;
+    state_json["count"] = entry.second;
+    double percentage = (100.0 * entry.second) / tablets.size();
+    state_json["percentage"] = tablets.empty() ? "0.0" : StringPrintf("%.2f", percentage);
+  }
+
+  // Used to make the Impala CREATE TABLE statement.
+  (*output)["master_addresses"] = MasterAddrsToCsv();
+
+  std::vector<scoped_refptr<MonitoredTask>> task_list;
+  table->GetTaskList(&task_list);
+  TaskListToJson(task_list, output);
+}
+
+void MasterPathHandlers::HandleMasters(const Webserver::WebRequest& /*req*/,
+                                       Webserver::WebResponse* resp) {
+  EasyJson* output = resp->output;
+  vector<ServerEntryPB> masters;
+  Status s = master_->ListMasters(&masters);
+  if (!s.ok()) {
+    string msg = s.CloneAndPrepend("Unable to list Masters").ToString();
+    LOG(WARNING) << msg;
+    (*output)["error"] = msg;
+    return;
+  }
+  output->Set("masters", EasyJson::kArray);
+  for (const ServerEntryPB& master : masters) {
+    EasyJson master_json = (*output)["masters"].PushBack(EasyJson::kObject);
+    if (master.has_error()) {
+      Status error = StatusFromPB(master.error());
+      master_json["error"] = error.ToString();
+      continue;
+    }
+    const ServerRegistrationPB& reg = master.registration();
+    master_json["uuid"] = master.instance_id().permanent_uuid();
+    if (!reg.http_addresses().empty()) {
+      master_json["target"] = Substitute("$0://$1:$2/",
+                                         reg.https_enabled() ? "https" : "http",
+                                         reg.http_addresses(0).host(),
+                                         reg.http_addresses(0).port());
+    }
+    master_json["role"] = master.has_role() ? RaftPeerPB_Role_Name(master.role()) : "N/A";
+    master_json["registration"] = pb_util::SecureShortDebugString(master.registration());
+  }
+}
+
+namespace {
+
+// Visitor for the catalog table which dumps tables and tablets in a JSON format. This
+// dump is interpreted by the CM agent in order to track time series entities in the SMON
+// database.
+//
+// This implementation relies on scanning the catalog table directly instead of using the
+// catalog manager APIs. This allows it to work even on a non-leader master, and avoids
+// any requirement for locking. For the purposes of metrics entity gathering, it's OK to
+// serve a slightly stale snapshot.
+//
+// It is tempting to directly dump the metadata protobufs using JsonWriter::Protobuf(...),
+// but then we would be tying ourselves to textual compatibility of the PB field names in
+// our catalog table. Instead, the implementation specifically dumps the fields that we
+// care about.
+//
+// This should be considered a "stable" protocol -- do not rename, remove, or restructure
+// without consulting with the CM team.
+class JsonDumper : public TableVisitor, public TabletVisitor {
+ public:
+  explicit JsonDumper(JsonWriter* jw) : jw_(jw) {
+  }
+
+  Status VisitTable(const std::string& table_id,
+                    const SysTablesEntryPB& metadata) OVERRIDE {
+    if (metadata.state() != SysTablesEntryPB::RUNNING) {
+      return Status::OK();
+    }
+
+    jw_->StartObject();
+    jw_->String("table_id");
+    jw_->String(table_id);
+
+    jw_->String("table_name");
+    jw_->String(metadata.name());
+
+    jw_->String("state");
+    jw_->String(SysTablesEntryPB::State_Name(metadata.state()));
+
+    jw_->EndObject();
+    return Status::OK();
+  }
+
+  Status VisitTablet(const std::string& table_id,
+                     const std::string& tablet_id,
+                     const SysTabletsEntryPB& metadata) OVERRIDE {
+    if (metadata.state() != SysTabletsEntryPB::RUNNING) {
+      return Status::OK();
+    }
+
+    jw_->StartObject();
+    jw_->String("table_id");
+    jw_->String(table_id);
+
+    jw_->String("tablet_id");
+    jw_->String(tablet_id);
+
+    jw_->String("state");
+    jw_->String(SysTabletsEntryPB::State_Name(metadata.state()));
+
+    // Dump replica UUIDs
+    if (metadata.has_consensus_state()) {
+      const consensus::ConsensusStatePB& cs = metadata.consensus_state();
+      jw_->String("replicas");
+      jw_->StartArray();
+      for (const RaftPeerPB& peer : cs.committed_config().peers()) {
+        jw_->StartObject();
+        jw_->String("type");
+        jw_->String(RaftPeerPB::MemberType_Name(peer.member_type()));
+
+        jw_->String("server_uuid");
+        jw_->String(peer.permanent_uuid());
+
+        jw_->String("addr");
+        jw_->String(Substitute("$0:$1", peer.last_known_addr().host(),
+                               peer.last_known_addr().port()));
+
+        jw_->EndObject();
+      }
+      jw_->EndArray();
+
+      if (!cs.leader_uuid().empty()) {
+        jw_->String("leader");
+        jw_->String(cs.leader_uuid());
+      }
+    }
+
+    jw_->EndObject();
+    return Status::OK();
+  }
+
+ private:
+  JsonWriter* jw_;
+};
+
+void JsonError(const Status& s, ostringstream* out) {
+  out->str("");
+  JsonWriter jw(out, JsonWriter::COMPACT);
+  jw.StartObject();
+  jw.String("error");
+  jw.String(s.ToString());
+  jw.EndObject();
+}
+} // anonymous namespace
+
+void MasterPathHandlers::HandleDumpEntities(const Webserver::WebRequest& /*req*/,
+                                            Webserver::PrerenderedWebResponse* resp) {
+  ostringstream* output = resp->output;
+  Status s = master_->catalog_manager()->CheckOnline();
+  if (!s.ok()) {
+    JsonError(s, output);
+    return;
+  }
+  JsonWriter jw(output, JsonWriter::COMPACT);
+  JsonDumper d(&jw);
+
+  jw.StartObject();
+
+  jw.String("tables");
+  jw.StartArray();
+  s = master_->catalog_manager()->sys_catalog()->VisitTables(&d);
+  if (!s.ok()) {
+    JsonError(s, output);
+    return;
+  }
+  jw.EndArray();
+
+  jw.String("tablets");
+  jw.StartArray();
+  s = master_->catalog_manager()->sys_catalog()->VisitTablets(&d);
+  if (!s.ok()) {
+    JsonError(s, output);
+    return;
+  }
+  jw.EndArray();
+
+  jw.String("tablet_servers");
+  jw.StartArray();
+  vector<std::shared_ptr<TSDescriptor> > descs;
+  master_->ts_manager()->GetAllDescriptors(&descs);
+  for (const std::shared_ptr<TSDescriptor>& desc : descs) {
+    jw.StartObject();
+
+    jw.String("uuid");
+    jw.String(desc->permanent_uuid());
+
+    ServerRegistrationPB reg;
+    desc->GetRegistration(&reg);
+
+    jw.String("rpc_addrs");
+    jw.StartArray();
+    for (const HostPortPB& host_port : reg.rpc_addresses()) {
+      jw.String(Substitute("$0:$1", host_port.host(), host_port.port()));
+    }
+    jw.EndArray();
+
+    jw.String("http_addrs");
+    jw.StartArray();
+    for (const HostPortPB& host_port : reg.http_addresses()) {
+      jw.String(Substitute("$0://$1:$2",
+                           reg.https_enabled() ? "https" : "http",
+                           host_port.host(), host_port.port()));
+    }
+    jw.EndArray();
+
+    jw.String("live");
+    jw.Bool(!desc->PresumedDead());
+
+    jw.String("millis_since_heartbeat");
+    jw.Int64(desc->TimeSinceHeartbeat().ToMilliseconds());
+
+    jw.String("version");
+    jw.String(reg.software_version());
+
+    jw.EndObject();
+  }
+  jw.EndArray();
+
+  jw.EndObject();
+}
+
+Status MasterPathHandlers::Register(Webserver* server) {
+  bool is_styled = true;
+  bool is_on_nav_bar = true;
+  server->RegisterPathHandler(
+      "/tablet-servers", "Tablet Servers",
+      boost::bind(&MasterPathHandlers::HandleTabletServers, this, _1, _2),
+      is_styled, is_on_nav_bar);
+  server->RegisterPathHandler(
+      "/tables", "Tables",
+      boost::bind(&MasterPathHandlers::HandleCatalogManager, this, _1, _2),
+      is_styled, is_on_nav_bar);
+  server->RegisterPathHandler(
+      "/table", "",
+      boost::bind(&MasterPathHandlers::HandleTablePage, this, _1, _2),
+      is_styled, false);
+  server->RegisterPathHandler(
+      "/masters", "Masters",
+      boost::bind(&MasterPathHandlers::HandleMasters, this, _1, _2),
+      is_styled, is_on_nav_bar);
+  server->RegisterPrerenderedPathHandler(
+      "/dump-entities", "Dump Entities",
+      boost::bind(&MasterPathHandlers::HandleDumpEntities, this, _1, _2),
+      false, false);
+  return Status::OK();
+}
+
+pair<string, string> MasterPathHandlers::TSDescToLinkPair(const TSDescriptor& desc,
+                                                          const string& tablet_id) const {
+  ServerRegistrationPB reg;
+  desc.GetRegistration(&reg);
+  if (reg.http_addresses().empty()) {
+    return std::make_pair(desc.permanent_uuid(), "");
+  }
+  string text = Substitute("$0:$1", reg.http_addresses(0).host(), reg.http_addresses(0).port());
+  string target = Substitute("$0://$1:$2/tablet?id=$3",
+                             reg.https_enabled() ? "https" : "http",
+                             reg.http_addresses(0).host(),
+                             reg.http_addresses(0).port(),
+                             tablet_id);
+  return std::make_pair(std::move(text), std::move(target));
+}
+
+string MasterPathHandlers::MasterAddrsToCsv() const {
+  if (master_->opts().IsDistributed()) {
+    vector<string> all_addresses;
+    all_addresses.reserve(master_->opts().master_addresses.size());
+    for (const HostPort& hp : master_->opts().master_addresses) {
+      all_addresses.push_back(hp.ToString());
+    }
+    return JoinElements(all_addresses, ",");
+  }
+  Sockaddr addr = master_->first_rpc_address();
+  HostPort hp;
+  Status s = HostPortFromSockaddrReplaceWildcard(addr, &hp);
+  if (s.ok()) {
+    return hp.ToString();
+  }
+  LOG(WARNING) << "Unable to determine proper local hostname: " << s.ToString();
+  return addr.ToString();
+}
+
+Status MasterPathHandlers::GetLeaderMasterHttpAddr(string* leader_http_addr) const {
+  vector<ServerEntryPB> masters;
+  RETURN_NOT_OK_PREPEND(master_->ListMasters(&masters), "unable to list masters");
+  for (const auto& master : masters) {
+    if (master.has_error()) {
+      continue;
+    }
+    if (master.role() != RaftPeerPB::LEADER) {
+      continue;
+    }
+    const ServerRegistrationPB& reg = master.registration();
+    if (reg.http_addresses().empty()) {
+      return Status::NotFound("leader master has no http address");
+    }
+    *leader_http_addr = Substitute("$0://$1:$2",
+                                   reg.https_enabled() ? "https" : "http",
+                                   reg.http_addresses(0).host(),
+                                   reg.http_addresses(0).port());
+    return Status::OK();
+  }
+  return Status::NotFound("no leader master known to this master");
+}
+
+void MasterPathHandlers::SetupLeaderMasterRedirect(const string& path,
+                                                   int redirects,
+                                                   EasyJson* output) const {
+  // Allow 3 redirects.
+  const int max_redirects = 3;
+  (*output)["error"] = "Master is not the leader.";
+  if (redirects >= max_redirects) {
+    (*output)["redirect_error"] = "Too many redirects attempting to find the leader master.";
+    return;
+  }
+  string leader_http_addr;
+  Status s = GetLeaderMasterHttpAddr(&leader_http_addr);
+  if (!s.ok()) {
+    (*output)["redirect_error"] = Substitute("Unable to redirect to leader master: $0",
+                                             s.ToString());
+    return;
+  }
+  (*output)["leader_redirect"] = Substitute("$0/$1&redirects=$2",
+                                            leader_http_addr, path, redirects + 1);
+}
+
+} // namespace master
+} // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/master/master_path_handlers.h
----------------------------------------------------------------------
diff --git a/src/kudu/master/master_path_handlers.h b/src/kudu/master/master_path_handlers.h
new file mode 100644
index 0000000..186e6d3
--- /dev/null
+++ b/src/kudu/master/master_path_handlers.h
@@ -0,0 +1,82 @@
+// 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.
+#ifndef KUDU_MASTER_MASTER_PATH_HANDLERS_H
+#define KUDU_MASTER_MASTER_PATH_HANDLERS_H
+
+#include <string>
+#include <utility>
+
+#include "kudu/gutil/macros.h"
+#include "kudu/server/webserver.h"
+#include "kudu/util/status.h"
+
+namespace kudu {
+class EasyJson;
+
+namespace master {
+
+class Master;
+class TSDescriptor;
+
+// Web page support for the master.
+class MasterPathHandlers {
+ public:
+  explicit MasterPathHandlers(Master* master)
+    : master_(master) {
+  }
+
+  ~MasterPathHandlers();
+
+  Status Register(Webserver* server);
+
+ private:
+  void HandleTabletServers(const Webserver::WebRequest& req,
+                           Webserver::WebResponse* resp);
+  void HandleCatalogManager(const Webserver::WebRequest& req,
+                            Webserver::WebResponse* resp);
+  void HandleTablePage(const Webserver::WebRequest& req,
+                       Webserver::WebResponse* resp);
+  void HandleMasters(const Webserver::WebRequest& req,
+                     Webserver::WebResponse* resp);
+  void HandleDumpEntities(const Webserver::WebRequest& req,
+                          Webserver::PrerenderedWebResponse* resp);
+
+  // Returns a pair (text, target) given a tserver's TSDescriptor and a tablet id.
+  // - text is the http host and port for the tserver, if available, or the tserver's uuid.
+  // - target is a url to the tablet page for the tablet on the tserver's webui,
+  //   or an empty string if no http address is available for the tserver.
+  std::pair<std::string, std::string> TSDescToLinkPair(const TSDescriptor& desc,
+                                                       const std::string& tablet_id) const;
+
+  // Return a CSV of master addresses suitable for display.
+  std::string MasterAddrsToCsv() const;
+
+  // If a leader master is known and has an http address, place it in leader_http_addr.
+  Status GetLeaderMasterHttpAddr(std::string* leader_http_addr) const;
+
+  // Adds the necessary properties to 'output' to set up a redirect to the leader master, or
+  // provide an error message if no redirect is possible.
+  // The redirect will link to <master web UI url>/path&redirects=(redirects + 1).
+  void SetupLeaderMasterRedirect(const std::string& path, int redirects, EasyJson* output) const;
+
+  Master* master_;
+  DISALLOW_COPY_AND_ASSIGN(MasterPathHandlers);
+};
+
+} // namespace master
+} // namespace kudu
+#endif /* KUDU_MASTER_MASTER_PATH_HANDLERS_H */

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/server/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/src/kudu/server/CMakeLists.txt b/src/kudu/server/CMakeLists.txt
index 4c0d9ab..eb2a0e0 100644
--- a/src/kudu/server/CMakeLists.txt
+++ b/src/kudu/server/CMakeLists.txt
@@ -39,16 +39,16 @@ target_link_libraries(server_base_proto
 #########################################
 
 set(SERVER_PROCESS_SRCS
-  default-path-handlers.cc
+  default_path_handlers.cc
   generic_service.cc
   glog_metrics.cc
-  pprof-path-handlers.cc
+  pprof_path_handlers.cc
   rpcz-path-handler.cc
   rpc_server.cc
   server_base.cc
   server_base_options.cc
   tcmalloc_metrics.cc
-  tracing-path-handlers.cc
+  tracing_path_handlers.cc
   webserver.cc
   webserver_options.cc
   webui_util.cc

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/server/default-path-handlers.cc
----------------------------------------------------------------------
diff --git a/src/kudu/server/default-path-handlers.cc b/src/kudu/server/default-path-handlers.cc
deleted file mode 100644
index 8260ec8..0000000
--- a/src/kudu/server/default-path-handlers.cc
+++ /dev/null
@@ -1,318 +0,0 @@
-// 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.
-
-#include "kudu/server/default-path-handlers.h"
-
-#include <sys/stat.h>
-
-#include <cstddef>
-#include <cstdint>
-#include <fstream>
-#include <map>
-#include <memory>
-#include <sstream>
-#include <string>
-#include <vector>
-
-#include <boost/algorithm/string.hpp>
-#include <boost/algorithm/string/predicate.hpp>
-#include <boost/bind.hpp> // IWYU pragma: keep
-#include <gflags/gflags.h>
-#include <gflags/gflags_declare.h>
-#include <glog/logging.h>
-#include <gperftools/malloc_extension.h>
-
-#include "kudu/gutil/macros.h"
-#include "kudu/gutil/map-util.h"
-#include "kudu/gutil/stringprintf.h"
-#include "kudu/gutil/strings/human_readable.h"
-#include "kudu/gutil/strings/numbers.h"
-#include "kudu/gutil/strings/split.h"
-#include "kudu/gutil/strings/substitute.h"
-#include "kudu/server/pprof-path-handlers.h"
-#include "kudu/server/webserver.h"
-#include "kudu/util/easy_json.h"
-#include "kudu/util/flag_tags.h"
-#include "kudu/util/flags.h"
-#include "kudu/util/jsonwriter.h"
-#include "kudu/util/logging.h"
-#include "kudu/util/mem_tracker.h"
-#include "kudu/util/metrics.h"
-#include "kudu/util/process_memory.h"
-#include "kudu/util/status.h"
-#include "kudu/util/web_callback_registry.h"
-
-using std::ifstream;
-using std::string;
-using std::vector;
-using strings::Substitute;
-
-DEFINE_int64(web_log_bytes, 1024 * 1024,
-    "The maximum number of bytes to display on the debug webserver's log page");
-TAG_FLAG(web_log_bytes, advanced);
-TAG_FLAG(web_log_bytes, runtime);
-
-// For configuration dashboard
-DECLARE_string(redact);
-DECLARE_string(rpc_encryption);
-DECLARE_string(rpc_authentication);
-DECLARE_string(webserver_certificate_file);
-
-namespace kudu {
-
-using std::shared_ptr;
-
-namespace {
-// Html/Text formatting tags
-struct Tags {
-  string pre_tag, end_pre_tag, line_break, header, end_header;
-
-  // If as_text is true, set the html tags to a corresponding raw text representation.
-  explicit Tags(bool as_text) {
-    if (as_text) {
-      pre_tag = "";
-      end_pre_tag = "\n";
-      line_break = "\n";
-      header = "";
-      end_header = "";
-    } else {
-      pre_tag = "<pre>";
-      end_pre_tag = "</pre>";
-      line_break = "<br/>";
-      header = "<h2>";
-      end_header = "</h2>";
-    }
-  }
-};
-} // anonymous namespace
-
-// Writes the last FLAGS_web_log_bytes of the INFO logfile to a webpage
-// Note to get best performance, set GLOG_logbuflevel=-1 to prevent log buffering
-static void LogsHandler(const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
-  EasyJson* output = resp->output;
-  (*output)["raw"] = (req.parsed_args.find("raw") != req.parsed_args.end());
-  string logfile;
-  GetFullLogFilename(google::INFO, &logfile);
-  (*output)["logfile"] = logfile;
-  struct stat file_stat;
-  if (stat(logfile.c_str(), &file_stat) == 0) {
-    size_t size = file_stat.st_size;
-    size_t seekpos = size < FLAGS_web_log_bytes ? 0L : size - FLAGS_web_log_bytes;
-    ifstream log(logfile.c_str(), std::ios::in);
-    // Note if the file rolls between stat and seek, this could fail
-    // (and we could wind up reading the whole file). But because the
-    // file is likely to be small, this is unlikely to be an issue in
-    // practice.
-    log.seekg(seekpos);
-    (*output)["web_log_bytes"] = FLAGS_web_log_bytes;
-    std::ostringstream ss;
-    ss << log.rdbuf();
-    (*output)["log"] = ss.str();
-  }
-}
-
-// Registered to handle "/flags", and prints out all command-line flags and their HTML
-// escaped values. If --redact is set with 'flag', the values of flags tagged as
-// sensitive will be redacted. The values would not be HTML escaped if in the raw text
-// mode, e.g. "/varz?raw".
-static void FlagsHandler(const Webserver::WebRequest& req,
-                         Webserver::PrerenderedWebResponse* resp) {
-  std::ostringstream* output = resp->output;
-  bool as_text = (req.parsed_args.find("raw") != req.parsed_args.end());
-  Tags tags(as_text);
-
-  (*output) << tags.header << "Command-line Flags" << tags.end_header;
-  (*output) << tags.pre_tag
-            << CommandlineFlagsIntoString(as_text ? EscapeMode::NONE : EscapeMode::HTML)
-            << tags.end_pre_tag;
-}
-
-// Registered to handle "/memz", and prints out memory allocation statistics.
-static void MemUsageHandler(const Webserver::WebRequest& req,
-                            Webserver::PrerenderedWebResponse* resp) {
-  std::ostringstream* output = resp->output;
-  bool as_text = (req.parsed_args.find("raw") != req.parsed_args.end());
-  Tags tags(as_text);
-
-  (*output) << tags.pre_tag;
-#ifndef TCMALLOC_ENABLED
-  (*output) << "Memory tracking is not available unless tcmalloc is enabled.";
-#else
-  faststring buf;
-  buf.resize(32 * 1024);
-  MallocExtension::instance()->GetStats(reinterpret_cast<char*>(buf.data()), buf.size());
-  // Replace new lines with <br> for html
-  string tmp(reinterpret_cast<char*>(buf.data()));
-  boost::replace_all(tmp, "\n", tags.line_break);
-  (*output) << tmp << tags.end_pre_tag;
-#endif
-}
-
-// Registered to handle "/mem-trackers", and prints out to handle memory tracker information.
-static void MemTrackersHandler(const Webserver::WebRequest& /*req*/,
-                               Webserver::PrerenderedWebResponse* resp) {
-  std::ostringstream* output = resp->output;
-  int64_t current_consumption = process_memory::CurrentConsumption();
-  int64_t hard_limit = process_memory::HardLimit();
-  *output << "<h1>Process memory usage</h1>\n";
-  *output << "<table class='table table-striped'>\n";
-  *output << Substitute("  <tr><th>Total consumption</th><td>$0</td></tr>\n",
-                        HumanReadableNumBytes::ToString(current_consumption));
-  *output << Substitute("  <tr><th>Memory limit</th><td>$0</td></tr>\n",
-                        HumanReadableNumBytes::ToString(hard_limit));
-  if (hard_limit > 0) {
-    double percentage = 100 * static_cast<double>(current_consumption) / hard_limit;
-    *output << Substitute("  <tr><th>Percentage consumed</th><td>$0%</td></tr>\n",
-                          StringPrintf("%.2f", percentage));
-  }
-  *output << "</table>\n";
-#ifndef TCMALLOC_ENABLED
-  *output << R"(
-      <div class="alert alert-warning">
-        <strong>NOTE:</strong> This build of Kudu has not enabled tcmalloc.
-        The above process memory stats will be inaccurate.
-      </div>
-               )";
-#endif
-
-  *output << "<h1>Memory usage by subsystem</h1>\n";
-  *output << "<table class='table table-striped'>\n";
-  *output << "  <thead><tr><th>Id</th><th>Parent</th><th>Limit</th><th>Current Consumption</th>"
-      "<th>Peak consumption</th></tr></thead>\n";
-  *output << "<tbody>\n";
-
-  vector<shared_ptr<MemTracker> > trackers;
-  MemTracker::ListTrackers(&trackers);
-  for (const shared_ptr<MemTracker>& tracker : trackers) {
-    string parent = tracker->parent() == nullptr ? "none" : tracker->parent()->id();
-    string limit_str = tracker->limit() == -1 ? "none" :
-                       HumanReadableNumBytes::ToString(tracker->limit());
-    string current_consumption_str = HumanReadableNumBytes::ToString(tracker->consumption());
-    string peak_consumption_str = HumanReadableNumBytes::ToString(tracker->peak_consumption());
-    (*output) << Substitute("  <tr><td>$0</td><td>$1</td><td>$2</td>" // id, parent, limit
-                            "<td>$3</td><td>$4</td></tr>\n", // current, peak
-                            tracker->id(), parent, limit_str, current_consumption_str,
-                            peak_consumption_str);
-  }
-  *output << "</tbody></table>\n";
-}
-
-static void ConfigurationHandler(const Webserver::WebRequest& /* req */,
-                                 Webserver::WebResponse* resp) {
-  EasyJson* output = resp->output;
-  EasyJson security_configs = output->Set("security_configs", EasyJson::kArray);
-
-  EasyJson rpc_encryption = security_configs.PushBack(EasyJson::kObject);
-  rpc_encryption["name"] = "RPC Encryption";
-  rpc_encryption["value"] = FLAGS_rpc_encryption;
-  rpc_encryption["secure"] = boost::iequals(FLAGS_rpc_encryption, "required");
-  rpc_encryption["id"] = "rpc_encryption";
-  rpc_encryption["explanation"] = "Configure with --rpc_encryption. Most secure value is "
-                                  "'required'.";
-
-  EasyJson rpc_authentication = security_configs.PushBack(EasyJson::kObject);
-  rpc_authentication["name"] = "RPC Authentication";
-  rpc_authentication["value"] = FLAGS_rpc_authentication;
-  rpc_authentication["secure"] = boost::iequals(FLAGS_rpc_authentication, "required");
-  rpc_authentication["id"] = "rpc_authentication";
-  rpc_authentication["explanation"] = "Configure with --rpc_authentication. Most secure value is "
-                                      "'required'.";
-
-  EasyJson webserver_encryption = security_configs.PushBack(EasyJson::kObject);
-  webserver_encryption["name"] = "Webserver Encryption";
-  webserver_encryption["value"] = FLAGS_webserver_certificate_file.empty() ? "off" : "on";
-  webserver_encryption["secure"] = !FLAGS_webserver_certificate_file.empty();
-  webserver_encryption["id"] = "webserver_encryption";
-  webserver_encryption["explanation"] = "Configure with --webserver_certificate_file and "
-                                        "webserver_private_key_file.";
-
-  EasyJson webserver_redaction = security_configs.PushBack(EasyJson::kObject);
-  webserver_redaction["name"] = "Webserver Redaction";
-  webserver_redaction["value"] = FLAGS_redact;
-  webserver_redaction["secure"] = boost::iequals(FLAGS_redact, "all");
-  webserver_redaction["id"] = "webserver_redaction";
-  webserver_redaction["explanation"] = "Configure with --redact. Most secure value is 'all'.";
-}
-
-void AddDefaultPathHandlers(Webserver* webserver) {
-  bool styled = true;
-  bool on_nav_bar = true;
-  webserver->RegisterPathHandler("/logs", "Logs", LogsHandler, styled, on_nav_bar);
-  webserver->RegisterPrerenderedPathHandler("/varz", "Flags", FlagsHandler, styled, on_nav_bar);
-  webserver->RegisterPrerenderedPathHandler("/memz", "Memory (total)", MemUsageHandler,
-                                            styled, on_nav_bar);
-  webserver->RegisterPrerenderedPathHandler("/mem-trackers", "Memory (detail)", MemTrackersHandler,
-                                            styled, on_nav_bar);
-  webserver->RegisterPathHandler("/config", "Configuration", ConfigurationHandler,
-                                  styled, on_nav_bar);
-
-  AddPprofPathHandlers(webserver);
-}
-
-
-static void WriteMetricsAsJson(const MetricRegistry* const metrics,
-                               const Webserver::WebRequest& req,
-                               Webserver::PrerenderedWebResponse* resp) {
-  std::ostringstream* output = resp->output;
-  const string* requested_metrics_param = FindOrNull(req.parsed_args, "metrics");
-  vector<string> requested_metrics;
-  MetricJsonOptions opts;
-
-  {
-    string arg = FindWithDefault(req.parsed_args, "include_raw_histograms", "false");
-    opts.include_raw_histograms = ParseLeadingBoolValue(arg.c_str(), false);
-  }
-  {
-    string arg = FindWithDefault(req.parsed_args, "include_schema", "false");
-    opts.include_schema_info = ParseLeadingBoolValue(arg.c_str(), false);
-  }
-  JsonWriter::Mode json_mode;
-  {
-    string arg = FindWithDefault(req.parsed_args, "compact", "false");
-    json_mode = ParseLeadingBoolValue(arg.c_str(), false) ?
-      JsonWriter::COMPACT : JsonWriter::PRETTY;
-  }
-
-  JsonWriter writer(output, json_mode);
-
-  if (requested_metrics_param != nullptr) {
-    SplitStringUsing(*requested_metrics_param, ",", &requested_metrics);
-  } else {
-    // Default to including all metrics.
-    requested_metrics.emplace_back("*");
-  }
-
-  WARN_NOT_OK(metrics->WriteAsJson(&writer, requested_metrics, opts),
-              "Couldn't write JSON metrics over HTTP");
-}
-
-void RegisterMetricsJsonHandler(Webserver* webserver, const MetricRegistry* const metrics) {
-  Webserver::PrerenderedPathHandlerCallback callback = boost::bind(WriteMetricsAsJson, metrics,
-                                                                   _1, _2);
-  bool not_styled = false;
-  bool not_on_nav_bar = false;
-  bool is_on_nav_bar = true;
-  webserver->RegisterPrerenderedPathHandler("/metrics", "Metrics", callback,
-                                            not_styled, is_on_nav_bar);
-
-  // The old name -- this is preserved for compatibility with older releases of
-  // monitoring software which expects the old name.
-  webserver->RegisterPrerenderedPathHandler("/jsonmetricz", "Metrics", callback,
-                                            not_styled, not_on_nav_bar);
-}
-
-} // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/server/default-path-handlers.h
----------------------------------------------------------------------
diff --git a/src/kudu/server/default-path-handlers.h b/src/kudu/server/default-path-handlers.h
deleted file mode 100644
index 82f3e2b..0000000
--- a/src/kudu/server/default-path-handlers.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// 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.
-
-#ifndef KUDU_SERVER_DEFAULT_PATH_HANDLERS_H
-#define KUDU_SERVER_DEFAULT_PATH_HANDLERS_H
-
-namespace kudu {
-
-class MetricRegistry;
-class Webserver;
-
-// Adds a set of default path handlers to the webserver to display
-// logs and configuration flags.
-void AddDefaultPathHandlers(Webserver* webserver);
-
-// Adds an endpoint to get metrics in JSON format.
-void RegisterMetricsJsonHandler(Webserver* webserver, const MetricRegistry* const metrics);
-
-} // namespace kudu
-
-#endif // KUDU_SERVER_DEFAULT_PATH_HANDLERS_H


[2/3] kudu git commit: Rename *-path-handlers.* to *_path_handlers.*

Posted by wd...@apache.org.
http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/server/default_path_handlers.cc
----------------------------------------------------------------------
diff --git a/src/kudu/server/default_path_handlers.cc b/src/kudu/server/default_path_handlers.cc
new file mode 100644
index 0000000..040aaaf
--- /dev/null
+++ b/src/kudu/server/default_path_handlers.cc
@@ -0,0 +1,319 @@
+// 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.
+
+#include "kudu/server/default_path_handlers.h"
+
+#include <sys/stat.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <fstream>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/bind.hpp> // IWYU pragma: keep
+#include <boost/iterator/iterator_traits.hpp>
+#include <gflags/gflags.h>
+#include <gflags/gflags_declare.h>
+#include <glog/logging.h>
+#include <gperftools/malloc_extension.h>
+
+#include "kudu/gutil/macros.h"
+#include "kudu/gutil/map-util.h"
+#include "kudu/gutil/stringprintf.h"
+#include "kudu/gutil/strings/human_readable.h"
+#include "kudu/gutil/strings/numbers.h"
+#include "kudu/gutil/strings/split.h"
+#include "kudu/gutil/strings/substitute.h"
+#include "kudu/server/pprof_path_handlers.h"
+#include "kudu/server/webserver.h"
+#include "kudu/util/easy_json.h"
+#include "kudu/util/faststring.h"
+#include "kudu/util/flag_tags.h"
+#include "kudu/util/flags.h"
+#include "kudu/util/jsonwriter.h"
+#include "kudu/util/logging.h"
+#include "kudu/util/mem_tracker.h"
+#include "kudu/util/metrics.h"
+#include "kudu/util/process_memory.h"
+#include "kudu/util/status.h"
+#include "kudu/util/web_callback_registry.h"
+
+using std::ifstream;
+using std::string;
+using std::vector;
+using strings::Substitute;
+
+DEFINE_int64(web_log_bytes, 1024 * 1024,
+    "The maximum number of bytes to display on the debug webserver's log page");
+TAG_FLAG(web_log_bytes, advanced);
+TAG_FLAG(web_log_bytes, runtime);
+
+// For configuration dashboard
+DECLARE_string(redact);
+DECLARE_string(rpc_encryption);
+DECLARE_string(rpc_authentication);
+DECLARE_string(webserver_certificate_file);
+
+namespace kudu {
+
+using std::shared_ptr;
+
+namespace {
+// Html/Text formatting tags
+struct Tags {
+  string pre_tag, end_pre_tag, line_break, header, end_header;
+
+  // If as_text is true, set the html tags to a corresponding raw text representation.
+  explicit Tags(bool as_text) {
+    if (as_text) {
+      pre_tag = "";
+      end_pre_tag = "\n";
+      line_break = "\n";
+      header = "";
+      end_header = "";
+    } else {
+      pre_tag = "<pre>";
+      end_pre_tag = "</pre>";
+      line_break = "<br/>";
+      header = "<h2>";
+      end_header = "</h2>";
+    }
+  }
+};
+} // anonymous namespace
+
+// Writes the last FLAGS_web_log_bytes of the INFO logfile to a webpage
+// Note to get best performance, set GLOG_logbuflevel=-1 to prevent log buffering
+static void LogsHandler(const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
+  EasyJson* output = resp->output;
+  (*output)["raw"] = (req.parsed_args.find("raw") != req.parsed_args.end());
+  string logfile;
+  GetFullLogFilename(google::INFO, &logfile);
+  (*output)["logfile"] = logfile;
+  struct stat file_stat;
+  if (stat(logfile.c_str(), &file_stat) == 0) {
+    size_t size = file_stat.st_size;
+    size_t seekpos = size < FLAGS_web_log_bytes ? 0L : size - FLAGS_web_log_bytes;
+    ifstream log(logfile.c_str(), std::ios::in);
+    // Note if the file rolls between stat and seek, this could fail
+    // (and we could wind up reading the whole file). But because the
+    // file is likely to be small, this is unlikely to be an issue in
+    // practice.
+    log.seekg(seekpos);
+    (*output)["web_log_bytes"] = FLAGS_web_log_bytes;
+    std::ostringstream ss;
+    ss << log.rdbuf();
+    (*output)["log"] = ss.str();
+  }
+}
+
+// Registered to handle "/flags", and prints out all command-line flags and their HTML
+// escaped values. If --redact is set with 'flag', the values of flags tagged as
+// sensitive will be redacted. The values would not be HTML escaped if in the raw text
+// mode, e.g. "/varz?raw".
+static void FlagsHandler(const Webserver::WebRequest& req,
+                         Webserver::PrerenderedWebResponse* resp) {
+  std::ostringstream* output = resp->output;
+  bool as_text = (req.parsed_args.find("raw") != req.parsed_args.end());
+  Tags tags(as_text);
+
+  (*output) << tags.header << "Command-line Flags" << tags.end_header;
+  (*output) << tags.pre_tag
+            << CommandlineFlagsIntoString(as_text ? EscapeMode::NONE : EscapeMode::HTML)
+            << tags.end_pre_tag;
+}
+
+// Registered to handle "/memz", and prints out memory allocation statistics.
+static void MemUsageHandler(const Webserver::WebRequest& req,
+                            Webserver::PrerenderedWebResponse* resp) {
+  std::ostringstream* output = resp->output;
+  bool as_text = (req.parsed_args.find("raw") != req.parsed_args.end());
+  Tags tags(as_text);
+
+  (*output) << tags.pre_tag;
+#ifndef TCMALLOC_ENABLED
+  (*output) << "Memory tracking is not available unless tcmalloc is enabled.";
+#else
+  faststring buf;
+  buf.resize(32 * 1024);
+  MallocExtension::instance()->GetStats(reinterpret_cast<char*>(buf.data()), buf.size());
+  // Replace new lines with <br> for html
+  string tmp(reinterpret_cast<char*>(buf.data()));
+  boost::replace_all(tmp, "\n", tags.line_break);
+  (*output) << tmp << tags.end_pre_tag;
+#endif
+}
+
+// Registered to handle "/mem-trackers", and prints out to handle memory tracker information.
+static void MemTrackersHandler(const Webserver::WebRequest& /*req*/,
+                               Webserver::PrerenderedWebResponse* resp) {
+  std::ostringstream* output = resp->output;
+  int64_t current_consumption = process_memory::CurrentConsumption();
+  int64_t hard_limit = process_memory::HardLimit();
+  *output << "<h1>Process memory usage</h1>\n";
+  *output << "<table class='table table-striped'>\n";
+  *output << Substitute("  <tr><th>Total consumption</th><td>$0</td></tr>\n",
+                        HumanReadableNumBytes::ToString(current_consumption));
+  *output << Substitute("  <tr><th>Memory limit</th><td>$0</td></tr>\n",
+                        HumanReadableNumBytes::ToString(hard_limit));
+  if (hard_limit > 0) {
+    double percentage = 100 * static_cast<double>(current_consumption) / hard_limit;
+    *output << Substitute("  <tr><th>Percentage consumed</th><td>$0%</td></tr>\n",
+                          StringPrintf("%.2f", percentage));
+  }
+  *output << "</table>\n";
+#ifndef TCMALLOC_ENABLED
+  *output << R"(
+      <div class="alert alert-warning">
+        <strong>NOTE:</strong> This build of Kudu has not enabled tcmalloc.
+        The above process memory stats will be inaccurate.
+      </div>
+               )";
+#endif
+
+  *output << "<h1>Memory usage by subsystem</h1>\n";
+  *output << "<table class='table table-striped'>\n";
+  *output << "  <thead><tr><th>Id</th><th>Parent</th><th>Limit</th><th>Current Consumption</th>"
+      "<th>Peak consumption</th></tr></thead>\n";
+  *output << "<tbody>\n";
+
+  vector<shared_ptr<MemTracker> > trackers;
+  MemTracker::ListTrackers(&trackers);
+  for (const shared_ptr<MemTracker>& tracker : trackers) {
+    string parent = tracker->parent() == nullptr ? "none" : tracker->parent()->id();
+    string limit_str = tracker->limit() == -1 ? "none" :
+                       HumanReadableNumBytes::ToString(tracker->limit());
+    string current_consumption_str = HumanReadableNumBytes::ToString(tracker->consumption());
+    string peak_consumption_str = HumanReadableNumBytes::ToString(tracker->peak_consumption());
+    (*output) << Substitute("  <tr><td>$0</td><td>$1</td><td>$2</td>" // id, parent, limit
+                            "<td>$3</td><td>$4</td></tr>\n", // current, peak
+                            tracker->id(), parent, limit_str, current_consumption_str,
+                            peak_consumption_str);
+  }
+  *output << "</tbody></table>\n";
+}
+
+static void ConfigurationHandler(const Webserver::WebRequest& /* req */,
+                                 Webserver::WebResponse* resp) {
+  EasyJson* output = resp->output;
+  EasyJson security_configs = output->Set("security_configs", EasyJson::kArray);
+
+  EasyJson rpc_encryption = security_configs.PushBack(EasyJson::kObject);
+  rpc_encryption["name"] = "RPC Encryption";
+  rpc_encryption["value"] = FLAGS_rpc_encryption;
+  rpc_encryption["secure"] = boost::iequals(FLAGS_rpc_encryption, "required");
+  rpc_encryption["id"] = "rpc_encryption";
+  rpc_encryption["explanation"] = "Configure with --rpc_encryption. Most secure value is "
+                                  "'required'.";
+
+  EasyJson rpc_authentication = security_configs.PushBack(EasyJson::kObject);
+  rpc_authentication["name"] = "RPC Authentication";
+  rpc_authentication["value"] = FLAGS_rpc_authentication;
+  rpc_authentication["secure"] = boost::iequals(FLAGS_rpc_authentication, "required");
+  rpc_authentication["id"] = "rpc_authentication";
+  rpc_authentication["explanation"] = "Configure with --rpc_authentication. Most secure value is "
+                                      "'required'.";
+
+  EasyJson webserver_encryption = security_configs.PushBack(EasyJson::kObject);
+  webserver_encryption["name"] = "Webserver Encryption";
+  webserver_encryption["value"] = FLAGS_webserver_certificate_file.empty() ? "off" : "on";
+  webserver_encryption["secure"] = !FLAGS_webserver_certificate_file.empty();
+  webserver_encryption["id"] = "webserver_encryption";
+  webserver_encryption["explanation"] = "Configure with --webserver_certificate_file and "
+                                        "webserver_private_key_file.";
+
+  EasyJson webserver_redaction = security_configs.PushBack(EasyJson::kObject);
+  webserver_redaction["name"] = "Webserver Redaction";
+  webserver_redaction["value"] = FLAGS_redact;
+  webserver_redaction["secure"] = boost::iequals(FLAGS_redact, "all");
+  webserver_redaction["id"] = "webserver_redaction";
+  webserver_redaction["explanation"] = "Configure with --redact. Most secure value is 'all'.";
+}
+
+void AddDefaultPathHandlers(Webserver* webserver) {
+  bool styled = true;
+  bool on_nav_bar = true;
+  webserver->RegisterPathHandler("/logs", "Logs", LogsHandler, styled, on_nav_bar);
+  webserver->RegisterPrerenderedPathHandler("/varz", "Flags", FlagsHandler, styled, on_nav_bar);
+  webserver->RegisterPrerenderedPathHandler("/memz", "Memory (total)", MemUsageHandler,
+                                            styled, on_nav_bar);
+  webserver->RegisterPrerenderedPathHandler("/mem-trackers", "Memory (detail)", MemTrackersHandler,
+                                            styled, on_nav_bar);
+  webserver->RegisterPathHandler("/config", "Configuration", ConfigurationHandler,
+                                  styled, on_nav_bar);
+
+  AddPprofPathHandlers(webserver);
+}
+
+
+static void WriteMetricsAsJson(const MetricRegistry* const metrics,
+                               const Webserver::WebRequest& req,
+                               Webserver::PrerenderedWebResponse* resp) {
+  std::ostringstream* output = resp->output;
+  const string* requested_metrics_param = FindOrNull(req.parsed_args, "metrics");
+  vector<string> requested_metrics;
+  MetricJsonOptions opts;
+
+  {
+    string arg = FindWithDefault(req.parsed_args, "include_raw_histograms", "false");
+    opts.include_raw_histograms = ParseLeadingBoolValue(arg.c_str(), false);
+  }
+  {
+    string arg = FindWithDefault(req.parsed_args, "include_schema", "false");
+    opts.include_schema_info = ParseLeadingBoolValue(arg.c_str(), false);
+  }
+  JsonWriter::Mode json_mode;
+  {
+    string arg = FindWithDefault(req.parsed_args, "compact", "false");
+    json_mode = ParseLeadingBoolValue(arg.c_str(), false) ?
+      JsonWriter::COMPACT : JsonWriter::PRETTY;
+  }
+
+  JsonWriter writer(output, json_mode);
+
+  if (requested_metrics_param != nullptr) {
+    SplitStringUsing(*requested_metrics_param, ",", &requested_metrics);
+  } else {
+    // Default to including all metrics.
+    requested_metrics.emplace_back("*");
+  }
+
+  WARN_NOT_OK(metrics->WriteAsJson(&writer, requested_metrics, opts),
+              "Couldn't write JSON metrics over HTTP");
+}
+
+void RegisterMetricsJsonHandler(Webserver* webserver, const MetricRegistry* const metrics) {
+  Webserver::PrerenderedPathHandlerCallback callback = boost::bind(WriteMetricsAsJson, metrics,
+                                                                   _1, _2);
+  bool not_styled = false;
+  bool not_on_nav_bar = false;
+  bool is_on_nav_bar = true;
+  webserver->RegisterPrerenderedPathHandler("/metrics", "Metrics", callback,
+                                            not_styled, is_on_nav_bar);
+
+  // The old name -- this is preserved for compatibility with older releases of
+  // monitoring software which expects the old name.
+  webserver->RegisterPrerenderedPathHandler("/jsonmetricz", "Metrics", callback,
+                                            not_styled, not_on_nav_bar);
+}
+
+} // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/server/default_path_handlers.h
----------------------------------------------------------------------
diff --git a/src/kudu/server/default_path_handlers.h b/src/kudu/server/default_path_handlers.h
new file mode 100644
index 0000000..82f3e2b
--- /dev/null
+++ b/src/kudu/server/default_path_handlers.h
@@ -0,0 +1,35 @@
+// 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.
+
+#ifndef KUDU_SERVER_DEFAULT_PATH_HANDLERS_H
+#define KUDU_SERVER_DEFAULT_PATH_HANDLERS_H
+
+namespace kudu {
+
+class MetricRegistry;
+class Webserver;
+
+// Adds a set of default path handlers to the webserver to display
+// logs and configuration flags.
+void AddDefaultPathHandlers(Webserver* webserver);
+
+// Adds an endpoint to get metrics in JSON format.
+void RegisterMetricsJsonHandler(Webserver* webserver, const MetricRegistry* const metrics);
+
+} // namespace kudu
+
+#endif // KUDU_SERVER_DEFAULT_PATH_HANDLERS_H

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/server/pprof-path-handlers.cc
----------------------------------------------------------------------
diff --git a/src/kudu/server/pprof-path-handlers.cc b/src/kudu/server/pprof-path-handlers.cc
deleted file mode 100644
index d668889..0000000
--- a/src/kudu/server/pprof-path-handlers.cc
+++ /dev/null
@@ -1,260 +0,0 @@
-// 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.
-
-#include "kudu/server/pprof-path-handlers.h"
-
-#include <unistd.h>
-
-#include <cstdint>
-#include <cstdlib>
-#include <fstream>
-#include <string>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include <gflags/gflags_declare.h>
-#include <glog/logging.h>
-#include <gperftools/heap-profiler.h>
-#include <gperftools/malloc_extension.h>
-#include <gperftools/profiler.h>
-
-#include "kudu/gutil/map-util.h"
-#include "kudu/gutil/strings/numbers.h"
-#include "kudu/gutil/strings/split.h"
-#include "kudu/gutil/strings/stringpiece.h"
-#include "kudu/gutil/strings/strip.h"
-#include "kudu/gutil/strings/substitute.h"
-#include "kudu/gutil/sysinfo.h"
-#include "kudu/server/webserver.h"
-#include "kudu/util/env.h"
-#include "kudu/util/faststring.h"
-#include "kudu/util/monotime.h"
-#include "kudu/util/spinlock_profiling.h"
-#include "kudu/util/status.h"
-#include "kudu/util/web_callback_registry.h"
-
-DECLARE_bool(enable_process_lifetime_heap_profiling);
-DECLARE_string(heap_profile_path);
-
-
-using std::endl;
-using std::ifstream;
-using std::ostringstream;
-using std::string;
-using std::vector;
-
-// GLog already implements symbolization. Just import their hidden symbol.
-namespace google {
-// Symbolizes a program counter.  On success, returns true and write the
-// symbol name to "out".  The symbol name is demangled if possible
-// (supports symbols generated by GCC 3.x or newer).  Otherwise,
-// returns false.
-bool Symbolize(void *pc, char *out, int out_size);
-}
-
-namespace kudu {
-
-const int kPprofDefaultSampleSecs = 30; // pprof default sample time in seconds.
-
-// pprof asks for the url /pprof/cmdline to figure out what application it's profiling.
-// The server should respond by sending the executable path.
-static void PprofCmdLineHandler(const Webserver::WebRequest& /*req*/,
-                                Webserver::PrerenderedWebResponse* resp) {
-  string executable_path;
-  Env* env = Env::Default();
-  WARN_NOT_OK(env->GetExecutablePath(&executable_path), "Failed to get executable path");
-  *resp->output << executable_path;
-}
-
-// pprof asks for the url /pprof/heap to get heap information. This should be implemented
-// by calling HeapProfileStart(filename), continue to do work, and then, some number of
-// seconds later, call GetHeapProfile() followed by HeapProfilerStop().
-static void PprofHeapHandler(const Webserver::WebRequest& req,
-                             Webserver::PrerenderedWebResponse* resp) {
-  ostringstream* output = resp->output;
-#ifndef TCMALLOC_ENABLED
-  *output << "Heap profiling is not available without tcmalloc.";
-#else
-  // Remote (on-demand) profiling is disabled if the process is already being profiled.
-  if (FLAGS_enable_process_lifetime_heap_profiling) {
-    *output << "Heap profiling is running for the process lifetime.";
-    return;
-  }
-
-  auto it = req.parsed_args.find("seconds");
-  int seconds = kPprofDefaultSampleSecs;
-  if (it != req.parsed_args.end()) {
-    seconds = atoi(it->second.c_str());
-  }
-
-  HeapProfilerStart(FLAGS_heap_profile_path.c_str());
-  // Sleep to allow for some samples to be collected.
-  SleepFor(MonoDelta::FromSeconds(seconds));
-  const char* profile = GetHeapProfile();
-  HeapProfilerStop();
-  *output << profile;
-  delete profile;
-#endif
-}
-
-// pprof asks for the url /pprof/profile?seconds=XX to get cpu-profiling information.
-// The server should respond by calling ProfilerStart(), continuing to do its work,
-// and then, XX seconds later, calling ProfilerStop().
-static void PprofCpuProfileHandler(const Webserver::WebRequest& req,
-                                   Webserver::PrerenderedWebResponse* resp) {
-  ostringstream* output = resp->output;
-#ifndef TCMALLOC_ENABLED
-  *output << "CPU profiling is not available without tcmalloc.";
-#else
-  auto it = req.parsed_args.find("seconds");
-  int seconds = kPprofDefaultSampleSecs;
-  if (it != req.parsed_args.end()) {
-    seconds = atoi(it->second.c_str());
-  }
-  // Build a temporary file name that is hopefully unique.
-  string tmp_prof_file_name = strings::Substitute("/tmp/kudu_cpu_profile.$0.$1", getpid(), rand());
-  ProfilerStart(tmp_prof_file_name.c_str());
-  SleepFor(MonoDelta::FromSeconds(seconds));
-  ProfilerStop();
-  ifstream prof_file(tmp_prof_file_name.c_str(), std::ios::in);
-  if (!prof_file.is_open()) {
-    *output << "Unable to open cpu profile: " << tmp_prof_file_name;
-    return;
-  }
-  *output << prof_file.rdbuf();
-  prof_file.close();
-#endif
-}
-
-// pprof asks for the url /pprof/growth to get heap-profiling delta (growth) information.
-// The server should respond by calling:
-// MallocExtension::instance()->GetHeapGrowthStacks(&output);
-static void PprofGrowthHandler(const Webserver::WebRequest& /*req*/,
-                               Webserver::PrerenderedWebResponse* resp) {
-#ifndef TCMALLOC_ENABLED
-  *resp->output << "Growth profiling is not available without tcmalloc.";
-#else
-  string heap_growth_stack;
-  MallocExtension::instance()->GetHeapGrowthStacks(&heap_growth_stack);
-  *resp->output << heap_growth_stack;
-#endif
-}
-
-// Lock contention profiling
-static void PprofContentionHandler(const Webserver::WebRequest& req,
-                                   Webserver::PrerenderedWebResponse* resp) {
-  ostringstream* output = resp->output;
-  string secs_str = FindWithDefault(req.parsed_args, "seconds", "");
-  int32_t seconds = ParseLeadingInt32Value(secs_str.c_str(), kPprofDefaultSampleSecs);
-  int64_t discarded_samples = 0;
-
-  *output << "--- contention" << endl;
-  *output << "sampling period = 1" << endl;
-  *output << "cycles/second = " << base::CyclesPerSecond() << endl;
-
-  MonoTime end = MonoTime::Now() + MonoDelta::FromSeconds(seconds);
-  StartSynchronizationProfiling();
-  while (MonoTime::Now() < end) {
-    SleepFor(MonoDelta::FromMilliseconds(500));
-    FlushSynchronizationProfile(output, &discarded_samples);
-  }
-  StopSynchronizationProfiling();
-  FlushSynchronizationProfile(output, &discarded_samples);
-
-  // pprof itself ignores this value, but we can at least look at it in the textual
-  // output.
-  *output << "discarded samples = " << discarded_samples << std::endl;
-
-#if defined(__linux__)
-  // procfs only exists on Linux.
-  faststring maps;
-  ReadFileToString(Env::Default(), "/proc/self/maps", &maps);
-  *output << maps.ToString();
-#endif // defined(__linux__)
-}
-
-
-// pprof asks for the url /pprof/symbol to map from hex addresses to variable names.
-// When the server receives a GET request for /pprof/symbol, it should return a line
-// formatted like: num_symbols: ###
-// where ### is the number of symbols found in the binary. For now, the only important
-// distinction is whether the value is 0, which it is for executables that lack debug
-// information, or not-0).
-//
-// In addition to the GET request for this url, the server must accept POST requests.
-// This means that after the HTTP headers, pprof will pass in a list of hex addresses
-// connected by +, like:
-//   curl -d '0x0824d061+0x0824d1cf' http://remote_host:80/pprof/symbol
-// The server should read the POST data, which will be in one line, and for each hex value
-// should write one line of output to the output stream, like so:
-// <hex address><tab><function name>
-// For instance:
-// 0x08b2dabd    _Update
-static void PprofSymbolHandler(const Webserver::WebRequest& req,
-                               Webserver::PrerenderedWebResponse* resp) {
-  if (req.request_method == "GET") {
-    // Per the above comment, pprof doesn't expect to know the actual number of symbols.
-    // Any non-zero value indicates that we support symbol lookup.
-    *resp->output << "num_symbols: 1";
-    return;
-  }
-
-  int missing_symbols = 0;
-  int invalid_addrs = 0;
-
-  // Symbolization request.
-  vector<StringPiece> pieces = strings::Split(req.post_data, "+");
-  for (StringPiece p : pieces) {
-    string hex_addr;
-    if (!TryStripPrefixString(p, "0x", &hex_addr)) {
-      invalid_addrs++;
-      continue;
-    }
-    uint64_t addr;
-    if (!safe_strtou64_base(hex_addr.c_str(), &addr, 16)) {
-      invalid_addrs++;
-      continue;
-    }
-    char symbol_buf[1024];
-    if (google::Symbolize(reinterpret_cast<void*>(addr), symbol_buf, sizeof(symbol_buf))) {
-      *resp->output << p << "\t" << symbol_buf << std::endl;
-    } else {
-      missing_symbols++;
-    }
-  }
-
-  LOG(INFO) << strings::Substitute(
-      "Handled request for /pprof/symbol: requested=$0 invalid_addrs=$1 missing=$2",
-      pieces.size(), invalid_addrs, missing_symbols);
-}
-
-void AddPprofPathHandlers(Webserver* webserver) {
-  // Path handlers for remote pprof profiling. For information see:
-  // https://gperftools.googlecode.com/svn/trunk/doc/pprof_remote_servers.html
-  webserver->RegisterPrerenderedPathHandler("/pprof/cmdline", "", PprofCmdLineHandler,
-                                            false, false);
-  webserver->RegisterPrerenderedPathHandler("/pprof/heap", "", PprofHeapHandler, false, false);
-  webserver->RegisterPrerenderedPathHandler("/pprof/growth", "", PprofGrowthHandler, false, false);
-  webserver->RegisterPrerenderedPathHandler("/pprof/profile", "", PprofCpuProfileHandler,
-                                            false, false);
-  webserver->RegisterPrerenderedPathHandler("/pprof/symbol", "", PprofSymbolHandler, false, false);
-  webserver->RegisterPrerenderedPathHandler("/pprof/contention", "", PprofContentionHandler,
-                                            false, false);
-}
-
-} // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/server/pprof-path-handlers.h
----------------------------------------------------------------------
diff --git a/src/kudu/server/pprof-path-handlers.h b/src/kudu/server/pprof-path-handlers.h
deleted file mode 100644
index e8c0329..0000000
--- a/src/kudu/server/pprof-path-handlers.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// 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.
-#ifndef KUDU_SERVER_PPROF_DEFAULT_PATH_HANDLERS_H
-#define KUDU_SERVER_PPROF_DEFAULT_PATH_HANDLERS_H
-
-namespace kudu {
-class Webserver;
-
-// Adds set of path handlers to support pprof profiling of a remote server.
-void AddPprofPathHandlers(Webserver* webserver);
-}
-
-#endif // KUDU_SERVER_PPROF_DEFAULT_PATH_HANDLERS_H

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/server/pprof_path_handlers.cc
----------------------------------------------------------------------
diff --git a/src/kudu/server/pprof_path_handlers.cc b/src/kudu/server/pprof_path_handlers.cc
new file mode 100644
index 0000000..1425941
--- /dev/null
+++ b/src/kudu/server/pprof_path_handlers.cc
@@ -0,0 +1,260 @@
+// 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.
+
+#include "kudu/server/pprof_path_handlers.h"
+
+#include <unistd.h>
+
+#include <cstdint>
+#include <cstdlib>
+#include <fstream>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include <gflags/gflags_declare.h>
+#include <glog/logging.h>
+#include <gperftools/heap-profiler.h>
+#include <gperftools/malloc_extension.h>
+#include <gperftools/profiler.h>
+
+#include "kudu/gutil/map-util.h"
+#include "kudu/gutil/strings/numbers.h"
+#include "kudu/gutil/strings/split.h"
+#include "kudu/gutil/strings/stringpiece.h"
+#include "kudu/gutil/strings/strip.h"
+#include "kudu/gutil/strings/substitute.h"
+#include "kudu/gutil/sysinfo.h"
+#include "kudu/server/webserver.h"
+#include "kudu/util/env.h"
+#include "kudu/util/faststring.h"
+#include "kudu/util/monotime.h"
+#include "kudu/util/spinlock_profiling.h"
+#include "kudu/util/status.h"
+#include "kudu/util/web_callback_registry.h"
+
+DECLARE_bool(enable_process_lifetime_heap_profiling);
+DECLARE_string(heap_profile_path);
+
+
+using std::endl;
+using std::ifstream;
+using std::ostringstream;
+using std::string;
+using std::vector;
+
+// GLog already implements symbolization. Just import their hidden symbol.
+namespace google {
+// Symbolizes a program counter.  On success, returns true and write the
+// symbol name to "out".  The symbol name is demangled if possible
+// (supports symbols generated by GCC 3.x or newer).  Otherwise,
+// returns false.
+bool Symbolize(void *pc, char *out, int out_size);
+}
+
+namespace kudu {
+
+const int kPprofDefaultSampleSecs = 30; // pprof default sample time in seconds.
+
+// pprof asks for the url /pprof/cmdline to figure out what application it's profiling.
+// The server should respond by sending the executable path.
+static void PprofCmdLineHandler(const Webserver::WebRequest& /*req*/,
+                                Webserver::PrerenderedWebResponse* resp) {
+  string executable_path;
+  Env* env = Env::Default();
+  WARN_NOT_OK(env->GetExecutablePath(&executable_path), "Failed to get executable path");
+  *resp->output << executable_path;
+}
+
+// pprof asks for the url /pprof/heap to get heap information. This should be implemented
+// by calling HeapProfileStart(filename), continue to do work, and then, some number of
+// seconds later, call GetHeapProfile() followed by HeapProfilerStop().
+static void PprofHeapHandler(const Webserver::WebRequest& req,
+                             Webserver::PrerenderedWebResponse* resp) {
+  ostringstream* output = resp->output;
+#ifndef TCMALLOC_ENABLED
+  *output << "Heap profiling is not available without tcmalloc.";
+#else
+  // Remote (on-demand) profiling is disabled if the process is already being profiled.
+  if (FLAGS_enable_process_lifetime_heap_profiling) {
+    *output << "Heap profiling is running for the process lifetime.";
+    return;
+  }
+
+  auto it = req.parsed_args.find("seconds");
+  int seconds = kPprofDefaultSampleSecs;
+  if (it != req.parsed_args.end()) {
+    seconds = atoi(it->second.c_str());
+  }
+
+  HeapProfilerStart(FLAGS_heap_profile_path.c_str());
+  // Sleep to allow for some samples to be collected.
+  SleepFor(MonoDelta::FromSeconds(seconds));
+  const char* profile = GetHeapProfile();
+  HeapProfilerStop();
+  *output << profile;
+  delete profile;
+#endif
+}
+
+// pprof asks for the url /pprof/profile?seconds=XX to get cpu-profiling information.
+// The server should respond by calling ProfilerStart(), continuing to do its work,
+// and then, XX seconds later, calling ProfilerStop().
+static void PprofCpuProfileHandler(const Webserver::WebRequest& req,
+                                   Webserver::PrerenderedWebResponse* resp) {
+  ostringstream* output = resp->output;
+#ifndef TCMALLOC_ENABLED
+  *output << "CPU profiling is not available without tcmalloc.";
+#else
+  auto it = req.parsed_args.find("seconds");
+  int seconds = kPprofDefaultSampleSecs;
+  if (it != req.parsed_args.end()) {
+    seconds = atoi(it->second.c_str());
+  }
+  // Build a temporary file name that is hopefully unique.
+  string tmp_prof_file_name = strings::Substitute("/tmp/kudu_cpu_profile.$0.$1", getpid(), rand());
+  ProfilerStart(tmp_prof_file_name.c_str());
+  SleepFor(MonoDelta::FromSeconds(seconds));
+  ProfilerStop();
+  ifstream prof_file(tmp_prof_file_name.c_str(), std::ios::in);
+  if (!prof_file.is_open()) {
+    *output << "Unable to open cpu profile: " << tmp_prof_file_name;
+    return;
+  }
+  *output << prof_file.rdbuf();
+  prof_file.close();
+#endif
+}
+
+// pprof asks for the url /pprof/growth to get heap-profiling delta (growth) information.
+// The server should respond by calling:
+// MallocExtension::instance()->GetHeapGrowthStacks(&output);
+static void PprofGrowthHandler(const Webserver::WebRequest& /*req*/,
+                               Webserver::PrerenderedWebResponse* resp) {
+#ifndef TCMALLOC_ENABLED
+  *resp->output << "Growth profiling is not available without tcmalloc.";
+#else
+  string heap_growth_stack;
+  MallocExtension::instance()->GetHeapGrowthStacks(&heap_growth_stack);
+  *resp->output << heap_growth_stack;
+#endif
+}
+
+// Lock contention profiling
+static void PprofContentionHandler(const Webserver::WebRequest& req,
+                                   Webserver::PrerenderedWebResponse* resp) {
+  ostringstream* output = resp->output;
+  string secs_str = FindWithDefault(req.parsed_args, "seconds", "");
+  int32_t seconds = ParseLeadingInt32Value(secs_str.c_str(), kPprofDefaultSampleSecs);
+  int64_t discarded_samples = 0;
+
+  *output << "--- contention" << endl;
+  *output << "sampling period = 1" << endl;
+  *output << "cycles/second = " << base::CyclesPerSecond() << endl;
+
+  MonoTime end = MonoTime::Now() + MonoDelta::FromSeconds(seconds);
+  StartSynchronizationProfiling();
+  while (MonoTime::Now() < end) {
+    SleepFor(MonoDelta::FromMilliseconds(500));
+    FlushSynchronizationProfile(output, &discarded_samples);
+  }
+  StopSynchronizationProfiling();
+  FlushSynchronizationProfile(output, &discarded_samples);
+
+  // pprof itself ignores this value, but we can at least look at it in the textual
+  // output.
+  *output << "discarded samples = " << discarded_samples << std::endl;
+
+#if defined(__linux__)
+  // procfs only exists on Linux.
+  faststring maps;
+  ReadFileToString(Env::Default(), "/proc/self/maps", &maps);
+  *output << maps.ToString();
+#endif // defined(__linux__)
+}
+
+
+// pprof asks for the url /pprof/symbol to map from hex addresses to variable names.
+// When the server receives a GET request for /pprof/symbol, it should return a line
+// formatted like: num_symbols: ###
+// where ### is the number of symbols found in the binary. For now, the only important
+// distinction is whether the value is 0, which it is for executables that lack debug
+// information, or not-0).
+//
+// In addition to the GET request for this url, the server must accept POST requests.
+// This means that after the HTTP headers, pprof will pass in a list of hex addresses
+// connected by +, like:
+//   curl -d '0x0824d061+0x0824d1cf' http://remote_host:80/pprof/symbol
+// The server should read the POST data, which will be in one line, and for each hex value
+// should write one line of output to the output stream, like so:
+// <hex address><tab><function name>
+// For instance:
+// 0x08b2dabd    _Update
+static void PprofSymbolHandler(const Webserver::WebRequest& req,
+                               Webserver::PrerenderedWebResponse* resp) {
+  if (req.request_method == "GET") {
+    // Per the above comment, pprof doesn't expect to know the actual number of symbols.
+    // Any non-zero value indicates that we support symbol lookup.
+    *resp->output << "num_symbols: 1";
+    return;
+  }
+
+  int missing_symbols = 0;
+  int invalid_addrs = 0;
+
+  // Symbolization request.
+  vector<StringPiece> pieces = strings::Split(req.post_data, "+");
+  for (StringPiece p : pieces) {
+    string hex_addr;
+    if (!TryStripPrefixString(p, "0x", &hex_addr)) {
+      invalid_addrs++;
+      continue;
+    }
+    uint64_t addr;
+    if (!safe_strtou64_base(hex_addr.c_str(), &addr, 16)) {
+      invalid_addrs++;
+      continue;
+    }
+    char symbol_buf[1024];
+    if (google::Symbolize(reinterpret_cast<void*>(addr), symbol_buf, sizeof(symbol_buf))) {
+      *resp->output << p << "\t" << symbol_buf << std::endl;
+    } else {
+      missing_symbols++;
+    }
+  }
+
+  LOG(INFO) << strings::Substitute(
+      "Handled request for /pprof/symbol: requested=$0 invalid_addrs=$1 missing=$2",
+      pieces.size(), invalid_addrs, missing_symbols);
+}
+
+void AddPprofPathHandlers(Webserver* webserver) {
+  // Path handlers for remote pprof profiling. For information see:
+  // https://gperftools.googlecode.com/svn/trunk/doc/pprof_remote_servers.html
+  webserver->RegisterPrerenderedPathHandler("/pprof/cmdline", "", PprofCmdLineHandler,
+                                            false, false);
+  webserver->RegisterPrerenderedPathHandler("/pprof/heap", "", PprofHeapHandler, false, false);
+  webserver->RegisterPrerenderedPathHandler("/pprof/growth", "", PprofGrowthHandler, false, false);
+  webserver->RegisterPrerenderedPathHandler("/pprof/profile", "", PprofCpuProfileHandler,
+                                            false, false);
+  webserver->RegisterPrerenderedPathHandler("/pprof/symbol", "", PprofSymbolHandler, false, false);
+  webserver->RegisterPrerenderedPathHandler("/pprof/contention", "", PprofContentionHandler,
+                                            false, false);
+}
+
+} // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/server/pprof_path_handlers.h
----------------------------------------------------------------------
diff --git a/src/kudu/server/pprof_path_handlers.h b/src/kudu/server/pprof_path_handlers.h
new file mode 100644
index 0000000..e8c0329
--- /dev/null
+++ b/src/kudu/server/pprof_path_handlers.h
@@ -0,0 +1,27 @@
+// 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.
+#ifndef KUDU_SERVER_PPROF_DEFAULT_PATH_HANDLERS_H
+#define KUDU_SERVER_PPROF_DEFAULT_PATH_HANDLERS_H
+
+namespace kudu {
+class Webserver;
+
+// Adds set of path handlers to support pprof profiling of a remote server.
+void AddPprofPathHandlers(Webserver* webserver);
+}
+
+#endif // KUDU_SERVER_PPROF_DEFAULT_PATH_HANDLERS_H

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/server/server_base.cc
----------------------------------------------------------------------
diff --git a/src/kudu/server/server_base.cc b/src/kudu/server/server_base.cc
index e17358f..9edd463 100644
--- a/src/kudu/server/server_base.cc
+++ b/src/kudu/server/server_base.cc
@@ -49,7 +49,7 @@
 #include "kudu/rpc/rpc_context.h"
 #include "kudu/rpc/service_if.h"
 #include "kudu/security/init.h"
-#include "kudu/server/default-path-handlers.h"
+#include "kudu/server/default_path_handlers.h"
 #include "kudu/server/generic_service.h"
 #include "kudu/server/glog_metrics.h"
 #include "kudu/server/rpc_server.h"
@@ -57,7 +57,7 @@
 #include "kudu/server/server_base.pb.h"
 #include "kudu/server/server_base_options.h"
 #include "kudu/server/tcmalloc_metrics.h"
-#include "kudu/server/tracing-path-handlers.h"
+#include "kudu/server/tracing_path_handlers.h"
 #include "kudu/server/webserver.h"
 #include "kudu/util/atomic.h"
 #include "kudu/util/env.h"

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/server/tracing-path-handlers.cc
----------------------------------------------------------------------
diff --git a/src/kudu/server/tracing-path-handlers.cc b/src/kudu/server/tracing-path-handlers.cc
deleted file mode 100644
index 7849c13..0000000
--- a/src/kudu/server/tracing-path-handlers.cc
+++ /dev/null
@@ -1,287 +0,0 @@
-// 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.
-#include "kudu/server/tracing-path-handlers.h"
-
-#include <map>
-#include <memory>
-#include <ostream>
-#include <string>
-#include <utility>
-#include <vector>
-
-
-#include <boost/bind.hpp> // IWYU pragma: keep
-#include <glog/logging.h>
-#include <rapidjson/document.h>
-
-#include "kudu/gutil/strings/escaping.h"
-#include "kudu/server/webserver.h"
-#include "kudu/util/debug/trace_event_impl.h"
-#include "kudu/util/jsonwriter.h"
-#include "kudu/util/monotime.h"
-#include "kudu/util/status.h"
-#include "kudu/util/web_callback_registry.h"
-#include "kudu/util/zlib.h"
-
-using std::map;
-using std::ostringstream;
-using std::pair;
-using std::string;
-using std::unique_ptr;
-using std::vector;
-
-using kudu::debug::CategoryFilter;
-using kudu::debug::TraceLog;
-using kudu::debug::TraceResultBuffer;
-
-namespace kudu {
-namespace server {
-
-enum Handler {
-  kBeginMonitoring,
-  kEndMonitoring,
-  kCaptureMonitoring,
-  kGetMonitoringStatus,
-  kCategories,
-  kBeginRecording,
-  kGetBufferPercentFull,
-  kEndRecording,
-  kEndRecordingCompressed,
-  kSimpleDump
-};
-
-namespace {
-
-Status ParseBase64JsonRequest(const string& json_base64,
-                              rapidjson::Document* doc) {
-  string json_str;
-  if (!strings::Base64Unescape(json_base64, &json_str)) {
-    return Status::InvalidArgument("Invalid base64-encoded JSON");
-  }
-
-  doc->Parse<0>(json_str.c_str());
-  if (!doc->IsObject()) {
-    return Status::InvalidArgument("Invalid JSON", json_str);
-  }
-  return Status::OK();
-}
-
-Status GetTracingOptions(const std::string& json_base64,
-                       std::string* category_filter_string,
-                       int* tracing_options) {
-  rapidjson::Document doc;
-  RETURN_NOT_OK(ParseBase64JsonRequest(json_base64, &doc));
-
-  bool use_continuous_tracing = false;
-  bool use_sampling = false;
-
-  if (!doc.HasMember("categoryFilter") ||
-      !doc["categoryFilter"].IsString()) {
-    return Status::InvalidArgument("missing categoryFilter");
-  }
-  *category_filter_string = doc["categoryFilter"].GetString();
-
-  if (doc.HasMember("useContinuousTracing") &&
-      doc["useContinuousTracing"].IsBool()) {
-    use_continuous_tracing = doc["useContinuousTracing"].GetBool();
-  }
-
-  if (doc.HasMember("useSampling") &&
-      doc["useSampling"].IsBool()) {
-    use_sampling = doc["useSampling"].GetBool();
-  }
-
-  *tracing_options = 0;
-  if (use_sampling)
-    *tracing_options |= TraceLog::ENABLE_SAMPLING;
-  if (use_continuous_tracing)
-    *tracing_options |= TraceLog::RECORD_CONTINUOUSLY;
-  return Status::OK();
-}
-
-Status BeginRecording(const Webserver::WebRequest& req,
-                      TraceLog::Mode mode) {
-  string filter_str;
-  int options;
-  RETURN_NOT_OK(GetTracingOptions(req.query_string, &filter_str, &options));
-
-  kudu::debug::TraceLog::GetInstance()->SetEnabled(
-    CategoryFilter(filter_str),
-    mode,
-    static_cast<TraceLog::Options>(options));
-  return Status::OK();
-}
-
-Status EndRecording(const Webserver::WebRequest& req,
-                    bool compressed,
-                    ostringstream* out) {
-  TraceLog* tl = TraceLog::GetInstance();
-  tl->SetDisabled();
-  string json = TraceResultBuffer::FlushTraceLogToString();
-
-  if (compressed) {
-    RETURN_NOT_OK_PREPEND(zlib::Compress(json, out),
-                          "Could not compress output");
-  } else {
-    *out << json;
-  }
-
-  return Status::OK();
-}
-
-Status CaptureMonitoring(ostringstream* out) {
-  TraceLog* tl = TraceLog::GetInstance();
-  if (!tl->IsEnabled()) {
-    return Status::IllegalState("monitoring not enabled");
-  }
-  *out << TraceResultBuffer::FlushTraceLogToStringButLeaveBufferIntact();
-  return Status::OK();
-}
-
-void GetCategories(ostringstream* out) {
-  vector<string> groups;
-  kudu::debug::TraceLog::GetInstance()->GetKnownCategoryGroups(&groups);
-  JsonWriter j(out, JsonWriter::COMPACT);
-  j.StartArray();
-  for (const string& g : groups) {
-    j.String(g);
-  }
-  j.EndArray();
-}
-
-void GetMonitoringStatus(ostringstream* out) {
-  TraceLog* tl = TraceLog::GetInstance();
-  bool is_monitoring = tl->IsEnabled();
-  std::string category_filter = tl->GetCurrentCategoryFilter().ToString();
-  int options = static_cast<int>(tl->trace_options());
-
-  ostringstream json_out;
-  JsonWriter j(&json_out, JsonWriter::COMPACT);
-  j.StartObject();
-
-  j.String("isMonitoring");
-  j.Bool(is_monitoring);
-
-  j.String("categoryFilter");
-  j.String(category_filter);
-
-  j.String("useContinuousTracing");
-  j.Bool((options & TraceLog::RECORD_CONTINUOUSLY) != 0);
-
-  j.String("useSampling");
-  j.Bool((options & TraceLog::ENABLE_SAMPLING) != 0);
-
-  j.EndObject();
-
-  string encoded;
-  strings::Base64Escape(json_out.str(), &encoded);
-  *out << encoded;
-}
-
-void HandleTraceJsonPage(const Webserver::ArgumentMap &args,
-                         std::ostringstream* output) {
-  TraceLog* tl = TraceLog::GetInstance();
-  tl->SetEnabled(CategoryFilter(CategoryFilter::kDefaultCategoryFilterString),
-                 TraceLog::RECORDING_MODE,
-                 TraceLog::RECORD_CONTINUOUSLY);
-  SleepFor(MonoDelta::FromSeconds(10));
-  tl->SetDisabled();
-
-  *output << TraceResultBuffer::FlushTraceLogToString();
-}
-
-Status DoHandleRequest(Handler handler,
-                       const Webserver::WebRequest& req,
-                       std::ostringstream* output) {
-  VLOG(2) << "Tracing request type=" << handler << ": " << req.query_string;
-
-  switch (handler) {
-    case kBeginMonitoring:
-      RETURN_NOT_OK(BeginRecording(req, TraceLog::MONITORING_MODE));
-      break;
-    case kCaptureMonitoring:
-      RETURN_NOT_OK(CaptureMonitoring(output));
-      break;
-    case kGetMonitoringStatus:
-      GetMonitoringStatus(output);
-      break;
-    case kCategories:
-      GetCategories(output);
-      break;
-    case kBeginRecording:
-      RETURN_NOT_OK(BeginRecording(req, TraceLog::RECORDING_MODE));
-      break;
-    case kGetBufferPercentFull:
-      *output << TraceLog::GetInstance()->GetBufferPercentFull();
-      break;
-    case kEndMonitoring:
-    case kEndRecording:
-      RETURN_NOT_OK(EndRecording(req, false, output));
-      break;
-    case kEndRecordingCompressed:
-      RETURN_NOT_OK(EndRecording(req, true, output));
-      break;
-    case kSimpleDump:
-      HandleTraceJsonPage(req.parsed_args, output);
-      break;
-  }
-
-  return Status::OK();
-}
-
-
-void HandleRequest(Handler handler,
-                   const Webserver::WebRequest& req,
-                   Webserver::PrerenderedWebResponse* resp) {
-  Status s = DoHandleRequest(handler, req, resp->output);
-  if (!s.ok()) {
-    LOG(WARNING) << "Tracing error for handler " << handler << ": "
-                 << s.ToString();
-    // The trace-viewer JS expects '##ERROR##' to indicate that an error
-    // occurred. TODO: change the JS to bubble up the actual error message
-    // to the user.
-    *resp->output << "##ERROR##";
-  }
-}
-} // anonymous namespace
-
-
-void TracingPathHandlers::RegisterHandlers(Webserver* server) {
-  // All of the tracing-related hand
-  std::map<string, Handler> handlers = {
-    { "/tracing/json/begin_monitoring", kBeginMonitoring },
-    { "/tracing/json/end_monitoring", kEndMonitoring },
-    { "/tracing/json/capture_monitoring", kCaptureMonitoring },
-    { "/tracing/json/get_monitoring_status", kGetMonitoringStatus },
-    { "/tracing/json/categories", kCategories },
-    { "/tracing/json/begin_recording", kBeginRecording },
-    { "/tracing/json/get_buffer_percent_full", kGetBufferPercentFull },
-    { "/tracing/json/end_recording", kEndRecording },
-    { "/tracing/json/end_recording_compressed", kEndRecordingCompressed },
-    { "/tracing/json/simple_dump", kSimpleDump } };
-
-  typedef pair<const string, Handler> HandlerPair;
-  for (const HandlerPair& e : handlers) {
-    server->RegisterPrerenderedPathHandler(
-      e.first, "",
-      boost::bind(&HandleRequest, e.second, _1, _2),
-      false /* styled */, false /* is_on_nav_bar */);
-  }
-}
-
-} // namespace server
-} // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/server/tracing-path-handlers.h
----------------------------------------------------------------------
diff --git a/src/kudu/server/tracing-path-handlers.h b/src/kudu/server/tracing-path-handlers.h
deleted file mode 100644
index 7e1feef..0000000
--- a/src/kudu/server/tracing-path-handlers.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// 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.
-#ifndef KUDU_SERVER_TRACING_PATH_HANDLERS_H
-#define KUDU_SERVER_TRACING_PATH_HANDLERS_H
-
-#include "kudu/gutil/macros.h"
-
-namespace kudu {
-
-class Webserver;
-
-namespace server {
-
-// Web handlers for Chromium tracing.
-// These handlers provide AJAX endpoints for /tracing.html provided by
-// the trace-viewer package.
-class TracingPathHandlers {
- public:
-  static void RegisterHandlers(Webserver* server);
-
-  DISALLOW_IMPLICIT_CONSTRUCTORS(TracingPathHandlers);
-};
-
-} // namespace server
-} // namespace kudu
-#endif /* KUDU_SERVER_TRACING_PATH_HANDLERS_H */

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/server/tracing_path_handlers.cc
----------------------------------------------------------------------
diff --git a/src/kudu/server/tracing_path_handlers.cc b/src/kudu/server/tracing_path_handlers.cc
new file mode 100644
index 0000000..5e57212
--- /dev/null
+++ b/src/kudu/server/tracing_path_handlers.cc
@@ -0,0 +1,285 @@
+// 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.
+#include "kudu/server/tracing_path_handlers.h"
+
+#include <map>
+#include <ostream>
+#include <string>
+#include <utility>
+#include <vector>
+
+
+#include <boost/bind.hpp> // IWYU pragma: keep
+#include <glog/logging.h>
+#include <rapidjson/document.h>
+
+#include "kudu/gutil/strings/escaping.h"
+#include "kudu/server/webserver.h"
+#include "kudu/util/debug/trace_event_impl.h"
+#include "kudu/util/jsonwriter.h"
+#include "kudu/util/monotime.h"
+#include "kudu/util/status.h"
+#include "kudu/util/web_callback_registry.h"
+#include "kudu/util/zlib.h"
+
+using std::map;
+using std::ostringstream;
+using std::pair;
+using std::string;
+using std::vector;
+
+using kudu::debug::CategoryFilter;
+using kudu::debug::TraceLog;
+using kudu::debug::TraceResultBuffer;
+
+namespace kudu {
+namespace server {
+
+enum Handler {
+  kBeginMonitoring,
+  kEndMonitoring,
+  kCaptureMonitoring,
+  kGetMonitoringStatus,
+  kCategories,
+  kBeginRecording,
+  kGetBufferPercentFull,
+  kEndRecording,
+  kEndRecordingCompressed,
+  kSimpleDump
+};
+
+namespace {
+
+Status ParseBase64JsonRequest(const string& json_base64,
+                              rapidjson::Document* doc) {
+  string json_str;
+  if (!strings::Base64Unescape(json_base64, &json_str)) {
+    return Status::InvalidArgument("Invalid base64-encoded JSON");
+  }
+
+  doc->Parse<0>(json_str.c_str());
+  if (!doc->IsObject()) {
+    return Status::InvalidArgument("Invalid JSON", json_str);
+  }
+  return Status::OK();
+}
+
+Status GetTracingOptions(const std::string& json_base64,
+                       std::string* category_filter_string,
+                       int* tracing_options) {
+  rapidjson::Document doc;
+  RETURN_NOT_OK(ParseBase64JsonRequest(json_base64, &doc));
+
+  bool use_continuous_tracing = false;
+  bool use_sampling = false;
+
+  if (!doc.HasMember("categoryFilter") ||
+      !doc["categoryFilter"].IsString()) {
+    return Status::InvalidArgument("missing categoryFilter");
+  }
+  *category_filter_string = doc["categoryFilter"].GetString();
+
+  if (doc.HasMember("useContinuousTracing") &&
+      doc["useContinuousTracing"].IsBool()) {
+    use_continuous_tracing = doc["useContinuousTracing"].GetBool();
+  }
+
+  if (doc.HasMember("useSampling") &&
+      doc["useSampling"].IsBool()) {
+    use_sampling = doc["useSampling"].GetBool();
+  }
+
+  *tracing_options = 0;
+  if (use_sampling)
+    *tracing_options |= TraceLog::ENABLE_SAMPLING;
+  if (use_continuous_tracing)
+    *tracing_options |= TraceLog::RECORD_CONTINUOUSLY;
+  return Status::OK();
+}
+
+Status BeginRecording(const Webserver::WebRequest& req,
+                      TraceLog::Mode mode) {
+  string filter_str;
+  int options;
+  RETURN_NOT_OK(GetTracingOptions(req.query_string, &filter_str, &options));
+
+  kudu::debug::TraceLog::GetInstance()->SetEnabled(
+    CategoryFilter(filter_str),
+    mode,
+    static_cast<TraceLog::Options>(options));
+  return Status::OK();
+}
+
+Status EndRecording(const Webserver::WebRequest& /*req*/,
+                    bool compressed,
+                    ostringstream* out) {
+  TraceLog* tl = TraceLog::GetInstance();
+  tl->SetDisabled();
+  string json = TraceResultBuffer::FlushTraceLogToString();
+
+  if (compressed) {
+    RETURN_NOT_OK_PREPEND(zlib::Compress(json, out),
+                          "Could not compress output");
+  } else {
+    *out << json;
+  }
+
+  return Status::OK();
+}
+
+Status CaptureMonitoring(ostringstream* out) {
+  TraceLog* tl = TraceLog::GetInstance();
+  if (!tl->IsEnabled()) {
+    return Status::IllegalState("monitoring not enabled");
+  }
+  *out << TraceResultBuffer::FlushTraceLogToStringButLeaveBufferIntact();
+  return Status::OK();
+}
+
+void GetCategories(ostringstream* out) {
+  vector<string> groups;
+  kudu::debug::TraceLog::GetInstance()->GetKnownCategoryGroups(&groups);
+  JsonWriter j(out, JsonWriter::COMPACT);
+  j.StartArray();
+  for (const string& g : groups) {
+    j.String(g);
+  }
+  j.EndArray();
+}
+
+void GetMonitoringStatus(ostringstream* out) {
+  TraceLog* tl = TraceLog::GetInstance();
+  bool is_monitoring = tl->IsEnabled();
+  std::string category_filter = tl->GetCurrentCategoryFilter().ToString();
+  int options = static_cast<int>(tl->trace_options());
+
+  ostringstream json_out;
+  JsonWriter j(&json_out, JsonWriter::COMPACT);
+  j.StartObject();
+
+  j.String("isMonitoring");
+  j.Bool(is_monitoring);
+
+  j.String("categoryFilter");
+  j.String(category_filter);
+
+  j.String("useContinuousTracing");
+  j.Bool((options & TraceLog::RECORD_CONTINUOUSLY) != 0);
+
+  j.String("useSampling");
+  j.Bool((options & TraceLog::ENABLE_SAMPLING) != 0);
+
+  j.EndObject();
+
+  string encoded;
+  strings::Base64Escape(json_out.str(), &encoded);
+  *out << encoded;
+}
+
+void HandleTraceJsonPage(const Webserver::ArgumentMap& /*args*/,
+                         std::ostringstream* output) {
+  TraceLog* tl = TraceLog::GetInstance();
+  tl->SetEnabled(CategoryFilter(CategoryFilter::kDefaultCategoryFilterString),
+                 TraceLog::RECORDING_MODE,
+                 TraceLog::RECORD_CONTINUOUSLY);
+  SleepFor(MonoDelta::FromSeconds(10));
+  tl->SetDisabled();
+
+  *output << TraceResultBuffer::FlushTraceLogToString();
+}
+
+Status DoHandleRequest(Handler handler,
+                       const Webserver::WebRequest& req,
+                       std::ostringstream* output) {
+  VLOG(2) << "Tracing request type=" << handler << ": " << req.query_string;
+
+  switch (handler) {
+    case kBeginMonitoring:
+      RETURN_NOT_OK(BeginRecording(req, TraceLog::MONITORING_MODE));
+      break;
+    case kCaptureMonitoring:
+      RETURN_NOT_OK(CaptureMonitoring(output));
+      break;
+    case kGetMonitoringStatus:
+      GetMonitoringStatus(output);
+      break;
+    case kCategories:
+      GetCategories(output);
+      break;
+    case kBeginRecording:
+      RETURN_NOT_OK(BeginRecording(req, TraceLog::RECORDING_MODE));
+      break;
+    case kGetBufferPercentFull:
+      *output << TraceLog::GetInstance()->GetBufferPercentFull();
+      break;
+    case kEndMonitoring:
+    case kEndRecording:
+      RETURN_NOT_OK(EndRecording(req, false, output));
+      break;
+    case kEndRecordingCompressed:
+      RETURN_NOT_OK(EndRecording(req, true, output));
+      break;
+    case kSimpleDump:
+      HandleTraceJsonPage(req.parsed_args, output);
+      break;
+  }
+
+  return Status::OK();
+}
+
+
+void HandleRequest(Handler handler,
+                   const Webserver::WebRequest& req,
+                   Webserver::PrerenderedWebResponse* resp) {
+  Status s = DoHandleRequest(handler, req, resp->output);
+  if (!s.ok()) {
+    LOG(WARNING) << "Tracing error for handler " << handler << ": "
+                 << s.ToString();
+    // The trace-viewer JS expects '##ERROR##' to indicate that an error
+    // occurred. TODO: change the JS to bubble up the actual error message
+    // to the user.
+    *resp->output << "##ERROR##";
+  }
+}
+} // anonymous namespace
+
+
+void TracingPathHandlers::RegisterHandlers(Webserver* server) {
+  // All of the tracing-related hand
+  std::map<string, Handler> handlers = {
+    { "/tracing/json/begin_monitoring", kBeginMonitoring },
+    { "/tracing/json/end_monitoring", kEndMonitoring },
+    { "/tracing/json/capture_monitoring", kCaptureMonitoring },
+    { "/tracing/json/get_monitoring_status", kGetMonitoringStatus },
+    { "/tracing/json/categories", kCategories },
+    { "/tracing/json/begin_recording", kBeginRecording },
+    { "/tracing/json/get_buffer_percent_full", kGetBufferPercentFull },
+    { "/tracing/json/end_recording", kEndRecording },
+    { "/tracing/json/end_recording_compressed", kEndRecordingCompressed },
+    { "/tracing/json/simple_dump", kSimpleDump } };
+
+  typedef pair<const string, Handler> HandlerPair;
+  for (const HandlerPair& e : handlers) {
+    server->RegisterPrerenderedPathHandler(
+      e.first, "",
+      boost::bind(&HandleRequest, e.second, _1, _2),
+      false /* styled */, false /* is_on_nav_bar */);
+  }
+}
+
+} // namespace server
+} // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/server/tracing_path_handlers.h
----------------------------------------------------------------------
diff --git a/src/kudu/server/tracing_path_handlers.h b/src/kudu/server/tracing_path_handlers.h
new file mode 100644
index 0000000..7e1feef
--- /dev/null
+++ b/src/kudu/server/tracing_path_handlers.h
@@ -0,0 +1,40 @@
+// 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.
+#ifndef KUDU_SERVER_TRACING_PATH_HANDLERS_H
+#define KUDU_SERVER_TRACING_PATH_HANDLERS_H
+
+#include "kudu/gutil/macros.h"
+
+namespace kudu {
+
+class Webserver;
+
+namespace server {
+
+// Web handlers for Chromium tracing.
+// These handlers provide AJAX endpoints for /tracing.html provided by
+// the trace-viewer package.
+class TracingPathHandlers {
+ public:
+  static void RegisterHandlers(Webserver* server);
+
+  DISALLOW_IMPLICIT_CONSTRUCTORS(TracingPathHandlers);
+};
+
+} // namespace server
+} // namespace kudu
+#endif /* KUDU_SERVER_TRACING_PATH_HANDLERS_H */

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/server/webserver-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/server/webserver-test.cc b/src/kudu/server/webserver-test.cc
index d8b3ef4..ca80822 100644
--- a/src/kudu/server/webserver-test.cc
+++ b/src/kudu/server/webserver-test.cc
@@ -31,7 +31,7 @@
 #include "kudu/gutil/strings/util.h"
 #include "kudu/security/test/test_certs.h"
 #include "kudu/security/test/test_pass.h"
-#include "kudu/server/default-path-handlers.h"
+#include "kudu/server/default_path_handlers.h"
 #include "kudu/server/webserver.h"
 #include "kudu/server/webserver_options.h"
 #include "kudu/util/curl_util.h"

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/tserver/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/src/kudu/tserver/CMakeLists.txt b/src/kudu/tserver/CMakeLists.txt
index c848f95..e062e2d 100644
--- a/src/kudu/tserver/CMakeLists.txt
+++ b/src/kudu/tserver/CMakeLists.txt
@@ -114,7 +114,7 @@ set(TSERVER_SRCS
   tablet_server_options.cc
   tablet_service.cc
   ts_tablet_manager.cc
-  tserver-path-handlers.cc
+  tserver_path_handlers.cc
 )
 
 add_library(tserver ${TSERVER_SRCS})

http://git-wip-us.apache.org/repos/asf/kudu/blob/1c90b2f5/src/kudu/tserver/tablet_server.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tserver/tablet_server.cc b/src/kudu/tserver/tablet_server.cc
index 2beea2b..95b2b1e 100644
--- a/src/kudu/tserver/tablet_server.cc
+++ b/src/kudu/tserver/tablet_server.cc
@@ -36,7 +36,7 @@
 #include "kudu/tserver/tablet_copy_service.h"
 #include "kudu/tserver/tablet_service.h"
 #include "kudu/tserver/ts_tablet_manager.h"
-#include "kudu/tserver/tserver-path-handlers.h"
+#include "kudu/tserver/tserver_path_handlers.h"
 #include "kudu/util/maintenance_manager.h"
 #include "kudu/util/net/net_util.h"
 #include "kudu/util/status.h"