From 69273a14015e15543482a8688154aa676a6b20cf Mon Sep 17 00:00:00 2001 From: Benoit Girard Date: Wed, 3 Apr 2013 18:59:17 -0400 Subject: [PATCH] Bug 734691 - Port multi-thread support to win/mac. r=snorp,smaug --HG-- extra : rebase_source : 172d75f2183c5ba50ce709aeefcd3534a35724e5 --- dom/workers/RuntimeService.cpp | 16 ++- tools/profiler/GeckoProfiler.h | 4 + tools/profiler/GeckoProfilerImpl.h | 11 ++ tools/profiler/Makefile.in | 3 + tools/profiler/ProfileEntry.cpp | 6 +- tools/profiler/ProfileEntry.h | 7 +- tools/profiler/PseudoStack.h | 4 + tools/profiler/TableTicker.cpp | 15 ++- tools/profiler/TableTicker.h | 18 ++- tools/profiler/platform-linux.cc | 193 +++++++++++++++-------------- tools/profiler/platform-macos.cc | 109 ++++++++++------ tools/profiler/platform-win32.cc | 104 ++++++++++------ tools/profiler/platform.cpp | 57 +++++---- tools/profiler/platform.h | 117 ++++++++++------- 14 files changed, 410 insertions(+), 254 deletions(-) diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp index fdfd21a2f0f2..c5744cae3ea2 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -265,6 +265,10 @@ JSBool OperationCallback(JSContext* aCx) { WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); + + // Now is a good time to turn on profiling if it's pending. + profiler_js_operation_callback(); + return worker->OperationCallback(aCx); } @@ -517,15 +521,19 @@ public: return NS_ERROR_FAILURE; } + JSRuntime* rt = JS_GetRuntime(cx); + profiler_register_thread("WebWorker"); +#ifdef MOZ_ENABLE_PROFILER_SPS + if (PseudoStack* stack = mozilla_get_pseudo_stack()) + stack->sampleRuntime(rt); +#endif { JSAutoRequest ar(cx); workerPrivate->DoRunLoop(cx); } - JSRuntime* rt = JS_GetRuntime(cx); - // XXX Bug 666963 - CTypes can create another JSContext for use with // closures, and then it holds that context in a reserved slot on the CType // prototype object. We have to destroy that context before we can destroy @@ -544,6 +552,10 @@ public: JS_DestroyContext(cx); } +#ifdef MOZ_ENABLE_PROFILER_SPS + if (PseudoStack* stack = mozilla_get_pseudo_stack()) + stack->sampleRuntime(nullptr); +#endif JS_DestroyRuntime(rt); workerPrivate->ScheduleDeletion(false); diff --git a/tools/profiler/GeckoProfiler.h b/tools/profiler/GeckoProfiler.h index 9105f0470ecb..9c2507a71dad 100644 --- a/tools/profiler/GeckoProfiler.h +++ b/tools/profiler/GeckoProfiler.h @@ -138,6 +138,10 @@ static inline void profiler_unlock() {} static inline void profiler_register_thread(const char* name) {} static inline void profiler_unregister_thread() {} +// Call by the JSRuntime's operation callback. This is used to enable +// profiling on auxilerary threads. +static inline void profiler_js_operation_callback() {} + #else #include "GeckoProfilerImpl.h" diff --git a/tools/profiler/GeckoProfilerImpl.h b/tools/profiler/GeckoProfilerImpl.h index 3a23e7cb694b..61ae16ee1c1d 100644 --- a/tools/profiler/GeckoProfilerImpl.h +++ b/tools/profiler/GeckoProfilerImpl.h @@ -152,6 +152,17 @@ void profiler_unregister_thread() mozilla_sampler_unregister_thread(); } +static inline +void profiler_js_operation_callback() +{ + PseudoStack *stack = tlsPseudoStack.get(); + if (!stack) { + return; + } + + stack->jsOperationCallback(); +} + // we want the class and function name but can't easily get that using preprocessor macros // __func__ doesn't have the class name and __PRETTY_FUNCTION__ has the parameters diff --git a/tools/profiler/Makefile.in b/tools/profiler/Makefile.in index eb62fbd25143..98bcadcffde8 100644 --- a/tools/profiler/Makefile.in +++ b/tools/profiler/Makefile.in @@ -43,6 +43,9 @@ ifndef _MSC_VER FAIL_ON_WARNINGS = 1 endif # !_MSC_VER +# Uncomment for better debugging in opt builds +#MOZ_OPTIMIZE_FLAGS += -O0 -g + CPPSRCS = \ platform.cpp \ nsProfilerFactory.cpp \ diff --git a/tools/profiler/ProfileEntry.cpp b/tools/profiler/ProfileEntry.cpp index 4435ebf4e9a2..d3608cace5fc 100644 --- a/tools/profiler/ProfileEntry.cpp +++ b/tools/profiler/ProfileEntry.cpp @@ -134,7 +134,10 @@ std::ostream& operator<<(std::ostream& stream, const ProfileEntry& entry) #define DYNAMIC_MAX_STRING 512 -ThreadProfile::ThreadProfile(const char* aName, int aEntrySize, PseudoStack *aStack, int aThreadId, bool aIsMainThread) +ThreadProfile::ThreadProfile(const char* aName, int aEntrySize, + PseudoStack *aStack, int aThreadId, + PlatformData* aPlatform, + bool aIsMainThread) : mWritePos(0) , mLastFlushPos(0) , mReadPos(0) @@ -144,6 +147,7 @@ ThreadProfile::ThreadProfile(const char* aName, int aEntrySize, PseudoStack *aSt , mName(strdup(aName)) , mThreadId(aThreadId) , mIsMainThread(aIsMainThread) + , mPlatformData(aPlatform) { mEntries = new ProfileEntry[mEntrySize]; } diff --git a/tools/profiler/ProfileEntry.h b/tools/profiler/ProfileEntry.h index a5483b59f887..378dc42481c2 100644 --- a/tools/profiler/ProfileEntry.h +++ b/tools/profiler/ProfileEntry.h @@ -12,7 +12,6 @@ #include "platform.h" #include "mozilla/Mutex.h" -class ThreadProfile; class ThreadProfile; class ProfileEntry @@ -57,7 +56,9 @@ typedef void (*IterateTagsCallback)(const ProfileEntry& entry, const char* tagSt class ThreadProfile { public: - ThreadProfile(const char* aName, int aEntrySize, PseudoStack *aStack, int aThreadId, bool aIsMainThread); + ThreadProfile(const char* aName, int aEntrySize, PseudoStack *aStack, + int aThreadId, PlatformData* aPlatformData, + bool aIsMainThread); ~ThreadProfile(); void addTag(ProfileEntry aTag); void flush(); @@ -76,6 +77,7 @@ public: const char* Name() const { return mName; } int ThreadId() const { return mThreadId; } + PlatformData* GetPlatformData() { return mPlatformData; } private: // Circular buffer 'Keep One Slot Open' implementation // for simplicity @@ -89,6 +91,7 @@ private: char* mName; int mThreadId; bool mIsMainThread; + PlatformData* mPlatformData; // Platform specific data. }; std::ostream& operator<<(std::ostream& stream, const ThreadProfile& profile); diff --git a/tools/profiler/PseudoStack.h b/tools/profiler/PseudoStack.h index 9079ffbfc3a8..b12bb226dae5 100644 --- a/tools/profiler/PseudoStack.h +++ b/tools/profiler/PseudoStack.h @@ -220,6 +220,10 @@ public: mStartJSSampling = true; } } + void jsOperationCallback() { + if (mStartJSSampling) + enableJSSampling(); + } void disableJSSampling() { mStartJSSampling = false; if (mRuntime) diff --git a/tools/profiler/TableTicker.cpp b/tools/profiler/TableTicker.cpp index 14a3350b1318..349f7e794338 100644 --- a/tools/profiler/TableTicker.cpp +++ b/tools/profiler/TableTicker.cpp @@ -179,6 +179,10 @@ void TableTicker::BuildJSObject(JSAObjectBuilder& b, JSCustomObject* profile) mozilla::MutexAutoLock lock(*sRegisteredThreadsMutex); for (size_t i = 0; i < sRegisteredThreads->size(); i++) { + // Thread not being profiled, skip it + if (!sRegisteredThreads->at(i)->Profile()) + continue; + MutexAutoLock lock(*sRegisteredThreads->at(i)->Profile()->GetMutex()); JSCustomObject* threadSamples = b.CreateObject(); @@ -291,7 +295,7 @@ void StackWalkCallback(void* aPC, void* aSP, void* aClosure) void TableTicker::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample) { #ifndef XP_MACOSX - uintptr_t thread = GetThreadHandle(platform_data()); + uintptr_t thread = GetThreadHandle(aSample->threadProfile->GetPlatformData()); MOZ_ASSERT(thread); #endif void* pc_array[1000]; @@ -308,7 +312,7 @@ void TableTicker::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample uint32_t maxFrames = uint32_t(array.size - array.count); #ifdef XP_MACOSX - pthread_t pt = GetProfiledThread(platform_data()); + pthread_t pt = GetProfiledThread(aSample->threadProfile->GetPlatformData()); void *stackEnd = reinterpret_cast(-1); if (pt) stackEnd = static_cast(pthread_get_stackaddr_np(pt)); @@ -389,11 +393,6 @@ void doSampleStackTrace(PseudoStack *aStack, ThreadProfile &aProfile, TickSample void TableTicker::Tick(TickSample* sample) { - if (!sample->threadProfile) { - // Platform doesn't support multithread, so use the main thread profile we created - sample->threadProfile = GetPrimaryThreadProfile(); - } - ThreadProfile& currThreadProfile = *sample->threadProfile; // Marker(s) come before the sample @@ -474,7 +473,7 @@ void mozilla_sampler_print_location1() } ThreadProfile threadProfile("Temp", PROFILE_DEFAULT_ENTRY, stack, - 0, false); + 0, Sampler::AllocPlatformData(0), false); doSampleStackTrace(stack, threadProfile, NULL); threadProfile.flush(); diff --git a/tools/profiler/TableTicker.h b/tools/profiler/TableTicker.h index edb91195430d..274f1aac05c0 100644 --- a/tools/profiler/TableTicker.h +++ b/tools/profiler/TableTicker.h @@ -26,7 +26,7 @@ class BreakpadSampler; class TableTicker: public Sampler { public: - TableTicker(int aInterval, int aEntrySize, PseudoStack *aStack, + TableTicker(int aInterval, int aEntrySize, const char** aFeatures, uint32_t aFeatureCount) : Sampler(aInterval, true, aEntrySize) , mPrimaryThreadProfile(nullptr) @@ -38,6 +38,7 @@ class TableTicker: public Sampler { //XXX: It's probably worth splitting the jank profiler out from the regular profiler at some point mJankOnly = hasFeature(aFeatures, aFeatureCount, "jank"); mProfileJS = hasFeature(aFeatures, aFeatureCount, "js"); + mProfileThreads = true || hasFeature(aFeatures, aFeatureCount, "threads"); mAddLeafAddresses = hasFeature(aFeatures, aFeatureCount, "leaf"); { @@ -46,10 +47,15 @@ class TableTicker: public Sampler { // Create ThreadProfile for each registered thread for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { ThreadInfo* info = sRegisteredThreads->at(i); + + if (!info->IsMainThread() && !mProfileThreads) + continue; + ThreadProfile* profile = new ThreadProfile(info->Name(), aEntrySize, info->Stack(), info->ThreadId(), + info->GetPlatformData(), info->IsMainThread()); profile->addTag(ProfileEntry('m', "Start")); @@ -81,8 +87,6 @@ class TableTicker: public Sampler { } } - virtual void SampleStack(TickSample* sample) {} - // Called within a signal. This function must be reentrant virtual void Tick(TickSample* sample); @@ -115,7 +119,8 @@ class TableTicker: public Sampler { virtual JSObject *ToJSObject(JSContext *aCx); JSCustomObject *GetMetaJSCustomObject(JSAObjectBuilder& b); - const bool ProfileJS() { return mProfileJS; } + bool ProfileJS() const { return mProfileJS; } + bool ProfileThreads() const { return mProfileThreads; } virtual BreakpadSampler* AsBreakpadSampler() { return nullptr; } @@ -133,13 +138,14 @@ protected: bool mUseStackWalk; bool mJankOnly; bool mProfileJS; + bool mProfileThreads; }; class BreakpadSampler: public TableTicker { public: - BreakpadSampler(int aInterval, int aEntrySize, PseudoStack *aStack, + BreakpadSampler(int aInterval, int aEntrySize, const char** aFeatures, uint32_t aFeatureCount) - : TableTicker(aInterval, aEntrySize, aStack, aFeatures, aFeatureCount) + : TableTicker(aInterval, aEntrySize, aFeatures, aFeatureCount) {} // Called within a signal. This function must be reentrant diff --git a/tools/profiler/platform-linux.cc b/tools/profiler/platform-linux.cc index 0b5915e88327..22668ef31031 100644 --- a/tools/profiler/platform-linux.cc +++ b/tools/profiler/platform-linux.cc @@ -64,6 +64,7 @@ #include "mozilla/Mutex.h" #include "ProfileEntry.h" #include "nsThreadUtils.h" +#include "TableTicker.h" #include #include @@ -84,6 +85,19 @@ pid_t gettid() #include "android-signal-defs.h" #endif +struct SamplerRegistry { + static void AddActiveSampler(Sampler *sampler) { + ASSERT(!SamplerRegistry::sampler); + SamplerRegistry::sampler = sampler; + } + static void RemoveActiveSampler(Sampler *sampler) { + SamplerRegistry::sampler = NULL; + } + static Sampler *sampler; +}; + +Sampler *SamplerRegistry::sampler = NULL; + static ThreadProfile* sCurrentThreadProfile = NULL; static void ProfilerSaveSignalHandler(int signal, siginfo_t* info, void* context) { @@ -150,93 +164,74 @@ static void ProfilerSignalHandler(int signal, siginfo_t* info, void* context) { sCurrentThreadProfile = NULL; } -#ifndef XP_MACOSX int tgkill(pid_t tgid, pid_t tid, int signalno) { return syscall(SYS_tgkill, tgid, tid, signalno); } -#endif -class Sampler::PlatformData : public Malloced { +class PlatformData : public Malloced { public: - explicit PlatformData(Sampler* sampler) - : sampler_(sampler), - signal_handler_installed_(false), - vm_tgid_(getpid()), -#ifndef XP_MACOSX - vm_tid_(gettid()), -#endif - signal_sender_launched_(false) -#ifdef XP_MACOSX - , signal_receiver_(pthread_self()) -#endif + PlatformData() { } - - void SignalSender() { - while (sampler_->IsActive()) { - sampler_->HandleSaveRequest(); - - if (!sampler_->IsPaused()) { -#ifdef XP_MACOSX - pthread_kill(signal_receiver_, SIGPROF); -#else - - std::vector threads = GetRegisteredThreads(); - - for (uint32_t i = 0; i < threads.size(); i++) { - ThreadInfo* info = threads[i]; - - // We use sCurrentThreadProfile the ThreadProfile for the - // thread we're profiling to the signal handler - sCurrentThreadProfile = info->Profile(); - - int threadId = info->ThreadId(); - if (threadId == 0) { - threadId = vm_tid_; - } - - if (tgkill(vm_tgid_, threadId, SIGPROF) != 0) { - printf_stderr("profiler failed to signal tid=%d\n", threadId); -#ifdef DEBUG - abort(); -#endif - continue; - } - - // Wait for the signal handler to run before moving on to the next one - while (sCurrentThreadProfile) - sched_yield(); - } -#endif - } - - // Convert ms to us and subtract 100 us to compensate delays - // occuring during signal delivery. - // TODO measure and confirm this. - const useconds_t interval = sampler_->interval_ * 1000 - 100; - //int result = usleep(interval); - usleep(interval); - } - } - - Sampler* sampler_; - bool signal_handler_installed_; - struct sigaction old_sigprof_signal_handler_; - struct sigaction old_sigsave_signal_handler_; - pid_t vm_tgid_; - pid_t vm_tid_; - bool signal_sender_launched_; - pthread_t signal_sender_thread_; -#ifdef XP_MACOSX - pthread_t signal_receiver_; -#endif }; +/* static */ PlatformData* +Sampler::AllocPlatformData(int aThreadId) +{ + return new PlatformData; +} -static void* SenderEntry(void* arg) { - Sampler::PlatformData* data = - reinterpret_cast(arg); - data->SignalSender(); +/* static */ void +Sampler::FreePlatformData(PlatformData* aData) +{ + delete aData; +} + +static void* SignalSender(void* arg) { + int vm_tgid_ = getpid(); + + while (SamplerRegistry::sampler->IsActive()) { + SamplerRegistry::sampler->HandleSaveRequest(); + + if (!SamplerRegistry::sampler->IsPaused()) { + std::vector threads = + SamplerRegistry::sampler->GetRegisteredThreads(); + + for (uint32_t i = 0; i < threads.size(); i++) { + ThreadInfo* info = threads[i]; + + // This will be null if we're not interested in profiling this thread. + if (!info->Profile()) + continue; + + // We use sCurrentThreadProfile the ThreadProfile for the + // thread we're profiling to the signal handler + sCurrentThreadProfile = info->Profile(); + + int threadId = info->ThreadId(); + + if (tgkill(vm_tgid_, threadId, SIGPROF) != 0) { + printf_stderr("profiler failed to signal tid=%d\n", threadId); +#ifdef DEBUG + abort(); +#endif + continue; + } + + // Wait for the signal handler to run before moving on to the next one + while (sCurrentThreadProfile) + sched_yield(); + } + } + + // Convert ms to us and subtract 100 us to compensate delays + // occuring during signal delivery. + // TODO measure and confirm this. + const useconds_t interval = + SamplerRegistry::sampler->interval() * 1000 - 100; + //int result = usleep(interval); + usleep(interval); + } return 0; } @@ -247,25 +242,25 @@ Sampler::Sampler(int interval, bool profiling, int entrySize) paused_(false), active_(false), entrySize_(entrySize) { - data_ = new PlatformData(this); } Sampler::~Sampler() { - ASSERT(!data_->signal_sender_launched_); - delete data_; + ASSERT(!signal_sender_launched_); } void Sampler::Start() { LOG("Sampler started"); + SamplerRegistry::AddActiveSampler(this); + // Request profiling signals. LOG("Request signal"); struct sigaction sa; sa.sa_sigaction = ProfilerSignalHandler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART | SA_SIGINFO; - if (sigaction(SIGPROF, &sa, &data_->old_sigprof_signal_handler_) != 0) { + if (sigaction(SIGPROF, &sa, &old_sigprof_signal_handler_) != 0) { LOG("Error installing signal"); return; } @@ -275,20 +270,20 @@ void Sampler::Start() { sa2.sa_sigaction = ProfilerSaveSignalHandler; sigemptyset(&sa2.sa_mask); sa2.sa_flags = SA_RESTART | SA_SIGINFO; - if (sigaction(SIGNAL_SAVE_PROFILE, &sa2, &data_->old_sigsave_signal_handler_) != 0) { + if (sigaction(SIGNAL_SAVE_PROFILE, &sa2, &old_sigsave_signal_handler_) != 0) { LOG("Error installing start signal"); return; } LOG("Signal installed"); - data_->signal_handler_installed_ = true; + signal_handler_installed_ = true; // Start a thread that sends SIGPROF signal to VM thread. // Sending the signal ourselves instead of relying on itimer provides // much better accuracy. SetActive(true); if (pthread_create( - &data_->signal_sender_thread_, NULL, SenderEntry, data_) == 0) { - data_->signal_sender_launched_ = true; + &signal_sender_thread_, NULL, SignalSender, NULL) == 0) { + signal_sender_launched_ = true; } LOG("Profiler thread started"); } @@ -299,16 +294,18 @@ void Sampler::Stop() { // Wait for signal sender termination (it will exit after setting // active_ to false). - if (data_->signal_sender_launched_) { - pthread_join(data_->signal_sender_thread_, NULL); - data_->signal_sender_launched_ = false; + if (signal_sender_launched_) { + pthread_join(signal_sender_thread_, NULL); + signal_sender_launched_ = false; } + SamplerRegistry::RemoveActiveSampler(this); + // Restore old signal handler - if (data_->signal_handler_installed_) { - sigaction(SIGNAL_SAVE_PROFILE, &data_->old_sigsave_signal_handler_, 0); - sigaction(SIGPROF, &data_->old_sigprof_signal_handler_, 0); - data_->signal_handler_installed_ = false; + if (signal_handler_installed_) { + sigaction(SIGNAL_SAVE_PROFILE, &old_sigsave_signal_handler_, 0); + sigaction(SIGPROF, &old_sigprof_signal_handler_, 0); + signal_handler_installed_ = false; } } @@ -316,15 +313,23 @@ bool Sampler::RegisterCurrentThread(const char* aName, PseudoStack* aPseudoStack { mozilla::MutexAutoLock lock(*sRegisteredThreadsMutex); - ThreadInfo* info = new ThreadInfo(aName, gettid(), aIsMainThread, aPseudoStack); + ThreadInfo* info = new ThreadInfo(aName, gettid(), + aIsMainThread, aPseudoStack); - if (sActiveSampler) { + bool profileThread = sActiveSampler && + (aIsMainThread || sActiveSampler->ProfileThreads()); + + if (profileThread) { // We need to create the ThreadProfile now info->SetProfile(new ThreadProfile(info->Name(), sActiveSampler->EntrySize(), info->Stack(), info->ThreadId(), + info->GetPlatformData(), aIsMainThread)); + if (sActiveSampler->ProfileJS()) { + info->Profile()->GetPseudoStack()->enableJSSampling(); + } } sRegisteredThreads->push_back(info); diff --git a/tools/profiler/platform-macos.cc b/tools/profiler/platform-macos.cc index 50945dc34fcd..43a66d84a34e 100644 --- a/tools/profiler/platform-macos.cc +++ b/tools/profiler/platform-macos.cc @@ -31,6 +31,7 @@ #include "nsThreadUtils.h" #include "platform.h" +#include "TableTicker.h" #include "UnwinderThread2.h" /* uwt__register_thread_for_profiling */ // this port is based off of v8 svn revision 9837 @@ -91,21 +92,13 @@ void OS::Sleep(int milliseconds) { usleep(1000 * milliseconds); } -class Thread::PlatformData : public Malloced { - public: - PlatformData() : thread_(kNoThread) {} - pthread_t thread_; // Thread handle for pthread. -}; - Thread::Thread(const char* name) - : data_(new PlatformData), - stack_size_(0) { + : stack_size_(0) { set_name(name); } Thread::~Thread() { - delete data_; } @@ -141,9 +134,9 @@ static void* ThreadEntry(void* arg) { } // END temp hack for SPS v1-vs-v2 - thread->data()->thread_ = pthread_self(); + thread->thread_ = pthread_self(); SetThreadName(thread->name()); - ASSERT(thread->data()->thread_ != kNoThread); + ASSERT(thread->thread_ != kNoThread); thread->Run(); return NULL; } @@ -163,15 +156,15 @@ void Thread::Start() { pthread_attr_setstacksize(&attr, static_cast(stack_size_)); attr_ptr = &attr; } - pthread_create(&data_->thread_, attr_ptr, ThreadEntry, this); - ASSERT(data_->thread_ != kNoThread); + pthread_create(&thread_, attr_ptr, ThreadEntry, this); + ASSERT(thread_ != kNoThread); } void Thread::Join() { - pthread_join(data_->thread_, NULL); + pthread_join(thread_, NULL); } -class Sampler::PlatformData : public Malloced { +class PlatformData : public Malloced { public: PlatformData() : profiled_thread_(mach_thread_self()) { @@ -197,6 +190,17 @@ class Sampler::PlatformData : public Malloced { pthread_t profiled_pthread_; }; +/* static */ PlatformData* +Sampler::AllocPlatformData(int aThreadId) +{ + return new PlatformData; +} + +/* static */ void +Sampler::FreePlatformData(PlatformData* aData) +{ + delete aData; +} class SamplerThread : public Thread { public: @@ -223,30 +227,35 @@ class SamplerThread : public Thread { SamplerRegistry::RemoveActiveSampler(sampler); delete instance_; instance_ = NULL; - /* - if (SamplerRegistry::GetState() == SamplerRegistry::HAS_NO_SAMPLERS) { - RuntimeProfiler::StopRuntimeProfilerThreadBeforeShutdown(instance_); - delete instance_; - instance_ = NULL; - } - */ } // Implement Thread::Run(). virtual void Run() { while (SamplerRegistry::sampler->IsActive()) { - if (!SamplerRegistry::sampler->IsPaused()) - SampleContext(SamplerRegistry::sampler); + std::vector threads = + SamplerRegistry::sampler->GetRegisteredThreads(); + for (uint32_t i = 0; i < threads.size(); i++) { + ThreadInfo* info = threads[i]; + + // This will be null if we're not interested in profiling this thread. + if (!info->Profile()) + continue; + + ThreadProfile* thread_profile = info->Profile(); + + if (!SamplerRegistry::sampler->IsPaused()) + SampleContext(SamplerRegistry::sampler, thread_profile); + } OS::Sleep(interval_); } } - void SampleContext(Sampler* sampler) { - thread_act_t profiled_thread = sampler->platform_data()->profiled_thread(); + void SampleContext(Sampler* sampler, ThreadProfile* thread_profile) { + thread_act_t profiled_thread = + thread_profile->GetPlatformData()->profiled_thread(); + TickSample sample_obj; TickSample* sample = &sample_obj; - //TickSample* sample = CpuProfiler::TickSampleEvent(sampler->isolate()); - //if (sample == NULL) sample = &sample_obj; if (KERN_SUCCESS != thread_suspend(profiled_thread)) return; @@ -276,13 +285,11 @@ class SamplerThread : public Thread { flavor, reinterpret_cast(&state), &count) == KERN_SUCCESS) { - //sample->state = sampler->isolate()->current_vm_state(); sample->pc = reinterpret_cast
(state.REGISTER_FIELD(ip)); sample->sp = reinterpret_cast
(state.REGISTER_FIELD(sp)); sample->fp = reinterpret_cast
(state.REGISTER_FIELD(bp)); sample->timestamp = mozilla::TimeStamp::Now(); - sample->threadProfile = NULL; - sampler->SampleStack(sample); + sample->threadProfile = thread_profile; sampler->Tick(sample); } thread_resume(profiled_thread); @@ -313,13 +320,11 @@ Sampler::Sampler(int interval, bool profiling, int entrySize) active_(false), entrySize_(entrySize) /*, samples_taken_(0)*/ { - data_ = new PlatformData; } Sampler::~Sampler() { ASSERT(!IsActive()); - delete data_; } @@ -337,27 +342,38 @@ void Sampler::Stop() { } pthread_t -Sampler::GetProfiledThread(Sampler::PlatformData* aData) +Sampler::GetProfiledThread(PlatformData* aData) { return aData->profiled_pthread(); } +#include +pid_t gettid() +{ + return (pid_t) syscall(SYS_thread_selfid); +} + bool Sampler::RegisterCurrentThread(const char* aName, PseudoStack* aPseudoStack, bool aIsMainThread) { mozilla::MutexAutoLock lock(*sRegisteredThreadsMutex); - if (!aIsMainThread) - return false; + ThreadInfo* info = new ThreadInfo(aName, gettid(), + aIsMainThread, aPseudoStack); - ThreadInfo* info = new ThreadInfo(aName, 0, true, aPseudoStack); + bool profileThread = sActiveSampler && + (aIsMainThread || sActiveSampler->ProfileThreads()); - if (sActiveSampler) { + if (profileThread) { // We need to create the ThreadProfile now info->SetProfile(new ThreadProfile(info->Name(), sActiveSampler->EntrySize(), info->Stack(), info->ThreadId(), - true)); + info->GetPlatformData(), + aIsMainThread)); + if (sActiveSampler->ProfileJS()) { + info->Profile()->GetPseudoStack()->enableJSSampling(); + } } sRegisteredThreads->push_back(info); @@ -366,5 +382,16 @@ bool Sampler::RegisterCurrentThread(const char* aName, PseudoStack* aPseudoStack void Sampler::UnregisterCurrentThread() { - // We only have the main thread currently and that will never be unregistered -} \ No newline at end of file + mozilla::MutexAutoLock lock(*sRegisteredThreadsMutex); + + int id = gettid(); + + for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { + ThreadInfo* info = sRegisteredThreads->at(i); + if (info->ThreadId() == id) { + delete info; + sRegisteredThreads->erase(sRegisteredThreads->begin() + i); + break; + } + } +} diff --git a/tools/profiler/platform-win32.cc b/tools/profiler/platform-win32.cc index 818b536b68f3..8406d39ba689 100644 --- a/tools/profiler/platform-win32.cc +++ b/tools/profiler/platform-win32.cc @@ -30,21 +30,21 @@ #include #include #include "platform.h" - +#include "TableTicker.h" #include "ProfileEntry.h" -class Sampler::PlatformData : public Malloced { +class PlatformData : public Malloced { public: // Get a handle to the calling thread. This is the thread that we are // going to profile. We need to make a copy of the handle because we are // going to use it in the sampler thread. Using GetThreadHandle() will // not work in this case. We're using OpenThread because DuplicateHandle // for some reason doesn't work in Chrome's sandbox. - PlatformData() : profiled_thread_(OpenThread(THREAD_GET_CONTEXT | + PlatformData(int aThreadId) : profiled_thread_(OpenThread(THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION, false, - GetCurrentThreadId())) {} + aThreadId)) {} ~PlatformData() { if (profiled_thread_ != NULL) { @@ -59,8 +59,20 @@ class Sampler::PlatformData : public Malloced { HANDLE profiled_thread_; }; +/* static */ PlatformData* +Sampler::AllocPlatformData(int aThreadId) +{ + return new PlatformData(aThreadId); +} + +/* static */ void +Sampler::FreePlatformData(PlatformData* aData) +{ + delete aData; +} + uintptr_t -Sampler::GetThreadHandle(Sampler::PlatformData* aData) +Sampler::GetThreadHandle(PlatformData* aData) { return (uintptr_t) aData->profiled_thread(); } @@ -97,8 +109,21 @@ class SamplerThread : public Thread { ::timeBeginPeriod(interval_); while (sampler_->IsActive()) { - if (!sampler_->IsPaused()) - SampleContext(sampler_); + std::vector threads = + sampler_->GetRegisteredThreads(); + for (uint32_t i = 0; i < threads.size(); i++) { + ThreadInfo* info = threads[i]; + + // This will be null if we're not interested in profiling this thread. + if (!info->Profile()) + continue; + + ThreadProfile* thread_profile = info->Profile(); + + if (!sampler_->IsPaused()) { + SampleContext(sampler_, thread_profile); + } + } OS::Sleep(interval_); } @@ -107,8 +132,10 @@ class SamplerThread : public Thread { ::timeEndPeriod(interval_); } - void SampleContext(Sampler* sampler) { - HANDLE profiled_thread = sampler->platform_data()->profiled_thread(); + void SampleContext(Sampler* sampler, ThreadProfile* thread_profile) { + uintptr_t thread = Sampler::GetThreadHandle( + thread_profile->GetPlatformData()); + HANDLE profiled_thread = reinterpret_cast(thread); if (profiled_thread == NULL) return; @@ -121,7 +148,7 @@ class SamplerThread : public Thread { // Grab the timestamp before pausing the thread, to avoid deadlocks. sample->timestamp = mozilla::TimeStamp::Now(); - sample->threadProfile = NULL; + sample->threadProfile = thread_profile; static const DWORD kSuspendFailed = static_cast(-1); if (SuspendThread(profiled_thread) == kSuspendFailed) @@ -139,7 +166,6 @@ class SamplerThread : public Thread { sample->fp = reinterpret_cast
(context.Ebp); #endif sample->context = &context; - sampler->SampleStack(sample); sampler->Tick(sample); } ResumeThread(profiled_thread); @@ -162,13 +188,11 @@ Sampler::Sampler(int interval, bool profiling, int entrySize) profiling_(profiling), paused_(false), active_(false), - entrySize_(entrySize), - data_(new PlatformData) { + entrySize_(entrySize) { } Sampler::~Sampler() { ASSERT(!IsActive()); - delete data_; } void Sampler::Start() { @@ -192,18 +216,11 @@ static unsigned int __stdcall ThreadEntry(void* arg) { return 0; } -class Thread::PlatformData : public Malloced { - public: - explicit PlatformData(HANDLE thread) : thread_(thread) {} - HANDLE thread_; - unsigned thread_id_; -}; - // Initialize a Win32 thread object. The thread has an invalid thread // handle until it is started. Thread::Thread(const char* name) : stack_size_(0) { - data_ = new PlatformData(kNoThread); + thread_ = kNoThread; set_name(name); } @@ -214,27 +231,26 @@ void Thread::set_name(const char* name) { // Close our own handle for the thread. Thread::~Thread() { - if (data_->thread_ != kNoThread) CloseHandle(data_->thread_); - delete data_; + if (thread_ != kNoThread) CloseHandle(thread_); } // Create a new thread. It is important to use _beginthreadex() instead of // the Win32 function CreateThread(), because the CreateThread() does not // initialize thread specific structures in the C runtime library. void Thread::Start() { - data_->thread_ = reinterpret_cast( + thread_ = reinterpret_cast( _beginthreadex(NULL, static_cast(stack_size_), ThreadEntry, this, 0, - &data_->thread_id_)); + &thread_id_)); } // Wait for thread to terminate. void Thread::Join() { - if (data_->thread_id_ != GetCurrentThreadId()) { - WaitForSingleObject(data_->thread_, INFINITE); + if (thread_id_ != GetCurrentThreadId()) { + WaitForSingleObject(thread_, INFINITE); } } @@ -246,18 +262,23 @@ bool Sampler::RegisterCurrentThread(const char* aName, PseudoStack* aPseudoStack { mozilla::MutexAutoLock lock(*sRegisteredThreadsMutex); - if (!aIsMainThread) - return false; + ThreadInfo* info = new ThreadInfo(aName, GetCurrentThreadId(), + aIsMainThread, aPseudoStack); - ThreadInfo* info = new ThreadInfo(aName, 0, true, aPseudoStack); + bool profileThread = sActiveSampler && + (aIsMainThread || sActiveSampler->ProfileThreads()); - if (sActiveSampler) { + if (profileThread) { // We need to create the ThreadProfile now info->SetProfile(new ThreadProfile(info->Name(), sActiveSampler->EntrySize(), info->Stack(), - info->ThreadId(), - true)); + GetCurrentThreadId(), + info->GetPlatformData(), + aIsMainThread)); + if (sActiveSampler->ProfileJS()) { + info->Profile()->GetPseudoStack()->enableJSSampling(); + } } sRegisteredThreads->push_back(info); @@ -266,5 +287,16 @@ bool Sampler::RegisterCurrentThread(const char* aName, PseudoStack* aPseudoStack void Sampler::UnregisterCurrentThread() { - // We only have the main thread currently and that will never be unregistered -} \ No newline at end of file + mozilla::MutexAutoLock lock(*sRegisteredThreadsMutex); + + int id = GetCurrentThreadId(); + + for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { + ThreadInfo* info = sRegisteredThreads->at(i); + if (info->ThreadId() == id) { + delete info; + sRegisteredThreads->erase(sRegisteredThreads->begin() + i); + break; + } + } +} diff --git a/tools/profiler/platform.cpp b/tools/profiler/platform.cpp index 27bbeb6df5b0..12ec83c07bfa 100644 --- a/tools/profiler/platform.cpp +++ b/tools/profiler/platform.cpp @@ -28,9 +28,10 @@ mozilla::ThreadLocal tlsTicker; // it as the flag itself. bool stack_key_initialized; -TimeStamp sLastTracerEvent; // is raced on -int sFrameNumber = 0; -int sLastFrameNumber = 0; +TimeStamp sLastTracerEvent; // is raced on +int sFrameNumber = 0; +int sLastFrameNumber = 0; +static bool sIsProfiling = false; // is raced on /* used to keep track of the last event that we sampled during */ unsigned int sLastSampledEventGeneration = 0; @@ -44,16 +45,18 @@ unsigned int sCurrentEventGeneration = 0; * a problem if 2^32 events happen between samples that we need * to know are associated with different events */ -std::vector* Sampler::sRegisteredThreads = new std::vector(); -mozilla::Mutex* Sampler::sRegisteredThreadsMutex = new mozilla::Mutex("sRegisteredThreads mutex"); +std::vector* Sampler::sRegisteredThreads = nullptr; +mozilla::Mutex* Sampler::sRegisteredThreadsMutex = nullptr; -Sampler* Sampler::sActiveSampler; +TableTicker* Sampler::sActiveSampler; ThreadInfo::~ThreadInfo() { free(mName); if (mProfile) delete mProfile; + + Sampler::FreePlatformData(mPlatformData); } bool sps_version2() @@ -235,9 +238,13 @@ void mozilla_sampler_init() } stack_key_initialized = true; + Sampler::Startup(); + PseudoStack *stack = new PseudoStack(); tlsPseudoStack.set(stack); + Sampler::RegisterCurrentThread("Gecko", stack, true); + if (sps_version2()) { // Read mode settings from MOZ_PROFILER_MODE and interval // settings from MOZ_PROFILER_INTERVAL and stack-scan threshhold @@ -306,9 +313,10 @@ void mozilla_sampler_shutdown() uwt__deinit(); } - Sampler::FreeRegisteredThreads(); - profiler_stop(); + + Sampler::Shutdown(); + // We can't delete the Stack because we can be between a // sampler call_enter/call_exit point. // TODO Need to find a safe time to delete Stack @@ -367,6 +375,7 @@ const char** mozilla_sampler_get_features() #endif "jank", "js", + "threads", NULL }; @@ -398,16 +407,28 @@ void mozilla_sampler_start(int aProfileEntries, int aInterval, if (sps_version2()) { t = new BreakpadSampler(aInterval ? aInterval : PROFILE_DEFAULT_INTERVAL, aProfileEntries ? aProfileEntries : PROFILE_DEFAULT_ENTRY, - stack, aFeatures, aFeatureCount); + aFeatures, aFeatureCount); } else { t = new TableTicker(aInterval ? aInterval : PROFILE_DEFAULT_INTERVAL, aProfileEntries ? aProfileEntries : PROFILE_DEFAULT_ENTRY, - stack, aFeatures, aFeatureCount); + aFeatures, aFeatureCount); } tlsTicker.set(t); t->Start(); - if (t->ProfileJS()) - stack->enableJSSampling(); + if (t->ProfileJS()) { + std::vector threads = t->GetRegisteredThreads(); + + for (uint32_t i = 0; i < threads.size(); i++) { + ThreadInfo* info = threads[i]; + ThreadProfile* thread_profile = info->Profile(); + if (!thread_profile) { + continue; + } + thread_profile->GetPseudoStack()->enableJSSampling(); + } + } + + sIsProfiling = true; nsCOMPtr os = mozilla::services::GetObserverService(); if (os) @@ -435,6 +456,8 @@ void mozilla_sampler_stop() if (disableJS) stack->disableJSSampling(); + sIsProfiling = false; + nsCOMPtr os = mozilla::services::GetObserverService(); if (os) os->NotifyObservers(nullptr, "profiler-stopped", nullptr); @@ -442,15 +465,7 @@ void mozilla_sampler_stop() bool mozilla_sampler_is_active() { - if (!stack_key_initialized) - profiler_init(); - - TableTicker *t = tlsTicker.get(); - if (!t) { - return false; - } - - return t->IsActive(); + return sIsProfiling; } static double sResponsivenessTimes[100]; diff --git a/tools/profiler/platform.h b/tools/profiler/platform.h index 8371927aa9d2..18086949ec31 100644 --- a/tools/profiler/platform.h +++ b/tools/profiler/platform.h @@ -35,14 +35,23 @@ #define __android_log_print(a, ...) #endif +#ifdef XP_UNIX +#include +#endif + #include "mozilla/StandardInteger.h" #include "mozilla/Util.h" #include "mozilla/unused.h" #include "mozilla/TimeStamp.h" +#include "mozilla/Mutex.h" #include "PlatformMacros.h" #include "v8-support.h" #include +#ifdef XP_WIN +#include +#endif + #define ASSERT(a) MOZ_ASSERT(a) #ifdef ANDROID @@ -183,14 +192,17 @@ class Thread { // prctl(). static const int kMaxThreadNameLength = 16; - class PlatformData; - PlatformData* data() { return data_; } +#ifdef XP_WIN + HANDLE thread_; + unsigned thread_id_; +#endif +#if defined(XP_MACOSX) + pthread_t thread_; +#endif private: void set_name(const char *name); - PlatformData* data_; - char name_[kMaxThreadNameLength]; int stack_size_; @@ -264,34 +276,9 @@ class TickSample { mozilla::TimeStamp timestamp; }; -class ThreadInfo { - public: - ThreadInfo(const char* aName, int aThreadId, bool aIsMainThread, PseudoStack* aPseudoStack) - : mName(strdup(aName)) - , mThreadId(aThreadId) - , mIsMainThread(aIsMainThread) - , mPseudoStack(aPseudoStack) - , mProfile(NULL) {} - - virtual ~ThreadInfo(); - - const char* Name() const { return mName; } - int ThreadId() const { return mThreadId; } - - bool IsMainThread() const { return mIsMainThread; } - PseudoStack* Stack() const { return mPseudoStack; } - - void SetProfile(ThreadProfile* aProfile) { mProfile = aProfile; } - ThreadProfile* Profile() const { return mProfile; } - - private: - char* mName; - int mThreadId; - const bool mIsMainThread; - PseudoStack* mPseudoStack; - ThreadProfile* mProfile; -}; - +class ThreadInfo; +class PlatformData; +class TableTicker; class Sampler { public: // Initialize sampler. @@ -300,9 +287,6 @@ class Sampler { int interval() const { return interval_; } - // 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; @@ -326,11 +310,14 @@ class Sampler { bool IsPaused() const { return paused_; } void SetPaused(bool value) { NoBarrier_Store(&paused_, value); } + virtual bool ProfileThreads() const = 0; + int EntrySize() { return entrySize_; } - class PlatformData; - - PlatformData* platform_data() { return data_; } + // We can't new/delete the type safely without defining it + // (-Wdelete-incomplete). Use these Alloc/Free functions instead. + static PlatformData* AllocPlatformData(int aThreadId); + static void FreePlatformData(PlatformData*); // If we move the backtracing code into the platform files we won't // need to have these hacks @@ -351,8 +338,13 @@ class Sampler { static bool RegisterCurrentThread(const char* aName, PseudoStack* aPseudoStack, bool aIsMainThread); static void UnregisterCurrentThread(); + static void Startup() { + sRegisteredThreads = new std::vector(); + sRegisteredThreadsMutex = new mozilla::Mutex("sRegisteredThreads mutex"); + } + // Should only be called on shutdown - static void FreeRegisteredThreads() { + static void Shutdown() { while (sRegisteredThreads->size() > 0) { sRegisteredThreads->pop_back(); } @@ -361,13 +353,13 @@ class Sampler { delete sRegisteredThreads; } - static Sampler* GetActiveSampler() { return sActiveSampler; } - static void SetActiveSampler(Sampler* sampler) { sActiveSampler = sampler; } + static TableTicker* GetActiveSampler() { return sActiveSampler; } + static void SetActiveSampler(TableTicker* sampler) { sActiveSampler = sampler; } protected: static std::vector* sRegisteredThreads; static mozilla::Mutex* sRegisteredThreadsMutex; - static Sampler* sActiveSampler; + static TableTicker* sActiveSampler; private: void SetActive(bool value) { NoBarrier_Store(&active_, value); } @@ -377,7 +369,46 @@ class Sampler { Atomic32 paused_; Atomic32 active_; const int entrySize_; - PlatformData* data_; // Platform specific data. + + // Refactor me! +#if defined(SPS_OS_linux) || defined(SPS_OS_android) + bool signal_handler_installed_; + struct sigaction old_sigprof_signal_handler_; + struct sigaction old_sigsave_signal_handler_; + bool signal_sender_launched_; + pthread_t signal_sender_thread_; +#endif +}; + +class ThreadInfo { + public: + ThreadInfo(const char* aName, int aThreadId, bool aIsMainThread, PseudoStack* aPseudoStack) + : mName(strdup(aName)) + , mThreadId(aThreadId) + , mIsMainThread(aIsMainThread) + , mPseudoStack(aPseudoStack) + , mPlatformData(Sampler::AllocPlatformData(aThreadId)) + , mProfile(NULL) {} + + virtual ~ThreadInfo(); + + const char* Name() const { return mName; } + int ThreadId() const { return mThreadId; } + + bool IsMainThread() const { return mIsMainThread; } + PseudoStack* Stack() const { return mPseudoStack; } + + void SetProfile(ThreadProfile* aProfile) { mProfile = aProfile; } + ThreadProfile* Profile() const { return mProfile; } + + PlatformData* GetPlatformData() const { return mPlatformData; } + private: + char* mName; + int mThreadId; + const bool mIsMainThread; + PseudoStack* mPseudoStack; + PlatformData* mPlatformData; + ThreadProfile* mProfile; }; #endif /* ndef TOOLS_PLATFORM_H_ */