Coverage Report

Created: 2026-03-12 17:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/service/http/default_path_handlers.cpp
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#include "service/http/default_path_handlers.h"
19
20
#include <gen_cpp/Metrics_types.h>
21
22
#include <boost/algorithm/string/replace.hpp>
23
#ifdef USE_JEMALLOC
24
#include "jemalloc/jemalloc.h"
25
#endif
26
#if !defined(__SANITIZE_ADDRESS__) && !defined(ADDRESS_SANITIZER) && !defined(LEAK_SANITIZER) && \
27
        !defined(THREAD_SANITIZER) && !defined(USE_JEMALLOC)
28
#include <gperftools/malloc_extension.h>
29
#endif
30
31
#include <functional>
32
#include <map>
33
#include <memory>
34
#include <mutex>
35
#include <sstream>
36
#include <string>
37
#include <utility>
38
#include <vector>
39
40
#include "common/config.h"
41
#include "runtime/process_profile.h"
42
#include "service/http/action/tablets_info_action.h"
43
#include "service/http/web_page_handler.h"
44
#include "util/easy_json.h"
45
#include "util/mem_info.h"
46
#include "util/perf_counters.h"
47
#include "util/pretty_printer.h"
48
#include "util/thread.h"
49
50
using std::vector;
51
using std::shared_ptr;
52
using std::string;
53
54
namespace doris {
55
56
// Writes the last config::web_log_bytes of the INFO logfile to a webpage
57
// Note to get best performance, set GLOG_logbuflevel=-1 to prevent log buffering
58
0
void logs_handler(const WebPageHandler::ArgumentMap& args, std::stringstream* output) {
59
    /*std::string logfile;
60
    get_full_log_filename(google::INFO, &logfile);
61
    (*output) << "<h2>INFO logs</h2>" << std::endl;
62
    (*output) << "Log path is: " << logfile << std::endl;
63
64
    struct stat file_stat;
65
66
    if (stat(logfile.c_str(), &file_stat) == 0) {
67
        long size = file_stat.st_size;
68
        long seekpos = size < config::web_log_bytes ? 0L : size - config::web_log_bytes;
69
        std::ifstream log(logfile.c_str(), std::ios::in);
70
        // Note if the file rolls between stat and seek, this could fail
71
        // (and we could wind up reading the whole file). But because the
72
        // file is likely to be small, this is unlikely to be an issue in
73
        // practice.
74
        log.seekg(seekpos);
75
        (*output) << "<br/>Showing last " << config::web_log_bytes << " bytes of log" << std::endl;
76
        (*output) << "<br/><pre>" << log.rdbuf() << "</pre>";
77
78
    } else {
79
        (*output) << "<br/>Couldn't open INFO log file: " << logfile;
80
    }*/
81
82
0
    (*output) << "<br/>Couldn't open INFO log file: ";
83
0
}
84
85
// Registered to handle "/varz", and prints out all command-line flags and their values
86
0
void config_handler(const WebPageHandler::ArgumentMap& args, std::stringstream* output) {
87
0
    (*output) << "<h2>Configurations</h2>";
88
0
    (*output) << "<pre>";
89
0
    std::lock_guard<std::mutex> lock(*config::get_mutable_string_config_lock());
90
0
    for (const auto& it : *(config::full_conf_map)) {
91
0
        (*output) << it.first << "=" << it.second << std::endl;
92
0
    }
93
0
    (*output) << "</pre>";
94
0
}
95
96
0
void memory_info_handler(std::stringstream* output) {
97
0
    (*output) << "<h2>Memory Info</h2>\n";
98
0
    (*output) << "<pre>";
99
0
    (*output) << "<h4 id=\"memoryDocumentsTitle\">Memory Documents</h4>\n"
100
0
              << "<a "
101
0
                 "href=https://doris.apache.org/zh-CN/docs/dev/admin-manual/memory-management/"
102
0
                 "overview>Memory Management Overview</a>\n"
103
0
              << "<a "
104
0
                 "href=https://doris.apache.org/zh-CN/docs/dev/admin-manual/memory-management/"
105
0
                 "memory-issue-faq>Memory Issue FAQ</a>\n"
106
0
              << "\n---\n\n";
107
108
0
    (*output) << "<h4 id=\"memoryPropertiesTitle\">Memory Properties</h4>\n"
109
0
              << "System Physical Mem: "
110
0
              << PrettyPrinter::print(MemInfo::physical_mem(), TUnit::BYTES) << std::endl
111
0
              << "System Page Size: " << MemInfo::get_page_size() << std::endl
112
0
              << "Mem Limit: " << MemInfo::mem_limit_str() << std::endl
113
0
              << "Soft Mem Limit: " << MemInfo::soft_mem_limit_str() << std::endl
114
0
              << "System Mem Available Low Water Mark: "
115
0
              << PrettyPrinter::print(MemInfo::sys_mem_available_low_water_mark(), TUnit::BYTES)
116
0
              << std::endl
117
0
              << "System Mem Available Warning Water Mark: "
118
0
              << PrettyPrinter::print(MemInfo::sys_mem_available_warning_water_mark(), TUnit::BYTES)
119
0
              << std::endl
120
0
              << "Cgroup Mem Limit: "
121
0
              << PrettyPrinter::print(MemInfo::cgroup_mem_limit(), TUnit::BYTES) << std::endl
122
0
              << "Cgroup Mem Usage: "
123
0
              << PrettyPrinter::print(MemInfo::cgroup_mem_usage(), TUnit::BYTES) << std::endl
124
0
              << "Cgroup Mem Refresh State: " << MemInfo::cgroup_mem_refresh_state() << std::endl
125
0
              << "\n---\n\n";
126
127
0
    (*output) << "<h4 id=\"memoryOptionSettingsTitle\">Memory Option Settings</h4>\n";
128
0
    {
129
0
        std::lock_guard<std::mutex> lock(*config::get_mutable_string_config_lock());
130
0
        for (const auto& it : *(config::full_conf_map)) {
131
0
            if (it.first.find("memory") != std::string::npos ||
132
0
                it.first.find("cache") != std::string::npos ||
133
0
                it.first.find("mem") != std::string::npos) {
134
0
                (*output) << it.first << "=" << it.second << std::endl;
135
0
            }
136
0
        }
137
0
    }
138
0
    (*output) << "\n---\n\n";
139
140
0
    (*output) << "<h4 id=\"jemallocProfilesTitle\">Jemalloc Profiles</h4>\n";
141
0
#if defined(ADDRESS_SANITIZER) || defined(LEAK_SANITIZER) || defined(THREAD_SANITIZER)
142
0
    (*output) << "Memory tracking is not available with address sanitizer builds.";
143
#elif defined(USE_JEMALLOC)
144
    std::string tmp;
145
    auto write_cb = [](void* opaque, const char* buf) {
146
        auto* _opaque = static_cast<std::string*>(opaque);
147
        _opaque->append(buf);
148
    };
149
    jemalloc_stats_print(write_cb, &tmp, "a");
150
    boost::replace_all(tmp, "\n", "<br>");
151
    (*output) << tmp;
152
#else
153
    char buf[2048];
154
    MallocExtension::instance()->GetStats(buf, 2048);
155
    // Replace new lines with <br> for html
156
    std::string tmp(buf);
157
    boost::replace_all(tmp, "\n", "<br>");
158
    (*output) << tmp;
159
#endif
160
0
    (*output) << "</pre>";
161
0
}
162
163
// Registered to handle "/profile".
164
0
void process_profile_handler(const WebPageHandler::ArgumentMap& args, std::stringstream* output) {
165
0
    (*output) << "<h4>Copy Process Profile To Clipboard (拷贝 Process Profile 到剪切板) </h4>";
166
0
    (*output) << "<button id=\"copyToClipboard\">Copy Page Text</button>" << std::endl;
167
0
    (*output) << "<script>" << std::endl;
168
0
    (*output) << "$('#copyToClipboard').click(function () {" << std::endl;
169
    // create a hidden textarea element
170
0
    (*output) << "     var textarea = document.createElement('textarea');" << std::endl;
171
0
    (*output) << "     textarea.style.position = 'absolute';" << std::endl;
172
0
    (*output) << "     textarea.style.left = '-9999px';" << std::endl;
173
    // get the content to copy
174
0
    (*output) << "     var contentToCopy = document.getElementById('allPageText').innerHTML;"
175
0
              << std::endl;
176
0
    (*output) << "     textarea.value = contentToCopy;"
177
0
              << std::endl; // set the content to the textarea
178
0
    (*output) << "     document.body.appendChild(textarea);" << std::endl;
179
0
    (*output) << "     textarea.select();" << std::endl;
180
0
    (*output) << "     textarea.setSelectionRange(0, 99999);"
181
0
              << std::endl; // compatible with mobile devices
182
0
    (*output) << "try {" << std::endl;
183
0
    (*output) << "          document.execCommand('copy');"
184
0
              << std::endl; //copy the selected text to the clipboard
185
0
    (*output) << "          alert('Process profile copied to clipboard!');" << std::endl;
186
0
    (*output) << "      } catch (err) {" << std::endl;
187
0
    (*output) << "          alert('Failed to copy process profile! ' + err);" << std::endl;
188
0
    (*output) << "      }" << std::endl;
189
0
    (*output) << "});" << std::endl;
190
0
    (*output) << "</script>" << std::endl;
191
192
0
    doris::ProcessProfile::instance()->refresh_profile();
193
194
0
    (*output) << "<div id=\"allPageText\">" << std::endl;
195
0
    (*output) << "<h2 id=\"processProfileTitle\">Process Profile</h2>" << std::endl;
196
0
    (*output) << "<pre id=\"processProfile\">"
197
0
              << doris::ProcessProfile::instance()->print_process_profile_no_root() << "</pre>"
198
0
              << "\n\n---\n\n";
199
0
    memory_info_handler(output);
200
201
    // TODO, expect more information about process status, CPU, IO, etc.
202
203
0
    (*output) << "</div>" << std::endl;
204
0
}
205
206
0
void display_tablets_callback(const WebPageHandler::ArgumentMap& args, EasyJson* ej) {
207
0
    std::string tablet_num_to_return;
208
0
    auto it = args.find("limit");
209
0
    if (it != args.end()) {
210
0
        tablet_num_to_return = it->second;
211
0
    } else {
212
0
        tablet_num_to_return = "1000"; // default
213
0
    }
214
0
    (*ej) = TabletsInfoAction::get_tablets_info(tablet_num_to_return);
215
0
}
216
217
// Registered to handle "/mem_tracker", and prints out memory tracker information.
218
0
void mem_tracker_handler(const WebPageHandler::ArgumentMap& args, std::stringstream* output) {
219
0
    (*output) << "<h2>mem_tracker webpage has been offline, please click <a "
220
0
                 "href=../profile>Process Profile</a>, see MemoryProfile and Memory Info</h2>\n";
221
0
}
222
223
0
void heap_handler(const WebPageHandler::ArgumentMap& args, std::stringstream* output) {
224
0
    (*output) << "<h2>Heap Profile</h2>" << std::endl;
225
226
0
#if defined(ADDRESS_SANITIZER) || defined(LEAK_SANITIZER) || defined(THREAD_SANITIZER) || \
227
0
        defined(USE_JEMALLOC)
228
0
    (*output) << "<pre>" << std::endl;
229
0
    (*output) << "Heap profiling is not available with address sanitizer builds." << std::endl;
230
0
    (*output) << "</pre>" << std::endl;
231
0
    return;
232
233
#else
234
    (*output) << "<pre>" << std::endl;
235
    (*output) << "Heap profiling will use pprof tool to sample and get heap profile. It will take "
236
                 "30 seconds"
237
              << std::endl;
238
    (*output) << "(Only one thread can obtain profile at the same time)" << std::endl;
239
    (*output) << std::endl;
240
    (*output) << "If you want to get the Heap profile, you need to install gperftools-2.0 on the "
241
                 "host machine,"
242
              << std::endl;
243
    (*output) << "and make sure there is a 'pprof' executable file in the system PATH or "
244
                 "'be/tools/bin/' directory."
245
              << std::endl;
246
    (*output) << "Doris will obtain Profile in the following ways:" << std::endl;
247
    (*output) << std::endl;
248
    (*output) << "    curl http://localhost:" << config::webserver_port
249
              << "/pprof/heap?seconds=30 > perf.data" << std::endl;
250
    (*output) << "    pprof --text be/lib/doris_be perf.data" << std::endl;
251
    (*output) << std::endl;
252
    (*output) << "</pre>" << std::endl;
253
    (*output) << "<div id=\"heap\">" << std::endl;
254
    (*output) << "    <div><button type=\"button\" id=\"getHeap\">Profile It!</button></div>"
255
              << std::endl;
256
    (*output) << "    <br/>" << std::endl;
257
    (*output) << "    <div id=\"heapResult\"><pre id=\"heapContent\"></pre></div>" << std::endl;
258
    (*output) << "</div>" << std::endl;
259
260
    (*output) << "<script>" << std::endl;
261
    (*output) << "$('#getHeap').click(function () {" << std::endl;
262
    (*output) << "    document.getElementById(\"heapContent\").innerText = \"Sampling... (30 "
263
                 "seconds)\";"
264
              << std::endl;
265
    (*output) << "    $.ajax({" << std::endl;
266
    (*output) << "        type: \"GET\"," << std::endl;
267
    (*output) << "        dataType: \"text\"," << std::endl;
268
    (*output) << "        url: \"pprof/heap?readable=true\"," << std::endl;
269
    (*output) << "        timeout: 60000," << std::endl;
270
    (*output) << "        success: function (result) {" << std::endl;
271
    (*output) << "            $('#heapResult').removeClass('hidden');" << std::endl;
272
    (*output) << "            document.getElementById(\"heapContent\").innerText = result;"
273
              << std::endl;
274
    (*output) << "        }" << std::endl;
275
    (*output) << "        ," << std::endl;
276
    (*output) << "        error: function (result) {" << std::endl;
277
    (*output) << "            alert(result);" << std::endl;
278
    (*output) << "        }" << std::endl;
279
    (*output) << "        ," << std::endl;
280
    (*output) << "    });" << std::endl;
281
    (*output) << "});" << std::endl;
282
283
    (*output) << "</script>" << std::endl;
284
285
    return;
286
#endif
287
0
}
288
289
0
void cpu_handler(const WebPageHandler::ArgumentMap& args, std::stringstream* output) {
290
0
    (*output) << "<h2>CPU Profile</h2>" << std::endl;
291
292
0
#if defined(ADDRESS_SANITIZER) || defined(LEAK_SANITIZER) || defined(THREAD_SANITIZER)
293
0
    (*output) << "<pre>" << std::endl;
294
0
    (*output) << "CPU profiling is not available with address sanitizer builds." << std::endl;
295
0
    (*output) << "</pre>" << std::endl;
296
0
    return;
297
298
#else
299
300
    (*output) << "<pre>" << std::endl;
301
    (*output) << "CPU profiling will use perf tool to sample and get CPU profile. It will take 30 "
302
                 "seconds"
303
              << std::endl;
304
    (*output) << "(Only one thread can obtain profile at the same time)" << std::endl;
305
    (*output) << std::endl;
306
    (*output) << "If you want to get the CPU profile in text form, you need to install "
307
                 "gperftools-2.0 on the host machine,"
308
              << std::endl;
309
    (*output) << "and make sure there is a 'pprof' executable file in the system PATH or "
310
                 "'be/tools/bin/' directory."
311
              << std::endl;
312
    (*output) << "Doris will obtain Profile in the following ways:" << std::endl;
313
    (*output) << std::endl;
314
    (*output) << "    curl http://localhost:" << config::webserver_port
315
              << "/pprof/profile?seconds=30 > perf.data" << std::endl;
316
    (*output) << "    pprof --text be/lib/doris_be perf.data" << std::endl;
317
    (*output) << std::endl;
318
    (*output) << "If you want to get the flame graph, you must first make sure that there is a "
319
                 "'perf' command on the host machine."
320
              << std::endl;
321
    (*output) << "And you need to download the FlameGraph and place it under 'be/tools/FlameGraph'."
322
              << std::endl;
323
    (*output) << "Finally, check if the following files exist. And should be executable."
324
              << std::endl;
325
    (*output) << std::endl;
326
    (*output) << "    be/tools/FlameGraph/stackcollapse-perf.pl" << std::endl;
327
    (*output) << "    be/tools/FlameGraph/flamegraph.pl" << std::endl;
328
    (*output) << std::endl;
329
    (*output) << "Doris will obtain the flame graph in the following ways:" << std::endl;
330
    (*output) << std::endl;
331
    (*output) << "    perf record -m 2 -g -p be_pid -o perf.data - sleep 30" << std::endl;
332
    (*output) << "    perf script -i perf.data | stackcollapse-perf.pl | flamegraph.pl > "
333
                 "flamegraph.svg"
334
              << std::endl;
335
    (*output) << std::endl;
336
    (*output) << "</pre>" << std::endl;
337
    (*output) << "<div id=\"cpu\">" << std::endl;
338
    (*output)
339
            << "    <div><button type=\"button\" id=\"getCpu\">Profile It! (Text)</button><button "
340
               "type=\"button\" id=\"getCpuGraph\">Profile It! (FlameGraph)</button></div>"
341
            << std::endl;
342
    (*output) << "    <br/>" << std::endl;
343
    (*output) << "    <div id=\"cpuResult\"><pre id=\"cpuContent\"></pre></div>" << std::endl;
344
    (*output) << "</div>" << std::endl;
345
346
    // for text profile
347
    (*output) << "<script>" << std::endl;
348
    (*output) << "$('#getCpu').click(function () {" << std::endl;
349
    (*output) << "    document.getElementById(\"cpuContent\").innerText = \"Sampling... (30 "
350
                 "seconds)\";"
351
              << std::endl;
352
    (*output) << "    $.ajax({" << std::endl;
353
    (*output) << "        type: \"GET\"," << std::endl;
354
    (*output) << "        dataType: \"text\"," << std::endl;
355
    (*output) << "        url: \"pprof/profile?type=text\"," << std::endl;
356
    (*output) << "        timeout: 120000," << std::endl;
357
    (*output) << "        success: function (result) {" << std::endl;
358
    (*output) << "            document.getElementById(\"cpuContent\").innerText = result;"
359
              << std::endl;
360
    (*output) << "        }" << std::endl;
361
    (*output) << "        ," << std::endl;
362
    (*output) << "        error: function (result) {" << std::endl;
363
    (*output) << "            alert(JSON.stringify(result));" << std::endl;
364
    (*output) << "        }" << std::endl;
365
    (*output) << "        ," << std::endl;
366
    (*output) << "    });" << std::endl;
367
    (*output) << "});" << std::endl;
368
369
    // for graph profile
370
    (*output) << "$('#getCpuGraph').click(function () {" << std::endl;
371
    (*output) << "    document.getElementById(\"cpuContent\").innerText = \"Sampling... (30 "
372
                 "seconds)\";"
373
              << std::endl;
374
    (*output) << "    $.ajax({" << std::endl;
375
    (*output) << "        type: \"GET\"," << std::endl;
376
    (*output) << "        dataType: \"text\"," << std::endl;
377
    (*output) << "        url: \"pprof/profile?type=flamegraph\"," << std::endl;
378
    (*output) << "        timeout: 120000," << std::endl;
379
    (*output) << "        success: function (result) {" << std::endl;
380
    (*output) << "            document.getElementById(\"cpuContent\").innerHTML = result;"
381
              << std::endl;
382
    (*output) << "        }" << std::endl;
383
    (*output) << "        ," << std::endl;
384
    (*output) << "        error: function (result) {" << std::endl;
385
    (*output) << "            alert(JSON.stringify(result));" << std::endl;
386
    (*output) << "        }" << std::endl;
387
    (*output) << "        ," << std::endl;
388
    (*output) << "    });" << std::endl;
389
    (*output) << "});" << std::endl;
390
391
    (*output) << "</script>" << std::endl;
392
393
    return;
394
#endif
395
0
}
396
397
7
void add_default_path_handlers(WebPageHandler* web_page_handler) {
398
    // TODO(yingchun): logs_handler is not implemented yet, so not show it on navigate bar
399
7
    web_page_handler->register_page("/logs", "Logs", logs_handler, false /* is_on_nav_bar */);
400
7
    if (!config::hide_webserver_config_page) {
401
7
        web_page_handler->register_page("/varz", "Configs", config_handler,
402
7
                                        true /* is_on_nav_bar */);
403
7
    }
404
7
    web_page_handler->register_page("/profile", "Process Profile", process_profile_handler,
405
7
                                    true /* is_on_nav_bar */);
406
7
    web_page_handler->register_page("/mem_tracker", "MemTracker", mem_tracker_handler,
407
7
                                    true /* is_on_nav_bar */);
408
7
    web_page_handler->register_page("/heap", "Heap Profile", heap_handler,
409
7
                                    true /* is_on_nav_bar */);
410
7
    web_page_handler->register_page("/cpu", "CPU Profile", cpu_handler, true /* is_on_nav_bar */);
411
7
    register_thread_display_page(web_page_handler);
412
7
    web_page_handler->register_template_page(
413
7
            "/tablets_page", "Tablets",
414
7
            [](auto&& PH1, auto&& PH2) {
415
0
                return display_tablets_callback(std::forward<decltype(PH1)>(PH1),
416
0
                                                std::forward<decltype(PH2)>(PH2));
417
0
            },
418
7
            true /* is_on_nav_bar */);
419
7
}
420
421
} // namespace doris