Happy New Year (again)
[hiphop-php.git] / hphp / runtime / ext / ext_xenon.cpp
blobf3f6ef5d262eb309ed628a19a567a8600d90e9ca
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
25 #include <signal.h>
26 #include <vector>
27 #include <time.h>
29 #include <iostream>
31 namespace HPHP {
33 TRACE_SET_MOD(xenon);
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");
42 return nullptr;
44 Xenon::getInstance().surpriseAll();
46 TRACE(1, "s_waitThread Ending\n");
47 return nullptr;
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
81 const StaticString
82 s_class("class"),
83 s_function("function"),
84 s_file("file"),
85 s_type("type"),
86 s_line("line"),
87 s_time("time"),
88 s_asyncInvalidCount("asyncInvalidCount"),
89 s_phpStack("phpStack"),
90 s_asyncStack("asyncStack");
92 static Array parsePhpStack(const Array& bt) {
93 Array stack;
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();
102 Array element;
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);
107 } else {
108 Array 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);
118 return stack;
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;
135 return instance;
138 Xenon::Xenon() : m_stopping(false), m_timerid(0), m_sec(10*60), m_nsec(0) {
141 Xenon::~Xenon() {
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
152 && m_timerid == 0
153 && seconds > 0
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);
162 sigevent sev={};
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);
168 itimerspec ts={};
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.
178 void Xenon::stop() {
179 if (m_timerid) {
180 m_stopping = true;
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() {
229 Array bt;
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;
237 return bt;
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
243 return bt;
245 Array depStack = currentWaitHandle->t_getdependencystack();
247 for (ArrayIter iter(depStack); iter; ++iter) {
248 Array frameData;
249 if (iter.secondRef().isNull()) {
250 frameData.set(s_function, "<prep>", true);
251 } else {
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);
263 return bt;
266 // Creates an array to respond to the Xenon PHP extension;
267 // builds the data into the format neeeded.
268 Array XenonRequestLocalData::createResponse() {
269 Array stacks;
270 for (ArrayIter it(m_stackSnapshots); it; ++it) {
271 Array frame = it.second().toArray();
272 Array element;
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);
279 return stacks;
282 void XenonRequestLocalData::log(bool skipFirst) {
283 TRACE(1, "XenonRequestLocalData::log\n");
284 time_t now = time(nullptr);
285 Array snapshot;
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();
299 } else {
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 {
326 public:
327 xenonExtension() : Extension("xenon", "1.0") { }
329 void moduleInit() override {
330 HHVM_FALIAS(HH\\xenon_get_data, xenon_get_data);
331 loadSystemlib();
333 } s_xenon_extension;
335 ///////////////////////////////////////////////////////////////////////////////