Coverage Report

Created: 2025-03-11 06:06

/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