de-dup THREAD_LOCAL macros
[hiphop-php.git] / hphp / runtime / base / hhprof.cpp
blob2a6416266f58c15b0552e644b99604caf7b0be38
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/base/hhprof.h"
19 #include <folly/File.h>
20 #include <folly/FileUtil.h>
21 #include <folly/Singleton.h>
23 #include "hphp/runtime/base/execution-context.h"
24 #include "hphp/runtime/base/memory-manager.h"
25 #include "hphp/runtime/vm/jit/code-cache.h"
26 #include "hphp/runtime/vm/jit/tc.h"
27 #include "hphp/util/current-executable.h"
28 #include "hphp/util/logger.h"
29 #include "hphp/util/stack-trace.h"
31 #include <sstream>
32 #include <string>
34 namespace HPHP {
36 TRACE_SET_MOD(hhprof);
38 THREAD_LOCAL(HHProf::Request, HHProf::Request::s_request);
40 namespace { folly::Singleton<HHProf> s_hhprof; }
41 static std::shared_ptr<HHProf> hhprof() {
42 // Centralize try_get() calls to document that despite the scary name, it
43 // won't fail under any conditions we subject it to.
44 std::shared_ptr<HHProf> h = s_hhprof.try_get();
45 assert(h.get() != nullptr);
46 return h;
49 ///////////////////////////////////////////////////////////////////////////////
50 // HHProf::Request.
52 void HHProf::Request::setupImpl(Transport* transport) {
53 m_active = hhprof()->setupRequest(transport);
54 TRACE(1, "setup request (active:%s)\n", (m_active ? "true" : "false"));
57 void HHProf::Request::teardownImpl() {
58 TRACE(1, "teardown request (active:%s)\n", (m_active ? "true" : "false"));
59 if (!m_active) return;
60 hhprof()->teardownRequest();
63 void HHProf::Request::startProfilingImpl() {
64 TRACE(1, "start profiling (active:%s)\n", (m_active ? "true" : "false"));
65 if (!m_active) return;
66 hhprof()->startProfiledRequest();
69 void HHProf::Request::finishProfilingImpl() {
70 TRACE(1, "finish profiling (active:%s)\n", (m_active ? "true" : "false"));
71 if (!m_active) return;
72 hhprof()->finishProfiledRequest();
75 ///////////////////////////////////////////////////////////////////////////////
76 // HHProf.
78 bool HHProf::reset(Transport* transport) {
79 if (m_requestState == RequestState::Pending) {
80 transport->sendString("Profile pending; try again", 503);
81 return true;
84 size_t lg_sample;
85 std::string lgSample = transport->getParam("lgSample");
86 if (lgSample == "") {
87 mallctlRead("prof.lg_sample", &lg_sample);
88 } else {
89 errno = 0;
90 lg_sample = strtoul(lgSample.c_str(), nullptr, 0);
91 if (lg_sample == ULONG_MAX && errno != 0) {
92 transport->sendString(folly::format(
93 "Invalid lg_sample {} for /hhprof/start request", lgSample).str(),
94 400);
95 return true;
99 bool accum;
100 std::string profileType = transport->getParam("profileType");
101 if (profileType == "" || profileType == "current") {
102 accum = false;
103 } else if (profileType == "cumulative") {
104 accum = true;
105 } else {
106 transport->sendString(folly::format(
107 "Invalid profile type {} for /hhprof/start request", profileType).str(),
108 400);
109 return true;
112 // TODO(#8947159): Configure cumulative statistics during reset.
113 if (false) {
114 mallctlWrite("prof.accum_init", accum);
116 mallctlWrite("prof.reset", lg_sample);
118 return false;
121 void HHProf::start(Transport* transport) {
122 mallctlWrite("prof.active", true);
124 std::string requestType = transport->getParam("requestType");
125 if (requestType == "all") {
126 m_requestType = RequestType::All;
127 } else {
128 if (requestType != "" && requestType != "next") {
129 transport->sendString(folly::format(
130 "Invalid request type {} for /hhprof/start request",
131 requestType).str(), 400);
132 return;
134 std::string url = transport->getParam("url");
135 if (url == "") {
136 m_requestType = RequestType::Next;
137 } else {
138 m_requestType = RequestType::NextURL;
139 m_url = url;
142 m_requestState = RequestState::Pending;
144 transport->sendString(folly::format("{}\n", m_nextID).str());
147 void HHProf::handleHHProfStartImpl(Transport* transport) {
148 if (!RuntimeOption::HHProfEnabled) {
149 transport->sendString("Specify -vHHProf.Enabled=true", 503);
150 return;
153 std::unique_lock<std::mutex> lock(m_mutex);
155 if (reset(transport)) return;
156 start(transport);
159 void HHProf::handleHHProfStatusImpl(Transport* transport) {
160 std::unique_lock<std::mutex> lock(m_mutex);
161 std::string requestTypeStr;
162 switch (m_requestType) {
163 case RequestType::None: requestTypeStr = "None"; break;
164 case RequestType::Next: requestTypeStr = "Next"; break;
165 case RequestType::NextURL: requestTypeStr = folly::format("NextURL ({})",
166 m_url).str(); break;
167 case RequestType::All: requestTypeStr = "All"; break;
169 std::string requestStateStr;
170 switch (m_requestState) {
171 case RequestState::None: requestStateStr = "None"; break;
172 case RequestState::Pending: requestStateStr = "Pending"; break;
173 case RequestState::Sampling: requestStateStr = "Sampling"; break;
174 case RequestState::Dumped: requestStateStr = "Dumped"; break;
176 bool active;
177 mallctlRead("prof.active", &active);
179 std::ostringstream res;
180 res << "---" << std::endl;
181 res << "HHProf.Enabled: " << (RuntimeOption::HHProfEnabled ? "true" : "false")
182 << std::endl;
183 res << "HHProf.Active: " << (RuntimeOption::HHProfActive ? "true" : "false")
184 << std::endl;
185 res << "HHProf.Accum: " << (RuntimeOption::HHProfAccum ? "true" : "false")
186 << std::endl;
187 res << "HHProf.Request: " << (RuntimeOption::HHProfRequest ? "true" : "false")
188 << std::endl;
189 res << "RequestType: " << requestTypeStr << std::endl;
190 res << "RequestState: " << requestStateStr << std::endl;
191 res << "DumpId: " << m_dumpResult.id() << std::endl;
192 res << "prof.active: " << (active ? "true" : "false")
193 << std::endl;
194 res << "..." << std::endl;
196 transport->sendString(res.str());
199 void HHProf::handleHHProfStopImpl(Transport* transport) {
200 if (!RuntimeOption::HHProfEnabled) {
201 transport->sendString("Specify -vHHProf.Enabled=true", 503);
202 return;
205 std::unique_lock<std::mutex> lock(m_mutex);
207 mallctlWrite("prof.active", false);
209 m_dumpResult = captureDump();
210 m_requestState = RequestState::Dumped;
212 transport->sendString("OK\n");
215 HHProf::DumpResult HHProf::captureDump() {
216 unsigned id = m_nextID++;
217 // Create and open a tempfile, use the tempfile's name when dumping a heap
218 // profile, then open the resulting file. Take care to close both file
219 // descriptors and unlink the output file after reading its contents.
220 char tmpName[] = "/tmp/hhprof.XXXXXX";
221 int tmpFd = mkstemp(tmpName);
222 if (tmpFd == -1) {
223 std::ostringstream estr;
224 estr << "Error opening " << tmpName << std::endl;
225 return HHProf::DumpResult("", id, 503, estr.str());
227 folly::File tmpFile(tmpFd, true);
229 SCOPE_EXIT { unlink(tmpName); };
231 int err = jemalloc_pprof_dump(tmpName, true);
232 if (err != 0) {
233 std::ostringstream estr;
234 estr << "Error " << err << " dumping " << tmpName << std::endl;
235 return HHProf::DumpResult("", id, 503, estr.str());
237 std::string dump;
238 if (!folly::readFile(tmpName, dump)) {
239 std::ostringstream estr;
240 estr << "Error reading " << tmpName << std::endl;
241 return HHProf::DumpResult("", id, 503, estr.str());
243 return HHProf::DumpResult(dump, id);
246 void HHProf::handlePProfHeapImpl(Transport* transport) {
247 if (!RuntimeOption::HHProfEnabled) {
248 transport->sendString("Specify -vHHProf.Enabled=true", 503);
249 return;
252 std::unique_lock<std::mutex> lock(m_mutex);
254 if (RuntimeOption::HHProfRequest) {
255 switch (m_requestState) {
256 case RequestState::None:
257 captureDump().send(transport);
258 break;
259 case RequestState::Pending:
260 case RequestState::Sampling:
261 switch (m_requestType) {
262 case RequestType::None:
263 case RequestType::Next:
264 case RequestType::NextURL:
265 m_dumpResult.send(transport);
266 break;
267 case RequestType::All:
268 captureDump().send(transport);
269 break;
271 break;
272 case RequestState::Dumped:
273 m_dumpResult.send(transport);
274 break;
276 } else {
277 captureDump().send(transport);
281 bool HHProf::shouldProfileRequest(Transport* transport) {
282 bool canProfile =
283 #ifdef USE_JEMALLOC
284 (mallctl != nullptr)
285 #else
286 false
287 #endif
290 switch (m_requestState) {
291 case RequestState::None:
292 return false;
293 case RequestState::Pending:
294 switch (m_requestType) {
295 case RequestType::None:
296 return false;
297 case RequestType::Next:
298 break;
299 case RequestType::NextURL:
300 if (transport->getCommand() != m_url) return false;
301 break;
302 case RequestType::All:
303 break;
305 if (canProfile) {
306 m_requestState = RequestState::Sampling;
308 return canProfile;
309 case RequestState::Sampling:
310 switch (m_requestType) {
311 case RequestType::None:
312 case RequestType::Next:
313 case RequestType::NextURL:
314 return false;
315 case RequestType::All:
316 return canProfile;
318 not_reached();
319 case RequestState::Dumped:
320 switch (m_requestType) {
321 case RequestType::None:
322 case RequestType::Next:
323 case RequestType::NextURL:
324 return false;
325 case RequestType::All:
326 return canProfile;
328 not_reached();
330 not_reached();
333 bool HHProf::setupRequest(Transport* transport) {
334 std::unique_lock<std::mutex> lock(m_mutex);
336 bool active;
337 mallctlRead("prof.active", &active);
338 if (!active) return false;
340 if (!(HHProf::shouldProfileRequest(transport))) return false;
341 MemoryManager::setupProfiling();
342 return true;
345 void HHProf::teardownRequest() {
346 MemoryManager::teardownProfiling();
349 void HHProf::startProfiledRequest() {
350 mallctlWrite("thread.prof.active", true);
353 void HHProf::finishProfiledRequest() {
354 std::unique_lock<std::mutex> lock(m_mutex);
356 mallctlWrite("thread.prof.active", false);
357 if (RuntimeOption::HHProfRequest && m_requestType != RequestType::All) {
358 m_dumpResult = captureDump();
359 m_requestState = RequestState::Dumped;
363 ///////////////////////////////////////////////////////////////////////////////
364 // Exposed API, mainly static wrapper methods.
366 /* static */ void HHProf::Init() {
367 if (!RuntimeOption::HHProfEnabled) return;
369 // Ensure that profiling is enabled in jemalloc.
370 bool opt_prof;
371 mallctlRead("opt.prof", &opt_prof);
372 if (!opt_prof) {
373 const char* err = "HHProf incompatible with MALLOC_CONF=prof:false";
374 Logger::Error("%s", err);
375 std::cerr << err << std::endl;
376 throw std::runtime_error(err);
379 mallctlWrite("prof.active", RuntimeOption::HHProfActive);
381 bool prof_accum;
382 mallctlRead("opt.prof_accum", &prof_accum);
383 if (RuntimeOption::HHProfAccum != prof_accum) {
384 // TODO(#8947159): Dynamically configure cumulative statistics rather than
385 // forcing the decision at startup.
386 const char* err = "HHProf.Accum requires MALLOC_CONF=prof_accum:true";
387 Logger::Error("%s", err);
388 std::cerr << err << std::endl;
389 throw std::runtime_error(err);
392 // Configure thread.prof.active for the main thread.
393 mallctlWrite("thread.prof.active", !RuntimeOption::HHProfRequest);
394 // Configure prof.thread_active_init before launching additional threads.
395 mallctlWrite("prof.thread_active_init", !RuntimeOption::HHProfRequest);
398 /* static */ void HHProf::HandleHHProfStart(Transport* transport) {
399 hhprof()->handleHHProfStartImpl(transport);
402 /* static */ void HHProf::HandleHHProfStatus(Transport* transport) {
403 hhprof()->handleHHProfStatusImpl(transport);
406 /* static */ void HHProf::HandleHHProfStop(Transport* transport) {
407 hhprof()->handleHHProfStopImpl(transport);
410 /* static */ void HHProf::HandlePProfCmdline(Transport* transport) {
411 if (!RuntimeOption::HHProfEnabled) {
412 transport->sendString("Specify -vHHProf.Enabled=true", 503);
413 return;
416 transport->sendString(current_executable_path());
419 /* static */ void HHProf::HandlePProfHeap(Transport* transport) {
420 hhprof()->handlePProfHeapImpl(transport);
423 /* static */ void HHProf::HandlePProfSymbol(Transport* transport) {
424 if (!RuntimeOption::HHProfEnabled) {
425 transport->sendString("Specify -vHHProf.Enabled=true", 503);
426 return;
429 auto const is_valid = [] (void* p) {
430 return jit::tc::isValidCodeAddress(jit::TCA(p));
433 switch (transport->getMethod()) {
434 case Transport::Method::HEAD:
435 // Useful only for detecting the presence of the pprof/symbol endpoint.
436 transport->sendString("OK\n");
437 break;
438 case Transport::Method::GET:
439 // Return non-zero to indicate symbol lookup is supported.
440 transport->sendString("num_symbols: 1\n");
441 break;
442 case Transport::Method::POST: {
443 // Split the '+'-delimited addresses and resolve their associated symbols.
444 size_t size;
445 auto data = static_cast<const char *>(transport->getPostData(size));
446 std::string result;
447 std::vector<folly::StringPiece> addrs;
448 folly::split('+', folly::StringPiece(data, size), addrs);
449 bool phpOnly = (transport->getParam("retain") == "^PHP::");
450 // For each address append a line of the form:
451 // <addr>\t<symbol>
452 StackTrace::PerfMap pm;
453 for (const auto &addr : addrs) {
454 if (!addr.size()) {
455 continue;
457 std::string sval(addr.data(), addr.size());
458 auto const pval = (void*)std::stoull(sval, 0, 16);
459 if (phpOnly && !is_valid(pval)) {
460 continue;
462 auto frame = StackTrace::Translate(pval, &pm);
463 if (frame->funcname != "TC?") {
464 folly::toAppend(addr, "\t", frame->funcname, "\n", &result);
465 } else if (phpOnly || is_valid(pval)) {
466 // Prefix address such that it can be distinguished as residing within
467 // an unresolved PHP function.
468 folly::toAppend(addr, "\tPHP::", sval, "\n", &result);
469 } else {
470 folly::toAppend(addr, "\t", sval, "\n", &result);
473 transport->sendString(result);
474 break;
476 default:
477 transport->sendString("Unsupported method", 503);
478 break;