2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2014 Facebook, Inc. (http://www.facebook.com) |
6 | Copyright (c) 1997-2010 The PHP Group |
7 +----------------------------------------------------------------------+
8 | This source file is subject to version 3.01 of the PHP license, |
9 | that is bundled with this package in the file LICENSE, and is |
10 | available through the world-wide-web at the following url: |
11 | http://www.php.net/license/3_01.txt |
12 | If you did not receive a copy of the PHP license and are unable to |
13 | obtain it through the world-wide-web, please send a note to |
14 | license@php.net so we can mail you a copy immediately. |
15 +----------------------------------------------------------------------+
18 #include "hphp/runtime/ext/ext_xenon.h"
19 #include "hphp/runtime/ext/ext_function.h"
20 #include "hphp/runtime/ext/asio/waitable_wait_handle.h"
21 #include "hphp/runtime/ext/ext_asio.h"
22 #include "hphp/runtime/base/request-injection-data.h"
23 #include "hphp/runtime/base/thread-info.h"
24 #include "hphp/runtime/base/runtime-option.h"
35 void *s_waitThread(void *arg
) {
36 TRACE(1, "s_waitThread Starting\n");
37 sem_t
* sem
= static_cast<sem_t
*>(arg
);
38 while (sem_wait(sem
) == 0) {
39 TRACE(1, "s_waitThread Fired\n");
40 if (Xenon::getInstance().m_stopping
) {
41 TRACE(1, "s_waitThread is exiting\n");
44 Xenon::getInstance().surpriseAll();
46 TRACE(1, "s_waitThread Ending\n");
50 ///////////////////////////////////////////////////////////////////////////////
52 // Data that is kept per request and is only valid per request.
53 // This structure gathers a php and async stack trace when log is called.
54 // These logged stacks can be then gathered via php a call, xenon_get_data.
55 // It needs to allocate and free its Array per request, because Array lifetime
56 // is per-request. So the flow for these objects are:
57 // allocated when a web request begins (if Xenon is enabled)
58 // grab snapshots of the php and async stack when log is called
59 // detach itself from its snapshots when the request is ending.
60 struct XenonRequestLocalData
: public RequestEventHandler
{
61 XenonRequestLocalData();
62 virtual ~XenonRequestLocalData();
63 void log(bool skipFirst
);
64 Array
logAsyncStack();
65 Array
createResponse();
67 // virtual from RequestEventHandler
68 virtual void requestInit();
69 virtual void requestShutdown();
71 // an array of (php, async) stacks
72 Array m_stackSnapshots
;
73 // number of times we invoked, but the async stack was invalid
74 int m_asyncInvalidCount
;
76 IMPLEMENT_STATIC_REQUEST_LOCAL(XenonRequestLocalData
, s_xenonData
);
78 ///////////////////////////////////////////////////////////////////////////////
79 // statics used by the Xenon classes
83 s_function("function"),
88 s_asyncInvalidCount("asyncInvalidCount"),
89 s_phpStack("phpStack"),
90 s_asyncStack("asyncStack");
92 static Array
parsePhpStack(const Array
& bt
) {
94 for (ArrayIter
it(bt
); it
; ++it
) {
95 const Array
& frame
= it
.second().toArray();
96 if (frame
.exists(s_function
)) {
97 if (frame
.exists(s_class
)) {
98 std::ostringstream ss
;
99 ss
<< frame
[s_class
].toString().c_str()
100 << frame
[s_type
].toString().c_str()
101 << frame
[s_function
].toString().c_str();
103 element
.set(s_function
, ss
.str(), true);
104 element
.set(s_file
, frame
[s_file
], true);
105 element
.set(s_line
, frame
[s_line
], true);
106 stack
.append(element
);
109 element
.set(s_function
, frame
[s_function
].toString().c_str(), true);
110 if (frame
.exists(s_file
) && frame
.exists(s_line
)) {
111 element
.set(s_file
, frame
[s_file
], true);
112 element
.set(s_line
, frame
[s_line
], true);
114 stack
.append(element
);
121 static c_WaitableWaitHandle
*objToWaitableWaitHandle(Object o
) {
122 assert(o
->instanceof(c_WaitableWaitHandle::classof()));
123 return static_cast<c_WaitableWaitHandle
*>(o
.get());
126 ///////////////////////////////////////////////////////////////////////////
127 // A singleton object that handles the two Xenon modes (always or timer).
128 // If in always on mode, the Xenon Surprise flags have to be on for each thread
129 // and are never cleared.
130 // For timer mode, when start is invoked, it adds a new timer to the existing
131 // handler for SIGVTALRM.
133 Xenon
& Xenon::getInstance() {
134 static Xenon instance
;
138 Xenon::Xenon() : m_stopping(false), m_timerid(0), m_sec(10*60), m_nsec(0) {
144 // XenonForceAlwaysOn is active - it doesn't need a timer, it is always on.
145 // Xenon needs to be started once per process.
146 // The number of seconds has to be greater than zero.
147 // We need to create a semaphore and a thread.
148 // If all of those happen, then we need a timer attached to a signal handler.
149 void Xenon::start(double seconds
) {
150 TRACE(1, "XenonForceAlwaysOn %d\n", RuntimeOption::XenonForceAlwaysOn
);
151 if (!RuntimeOption::XenonForceAlwaysOn
154 && sem_init(&m_timerTriggered
, 0, 0) == 0
155 && pthread_create(&m_triggerThread
, nullptr, s_waitThread
,
156 static_cast<void*>(&m_timerTriggered
)) == 0) {
158 m_sec
= (int)seconds
;
159 m_nsec
= (int)((seconds
- m_sec
) * 1000000000);
160 TRACE(1, "Xenon::start %ld seconds, %ld nanoseconds\n", m_sec
, m_nsec
);
163 sev
.sigev_notify
= SIGEV_SIGNAL
;
164 sev
.sigev_signo
= SIGVTALRM
;
165 sev
.sigev_value
.sival_ptr
= nullptr; // null for Xenon signals
166 timer_create(CLOCK_REALTIME
, &sev
, &m_timerid
);
169 ts
.it_value
.tv_sec
= m_sec
;
170 ts
.it_value
.tv_nsec
= m_nsec
;
171 ts
.it_interval
.tv_sec
= m_sec
;
172 ts
.it_interval
.tv_nsec
= m_nsec
;
173 timer_settime(m_timerid
, 0, &ts
, nullptr);
177 // If Xenon owns a pthread, tell it to stop, also clean up anything from start.
181 sem_post(&m_timerTriggered
);
182 pthread_join(m_triggerThread
, nullptr);
183 TRACE(1, "Xenon::stop has stopped the waiting thread\n");
184 timer_delete(m_timerid
);
185 sem_destroy(&m_timerTriggered
);
189 // Xenon data is gathered for logging per request, "if we should"
190 // meaning that if Xenon's Surprise flag has been turned on by someone, we
191 // should log the stacks. If we are in XenonForceAlwaysOn, do not clear
192 // the Surprise flag. The data is gathered in thread local storage.
193 void Xenon::log(bool skipFirst
) {
194 RequestInjectionData
*rid
= &ThreadInfo::s_threadInfo
->m_reqInjectionData
;
195 if (rid
->checkXenonSignalFlag()) {
196 if (!RuntimeOption::XenonForceAlwaysOn
) {
197 rid
->clearXenonSignalFlag();
199 TRACE(1, "Xenon::log\n");
200 s_xenonData
->log(skipFirst
);
204 // Called from timer handler, Lets non-signal code know the timer was fired.
205 void Xenon::onTimer() {
206 sem_post(&m_timerTriggered
);
209 // Turns on the Xenon Surprise flag for every thread via a lambda function
210 // passed to ExecutePerThread.
211 void Xenon::surpriseAll() {
212 TRACE(1, "Xenon::surpriseAll\n");
213 ThreadInfo::ExecutePerThread(
214 [](ThreadInfo
*t
) {t
->m_reqInjectionData
.setXenonSignalFlag();} );
217 ///////////////////////////////////////////////////////////////////////////////
218 // There is one XenonRequestLocalData per thread, stored in thread local area
220 XenonRequestLocalData::XenonRequestLocalData() {
221 TRACE(1, "XenonRequestLocalData\n");
224 XenonRequestLocalData::~XenonRequestLocalData() {
225 TRACE(1, "~XenonRequestLocalData\n");
228 Array
XenonRequestLocalData::logAsyncStack() {
230 auto session
= AsioSession::Get();
231 // we need this check here to see if the asio is in a valid state for queries
232 // if it is not, then return
233 // calling getCurrentWaitHandle directly while asio is not in a valid state
234 // will assert, so we need to check this ourselves before invoking it
235 if (session
->isInContext() && !session
->getCurrentContext()->isRunning()) {
236 ++m_asyncInvalidCount
;
240 auto currentWaitHandle
= session
->getCurrentWaitHandle();
241 if (currentWaitHandle
== nullptr) {
242 // if we have a nullptr, then we have no async stack to store for this log
245 Array depStack
= currentWaitHandle
->t_getdependencystack();
247 for (ArrayIter
iter(depStack
); iter
; ++iter
) {
249 if (iter
.secondRef().isNull()) {
250 frameData
.set(s_function
, "<prep>", true);
252 auto wh
= objToWaitableWaitHandle(iter
.secondRef().toObject());
253 frameData
.set(s_function
, wh
->t_getname(), true);
254 // Continuation wait handles may have a source location to add.
255 auto contWh
= dynamic_cast<c_AsyncFunctionWaitHandle
*>(wh
);
256 if (contWh
!= nullptr && !contWh
->isRunning()) {
257 frameData
.set(s_file
, contWh
->getFileName(), true);
258 frameData
.set(s_line
, contWh
->getLineNumber(), true);
261 bt
.append(frameData
);
266 // Creates an array to respond to the Xenon PHP extension;
267 // builds the data into the format neeeded.
268 Array
XenonRequestLocalData::createResponse() {
270 for (ArrayIter
it(m_stackSnapshots
); it
; ++it
) {
271 Array frame
= it
.second().toArray();
273 element
.set(s_time
, frame
[s_time
], true);
274 element
.set(s_phpStack
, parsePhpStack(frame
[s_phpStack
].toArray()), true);
275 element
.set(s_asyncStack
, frame
[s_asyncStack
], true);
276 stacks
.append(element
);
278 stacks
.set(s_asyncInvalidCount
, m_asyncInvalidCount
, true);
282 void XenonRequestLocalData::log(bool skipFirst
) {
283 TRACE(1, "XenonRequestLocalData::log\n");
284 time_t now
= time(nullptr);
286 snapshot
.set(s_time
, now
, true);
287 snapshot
.set(s_phpStack
, g_context
->debugBacktrace(skipFirst
, true, false,
288 nullptr, true), true);
289 snapshot
.set(s_asyncStack
, logAsyncStack(), true);
290 m_stackSnapshots
.append(snapshot
);
293 void XenonRequestLocalData::requestInit() {
294 TRACE(1, "XenonRequestLocalData::requestInit\n");
295 m_asyncInvalidCount
= 0;
296 m_stackSnapshots
= Array::Create();
297 if (RuntimeOption::XenonForceAlwaysOn
) {
298 ThreadInfo::s_threadInfo
->m_reqInjectionData
.setXenonSignalFlag();
300 // clear any Xenon flags that might still be on in this thread so
301 // that we do not have a bias towards the first function
302 ThreadInfo::s_threadInfo
->m_reqInjectionData
.clearXenonSignalFlag();
306 void XenonRequestLocalData::requestShutdown() {
307 TRACE(1, "XenonRequestLocalData::requestShutdown\n");
308 ThreadInfo::s_threadInfo
->m_reqInjectionData
.clearXenonSignalFlag();
309 m_stackSnapshots
.detach();
312 ///////////////////////////////////////////////////////////////////////////////
313 // Function that allows php code to access request local data that has been
314 // gathered via surprise flags.
316 static Array
HHVM_FUNCTION(xenon_get_data
, void) {
317 if (RuntimeOption::XenonForceAlwaysOn
||
318 RuntimeOption::XenonPeriodSeconds
> 0) {
319 TRACE(1, "xenon_get_data\n");
320 return s_xenonData
->createResponse();
322 return Array::Create();
325 class xenonExtension
: public Extension
{
327 xenonExtension() : Extension("xenon", "1.0") { }
329 void moduleInit() override
{
330 HHVM_FALIAS(HH
\\xenon_get_data
, xenon_get_data
);
335 ///////////////////////////////////////////////////////////////////////////////