PageRenderTime 78ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/hphp/runtime/ext/xenon/ext_xenon.cpp

https://gitlab.com/alvinahmadov2/hhvm
C++ | 338 lines | 228 code | 49 blank | 61 comment | 35 complexity | 0873461e0896a9f81a718917f95cb49f MD5 | raw file
  1. /*
  2. +----------------------------------------------------------------------+
  3. | HipHop for PHP |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 2010-2015 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. +----------------------------------------------------------------------+
  16. */
  17. #include "hphp/runtime/ext/xenon/ext_xenon.h"
  18. #include "hphp/runtime/base/array-init.h"
  19. #include "hphp/runtime/base/request-injection-data.h"
  20. #include "hphp/runtime/base/request-local.h"
  21. #include "hphp/runtime/base/runtime-option.h"
  22. #include "hphp/runtime/base/surprise-flags.h"
  23. #include "hphp/runtime/base/thread-info.h"
  24. #include "hphp/runtime/base/backtrace.h"
  25. #include "hphp/runtime/ext/std/ext_std_function.h"
  26. #include "hphp/runtime/vm/vm-regs.h"
  27. #include <signal.h>
  28. #include <time.h>
  29. #include <iostream>
  30. namespace HPHP {
  31. TRACE_SET_MOD(xenon);
  32. void *s_waitThread(void *arg) {
  33. TRACE(1, "s_waitThread Starting\n");
  34. sem_t* sem = static_cast<sem_t*>(arg);
  35. while (true) {
  36. if (sem_wait(sem) == 0) {
  37. TRACE(1, "s_waitThread Fired\n");
  38. if (Xenon::getInstance().m_stopping) {
  39. TRACE(1, "s_waitThread is exiting\n");
  40. return nullptr;
  41. }
  42. Xenon::getInstance().surpriseAll();
  43. } else if (errno != EINTR) {
  44. break;
  45. }
  46. }
  47. TRACE(1, "s_waitThread Ending\n");
  48. return nullptr;
  49. }
  50. ///////////////////////////////////////////////////////////////////////////////
  51. // Data that is kept per request and is only valid per request.
  52. // This structure gathers a php and async stack trace when log is called.
  53. // These logged stacks can be then gathered via php a call, xenon_get_data.
  54. // It needs to allocate and free its Array per request, because Array lifetime
  55. // is per-request. So the flow for these objects are:
  56. // allocated when a web request begins (if Xenon is enabled)
  57. // grab snapshots of the php and async stack when log is called
  58. // detach itself from its snapshots when the request is ending.
  59. namespace {
  60. struct XenonRequestLocalData final : RequestEventHandler {
  61. XenonRequestLocalData();
  62. ~XenonRequestLocalData();
  63. void log(Xenon::SampleType t);
  64. Array createResponse();
  65. // implement RequestEventHandler
  66. void requestInit() override;
  67. void requestShutdown() override;
  68. void vscan(IMarker& mark) const override {
  69. mark(m_stackSnapshots);
  70. }
  71. // an array of php stacks
  72. Array m_stackSnapshots;
  73. };
  74. IMPLEMENT_STATIC_REQUEST_LOCAL(XenonRequestLocalData, s_xenonData);
  75. }
  76. ///////////////////////////////////////////////////////////////////////////////
  77. // statics used by the Xenon classes
  78. const StaticString
  79. s_class("class"),
  80. s_function("function"),
  81. s_file("file"),
  82. s_line("line"),
  83. s_metadata("metadata"),
  84. s_time("time"),
  85. s_isWait("ioWaitSample"),
  86. s_phpStack("phpStack");
  87. namespace {
  88. Array parsePhpStack(const Array& bt) {
  89. PackedArrayInit stack(bt->size());
  90. for (ArrayIter it(bt); it; ++it) {
  91. const auto& frame = it.second().toArray();
  92. if (frame.exists(s_function)) {
  93. bool fileline = frame.exists(s_file) && frame.exists(s_line);
  94. bool metadata = frame.exists(s_metadata);
  95. ArrayInit element(
  96. 1 + (fileline ? 2 : 0) + (metadata ? 1 : 0),
  97. ArrayInit::Map{}
  98. );
  99. if (frame.exists(s_class)) {
  100. auto func = folly::to<std::string>(
  101. frame[s_class].toString().c_str(),
  102. "::",
  103. frame[s_function].toString().c_str()
  104. );
  105. element.set(s_function, func);
  106. } else {
  107. element.set(s_function, frame[s_function].toString());
  108. }
  109. if (fileline) {
  110. element.set(s_file, frame[s_file]);
  111. element.set(s_line, frame[s_line]);
  112. }
  113. if (metadata) {
  114. element.set(s_metadata, frame[s_metadata]);
  115. }
  116. stack.append(element.toArray());
  117. }
  118. }
  119. return stack.toArray();
  120. }
  121. } // namespace
  122. ///////////////////////////////////////////////////////////////////////////
  123. // A singleton object that handles the two Xenon modes (always or timer).
  124. // If in always on mode, the Xenon Surprise flags have to be on for each thread
  125. // and are never cleared.
  126. // For timer mode, when start is invoked, it adds a new timer to the existing
  127. // handler for SIGVTALRM.
  128. Xenon& Xenon::getInstance() noexcept {
  129. static Xenon instance;
  130. return instance;
  131. }
  132. Xenon::Xenon() noexcept : m_stopping(false) {
  133. #if !defined(__APPLE__) && !defined(_MSC_VER)
  134. m_timerid = 0;
  135. #endif
  136. }
  137. // XenonForceAlwaysOn is active - it doesn't need a timer, it is always on.
  138. // Xenon needs to be started once per process.
  139. // The number of milliseconds has to be greater than zero.
  140. // We need to create a semaphore and a thread.
  141. // If all of those happen, then we need a timer attached to a signal handler.
  142. void Xenon::start(uint64_t msec) {
  143. #if !defined(__APPLE__) && !defined(_MSC_VER)
  144. TRACE(1, "XenonForceAlwaysOn %d\n", RuntimeOption::XenonForceAlwaysOn);
  145. if (!RuntimeOption::XenonForceAlwaysOn
  146. && m_timerid == 0
  147. && msec > 0
  148. && sem_init(&m_timerTriggered, 0, 0) == 0
  149. && pthread_create(&m_triggerThread, nullptr, s_waitThread,
  150. static_cast<void*>(&m_timerTriggered)) == 0) {
  151. time_t sec = msec / 1000;
  152. long nsec = (msec % 1000) * 1000000;
  153. TRACE(1, "Xenon::start periodic %ld seconds, %ld nanoseconds\n", sec, nsec);
  154. // for the initial timer, we want to stagger time for large installations
  155. unsigned int seed = time(nullptr);
  156. uint64_t msecInit = msec * (1.0 + rand_r(&seed) / (double)RAND_MAX);
  157. time_t fSec = msecInit / 1000;
  158. long fNsec = (msecInit % 1000) * 1000000;
  159. TRACE(1, "Xenon::start initial %ld seconds, %ld nanoseconds\n",
  160. fSec, fNsec);
  161. sigevent sev={};
  162. sev.sigev_notify = SIGEV_SIGNAL;
  163. sev.sigev_signo = SIGVTALRM;
  164. sev.sigev_value.sival_ptr = nullptr; // null for Xenon signals
  165. timer_create(CLOCK_REALTIME, &sev, &m_timerid);
  166. itimerspec ts={};
  167. ts.it_value.tv_sec = fSec;
  168. ts.it_value.tv_nsec = fNsec;
  169. ts.it_interval.tv_sec = sec;
  170. ts.it_interval.tv_nsec = nsec;
  171. timer_settime(m_timerid, 0, &ts, nullptr);
  172. }
  173. #endif
  174. }
  175. // If Xenon owns a pthread, tell it to stop, also clean up anything from start.
  176. void Xenon::stop() {
  177. #if !defined(__APPLE__) && !defined(_MSC_VER)
  178. if (m_timerid) {
  179. m_stopping = true;
  180. sem_post(&m_timerTriggered);
  181. pthread_join(m_triggerThread, nullptr);
  182. TRACE(1, "Xenon::stop has stopped the waiting thread\n");
  183. timer_delete(m_timerid);
  184. sem_destroy(&m_timerTriggered);
  185. }
  186. #endif
  187. }
  188. // Xenon data is gathered for logging per request, "if we should"
  189. // meaning that if Xenon's Surprise flag has been turned on by someone, we
  190. // should log the stacks. If we are in XenonForceAlwaysOn, do not clear
  191. // the Surprise flag. The data is gathered in thread local storage.
  192. // If the sample is Enter, then do not record this function name because it
  193. // hasn't done anything. The sample belongs to the previous function.
  194. void Xenon::log(SampleType t) const {
  195. if (getSurpriseFlag(XenonSignalFlag)) {
  196. if (!RuntimeOption::XenonForceAlwaysOn) {
  197. clearSurpriseFlag(XenonSignalFlag);
  198. }
  199. TRACE(1, "Xenon::log %s\n", (t == IOWaitSample) ? "IOWait" : "Normal");
  200. s_xenonData->log(t);
  201. }
  202. }
  203. // Called from timer handler, Lets non-signal code know the timer was fired.
  204. void Xenon::onTimer() {
  205. sem_post(&m_timerTriggered);
  206. }
  207. // Turns on the Xenon Surprise flag for every thread via a lambda function
  208. // passed to ExecutePerThread.
  209. void Xenon::surpriseAll() {
  210. TRACE(1, "Xenon::surpriseAll\n");
  211. ThreadInfo::ExecutePerThread(
  212. [] (ThreadInfo* t) { t->m_reqInjectionData.setFlag(XenonSignalFlag); }
  213. );
  214. }
  215. ///////////////////////////////////////////////////////////////////////////////
  216. // There is one XenonRequestLocalData per thread, stored in thread local area
  217. namespace {
  218. XenonRequestLocalData::XenonRequestLocalData() {
  219. TRACE(1, "XenonRequestLocalData\n");
  220. }
  221. XenonRequestLocalData::~XenonRequestLocalData() {
  222. TRACE(1, "~XenonRequestLocalData\n");
  223. }
  224. // Creates an array to respond to the Xenon PHP extension;
  225. // builds the data into the format neeeded.
  226. Array XenonRequestLocalData::createResponse() {
  227. PackedArrayInit stacks(m_stackSnapshots.size());
  228. for (ArrayIter it(m_stackSnapshots); it; ++it) {
  229. const auto& frame = it.second().toArray();
  230. stacks.append(make_map_array(
  231. s_time, frame[s_time],
  232. s_phpStack, parsePhpStack(frame[s_phpStack].toArray()),
  233. s_isWait, frame[s_isWait]
  234. ));
  235. }
  236. return stacks.toArray();
  237. }
  238. void XenonRequestLocalData::log(Xenon::SampleType t) {
  239. TRACE(1, "XenonRequestLocalData::log\n");
  240. time_t now = time(nullptr);
  241. auto bt = createBacktrace(BacktraceArgs()
  242. .skipTop(t == Xenon::EnterSample)
  243. .withSelf()
  244. .withMetadata()
  245. .ignoreArgs());
  246. m_stackSnapshots.append(make_map_array(
  247. s_time, now,
  248. s_phpStack, bt,
  249. s_isWait, (t == Xenon::IOWaitSample)
  250. ));
  251. }
  252. void XenonRequestLocalData::requestInit() {
  253. TRACE(1, "XenonRequestLocalData::requestInit\n");
  254. m_stackSnapshots = Array::Create();
  255. if (RuntimeOption::XenonForceAlwaysOn) {
  256. setSurpriseFlag(XenonSignalFlag);
  257. } else {
  258. // Clear any Xenon flags that might still be on in this thread so that we do
  259. // not have a bias towards the first function.
  260. clearSurpriseFlag(XenonSignalFlag);
  261. }
  262. }
  263. void XenonRequestLocalData::requestShutdown() {
  264. TRACE(1, "XenonRequestLocalData::requestShutdown\n");
  265. clearSurpriseFlag(XenonSignalFlag);
  266. m_stackSnapshots.detach();
  267. }
  268. ///////////////////////////////////////////////////////////////////////////////
  269. // Function that allows php code to access request local data that has been
  270. // gathered via surprise flags.
  271. Array HHVM_FUNCTION(xenon_get_data, void) {
  272. if (RuntimeOption::XenonForceAlwaysOn ||
  273. RuntimeOption::XenonPeriodSeconds > 0) {
  274. TRACE(1, "xenon_get_data\n");
  275. return s_xenonData->createResponse();
  276. }
  277. return empty_array();
  278. }
  279. } // namespace
  280. class xenonExtension final : public Extension {
  281. public:
  282. xenonExtension() : Extension("xenon", "1.0") { }
  283. void moduleInit() override {
  284. HHVM_FALIAS(HH\\xenon_get_data, xenon_get_data);
  285. loadSystemlib();
  286. }
  287. } s_xenon_extension;
  288. ///////////////////////////////////////////////////////////////////////////////
  289. }