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 |