Coverage Report

Created: 2026-03-12 14:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
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