// 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 "http/default_path_handlers.h" #include #ifdef USE_JEMALLOC #include "jemalloc/jemalloc.h" #else #include #endif #include #include #include "agent/utils.h" #include "common/configbase.h" #include "gutil/strings/numbers.h" #include "gutil/strings/substitute.h" #include "http/action/tablets_info_action.h" #include "http/web_page_handler.h" #include "runtime/memory/mem_tracker_limiter.h" #include "util/debug_util.h" #include "util/perf_counters.h" #include "util/pretty_printer.h" #include "util/thread.h" using std::vector; using std::shared_ptr; using std::string; namespace doris { // Writes the last config::web_log_bytes of the INFO logfile to a webpage // Note to get best performance, set GLOG_logbuflevel=-1 to prevent log buffering void logs_handler(const WebPageHandler::ArgumentMap& args, std::stringstream* output) { /*std::string logfile; get_full_log_filename(google::INFO, &logfile); (*output) << "

INFO logs

" << std::endl; (*output) << "Log path is: " << logfile << std::endl; struct stat file_stat; if (stat(logfile.c_str(), &file_stat) == 0) { long size = file_stat.st_size; long seekpos = size < config::web_log_bytes ? 0L : size - config::web_log_bytes; std::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) << "
Showing last " << config::web_log_bytes << " bytes of log" << std::endl; (*output) << "
" << log.rdbuf() << "
"; } else { (*output) << "
Couldn't open INFO log file: " << logfile; }*/ (*output) << "
Couldn't open INFO log file: "; } // Registered to handle "/varz", and prints out all command-line flags and their values void config_handler(const WebPageHandler::ArgumentMap& args, std::stringstream* output) { (*output) << "

Configurations

"; (*output) << "
";
    std::lock_guard lock(*config::get_mutable_string_config_lock());
    for (const auto& it : *(config::full_conf_map)) {
        (*output) << it.first << "=" << it.second << std::endl;
    }
    (*output) << "
"; } // Registered to handle "/memz", and prints out memory allocation statistics. void mem_usage_handler(const WebPageHandler::ArgumentMap& args, std::stringstream* output) { (*output) << "
"
              << "Mem Limit: " << PrettyPrinter::print(MemInfo::mem_limit(), TUnit::BYTES)
              << std::endl
              << "Physical Mem From Perf: "
              << PrettyPrinter::print(PerfCounters::get_vm_rss(), TUnit::BYTES) << std::endl
              << "
"; (*output) << "
";
#if defined(ADDRESS_SANITIZER) || defined(LEAK_SANITIZER) || defined(THREAD_SANITIZER)
    (*output) << "Memory tracking is not available with address sanitizer builds.";
