michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "nsThreadUtils.h" michael@0: michael@0: #include "platform.h" michael@0: #include "TableTicker.h" michael@0: #include "UnwinderThread2.h" /* uwt__register_thread_for_profiling */ michael@0: michael@0: // this port is based off of v8 svn revision 9837 michael@0: michael@0: // XXX: this is a very stubbed out implementation michael@0: // that only supports a single Sampler michael@0: struct SamplerRegistry { michael@0: static void AddActiveSampler(Sampler *sampler) { michael@0: ASSERT(!SamplerRegistry::sampler); michael@0: SamplerRegistry::sampler = sampler; michael@0: } michael@0: static void RemoveActiveSampler(Sampler *sampler) { michael@0: SamplerRegistry::sampler = NULL; michael@0: } michael@0: static Sampler *sampler; michael@0: }; michael@0: michael@0: Sampler *SamplerRegistry::sampler = NULL; michael@0: michael@0: // 0 is never a valid thread id on MacOSX since a ptread_t is michael@0: // a pointer. michael@0: static const pthread_t kNoThread = (pthread_t) 0; michael@0: michael@0: void OS::Startup() { michael@0: } michael@0: michael@0: void OS::Sleep(int milliseconds) { michael@0: usleep(1000 * milliseconds); michael@0: } michael@0: michael@0: void OS::SleepMicro(int microseconds) { michael@0: usleep(microseconds); michael@0: } michael@0: michael@0: Thread::Thread(const char* name) michael@0: : stack_size_(0) { michael@0: set_name(name); michael@0: } michael@0: michael@0: michael@0: Thread::~Thread() { michael@0: } michael@0: michael@0: michael@0: static void SetThreadName(const char* name) { michael@0: // pthread_setname_np is only available in 10.6 or later, so test michael@0: // for it at runtime. michael@0: int (*dynamic_pthread_setname_np)(const char*); michael@0: *reinterpret_cast(&dynamic_pthread_setname_np) = michael@0: dlsym(RTLD_DEFAULT, "pthread_setname_np"); michael@0: if (!dynamic_pthread_setname_np) michael@0: return; michael@0: michael@0: // Mac OS X does not expose the length limit of the name, so hardcode it. michael@0: static const int kMaxNameLength = 63; michael@0: USE(kMaxNameLength); michael@0: ASSERT(Thread::kMaxThreadNameLength <= kMaxNameLength); michael@0: dynamic_pthread_setname_np(name); michael@0: } michael@0: michael@0: michael@0: static void* ThreadEntry(void* arg) { michael@0: Thread* thread = reinterpret_cast(arg); michael@0: michael@0: thread->thread_ = pthread_self(); michael@0: SetThreadName(thread->name()); michael@0: ASSERT(thread->thread_ != kNoThread); michael@0: thread->Run(); michael@0: return NULL; michael@0: } michael@0: michael@0: michael@0: void Thread::set_name(const char* name) { michael@0: strncpy(name_, name, sizeof(name_)); michael@0: name_[sizeof(name_) - 1] = '\0'; michael@0: } michael@0: michael@0: michael@0: void Thread::Start() { michael@0: pthread_attr_t* attr_ptr = NULL; michael@0: pthread_attr_t attr; michael@0: if (stack_size_ > 0) { michael@0: pthread_attr_init(&attr); michael@0: pthread_attr_setstacksize(&attr, static_cast(stack_size_)); michael@0: attr_ptr = &attr; michael@0: } michael@0: pthread_create(&thread_, attr_ptr, ThreadEntry, this); michael@0: ASSERT(thread_ != kNoThread); michael@0: } michael@0: michael@0: void Thread::Join() { michael@0: pthread_join(thread_, NULL); michael@0: } michael@0: michael@0: class PlatformData : public Malloced { michael@0: public: michael@0: PlatformData() : profiled_thread_(mach_thread_self()) michael@0: { michael@0: profiled_pthread_ = pthread_from_mach_thread_np(profiled_thread_); michael@0: } michael@0: michael@0: ~PlatformData() { michael@0: // Deallocate Mach port for thread. michael@0: mach_port_deallocate(mach_task_self(), profiled_thread_); michael@0: } michael@0: michael@0: thread_act_t profiled_thread() { return profiled_thread_; } michael@0: pthread_t profiled_pthread() { return profiled_pthread_; } michael@0: michael@0: private: michael@0: // Note: for profiled_thread_ Mach primitives are used instead of PThread's michael@0: // because the latter doesn't provide thread manipulation primitives required. michael@0: // For details, consult "Mac OS X Internals" book, Section 7.3. michael@0: thread_act_t profiled_thread_; michael@0: // we also store the pthread because Mach threads have no concept of stack michael@0: // and we want to be able to get the stack size when we need to unwind the michael@0: // stack using frame pointers. michael@0: pthread_t profiled_pthread_; michael@0: }; michael@0: michael@0: /* static */ PlatformData* michael@0: Sampler::AllocPlatformData(int aThreadId) michael@0: { michael@0: return new PlatformData; michael@0: } michael@0: michael@0: /* static */ void michael@0: Sampler::FreePlatformData(PlatformData* aData) michael@0: { michael@0: delete aData; michael@0: } michael@0: michael@0: class SamplerThread : public Thread { michael@0: public: michael@0: explicit SamplerThread(double interval) michael@0: : Thread("SamplerThread") michael@0: , intervalMicro_(floor(interval * 1000 + 0.5)) michael@0: { michael@0: if (intervalMicro_ <= 0) { michael@0: intervalMicro_ = 1; michael@0: } michael@0: } michael@0: michael@0: static void AddActiveSampler(Sampler* sampler) { michael@0: mozilla::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); michael@0: SamplerRegistry::AddActiveSampler(sampler); michael@0: if (instance_ == NULL) { michael@0: instance_ = new SamplerThread(sampler->interval()); michael@0: instance_->Start(); michael@0: } michael@0: } michael@0: michael@0: static void RemoveActiveSampler(Sampler* sampler) { michael@0: mozilla::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); michael@0: instance_->Join(); michael@0: //XXX: unlike v8 we need to remove the active sampler after doing the Join michael@0: // because we drop the sampler immediately michael@0: SamplerRegistry::RemoveActiveSampler(sampler); michael@0: delete instance_; michael@0: instance_ = NULL; michael@0: } michael@0: michael@0: // Implement Thread::Run(). michael@0: virtual void Run() { michael@0: while (SamplerRegistry::sampler->IsActive()) { michael@0: if (!SamplerRegistry::sampler->IsPaused()) { michael@0: mozilla::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); michael@0: std::vector threads = michael@0: SamplerRegistry::sampler->GetRegisteredThreads(); michael@0: for (uint32_t i = 0; i < threads.size(); i++) { michael@0: ThreadInfo* info = threads[i]; michael@0: michael@0: // This will be null if we're not interested in profiling this thread. michael@0: if (!info->Profile()) michael@0: continue; michael@0: michael@0: PseudoStack::SleepState sleeping = info->Stack()->observeSleeping(); michael@0: if (sleeping == PseudoStack::SLEEPING_AGAIN) { michael@0: info->Profile()->DuplicateLastSample(); michael@0: //XXX: This causes flushes regardless of jank-only mode michael@0: info->Profile()->flush(); michael@0: continue; michael@0: } michael@0: michael@0: ThreadProfile* thread_profile = info->Profile(); michael@0: michael@0: SampleContext(SamplerRegistry::sampler, thread_profile); michael@0: } michael@0: } michael@0: OS::SleepMicro(intervalMicro_); michael@0: } michael@0: } michael@0: michael@0: void SampleContext(Sampler* sampler, ThreadProfile* thread_profile) { michael@0: thread_act_t profiled_thread = michael@0: thread_profile->GetPlatformData()->profiled_thread(); michael@0: michael@0: TickSample sample_obj; michael@0: TickSample* sample = &sample_obj; michael@0: michael@0: if (KERN_SUCCESS != thread_suspend(profiled_thread)) return; michael@0: michael@0: #if V8_HOST_ARCH_X64 michael@0: thread_state_flavor_t flavor = x86_THREAD_STATE64; michael@0: x86_thread_state64_t state; michael@0: mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT; michael@0: #if __DARWIN_UNIX03 michael@0: #define REGISTER_FIELD(name) __r ## name michael@0: #else michael@0: #define REGISTER_FIELD(name) r ## name michael@0: #endif // __DARWIN_UNIX03 michael@0: #elif V8_HOST_ARCH_IA32 michael@0: thread_state_flavor_t flavor = i386_THREAD_STATE; michael@0: i386_thread_state_t state; michael@0: mach_msg_type_number_t count = i386_THREAD_STATE_COUNT; michael@0: #if __DARWIN_UNIX03 michael@0: #define REGISTER_FIELD(name) __e ## name michael@0: #else michael@0: #define REGISTER_FIELD(name) e ## name michael@0: #endif // __DARWIN_UNIX03 michael@0: #else michael@0: #error Unsupported Mac OS X host architecture. michael@0: #endif // V8_HOST_ARCH michael@0: michael@0: if (thread_get_state(profiled_thread, michael@0: flavor, michael@0: reinterpret_cast(&state), michael@0: &count) == KERN_SUCCESS) { michael@0: sample->pc = reinterpret_cast
(state.REGISTER_FIELD(ip)); michael@0: sample->sp = reinterpret_cast
(state.REGISTER_FIELD(sp)); michael@0: sample->fp = reinterpret_cast
(state.REGISTER_FIELD(bp)); michael@0: sample->timestamp = mozilla::TimeStamp::Now(); michael@0: sample->threadProfile = thread_profile; michael@0: sampler->Tick(sample); michael@0: } michael@0: thread_resume(profiled_thread); michael@0: } michael@0: michael@0: int intervalMicro_; michael@0: //RuntimeProfilerRateLimiter rate_limiter_; michael@0: michael@0: static SamplerThread* instance_; michael@0: michael@0: DISALLOW_COPY_AND_ASSIGN(SamplerThread); michael@0: }; michael@0: michael@0: #undef REGISTER_FIELD michael@0: michael@0: SamplerThread* SamplerThread::instance_ = NULL; michael@0: michael@0: Sampler::Sampler(double interval, bool profiling, int entrySize) michael@0: : // isolate_(isolate), michael@0: interval_(interval), michael@0: profiling_(profiling), michael@0: paused_(false), michael@0: active_(false), michael@0: entrySize_(entrySize) /*, michael@0: samples_taken_(0)*/ { michael@0: } michael@0: michael@0: michael@0: Sampler::~Sampler() { michael@0: ASSERT(!IsActive()); michael@0: } michael@0: michael@0: michael@0: void Sampler::Start() { michael@0: ASSERT(!IsActive()); michael@0: SetActive(true); michael@0: SamplerThread::AddActiveSampler(this); michael@0: } michael@0: michael@0: michael@0: void Sampler::Stop() { michael@0: ASSERT(IsActive()); michael@0: SetActive(false); michael@0: SamplerThread::RemoveActiveSampler(this); michael@0: } michael@0: michael@0: pthread_t michael@0: Sampler::GetProfiledThread(PlatformData* aData) michael@0: { michael@0: return aData->profiled_pthread(); michael@0: } michael@0: michael@0: #include michael@0: pid_t gettid() michael@0: { michael@0: return (pid_t) syscall(SYS_thread_selfid); michael@0: } michael@0: michael@0: /* static */ Thread::tid_t michael@0: Thread::GetCurrentId() michael@0: { michael@0: return gettid(); michael@0: } michael@0: michael@0: bool Sampler::RegisterCurrentThread(const char* aName, michael@0: PseudoStack* aPseudoStack, michael@0: bool aIsMainThread, void* stackTop) michael@0: { michael@0: if (!Sampler::sRegisteredThreadsMutex) michael@0: return false; michael@0: michael@0: michael@0: mozilla::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); michael@0: michael@0: int id = gettid(); michael@0: for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { michael@0: ThreadInfo* info = sRegisteredThreads->at(i); michael@0: if (info->ThreadId() == id) { michael@0: // Thread already registered. This means the first unregister will be michael@0: // too early. michael@0: ASSERT(false); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: set_tls_stack_top(stackTop); michael@0: michael@0: ThreadInfo* info = new ThreadInfo(aName, id, michael@0: aIsMainThread, aPseudoStack, stackTop); michael@0: michael@0: if (sActiveSampler) { michael@0: sActiveSampler->RegisterThread(info); michael@0: } michael@0: michael@0: sRegisteredThreads->push_back(info); michael@0: michael@0: uwt__register_thread_for_profiling(stackTop); michael@0: return true; michael@0: } michael@0: michael@0: void Sampler::UnregisterCurrentThread() michael@0: { michael@0: if (!Sampler::sRegisteredThreadsMutex) michael@0: return; michael@0: michael@0: tlsStackTop.set(nullptr); michael@0: michael@0: mozilla::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); michael@0: michael@0: int id = gettid(); michael@0: michael@0: for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { michael@0: ThreadInfo* info = sRegisteredThreads->at(i); michael@0: if (info->ThreadId() == id) { michael@0: delete info; michael@0: sRegisteredThreads->erase(sRegisteredThreads->begin() + i); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void TickSample::PopulateContext(void* aContext) michael@0: { michael@0: // Note that this asm changes if PopulateContext's parameter list is altered michael@0: #if defined(SPS_PLAT_amd64_darwin) michael@0: asm ( michael@0: // Compute caller's %rsp by adding to %rbp: michael@0: // 8 bytes for previous %rbp, 8 bytes for return address michael@0: "leaq 0x10(%%rbp), %0\n\t" michael@0: // Dereference %rbp to get previous %rbp michael@0: "movq (%%rbp), %1\n\t" michael@0: : michael@0: "=r"(sp), michael@0: "=r"(fp) michael@0: ); michael@0: #elif defined(SPS_PLAT_x86_darwin) michael@0: asm ( michael@0: // Compute caller's %esp by adding to %ebp: michael@0: // 4 bytes for aContext + 4 bytes for return address + michael@0: // 4 bytes for previous %ebp michael@0: "leal 0xc(%%ebp), %0\n\t" michael@0: // Dereference %ebp to get previous %ebp michael@0: "movl (%%ebp), %1\n\t" michael@0: : michael@0: "=r"(sp), michael@0: "=r"(fp) michael@0: ); michael@0: #else michael@0: # error "Unsupported architecture" michael@0: #endif michael@0: pc = reinterpret_cast
(__builtin_extract_return_addr( michael@0: __builtin_return_address(0))); michael@0: } michael@0: