blob: 7574def7acb9c2ea037b195f88ad141d52309d36 [file] [log] [blame]
// 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>
#ifdef TCMALLOC_ENABLED
#include <gperftools/malloc_extension.h>
#endif
#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/array_view.h"
#include "kudu/util/debug-util.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/monotime.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 indicates that redaction is enabled for the web UI, 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 "/stacks".
//
// Prints out the current stack trace of all threads in the process.
static void StacksHandler(const Webserver::WebRequest& /*req*/,
Webserver::PrerenderedWebResponse* resp) {
std::ostringstream* output = resp->output;
StackTraceSnapshot snap;
auto start = MonoTime::Now();
Status s = snap.SnapshotAllStacks();
if (!s.ok()) {
*output << "Failed to collect stacks: " << s.ToString();
return;
}
auto dur = MonoTime::Now() - start;
*output << "Collected stacks from " << snap.num_threads() << " threads in "
<< dur.ToString() << "\n";
if (snap.num_failed()) {
*output << "Failed to collect stacks from " << snap.num_failed() << " threads "
<< "(they may have exited while we were iterating over the threads)\n";
}
*output << "\n";
snap.VisitGroups([&](ArrayView<StackTraceSnapshot::ThreadInfo> threads) {
if (threads.size() > 1) {
*output << threads.size() << " threads with same stack:\n";
}
for (auto& info : threads) {
*output << "TID " << info.tid << "(" << info.thread_name << "):\n";
}
*output << threads[0].stack.Symbolize() << "\n\n";
});
}
// 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);
webserver->RegisterPrerenderedPathHandler("/stacks", "Stacks", StacksHandler,
/*is_styled=*/false,
/*is_on_nav_bar=*/false);
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