#elif defined(USE_JEMALLOC)
    std::string tmp;
    auto write_cb = [](void* opaque, const char* buf) {
        auto* _opaque = static_cast(opaque);
        _opaque->append(buf);
    };
    je_malloc_stats_print(write_cb, &tmp, "a");
    boost::replace_all(tmp, "\n", "
"); (*output) << tmp << "
"; #else char buf[2048]; MallocExtension::instance()->GetStats(buf, 2048); // Replace new lines with
for html std::string tmp(buf); boost::replace_all(tmp, "\n", "
"); (*output) << tmp << ""; #endif } void display_tablets_callback(const WebPageHandler::ArgumentMap& args, EasyJson* ej) { TabletsInfoAction tablet_info_action; std::string tablet_num_to_return; WebPageHandler::ArgumentMap::const_iterator it = args.find("limit"); if (it != args.end()) { tablet_num_to_return = it->second; } else { tablet_num_to_return = "1000"; // default } (*ej) = tablet_info_action.get_tablets_info(tablet_num_to_return); } // Registered to handle "/mem_tracker", and prints out memory tracker information. void mem_tracker_handler(const WebPageHandler::ArgumentMap& args, std::stringstream* output) { (*output) << "

Memory usage by subsystem

\n"; std::vector snapshots; auto iter = args.find("type"); if (iter != args.end()) { if (iter->second == "global") { MemTrackerLimiter::make_type_snapshots(&snapshots, MemTrackerLimiter::Type::GLOBAL); } else if (iter->second == "query") { MemTrackerLimiter::make_type_snapshots(&snapshots, MemTrackerLimiter::Type::QUERY); } else if (iter->second == "load") { MemTrackerLimiter::make_type_snapshots(&snapshots, MemTrackerLimiter::Type::LOAD); } else if (iter->second == "compaction") { MemTrackerLimiter::make_type_snapshots(&snapshots, MemTrackerLimiter::Type::COMPACTION); } else if (iter->second == "schema_change") { MemTrackerLimiter::make_type_snapshots(&snapshots, MemTrackerLimiter::Type::SCHEMA_CHANGE); } else if (iter->second == "clone") { MemTrackerLimiter::make_type_snapshots(&snapshots, MemTrackerLimiter::Type::CLONE); } else if (iter->second == "experimental") { MemTrackerLimiter::make_type_snapshots(&snapshots, MemTrackerLimiter::Type::EXPERIMENTAL); } } else { (*output) << "

*Note: (see documentation for details)

\n"; (*output) << "

1.`/mem_tracker?type=global` to view the memory statistics of each " "type, `global`life cycle is the same as the process, e.g. each Cache, " "StorageEngine, each Manager.

\n"; (*output) << "

2.`/mem_tracker` counts virtual memory, which is equal to `Actual " "memory used` in `/memz`

\n"; (*output) << "

3.`process` is equal to the sum of all types of memory, " "`/mem_tracker` can be logically divided into 4 layers: 1)`process` 2)`type` " "3)`query/load/compation task etc.` 4)`exec node etc.`

\n"; MemTrackerLimiter::make_process_snapshots(&snapshots); } (*output) << "\n"; (*output) << "" "" "" "" "" "" "" "" "" ""; (*output) << "\n"; for (const auto& item : snapshots) { string limit_str = item.limit == -1 ? "none" : AccurateItoaKMGT(item.limit); string current_consumption_normalize = AccurateItoaKMGT(item.cur_consumption); string peak_consumption_normalize = AccurateItoaKMGT(item.peak_consumption); (*output) << strings::Substitute( "\n", item.type, item.label, item.parent_label, limit_str, item.cur_consumption, current_consumption_normalize, item.peak_consumption, peak_consumption_normalize); } (*output) << "
TypeLabelParent LabelLimitCurrent Consumption(Bytes)Current Consumption(Normalize)Peak Consumption(Bytes)Peak Consumption(Normalize)
$0$1$2$3$4$5$6$7
\n"; } void heap_handler(const WebPageHandler::ArgumentMap& args, std::stringstream* output) { (*output) << "

Heap Profile

" << std::endl; #if defined(ADDRESS_SANITIZER) || defined(LEAK_SANITIZER) || defined(THREAD_SANITIZER) || \ defined(USE_JEMALLOC) (*output) << "
" << std::endl;
    (*output) << "Heap profiling is not available with address sanitizer builds." << std::endl;
    (*output) << "
" << std::endl; return; #else (*output) << "
" << std::endl;
    (*output) << "Heap profiling will use pprof tool to sample and get heap profile. It will take "
                 "30 seconds"
              << std::endl;
    (*output) << "(Only one thread can obtain profile at the same time)" << std::endl;
    (*output) << std::endl;
    (*output) << "If you want to get the Heap profile, you need to install gperftools-2.0 on the "
                 "host machine,"
              << std::endl;
    (*output) << "and make sure there is a 'pprof' executable file in the system PATH or "
                 "'be/tools/bin/' directory."
              << std::endl;
    (*output) << "Doris will obtain Profile in the following ways:" << std::endl;
    (*output) << std::endl;
    (*output) << "    curl http://localhost:" << config::webserver_port
              << "/pprof/heap?seconds=30 > perf.data" << std::endl;
    (*output) << "    pprof --text be/lib/doris_be perf.data" << std::endl;
    (*output) << std::endl;
    (*output) << "
