/Users/deen/code/yugabyte-db/src/yb/server/pprof-path-handlers.cc
Line | Count | Source (jump to first uncovered line) |
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 | | // |
18 | | // The following only applies to changes made to this file as part of YugaByte development. |
19 | | // |
20 | | // Portions Copyright (c) YugaByte, Inc. |
21 | | // |
22 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
23 | | // in compliance with the License. You may obtain a copy of the License at |
24 | | // |
25 | | // http://www.apache.org/licenses/LICENSE-2.0 |
26 | | // |
27 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
28 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
29 | | // or implied. See the License for the specific language governing permissions and limitations |
30 | | // under the License. |
31 | | // |
32 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
33 | | // you may not use this file except in compliance with the License. |
34 | | // You may obtain a copy of the License at |
35 | | // |
36 | | // http://www.apache.org/licenses/LICENSE-2.0 |
37 | | // |
38 | | // Unless required by applicable law or agreed to in writing, software |
39 | | // distributed under the License is distributed on an "AS IS" BASIS, |
40 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
41 | | // See the License for the specific language governing permissions and |
42 | | // limitations under the License. |
43 | | |
44 | | #include "yb/server/pprof-path-handlers.h" |
45 | | |
46 | | #ifdef TCMALLOC_ENABLED |
47 | | #include <gperftools/heap-profiler.h> |
48 | | #include <gperftools/malloc_extension.h> |
49 | | #include <gperftools/profiler.h> |
50 | | #endif |
51 | | |
52 | | #include <fstream> |
53 | | #include <string> |
54 | | #include <vector> |
55 | | |
56 | | #include <glog/logging.h> |
57 | | #include "yb/gutil/map-util.h" |
58 | | #include "yb/gutil/strings/numbers.h" |
59 | | #include "yb/gutil/strings/split.h" |
60 | | #include "yb/gutil/strings/stringpiece.h" |
61 | | #include "yb/gutil/strings/substitute.h" |
62 | | #include "yb/gutil/sysinfo.h" |
63 | | #include "yb/server/webserver.h" |
64 | | #include "yb/util/env.h" |
65 | | #include "yb/util/monotime.h" |
66 | | #include "yb/util/spinlock_profiling.h" |
67 | | #include "yb/util/status.h" |
68 | | #include "yb/util/status_log.h" |
69 | | |
70 | | DECLARE_bool(enable_process_lifetime_heap_profiling); |
71 | | DECLARE_string(heap_profile_path); |
72 | | |
73 | | |
74 | | using std::endl; |
75 | | using std::ifstream; |
76 | | using std::ostringstream; |
77 | | using std::string; |
78 | | using std::stringstream; |
79 | | |
80 | | // GLog already implements symbolization. Just import their hidden symbol. |
81 | | namespace google { |
82 | | // Symbolizes a program counter. On success, returns true and write the |
83 | | // symbol name to "out". The symbol name is demangled if possible |
84 | | // (supports symbols generated by GCC 3.x or newer). Otherwise, |
85 | | // returns false. |
86 | | bool Symbolize(void *pc, char *out, int out_size); |
87 | | } |
88 | | |
89 | | namespace yb { |
90 | | |
91 | | const int PPROF_DEFAULT_SAMPLE_SECS = 30; // pprof default sample time in seconds. |
92 | | |
93 | | // pprof asks for the url /pprof/cmdline to figure out what application it's profiling. |
94 | | // The server should respond by sending the executable path. |
95 | | static void PprofCmdLineHandler(const Webserver::WebRequest& req, |
96 | 1 | Webserver::WebResponse* resp) { |
97 | 1 | std::stringstream *output = &resp->output; |
98 | 1 | string executable_path; |
99 | 1 | Env* env = Env::Default(); |
100 | 1 | WARN_NOT_OK(env->GetExecutablePath(&executable_path), "Failed to get executable path"); |
101 | 1 | *output << executable_path; |
102 | 1 | } |
103 | | |
104 | | // pprof asks for the url /pprof/heap to get heap information. This should be implemented |
105 | | // by calling HeapProfileStart(filename), continue to do work, and then, some number of |
106 | | // seconds later, call GetHeapProfile() followed by HeapProfilerStop(). |
107 | | static void PprofHeapHandler(const Webserver::WebRequest& req, |
108 | 0 | Webserver::WebResponse* resp) { |
109 | 0 | std::stringstream *output = &resp->output; |
110 | 0 | #ifndef TCMALLOC_ENABLED |
111 | 0 | (*output) << "Heap profiling is not available without tcmalloc."; |
112 | | #else |
113 | | // Remote (on-demand) profiling is disabled if the process is already being profiled. |
114 | | if (FLAGS_enable_process_lifetime_heap_profiling) { |
115 | | (*output) << "Heap profiling is running for the process lifetime."; |
116 | | return; |
117 | | } |
118 | | |
119 | | auto it = req.parsed_args.find("seconds"); |
120 | | int seconds = PPROF_DEFAULT_SAMPLE_SECS; |
121 | | if (it != req.parsed_args.end()) { |
122 | | seconds = atoi(it->second.c_str()); |
123 | | } |
124 | | |
125 | | LOG(INFO) << "Starting a heap profile:" |
126 | | << " path prefix=" << FLAGS_heap_profile_path |
127 | | << " seconds=" << seconds; |
128 | | |
129 | | HeapProfilerStart(FLAGS_heap_profile_path.c_str()); |
130 | | // Sleep to allow for some samples to be collected. |
131 | | SleepFor(MonoDelta::FromSeconds(seconds)); |
132 | | const char* profile = GetHeapProfile(); |
133 | | HeapProfilerStop(); |
134 | | (*output) << profile; |
135 | | delete profile; |
136 | | #endif |
137 | 0 | } |
138 | | |
139 | | // pprof asks for the url /pprof/profile?seconds=XX to get cpu-profiling information. |
140 | | // The server should respond by calling ProfilerStart(), continuing to do its work, |
141 | | // and then, XX seconds later, calling ProfilerStop(). |
142 | | static void PprofCpuProfileHandler(const Webserver::WebRequest& req, |
143 | 0 | Webserver::WebResponse* resp) { |
144 | 0 | std::stringstream *output = &resp->output; |
145 | 0 | #ifndef TCMALLOC_ENABLED |
146 | 0 | (*output) << "CPU profiling is not available without tcmalloc."; |
147 | | #else |
148 | | auto it = req.parsed_args.find("seconds"); |
149 | | int seconds = PPROF_DEFAULT_SAMPLE_SECS; |
150 | | if (it != req.parsed_args.end()) { |
151 | | seconds = atoi(it->second.c_str()); |
152 | | } |
153 | | // Build a temporary file name that is hopefully unique. |
154 | | string tmp_prof_file_name = strings::Substitute("/tmp/yb_cpu_profile.$0.$1", getpid(), rand()); |
155 | | |
156 | | LOG(INFO) << "Starting a cpu profile:" |
157 | | << " profiler file name=" << tmp_prof_file_name |
158 | | << " seconds=" << seconds; |
159 | | |
160 | | ProfilerStart(tmp_prof_file_name.c_str()); |
161 | | SleepFor(MonoDelta::FromSeconds(seconds)); |
162 | | ProfilerStop(); |
163 | | ifstream prof_file(tmp_prof_file_name.c_str(), std::ios::in); |
164 | | if (!prof_file.is_open()) { |
165 | | (*output) << "Unable to open cpu profile: " << tmp_prof_file_name; |
166 | | return; |
167 | | } |
168 | | (*output) << prof_file.rdbuf(); |
169 | | prof_file.close(); |
170 | | #endif |
171 | 0 | } |
172 | | |
173 | | // pprof asks for the url /pprof/growth to get heap-profiling delta (growth) information. |
174 | | // The server should respond by calling: |
175 | | // MallocExtension::instance()->GetHeapGrowthStacks(&output); |
176 | 0 | static void PprofGrowthHandler(const Webserver::WebRequest& req, Webserver::WebResponse* resp) { |
177 | 0 | std::stringstream *output = &resp->output; |
178 | 0 | #ifndef TCMALLOC_ENABLED |
179 | 0 | (*output) << "Growth profiling is not available without tcmalloc."; |
180 | | #else |
181 | | string heap_growth_stack; |
182 | | MallocExtension::instance()->GetHeapGrowthStacks(&heap_growth_stack); |
183 | | (*output) << heap_growth_stack; |
184 | | #endif |
185 | 0 | } |
186 | | |
187 | | // Lock contention profiling |
188 | | static void PprofContentionHandler(const Webserver::WebRequest& req, |
189 | 1 | Webserver::WebResponse* resp) { |
190 | 1 | std::stringstream *output = &resp->output; |
191 | 1 | string secs_str = FindWithDefault(req.parsed_args, "seconds", ""); |
192 | 1 | int32_t seconds = ParseLeadingInt32Value(secs_str.c_str(), PPROF_DEFAULT_SAMPLE_SECS); |
193 | 1 | int64_t discarded_samples = 0; |
194 | | |
195 | 1 | *output << "--- contention" << endl; |
196 | 1 | *output << "sampling period = 1" << endl; |
197 | 1 | *output << "cycles/second = " << base::CyclesPerSecond() << endl; |
198 | | |
199 | 1 | StartSynchronizationProfiling(); |
200 | 1 | SleepFor(MonoDelta::FromSeconds(seconds)); |
201 | 1 | StopSynchronizationProfiling(); |
202 | 1 | FlushSynchronizationProfile(output, &discarded_samples); |
203 | | |
204 | | // pprof itself ignores this value, but we can at least look at it in the textual |
205 | | // output. |
206 | 1 | *output << "Discarded samples = " << discarded_samples << std::endl; |
207 | | |
208 | | #if defined(__linux__) |
209 | | // procfs only exists on Linux. |
210 | | faststring maps; |
211 | | WARN_NOT_OK(ReadFileToString(Env::Default(), "/proc/self/maps", &maps), |
212 | | "Failed to read /proc/self/maps"); |
213 | | *output << maps.ToString(); |
214 | | #endif // defined(__linux__) |
215 | 1 | } |
216 | | |
217 | | |
218 | | // pprof asks for the url /pprof/symbol to map from hex addresses to variable names. |
219 | | // When the server receives a GET request for /pprof/symbol, it should return a line |
220 | | // formatted like: num_symbols: ### |
221 | | // where ### is the number of symbols found in the binary. For now, the only important |
222 | | // distinction is whether the value is 0, which it is for executables that lack debug |
223 | | // information, or not-0). |
224 | | // |
225 | | // In addition to the GET request for this url, the server must accept POST requests. |
226 | | // This means that after the HTTP headers, pprof will pass in a list of hex addresses |
227 | | // connected by +, like: |
228 | | // curl -d '0x0824d061+0x0824d1cf' http://remote_host:80/pprof/symbol |
229 | | // The server should read the POST data, which will be in one line, and for each hex value |
230 | | // should write one line of output to the output stream, like so: |
231 | | // <hex address><tab><function name> |
232 | | // For instance: |
233 | | // 0x08b2dabd _Update |
234 | | static void PprofSymbolHandler(const Webserver::WebRequest& req, |
235 | 2 | Webserver::WebResponse* resp) { |
236 | 2 | std::stringstream *output = &resp->output; |
237 | 2 | if (req.request_method == "GET") { |
238 | | // Per the above comment, pprof doesn't expect to know the actual number of symbols. |
239 | | // Any non-zero value indicates that we support symbol lookup. |
240 | 1 | (*output) << "num_symbols: 1"; |
241 | 1 | return; |
242 | 1 | } |
243 | | |
244 | 1 | int missing_symbols = 0; |
245 | 1 | int invalid_addrs = 0; |
246 | | |
247 | | // Symbolization request. |
248 | 1 | vector<GStringPiece> pieces = strings::Split(req.post_data, "+"); |
249 | 2 | for (GStringPiece p : pieces) { |
250 | 2 | string hex_addr; |
251 | 2 | if (!TryStripPrefixString(p, "0x", &hex_addr)) { |
252 | 0 | invalid_addrs++; |
253 | 0 | continue; |
254 | 0 | } |
255 | 2 | uint64_t addr; |
256 | 2 | if (!safe_strtou64_base(hex_addr.c_str(), &addr, 16)) { |
257 | 0 | invalid_addrs++; |
258 | 0 | continue; |
259 | 0 | } |
260 | 2 | char symbol_buf[1024]; |
261 | 2 | if (google::Symbolize(reinterpret_cast<void*>(addr), symbol_buf, sizeof(symbol_buf))) { |
262 | 2 | *output << p << "\t" << symbol_buf << std::endl; |
263 | 0 | } else { |
264 | 0 | missing_symbols++; |
265 | 0 | } |
266 | 2 | } |
267 | | |
268 | 1 | LOG(INFO) << strings::Substitute( |
269 | 1 | "Handled request for /pprof/symbol: requested=$0 invalid_addrs=$1 missing=$2", |
270 | 1 | pieces.size(), invalid_addrs, missing_symbols); |
271 | 1 | } |
272 | | |
273 | 17.1k | void AddPprofPathHandlers(Webserver* webserver) { |
274 | | // Path handlers for remote pprof profiling. For information see: |
275 | | // https://gperftools.googlecode.com/svn/trunk/doc/pprof_remote_servers.html |
276 | 17.1k | webserver->RegisterPathHandler("/pprof/cmdline", "", PprofCmdLineHandler, false, false); |
277 | 17.1k | webserver->RegisterPathHandler("/pprof/heap", "", PprofHeapHandler, false, false); |
278 | 17.1k | webserver->RegisterPathHandler("/pprof/growth", "", PprofGrowthHandler, false, false); |
279 | 17.1k | webserver->RegisterPathHandler("/pprof/profile", "", PprofCpuProfileHandler, false, false); |
280 | 17.1k | webserver->RegisterPathHandler("/pprof/symbol", "", PprofSymbolHandler, false, false); |
281 | 17.1k | webserver->RegisterPathHandler("/pprof/contention", "", PprofContentionHandler, false, false); |
282 | 17.1k | } |
283 | | |
284 | | } // namespace yb |