www: miscellaneous mustache updates
1. Converted /dashboards and /threadz into mustache templates. In /threadz,
I tried to defer as much work as possible outside the lock, since it must
be taken in write mode to create a new thread. While I was there I added
"fancy tables" to /threadz; /dashboards remains visually unchanged.
2. In /tablets, changed the handling of tablet links. In doing so, all links
(except for the main template) are now in templates instead of in code.
Screenshots: https://imgur.com/a/AOTQuH4
Change-Id: Idb97a9e3bbefb8ee607638af6e069959c5354225
Reviewed-on: http://gerrit.cloudera.org:8080/14473
Tested-by: Kudu Jenkins
Reviewed-by: Andrew Wong <awong@cloudera.com>
Reviewed-by: Alexey Serbin <aserbin@cloudera.com>
diff --git a/src/kudu/tserver/tserver_path_handlers.cc b/src/kudu/tserver/tserver_path_handlers.cc
index 8030054..d51f27f 100644
--- a/src/kudu/tserver/tserver_path_handlers.cc
+++ b/src/kudu/tserver/tserver_path_handlers.cc
@@ -104,12 +104,6 @@
return a.member_type() < b.member_type();
}
-string TabletLink(const string& id) {
- return Substitute("<a href=\"/tablet?id=$0\">$1</a>",
- UrlEncodeToString(id),
- EscapeForHtmlToString(id));
-}
-
bool IsTombstoned(const scoped_refptr<TabletReplica>& replica) {
return replica->data_state() == tablet::TABLET_DATA_TOMBSTONED;
}
@@ -225,7 +219,7 @@
"/log-anchors", "",
boost::bind(&TabletServerPathHandlers::HandleLogAnchorsPage, this, _1, _2),
true /* styled */, false /* is_on_nav_bar */);
- server->RegisterPrerenderedPathHandler(
+ server->RegisterPathHandler(
"/dashboards", "Dashboards",
boost::bind(&TabletServerPathHandlers::HandleDashboardsPage, this, _1, _2),
true /* styled */, true /* is_on_nav_bar */);
@@ -342,15 +336,15 @@
EasyJson details_json = replicas_json->Set("replicas", EasyJson::kArray);
for (const scoped_refptr<TabletReplica>& replica : replicas) {
EasyJson replica_json = details_json.PushBack(EasyJson::kObject);
- const auto* tablet = replica->tablet();
const auto& tmeta = replica->tablet_metadata();
TabletStatusPB status;
replica->GetTabletStatusPB(&status);
replica_json["table_name"] = status.table_name();
- if (tablet != nullptr) {
- replica_json["id_or_link"] = TabletLink(status.tablet_id());
- } else {
- replica_json["id_or_link"] = status.tablet_id();
+ replica_json["id"] = status.tablet_id();
+ if (replica->tablet() != nullptr) {
+ EasyJson link_json = replica_json.Set("link", EasyJson::kObject);
+ link_json["id"] = status.tablet_id();
+ link_json["url"] = Substitute("/tablet?id=$0", UrlEncodeToString(status.tablet_id()));
}
replica_json["partition"] =
tmeta->partition_schema().PartitionDebugString(tmeta->partition(),
@@ -588,29 +582,7 @@
}
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 currently running and recently "
- "completed scans.");
- *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));
+ Webserver::WebResponse* /*resp*/) {
}
void TabletServerPathHandlers::HandleMaintenanceManagerPage(const Webserver::WebRequest& req,
diff --git a/src/kudu/tserver/tserver_path_handlers.h b/src/kudu/tserver/tserver_path_handlers.h
index 7b7beed..7cf370b 100644
--- a/src/kudu/tserver/tserver_path_handlers.h
+++ b/src/kudu/tserver/tserver_path_handlers.h
@@ -17,8 +17,6 @@
#ifndef KUDU_TSERVER_TSERVER_PATH_HANDLERS_H
#define KUDU_TSERVER_TSERVER_PATH_HANDLERS_H
-#include <string>
-
#include "kudu/gutil/macros.h"
#include "kudu/server/webserver.h"
#include "kudu/util/status.h"
@@ -54,11 +52,9 @@
void HandleConsensusStatusPage(const Webserver::WebRequest& req,
Webserver::WebResponse* resp);
void HandleDashboardsPage(const Webserver::WebRequest& req,
- Webserver::PrerenderedWebResponse* resp);
+ Webserver::WebResponse* resp);
void HandleMaintenanceManagerPage(const Webserver::WebRequest& req,
Webserver::WebResponse* resp);
- std::string GetDashboardLine(const std::string& link,
- const std::string& text, const std::string& desc);
TabletServer* tserver_;
diff --git a/src/kudu/util/thread.cc b/src/kudu/util/thread.cc
index 1c16763..9f5eb01 100644
--- a/src/kudu/util/thread.cc
+++ b/src/kudu/util/thread.cc
@@ -28,7 +28,6 @@
#include <algorithm>
#include <cerrno>
#include <cstring>
-#include <map>
#include <memory>
#include <mutex>
#include <sstream>
@@ -50,6 +49,7 @@
#include "kudu/gutil/once.h"
#include "kudu/gutil/port.h"
#include "kudu/gutil/strings/substitute.h"
+#include "kudu/util/easy_json.h"
#include "kudu/util/env.h"
#include "kudu/util/flag_tags.h"
#include "kudu/util/kernel_stack_watchdog.h"
@@ -66,8 +66,8 @@
using boost::bind;
using boost::mem_fn;
-using std::map;
using std::ostringstream;
+using std::pair;
using std::shared_ptr;
using std::string;
using std::vector;
@@ -200,6 +200,12 @@
const string& category() const { return category_; }
int64_t thread_id() const { return thread_id_; }
+ struct Comparator {
+ bool operator()(const ThreadDescriptor& rhs, const ThreadDescriptor& lhs) const {
+ return rhs.name() < lhs.name();
+ }
+ };
+
private:
string name_;
string category_;
@@ -241,9 +247,9 @@
// Webpage callback; prints all threads by category.
void ThreadPathHandler(const WebCallbackRegistry::WebRequest& req,
- WebCallbackRegistry::PrerenderedWebResponse* resp) const;
- void PrintThreadDescriptorRow(const ThreadDescriptor& desc,
- ostringstream* output) const;
+ WebCallbackRegistry::WebResponse* resp) const;
+ void SummarizeThreadDescriptor(const ThreadDescriptor& desc,
+ EasyJson* output) const;
};
void ThreadMgr::SetThreadName(const string& name, int64_t tid) {
@@ -295,11 +301,11 @@
Bind(&GetInVoluntaryContextSwitches)));
if (web) {
- WebCallbackRegistry::PrerenderedPathHandlerCallback thread_callback =
- bind<void>(mem_fn(&ThreadMgr::ThreadPathHandler), this, _1, _2);
- DCHECK_NOTNULL(web)->RegisterPrerenderedPathHandler("/threadz", "Threads", thread_callback,
- true /* is_styled*/,
- true /* is_on_nav_bar */);
+ auto thread_callback = bind<void>(mem_fn(&ThreadMgr::ThreadPathHandler),
+ this, _1, _2);
+ DCHECK_NOTNULL(web)->RegisterPathHandler("/threadz", "Threads", thread_callback,
+ /* is_styled= */ true,
+ /* is_on_nav_bar= */ true);
}
return Status::OK();
}
@@ -367,84 +373,81 @@
ANNOTATE_IGNORE_READS_AND_WRITES_END();
}
-void ThreadMgr::PrintThreadDescriptorRow(const ThreadDescriptor& desc,
- ostringstream* output) const {
+void ThreadMgr::SummarizeThreadDescriptor(const ThreadDescriptor& desc,
+ EasyJson* output) const {
ThreadStats stats;
Status status = GetThreadStats(desc.thread_id(), &stats);
if (!status.ok()) {
KLOG_EVERY_N(INFO, 100) << "Could not get per-thread statistics: "
<< status.ToString();
}
- (*output) << "<tr><td>" << desc.name() << "</td><td>"
- << (static_cast<double>(stats.user_ns) / 1e9) << "</td><td>"
- << (static_cast<double>(stats.kernel_ns) / 1e9) << "</td><td>"
- << (static_cast<double>(stats.iowait_ns) / 1e9) << "</td></tr>";
+ EasyJson thr = output->PushBack(EasyJson::kObject);
+ thr["thread_name"] = desc.name();
+ thr["user_sec"] = static_cast<double>(stats.user_ns) / 1e9;
+ thr["kernel_sec"] = static_cast<double>(stats.kernel_ns) / 1e9;
+ thr["iowait_sec"] = static_cast<double>(stats.iowait_ns) / 1e9;
}
-void ThreadMgr::ThreadPathHandler(
- const WebCallbackRegistry::WebRequest& req,
- WebCallbackRegistry::PrerenderedWebResponse* resp) const {
- ostringstream& output = resp->output;
- vector<ThreadDescriptor> descriptors_to_print;
- const auto category_name = req.parsed_args.find("group");
- if (category_name != req.parsed_args.end()) {
- const auto& group = category_name->second;
- const auto& group_esc = EscapeForHtmlToString(group);
- output << "<h2>Thread Group: " << group_esc << "</h2>";
- if (group != "all") {
+void ThreadMgr::ThreadPathHandler(const WebCallbackRegistry::WebRequest& req,
+ WebCallbackRegistry::WebResponse* resp) const {
+ EasyJson& output = resp->output;
+ const auto* category_name = FindOrNull(req.parsed_args, "group");
+ if (category_name) {
+ // List all threads belonging to the desired thread group.
+ bool requested_all = *category_name == "all";
+ EasyJson rtg = output.Set("requested_thread_group", EasyJson::kObject);
+ rtg["group_name"] = EscapeForHtmlToString(*category_name);
+ rtg["requested_all"] = requested_all;
+
+ // The critical section is as short as possible so as to minimize the delay
+ // imposed on new threads that acquire the lock in write mode.
+ vector<ThreadDescriptor> descriptors_to_print;
+ if (!requested_all) {
shared_lock<decltype(lock_)> l(lock_);
- const auto it = thread_categories_.find(group);
- if (it == thread_categories_.end()) {
- output << "Thread group '" << group_esc << "' not found";
+ const auto* category = FindOrNull(thread_categories_, *category_name);
+ if (!category) {
return;
}
- for (const auto& elem : it->second) {
- descriptors_to_print.push_back(elem.second);
+ for (const auto& elem : *category) {
+ descriptors_to_print.emplace_back(elem.second);
}
- output << "<h3>" << it->first << " : " << it->second.size() << "</h3>";
} else {
shared_lock<decltype(lock_)> l(lock_);
for (const auto& category : thread_categories_) {
for (const auto& elem : category.second) {
- descriptors_to_print.push_back(elem.second);
+ descriptors_to_print.emplace_back(elem.second);
}
}
- output << "<h3>All Threads : </h3>";
}
- output << "<table class='table table-hover table-border'>"
- "<thead><tr><th>Thread name</th><th>Cumulative User CPU(s)</th>"
- "<th>Cumulative Kernel CPU(s)</th>"
- "<th>Cumulative IO-wait(s)</th></tr></thead>"
- "<tbody>\n";
- // Sort the entries in the table by the name of a thread.
- // TODO(aserbin): use "mustache + fancy table" instead.
- std::sort(descriptors_to_print.begin(), descriptors_to_print.end(),
- [](const ThreadDescriptor& lhs, const ThreadDescriptor& rhs) {
- return lhs.name() < rhs.name();
- });
+
+ EasyJson found = rtg.Set("found", EasyJson::kObject);
+ EasyJson threads = found.Set("threads", EasyJson::kArray);
for (const auto& desc : descriptors_to_print) {
- PrintThreadDescriptorRow(desc, &output);
+ SummarizeThreadDescriptor(desc, &threads);
}
- output << "</tbody></table>";
} else {
- // Using the tree map (std::map) to have the list of the thread categories
- // at the '/threadz' page sorted alphabetically.
- // TODO(aserbin): use "mustache + fancy table" instead.
- map<string, size_t> thread_categories_info;
+ // List all thread groups and the number of threads running in each.
+ vector<pair<string, size_t>> thread_categories_info;
+ uint64_t running;
{
+ // See comment above regarding short critical sections.
shared_lock<decltype(lock_)> l(lock_);
- output << "<h2>Thread Groups</h2>"
- "<h4>" << threads_running_metric_ << " thread(s) running"
- "<a href='/threadz?group=all'><h3>All Threads</h3>";
+ running = threads_running_metric_;
+ thread_categories_info.reserve(thread_categories_.size());
for (const auto& category : thread_categories_) {
- thread_categories_info.emplace(category.first, category.second.size());
+ thread_categories_info.emplace_back(category.first, category.second.size());
}
}
+
+ output["total_threads_running"] = running;
+ EasyJson groups = output.Set("groups", EasyJson::kArray);
for (const auto& elem : thread_categories_info) {
string category_arg;
UrlEncode(elem.first, &category_arg);
- output << "<a href='/threadz?group=" << category_arg << "'><h3>"
- << elem.first << " : " << elem.second << "</h3></a>";
+ EasyJson g = groups.PushBack(EasyJson::kObject);
+ g["encoded_group_name"] = category_arg;
+ g["group_name"] = elem.first;
+ g["threads_running"] = elem.second;
}
}
}
diff --git a/www/dashboards.mustache b/www/dashboards.mustache
new file mode 100644
index 0000000..d86ae27
--- /dev/null
+++ b/www/dashboards.mustache
@@ -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.
+}}
+
+<h3>Dashboards</h3>
+<table class='table table-striped'>
+ <thead><tr><th>Dashboard</th><th>Description</th></tr></thead>
+ <tbody
+ <tr><td><a href="/scans">Scans</a></td><td>List of currently running and recently completed scans.</td></tr>
+ <tr><td><a href="/transactions">Transactions</a></td><td>List of transactions that are currently running.</td></tr>
+ <tr><td><a href="/maintenance-manager">Maintenance Manager</a></td><td>List of operations that are currently running and those that are registered.</td></tr>
+</tbody></table>
diff --git a/www/kudu.js b/www/kudu.js
index 2e7df97..9f02320 100644
--- a/www/kudu.js
+++ b/www/kudu.js
@@ -67,8 +67,21 @@
return 0;
}
+// A comparison function for floating point numbers.
+function floatsSorter(left, right) {
+ left_float = parseFloat(left)
+ right_float = parseFloat(right)
+ if (left_float < right_float) {
+ return -1;
+ }
+ if (left_float > right_float) {
+ return 1;
+ }
+ return 0;
+}
+
// Converts numeric strings to numbers and then compares them.
-function compareNumericStrings(left, right) {
+function numericStringsSorter(left, right) {
left_num = parseInt(left, 10);
right_num = parseInt(right, 10);
if (left_num < right_num) {
@@ -103,32 +116,32 @@
}
// Year.
- var ret = compareNumericStrings(left.substr(0, 4), right.substr(0, 4));
+ var ret = numericStringsSorter(left.substr(0, 4), right.substr(0, 4));
if (ret != 0) {
return ret;
}
// Month.
- ret = compareNumericStrings(left.substr(5, 2), right.substr(5, 2));
+ ret = numericStringsSorter(left.substr(5, 2), right.substr(5, 2));
if (ret != 0) {
return ret;
}
// Day.
- ret = compareNumericStrings(left.substr(8, 2), right.substr(8, 2));
+ ret = numericStringsSorter(left.substr(8, 2), right.substr(8, 2));
if (ret != 0) {
return ret;
}
// Hour.
- ret = compareNumericStrings(left.substr(11, 2), right.substr(11, 2));
+ ret = numericStringsSorter(left.substr(11, 2), right.substr(11, 2));
if (ret != 0) {
return ret;
}
// Minute.
- ret = compareNumericStrings(left.substr(14, 2), right.substr(14, 2));
+ ret = numericStringsSorter(left.substr(14, 2), right.substr(14, 2));
if (ret != 0) {
return ret;
}
// Second.
- ret = compareNumericStrings(left.substr(17, 2), right.substr(17, 2));
+ ret = numericStringsSorter(left.substr(17, 2), right.substr(17, 2));
if (ret != 0) {
return ret;
}
diff --git a/www/tablets.mustache b/www/tablets.mustache
index d7eee69..2a54ada 100644
--- a/www/tablets.mustache
+++ b/www/tablets.mustache
@@ -47,7 +47,10 @@
{{#replicas}}
<tr>
<td>{{table_name}}</td>
- <td>{{{id_or_link}}}</td>
+ <td>
+ {{#link}}<a href="{{url}}">{{id}}</a>{{/link}}
+ {{^link}}{{id}}{{/link}}
+ </td>
<td>{{partition}}</td>
<td>{{state}}</td>
<td>{{n_bytes}}</td>
@@ -90,7 +93,10 @@
{{#replicas}}
<tr>
<td>{{table_name}}</td>
- <td>{{{id_or_link}}}</td>
+ <td>
+ {{#link}}<a href="{{url}}">{{id}}</a>{{/link}}
+ {{^link}}{{id}}{{/link}}
+ </td>
<td>{{partition}}</td>
<td>{{state}}</td>
<td>{{n_bytes}}</td>
diff --git a/www/threadz.mustache b/www/threadz.mustache
new file mode 100644
index 0000000..efabdd7
--- /dev/null
+++ b/www/threadz.mustache
@@ -0,0 +1,68 @@
+{{!
+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.
+}}
+
+{{#requested_thread_group}}
+<h2>Thread Group: {{group_name}}</h2>
+{{#requested_all}}<h3>All Threads : </h3>{{/requested_all}}
+{{#found}}
+<table class='table table-hover' data-sort-name='name' data-toggle='table'>
+ <thead>
+ <tr>
+ <th data-field='name' data-sortable='true' data-sorter='stringsSorter'>Thread name</th>
+ <th data-sortable='true' data-sorter='floatsSorter'>Cumulative User CPU (s)</th>
+ <th data-sortable='true' data-sorter='floatsSorter'>Cumulative Kernel CPU (s)</th>
+ <th data-sortable='true' data-sorter='floatsSorter'>Cumulative IO-wait (s)</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#threads}}
+ <tr>
+ <td>{{thread_name}}</td>
+ <td>{{user_sec}}</td>
+ <td>{{kernel_sec}}</td>
+ <td>{{iowait_sec}}</td>
+ </tr>
+ {{/threads}}
+ </tbody>
+</table>
+{{/found}}
+{{^found}}Thread group {{group_name}} not found{{/found}}
+{{/requested_thread_group}}
+
+{{^requested_thread_group}}
+<h2>Thread Groups</h2>
+<h4>{{total_threads_running}} thread(s) running</h4>
+<a href='/threadz?group=all'><h3>All Threads</h3></a>
+<table class='table table-hover' data-sort-name='group' data-toggle='table'>
+ <thead>
+ <tr>
+ <th data-field='group' data-sortable='true' data-sorter='stringsSorter'>Group</th>
+ <th data-sortable='true' data-sorter='numericStringsSorter'>Threads running</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#groups}}
+ <tr>
+ <td><a href='/threadz?group={{encoded_group_name}}'>{{group_name}}</a></td>
+ <td>{{threads_running}}</td>
+ </tr>
+ {{/groups}}
+ </tbody>
+</table>
+{{/requested_thread_group}}