" << std::endl; (*output) << "
" << std::endl; (*output) << "
" << std::endl; (*output) << "
" << std::endl; (*output) << "
" << std::endl; (*output) << "
" << std::endl; (*output) << "" << std::endl; return; #endif } void cpu_handler(const WebPageHandler::ArgumentMap& args, std::stringstream* output) { (*output) << "

CPU Profile

" << std::endl; #if defined(ADDRESS_SANITIZER) || defined(LEAK_SANITIZER) || defined(THREAD_SANITIZER) || \ defined(USE_JEMALLOC) (*output) << "
" << std::endl;
    (*output) << "CPU profiling is not available with address sanitizer builds." << std::endl;
    (*output) << "
" << std::endl; return; #else (*output) << "
" << std::endl;
    (*output) << "CPU profiling will use perf tool to sample and get CPU profile. It will take 30 "
                 "seconds"
              << std::endl;
    (*output) << "(Only one thread can obtain profile at the same time)" << std::endl;
    (*output) << std::endl;
    (*output) << "If you want to get the CPU profile in text form, you need to install "
                 "gperftools-2.0 on the host machine,"
              << std::endl;
    (*output) << "and make sure there is a 'pprof' executable file in the system PATH or "
                 "'be/tools/bin/' directory."
              << std::endl;
    (*output) << "Doris will obtain Profile in the following ways:" << std::endl;
    (*output) << std::endl;
    (*output) << "    curl http://localhost:" << config::webserver_port
              << "/pprof/profile?seconds=30 > perf.data" << std::endl;
    (*output) << "    pprof --text be/lib/doris_be perf.data" << std::endl;
    (*output) << std::endl;
    (*output) << "If you want to get the flame graph, you must first make sure that there is a "
                 "'perf' command on the host machine."
              << std::endl;
    (*output) << "And you need to download the FlameGraph and place it under 'be/tools/FlameGraph'."
              << std::endl;
    (*output) << "Finally, check if the following files exist" << std::endl;
    (*output) << std::endl;
    (*output) << "    be/tools/FlameGraph/stackcollapse-perf.pl" << std::endl;
    (*output) << "    be/tools/FlameGraph/flamegraph.pl" << std::endl;
    (*output) << std::endl;
    (*output) << "Doris will obtain the flame graph in the following ways:" << std::endl;
    (*output) << std::endl;
    (*output) << "    perf record -m 2 -g -p be_pid -o perf.data - sleep 30" << std::endl;
    (*output) << "    perf script -i perf.data | stackcollapse-perf.pl | flamegraph.pl > "
                 "flamegraph.svg"
              << std::endl;
    (*output) << std::endl;
    (*output) << "
" << std::endl; (*output) << "
" << std::endl; (*output) << "
" << std::endl; (*output) << "
" << std::endl; (*output) << "
" << std::endl; (*output) << "
" << std::endl; (*output) << "
" << std::endl; (*output) << "
" << std::endl; // for text profile (*output) << "" << std::endl; return; #endif } void add_default_path_handlers(WebPageHandler* web_page_handler) { // TODO(yingchun): logs_handler is not implemented yet, so not show it on navigate bar web_page_handler->register_page("/logs", "Logs", logs_handler, false /* is_on_nav_bar */); if (!config::hide_webserver_config_page) { web_page_handler->register_page("/varz", "Configs", config_handler, true /* is_on_nav_bar */); } web_page_handler->register_page("/memz", "Memory", mem_usage_handler, true /* is_on_nav_bar */); web_page_handler->register_page( "/mem_tracker", "MemTracker", std::bind(&mem_tracker_handler, std::placeholders::_1, std::placeholders::_2), true /* is_on_nav_bar */); web_page_handler->register_page("/heap", "Heap Profile", heap_handler, true /* is_on_nav_bar */); web_page_handler->register_page("/cpu", "CPU Profile", cpu_handler, true /* is_on_nav_bar */); register_thread_display_page(web_page_handler); web_page_handler->register_template_page( "/tablets_page", "Tablets", std::bind(&display_tablets_callback, std::placeholders::_1, std::placeholders::_2), true /* is_on_nav_bar */); } } // namespace doris