be/src/service/http/action/pprof_actions.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/action/pprof_actions.h" |
19 | | |
20 | | #include "service/http/http_handler_with_auth.h" |
21 | | |
22 | | #if !defined(__SANITIZE_ADDRESS__) && !defined(ADDRESS_SANITIZER) && !defined(LEAK_SANITIZER) && \ |
23 | | !defined(THREAD_SANITIZER) && !defined(USE_JEMALLOC) |
24 | | #include <gperftools/heap-profiler.h> // IWYU pragma: keep |
25 | | #include <gperftools/malloc_extension.h> // IWYU pragma: keep |
26 | | #endif |
27 | | #if !defined(__SANITIZE_ADDRESS__) && !defined(ADDRESS_SANITIZER) && !defined(LEAK_SANITIZER) && \ |
28 | | !defined(THREAD_SANITIZER) |
29 | | #include <gperftools/profiler.h> // IWYU pragma: keep |
30 | | #endif |
31 | | #include <stdio.h> |
32 | | |
33 | | #include <fstream> |
34 | | #include <memory> |
35 | | #include <mutex> |
36 | | #include <string> |
37 | | |
38 | | #include "common/config.h" |
39 | | #include "common/object_pool.h" |
40 | | #include "io/fs/local_file_system.h" |
41 | | #include "runtime/exec_env.h" |
42 | | #include "service/http/ev_http_server.h" |
43 | | #include "service/http/http_channel.h" |
44 | | #include "service/http/http_handler.h" |
45 | | #include "service/http/http_method.h" |
46 | | #include "service/http/http_request.h" |
47 | | #include "util/bfd_parser.h" |
48 | | #include "util/pprof_utils.h" // IWYU pragma: keep |
49 | | |
50 | | namespace doris { |
51 | | |
52 | | // pprof default sample time in seconds. |
53 | | [[maybe_unused]] static const std::string SECOND_KEY = "seconds"; |
54 | | static const int kPprofDefaultSampleSecs = 30; |
55 | | |
56 | | // Protect, only one thread can work |
57 | | static std::mutex kPprofActionMutex; |
58 | | |
59 | | class HeapAction : public HttpHandlerWithAuth { |
60 | | public: |
61 | 7 | HeapAction(ExecEnv* exec_env) : HttpHandlerWithAuth(exec_env) {} |
62 | | |
63 | | virtual ~HeapAction() {} |
64 | | |
65 | | virtual void handle(HttpRequest* req) override; |
66 | | }; |
67 | | |
68 | 0 | void HeapAction::handle(HttpRequest* req) { |
69 | 0 | std::lock_guard<std::mutex> lock(kPprofActionMutex); |
70 | 0 | #if defined(ADDRESS_SANITIZER) || defined(LEAK_SANITIZER) || defined(THREAD_SANITIZER) || \ |
71 | 0 | defined(USE_JEMALLOC) |
72 | 0 | (void)kPprofDefaultSampleSecs; // Avoid unused variable warning. |
73 | |
|
74 | 0 | std::string str = "Heap profiling is not available with address sanitizer or jemalloc builds."; |
75 | |
|
76 | 0 | HttpChannel::send_reply(req, str); |
77 | | #else |
78 | | int seconds = kPprofDefaultSampleSecs; |
79 | | const std::string& seconds_str = req->param(SECOND_KEY); |
80 | | if (!seconds_str.empty()) { |
81 | | seconds = std::atoi(seconds_str.c_str()); |
82 | | } |
83 | | |
84 | | std::stringstream tmp_prof_file_name; |
85 | | // Build a temporary file name that is hopefully unique. |
86 | | tmp_prof_file_name << config::pprof_profile_dir << "/heap_profile." << getpid() << "." |
87 | | << rand(); |
88 | | |
89 | | HeapProfilerStart(tmp_prof_file_name.str().c_str()); |
90 | | // Sleep to allow for some samples to be collected. |
91 | | sleep(seconds); |
92 | | const char* profile = GetHeapProfile(); |
93 | | HeapProfilerStop(); |
94 | | std::string str = profile; |
95 | | delete profile; |
96 | | |
97 | | const std::string& readable_str = req->param("readable"); |
98 | | if (!readable_str.empty()) { |
99 | | std::stringstream readable_res; |
100 | | Status st = PprofUtils::get_readable_profile(str, false, &readable_res); |
101 | | if (!st.ok()) { |
102 | | HttpChannel::send_reply(req, st.to_string()); |
103 | | } else { |
104 | | HttpChannel::send_reply(req, readable_res.str()); |
105 | | } |
106 | | } else { |
107 | | HttpChannel::send_reply(req, str); |
108 | | } |
109 | | #endif |
110 | 0 | } |
111 | | |
112 | | class GrowthAction : public HttpHandlerWithAuth { |
113 | | public: |
114 | 7 | GrowthAction(ExecEnv* exec_env) : HttpHandlerWithAuth(exec_env) {} |
115 | | |
116 | | virtual ~GrowthAction() {} |
117 | | |
118 | | virtual void handle(HttpRequest* req) override; |
119 | | }; |
120 | | |
121 | 0 | void GrowthAction::handle(HttpRequest* req) { |
122 | 0 | #if defined(ADDRESS_SANITIZER) || defined(LEAK_SANITIZER) || defined(THREAD_SANITIZER) || \ |
123 | 0 | defined(USE_JEMALLOC) |
124 | 0 | std::string str = |
125 | 0 | "Growth profiling is not available with address sanitizer or jemalloc builds."; |
126 | 0 | HttpChannel::send_reply(req, str); |
127 | | #else |
128 | | std::lock_guard<std::mutex> lock(kPprofActionMutex); |
129 | | |
130 | | std::string heap_growth_stack; |
131 | | MallocExtension::instance()->GetHeapGrowthStacks(&heap_growth_stack); |
132 | | |
133 | | HttpChannel::send_reply(req, heap_growth_stack); |
134 | | #endif |
135 | 0 | } |
136 | | |
137 | | class ProfileAction : public HttpHandlerWithAuth { |
138 | | public: |
139 | 7 | ProfileAction(ExecEnv* exec_env) : HttpHandlerWithAuth(exec_env) {} |
140 | | |
141 | | virtual ~ProfileAction() {} |
142 | | |
143 | | virtual void handle(HttpRequest* req) override; |
144 | | }; |
145 | | |
146 | 0 | void ProfileAction::handle(HttpRequest* req) { |
147 | 0 | #if defined(ADDRESS_SANITIZER) || defined(LEAK_SANITIZER) || defined(THREAD_SANITIZER) |
148 | 0 | std::string str = "CPU profiling is not available with address sanitizer or jemalloc builds."; |
149 | 0 | HttpChannel::send_reply(req, str); |
150 | | #else |
151 | | std::lock_guard<std::mutex> lock(kPprofActionMutex); |
152 | | |
153 | | int seconds = kPprofDefaultSampleSecs; |
154 | | const std::string& seconds_str = req->param(SECOND_KEY); |
155 | | if (!seconds_str.empty()) { |
156 | | seconds = std::atoi(seconds_str.c_str()); |
157 | | } |
158 | | |
159 | | const std::string& type_str = req->param("type"); |
160 | | if (type_str != "flamegraph") { |
161 | | // use pprof the sample the CPU |
162 | | std::ostringstream tmp_prof_file_name; |
163 | | tmp_prof_file_name << config::pprof_profile_dir << "/doris_profile." << getpid() << "." |
164 | | << rand(); |
165 | | ProfilerStart(tmp_prof_file_name.str().c_str()); |
166 | | sleep(seconds); |
167 | | ProfilerStop(); |
168 | | |
169 | | if (type_str != "text") { |
170 | | // return raw content via http response directly |
171 | | std::ifstream prof_file(tmp_prof_file_name.str().c_str(), std::ios::in); |
172 | | std::stringstream ss; |
173 | | if (!prof_file.is_open()) { |
174 | | ss << "Unable to open cpu profile: " << tmp_prof_file_name.str(); |
175 | | std::string str = ss.str(); |
176 | | HttpChannel::send_reply(req, str); |
177 | | return; |
178 | | } |
179 | | ss << prof_file.rdbuf(); |
180 | | prof_file.close(); |
181 | | std::string str = ss.str(); |
182 | | HttpChannel::send_reply(req, str); |
183 | | return; |
184 | | } |
185 | | |
186 | | // text type. we will return readable content via http response |
187 | | std::stringstream readable_res; |
188 | | Status st = PprofUtils::get_readable_profile(tmp_prof_file_name.str(), true, &readable_res); |
189 | | if (!st.ok()) { |
190 | | HttpChannel::send_reply(req, st.to_string()); |
191 | | } else { |
192 | | HttpChannel::send_reply(req, readable_res.str()); |
193 | | } |
194 | | } else { |
195 | | // generate flamegraph |
196 | | std::string svg_file_content; |
197 | | std::string flamegraph_install_dir = |
198 | | std::string(std::getenv("DORIS_HOME")) + "/tools/FlameGraph/"; |
199 | | Status st = PprofUtils::generate_flamegraph(seconds, flamegraph_install_dir, false, |
200 | | &svg_file_content); |
201 | | if (!st.ok()) { |
202 | | HttpChannel::send_reply(req, st.to_string()); |
203 | | } else { |
204 | | HttpChannel::send_reply(req, svg_file_content); |
205 | | } |
206 | | } |
207 | | #endif |
208 | 0 | } |
209 | | |
210 | | class PmuProfileAction : public HttpHandlerWithAuth { |
211 | | public: |
212 | 7 | PmuProfileAction(ExecEnv* exec_env) : HttpHandlerWithAuth(exec_env) {} |
213 | | |
214 | | virtual ~PmuProfileAction() {} |
215 | 0 | virtual void handle(HttpRequest* req) override {} |
216 | | }; |
217 | | |
218 | | class ContentionAction : public HttpHandlerWithAuth { |
219 | | public: |
220 | 7 | ContentionAction(ExecEnv* exec_env) : HttpHandlerWithAuth(exec_env) {} |
221 | | |
222 | | virtual ~ContentionAction() {} |
223 | | |
224 | 0 | virtual void handle(HttpRequest* req) override {} |
225 | | }; |
226 | | |
227 | | class CmdlineAction : public HttpHandlerWithAuth { |
228 | | public: |
229 | 7 | CmdlineAction(ExecEnv* exec_env) : HttpHandlerWithAuth(exec_env) {} |
230 | | |
231 | | virtual ~CmdlineAction() {} |
232 | | virtual void handle(HttpRequest* req) override; |
233 | | }; |
234 | | |
235 | 0 | void CmdlineAction::handle(HttpRequest* req) { |
236 | 0 | FILE* fp = fopen("/proc/self/cmdline", "r"); |
237 | 0 | if (fp == nullptr) { |
238 | 0 | std::string str = "Unable to open file: /proc/self/cmdline"; |
239 | 0 | HttpChannel::send_reply(req, str); |
240 | 0 | return; |
241 | 0 | } |
242 | | |
243 | 0 | std::string str; |
244 | 0 | char buf[1024]; |
245 | 0 | if (fscanf(fp, "%1023s ", buf) == 1) { |
246 | 0 | str = buf; |
247 | 0 | } else { |
248 | 0 | str = "Unable to read file: /proc/self/cmdline"; |
249 | 0 | } |
250 | |
|
251 | 0 | fclose(fp); |
252 | |
|
253 | 0 | HttpChannel::send_reply(req, str); |
254 | 0 | } |
255 | | |
256 | | class SymbolAction : public HttpHandlerWithAuth { |
257 | | public: |
258 | | SymbolAction(BfdParser* parser, ExecEnv* exec_env) |
259 | 7 | : HttpHandlerWithAuth(exec_env), _parser(parser) {} |
260 | | virtual ~SymbolAction() {} |
261 | | |
262 | | virtual void handle(HttpRequest* req) override; |
263 | | |
264 | | private: |
265 | | BfdParser* _parser; |
266 | | }; |
267 | | |
268 | 0 | void SymbolAction::handle(HttpRequest* req) { |
269 | | // TODO: Implement symbol resolution. Without this, the binary needs to be passed |
270 | | // to pprof to resolve all symbols. |
271 | 0 | if (req->method() == HttpMethod::GET) { |
272 | 0 | std::stringstream ss; |
273 | 0 | ss << "num_symbols: " << _parser->num_symbols(); |
274 | 0 | std::string str = ss.str(); |
275 | |
|
276 | 0 | HttpChannel::send_reply(req, str); |
277 | 0 | return; |
278 | 0 | } else if (req->method() == HttpMethod::HEAD) { |
279 | 0 | HttpChannel::send_reply(req); |
280 | 0 | return; |
281 | 0 | } else if (req->method() == HttpMethod::POST) { |
282 | 0 | std::string request = req->get_request_body(); |
283 | | // parse address |
284 | 0 | std::string result; |
285 | 0 | const char* ptr = request.c_str(); |
286 | 0 | const char* end = request.c_str() + request.size(); |
287 | 0 | while (ptr < end && *ptr != '\0') { |
288 | 0 | std::string file_name; |
289 | 0 | std::string func_name; |
290 | 0 | unsigned int lineno = 0; |
291 | 0 | const char* old_ptr = ptr; |
292 | 0 | if (!_parser->decode_address(ptr, &ptr, &file_name, &func_name, &lineno)) { |
293 | 0 | result.append(old_ptr, ptr - old_ptr); |
294 | 0 | result.push_back('\t'); |
295 | 0 | result.append(func_name); |
296 | 0 | result.push_back('\n'); |
297 | 0 | } |
298 | 0 | if (ptr < end && *ptr == '+') { |
299 | 0 | ptr++; |
300 | 0 | } |
301 | 0 | } |
302 | |
|
303 | 0 | HttpChannel::send_reply(req, result); |
304 | 0 | } |
305 | 0 | } |
306 | | |
307 | 7 | Status PprofActions::setup(ExecEnv* exec_env, EvHttpServer* http_server, ObjectPool& pool) { |
308 | 7 | if (!config::pprof_profile_dir.empty()) { |
309 | 7 | RETURN_IF_ERROR(io::global_local_filesystem()->create_directory(config::pprof_profile_dir)); |
310 | 7 | } |
311 | | |
312 | 7 | http_server->register_handler(HttpMethod::GET, "/pprof/heap", |
313 | 7 | pool.add(new HeapAction(exec_env))); |
314 | 7 | http_server->register_handler(HttpMethod::GET, "/pprof/growth", |
315 | 7 | pool.add(new GrowthAction(exec_env))); |
316 | 7 | http_server->register_handler(HttpMethod::GET, "/pprof/profile", |
317 | 7 | pool.add(new ProfileAction(exec_env))); |
318 | 7 | http_server->register_handler(HttpMethod::GET, "/pprof/pmuprofile", |
319 | 7 | pool.add(new PmuProfileAction(exec_env))); |
320 | 7 | http_server->register_handler(HttpMethod::GET, "/pprof/contention", |
321 | 7 | pool.add(new ContentionAction(exec_env))); |
322 | 7 | http_server->register_handler(HttpMethod::GET, "/pprof/cmdline", |
323 | 7 | pool.add(new CmdlineAction(exec_env))); |
324 | 7 | auto action = pool.add(new SymbolAction(exec_env->bfd_parser(), exec_env)); |
325 | 7 | http_server->register_handler(HttpMethod::GET, "/pprof/symbol", action); |
326 | 7 | http_server->register_handler(HttpMethod::HEAD, "/pprof/symbol", action); |
327 | 7 | http_server->register_handler(HttpMethod::POST, "/pprof/symbol", action); |
328 | 7 | return Status::OK(); |
329 | 7 | } |
330 | | |
331 | | } // namespace doris |