hotspots_service.cpp 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233
  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 <stdio.h>
  18. #include <thread>
  19. #include <gflags/gflags.h>
  20. #include "butil/files/file_enumerator.h"
  21. #include "butil/file_util.h" // butil::FilePath
  22. #include "butil/popen.h" // butil::read_command_output
  23. #include "butil/fd_guard.h" // butil::fd_guard
  24. #include "brpc/log.h"
  25. #include "brpc/controller.h"
  26. #include "brpc/server.h"
  27. #include "brpc/reloadable_flags.h"
  28. #include "brpc/builtin/pprof_perl.h"
  29. #include "brpc/builtin/flamegraph_perl.h"
  30. #include "brpc/builtin/hotspots_service.h"
  31. #include "brpc/details/tcmalloc_extension.h"
  32. extern "C" {
  33. int __attribute__((weak)) ProfilerStart(const char* fname);
  34. void __attribute__((weak)) ProfilerStop();
  35. }
  36. namespace bthread {
  37. bool ContentionProfilerStart(const char* filename);
  38. void ContentionProfilerStop();
  39. }
  40. namespace brpc {
  41. enum class DisplayType{
  42. kUnknown,
  43. kDot,
  44. #if defined(OS_LINUX)
  45. kFlameGraph,
  46. #endif
  47. kText
  48. };
  49. static const char* DisplayTypeToString(DisplayType type) {
  50. switch (type) {
  51. case DisplayType::kDot: return "dot";
  52. #if defined(OS_LINUX)
  53. case DisplayType::kFlameGraph: return "flame";
  54. #endif
  55. case DisplayType::kText: return "text";
  56. default: return "unknown";
  57. }
  58. }
  59. static DisplayType StringToDisplayType(const std::string& val) {
  60. static butil::CaseIgnoredFlatMap<DisplayType>* display_type_map;
  61. static std::once_flag flag;
  62. std::call_once(flag, []() {
  63. display_type_map = new butil::CaseIgnoredFlatMap<DisplayType>;
  64. display_type_map->init(10);
  65. (*display_type_map)["dot"] = DisplayType::kDot;
  66. #if defined(OS_LINUX)
  67. (*display_type_map)["flame"] = DisplayType::kFlameGraph;
  68. #endif
  69. (*display_type_map)["text"] = DisplayType::kText;
  70. });
  71. auto type = display_type_map->seek(val);
  72. if (type == nullptr) {
  73. return DisplayType::kUnknown;
  74. }
  75. return *type;
  76. }
  77. static std::string DisplayTypeToPProfArgument(DisplayType type) {
  78. switch (type) {
  79. #if defined(OS_LINUX)
  80. case DisplayType::kDot: return " --dot ";
  81. case DisplayType::kFlameGraph: return " --collapsed ";
  82. case DisplayType::kText: return " --text ";
  83. #elif defined(OS_MACOSX)
  84. case DisplayType::kDot: return " -dot ";
  85. case DisplayType::kText: return " -text ";
  86. #endif
  87. default: return " unknown type ";
  88. }
  89. }
  90. static std::string GeneratePerlScriptPath(const std::string& filename) {
  91. std::string path;
  92. path.reserve(FLAGS_rpc_profiling_dir.size() + 1 + filename.size());
  93. path += FLAGS_rpc_profiling_dir;
  94. path.push_back('/');
  95. path += filename;
  96. return path;
  97. }
  98. extern bool cpu_profiler_enabled;
  99. DEFINE_int32(max_profiling_seconds, 300, "upper limit of running time of profilers");
  100. BRPC_VALIDATE_GFLAG(max_profiling_seconds, NonNegativeInteger);
  101. DEFINE_int32(max_profiles_kept, 32,
  102. "max profiles kept for cpu/heap/growth/contention respectively");
  103. BRPC_VALIDATE_GFLAG(max_profiles_kept, PassValidate);
  104. static const char* const PPROF_FILENAME = "pprof.pl";
  105. static const char* const FLAMEGRAPH_FILENAME = "flamegraph.pl";
  106. static int DEFAULT_PROFILING_SECONDS = 10;
  107. static size_t CONCURRENT_PROFILING_LIMIT = 256;
  108. struct ProfilingWaiter {
  109. Controller* cntl;
  110. ::google::protobuf::Closure* done;
  111. };
  112. // Information of the client doing profiling.
  113. struct ProfilingClient {
  114. ProfilingClient() : end_us(0), seconds(0), id(0) {}
  115. int64_t end_us;
  116. int seconds;
  117. int64_t id;
  118. butil::EndPoint point;
  119. };
  120. struct ProfilingResult {
  121. ProfilingResult() : id(0), status_code(HTTP_STATUS_OK) {}
  122. int64_t id;
  123. int status_code;
  124. butil::IOBuf result;
  125. };
  126. static bool g_written_pprof_perl = false;
  127. struct ProfilingEnvironment {
  128. pthread_mutex_t mutex;
  129. int64_t cur_id;
  130. ProfilingClient* client;
  131. std::vector<ProfilingWaiter>* waiters;
  132. ProfilingResult* cached_result;
  133. };
  134. // Different ProfilingType have different env.
  135. static ProfilingEnvironment g_env[4] = {
  136. { PTHREAD_MUTEX_INITIALIZER, 0, NULL, NULL, NULL },
  137. { PTHREAD_MUTEX_INITIALIZER, 0, NULL, NULL, NULL },
  138. { PTHREAD_MUTEX_INITIALIZER, 0, NULL, NULL, NULL },
  139. { PTHREAD_MUTEX_INITIALIZER, 0, NULL, NULL, NULL }
  140. };
  141. // The `content' should be small so that it can be written into file in one
  142. // fwrite (at most time).
  143. static bool WriteSmallFile(const char* filepath_in,
  144. const butil::StringPiece& content) {
  145. butil::File::Error error;
  146. butil::FilePath path(filepath_in);
  147. butil::FilePath dir = path.DirName();
  148. if (!butil::CreateDirectoryAndGetError(dir, &error)) {
  149. LOG(ERROR) << "Fail to create directory=`" << dir.value()
  150. << "', " << error;
  151. return false;
  152. }
  153. FILE* fp = fopen(path.value().c_str(), "w");
  154. if (NULL == fp) {
  155. LOG(ERROR) << "Fail to open `" << path.value() << '\'';
  156. return false;
  157. }
  158. bool ret = true;
  159. if (fwrite(content.data(), content.size(), 1UL, fp) != 1UL) {
  160. LOG(ERROR) << "Fail to write into " << path.value();
  161. ret = false;
  162. }
  163. CHECK_EQ(0, fclose(fp));
  164. return ret;
  165. }
  166. static bool WriteSmallFile(const char* filepath_in,
  167. const butil::IOBuf& content) {
  168. butil::File::Error error;
  169. butil::FilePath path(filepath_in);
  170. butil::FilePath dir = path.DirName();
  171. if (!butil::CreateDirectoryAndGetError(dir, &error)) {
  172. LOG(ERROR) << "Fail to create directory=`" << dir.value()
  173. << "', " << error;
  174. return false;
  175. }
  176. FILE* fp = fopen(path.value().c_str(), "w");
  177. if (NULL == fp) {
  178. LOG(ERROR) << "Fail to open `" << path.value() << '\'';
  179. return false;
  180. }
  181. butil::IOBufAsZeroCopyInputStream iter(content);
  182. const void* data = NULL;
  183. int size = 0;
  184. while (iter.Next(&data, &size)) {
  185. if (fwrite(data, size, 1UL, fp) != 1UL) {
  186. LOG(ERROR) << "Fail to write into " << path.value();
  187. fclose(fp);
  188. return false;
  189. }
  190. }
  191. fclose(fp);
  192. return true;
  193. }
  194. static int ReadSeconds(const Controller* cntl) {
  195. int seconds = DEFAULT_PROFILING_SECONDS;
  196. const std::string* param =
  197. cntl->http_request().uri().GetQuery("seconds");
  198. if (param != NULL) {
  199. char* endptr = NULL;
  200. const long sec = strtol(param->c_str(), &endptr, 10);
  201. if (endptr == param->c_str() + param->length()) {
  202. seconds = sec;
  203. } else {
  204. return -1;
  205. }
  206. }
  207. seconds = std::min(seconds, FLAGS_max_profiling_seconds);
  208. return seconds;
  209. }
  210. static const char* GetBaseName(const std::string* full_base_name) {
  211. if (full_base_name == NULL) {
  212. return NULL;
  213. }
  214. size_t offset = full_base_name->find_last_of('/');
  215. if (offset == std::string::npos) {
  216. offset = 0;
  217. } else {
  218. ++offset;
  219. }
  220. return full_base_name->c_str() + offset;
  221. }
  222. static const char* GetBaseName(const char* full_base_name) {
  223. butil::StringPiece s(full_base_name);
  224. size_t offset = s.find_last_of('/');
  225. if (offset == butil::StringPiece::npos) {
  226. offset = 0;
  227. } else {
  228. ++offset;
  229. }
  230. return s.data() + offset;
  231. }
  232. // Test if path of the profile is valid.
  233. // NOTE: this function MUST be applied to all parameters finally passed to
  234. // system related functions (popen/system/exec ...) to avoid potential
  235. // injections from URL and other user inputs.
  236. static bool ValidProfilePath(const butil::StringPiece& path) {
  237. if (!path.starts_with(FLAGS_rpc_profiling_dir)) {
  238. // Must be under the directory.
  239. return false;
  240. }
  241. int consecutive_dot_count = 0;
  242. for (size_t i = 0; i < path.size(); ++i) {
  243. const char c = path[i];
  244. if (c == '.') {
  245. ++consecutive_dot_count;
  246. if (consecutive_dot_count >= 2) {
  247. // Disallow consecutive dots to go to upper level directories.
  248. return false;
  249. } else {
  250. continue;
  251. }
  252. } else {
  253. consecutive_dot_count = 0;
  254. }
  255. if (!isalpha(c) && !isdigit(c) &&
  256. c != '_' && c != '-' && c != '/') {
  257. return false;
  258. }
  259. }
  260. return true;
  261. }
  262. static int MakeCacheName(char* cache_name, size_t len,
  263. const char* prof_name,
  264. const char* base_name,
  265. DisplayType display_type,
  266. bool show_ccount) {
  267. if (base_name) {
  268. return snprintf(cache_name, len, "%s.cache/base_%s.%s%s", prof_name,
  269. base_name,
  270. DisplayTypeToString(display_type),
  271. (show_ccount ? ".ccount" : ""));
  272. } else {
  273. return snprintf(cache_name, len, "%s.cache/%s%s", prof_name,
  274. DisplayTypeToString(display_type),
  275. (show_ccount ? ".ccount" : ""));
  276. }
  277. }
  278. static int MakeProfName(ProfilingType type, char* buf, size_t buf_len) {
  279. int nr = snprintf(buf, buf_len, "%s/%s/", FLAGS_rpc_profiling_dir.c_str(),
  280. GetProgramChecksum());
  281. if (nr < 0) {
  282. return -1;
  283. }
  284. buf += nr;
  285. buf_len -= nr;
  286. time_t rawtime;
  287. time(&rawtime);
  288. struct tm* timeinfo = localtime(&rawtime);
  289. const size_t nw = strftime(buf, buf_len, "%Y%m%d.%H%M%S", timeinfo);
  290. buf += nw;
  291. buf_len -= nw;
  292. // We have checksum in the path, getpid() is not necessary now.
  293. snprintf(buf, buf_len, ".%s", ProfilingType2String(type));
  294. return 0;
  295. }
  296. static void ConsumeWaiters(ProfilingType type, const Controller* cur_cntl,
  297. std::vector<ProfilingWaiter>* waiters) {
  298. waiters->clear();
  299. if ((int)type >= (int)arraysize(g_env)) {
  300. LOG(ERROR) << "Invalid type=" << type;
  301. return;
  302. }
  303. ProfilingEnvironment& env = g_env[type];
  304. if (env.client) {
  305. BAIDU_SCOPED_LOCK(env.mutex);
  306. if (env.client == NULL) {
  307. return;
  308. }
  309. if (env.cached_result == NULL) {
  310. env.cached_result = new ProfilingResult;
  311. }
  312. env.cached_result->id = env.client->id;
  313. env.cached_result->status_code =
  314. cur_cntl->http_response().status_code();
  315. env.cached_result->result = cur_cntl->response_attachment();
  316. delete env.client;
  317. env.client = NULL;
  318. if (env.waiters) {
  319. env.waiters->swap(*waiters);
  320. }
  321. }
  322. }
  323. // This function is always called with g_env[type].mutex UNLOCKED.
  324. static void NotifyWaiters(ProfilingType type, const Controller* cur_cntl,
  325. const std::string* view) {
  326. if (view != NULL) {
  327. return;
  328. }
  329. std::vector<ProfilingWaiter> saved_waiters;
  330. CHECK(g_env[type].client);
  331. ConsumeWaiters(type, cur_cntl, &saved_waiters);
  332. for (size_t i = 0; i < saved_waiters.size(); ++i) {
  333. Controller* cntl = saved_waiters[i].cntl;
  334. ::google::protobuf::Closure* done = saved_waiters[i].done;
  335. cntl->http_response().set_status_code(
  336. cur_cntl->http_response().status_code());
  337. cntl->response_attachment().append(cur_cntl->response_attachment());
  338. done->Run();
  339. }
  340. }
  341. #if defined(OS_MACOSX)
  342. static bool check_GOOGLE_PPROF_BINARY_PATH() {
  343. char* str = getenv("GOOGLE_PPROF_BINARY_PATH");
  344. if (str == NULL) {
  345. return false;
  346. }
  347. butil::fd_guard fd(open(str, O_RDONLY));
  348. if (fd < 0) {
  349. return false;
  350. }
  351. return true;
  352. }
  353. static bool has_GOOGLE_PPROF_BINARY_PATH() {
  354. static bool val = check_GOOGLE_PPROF_BINARY_PATH();
  355. return val;
  356. }
  357. #endif
  358. static void DisplayResult(Controller* cntl,
  359. google::protobuf::Closure* done,
  360. const char* prof_name,
  361. const butil::IOBuf& result_prefix) {
  362. ClosureGuard done_guard(done);
  363. butil::IOBuf prof_result;
  364. if (cntl->IsCanceled()) {
  365. // If the page is refreshed, older connections are likely to be
  366. // already closed by browser.
  367. return;
  368. }
  369. butil::IOBuf& resp = cntl->response_attachment();
  370. const bool use_html = UseHTML(cntl->http_request());
  371. const bool show_ccount = cntl->http_request().uri().GetQuery("ccount");
  372. const std::string* base_name = cntl->http_request().uri().GetQuery("base");
  373. const std::string* display_type_query = cntl->http_request().uri().GetQuery("display_type");
  374. DisplayType display_type = DisplayType::kDot;
  375. if (display_type_query) {
  376. display_type = StringToDisplayType(*display_type_query);
  377. if (display_type == DisplayType::kUnknown) {
  378. return cntl->SetFailed(EINVAL, "Invalid display_type=%s", display_type_query->c_str());
  379. }
  380. }
  381. if (base_name != NULL) {
  382. if (!ValidProfilePath(*base_name)) {
  383. return cntl->SetFailed(EINVAL, "Invalid query `base'");
  384. }
  385. if (!butil::PathExists(butil::FilePath(*base_name))) {
  386. return cntl->SetFailed(
  387. EINVAL, "The profile denoted by `base' does not exist");
  388. }
  389. }
  390. butil::IOBufBuilder os;
  391. os << result_prefix;
  392. char expected_result_name[256];
  393. MakeCacheName(expected_result_name, sizeof(expected_result_name),
  394. prof_name, GetBaseName(base_name),
  395. display_type, show_ccount);
  396. // Try to read cache first.
  397. FILE* fp = fopen(expected_result_name, "r");
  398. if (fp != NULL) {
  399. bool succ = false;
  400. char buffer[1024];
  401. while (1) {
  402. size_t nr = fread(buffer, 1, sizeof(buffer), fp);
  403. if (nr != 0) {
  404. prof_result.append(buffer, nr);
  405. }
  406. if (nr != sizeof(buffer)) {
  407. if (feof(fp)) {
  408. succ = true;
  409. break;
  410. } else if (ferror(fp)) {
  411. LOG(ERROR) << "Encountered error while reading for "
  412. << expected_result_name;
  413. break;
  414. }
  415. // retry;
  416. }
  417. }
  418. PLOG_IF(ERROR, fclose(fp) != 0) << "Fail to close fp";
  419. if (succ) {
  420. RPC_VLOG << "Hit cache=" << expected_result_name;
  421. os.move_to(resp);
  422. if (use_html) {
  423. resp.append("<pre>");
  424. }
  425. resp.append(prof_result);
  426. if (use_html) {
  427. resp.append("</pre></body></html>");
  428. }
  429. return;
  430. }
  431. }
  432. std::ostringstream cmd_builder;
  433. std::string pprof_tool{GeneratePerlScriptPath(PPROF_FILENAME)};
  434. std::string flamegraph_tool{GeneratePerlScriptPath(FLAMEGRAPH_FILENAME)};
  435. #if defined(OS_LINUX)
  436. cmd_builder << "perl " << pprof_tool
  437. << DisplayTypeToPProfArgument(display_type)
  438. << (show_ccount ? " --contention " : "");
  439. if (base_name) {
  440. cmd_builder << "--base " << *base_name << ' ';
  441. }
  442. cmd_builder << GetProgramName() << " " << prof_name;
  443. if (display_type == DisplayType::kFlameGraph) {
  444. // For flamegraph, we don't care about pprof error msg,
  445. // which will cause confusing messages in the final result.
  446. cmd_builder << " 2>/dev/null " << " | " << "perl " << flamegraph_tool;
  447. }
  448. cmd_builder << " 2>&1 ";
  449. #elif defined(OS_MACOSX)
  450. cmd_builder << getenv("GOOGLE_PPROF_BINARY_PATH") << " "
  451. << DisplayTypeToPProfArgument(display_type)
  452. << (show_ccount ? " -contentions " : "");
  453. if (base_name) {
  454. cmd_builder << "-base " << *base_name << ' ';
  455. }
  456. cmd_builder << prof_name << " 2>&1 ";
  457. #endif
  458. const std::string cmd = cmd_builder.str();
  459. for (int ntry = 0; ntry < 2; ++ntry) {
  460. if (!g_written_pprof_perl) {
  461. if (!WriteSmallFile(pprof_tool.c_str(), pprof_perl()) ||
  462. !WriteSmallFile(flamegraph_tool.c_str(), flamegraph_perl())) {
  463. os << "Fail to write " << pprof_tool
  464. << (use_html ? "</body></html>" : "\n");
  465. os.move_to(resp);
  466. cntl->http_response().set_status_code(
  467. HTTP_STATUS_INTERNAL_SERVER_ERROR);
  468. return;
  469. }
  470. g_written_pprof_perl = true;
  471. }
  472. errno = 0; // read_command_output may not set errno, clear it to make sure if
  473. // we see non-zero errno, it's real error.
  474. butil::IOBufBuilder pprof_output;
  475. const int rc = butil::read_command_output(pprof_output, cmd.c_str());
  476. if (rc != 0) {
  477. butil::FilePath pprof_path(pprof_tool);
  478. if (!butil::PathExists(pprof_path)) {
  479. // Write the script again.
  480. g_written_pprof_perl = false;
  481. // tell user.
  482. os << pprof_path.value() << " was removed, recreate ...\n\n";
  483. continue;
  484. }
  485. butil::FilePath flamegraph_path(flamegraph_tool);
  486. if (!butil::PathExists(flamegraph_path)) {
  487. // Write the script again.
  488. g_written_pprof_perl = false;
  489. // tell user.
  490. os << flamegraph_path.value() << " was removed, recreate ...\n\n";
  491. continue;
  492. }
  493. if (rc < 0) {
  494. os << "Fail to execute `" << cmd << "', " << berror()
  495. << (use_html ? "</body></html>" : "\n");
  496. os.move_to(resp);
  497. cntl->http_response().set_status_code(
  498. HTTP_STATUS_INTERNAL_SERVER_ERROR);
  499. return;
  500. }
  501. // cmd returns non zero, quit normally
  502. }
  503. pprof_output.move_to(prof_result);
  504. // Cache result in file.
  505. char result_name[256];
  506. MakeCacheName(result_name, sizeof(result_name), prof_name,
  507. GetBaseName(base_name), display_type, show_ccount);
  508. // Append the profile name as the visual reminder for what
  509. // current profile is.
  510. butil::IOBuf before_label;
  511. butil::IOBuf tmp;
  512. if (cntl->http_request().uri().GetQuery("view") == NULL) {
  513. tmp.append(prof_name);
  514. tmp.append("[addToProfEnd]");
  515. }
  516. if (prof_result.cut_until(&before_label, ",label=\"") == 0) {
  517. tmp.append(before_label);
  518. tmp.append(",label=\"[");
  519. tmp.append(GetBaseName(prof_name));
  520. if (base_name) {
  521. tmp.append(" - ");
  522. tmp.append(GetBaseName(base_name));
  523. }
  524. tmp.append("]\\l");
  525. tmp.append(prof_result);
  526. tmp.swap(prof_result);
  527. } else {
  528. // Assume it's text. append before result directly.
  529. tmp.append("[");
  530. tmp.append(GetBaseName(prof_name));
  531. if (base_name) {
  532. tmp.append(" - ");
  533. tmp.append(GetBaseName(base_name));
  534. }
  535. tmp.append("]\n");
  536. tmp.append(prof_result);
  537. tmp.swap(prof_result);
  538. }
  539. if (!WriteSmallFile(result_name, prof_result)) {
  540. LOG(ERROR) << "Fail to write " << result_name;
  541. CHECK(butil::DeleteFile(butil::FilePath(result_name), false));
  542. }
  543. break;
  544. }
  545. CHECK(!use_html);
  546. // NOTE: not send prof_result to os first which does copying.
  547. os.move_to(resp);
  548. if (use_html) {
  549. resp.append("<pre>");
  550. }
  551. resp.append(prof_result);
  552. if (use_html) {
  553. resp.append("</pre></body></html>");
  554. }
  555. }
  556. static void DoProfiling(ProfilingType type,
  557. ::google::protobuf::RpcController* cntl_base,
  558. ::google::protobuf::Closure* done) {
  559. ClosureGuard done_guard(done);
  560. Controller *cntl = static_cast<Controller*>(cntl_base);
  561. butil::IOBuf& resp = cntl->response_attachment();
  562. const bool use_html = UseHTML(cntl->http_request());
  563. cntl->http_response().set_content_type(use_html ? "text/html" : "text/plain");
  564. butil::IOBufBuilder os;
  565. if (use_html) {
  566. os << "<!DOCTYPE html><html><head>\n"
  567. "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
  568. "<script language=\"javascript\" type=\"text/javascript\" src=\"/js/jquery_min\"></script>\n"
  569. << TabsHead()
  570. << "<style type=\"text/css\">\n"
  571. ".logo {position: fixed; bottom: 0px; right: 0px; }\n"
  572. ".logo_text {color: #B0B0B0; }\n"
  573. "</style>\n"
  574. "</head>\n"
  575. "<body>\n";
  576. cntl->server()->PrintTabsBody(os, ProfilingType2String(type));
  577. }
  578. const std::string* view = cntl->http_request().uri().GetQuery("view");
  579. if (view) {
  580. if (!ValidProfilePath(*view)) {
  581. return cntl->SetFailed(EINVAL, "Invalid query `view'");
  582. }
  583. if (!butil::PathExists(butil::FilePath(*view))) {
  584. return cntl->SetFailed(
  585. EINVAL, "The profile denoted by `view' does not exist");
  586. }
  587. DisplayResult(cntl, done_guard.release(), view->c_str(), os.buf());
  588. return;
  589. }
  590. const int seconds = ReadSeconds(cntl);
  591. if ((type == PROFILING_CPU || type == PROFILING_CONTENTION)) {
  592. if (seconds < 0) {
  593. os << "Invalid seconds" << (use_html ? "</body></html>" : "\n");
  594. os.move_to(cntl->response_attachment());
  595. cntl->http_response().set_status_code(HTTP_STATUS_BAD_REQUEST);
  596. return;
  597. }
  598. }
  599. // Log requester
  600. std::ostringstream client_info;
  601. client_info << cntl->remote_side();
  602. if (cntl->auth_context()) {
  603. client_info << "(auth=" << cntl->auth_context()->user() << ')';
  604. } else {
  605. client_info << "(no auth)";
  606. }
  607. client_info << " requests for profiling " << ProfilingType2String(type);
  608. if (type == PROFILING_CPU || type == PROFILING_CONTENTION) {
  609. LOG(INFO) << client_info.str() << " for " << seconds << " seconds";
  610. } else {
  611. LOG(INFO) << client_info.str();
  612. }
  613. int64_t prof_id = 0;
  614. const std::string* prof_id_str =
  615. cntl->http_request().uri().GetQuery("profiling_id");
  616. if (prof_id_str != NULL) {
  617. char* endptr = NULL;
  618. prof_id = strtoll(prof_id_str->c_str(), &endptr, 10);
  619. LOG_IF(ERROR, *endptr != '\0') << "Invalid profiling_id=" << prof_id;
  620. }
  621. {
  622. BAIDU_SCOPED_LOCK(g_env[type].mutex);
  623. if (g_env[type].client) {
  624. if (NULL == g_env[type].waiters) {
  625. g_env[type].waiters = new std::vector<ProfilingWaiter>;
  626. }
  627. ProfilingWaiter waiter = { cntl, done_guard.release() };
  628. g_env[type].waiters->push_back(waiter);
  629. RPC_VLOG << "Queue request from " << cntl->remote_side();
  630. return;
  631. }
  632. if (g_env[type].cached_result != NULL &&
  633. g_env[type].cached_result->id == prof_id) {
  634. cntl->http_response().set_status_code(
  635. g_env[type].cached_result->status_code);
  636. cntl->response_attachment().append(
  637. g_env[type].cached_result->result);
  638. RPC_VLOG << "Hit cached result, id=" << prof_id;
  639. return;
  640. }
  641. CHECK(NULL == g_env[type].client);
  642. g_env[type].client = new ProfilingClient;
  643. g_env[type].client->end_us = butil::cpuwide_time_us() + seconds * 1000000L;
  644. g_env[type].client->seconds = seconds;
  645. // This id work arounds an issue of chrome (or jquery under chrome) that
  646. // the ajax call in another tab may be delayed until ajax call in
  647. // current tab finishes. We assign a increasing-only id to each
  648. // profiling and save last profiling result along with the assigned id.
  649. // If the delay happens, the viewr should send the ajax call with an
  650. // id matching the id in cached result, then the result will be returned
  651. // directly instead of running another profiling which may take long
  652. // time.
  653. if (0 == ++ g_env[type].cur_id) { // skip 0
  654. ++ g_env[type].cur_id;
  655. }
  656. g_env[type].client->id = g_env[type].cur_id;
  657. g_env[type].client->point = cntl->remote_side();
  658. }
  659. RPC_VLOG << "Apply request from " << cntl->remote_side();
  660. char prof_name[128];
  661. if (MakeProfName(type, prof_name, sizeof(prof_name)) != 0) {
  662. os << "Fail to create prof name: " << berror()
  663. << (use_html ? "</body></html>" : "\n");
  664. os.move_to(resp);
  665. cntl->http_response().set_status_code(HTTP_STATUS_INTERNAL_SERVER_ERROR);
  666. return NotifyWaiters(type, cntl, view);
  667. }
  668. #if defined(OS_MACOSX)
  669. if (!has_GOOGLE_PPROF_BINARY_PATH()) {
  670. os << "no GOOGLE_PPROF_BINARY_PATH in env"
  671. << (use_html ? "</body></html>" : "\n");
  672. os.move_to(resp);
  673. cntl->http_response().set_status_code(HTTP_STATUS_FORBIDDEN);
  674. return NotifyWaiters(type, cntl, view);
  675. }
  676. #endif
  677. if (type == PROFILING_CPU) {
  678. if ((void*)ProfilerStart == NULL || (void*)ProfilerStop == NULL) {
  679. os << "CPU profiler is not enabled"
  680. << (use_html ? "</body></html>" : "\n");
  681. os.move_to(resp);
  682. cntl->http_response().set_status_code(HTTP_STATUS_FORBIDDEN);
  683. return NotifyWaiters(type, cntl, view);
  684. }
  685. butil::File::Error error;
  686. const butil::FilePath dir = butil::FilePath(prof_name).DirName();
  687. if (!butil::CreateDirectoryAndGetError(dir, &error)) {
  688. os << "Fail to create directory=`" << dir.value() << ", "
  689. << error << (use_html ? "</body></html>" : "\n");
  690. os.move_to(resp);
  691. cntl->http_response().set_status_code(
  692. HTTP_STATUS_INTERNAL_SERVER_ERROR);
  693. return NotifyWaiters(type, cntl, view);
  694. }
  695. if (!ProfilerStart(prof_name)) {
  696. os << "Another profiler (not via /hotspots/cpu) is running, "
  697. "try again later" << (use_html ? "</body></html>" : "\n");
  698. os.move_to(resp);
  699. cntl->http_response().set_status_code(HTTP_STATUS_SERVICE_UNAVAILABLE);
  700. return NotifyWaiters(type, cntl, view);
  701. }
  702. if (bthread_usleep(seconds * 1000000L) != 0) {
  703. PLOG(WARNING) << "Profiling has been interrupted";
  704. }
  705. ProfilerStop();
  706. } else if (type == PROFILING_CONTENTION) {
  707. if (!bthread::ContentionProfilerStart(prof_name)) {
  708. os << "Another profiler (not via /hotspots/contention) is running, "
  709. "try again later" << (use_html ? "</body></html>" : "\n");
  710. os.move_to(resp);
  711. cntl->http_response().set_status_code(HTTP_STATUS_SERVICE_UNAVAILABLE);
  712. return NotifyWaiters(type, cntl, view);
  713. }
  714. if (bthread_usleep(seconds * 1000000L) != 0) {
  715. PLOG(WARNING) << "Profiling has been interrupted";
  716. }
  717. bthread::ContentionProfilerStop();
  718. } else if (type == PROFILING_HEAP) {
  719. MallocExtension* malloc_ext = MallocExtension::instance();
  720. if (malloc_ext == NULL || !has_TCMALLOC_SAMPLE_PARAMETER()) {
  721. os << "Heap profiler is not enabled";
  722. if (malloc_ext != NULL) {
  723. os << " (no TCMALLOC_SAMPLE_PARAMETER in env)";
  724. }
  725. os << '.' << (use_html ? "</body></html>" : "\n");
  726. os.move_to(resp);
  727. cntl->http_response().set_status_code(HTTP_STATUS_FORBIDDEN);
  728. return NotifyWaiters(type, cntl, view);
  729. }
  730. std::string obj;
  731. malloc_ext->GetHeapSample(&obj);
  732. if (!WriteSmallFile(prof_name, obj)) {
  733. os << "Fail to write " << prof_name
  734. << (use_html ? "</body></html>" : "\n");
  735. os.move_to(resp);
  736. cntl->http_response().set_status_code(
  737. HTTP_STATUS_INTERNAL_SERVER_ERROR);
  738. return NotifyWaiters(type, cntl, view);
  739. }
  740. } else if (type == PROFILING_GROWTH) {
  741. MallocExtension* malloc_ext = MallocExtension::instance();
  742. if (malloc_ext == NULL) {
  743. os << "Growth profiler is not enabled."
  744. << (use_html ? "</body></html>" : "\n");
  745. os.move_to(resp);
  746. cntl->http_response().set_status_code(HTTP_STATUS_FORBIDDEN);
  747. return NotifyWaiters(type, cntl, view);
  748. }
  749. std::string obj;
  750. malloc_ext->GetHeapGrowthStacks(&obj);
  751. if (!WriteSmallFile(prof_name, obj)) {
  752. os << "Fail to write " << prof_name
  753. << (use_html ? "</body></html>" : "\n");
  754. os.move_to(resp);
  755. cntl->http_response().set_status_code(
  756. HTTP_STATUS_INTERNAL_SERVER_ERROR);
  757. return NotifyWaiters(type, cntl, view);
  758. }
  759. } else {
  760. os << "Unknown ProfilingType=" << type
  761. << (use_html ? "</body></html>" : "\n");
  762. os.move_to(resp);
  763. cntl->http_response().set_status_code(
  764. HTTP_STATUS_INTERNAL_SERVER_ERROR);
  765. return NotifyWaiters(type, cntl, view);
  766. }
  767. std::vector<ProfilingWaiter> waiters;
  768. // NOTE: Must be called before DisplayResult which calls done->Run() and
  769. // deletes cntl.
  770. ConsumeWaiters(type, cntl, &waiters);
  771. DisplayResult(cntl, done_guard.release(), prof_name, os.buf());
  772. for (size_t i = 0; i < waiters.size(); ++i) {
  773. DisplayResult(waiters[i].cntl, waiters[i].done, prof_name, os.buf());
  774. }
  775. }
  776. static void StartProfiling(ProfilingType type,
  777. ::google::protobuf::RpcController* cntl_base,
  778. ::google::protobuf::Closure* done) {
  779. ClosureGuard done_guard(done);
  780. Controller *cntl = static_cast<Controller*>(cntl_base);
  781. butil::IOBuf& resp = cntl->response_attachment();
  782. const bool use_html = UseHTML(cntl->http_request());
  783. butil::IOBufBuilder os;
  784. bool enabled = false;
  785. const char* extra_desc = "";
  786. if (type == PROFILING_CPU) {
  787. enabled = cpu_profiler_enabled;
  788. } else if (type == PROFILING_CONTENTION) {
  789. enabled = true;
  790. } else if (type == PROFILING_HEAP) {
  791. enabled = IsHeapProfilerEnabled();
  792. if (enabled && !has_TCMALLOC_SAMPLE_PARAMETER()) {
  793. enabled = false;
  794. extra_desc = " (no TCMALLOC_SAMPLE_PARAMETER in env)";
  795. }
  796. } else if (type == PROFILING_GROWTH) {
  797. enabled = IsHeapProfilerEnabled();
  798. }
  799. const char* const type_str = ProfilingType2String(type);
  800. #if defined(OS_MACOSX)
  801. if (!has_GOOGLE_PPROF_BINARY_PATH()) {
  802. enabled = false;
  803. extra_desc = "(no GOOGLE_PPROF_BINARY_PATH in env)";
  804. }
  805. #endif
  806. if (!use_html) {
  807. if (!enabled) {
  808. os << "Error: " << type_str << " profiler is not enabled."
  809. << extra_desc << "\n"
  810. "Read the docs: docs/cn/{cpu_profiler.md,heap_profiler.md}\n";
  811. os.move_to(cntl->response_attachment());
  812. cntl->http_response().set_status_code(HTTP_STATUS_FORBIDDEN);
  813. return;
  814. }
  815. // Console can only use non-responsive version, namely the curl
  816. // blocks until profiling is done.
  817. return DoProfiling(type, cntl, done_guard.release());
  818. }
  819. const int seconds = ReadSeconds(cntl);
  820. const std::string* view = cntl->http_request().uri().GetQuery("view");
  821. const bool show_ccount = cntl->http_request().uri().GetQuery("ccount");
  822. const std::string* base_name = cntl->http_request().uri().GetQuery("base");
  823. const std::string* display_type_query = cntl->http_request().uri().GetQuery("display_type");
  824. DisplayType display_type = DisplayType::kDot;
  825. if (display_type_query) {
  826. display_type = StringToDisplayType(*display_type_query);
  827. if (display_type == DisplayType::kUnknown) {
  828. return cntl->SetFailed(EINVAL, "Invalid display_type=%s", display_type_query->c_str());
  829. }
  830. }
  831. ProfilingClient profiling_client;
  832. size_t nwaiters = 0;
  833. ProfilingEnvironment & env = g_env[type];
  834. if (view == NULL) {
  835. BAIDU_SCOPED_LOCK(env.mutex);
  836. if (env.client) {
  837. profiling_client = *env.client;
  838. nwaiters = (env.waiters ? env.waiters->size() : 0);
  839. }
  840. }
  841. cntl->http_response().set_content_type("text/html");
  842. os << "<!DOCTYPE html><html><head>\n"
  843. "<script language=\"javascript\" type=\"text/javascript\""
  844. " src=\"/js/jquery_min\"></script>\n"
  845. << TabsHead()
  846. << "<style type=\"text/css\">\n"
  847. ".logo {position: fixed; bottom: 0px; right: 0px; }\n"
  848. ".logo_text {color: #B0B0B0; }\n"
  849. " </style>\n"
  850. "<script type=\"text/javascript\">\n"
  851. "function generateURL() {\n"
  852. " var past_prof = document.getElementById('view_prof').value;\n"
  853. " var base_prof = document.getElementById('base_prof').value;\n"
  854. " var display_type = document.getElementById('display_type').value;\n";
  855. if (type == PROFILING_CONTENTION) {
  856. os << " var show_ccount = document.getElementById('ccount_cb').checked;\n";
  857. }
  858. os << " var targetURL = '/hotspots/" << type_str << "';\n"
  859. " targetURL += '?display_type=' + display_type;\n"
  860. " if (past_prof != '') {\n"
  861. " targetURL += '&view=' + past_prof;\n"
  862. " }\n"
  863. " if (base_prof != '') {\n"
  864. " targetURL += '&base=' + base_prof;\n"
  865. " }\n";
  866. if (type == PROFILING_CONTENTION) {
  867. os <<
  868. " if (show_ccount) {\n"
  869. " targetURL += '&ccount';\n"
  870. " }\n";
  871. }
  872. os << " return targetURL;\n"
  873. "}\n"
  874. "$(function() {\n"
  875. " function onDataReceived(data) {\n";
  876. if (view == NULL) {
  877. os <<
  878. " var selEnd = data.indexOf('[addToProfEnd]');\n"
  879. " if (selEnd != -1) {\n"
  880. " var sel = document.getElementById('view_prof');\n"
  881. " var option = document.createElement('option');\n"
  882. " option.value = data.substring(0, selEnd);\n"
  883. " option.text = option.value;\n"
  884. " var slash_index = option.value.lastIndexOf('/');\n"
  885. " if (slash_index != -1) {\n"
  886. " option.text = option.value.substring(slash_index + 1);\n"
  887. " }\n"
  888. " var option1 = sel.options[1];\n"
  889. " if (option1 == null || option1.text != option.text) {\n"
  890. " sel.add(option, 1);\n"
  891. " } else if (option1 != null) {\n"
  892. " console.log('merged ' + option.text);\n"
  893. " }\n"
  894. " sel.selectedIndex = 1;\n"
  895. " window.history.pushState('', '', generateURL());\n"
  896. " data = data.substring(selEnd + '[addToProfEnd]'.length);\n"
  897. " }\n";
  898. }
  899. os <<
  900. " var index = data.indexOf('digraph ');\n"
  901. " if (index == -1) {\n"
  902. " var selEnd = data.indexOf('[addToProfEnd]');\n"
  903. " if (selEnd != -1) {\n"
  904. " data = data.substring(selEnd + '[addToProfEnd]'.length);\n"
  905. " }\n"
  906. " $(\"#profiling-result\").html('<pre>' + data + '</pre>');\n"
  907. " if (data.indexOf('FlameGraph') != -1) { init(); }"
  908. " } else {\n"
  909. " $(\"#profiling-result\").html('Plotting ...');\n"
  910. " var svg = Viz(data.substring(index), \"svg\");\n"
  911. " $(\"#profiling-result\").html(svg);\n"
  912. " }\n"
  913. " }\n"
  914. " function onErrorReceived(xhr, ajaxOptions, thrownError) {\n"
  915. " $(\"#profiling-result\").html(xhr.responseText);\n"
  916. " }\n"
  917. " $.ajax({\n"
  918. " url: \"/hotspots/" << type_str << "_non_responsive?console=1";
  919. if (type == PROFILING_CPU || type == PROFILING_CONTENTION) {
  920. os << "&seconds=" << seconds;
  921. }
  922. if (profiling_client.id != 0) {
  923. os << "&profiling_id=" << profiling_client.id;
  924. }
  925. os << "&display_type=" << DisplayTypeToString(display_type);
  926. if (show_ccount) {
  927. os << "&ccount";
  928. }
  929. if (view) {
  930. os << "&view=" << *view;
  931. }
  932. if (base_name) {
  933. os << "&base=" << *base_name;
  934. }
  935. os << "\",\n"
  936. " type: \"GET\",\n"
  937. " dataType: \"html\",\n"
  938. " success: onDataReceived,\n"
  939. " error: onErrorReceived\n"
  940. " });\n"
  941. "});\n"
  942. "function onSelectProf() {\n"
  943. " window.location.href = generateURL();\n"
  944. "}\n"
  945. "function onChangedCB(cb) {\n"
  946. " onSelectProf();\n"
  947. "}\n"
  948. "</script>\n"
  949. "</head>\n"
  950. "<body>\n";
  951. cntl->server()->PrintTabsBody(os, type_str);
  952. TRACEPRINTF("Begin to enumerate profiles");
  953. std::vector<std::string> past_profs;
  954. butil::FilePath prof_dir(FLAGS_rpc_profiling_dir);
  955. prof_dir = prof_dir.Append(GetProgramChecksum());
  956. std::string file_pattern;
  957. file_pattern.reserve(15);
  958. file_pattern.append("*.");
  959. file_pattern.append(type_str);
  960. butil::FileEnumerator prof_enum(prof_dir, false/*non recursive*/,
  961. butil::FileEnumerator::FILES,
  962. file_pattern);
  963. std::string file_path;
  964. for (butil::FilePath name = prof_enum.Next(); !name.empty();
  965. name = prof_enum.Next()) {
  966. // NOTE: name already includes dir.
  967. if (past_profs.empty()) {
  968. past_profs.reserve(16);
  969. }
  970. past_profs.push_back(name.value());
  971. }
  972. if (!past_profs.empty()) {
  973. TRACEPRINTF("Sort %lu profiles in decending order", past_profs.size());
  974. std::sort(past_profs.begin(), past_profs.end(), std::greater<std::string>());
  975. int max_profiles = FLAGS_max_profiles_kept/*may be reloaded*/;
  976. if (max_profiles < 0) {
  977. max_profiles = 0;
  978. }
  979. if (past_profs.size() > (size_t)max_profiles) {
  980. TRACEPRINTF("Remove %lu profiles",
  981. past_profs.size() - (size_t)max_profiles);
  982. for (size_t i = max_profiles; i < past_profs.size(); ++i) {
  983. CHECK(butil::DeleteFile(butil::FilePath(past_profs[i]), false));
  984. std::string cache_path;
  985. cache_path.reserve(past_profs[i].size() + 7);
  986. cache_path += past_profs[i];
  987. cache_path += ".cache";
  988. CHECK(butil::DeleteFile(butil::FilePath(cache_path), true));
  989. }
  990. past_profs.resize(max_profiles);
  991. }
  992. }
  993. TRACEPRINTF("End enumeration");
  994. os << "<pre style='display:inline'>View: </pre>"
  995. "<select id='view_prof' onchange='onSelectProf()'>";
  996. os << "<option value=''>&lt;new profile&gt;</option>";
  997. for (size_t i = 0; i < past_profs.size(); ++i) {
  998. os << "<option value='" << past_profs[i] << "' ";
  999. if (view != NULL && past_profs[i] == *view) {
  1000. os << "selected";
  1001. }
  1002. os << '>' << GetBaseName(&past_profs[i]);
  1003. }
  1004. os << "</select>";
  1005. os << "<div><pre style='display:inline'>Display: </pre>"
  1006. "<select id='display_type' onchange='onSelectProf()'>"
  1007. "<option value=dot" << (display_type == DisplayType::kDot ? " selected" : "") << ">dot</option>"
  1008. #if defined(OS_LINUX)
  1009. "<option value=flame" << (display_type == DisplayType::kFlameGraph ? " selected" : "") << ">flame</option>"
  1010. #endif
  1011. "<option value=text" << (display_type == DisplayType::kText ? " selected" : "") << ">text</option></select>";
  1012. if (type == PROFILING_CONTENTION) {
  1013. os << "&nbsp;&nbsp;&nbsp;<label for='ccount_cb'>"
  1014. "<input id='ccount_cb' type='checkbox'"
  1015. << (show_ccount ? " checked=''" : "") <<
  1016. " onclick='onChangedCB(this);'>count</label>";
  1017. }
  1018. os << "</div><div><pre style='display:inline'>Diff: </pre>"
  1019. "<select id='base_prof' onchange='onSelectProf()'>"
  1020. "<option value=''>&lt;none&gt;</option>";
  1021. for (size_t i = 0; i < past_profs.size(); ++i) {
  1022. os << "<option value='" << past_profs[i] << "' ";
  1023. if (base_name != NULL && past_profs[i] == *base_name) {
  1024. os << "selected";
  1025. }
  1026. os << '>' << GetBaseName(&past_profs[i]);
  1027. }
  1028. os << "</select></div>";
  1029. if (!enabled && view == NULL) {
  1030. os << "<p><span style='color:red'>Error:</span> "
  1031. << type_str << " profiler is not enabled." << extra_desc << "</p>"
  1032. "<p>To enable all profilers, link tcmalloc and define macros BRPC_ENABLE_CPU_PROFILER"
  1033. "</p><p>Or read docs: <a href='https://github.com/brpc/brpc/blob/master/docs/cn/cpu_profiler.md'>cpu_profiler</a>"
  1034. " and <a href='https://github.com/brpc/brpc/blob/master/docs/cn/heap_profiler.md'>heap_profiler</a>"
  1035. "</p></body></html>";
  1036. os.move_to(cntl->response_attachment());
  1037. cntl->http_response().set_status_code(HTTP_STATUS_FORBIDDEN);
  1038. return;
  1039. }
  1040. if ((type == PROFILING_CPU || type == PROFILING_CONTENTION) && view == NULL) {
  1041. if (seconds < 0) {
  1042. os << "Invalid seconds</body></html>";
  1043. os.move_to(cntl->response_attachment());
  1044. cntl->http_response().set_status_code(HTTP_STATUS_BAD_REQUEST);
  1045. return;
  1046. }
  1047. }
  1048. if (nwaiters >= CONCURRENT_PROFILING_LIMIT) {
  1049. os << "Your profiling request is rejected because of "
  1050. "too many concurrent profiling requests</body></html>";
  1051. os.move_to(cntl->response_attachment());
  1052. cntl->http_response().set_status_code(HTTP_STATUS_SERVICE_UNAVAILABLE);
  1053. return;
  1054. }
  1055. os << "<div id=\"profiling-result\">";
  1056. if (profiling_client.seconds != 0) {
  1057. const int wait_seconds =
  1058. (int)ceil((profiling_client.end_us - butil::cpuwide_time_us())
  1059. / 1000000.0);
  1060. os << "Your request is merged with the request from "
  1061. << profiling_client.point;
  1062. if (type == PROFILING_CPU || type == PROFILING_CONTENTION) {
  1063. os << ", showing in about " << wait_seconds << " seconds ...";
  1064. }
  1065. } else {
  1066. if ((type == PROFILING_CPU || type == PROFILING_CONTENTION) && view == NULL) {
  1067. os << "Profiling " << ProfilingType2String(type) << " for "
  1068. << seconds << " seconds ...";
  1069. } else {
  1070. os << "Generating " << type_str << " profile ...";
  1071. }
  1072. }
  1073. os << "</div><pre class='logo'><span class='logo_text'>" << logo()
  1074. << "</span></pre></body>\n";
  1075. if (display_type == DisplayType::kDot) {
  1076. // don't need viz.js in text mode.
  1077. os << "<script language=\"javascript\" type=\"text/javascript\""
  1078. " src=\"/js/viz_min\"></script>\n";
  1079. }
  1080. os << "</html>";
  1081. os.move_to(resp);
  1082. }
  1083. void HotspotsService::cpu(
  1084. ::google::protobuf::RpcController* cntl_base,
  1085. const ::brpc::HotspotsRequest*,
  1086. ::brpc::HotspotsResponse*,
  1087. ::google::protobuf::Closure* done) {
  1088. return StartProfiling(PROFILING_CPU, cntl_base, done);
  1089. }
  1090. void HotspotsService::heap(
  1091. ::google::protobuf::RpcController* cntl_base,
  1092. const ::brpc::HotspotsRequest*,
  1093. ::brpc::HotspotsResponse*,
  1094. ::google::protobuf::Closure* done) {
  1095. return StartProfiling(PROFILING_HEAP, cntl_base, done);
  1096. }
  1097. void HotspotsService::growth(
  1098. ::google::protobuf::RpcController* cntl_base,
  1099. const ::brpc::HotspotsRequest*,
  1100. ::brpc::HotspotsResponse*,
  1101. ::google::protobuf::Closure* done) {
  1102. return StartProfiling(PROFILING_GROWTH, cntl_base, done);
  1103. }
  1104. void HotspotsService::contention(
  1105. ::google::protobuf::RpcController* cntl_base,
  1106. const ::brpc::HotspotsRequest*,
  1107. ::brpc::HotspotsResponse*,
  1108. ::google::protobuf::Closure* done) {
  1109. return StartProfiling(PROFILING_CONTENTION, cntl_base, done);
  1110. }
  1111. void HotspotsService::cpu_non_responsive(
  1112. ::google::protobuf::RpcController* cntl_base,
  1113. const ::brpc::HotspotsRequest*,
  1114. ::brpc::HotspotsResponse*,
  1115. ::google::protobuf::Closure* done) {
  1116. return DoProfiling(PROFILING_CPU, cntl_base, done);
  1117. }
  1118. void HotspotsService::heap_non_responsive(
  1119. ::google::protobuf::RpcController* cntl_base,
  1120. const ::brpc::HotspotsRequest*,
  1121. ::brpc::HotspotsResponse*,
  1122. ::google::protobuf::Closure* done) {
  1123. return DoProfiling(PROFILING_HEAP, cntl_base, done);
  1124. }
  1125. void HotspotsService::growth_non_responsive(
  1126. ::google::protobuf::RpcController* cntl_base,
  1127. const ::brpc::HotspotsRequest*,
  1128. ::brpc::HotspotsResponse*,
  1129. ::google::protobuf::Closure* done) {
  1130. return DoProfiling(PROFILING_GROWTH, cntl_base, done);
  1131. }
  1132. void HotspotsService::contention_non_responsive(
  1133. ::google::protobuf::RpcController* cntl_base,
  1134. const ::brpc::HotspotsRequest*,
  1135. ::brpc::HotspotsResponse*,
  1136. ::google::protobuf::Closure* done) {
  1137. return DoProfiling(PROFILING_CONTENTION, cntl_base, done);
  1138. }
  1139. void HotspotsService::GetTabInfo(TabInfoList* info_list) const {
  1140. TabInfo* info = info_list->add();
  1141. info->path = "/hotspots/cpu";
  1142. info->tab_name = "cpu";
  1143. info = info_list->add();
  1144. info->path = "/hotspots/heap";
  1145. info->tab_name = "heap";
  1146. info = info_list->add();
  1147. info->path = "/hotspots/growth";
  1148. info->tab_name = "growth";
  1149. info = info_list->add();
  1150. info->path = "/hotspots/contention";
  1151. info->tab_name = "contention";
  1152. }
  1153. } // namespace brpc