Reimplement profiler sampler on Mac OS X to get it working under Chromium.
authormikhail.naganov@gmail.com <mikhail.naganov@gmail.com@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Wed, 1 Jul 2009 08:46:59 +0000 (08:46 +0000)
committermikhail.naganov@gmail.com <mikhail.naganov@gmail.com@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Wed, 1 Jul 2009 08:46:59 +0000 (08:46 +0000)
Previous implementation of sampler for OS X was copied from the Linux one. But BSD (OS X) and Linux has a very important difference in signal handling. LinuxThreads doesn't support the notion of process-directed signals. So, the SIGPROF signal was directed to the thread that installed the handler---the V8 thread. But on BSD, signal handling is implemented according to POSIX spec, where process-directed signal is to be handled by an arbitrary selected thread. By a coincidence, in V8's sample shell and in Chromium's test shell, V8's thread was picked almost every time, so sampling seemed working. But not in case of Chromium.

So, I've changed the implementation of profiler sampler to use the same scheme as on Windows---a dedicated thread with high priority is used to periodically pause and sample V8's thread.

Review URL: http://codereview.chromium.org/147150

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@2315 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/log.cc
src/platform-freebsd.cc
src/platform-linux.cc
src/platform-macos.cc
src/platform-win32.cc
src/platform.h

