This is an automated email from the ASF dual-hosted git repository. adar pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/kudu.git
commit 03cbd7559cf24e916924ddd747438f8f7e362cde Author: Adar Dembo <[email protected]> AuthorDate: Wed Oct 16 16:44:38 2019 -0700 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 <[email protected]> Reviewed-by: Alexey Serbin <[email protected]> --- src/kudu/tserver/tserver_path_handlers.cc | 42 ++--------- src/kudu/tserver/tserver_path_handlers.h | 6 +- src/kudu/util/thread.cc | 119 +++++++++++++++--------------- www/dashboards.mustache | 27 +++++++ www/kudu.js | 27 +++++-- www/tablets.mustache | 10 ++- www/threadz.mustache | 68 +++++++++++++++++ 7 files changed, 192 insertions(+), 107 deletions(-) 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 @@ bool CompareByMemberType(const RaftPeerPB& a, const RaftPeerPB& b) { 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 @@ Status TabletServerPathHandlers::Register(Webserver* server) { "/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 @@ void TabletServerPathHandlers::HandleTabletsPage(const Webserver::WebRequest& /* 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::HandleScansPage(const Webserver::WebRequest& /*re } 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 @@ class TabletServerPathHandlers { 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 @@ class ThreadMgr { 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 @@ class ThreadMgr { // 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 @@ Status ThreadMgr::StartInstrumentation(const scoped_refptr<MetricEntity>& metric 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 @@ void ThreadMgr::RemoveThread(const pthread_t& pthread_id, const string& category 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 @@ function bytesSorter(left, right) { 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 @@ function timesSorter(left, right) { } // 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 @@ There are no tablet replicas. {{#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 @@ There are no tablet replicas. {{#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}}
