123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572 |
- // 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 "bthread/bthread.h" // bthread_usleep
- #include "butil/fd_guard.h"
- extern "C" {
- #if defined(OS_LINUX)
- extern char *program_invocation_name;
- #endif
- int __attribute__((weak)) ProfilerStart(const char* fname);
- void __attribute__((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);
- 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 http://wiki.baidu.com/display/RPC",
- 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
|