text: /*
     +----------------------------------------------------------------------+
     | HipHop for PHP                                                       |
     +----------------------------------------------------------------------+
     | Copyright (c) 2010-2016 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 <folly/Random.h>
  
  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 a php 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, c_WaitableWaitHandle* wh = nullptr);
    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_stack("stack"),
    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
      auto const msecInit = folly::Random::rand32(static_cast<uint32_t>(msec));
      auto const fSec = msecInit / 1000;
      auto const fNsec = (msecInit % 1000) * 1000000;
      TRACE(1, "Xenon::start initial %d seconds, %d 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, c_WaitableWaitHandle* wh) const {
    if (getSurpriseFlag(XenonSignalFlag)) {
      if (!RuntimeOption::XenonForceAlwaysOn) {
        clearSurpriseFlag(XenonSignalFlag);
      }
      TRACE(1, "Xenon::log %s\n", (t == IOWaitSample) ? "IOWait" : "Normal");
      s_xenonData->log(t, wh);
    }
  }
  
  // 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_stack, frame[s_stack].toArray(),
        s_phpStack, parsePhpStack(frame[s_stack].toArray()),
        s_isWait, frame[s_isWait]
      ));
    }
    return stacks.toArray();
  }
  
  void XenonRequestLocalData::log(Xenon::SampleType t, c_WaitableWaitHandle* wh) {
    TRACE(1, "XenonRequestLocalData::log\n");
    time_t now = time(nullptr);
    auto bt = createBacktrace(BacktraceArgs()
                               .skipTop(t == Xenon::EnterSample)
                               .fromWaitHandle(wh)
                               .withMetadata()
                               .ignoreArgs());
    m_stackSnapshots.append(make_map_array(
      s_time, now,
      s_stack, 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.reset();
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  // 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
  
  struct xenonExtension final : Extension {
    xenonExtension() : Extension("xenon", "1.0") { }
  
    void moduleInit() override {
      HHVM_FALIAS(HH\\xenon_get_data, xenon_get_data);
      loadSystemlib();
    }
  } s_xenon_extension;
  
  ///////////////////////////////////////////////////////////////////////////////
  }
  