/src/brpc/src/brpc/details/jemalloc_profiler.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | |
19 | | #include "brpc/controller.h" |
20 | | #include "brpc/http_header.h" |
21 | | #include "brpc/reloadable_flags.h" |
22 | | #include "brpc/uri.h" |
23 | | #include "butil/files/file_path.h" |
24 | | #include "butil/iobuf.h" |
25 | | #include "butil/popen.h" |
26 | | #include "gflags/gflags.h" |
27 | | #include "gflags/gflags_declare.h" |
28 | | #include <brpc/details/jemalloc_profiler.h> |
29 | | #include <brpc/builtin/common.h> |
30 | | #include <butil/file_util.h> |
31 | | #include <butil/process_util.h> |
32 | | #include <bthread/bthread.h> |
33 | | #include <cerrno> |
34 | | #include <cstdlib> |
35 | | |
36 | | extern "C" { |
37 | | // weak symbol: resolved at runtime by the linker if we are using jemalloc, nullptr otherwise |
38 | | int BAIDU_WEAK mallctl(const char*, void*, size_t*, void*, size_t); |
39 | | void BAIDU_WEAK malloc_stats_print(void (*write_cb)(void *, const char *), void *cbopaque, const char *opts); |
40 | | } |
41 | | |
42 | | namespace brpc { |
43 | | |
44 | | // https://jemalloc.net/jemalloc.3.html |
45 | | DEFINE_bool(je_prof_active, false, "control jemalloc prof.active, jemalloc profiling enabled but inactive," |
46 | | "it toggle profiling at any time during process running"); |
47 | | DEFINE_int32(je_prof_dump, 0, "control jemalloc prof.dump, change this only dump profile"); |
48 | | DEFINE_int32(je_prof_reset, 19, "control jemalloc prof.reset, reset all memory profile statistics, " |
49 | | "and optionally update the sample rate, default 2^19 B"); |
50 | | |
51 | | DECLARE_int32(max_flame_graph_width); |
52 | | |
53 | | // define in src/brpc/builtin/pprof_service.cpp |
54 | | extern int MakeProfName(ProfilingType type, char* buf, size_t buf_len); |
55 | | |
56 | 0 | static bool HasInit(const std::string& fn) { |
57 | 0 | static std::set<std::string> fns; |
58 | 0 | if (fns.count(fn) > 0) { |
59 | 0 | return true; |
60 | 0 | } |
61 | 0 | fns.insert(fn); |
62 | 0 | return false; |
63 | 0 | } |
64 | | |
65 | 0 | bool HasJemalloc() { |
66 | 0 | return mallctl != nullptr; |
67 | 0 | } |
68 | | |
69 | | // env need MALLOC_CONF="prof:true" before process start |
70 | 0 | static bool HasEnableJemallocProfile() { |
71 | 0 | bool prof = false; |
72 | 0 | size_t len = sizeof(prof); |
73 | 0 | int ret = mallctl("opt.prof", &prof, &len, nullptr, 0); |
74 | 0 | if (ret != 0) { |
75 | 0 | LOG(WARNING) << "mallctl get opt.prof err, ret:" << ret; |
76 | 0 | return false; |
77 | 0 | } |
78 | 0 | return prof; |
79 | 0 | } |
80 | | |
81 | 0 | static void WriteCb(void *opaque, const char *str) { |
82 | | // maybe call n times WriteCb by single malloc_stats_print |
83 | 0 | static_cast<std::string*>(opaque)->append(str); |
84 | 0 | } |
85 | | |
86 | 0 | std::string StatsPrint(const std::string& opts) { |
87 | 0 | if (malloc_stats_print == nullptr) { |
88 | 0 | return "your jemalloc no support malloc_stats_print"; |
89 | 0 | } |
90 | | |
91 | 0 | std::string stat_str; |
92 | 0 | malloc_stats_print(WriteCb, &stat_str, opts.c_str()); |
93 | 0 | return stat_str; |
94 | 0 | } |
95 | | |
96 | 0 | static int JeProfileActive(bool active) { |
97 | 0 | if (!HasJemalloc()) { |
98 | 0 | LOG(WARNING) << "no jemalloc"; |
99 | 0 | return -1; |
100 | 0 | } |
101 | | |
102 | 0 | if (!HasEnableJemallocProfile()) { |
103 | 0 | LOG(WARNING) << "jemalloc have not set opt.prof before start"; |
104 | 0 | return -1; |
105 | 0 | } |
106 | | |
107 | 0 | int ret = mallctl("prof.active", nullptr, nullptr, &active, sizeof(active)); |
108 | 0 | if (ret != 0) { |
109 | 0 | LOG(WARNING) << "mallctl set prof.active:" << active << " err, ret:" << ret; |
110 | 0 | return ret; |
111 | 0 | } |
112 | 0 | LOG(INFO) << "mallctl set prof.active:" << active << " succ"; |
113 | 0 | return 0; |
114 | 0 | } |
115 | | |
116 | 0 | static std::string JeProfileDump() { |
117 | 0 | if (!HasJemalloc()) { |
118 | 0 | LOG(WARNING) << "no jemalloc"; |
119 | 0 | return ""; |
120 | 0 | } |
121 | | |
122 | 0 | if (!HasEnableJemallocProfile()) { |
123 | 0 | LOG(WARNING) << "jemalloc have not set opt.prof before start"; |
124 | 0 | return ""; |
125 | 0 | } |
126 | | |
127 | 0 | char prof_name[256]; |
128 | 0 | if (MakeProfName(PROFILING_HEAP, prof_name, sizeof(prof_name)) != 0) { |
129 | 0 | LOG(WARNING) << "Fail to create .prof file, " << berror(); |
130 | 0 | return ""; |
131 | 0 | } |
132 | 0 | butil::File::Error error; |
133 | 0 | const butil::FilePath dir = butil::FilePath(prof_name).DirName(); |
134 | 0 | if (!butil::CreateDirectoryAndGetError(dir, &error)) { |
135 | 0 | LOG(WARNING) << "Fail to create directory= " << dir.value(); |
136 | 0 | return ""; |
137 | 0 | } |
138 | | |
139 | 0 | const char* p_prof_name = prof_name; |
140 | 0 | int ret = mallctl("prof.dump", NULL, NULL, (void*)&p_prof_name, sizeof(p_prof_name)); |
141 | 0 | if (ret != 0) { |
142 | 0 | LOG(WARNING) << "mallctl set prof.dump:" << p_prof_name << " err, ret:" << ret; |
143 | 0 | return ""; |
144 | 0 | } |
145 | 0 | LOG(INFO) << "heap profile dump:" << prof_name << " succ"; |
146 | 0 | return prof_name; |
147 | 0 | } |
148 | | |
149 | 0 | static int JeProfileReset(size_t lg_sample) { |
150 | 0 | if (!HasJemalloc()) { |
151 | 0 | LOG(WARNING) << "no jemalloc"; |
152 | 0 | return -1; |
153 | 0 | } |
154 | | |
155 | 0 | if (!HasEnableJemallocProfile()) { |
156 | 0 | LOG(WARNING) << "jemalloc have not set opt.prof before start"; |
157 | 0 | return -1; |
158 | 0 | } |
159 | | |
160 | 0 | int ret = mallctl("prof.reset", nullptr, nullptr, &lg_sample, sizeof(lg_sample)); |
161 | 0 | if (ret != 0) { |
162 | 0 | LOG(WARNING) << "mallctl set prof.reset:" << lg_sample << " err, ret:" << ret; |
163 | 0 | return ret; |
164 | 0 | } |
165 | 0 | LOG(INFO) << "mallctl set prof.reset:" << lg_sample << " succ"; |
166 | |
|
167 | 0 | FLAGS_je_prof_active = false; |
168 | 0 | if (FLAGS_je_prof_active) { |
169 | 0 | LOG(WARNING) << "reset FLAGS_je_prof_active fail"; |
170 | 0 | return -1; |
171 | 0 | } |
172 | | |
173 | 0 | return 0; |
174 | 0 | } |
175 | | |
176 | 0 | void JeControlProfile(Controller* cntl) { |
177 | 0 | const brpc::URI& uri = cntl->http_request().uri(); |
178 | | // http:ip:port/pprof/heap?display=(text|svg|stats|flamegraph) |
179 | 0 | const std::string* uri_display = uri.GetQuery("display"); |
180 | |
|
181 | 0 | butil::IOBuf& buf = cntl->response_attachment(); |
182 | 0 | cntl->http_response().set_content_type("text/plain"); |
183 | | |
184 | | // support ip:port/pprof/heap?display=stats |
185 | 0 | if (uri_display != nullptr && *uri_display == "stats") { |
186 | 0 | const std::string* uri_opts = uri.GetQuery("opts"); |
187 | 0 | std::string opts = !uri_opts || uri_opts->empty() ? "Ja" : *uri_opts; |
188 | 0 | buf.append(StatsPrint(opts)); |
189 | 0 | return; |
190 | 0 | } |
191 | | |
192 | 0 | if (!HasEnableJemallocProfile()) { |
193 | 0 | cntl->SetFailed(ENOMETHOD, "Heap profiler is not enabled, (no MALLOC_CONF=prof:true in env)"); |
194 | 0 | return; |
195 | 0 | } |
196 | | |
197 | | // only dump profile |
198 | 0 | const std::string prof_name = JeProfileDump(); |
199 | 0 | if (prof_name.empty()) { |
200 | 0 | cntl->SetFailed(-1, "Fail to dump profile"); |
201 | 0 | buf.append("\nFail to dump profile"); |
202 | 0 | return; |
203 | 0 | } |
204 | | |
205 | | // support jeprof ip:port/pprof/heap |
206 | 0 | if (uri_display == nullptr || uri_display->empty()) { |
207 | 0 | std::string content; |
208 | 0 | if (!butil::ReadFileToString(butil::FilePath(prof_name), &content)) { |
209 | 0 | LOG(WARNING) << "read " << prof_name << " fail"; |
210 | 0 | return; |
211 | 0 | } |
212 | 0 | buf.append(content); |
213 | 0 | return; |
214 | 0 | } |
215 | | |
216 | | // support curl/browser |
217 | 0 | buf.append(prof_name); |
218 | |
|
219 | 0 | std::string jeprof; |
220 | 0 | const char* f_jeprof = std::getenv("JEPROF_FILE"); |
221 | 0 | if (f_jeprof != nullptr && butil::PathExists(butil::FilePath(f_jeprof))) { |
222 | 0 | jeprof = f_jeprof; |
223 | 0 | } else { |
224 | 0 | LOG(WARNING) << "env JEPROF_FILE invalid"; |
225 | 0 | buf.append("\nenv JEPROF_FILE invalid"); |
226 | 0 | jeprof = "jeprof "; // use PATH |
227 | 0 | } |
228 | | |
229 | 0 | char process_path[500] = {}; |
230 | 0 | ssize_t len = butil::GetProcessAbsolutePath(process_path, sizeof(process_path)); |
231 | 0 | if (len == -1) { |
232 | 0 | LOG(WARNING) << "GetProcessAbsolutePath of process err"; |
233 | 0 | buf.append("\nGetProcessAbsolutePath of process err"); |
234 | 0 | return; |
235 | 0 | } |
236 | 0 | const std::string process_file(process_path, len); |
237 | |
|
238 | 0 | std::string cmd_str = jeprof + " " + process_file + " " + prof_name; |
239 | 0 | bool display_img = false; |
240 | 0 | if (*uri_display == "svg") { |
241 | 0 | cmd_str += " --svg "; |
242 | 0 | display_img = true; |
243 | 0 | } else if (*uri_display == "flamegraph") { |
244 | 0 | const char* flamegraph_tool = getenv("FLAMEGRAPH_PL_PATH"); |
245 | 0 | if (!flamegraph_tool) { |
246 | 0 | LOG(WARNING) << " display: " << *uri_display << " invalid, env FLAMEGRAPH_PL_PATH invalid"; |
247 | 0 | buf.append("\ndisplay:" + *uri_display + " invalid, env FLAMEGRAPH_PL_PATH invalid"); |
248 | 0 | return; |
249 | 0 | } |
250 | 0 | const int width_size = FLAGS_max_flame_graph_width > 0 ? FLAGS_max_flame_graph_width : 1200; |
251 | 0 | cmd_str += " --collapsed | " + std::string(flamegraph_tool) + " --colors mem --width " + std::to_string(width_size); |
252 | 0 | display_img = true; |
253 | 0 | } else if (*uri_display == "text") { |
254 | 0 | cmd_str += " --text "; |
255 | 0 | } else { |
256 | 0 | LOG(WARNING) << " display: " << *uri_display << " invalid"; |
257 | 0 | buf.append("\ndisplay:" + *uri_display + " invalid"); |
258 | 0 | return; |
259 | 0 | } |
260 | 0 | butil::IOBufBuilder builder; |
261 | 0 | if (butil::read_command_output(builder, cmd_str.c_str()) != 0) { |
262 | 0 | buf.append("\nread_command_output <" + cmd_str + "> fail"); |
263 | 0 | LOG(WARNING) << "read_command_output <" + cmd_str + "> fail"; |
264 | 0 | return; |
265 | 0 | } |
266 | | |
267 | 0 | if (display_img) { |
268 | 0 | buf.swap(builder.buf()); |
269 | 0 | cntl->http_response().set_content_type("image/svg+xml"); |
270 | 0 | } else { |
271 | 0 | buf.append("\ncmd: " + cmd_str + "\n"); |
272 | 0 | buf.append(builder.buf()); |
273 | 0 | } |
274 | 0 | } |
275 | | |
276 | 0 | static bool validate_je_prof_active(const char*, bool enable) { |
277 | 0 | if (!HasJemalloc()) { |
278 | 0 | return true; |
279 | 0 | } |
280 | | |
281 | 0 | if (!HasInit(__func__)) { |
282 | 0 | return true; |
283 | 0 | } |
284 | | |
285 | 0 | if (JeProfileActive(enable) != 0) { |
286 | 0 | LOG(WARNING) << "JeControlSample err"; |
287 | 0 | return false; |
288 | 0 | } |
289 | | |
290 | 0 | return true; |
291 | 0 | } |
292 | | |
293 | 0 | static bool validate_je_prof_dump(const char*, int32_t val) { |
294 | 0 | if (!HasJemalloc()) { |
295 | 0 | return true; |
296 | 0 | } |
297 | 0 | if (!HasInit(__func__)) { |
298 | 0 | return true; |
299 | 0 | } |
300 | | |
301 | 0 | const std::string prof_name = JeProfileDump(); |
302 | 0 | if (prof_name.empty()) { |
303 | 0 | LOG(WARNING) << "Fail to dump profile"; |
304 | 0 | return false; |
305 | 0 | } |
306 | 0 | return true; |
307 | 0 | } |
308 | | |
309 | 0 | static bool validate_je_prof_reset(const char*, int32_t val) { |
310 | 0 | if (!HasJemalloc()) { |
311 | 0 | return true; |
312 | 0 | } |
313 | 0 | if (!HasInit(__func__)) { |
314 | 0 | return true; |
315 | 0 | } |
316 | | |
317 | 0 | if (JeProfileReset(val) != 0) { |
318 | 0 | LOG(WARNING) << "JeProfileReset err"; |
319 | 0 | return false; |
320 | 0 | } |
321 | | |
322 | 0 | return true; |
323 | 0 | } |
324 | | |
325 | | // e.g: curl ip:port/flags/je_prof_active?setvalue=true or update flags in web |
326 | | BRPC_VALIDATE_GFLAG(je_prof_active, validate_je_prof_active); |
327 | | BRPC_VALIDATE_GFLAG(je_prof_dump, validate_je_prof_dump); |
328 | | BRPC_VALIDATE_GFLAG(je_prof_reset, validate_je_prof_reset); |
329 | | |
330 | | } |
331 | | |