580 lines
19 KiB
C++
580 lines
19 KiB
C++
// Licensed to the Apache Software Foundation (ASF) under one
|
|
// or more contributor license agreements. See the NOTICE file
|
|
// distributed with this work for additional information
|
|
// regarding copyright ownership. The ASF licenses this file
|
|
// to you under the Apache License, Version 2.0 (the
|
|
// "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
#include <time.h> // strftime
|
|
#include <pthread.h>
|
|
#include <map>
|
|
#include <limits>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h> // O_RDONLY
|
|
#include "butil/string_printf.h" // string_printf
|
|
#include "butil/string_splitter.h" // StringSplitter
|
|
#include "butil/file_util.h" // butil::FilePath
|
|
#include "butil/files/scoped_file.h" // ScopedFILE
|
|
#include "butil/time.h"
|
|
#include "butil/popen.h" // butil::read_command_output
|
|
#include "butil/process_util.h" // butil::ReadCommandLine
|
|
#include "brpc/log.h"
|
|
#include "brpc/controller.h" // Controller
|
|
#include "brpc/closure_guard.h" // ClosureGuard
|
|
#include "brpc/builtin/pprof_service.h"
|
|
#include "brpc/builtin/common.h"
|
|
#include "brpc/details/tcmalloc_extension.h"
|
|
#include "brpc/details/jemalloc_profiler.h"
|
|
#include "bthread/bthread.h" // bthread_usleep
|
|
#include "butil/fd_guard.h"
|
|
|
|
extern "C" {
|
|
#if defined(OS_LINUX)
|
|
extern char *program_invocation_name;
|
|
#endif
|
|
int BAIDU_WEAK ProfilerStart(const char* fname);
|
|
void BAIDU_WEAK ProfilerStop();
|
|
}
|
|
|
|
namespace bthread {
|
|
bool ContentionProfilerStart(const char* filename);
|
|
void ContentionProfilerStop();
|
|
}
|
|
|
|
|
|
namespace brpc {
|
|
|
|
static int ReadSeconds(Controller* cntl) {
|
|
int seconds = 0;
|
|
const std::string* param =
|
|
cntl->http_request().uri().GetQuery("seconds");
|
|
if (param != NULL) {
|
|
char* endptr = NULL;
|
|
const long sec = strtol(param->c_str(), &endptr, 10);
|
|
if (endptr == param->c_str() + param->length()) {
|
|
seconds = sec;
|
|
} else {
|
|
cntl->SetFailed(EINVAL, "Invalid seconds=%s", param->c_str());
|
|
}
|
|
}
|
|
|
|
return seconds;
|
|
}
|
|
|
|
int MakeProfName(ProfilingType type, char* buf, size_t buf_len) {
|
|
// Add pprof_ prefix to separate from /hotspots
|
|
int nr = snprintf(buf, buf_len, "%s/pprof_%s/", FLAGS_rpc_profiling_dir.c_str(),
|
|
GetProgramChecksum());
|
|
if (nr < 0) {
|
|
return -1;
|
|
}
|
|
buf += nr;
|
|
buf_len -= nr;
|
|
|
|
time_t rawtime;
|
|
time(&rawtime);
|
|
struct tm* timeinfo = localtime(&rawtime);
|
|
const size_t nw = strftime(buf, buf_len, "%Y%m%d.%H%M%S", timeinfo);
|
|
buf += nw;
|
|
buf_len -= nw;
|
|
|
|
// We have checksum in the path, getpid() is not necessary now.
|
|
snprintf(buf, buf_len, ".%s", ProfilingType2String(type));
|
|
return 0;
|
|
}
|
|
|
|
void PProfService::profile(
|
|
::google::protobuf::RpcController* controller_base,
|
|
const ::brpc::ProfileRequest* /*request*/,
|
|
::brpc::ProfileResponse* /*response*/,
|
|
::google::protobuf::Closure* done) {
|
|
ClosureGuard done_guard(done);
|
|
Controller* cntl = static_cast<Controller*>(controller_base);
|
|
cntl->http_response().set_content_type("text/plain");
|
|
if ((void*)ProfilerStart == NULL || (void*)ProfilerStop == NULL) {
|
|
cntl->SetFailed(ENOMETHOD, "%s, to enable cpu profiler, check out "
|
|
"docs/cn/cpu_profiler.md",
|
|
berror(ENOMETHOD));
|
|
return;
|
|
}
|
|
int sleep_sec = ReadSeconds(cntl);
|
|
if (sleep_sec <= 0) {
|
|
if (!cntl->Failed()) {
|
|
cntl->SetFailed(EINVAL, "You have to specify ?seconds=N. If you're "
|
|
"using pprof, add --seconds=N");
|
|
}
|
|
return;
|
|
}
|
|
// Log requester
|
|
std::ostringstream client_info;
|
|
client_info << cntl->remote_side();
|
|
if (cntl->auth_context()) {
|
|
client_info << "(auth=" << cntl->auth_context()->user() << ')';
|
|
} else {
|
|
client_info << "(no auth)";
|
|
}
|
|
LOG(INFO) << client_info.str() << " requests for cpu profile for "
|
|
<< sleep_sec << " seconds";
|
|
|
|
char prof_name[256];
|
|
if (MakeProfName(PROFILING_CPU, prof_name, sizeof(prof_name)) != 0) {
|
|
cntl->SetFailed(errno, "Fail to create .prof file, %s", berror());
|
|
return;
|
|
}
|
|
butil::File::Error error;
|
|
const butil::FilePath dir = butil::FilePath(prof_name).DirName();
|
|
if (!butil::CreateDirectoryAndGetError(dir, &error)) {
|
|
cntl->SetFailed(EPERM, "Fail to create directory=`%s'",dir.value().c_str());
|
|
return;
|
|
}
|
|
if (!ProfilerStart(prof_name)) {
|
|
cntl->SetFailed(EAGAIN, "Another profiler is running, try again later");
|
|
return;
|
|
}
|
|
if (bthread_usleep(sleep_sec * 1000000L) != 0) {
|
|
PLOG(WARNING) << "Profiling has been interrupted";
|
|
}
|
|
ProfilerStop();
|
|
|
|
butil::fd_guard fd(open(prof_name, O_RDONLY));
|
|
if (fd < 0) {
|
|
cntl->SetFailed(ENOENT, "Fail to open %s", prof_name);
|
|
return;
|
|
}
|
|
butil::IOPortal portal;
|
|
portal.append_from_file_descriptor(fd, ULONG_MAX);
|
|
cntl->response_attachment().swap(portal);
|
|
}
|
|
|
|
void PProfService::contention(
|
|
::google::protobuf::RpcController* controller_base,
|
|
const ::brpc::ProfileRequest* /*request*/,
|
|
::brpc::ProfileResponse* /*response*/,
|
|
::google::protobuf::Closure* done) {
|
|
ClosureGuard done_guard(done);
|
|
Controller* cntl = static_cast<Controller*>(controller_base);
|
|
cntl->http_response().set_content_type("text/plain");
|
|
int sleep_sec = ReadSeconds(cntl);
|
|
if (sleep_sec <= 0) {
|
|
if (!cntl->Failed()) {
|
|
cntl->SetFailed(EINVAL, "You have to specify ?seconds=N. If you're "
|
|
"using pprof, add --seconds=N");
|
|
}
|
|
return;
|
|
}
|
|
// Log requester
|
|
std::ostringstream client_info;
|
|
client_info << cntl->remote_side();
|
|
if (cntl->auth_context()) {
|
|
client_info << "(auth=" << cntl->auth_context()->user() << ')';
|
|
} else {
|
|
client_info << "(no auth)";
|
|
}
|
|
LOG(INFO) << client_info.str() << " requests for contention profile for "
|
|
<< sleep_sec << " seconds";
|
|
|
|
char prof_name[256];
|
|
if (MakeProfName(PROFILING_CONTENTION, prof_name, sizeof(prof_name)) != 0) {
|
|
cntl->SetFailed(errno, "Fail to create .prof file, %s", berror());
|
|
return;
|
|
}
|
|
if (!bthread::ContentionProfilerStart(prof_name)) {
|
|
cntl->SetFailed(EAGAIN, "Another profiler is running, try again later");
|
|
return;
|
|
}
|
|
if (bthread_usleep(sleep_sec * 1000000L) != 0) {
|
|
PLOG(WARNING) << "Profiling has been interrupted";
|
|
}
|
|
bthread::ContentionProfilerStop();
|
|
|
|
butil::fd_guard fd(open(prof_name, O_RDONLY));
|
|
if (fd < 0) {
|
|
cntl->SetFailed(ENOENT, "Fail to open %s", prof_name);
|
|
return;
|
|
}
|
|
butil::IOPortal portal;
|
|
portal.append_from_file_descriptor(fd, ULONG_MAX);
|
|
cntl->response_attachment().swap(portal);
|
|
}
|
|
|
|
void PProfService::heap(
|
|
::google::protobuf::RpcController* controller_base,
|
|
const ::brpc::ProfileRequest* /*request*/,
|
|
::brpc::ProfileResponse* /*response*/,
|
|
::google::protobuf::Closure* done) {
|
|
ClosureGuard done_guard(done);
|
|
Controller* cntl = static_cast<Controller*>(controller_base);
|
|
|
|
if (HasJemalloc()) {
|
|
JeControlProfile(cntl);
|
|
return;
|
|
}
|
|
|
|
MallocExtension* malloc_ext = MallocExtension::instance();
|
|
if (malloc_ext == NULL || !has_TCMALLOC_SAMPLE_PARAMETER()) {
|
|
const char* extra_desc = "";
|
|
if (malloc_ext != NULL) {
|
|
extra_desc = " (no TCMALLOC_SAMPLE_PARAMETER in env)";
|
|
}
|
|
cntl->SetFailed(ENOMETHOD, "Heap profiler is not enabled%s,"
|
|
"check out https://github.com/apache/brpc/blob/master/docs/cn/heap_profiler.md",
|
|
extra_desc);
|
|
return;
|
|
}
|
|
// Log requester
|
|
std::ostringstream client_info;
|
|
client_info << cntl->remote_side();
|
|
if (cntl->auth_context()) {
|
|
client_info << "(auth=" << cntl->auth_context()->user() << ')';
|
|
} else {
|
|
client_info << "(no auth)";
|
|
}
|
|
LOG(INFO) << client_info.str() << " requests for heap profile";
|
|
|
|
std::string obj;
|
|
malloc_ext->GetHeapSample(&obj);
|
|
cntl->http_response().set_content_type("text/plain");
|
|
cntl->response_attachment().append(obj);
|
|
}
|
|
|
|
void PProfService::growth(
|
|
::google::protobuf::RpcController* controller_base,
|
|
const ::brpc::ProfileRequest* /*request*/,
|
|
::brpc::ProfileResponse* /*response*/,
|
|
::google::protobuf::Closure* done) {
|
|
ClosureGuard done_guard(done);
|
|
Controller* cntl = static_cast<Controller*>(controller_base);
|
|
MallocExtension* malloc_ext = MallocExtension::instance();
|
|
if (malloc_ext == NULL) {
|
|
cntl->SetFailed(ENOMETHOD, "%s, to enable growth profiler, check out "
|
|
"docs/cn/heap_profiler.md",
|
|
berror(ENOMETHOD));
|
|
return;
|
|
}
|
|
// Log requester
|
|
std::ostringstream client_info;
|
|
client_info << cntl->remote_side();
|
|
if (cntl->auth_context()) {
|
|
client_info << "(auth=" << cntl->auth_context()->user() << ')';
|
|
} else {
|
|
client_info << "(no auth)";
|
|
}
|
|
LOG(INFO) << client_info.str() << " requests for growth profile";
|
|
|
|
std::string obj;
|
|
malloc_ext->GetHeapGrowthStacks(&obj);
|
|
cntl->http_response().set_content_type("text/plain");
|
|
cntl->response_attachment().append(obj);
|
|
}
|
|
|
|
typedef std::map<uintptr_t, std::string> SymbolMap;
|
|
struct LibInfo {
|
|
uintptr_t start_addr;
|
|
uintptr_t end_addr;
|
|
size_t offset;
|
|
std::string path;
|
|
};
|
|
static SymbolMap symbol_map;
|
|
static pthread_once_t s_load_symbolmap_once = PTHREAD_ONCE_INIT;
|
|
|
|
static bool HasExt(const std::string& name, const std::string& ext) {
|
|
size_t index = name.find(ext);
|
|
if (index == std::string::npos) {
|
|
return false;
|
|
}
|
|
return (index + ext.size() == name.size() ||
|
|
name[index + ext.size()] == '.');
|
|
}
|
|
|
|
static int ExtractSymbolsFromBinary(
|
|
std::map<uintptr_t, std::string>& addr_map,
|
|
const LibInfo& lib_info) {
|
|
butil::Timer tm;
|
|
tm.start();
|
|
std::string cmd = "nm -C -p ";
|
|
cmd.append(lib_info.path);
|
|
std::stringstream ss;
|
|
const int rc = butil::read_command_output(ss, cmd.c_str());
|
|
if (rc < 0) {
|
|
LOG(ERROR) << "Fail to popen `" << cmd << "'";
|
|
return -1;
|
|
}
|
|
std::string line;
|
|
while (std::getline(ss, line)) {
|
|
butil::StringSplitter sp(line.c_str(), ' ');
|
|
if (sp == NULL) {
|
|
continue;
|
|
}
|
|
char* endptr = NULL;
|
|
uintptr_t addr = strtoull(sp.field(), &endptr, 16);
|
|
if (*endptr != ' ') {
|
|
continue;
|
|
}
|
|
if (addr < lib_info.start_addr) {
|
|
addr = addr + lib_info.start_addr - lib_info.offset;
|
|
}
|
|
if (addr >= lib_info.end_addr) {
|
|
continue;
|
|
}
|
|
++sp;
|
|
if (sp == NULL) {
|
|
continue;
|
|
}
|
|
if (sp.length() != 1UL) {
|
|
continue;
|
|
}
|
|
//const char c = *sp.field();
|
|
|
|
++sp;
|
|
if (sp == NULL) {
|
|
continue;
|
|
}
|
|
const char* name_begin = sp.field();
|
|
if (strncmp(name_begin, "typeinfo ", 9) == 0 ||
|
|
strncmp(name_begin, "VTT ", 4) == 0 ||
|
|
strncmp(name_begin, "vtable ", 7) == 0 ||
|
|
strncmp(name_begin, "global ", 7) == 0 ||
|
|
strncmp(name_begin, "guard ", 6) == 0) {
|
|
addr_map[addr] = std::string();
|
|
continue;
|
|
}
|
|
|
|
const char* name_end = sp.field();
|
|
bool stop = false;
|
|
char last_char = '\0';
|
|
while (1) {
|
|
switch (*name_end) {
|
|
case 0:
|
|
case '\r':
|
|
case '\n':
|
|
stop = true;
|
|
break;
|
|
case '(':
|
|
case '<':
|
|
// \(.*\w\)[(<]... -> \1
|
|
// foo(..) -> foo
|
|
// foo<...>(...) -> foo
|
|
// a::b::foo(...) -> a::b::foo
|
|
// a::(b)::foo(...) -> a::(b)::foo
|
|
if (isalpha(last_char) || isdigit(last_char) ||
|
|
last_char == '_') {
|
|
stop = true;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
if (stop) {
|
|
break;
|
|
}
|
|
last_char = *name_end++;
|
|
}
|
|
// If address conflicts, choose a shorter name (not necessarily to be
|
|
// T type in nm). This works fine because aliases often have more
|
|
// prefixes.
|
|
const size_t name_len = name_end - name_begin;
|
|
SymbolMap::iterator it = addr_map.find(addr);
|
|
if (it != addr_map.end()) {
|
|
if (name_len < it->second.size()) {
|
|
it->second.assign(name_begin, name_len);
|
|
}
|
|
} else {
|
|
addr_map[addr] = std::string(name_begin, name_len);
|
|
}
|
|
}
|
|
if (addr_map.find(lib_info.end_addr) == addr_map.end()) {
|
|
addr_map[lib_info.end_addr] = std::string();
|
|
}
|
|
tm.stop();
|
|
RPC_VLOG << "Loaded " << lib_info.path << " in " << tm.m_elapsed() << "ms";
|
|
return 0;
|
|
}
|
|
|
|
static void LoadSymbols() {
|
|
butil::Timer tm;
|
|
tm.start();
|
|
butil::ScopedFILE fp(fopen("/proc/self/maps", "r"));
|
|
if (fp == NULL) {
|
|
return;
|
|
}
|
|
char* line = NULL;
|
|
size_t line_len = 0;
|
|
ssize_t nr = 0;
|
|
while ((nr = getline(&line, &line_len, fp.get())) != -1) {
|
|
butil::StringSplitter sp(line, line + nr, ' ');
|
|
if (sp == NULL) {
|
|
continue;
|
|
}
|
|
char* endptr;
|
|
uintptr_t start_addr = strtoull(sp.field(), &endptr, 16);
|
|
if (*endptr != '-') {
|
|
continue;
|
|
}
|
|
++endptr;
|
|
uintptr_t end_addr = strtoull(endptr, &endptr, 16);
|
|
if (*endptr != ' ') {
|
|
continue;
|
|
}
|
|
++sp;
|
|
// ..x. must be executable
|
|
if (sp == NULL || sp.length() != 4 || sp.field()[2] != 'x') {
|
|
continue;
|
|
}
|
|
++sp;
|
|
if (sp == NULL) {
|
|
continue;
|
|
}
|
|
size_t offset = strtoull(sp.field(), &endptr, 16);
|
|
if (*endptr != ' ') {
|
|
continue;
|
|
}
|
|
//skip $4~$5
|
|
for (int i = 0; i < 3; ++i) {
|
|
++sp;
|
|
}
|
|
if (sp == NULL) {
|
|
continue;
|
|
}
|
|
size_t n = sp.length();
|
|
if (sp.field()[n-1] == '\n') {
|
|
--n;
|
|
}
|
|
std::string path(sp.field(), n);
|
|
if (!HasExt(path, ".so") && !HasExt(path, ".dll") &&
|
|
!HasExt(path, ".dylib") && !HasExt(path, ".bundle")) {
|
|
continue;
|
|
}
|
|
LibInfo info;
|
|
info.start_addr = start_addr;
|
|
info.end_addr = end_addr;
|
|
info.offset = offset;
|
|
info.path = path;
|
|
ExtractSymbolsFromBinary(symbol_map, info);
|
|
}
|
|
free(line);
|
|
|
|
LibInfo info;
|
|
info.start_addr = 0;
|
|
info.end_addr = std::numeric_limits<uintptr_t>::max();
|
|
info.offset = 0;
|
|
#if defined(OS_LINUX)
|
|
info.path = program_invocation_name;
|
|
#elif defined(OS_MACOSX)
|
|
info.path = getprogname();
|
|
#endif
|
|
ExtractSymbolsFromBinary(symbol_map, info);
|
|
|
|
butil::Timer tm2;
|
|
tm2.start();
|
|
size_t num_removed = 0;
|
|
bool last_is_empty = false;
|
|
for (SymbolMap::iterator
|
|
it = symbol_map.begin(); it != symbol_map.end();) {
|
|
if (it->second.empty()) {
|
|
if (last_is_empty) {
|
|
symbol_map.erase(it++);
|
|
++num_removed;
|
|
} else {
|
|
++it;
|
|
}
|
|
last_is_empty = true;
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
tm2.stop();
|
|
RPC_VLOG_IF(num_removed) << "Removed " << num_removed << " entries in "
|
|
<< tm2.m_elapsed() << "ms";
|
|
|
|
tm.stop();
|
|
RPC_VLOG << "Loaded all symbols in " << tm.m_elapsed() << "ms";
|
|
}
|
|
|
|
static void FindSymbols(butil::IOBuf* out, std::vector<uintptr_t>& addr_list) {
|
|
char buf[32];
|
|
for (size_t i = 0; i < addr_list.size(); ++i) {
|
|
int len = snprintf(buf, sizeof(buf), "0x%08lx\t", addr_list[i]);
|
|
out->append(buf, len);
|
|
SymbolMap::const_iterator it = symbol_map.lower_bound(addr_list[i]);
|
|
if (it == symbol_map.end() || it->first != addr_list[i]) {
|
|
if (it != symbol_map.begin()) {
|
|
--it;
|
|
} else {
|
|
len = snprintf(buf, sizeof(buf), "0x%08lx\n", addr_list[i]);
|
|
out->append(buf, len);
|
|
continue;
|
|
}
|
|
}
|
|
if (it->second.empty()) {
|
|
len = snprintf(buf, sizeof(buf), "0x%08lx\n", addr_list[i]);
|
|
out->append(buf, len);
|
|
} else {
|
|
out->append(it->second);
|
|
out->push_back('\n');
|
|
}
|
|
}
|
|
}
|
|
|
|
void PProfService::symbol(
|
|
::google::protobuf::RpcController* controller_base,
|
|
const ::brpc::ProfileRequest* /*request*/,
|
|
::brpc::ProfileResponse* /*response*/,
|
|
::google::protobuf::Closure* done) {
|
|
ClosureGuard done_guard(done);
|
|
Controller* cntl = static_cast<Controller*>(controller_base);
|
|
cntl->http_response().set_content_type("text/plain");
|
|
|
|
// Load /proc/self/maps
|
|
pthread_once(&s_load_symbolmap_once, LoadSymbols);
|
|
|
|
if (cntl->http_request().method() != HTTP_METHOD_POST) {
|
|
char buf[64];
|
|
snprintf(buf, sizeof(buf), "num_symbols: %lu\n", symbol_map.size());
|
|
cntl->response_attachment().append(buf);
|
|
} else {
|
|
// addr_str is addressed separated by +
|
|
std::string addr_str = cntl->request_attachment().to_string();
|
|
// May be quoted
|
|
const char* addr_cstr = addr_str.c_str();
|
|
if (*addr_cstr == '\'' || *addr_cstr == '"') {
|
|
++addr_cstr;
|
|
}
|
|
std::vector<uintptr_t> addr_list;
|
|
addr_list.reserve(32);
|
|
butil::StringSplitter sp(addr_cstr, '+');
|
|
for ( ; sp != NULL; ++sp) {
|
|
char* endptr;
|
|
uintptr_t addr = strtoull(sp.field(), &endptr, 16);
|
|
addr_list.push_back(addr);
|
|
}
|
|
FindSymbols(&cntl->response_attachment(), addr_list);
|
|
}
|
|
}
|
|
|
|
void PProfService::cmdline(::google::protobuf::RpcController* controller_base,
|
|
const ::brpc::ProfileRequest* /*request*/,
|
|
::brpc::ProfileResponse* /*response*/,
|
|
::google::protobuf::Closure* done) {
|
|
ClosureGuard done_guard(done);
|
|
Controller* cntl = static_cast<Controller*>(controller_base);
|
|
cntl->http_response().set_content_type("text/plain" /*FIXME*/);
|
|
char buf[1024]; // should be enough?
|
|
const ssize_t nr = butil::ReadCommandLine(buf, sizeof(buf), true);
|
|
if (nr < 0) {
|
|
cntl->SetFailed(ENOENT, "Fail to read cmdline");
|
|
return;
|
|
}
|
|
cntl->response_attachment().append(buf, nr);
|
|
}
|
|
|
|
} // namespace brpc
|