profilez_handler.cc
1 // Copyright 2018, Beeri 15. All rights reserved.
2 // Author: Roman Gershman (romange@gmail.com)
3 //
4 #include <unordered_map>
5 
6 #include <gperftools/heap-profiler.h>
7 #include <gperftools/malloc_extension.h>
8 #include <gperftools/profiler.h>
9 
10 #include "base/logging.h"
11 #include "base/walltime.h"
12 #include "strings/human_readable.h"
13 #include "strings/numbers.h"
14 #include "strings/split.h"
15 #include "strings/strcat.h"
16 #include "util/fibers/fibers_ext.h"
17 #include "util/http/http_common.h"
18 #include "util/spawn.h"
19 
20 namespace util {
21 namespace http {
22 namespace {
23 char last_profile_suffix[100] = {0};
24 }
25 
26 using namespace std;
27 using namespace boost;
28 using beast::http::field;
29 namespace h2 = beast::http;
30 typedef h2::response<h2::string_body> StringResponse;
31 
32 static void HandleCpuProfile(bool enable, StringResponse* response) {
33  string profile_name = "/tmp/" + base::ProgramBaseName();
34  response->set(h2::field::cache_control, "no-cache, no-store, must-revalidate");
35  response->set(h2::field::pragma, "no-cache");
36  response->set(field::content_type, kHtmlMime);
37 
38  auto& body = response->body();
39 
40  if (enable) {
41  if (last_profile_suffix[0]) {
42  body.append("<p> Yo, already profiling, stupid!</p>\n");
43  } else {
44  string suffix = base::LocalTimeNow("_%d%m%Y_%H%M%S.prof");
45  profile_name.append(suffix);
46  strcpy(last_profile_suffix, suffix.c_str());
47  int res = ProfilerStart(profile_name.c_str());
48  LOG(INFO) << "Starting profiling into " << profile_name << " " << res;
49  body.append(
50  "<h1> Wow, your code is so fast!</h1> \n"
51  "<img "
52  "src='https://gist.github.com/romange/4760c3eebc407755f856fec8e5b6d4c1/raw/"
53  "8da7e4129da2ebef26a0fad7b637364439f33e97/profiler2.gif'>\n");
54  }
55  return;
56  }
57  ProfilerStop();
58  if (last_profile_suffix[0] == '\0') {
59  body.append("<h3>Profiling is off, commander!</h3> \n")
60  .append("<img src='https://i.giphy.com/media/l0IykG0AM7911MrCM/giphy.webp'>\n");
61  return;
62  }
63  string cmd("nice -n 15 pprof -noinlines -lines -unit ms --svg ");
64  string symbols_name = base::ProgramAbsoluteFileName() + ".debug";
65  LOG(INFO) << "Symbols " << symbols_name << ", suffix: " << last_profile_suffix;
66  if (access(symbols_name.c_str(), R_OK) != 0) {
67  symbols_name = base::ProgramAbsoluteFileName();
68  }
69  cmd.append(symbols_name).append(" ");
70 
71  profile_name.append(last_profile_suffix);
72  cmd.append(profile_name).append(" > ");
73 
74  string err_log = profile_name + ".err";
75  profile_name.append(".svg");
76 
77  cmd.append(profile_name).append(" 2> ").append(err_log);
78 
79  LOG(INFO) << "Running command: " << cmd;
80  last_profile_suffix[0] = '\0';
81 
82  int sh_res = util::sh_exec(cmd.c_str());
83  if (sh_res != 0) {
84  LOG(ERROR) << "Error running sh_exec, status: " << errno << " " << strerror(errno);
85  }
86 
87  // Redirect browser to show this file.
88  string url("filez?file=");
89  url.append(profile_name);
90  LOG(INFO) << "Redirecting to " << url;
91  google::FlushLogFiles(google::INFO);
92 
93  response->set(h2::field::location, url);
94  response->result(h2::status::moved_permanently);
95 }
96 
97 static void HandleHeapProfile(bool enable, StringResponse* response) {
98  string profile_name = "/tmp/" + base::ProgramBaseName();
99  response->set(h2::field::cache_control, "no-cache, no-store, must-revalidate");
100  response->set(h2::field::pragma, "no-cache");
101  response->set(field::content_type, kHtmlMime);
102  auto& body = response->body();
103 
104  if (enable) {
105  if (IsHeapProfilerRunning()) {
106  body.append("<p> Man, heap profiling is already running, relax!</p>\n");
107  } else {
108  string suffix = base::LocalTimeNow("_heap_%d%m%Y_%H%M%S");
109  profile_name.append(suffix);
110  HeapProfilerStart(profile_name.c_str());
111  LOG(INFO) << "Starting heap profiling into " << profile_name;
112  body.append("<p> Let's find memory leaks, w00t!</p> \n");
113  }
114  } else {
115  HeapProfilerStop();
116  body.append(
117  "<h3>Heap profiling is off, master!</h3> \n"
118  "<img src='https://i.giphy.com/media/l0IykG0AM7911MrCM/giphy.webp'>\n");
119  }
120 }
121 
122 StringResponse ProfilezHandler(const QueryArgs& args) {
123  bool enable = false;
124  bool heap = false;
125  for (const auto& k_v : args) {
126  if (k_v.first == "profile") {
127  enable = (k_v.second == "on");
128  } else if (k_v.first == "heap") {
129  heap = true;
130  enable = (k_v.second == "on");
131  }
132  }
133 
134  fibers_ext::Done done;
135  StringResponse response;
136  std::thread([=, &response]() mutable {
137  if (!heap) {
138  HandleCpuProfile(enable, &response);
139  } else {
140  HandleHeapProfile(enable, &response);
141  }
142  done.Notify();
143  }).detach();
144 
145  // Instead of joining the thread which is not fiber-friendly,
146  // I use done to block the fiber but free the thread to handle other fibers.
147  // Still this fiber connection is blocked.
148  done.Wait();
149  return response;
150 }
151 
152 } // namespace http
153 } // namespace util