index 0dba08d..2ca89dd 100644 (file)
@@ -176,8 +176,11 @@ class Ticker: public Sampler {
 
   ~Ticker() { if (IsActive()) Stop(); }
 
+  void SampleStack(TickSample* sample) {
+    StackTracer::Trace(sample);
+  }
+
   void Tick(TickSample* sample) {
-    if (IsProfiling()) StackTracer::Trace(sample);
     if (profiler_) profiler_->Insert(sample);
     if (window_) window_->AddState(sample->state);
   }
index acef74c..92d72f8 100644 (file)
@@ -561,6 +561,7 @@ static void ProfilerSignalHandler(int signal, siginfo_t* info, void* context) {
     sample.sp = mcontext.mc_esp;
     sample.fp = mcontext.mc_ebp;
 #endif
+    active_sampler_->SampleStack(&sample);
   }
 
   // We always sample the VM state.
index 39495ab..bccf9e6 100644 (file)
@@ -639,6 +639,7 @@ static void ProfilerSignalHandler(int signal, siginfo_t* info, void* context) {
     sample.fp = mcontext.arm_fp;
 #endif
 #endif
+    active_sampler_->SampleStack(&sample);
   }
 
   // We always sample the VM state.
index f5b6458..6e79de2 100644 (file)
@@ -38,6 +38,7 @@
 #include <pthread.h>
 #include <semaphore.h>
 #include <signal.h>
+#include <mach/mach.h>
 #include <mach/semaphore.h>
 #include <mach/task.h>
 #include <sys/time.h>
@@ -475,63 +476,94 @@ Semaphore* OS::CreateSemaphore(int count) {
 
 #ifdef ENABLE_LOGGING_AND_PROFILING
 
-static Sampler* active_sampler_ = NULL;
-
-static void ProfilerSignalHandler(int signal, siginfo_t* info, void* context) {
-  USE(info);
-  if (signal != SIGPROF) return;
-  if (active_sampler_ == NULL) return;
-
-  TickSample sample;
+class Sampler::PlatformData : public Malloced {
+ public:
+  explicit PlatformData(Sampler* sampler)
+      : sampler_(sampler),
+        task_self_(mach_task_self()),
+        profiled_thread_(0),
+        sampler_thread_(0) {
+  }
 
-  // If profiling, we extract the current pc and sp.
-  if (active_sampler_->IsProfiling()) {
-    // Extracting the sample from the context is extremely machine dependent.
-    ucontext_t* ucontext = reinterpret_cast<ucontext_t*>(context);
-    mcontext_t& mcontext = ucontext->uc_mcontext;
+  Sampler* sampler_;
+  // Note: for profiled_thread_ Mach primitives are used instead of PThread's
+  // because the latter doesn't provide thread manipulation primitives required.
+  // For details, consult "Mac OS X Internals" book, Section 7.3.
+  mach_port_t task_self_;
+  thread_act_t profiled_thread_;
+  pthread_t sampler_thread_;
+
+  // Sampler thread handler.
+  void Runner() {
+    // Loop until the sampler is disengaged.
+    while (sampler_->IsActive()) {
+      TickSample sample;
+
+      // If profiling, we record the pc and sp of the profiled thread.
+      if (sampler_->IsProfiling()
+          && KERN_SUCCESS == thread_suspend(profiled_thread_)) {
 #if V8_HOST_ARCH_X64
-    UNIMPLEMENTED();
-    USE(mcontext);
-    sample.pc = 0;
-    sample.sp = 0;
-    sample.fp = 0;
+        thread_state_flavor_t flavor = x86_THREAD_STATE64;
+        x86_thread_state64_t state;
+        mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT;
+#elif V8_HOST_ARCH_IA32
+        thread_state_flavor_t flavor = i386_THREAD_STATE;
+        i386_thread_state_t state;
+        mach_msg_type_number_t count = i386_THREAD_STATE_COUNT;
+#else
+#error Unsupported Mac OS X host architecture.
+#endif  // V8_TARGET_ARCH_IA32
+        if (KERN_SUCCESS == thread_get_state(profiled_thread_,
+                                             flavor,
+                                             (natural_t*)&state,
+                                             &count)) {
+#if V8_HOST_ARCH_X64
+          UNIMPLEMENTED();
+          sample.pc = 0;
+          sample.sp = 0;
+          sample.fp = 0;
 #elif V8_HOST_ARCH_IA32
 #if __DARWIN_UNIX03
-    sample.pc = mcontext->__ss.__eip;
-    sample.sp = mcontext->__ss.__esp;
-    sample.fp = mcontext->__ss.__ebp;
+          sample.pc = state.__eip;
+          sample.sp = state.__esp;
+          sample.fp = state.__ebp;
 #else  // !__DARWIN_UNIX03
-    sample.pc = mcontext->ss.eip;
-    sample.sp = mcontext->ss.esp;
-    sample.fp = mcontext->ss.ebp;
+          sample.pc = state.eip;
+          sample.sp = state.esp;
+          sample.fp = state.ebp;
 #endif  // __DARWIN_UNIX03
 #else
 #error Unsupported Mac OS X host architecture.
 #endif  // V8_HOST_ARCH_IA32
+          sampler_->SampleStack(&sample);
+        }
+        thread_resume(profiled_thread_);
+      }
+
+      // We always sample the VM state.
+      sample.state = Logger::state();
+      // Invoke tick handler with program counter and stack pointer.
+      sampler_->Tick(&sample);
+
+      // Wait until next sampling.
+      usleep(sampler_->interval_ * 1000);
+    }
   }
+};
 
-  // We always sample the VM state.
-  sample.state = Logger::state();
 
-  active_sampler_->Tick(&sample);
+// Entry point for sampler thread.
+static void* SamplerEntry(void* arg) {
+  Sampler::PlatformData* data =
+      reinterpret_cast<Sampler::PlatformData*>(arg);
+  data->Runner();
+  return 0;
 }
 
 
-class Sampler::PlatformData : public Malloced {
- public:
-  PlatformData() {
-    signal_handler_installed_ = false;
-  }
-
-  bool signal_handler_installed_;
-  struct sigaction old_signal_handler_;
-  struct itimerval old_timer_value_;
-};
-
-
 Sampler::Sampler(int interval, bool profiling)
     : interval_(interval), profiling_(profiling), active_(false) {
-  data_ = new PlatformData();
+  data_ = new PlatformData(this);
 }
 
 
@@ -541,43 +573,40 @@ Sampler::~Sampler() {
 
 
 void Sampler::Start() {
-  // There can only be one active sampler at the time on POSIX
-  // platforms.
-  if (active_sampler_ != NULL) return;
-
-  // Request profiling signals.
-  struct sigaction sa;
-  sa.sa_sigaction = ProfilerSignalHandler;
-  sigemptyset(&sa.sa_mask);
-  sa.sa_flags = SA_SIGINFO;
-  if (sigaction(SIGPROF, &sa, &data_->old_signal_handler_) != 0) return;
-  data_->signal_handler_installed_ = true;
-
-  // Set the itimer to generate a tick for each interval.
-  itimerval itimer;
-  itimer.it_interval.tv_sec = interval_ / 1000;
-  itimer.it_interval.tv_usec = (interval_ % 1000) * 1000;
-  itimer.it_value.tv_sec = itimer.it_interval.tv_sec;
-  itimer.it_value.tv_usec = itimer.it_interval.tv_usec;
-  setitimer(ITIMER_PROF, &itimer, &data_->old_timer_value_);
-
-  // Set this sampler as the active sampler.
-  active_sampler_ = this;
+  // If we are profiling, we need to be able to access the calling
+  // thread.
+  if (IsProfiling()) {
+    data_->profiled_thread_ = mach_thread_self();
+  }
+
+  // Create sampler thread with high priority.
+  // According to POSIX spec, when SCHED_FIFO policy is used, a thread
+  // runs until it exits or blocks.
+  pthread_attr_t sched_attr;
+  sched_param fifo_param;
+  pthread_attr_init(&sched_attr);
+  pthread_attr_setinheritsched(&sched_attr, PTHREAD_EXPLICIT_SCHED);
+  pthread_attr_setschedpolicy(&sched_attr, SCHED_FIFO);
+  fifo_param.sched_priority = sched_get_priority_max(SCHED_FIFO);
+  pthread_attr_setschedparam(&sched_attr, &fifo_param);
+
   active_ = true;
+  pthread_create(&data_->sampler_thread_, &sched_attr, SamplerEntry, data_);
 }
 
 
 void Sampler::Stop() {
-  // Restore old signal handler
-  if (data_->signal_handler_installed_) {
-    setitimer(ITIMER_PROF, &data_->old_timer_value_, NULL);
-    sigaction(SIGPROF, &data_->old_signal_handler_, 0);
-    data_->signal_handler_installed_ = false;
-  }
-
-  // This sampler is no longer the active sampler.
-  active_sampler_ = NULL;
+  // Seting active to false triggers termination of the sampler
+  // thread.
   active_ = false;
+
+  // Wait for sampler thread to terminate.
+  pthread_join(data_->sampler_thread_, NULL);
+
+  // Deallocate Mach port for thread.
+  if (IsProfiling()) {
+    mach_port_deallocate(data_->task_self_, data_->profiled_thread_);
+  }
 }
 
 #endif  // ENABLE_LOGGING_AND_PROFILING
index 1b0f9b2..a8a6243 100644 (file)
@@ -1776,32 +1776,30 @@ class Sampler::PlatformData : public Malloced {
       TickSample sample;
 
       // If profiling, we record the pc and sp of the profiled thread.
-      if (sampler_->IsProfiling()) {
-        // Pause the profiled thread and get its context.
-        SuspendThread(profiled_thread_);
+      if (sampler_->IsProfiling()
+          && SuspendThread(profiled_thread_) != (DWORD)-1) {
         context.ContextFlags = CONTEXT_FULL;
-        GetThreadContext(profiled_thread_, &context);
-        // Invoke tick handler with program counter and stack pointer.
+        if (GetThreadContext(profiled_thread_, &context) != 0) {
 #if V8_HOST_ARCH_X64
-        UNIMPLEMENTED();
-        sample.pc = context.Rip;
-        sample.sp = context.Rsp;
-        sample.fp = context.Rbp;
+          UNIMPLEMENTED();
+          sample.pc = context.Rip;
+          sample.sp = context.Rsp;
+          sample.fp = context.Rbp;
 #else
-        sample.pc = context.Eip;
-        sample.sp = context.Esp;
-        sample.fp = context.Ebp;
+          sample.pc = context.Eip;
+          sample.sp = context.Esp;
+          sample.fp = context.Ebp;
 #endif
+          sampler_->SampleStack(&sample);
+        }
+        ResumeThread(profiled_thread_);
       }
 
       // We always sample the VM state.
       sample.state = Logger::state();
+      // Invoke tick handler with program counter and stack pointer.
       sampler_->Tick(&sample);
 
-      if (sampler_->IsProfiling()) {
-        ResumeThread(profiled_thread_);
-      }
-
       // Wait until next sampling.
       Sleep(sampler_->interval_);
     }
index b5123c5..11a1e79 100644 (file)
@@ -510,6 +510,9 @@ class Sampler {
   explicit Sampler(int interval, bool profiling);
   virtual ~Sampler();
 
+  // Performs stack sampling.
+  virtual void SampleStack(TickSample* sample) = 0;
+
   // This method is called for each sampling period with the current
   // program counter.
   virtual void Tick(TickSample* sample) = 0;
@@ -527,8 +530,8 @@ class Sampler {
   class PlatformData;
 
  private:
-  int interval_;
-  bool profiling_;
+  const int interval_;
+  const bool profiling_;
   bool active_;
   PlatformData* data_;  // Platform specific data.
   DISALLOW_IMPLICIT_CONSTRUCTORS(Sampler);