michael@0: // Copyright (c) 2006-2011 The Chromium Authors. All rights reserved. michael@0: // michael@0: // Redistribution and use in source and binary forms, with or without michael@0: // modification, are permitted provided that the following conditions michael@0: // are met: michael@0: // * Redistributions of source code must retain the above copyright michael@0: // notice, this list of conditions and the following disclaimer. michael@0: // * Redistributions in binary form must reproduce the above copyright michael@0: // notice, this list of conditions and the following disclaimer in michael@0: // the documentation and/or other materials provided with the michael@0: // distribution. michael@0: // * Neither the name of Google, Inc. nor the names of its contributors michael@0: // may be used to endorse or promote products derived from this michael@0: // software without specific prior written permission. michael@0: // michael@0: // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS michael@0: // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE michael@0: // COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, michael@0: // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, michael@0: // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS michael@0: // OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED michael@0: // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, michael@0: // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT michael@0: // OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF michael@0: // SUCH DAMAGE. michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include "platform.h" michael@0: #include "TableTicker.h" michael@0: #include "ProfileEntry.h" michael@0: #include "UnwinderThread2.h" michael@0: michael@0: class PlatformData : public Malloced { michael@0: public: michael@0: // Get a handle to the calling thread. This is the thread that we are michael@0: // going to profile. We need to make a copy of the handle because we are michael@0: // going to use it in the sampler thread. Using GetThreadHandle() will michael@0: // not work in this case. We're using OpenThread because DuplicateHandle michael@0: // for some reason doesn't work in Chrome's sandbox. michael@0: PlatformData(int aThreadId) : profiled_thread_(OpenThread(THREAD_GET_CONTEXT | michael@0: THREAD_SUSPEND_RESUME | michael@0: THREAD_QUERY_INFORMATION, michael@0: false, michael@0: aThreadId)) {} michael@0: michael@0: ~PlatformData() { michael@0: if (profiled_thread_ != NULL) { michael@0: CloseHandle(profiled_thread_); michael@0: profiled_thread_ = NULL; michael@0: } michael@0: } michael@0: michael@0: HANDLE profiled_thread() { return profiled_thread_; } michael@0: michael@0: private: michael@0: HANDLE profiled_thread_; michael@0: }; michael@0: michael@0: /* static */ PlatformData* michael@0: Sampler::AllocPlatformData(int aThreadId) michael@0: { michael@0: return new PlatformData(aThreadId); 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: uintptr_t michael@0: Sampler::GetThreadHandle(PlatformData* aData) michael@0: { michael@0: return (uintptr_t) aData->profiled_thread(); michael@0: } michael@0: michael@0: class SamplerThread : public Thread { michael@0: public: michael@0: SamplerThread(double interval, Sampler* sampler) michael@0: : Thread("SamplerThread") michael@0: , interval_(interval) michael@0: , sampler_(sampler) michael@0: { michael@0: interval_ = floor(interval + 0.5); michael@0: if (interval_ <= 0) { michael@0: interval_ = 1; michael@0: } michael@0: } michael@0: michael@0: static void StartSampler(Sampler* sampler) { michael@0: if (instance_ == NULL) { michael@0: instance_ = new SamplerThread(sampler->interval(), sampler); michael@0: instance_->Start(); michael@0: } else { michael@0: ASSERT(instance_->interval_ == sampler->interval()); michael@0: } michael@0: } michael@0: michael@0: static void StopSampler() { michael@0: instance_->Join(); michael@0: delete instance_; michael@0: instance_ = NULL; michael@0: } michael@0: michael@0: // Implement Thread::Run(). michael@0: virtual void Run() { michael@0: michael@0: // By default we'll not adjust the timer resolution which tends to be around michael@0: // 16ms. However, if the requested interval is sufficiently low we'll try to michael@0: // adjust the resolution to match. michael@0: if (interval_ < 10) michael@0: ::timeBeginPeriod(interval_); michael@0: michael@0: while (sampler_->IsActive()) { michael@0: if (!sampler_->IsPaused()) { michael@0: mozilla::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); michael@0: std::vector threads = michael@0: 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(sampler_, thread_profile); michael@0: } michael@0: } michael@0: OS::Sleep(interval_); michael@0: } michael@0: michael@0: // disable any timer resolution changes we've made michael@0: if (interval_ < 10) michael@0: ::timeEndPeriod(interval_); michael@0: } michael@0: michael@0: void SampleContext(Sampler* sampler, ThreadProfile* thread_profile) { michael@0: uintptr_t thread = Sampler::GetThreadHandle( michael@0: thread_profile->GetPlatformData()); michael@0: HANDLE profiled_thread = reinterpret_cast(thread); michael@0: if (profiled_thread == NULL) michael@0: return; michael@0: michael@0: // Context used for sampling the register state of the profiled thread. michael@0: CONTEXT context; michael@0: memset(&context, 0, sizeof(context)); michael@0: michael@0: TickSample sample_obj; michael@0: TickSample* sample = &sample_obj; michael@0: michael@0: // Grab the timestamp before pausing the thread, to avoid deadlocks. michael@0: sample->timestamp = mozilla::TimeStamp::Now(); michael@0: sample->threadProfile = thread_profile; michael@0: michael@0: static const DWORD kSuspendFailed = static_cast(-1); michael@0: if (SuspendThread(profiled_thread) == kSuspendFailed) michael@0: return; michael@0: michael@0: context.ContextFlags = CONTEXT_CONTROL; michael@0: if (GetThreadContext(profiled_thread, &context) != 0) { michael@0: #if V8_HOST_ARCH_X64 michael@0: sample->pc = reinterpret_cast
(context.Rip); michael@0: sample->sp = reinterpret_cast
(context.Rsp); michael@0: sample->fp = reinterpret_cast
(context.Rbp); michael@0: #else michael@0: sample->pc = reinterpret_cast
(context.Eip); michael@0: sample->sp = reinterpret_cast
(context.Esp); michael@0: sample->fp = reinterpret_cast
(context.Ebp); michael@0: #endif michael@0: sample->context = &context; michael@0: sampler->Tick(sample); michael@0: } michael@0: ResumeThread(profiled_thread); michael@0: } michael@0: michael@0: Sampler* sampler_; michael@0: int interval_; // units: ms michael@0: michael@0: // Protects the process wide state below. michael@0: static SamplerThread* instance_; michael@0: michael@0: DISALLOW_COPY_AND_ASSIGN(SamplerThread); michael@0: }; michael@0: michael@0: SamplerThread* SamplerThread::instance_ = NULL; michael@0: michael@0: michael@0: Sampler::Sampler(double interval, bool profiling, int entrySize) michael@0: : interval_(interval), michael@0: profiling_(profiling), michael@0: paused_(false), michael@0: active_(false), michael@0: entrySize_(entrySize) { michael@0: } michael@0: michael@0: Sampler::~Sampler() { michael@0: ASSERT(!IsActive()); michael@0: } michael@0: michael@0: void Sampler::Start() { michael@0: ASSERT(!IsActive()); michael@0: SetActive(true); michael@0: SamplerThread::StartSampler(this); michael@0: } michael@0: michael@0: void Sampler::Stop() { michael@0: ASSERT(IsActive()); michael@0: SetActive(false); michael@0: SamplerThread::StopSampler(); michael@0: } michael@0: michael@0: michael@0: static const HANDLE kNoThread = INVALID_HANDLE_VALUE; michael@0: michael@0: static unsigned int __stdcall ThreadEntry(void* arg) { michael@0: Thread* thread = reinterpret_cast(arg); michael@0: thread->Run(); michael@0: return 0; michael@0: } michael@0: michael@0: // Initialize a Win32 thread object. The thread has an invalid thread michael@0: // handle until it is started. michael@0: Thread::Thread(const char* name) michael@0: : stack_size_(0) { michael@0: thread_ = kNoThread; michael@0: set_name(name); 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: // Close our own handle for the thread. michael@0: Thread::~Thread() { michael@0: if (thread_ != kNoThread) CloseHandle(thread_); michael@0: } michael@0: michael@0: // Create a new thread. It is important to use _beginthreadex() instead of michael@0: // the Win32 function CreateThread(), because the CreateThread() does not michael@0: // initialize thread specific structures in the C runtime library. michael@0: void Thread::Start() { michael@0: thread_ = reinterpret_cast( michael@0: _beginthreadex(NULL, michael@0: static_cast(stack_size_), michael@0: ThreadEntry, michael@0: this, michael@0: 0, michael@0: (unsigned int*) &thread_id_)); michael@0: } michael@0: michael@0: // Wait for thread to terminate. michael@0: void Thread::Join() { michael@0: if (thread_id_ != GetCurrentId()) { michael@0: WaitForSingleObject(thread_, INFINITE); michael@0: } michael@0: } michael@0: michael@0: /* static */ Thread::tid_t michael@0: Thread::GetCurrentId() michael@0: { michael@0: return GetCurrentThreadId(); michael@0: } michael@0: michael@0: void OS::Startup() { michael@0: } michael@0: michael@0: void OS::Sleep(int milliseconds) { michael@0: ::Sleep(milliseconds); 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 = GetCurrentThreadId(); 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: // 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 = GetCurrentThreadId(); 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: MOZ_ASSERT(aContext); michael@0: CONTEXT* pContext = reinterpret_cast(aContext); michael@0: context = pContext; michael@0: RtlCaptureContext(pContext); michael@0: michael@0: #if defined(SPS_PLAT_amd64_windows) michael@0: michael@0: pc = reinterpret_cast
(pContext->Rip); michael@0: sp = reinterpret_cast
(pContext->Rsp); michael@0: fp = reinterpret_cast
(pContext->Rbp); michael@0: michael@0: #elif defined(SPS_PLAT_x86_windows) michael@0: michael@0: pc = reinterpret_cast
(pContext->Eip); michael@0: sp = reinterpret_cast
(pContext->Esp); michael@0: fp = reinterpret_cast
(pContext->Ebp); michael@0: michael@0: #endif michael@0: } michael@0: