/hphp/runtime/ext/xenon/ext_xenon.cpp
C++ | 338 lines | 228 code | 49 blank | 61 comment | 35 complexity | 0873461e0896a9f81a718917f95cb49f MD5 | raw file
- /*
- +----------------------------------------------------------------------+
- | HipHop for PHP |
- +----------------------------------------------------------------------+
- | Copyright (c) 2010-2015 Facebook, Inc. (http://www.facebook.com) |
- | Copyright (c) 1997-2010 The PHP Group |
- +----------------------------------------------------------------------+
- | This source file is subject to version 3.01 of the PHP license, |
- | that is bundled with this package in the file LICENSE, and is |
- | available through the world-wide-web at the following url: |
- | http://www.php.net/license/3_01.txt |
- | If you did not receive a copy of the PHP license and are unable to |
- | obtain it through the world-wide-web, please send a note to |
- | license@php.net so we can mail you a copy immediately. |
- +----------------------------------------------------------------------+
- */
- #include "hphp/runtime/ext/xenon/ext_xenon.h"
- #include "hphp/runtime/base/array-init.h"
- #include "hphp/runtime/base/request-injection-data.h"
- #include "hphp/runtime/base/request-local.h"
- #include "hphp/runtime/base/runtime-option.h"
- #include "hphp/runtime/base/surprise-flags.h"
- #include "hphp/runtime/base/thread-info.h"
- #include "hphp/runtime/base/backtrace.h"
- #include "hphp/runtime/ext/std/ext_std_function.h"
- #include "hphp/runtime/vm/vm-regs.h"
- #include <signal.h>
- #include <time.h>
- #include <iostream>
- namespace HPHP {
- TRACE_SET_MOD(xenon);
- void *s_waitThread(void *arg) {
- TRACE(1, "s_waitThread Starting\n");
- sem_t* sem = static_cast<sem_t*>(arg);
- while (true) {
- if (sem_wait(sem) == 0) {
- TRACE(1, "s_waitThread Fired\n");
- if (Xenon::getInstance().m_stopping) {
- TRACE(1, "s_waitThread is exiting\n");
- return nullptr;
- }
- Xenon::getInstance().surpriseAll();
- } else if (errno != EINTR) {
- break;
- }
- }
- TRACE(1, "s_waitThread Ending\n");
- return nullptr;
- }
- ///////////////////////////////////////////////////////////////////////////////
- // Data that is kept per request and is only valid per request.
- // This structure gathers a php and async stack trace when log is called.
- // These logged stacks can be then gathered via php a call, xenon_get_data.
- // It needs to allocate and free its Array per request, because Array lifetime
- // is per-request. So the flow for these objects are:
- // allocated when a web request begins (if Xenon is enabled)
- // grab snapshots of the php and async stack when log is called
- // detach itself from its snapshots when the request is ending.
- namespace {
- struct XenonRequestLocalData final : RequestEventHandler {
- XenonRequestLocalData();
- ~XenonRequestLocalData();
- void log(Xenon::SampleType t);
- Array createResponse();
- // implement RequestEventHandler
- void requestInit() override;
- void requestShutdown() override;
- void vscan(IMarker& mark) const override {
- mark(m_stackSnapshots);
- }
- // an array of php stacks
- Array m_stackSnapshots;
- };
- IMPLEMENT_STATIC_REQUEST_LOCAL(XenonRequestLocalData, s_xenonData);
- }
- ///////////////////////////////////////////////////////////////////////////////
- // statics used by the Xenon classes
- const StaticString
- s_class("class"),
- s_function("function"),
- s_file("file"),
- s_line("line"),
- s_metadata("metadata"),
- s_time("time"),
- s_isWait("ioWaitSample"),
- s_phpStack("phpStack");
- namespace {
- Array parsePhpStack(const Array& bt) {
- PackedArrayInit stack(bt->size());
- for (ArrayIter it(bt); it; ++it) {
- const auto& frame = it.second().toArray();
- if (frame.exists(s_function)) {
- bool fileline = frame.exists(s_file) && frame.exists(s_line);
- bool metadata = frame.exists(s_metadata);
- ArrayInit element(
- 1 + (fileline ? 2 : 0) + (metadata ? 1 : 0),
- ArrayInit::Map{}
- );
- if (frame.exists(s_class)) {
- auto func = folly::to<std::string>(
- frame[s_class].toString().c_str(),
- "::",
- frame[s_function].toString().c_str()
- );
- element.set(s_function, func);
- } else {
- element.set(s_function, frame[s_function].toString());
- }
- if (fileline) {
- element.set(s_file, frame[s_file]);
- element.set(s_line, frame[s_line]);
- }
- if (metadata) {
- element.set(s_metadata, frame[s_metadata]);
- }
- stack.append(element.toArray());
- }
- }
- return stack.toArray();
- }
- } // namespace
- ///////////////////////////////////////////////////////////////////////////
- // A singleton object that handles the two Xenon modes (always or timer).
- // If in always on mode, the Xenon Surprise flags have to be on for each thread
- // and are never cleared.
- // For timer mode, when start is invoked, it adds a new timer to the existing
- // handler for SIGVTALRM.
- Xenon& Xenon::getInstance() noexcept {
- static Xenon instance;
- return instance;
- }
- Xenon::Xenon() noexcept : m_stopping(false) {
- #if !defined(__APPLE__) && !defined(_MSC_VER)
- m_timerid = 0;
- #endif
- }
- // XenonForceAlwaysOn is active - it doesn't need a timer, it is always on.
- // Xenon needs to be started once per process.
- // The number of milliseconds has to be greater than zero.
- // We need to create a semaphore and a thread.
- // If all of those happen, then we need a timer attached to a signal handler.
- void Xenon::start(uint64_t msec) {
- #if !defined(__APPLE__) && !defined(_MSC_VER)
- TRACE(1, "XenonForceAlwaysOn %d\n", RuntimeOption::XenonForceAlwaysOn);
- if (!RuntimeOption::XenonForceAlwaysOn
- && m_timerid == 0
- && msec > 0
- && sem_init(&m_timerTriggered, 0, 0) == 0
- && pthread_create(&m_triggerThread, nullptr, s_waitThread,
- static_cast<void*>(&m_timerTriggered)) == 0) {
- time_t sec = msec / 1000;
- long nsec = (msec % 1000) * 1000000;
- TRACE(1, "Xenon::start periodic %ld seconds, %ld nanoseconds\n", sec, nsec);
- // for the initial timer, we want to stagger time for large installations
- unsigned int seed = time(nullptr);
- uint64_t msecInit = msec * (1.0 + rand_r(&seed) / (double)RAND_MAX);
- time_t fSec = msecInit / 1000;
- long fNsec = (msecInit % 1000) * 1000000;
- TRACE(1, "Xenon::start initial %ld seconds, %ld nanoseconds\n",
- fSec, fNsec);
- sigevent sev={};
- sev.sigev_notify = SIGEV_SIGNAL;
- sev.sigev_signo = SIGVTALRM;
- sev.sigev_value.sival_ptr = nullptr; // null for Xenon signals
- timer_create(CLOCK_REALTIME, &sev, &m_timerid);
- itimerspec ts={};
- ts.it_value.tv_sec = fSec;
- ts.it_value.tv_nsec = fNsec;
- ts.it_interval.tv_sec = sec;
- ts.it_interval.tv_nsec = nsec;
- timer_settime(m_timerid, 0, &ts, nullptr);
- }
- #endif
- }
- // If Xenon owns a pthread, tell it to stop, also clean up anything from start.
- void Xenon::stop() {
- #if !defined(__APPLE__) && !defined(_MSC_VER)
- if (m_timerid) {
- m_stopping = true;
- sem_post(&m_timerTriggered);
- pthread_join(m_triggerThread, nullptr);
- TRACE(1, "Xenon::stop has stopped the waiting thread\n");
- timer_delete(m_timerid);
- sem_destroy(&m_timerTriggered);
- }
- #endif
- }
- // Xenon data is gathered for logging per request, "if we should"
- // meaning that if Xenon's Surprise flag has been turned on by someone, we
- // should log the stacks. If we are in XenonForceAlwaysOn, do not clear
- // the Surprise flag. The data is gathered in thread local storage.
- // If the sample is Enter, then do not record this function name because it
- // hasn't done anything. The sample belongs to the previous function.
- void Xenon::log(SampleType t) const {
- if (getSurpriseFlag(XenonSignalFlag)) {
- if (!RuntimeOption::XenonForceAlwaysOn) {
- clearSurpriseFlag(XenonSignalFlag);
- }
- TRACE(1, "Xenon::log %s\n", (t == IOWaitSample) ? "IOWait" : "Normal");
- s_xenonData->log(t);
- }
- }
- // Called from timer handler, Lets non-signal code know the timer was fired.
- void Xenon::onTimer() {
- sem_post(&m_timerTriggered);
- }
- // Turns on the Xenon Surprise flag for every thread via a lambda function
- // passed to ExecutePerThread.
- void Xenon::surpriseAll() {
- TRACE(1, "Xenon::surpriseAll\n");
- ThreadInfo::ExecutePerThread(
- [] (ThreadInfo* t) { t->m_reqInjectionData.setFlag(XenonSignalFlag); }
- );
- }
- ///////////////////////////////////////////////////////////////////////////////
- // There is one XenonRequestLocalData per thread, stored in thread local area
- namespace {
- XenonRequestLocalData::XenonRequestLocalData() {
- TRACE(1, "XenonRequestLocalData\n");
- }
- XenonRequestLocalData::~XenonRequestLocalData() {
- TRACE(1, "~XenonRequestLocalData\n");
- }
- // Creates an array to respond to the Xenon PHP extension;
- // builds the data into the format neeeded.
- Array XenonRequestLocalData::createResponse() {
- PackedArrayInit stacks(m_stackSnapshots.size());
- for (ArrayIter it(m_stackSnapshots); it; ++it) {
- const auto& frame = it.second().toArray();
- stacks.append(make_map_array(
- s_time, frame[s_time],
- s_phpStack, parsePhpStack(frame[s_phpStack].toArray()),
- s_isWait, frame[s_isWait]
- ));
- }
- return stacks.toArray();
- }
- void XenonRequestLocalData::log(Xenon::SampleType t) {
- TRACE(1, "XenonRequestLocalData::log\n");
- time_t now = time(nullptr);
- auto bt = createBacktrace(BacktraceArgs()
- .skipTop(t == Xenon::EnterSample)
- .withSelf()
- .withMetadata()
- .ignoreArgs());
- m_stackSnapshots.append(make_map_array(
- s_time, now,
- s_phpStack, bt,
- s_isWait, (t == Xenon::IOWaitSample)
- ));
- }
- void XenonRequestLocalData::requestInit() {
- TRACE(1, "XenonRequestLocalData::requestInit\n");
- m_stackSnapshots = Array::Create();
- if (RuntimeOption::XenonForceAlwaysOn) {
- setSurpriseFlag(XenonSignalFlag);
- } else {
- // Clear any Xenon flags that might still be on in this thread so that we do
- // not have a bias towards the first function.
- clearSurpriseFlag(XenonSignalFlag);
- }
- }
- void XenonRequestLocalData::requestShutdown() {
- TRACE(1, "XenonRequestLocalData::requestShutdown\n");
- clearSurpriseFlag(XenonSignalFlag);
- m_stackSnapshots.detach();
- }
- ///////////////////////////////////////////////////////////////////////////////
- // Function that allows php code to access request local data that has been
- // gathered via surprise flags.
- Array HHVM_FUNCTION(xenon_get_data, void) {
- if (RuntimeOption::XenonForceAlwaysOn ||
- RuntimeOption::XenonPeriodSeconds > 0) {
- TRACE(1, "xenon_get_data\n");
- return s_xenonData->createResponse();
- }
- return empty_array();
- }
- } // namespace
- class xenonExtension final : public Extension {
- public:
- xenonExtension() : Extension("xenon", "1.0") { }
- void moduleInit() override {
- HHVM_FALIAS(HH\\xenon_get_data, xenon_get_data);
- loadSystemlib();
- }
- } s_xenon_extension;
- ///////////////////////////////////////////////////////////////////////////////
- }