pprof_service.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  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. #include <time.h> // strftime
  18. #include <pthread.h>
  19. #include <map>
  20. #include <limits>
  21. #include <sys/stat.h>
  22. #include <fcntl.h> // O_RDONLY
  23. #include "butil/string_printf.h" // string_printf
  24. #include "butil/string_splitter.h" // StringSplitter
  25. #include "butil/file_util.h" // butil::FilePath
  26. #include "butil/files/scoped_file.h" // ScopedFILE
  27. #include "butil/time.h"
  28. #include "butil/popen.h" // butil::read_command_output
  29. #include "butil/process_util.h" // butil::ReadCommandLine
  30. #include "brpc/log.h"
  31. #include "brpc/controller.h" // Controller
  32. #include "brpc/closure_guard.h" // ClosureGuard
  33. #include "brpc/builtin/pprof_service.h"
  34. #include "brpc/builtin/common.h"
  35. #include "brpc/details/tcmalloc_extension.h"
  36. #include "bthread/bthread.h" // bthread_usleep
  37. #include "butil/fd_guard.h"
  38. extern "C" {
  39. #if defined(OS_LINUX)
  40. extern char *program_invocation_name;
  41. #endif
  42. int __attribute__((weak)) ProfilerStart(const char* fname);
  43. void __attribute__((weak)) ProfilerStop();
  44. }
  45. namespace bthread {
  46. bool ContentionProfilerStart(const char* filename);
  47. void ContentionProfilerStop();
  48. }
  49. namespace brpc {
  50. static int ReadSeconds(Controller* cntl) {
  51. int seconds = 0;
  52. const std::string* param =
  53. cntl->http_request().uri().GetQuery("seconds");
  54. if (param != NULL) {
  55. char* endptr = NULL;
  56. const long sec = strtol(param->c_str(), &endptr, 10);
  57. if (endptr == param->c_str() + param->length()) {
  58. seconds = sec;
  59. } else {
  60. cntl->SetFailed(EINVAL, "Invalid seconds=%s", param->c_str());
  61. }
  62. }
  63. return seconds;
  64. }
  65. int MakeProfName(ProfilingType type, char* buf, size_t buf_len) {
  66. // Add pprof_ prefix to separate from /hotspots
  67. int nr = snprintf(buf, buf_len, "%s/pprof_%s/", FLAGS_rpc_profiling_dir.c_str(),
  68. GetProgramChecksum());
  69. if (nr < 0) {
  70. return -1;
  71. }
  72. buf += nr;
  73. buf_len -= nr;
  74. time_t rawtime;
  75. time(&rawtime);
  76. struct tm* timeinfo = localtime(&rawtime);
  77. const size_t nw = strftime(buf, buf_len, "%Y%m%d.%H%M%S", timeinfo);
  78. buf += nw;
  79. buf_len -= nw;
  80. // We have checksum in the path, getpid() is not necessary now.
  81. snprintf(buf, buf_len, ".%s", ProfilingType2String(type));
  82. return 0;
  83. }
  84. void PProfService::profile(
  85. ::google::protobuf::RpcController* controller_base,
  86. const ::brpc::ProfileRequest* /*request*/,
  87. ::brpc::ProfileResponse* /*response*/,
  88. ::google::protobuf::Closure* done) {
  89. ClosureGuard done_guard(done);
  90. Controller* cntl = static_cast<Controller*>(controller_base);
  91. cntl->http_response().set_content_type("text/plain");
  92. if ((void*)ProfilerStart == NULL || (void*)ProfilerStop == NULL) {
  93. cntl->SetFailed(ENOMETHOD, "%s, to enable cpu profiler, check out "
  94. "docs/cn/cpu_profiler.md",
  95. berror(ENOMETHOD));
  96. return;
  97. }
  98. int sleep_sec = ReadSeconds(cntl);
  99. if (sleep_sec <= 0) {
  100. if (!cntl->Failed()) {
  101. cntl->SetFailed(EINVAL, "You have to specify ?seconds=N. If you're "
  102. "using pprof, add --seconds=N");
  103. }
  104. return;
  105. }
  106. // Log requester
  107. std::ostringstream client_info;
  108. client_info << cntl->remote_side();
  109. if (cntl->auth_context()) {
  110. client_info << "(auth=" << cntl->auth_context()->user() << ')';
  111. } else {
  112. client_info << "(no auth)";
  113. }
  114. LOG(INFO) << client_info.str() << " requests for cpu profile for "
  115. << sleep_sec << " seconds";
  116. char prof_name[256];
  117. if (MakeProfName(PROFILING_CPU, prof_name, sizeof(prof_name)) != 0) {
  118. cntl->SetFailed(errno, "Fail to create .prof file, %s", berror());
  119. return;
  120. }
  121. butil::File::Error error;
  122. const butil::FilePath dir = butil::FilePath(prof_name).DirName();
  123. if (!butil::CreateDirectoryAndGetError(dir, &error)) {
  124. cntl->SetFailed(EPERM, "Fail to create directory=`%s'",dir.value().c_str());
  125. return;
  126. }
  127. if (!ProfilerStart(prof_name)) {
  128. cntl->SetFailed(EAGAIN, "Another profiler is running, try again later");
  129. return;
  130. }
  131. if (bthread_usleep(sleep_sec * 1000000L) != 0) {
  132. PLOG(WARNING) << "Profiling has been interrupted";
  133. }
  134. ProfilerStop();
  135. butil::fd_guard fd(open(prof_name, O_RDONLY));
  136. if (fd < 0) {
  137. cntl->SetFailed(ENOENT, "Fail to open %s", prof_name);
  138. return;
  139. }
  140. butil::IOPortal portal;
  141. portal.append_from_file_descriptor(fd, ULONG_MAX);
  142. cntl->response_attachment().swap(portal);
  143. }
  144. void PProfService::contention(
  145. ::google::protobuf::RpcController* controller_base,
  146. const ::brpc::ProfileRequest* /*request*/,
  147. ::brpc::ProfileResponse* /*response*/,
  148. ::google::protobuf::Closure* done) {
  149. ClosureGuard done_guard(done);
  150. Controller* cntl = static_cast<Controller*>(controller_base);
  151. cntl->http_response().set_content_type("text/plain");
  152. int sleep_sec = ReadSeconds(cntl);
  153. if (sleep_sec <= 0) {
  154. if (!cntl->Failed()) {
  155. cntl->SetFailed(EINVAL, "You have to specify ?seconds=N. If you're "
  156. "using pprof, add --seconds=N");
  157. }
  158. return;
  159. }
  160. // Log requester
  161. std::ostringstream client_info;
  162. client_info << cntl->remote_side();
  163. if (cntl->auth_context()) {
  164. client_info << "(auth=" << cntl->auth_context()->user() << ')';
  165. } else {
  166. client_info << "(no auth)";
  167. }
  168. LOG(INFO) << client_info.str() << " requests for contention profile for "
  169. << sleep_sec << " seconds";
  170. char prof_name[256];
  171. if (MakeProfName(PROFILING_CONTENTION, prof_name, sizeof(prof_name)) != 0) {
  172. cntl->SetFailed(errno, "Fail to create .prof file, %s", berror());
  173. return;
  174. }
  175. if (!bthread::ContentionProfilerStart(prof_name)) {
  176. cntl->SetFailed(EAGAIN, "Another profiler is running, try again later");
  177. return;
  178. }
  179. if (bthread_usleep(sleep_sec * 1000000L) != 0) {
  180. PLOG(WARNING) << "Profiling has been interrupted";
  181. }
  182. bthread::ContentionProfilerStop();
  183. butil::fd_guard fd(open(prof_name, O_RDONLY));
  184. if (fd < 0) {
  185. cntl->SetFailed(ENOENT, "Fail to open %s", prof_name);
  186. return;
  187. }
  188. butil::IOPortal portal;
  189. portal.append_from_file_descriptor(fd, ULONG_MAX);
  190. cntl->response_attachment().swap(portal);
  191. }
  192. void PProfService::heap(
  193. ::google::protobuf::RpcController* controller_base,
  194. const ::brpc::ProfileRequest* /*request*/,
  195. ::brpc::ProfileResponse* /*response*/,
  196. ::google::protobuf::Closure* done) {
  197. ClosureGuard done_guard(done);
  198. Controller* cntl = static_cast<Controller*>(controller_base);
  199. MallocExtension* malloc_ext = MallocExtension::instance();
  200. if (malloc_ext == NULL || !has_TCMALLOC_SAMPLE_PARAMETER()) {
  201. const char* extra_desc = "";
  202. if (malloc_ext != NULL) {
  203. extra_desc = " (no TCMALLOC_SAMPLE_PARAMETER in env)";
  204. }
  205. cntl->SetFailed(ENOMETHOD, "Heap profiler is not enabled%s,"
  206. "check out http://wiki.baidu.com/display/RPC",
  207. extra_desc);
  208. return;
  209. }
  210. // Log requester
  211. std::ostringstream client_info;
  212. client_info << cntl->remote_side();
  213. if (cntl->auth_context()) {
  214. client_info << "(auth=" << cntl->auth_context()->user() << ')';
  215. } else {
  216. client_info << "(no auth)";
  217. }
  218. LOG(INFO) << client_info.str() << " requests for heap profile";
  219. std::string obj;
  220. malloc_ext->GetHeapSample(&obj);
  221. cntl->http_response().set_content_type("text/plain");
  222. cntl->response_attachment().append(obj);
  223. }
  224. void PProfService::growth(
  225. ::google::protobuf::RpcController* controller_base,
  226. const ::brpc::ProfileRequest* /*request*/,
  227. ::brpc::ProfileResponse* /*response*/,
  228. ::google::protobuf::Closure* done) {
  229. ClosureGuard done_guard(done);
  230. Controller* cntl = static_cast<Controller*>(controller_base);
  231. MallocExtension* malloc_ext = MallocExtension::instance();
  232. if (malloc_ext == NULL) {
  233. cntl->SetFailed(ENOMETHOD, "%s, to enable growth profiler, check out "
  234. "docs/cn/heap_profiler.md",
  235. berror(ENOMETHOD));
  236. return;
  237. }
  238. // Log requester
  239. std::ostringstream client_info;
  240. client_info << cntl->remote_side();
  241. if (cntl->auth_context()) {
  242. client_info << "(auth=" << cntl->auth_context()->user() << ')';
  243. } else {
  244. client_info << "(no auth)";
  245. }
  246. LOG(INFO) << client_info.str() << " requests for growth profile";
  247. std::string obj;
  248. malloc_ext->GetHeapGrowthStacks(&obj);
  249. cntl->http_response().set_content_type("text/plain");
  250. cntl->response_attachment().append(obj);
  251. }
  252. typedef std::map<uintptr_t, std::string> SymbolMap;
  253. struct LibInfo {
  254. uintptr_t start_addr;
  255. uintptr_t end_addr;
  256. size_t offset;
  257. std::string path;
  258. };
  259. static SymbolMap symbol_map;
  260. static pthread_once_t s_load_symbolmap_once = PTHREAD_ONCE_INIT;
  261. static bool HasExt(const std::string& name, const std::string& ext) {
  262. size_t index = name.find(ext);
  263. if (index == std::string::npos) {
  264. return false;
  265. }
  266. return (index + ext.size() == name.size() ||
  267. name[index + ext.size()] == '.');
  268. }
  269. static int ExtractSymbolsFromBinary(
  270. std::map<uintptr_t, std::string>& addr_map,
  271. const LibInfo& lib_info) {
  272. butil::Timer tm;
  273. tm.start();
  274. std::string cmd = "nm -C -p ";
  275. cmd.append(lib_info.path);
  276. std::stringstream ss;
  277. const int rc = butil::read_command_output(ss, cmd.c_str());
  278. if (rc < 0) {
  279. LOG(ERROR) << "Fail to popen `" << cmd << "'";
  280. return -1;
  281. }
  282. std::string line;
  283. while (std::getline(ss, line)) {
  284. butil::StringSplitter sp(line.c_str(), ' ');
  285. if (sp == NULL) {
  286. continue;
  287. }
  288. char* endptr = NULL;
  289. uintptr_t addr = strtoull(sp.field(), &endptr, 16);
  290. if (*endptr != ' ') {
  291. continue;
  292. }
  293. if (addr < lib_info.start_addr) {
  294. addr = addr + lib_info.start_addr - lib_info.offset;
  295. }
  296. if (addr >= lib_info.end_addr) {
  297. continue;
  298. }
  299. ++sp;
  300. if (sp == NULL) {
  301. continue;
  302. }
  303. if (sp.length() != 1UL) {
  304. continue;
  305. }
  306. //const char c = *sp.field();
  307. ++sp;
  308. if (sp == NULL) {
  309. continue;
  310. }
  311. const char* name_begin = sp.field();
  312. if (strncmp(name_begin, "typeinfo ", 9) == 0 ||
  313. strncmp(name_begin, "VTT ", 4) == 0 ||
  314. strncmp(name_begin, "vtable ", 7) == 0 ||
  315. strncmp(name_begin, "global ", 7) == 0 ||
  316. strncmp(name_begin, "guard ", 6) == 0) {
  317. addr_map[addr] = std::string();
  318. continue;
  319. }
  320. const char* name_end = sp.field();
  321. bool stop = false;
  322. char last_char = '\0';
  323. while (1) {
  324. switch (*name_end) {
  325. case 0:
  326. case '\r':
  327. case '\n':
  328. stop = true;
  329. break;
  330. case '(':
  331. case '<':
  332. // \(.*\w\)[(<]... -> \1
  333. // foo(..) -> foo
  334. // foo<...>(...) -> foo
  335. // a::b::foo(...) -> a::b::foo
  336. // a::(b)::foo(...) -> a::(b)::foo
  337. if (isalpha(last_char) || isdigit(last_char) ||
  338. last_char == '_') {
  339. stop = true;
  340. }
  341. default:
  342. break;
  343. }
  344. if (stop) {
  345. break;
  346. }
  347. last_char = *name_end++;
  348. }
  349. // If address conflicts, choose a shorter name (not necessarily to be
  350. // T type in nm). This works fine because aliases often have more
  351. // prefixes.
  352. const size_t name_len = name_end - name_begin;
  353. SymbolMap::iterator it = addr_map.find(addr);
  354. if (it != addr_map.end()) {
  355. if (name_len < it->second.size()) {
  356. it->second.assign(name_begin, name_len);
  357. }
  358. } else {
  359. addr_map[addr] = std::string(name_begin, name_len);
  360. }
  361. }
  362. if (addr_map.find(lib_info.end_addr) == addr_map.end()) {
  363. addr_map[lib_info.end_addr] = std::string();
  364. }
  365. tm.stop();
  366. RPC_VLOG << "Loaded " << lib_info.path << " in " << tm.m_elapsed() << "ms";
  367. return 0;
  368. }
  369. static void LoadSymbols() {
  370. butil::Timer tm;
  371. tm.start();
  372. butil::ScopedFILE fp(fopen("/proc/self/maps", "r"));
  373. if (fp == NULL) {
  374. return;
  375. }
  376. char* line = NULL;
  377. size_t line_len = 0;
  378. ssize_t nr = 0;
  379. while ((nr = getline(&line, &line_len, fp.get())) != -1) {
  380. butil::StringSplitter sp(line, line + nr, ' ');
  381. if (sp == NULL) {
  382. continue;
  383. }
  384. char* endptr;
  385. uintptr_t start_addr = strtoull(sp.field(), &endptr, 16);
  386. if (*endptr != '-') {
  387. continue;
  388. }
  389. ++endptr;
  390. uintptr_t end_addr = strtoull(endptr, &endptr, 16);
  391. if (*endptr != ' ') {
  392. continue;
  393. }
  394. ++sp;
  395. // ..x. must be executable
  396. if (sp == NULL || sp.length() != 4 || sp.field()[2] != 'x') {
  397. continue;
  398. }
  399. ++sp;
  400. if (sp == NULL) {
  401. continue;
  402. }
  403. size_t offset = strtoull(sp.field(), &endptr, 16);
  404. if (*endptr != ' ') {
  405. continue;
  406. }
  407. //skip $4~$5
  408. for (int i = 0; i < 3; ++i) {
  409. ++sp;
  410. }
  411. if (sp == NULL) {
  412. continue;
  413. }
  414. size_t n = sp.length();
  415. if (sp.field()[n-1] == '\n') {
  416. --n;
  417. }
  418. std::string path(sp.field(), n);
  419. if (!HasExt(path, ".so") && !HasExt(path, ".dll") &&
  420. !HasExt(path, ".dylib") && !HasExt(path, ".bundle")) {
  421. continue;
  422. }
  423. LibInfo info;
  424. info.start_addr = start_addr;
  425. info.end_addr = end_addr;
  426. info.offset = offset;
  427. info.path = path;
  428. ExtractSymbolsFromBinary(symbol_map, info);
  429. }
  430. free(line);
  431. LibInfo info;
  432. info.start_addr = 0;
  433. info.end_addr = std::numeric_limits<uintptr_t>::max();
  434. info.offset = 0;
  435. #if defined(OS_LINUX)
  436. info.path = program_invocation_name;
  437. #elif defined(OS_MACOSX)
  438. info.path = getprogname();
  439. #endif
  440. ExtractSymbolsFromBinary(symbol_map, info);
  441. butil::Timer tm2;
  442. tm2.start();
  443. size_t num_removed = 0;
  444. bool last_is_empty = false;
  445. for (SymbolMap::iterator
  446. it = symbol_map.begin(); it != symbol_map.end();) {
  447. if (it->second.empty()) {
  448. if (last_is_empty) {
  449. symbol_map.erase(it++);
  450. ++num_removed;
  451. } else {
  452. ++it;
  453. }
  454. last_is_empty = true;
  455. } else {
  456. ++it;
  457. }
  458. }
  459. tm2.stop();
  460. RPC_VLOG_IF(num_removed) << "Removed " << num_removed << " entries in "
  461. << tm2.m_elapsed() << "ms";
  462. tm.stop();
  463. RPC_VLOG << "Loaded all symbols in " << tm.m_elapsed() << "ms";
  464. }
  465. static void FindSymbols(butil::IOBuf* out, std::vector<uintptr_t>& addr_list) {
  466. char buf[32];
  467. for (size_t i = 0; i < addr_list.size(); ++i) {
  468. int len = snprintf(buf, sizeof(buf), "0x%08lx\t", addr_list[i]);
  469. out->append(buf, len);
  470. SymbolMap::const_iterator it = symbol_map.lower_bound(addr_list[i]);
  471. if (it == symbol_map.end() || it->first != addr_list[i]) {
  472. if (it != symbol_map.begin()) {
  473. --it;
  474. } else {
  475. len = snprintf(buf, sizeof(buf), "0x%08lx\n", addr_list[i]);
  476. out->append(buf, len);
  477. continue;
  478. }
  479. }
  480. if (it->second.empty()) {
  481. len = snprintf(buf, sizeof(buf), "0x%08lx\n", addr_list[i]);
  482. out->append(buf, len);
  483. } else {
  484. out->append(it->second);
  485. out->push_back('\n');
  486. }
  487. }
  488. }
  489. void PProfService::symbol(
  490. ::google::protobuf::RpcController* controller_base,
  491. const ::brpc::ProfileRequest* /*request*/,
  492. ::brpc::ProfileResponse* /*response*/,
  493. ::google::protobuf::Closure* done) {
  494. ClosureGuard done_guard(done);
  495. Controller* cntl = static_cast<Controller*>(controller_base);
  496. cntl->http_response().set_content_type("text/plain");
  497. // Load /proc/self/maps
  498. pthread_once(&s_load_symbolmap_once, LoadSymbols);
  499. if (cntl->http_request().method() != HTTP_METHOD_POST) {
  500. char buf[64];
  501. snprintf(buf, sizeof(buf), "num_symbols: %lu\n", symbol_map.size());
  502. cntl->response_attachment().append(buf);
  503. } else {
  504. // addr_str is addressed separated by +
  505. std::string addr_str = cntl->request_attachment().to_string();
  506. // May be quoted
  507. const char* addr_cstr = addr_str.c_str();
  508. if (*addr_cstr == '\'' || *addr_cstr == '"') {
  509. ++addr_cstr;
  510. }
  511. std::vector<uintptr_t> addr_list;
  512. addr_list.reserve(32);
  513. butil::StringSplitter sp(addr_cstr, '+');
  514. for ( ; sp != NULL; ++sp) {
  515. char* endptr;
  516. uintptr_t addr = strtoull(sp.field(), &endptr, 16);
  517. addr_list.push_back(addr);
  518. }
  519. FindSymbols(&cntl->response_attachment(), addr_list);
  520. }
  521. }
  522. void PProfService::cmdline(::google::protobuf::RpcController* controller_base,
  523. const ::brpc::ProfileRequest* /*request*/,
  524. ::brpc::ProfileResponse* /*response*/,
  525. ::google::protobuf::Closure* done) {
  526. ClosureGuard done_guard(done);
  527. Controller* cntl = static_cast<Controller*>(controller_base);
  528. cntl->http_response().set_content_type("text/plain" /*FIXME*/);
  529. char buf[1024]; // should be enough?
  530. const ssize_t nr = butil::ReadCommandLine(buf, sizeof(buf), true);
  531. if (nr < 0) {
  532. cntl->SetFailed(ENOENT, "Fail to read cmdline");
  533. return;
  534. }
  535. cntl->response_attachment().append(buf, nr);
  536. }
  537. } // namespace brpc