2 +----------------------------------------------------------------------+
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"
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);
49 ///////////////////////////////////////////////////////////////////////////////
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 ///////////////////////////////////////////////////////////////////////////////
78 bool HHProf::reset(Transport
* transport
) {
79 if (m_requestState
== RequestState::Pending
) {
80 transport
->sendString("Profile pending; try again", 503);
85 std::string lgSample
= transport
->getParam("lgSample");
87 mallctlRead("prof.lg_sample", &lg_sample
);
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(),
100 std::string profileType
= transport
->getParam("profileType");
101 if (profileType
== "" || profileType
== "current") {
103 } else if (profileType
== "cumulative") {
106 transport
->sendString(folly::format(
107 "Invalid profile type {} for /hhprof/start request", profileType
).str(),
112 // TODO(#8947159): Configure cumulative statistics during reset.
114 mallctlWrite("prof.accum_init", accum
);
116 mallctlWrite("prof.reset", lg_sample
);
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
;
128 if (requestType
!= "" && requestType
!= "next") {
129 transport
->sendString(folly::format(
130 "Invalid request type {} for /hhprof/start request",
131 requestType
).str(), 400);
134 std::string url
= transport
->getParam("url");
136 m_requestType
= RequestType::Next
;
138 m_requestType
= RequestType::NextURL
;
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);
153 std::unique_lock
<std::mutex
> lock(m_mutex
);
155 if (reset(transport
)) return;
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 ({})",
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;
177 mallctlRead("prof.active", &active
);
179 std::ostringstream res
;
180 res
<< "---" << std::endl
;
181 res
<< "HHProf.Enabled: " << (RuntimeOption::HHProfEnabled
? "true" : "false")
183 res
<< "HHProf.Active: " << (RuntimeOption::HHProfActive
? "true" : "false")
185 res
<< "HHProf.Accum: " << (RuntimeOption::HHProfAccum
? "true" : "false")
187 res
<< "HHProf.Request: " << (RuntimeOption::HHProfRequest
? "true" : "false")
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")
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);
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
);
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);
233 std::ostringstream estr
;
234 estr
<< "Error " << err
<< " dumping " << tmpName
<< std::endl
;
235 return HHProf::DumpResult("", id
, 503, estr
.str());
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);
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
);
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
);
267 case RequestType::All
:
268 captureDump().send(transport
);
272 case RequestState::Dumped
:
273 m_dumpResult
.send(transport
);
277 captureDump().send(transport
);
281 bool HHProf::shouldProfileRequest(Transport
* transport
) {
290 switch (m_requestState
) {
291 case RequestState::None
:
293 case RequestState::Pending
:
294 switch (m_requestType
) {
295 case RequestType::None
:
297 case RequestType::Next
:
299 case RequestType::NextURL
:
300 if (transport
->getCommand() != m_url
) return false;
302 case RequestType::All
:
306 m_requestState
= RequestState::Sampling
;
309 case RequestState::Sampling
:
310 switch (m_requestType
) {
311 case RequestType::None
:
312 case RequestType::Next
:
313 case RequestType::NextURL
:
315 case RequestType::All
:
319 case RequestState::Dumped
:
320 switch (m_requestType
) {
321 case RequestType::None
:
322 case RequestType::Next
:
323 case RequestType::NextURL
:
325 case RequestType::All
:
333 bool HHProf::setupRequest(Transport
* transport
) {
334 std::unique_lock
<std::mutex
> lock(m_mutex
);
337 mallctlRead("prof.active", &active
);
338 if (!active
) return false;
340 if (!(HHProf::shouldProfileRequest(transport
))) return false;
341 MemoryManager::setupProfiling();
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.
371 mallctlRead("opt.prof", &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
);
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);
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);
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");
438 case Transport::Method::GET
:
439 // Return non-zero to indicate symbol lookup is supported.
440 transport
->sendString("num_symbols: 1\n");
442 case Transport::Method::POST
: {
443 // Split the '+'-delimited addresses and resolve their associated symbols.
445 auto data
= static_cast<const char *>(transport
->getPostData(size
));
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:
452 StackTrace::PerfMap pm
;
453 for (const auto &addr
: addrs
) {
457 std::string
sval(addr
.data(), addr
.size());
458 auto const pval
= (void*)std::stoull(sval
, 0, 16);
459 if (phpOnly
&& !is_valid(pval
)) {
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
);
470 folly::toAppend(addr
, "\t", sval
, "\n", &result
);
473 transport
->sendString(result
);
477 transport
->sendString("Unsupported method", 503);