diff options
Diffstat (limited to 'src/profiler.cc')
-rw-r--r-- | src/profiler.cc | 400 |
1 files changed, 120 insertions, 280 deletions
diff --git a/src/profiler.cc b/src/profiler.cc index 8675348..c51c7b2 100644 --- a/src/profiler.cc +++ b/src/profiler.cc @@ -58,15 +58,13 @@ typedef int ucontext_t; // just to quiet the compiler, mostly #include "base/spinlock.h" #include "base/sysinfo.h" /* for GetUniquePathFromEnv, etc */ #include "profiledata.h" +#include "profile-handler.h" #ifdef HAVE_CONFLICT_SIGNAL_H #include "conflict-signal.h" /* used on msvc machines */ #endif using std::string; -DEFINE_string(cpu_profile, "", - "Profile file name (used if CPUPROFILE env var not specified)"); - // Collects up all profile data. This is a singleton, which is // initialized by a constructor at startup. class CpuProfiler { @@ -87,94 +85,40 @@ class CpuProfiler { void GetCurrentState(ProfilerState* state); - // Register the current thread with the profiler. This should be - // called only once per thread. - // - // The profiler attempts to determine whether or not timers are - // shared by all threads in the process. (With LinuxThreads, and - // with NPTL on some Linux kernel versions, each thread has separate - // timers.) - // - // On systems which have a separate interval timer for each thread, - // this function starts the timer for the current thread. Profiling - // is disabled by ignoring the resulting signals, and enabled by - // setting their handler to be prof_handler. - // - // Prior to determining whether timers are shared, this function - // will unconditionally start the timer. However, if this function - // determines that timers are shared, then it will stop the timer if - // profiling is not currently enabled. - void RegisterThread(); - static CpuProfiler instance_; private: - static const int kMaxFrequency = 4000; // Largest allowed frequency - static const int kDefaultFrequency = 100; // Default frequency - - // Sample frequency, read-only after construction. - int frequency_; - - // These locks implement the locking requirements described in the - // ProfileData documentation, specifically: - // - // control_lock_ is held all over all collector_ method calls except for - // the 'Add' call made from the signal handler, to protect against - // concurrent use of collector_'s control routines. + // This lock implements the locking requirements described in the ProfileData + // documentation, specifically: // - // signal_lock_ is held over calls to 'Start', 'Stop', 'Flush', and - // 'Add', to protect against concurrent use of data collection and - // writing routines. Code other than the signal handler must disable - // the timer signal while holding signal_lock, to prevent deadlock. - // - // Locking order is control_lock_ first, and then signal_lock_. - // signal_lock_ is acquired by the prof_handler without first - // acquiring control_lock_. - SpinLock control_lock_; - SpinLock signal_lock_; + // lock_ is held all over all collector_ method calls except for the 'Add' + // call made from the signal handler, to protect against concurrent use of + // collector_'s control routines. Code other than signal handler must + // unregister the signal handler before calling any collector_ method. + // 'Add' method in the collector is protected by a guarantee from + // ProfileHandle that only one instance of prof_handler can run at a time. + SpinLock lock_; ProfileData collector_; - // Filter function and its argument, if any. (NULL means include - // all samples). Set at start, read-only while running. Written - // while holding both control_lock_ and signal_lock_, read and - // executed under signal_lock_. + // Filter function and its argument, if any. (NULL means include all + // samples). Set at start, read-only while running. Written while holding + // lock_, read and executed in the context of SIGPROF interrupt. int (*filter_)(void*); void* filter_arg_; - // Whether or not the threading system provides interval timers - // that are shared by all threads in a process. - enum { - TIMERS_UNTOUCHED, // No timer initialization attempted yet. - TIMERS_ONE_SET, // First thread has registered and set timer. - TIMERS_SHARED, // Timers are shared by all threads. - TIMERS_SEPARATE // Timers are separate in each thread. - } timer_sharing_; - - // Start the interval timer used for profiling. If the thread - // library shares timers between threads, this is used to enable and - // disable the timer when starting and stopping profiling. If - // timers are not shared, this is used to enable the timer in each - // thread. - void StartTimer(); - - // Stop the interval timer used for profiling. Used only if the - // thread library shares timers between threads. - void StopTimer(); - - // Returns true if the profiling interval timer enabled in the - // current thread. This actually checks the kernel's interval timer - // setting. (It is used to detect whether timers are shared or - // separate.) - bool IsTimerRunning(); - - // Sets the timer interrupt signal handler to one that stores the pc. - static void EnableHandler(); - - // Disables (ignores) the timer interrupt signal. - static void DisableHandler(); - - // Signale handler that records the interrupted pc in the profile data - static void prof_handler(int sig, siginfo_t*, void* signal_ucontext); + // Opague token returned by the profile handler. To be used when calling + // ProfileHandlerUnregisterCallback. + ProfileHandlerToken* prof_handler_token_; + + // Sets up a callback to receive SIGPROF interrupt. + void EnableHandler(); + + // Disables receiving SIGPROF interrupt. + void DisableHandler(); + + // Signal handler that records the interrupted pc in the profile data. + static void prof_handler(int sig, siginfo_t*, void* signal_ucontext, + void* cpu_profiler); }; // Profile data structure singleton: Constructor will check to see if @@ -184,25 +128,10 @@ CpuProfiler CpuProfiler::instance_; // Initialize profiling: activated if getenv("CPUPROFILE") exists. CpuProfiler::CpuProfiler() - : timer_sharing_(TIMERS_UNTOUCHED) { - // Get frequency of interrupts (if specified) - char junk; - const char* fr = getenv("CPUPROFILE_FREQUENCY"); - if (fr != NULL && (sscanf(fr, "%d%c", &frequency_, &junk) == 1) && - (frequency_ > 0)) { - // Limit to kMaxFrequency - frequency_ = (frequency_ > kMaxFrequency) ? kMaxFrequency : frequency_; - } else { - frequency_ = kDefaultFrequency; - } - - // Ignore signals until we decide to turn profiling on. (Paranoia; - // should already be ignored.) - DisableHandler(); - - RegisterThread(); - - // Should profiling be enabled automatically at start? + : prof_handler_token_(NULL) { + // TODO(cgd) Move this code *out* of the CpuProfile constructor into a + // separate object responsible for initialization. With ProfileHandler there + // is no need to limit the number of profilers. char fname[PATH_MAX]; if (!GetUniquePathFromEnv("CPUPROFILE", fname)) { return; @@ -219,41 +148,26 @@ CpuProfiler::CpuProfiler() } } -bool CpuProfiler::Start(const char* fname, - const ProfilerOptions* options) { - SpinLockHolder cl(&control_lock_); +bool CpuProfiler::Start(const char* fname, const ProfilerOptions* options) { + SpinLockHolder cl(&lock_); if (collector_.enabled()) { return false; } - { - // spin lock really is needed to protect init here, since it's - // conceivable that prof_handler may still be running from a - // previous profiler run. (For instance, if prof_handler just - // started, had not grabbed the spinlock, then was switched out, - // it might start again right now.) Any such late sample will be - // recorded against the new profile, but there's no harm in that. - SpinLockHolder sl(&signal_lock_); - - ProfileData::Options collector_options; - collector_options.set_frequency(frequency_); - if (!collector_.Start(fname, collector_options)) { - return false; - } - - filter_ = NULL; - if (options != NULL && options->filter_in_thread != NULL) { - filter_ = options->filter_in_thread; - filter_arg_ = options->filter_in_thread_arg; - } - - // Must unlock before setting prof_handler to avoid deadlock - // with signal delivered to this thread. + ProfileHandlerState prof_handler_state; + ProfileHandlerGetState(&prof_handler_state); + + ProfileData::Options collector_options; + collector_options.set_frequency(prof_handler_state.frequency); + if (!collector_.Start(fname, collector_options)) { + return false; } - if (timer_sharing_ == TIMERS_SHARED) { - StartTimer(); + filter_ = NULL; + if (options != NULL && options->filter_in_thread != NULL) { + filter_ = options->filter_in_thread; + filter_arg_ = options->filter_in_thread_arg; } // Setup handler for SIGPROF interrupts @@ -268,55 +182,48 @@ CpuProfiler::~CpuProfiler() { // Stop profiling and write out any collected profile data void CpuProfiler::Stop() { - SpinLockHolder cl(&control_lock_); + SpinLockHolder cl(&lock_); if (!collector_.enabled()) { return; } - // Ignore timer signals. Note that the handler may have just - // started and might not have taken signal_lock_ yet. Holding - // signal_lock_ below along with the semantics of collector_.Add() - // (which does nothing if collection is not enabled) prevents that - // late sample from causing a problem. + // Unregister prof_handler to stop receiving SIGPROF interrupts before + // stopping the collector. DisableHandler(); - if (timer_sharing_ == TIMERS_SHARED) { - StopTimer(); - } - - { - SpinLockHolder sl(&signal_lock_); - collector_.Stop(); - } + // DisableHandler waits for the currently running callback to complete and + // guarantees no future invocations. It is safe to stop the collector. + collector_.Stop(); } void CpuProfiler::FlushTable() { - SpinLockHolder cl(&control_lock_); + SpinLockHolder cl(&lock_); if (!collector_.enabled()) { return; } - // Disable timer signal while holding signal_lock_, to prevent deadlock - // if we take a timer signal while flushing. + // Unregister prof_handler to stop receiving SIGPROF interrupts before + // flushing the profile data. DisableHandler(); - { - SpinLockHolder sl(&signal_lock_); - collector_.FlushTable(); - } + + // DisableHandler waits for the currently running callback to complete and + // guarantees no future invocations. It is safe to flush the profile data. + collector_.FlushTable(); + EnableHandler(); } bool CpuProfiler::Enabled() { - SpinLockHolder cl(&control_lock_); + SpinLockHolder cl(&lock_); return collector_.enabled(); } void CpuProfiler::GetCurrentState(ProfilerState* state) { ProfileData::State collector_state; { - SpinLockHolder cl(&control_lock_); + SpinLockHolder cl(&lock_); collector_.GetCurrentState(&collector_state); } @@ -328,141 +235,56 @@ void CpuProfiler::GetCurrentState(ProfilerState* state) { state->profile_name[buf_size-1] = '\0'; } -void CpuProfiler::RegisterThread() { - SpinLockHolder cl(&control_lock_); - - // We try to detect whether timers are being shared by setting a - // timer in the first call to this function, then checking whether - // it's set in the second call. - // - // Note that this detection method requires that the first two calls - // to RegisterThread must be made from different threads. (Subsequent - // calls will see timer_sharing_ set to either TIMERS_SEPARATE or - // TIMERS_SHARED, and won't try to detect the timer sharing type.) - // - // Also note that if timer settings were inherited across new thread - // creation but *not* shared, this approach wouldn't work. That's - // not an issue for any Linux threading implementation, and should - // not be a problem for a POSIX-compliant threads implementation. - switch (timer_sharing_) { - case TIMERS_UNTOUCHED: - StartTimer(); - timer_sharing_ = TIMERS_ONE_SET; - break; - case TIMERS_ONE_SET: - // If the timer is running, that means that the main thread's - // timer setup is seen in this (second) thread -- and therefore - // that timers are shared. - if (IsTimerRunning()) { - timer_sharing_ = TIMERS_SHARED; - // If profiling has already been enabled, we have to keep the - // timer running. If not, we disable the timer here and - // re-enable it in start. - if (!collector_.enabled()) { - StopTimer(); - } - } else { - timer_sharing_ = TIMERS_SEPARATE; - StartTimer(); - } - break; - case TIMERS_SHARED: - // Nothing needed. - break; - case TIMERS_SEPARATE: - StartTimer(); - break; - } -} - -void CpuProfiler::StartTimer() { - // TODO: Randomize the initial interrupt value? - // TODO: Randomize the inter-interrupt period on every interrupt? - struct itimerval timer; - timer.it_interval.tv_sec = 0; - timer.it_interval.tv_usec = 1000000 / frequency_; - timer.it_value = timer.it_interval; - setitimer(ITIMER_PROF, &timer, 0); -} - -void CpuProfiler::StopTimer() { - struct itimerval timer; - memset(&timer, 0, sizeof timer); - setitimer(ITIMER_PROF, &timer, 0); -} - -bool CpuProfiler::IsTimerRunning() { - itimerval current_timer; - RAW_CHECK(0 == getitimer(ITIMER_PROF, ¤t_timer), "getitimer failed"); - return (current_timer.it_value.tv_sec != 0 || - current_timer.it_value.tv_usec != 0); -} - void CpuProfiler::EnableHandler() { - struct sigaction sa; - sa.sa_sigaction = prof_handler; - sa.sa_flags = SA_RESTART | SA_SIGINFO; - sigemptyset(&sa.sa_mask); - RAW_CHECK(sigaction(SIGPROF, &sa, NULL) == 0, "sigaction failed"); + RAW_CHECK(prof_handler_token_ == NULL, "SIGPROF handler already registered"); + prof_handler_token_ = ProfileHandlerRegisterCallback(prof_handler, this); + RAW_CHECK(prof_handler_token_ != NULL, "Failed to set up SIGPROF handler"); } void CpuProfiler::DisableHandler() { - struct sigaction sa; - sa.sa_handler = SIG_IGN; - sa.sa_flags = SA_RESTART; - sigemptyset(&sa.sa_mask); - RAW_CHECK(sigaction(SIGPROF, &sa, NULL) == 0, "sigaction failed"); + RAW_CHECK(prof_handler_token_ != NULL, "SIGPROF handler is not registered"); + ProfileHandlerUnregisterCallback(prof_handler_token_); + prof_handler_token_ = NULL; } -// Signal handler that records the pc in the profile-data structure -// -// NOTE: it is possible for profiling to be disabled just as this -// signal handler starts, before signal_lock_ is acquired. Therefore, -// collector_.Add must check whether profiling is enabled before -// trying to record any data. (See also comments in Start and Stop.) -void CpuProfiler::prof_handler(int sig, siginfo_t*, void* signal_ucontext) { - int saved_errno = errno; - - // Hold the spin lock while we're gathering the trace because there's - // no real harm in holding it and there's little point in releasing - // and re-acquiring it. (We'll only be blocking Start, Stop, and - // Flush.) We make sure to release it before restoring errno. - { - SpinLockHolder sl(&instance_.signal_lock_); - - if (instance_.filter_ == NULL || - (*instance_.filter_)(instance_.filter_arg_)) { - void* stack[ProfileData::kMaxStackDepth]; - - // The top-most active routine doesn't show up as a normal - // frame, but as the "pc" value in the signal handler context. - stack[0] = GetPC(*reinterpret_cast<ucontext_t*>(signal_ucontext)); - - // We skip the top two stack trace entries (this function and one - // signal handler frame) since they are artifacts of profiling and - // should not be measured. Other profiling related frames may be - // removed by "pprof" at analysis time. Instead of skipping the top - // frames, we could skip nothing, but that would increase the - // profile size unnecessarily. - int depth = GetStackTraceWithContext(stack + 1, arraysize(stack) - 1, - 2, signal_ucontext); - depth++; // To account for pc value in stack[0]; - - instance_.collector_.Add(depth, stack); - } +// Signal handler that records the pc in the profile-data structure. We do no +// synchronization here. profile-handler.cc guarantees that at most one +// instance of prof_handler() will run at a time. All other routines that +// access the data touched by prof_handler() disable this signal handler before +// accessing the data and therefore cannot execute concurrently with +// prof_handler(). +void CpuProfiler::prof_handler(int sig, siginfo_t*, void* signal_ucontext, + void* cpu_profiler) { + CpuProfiler* instance = static_cast<CpuProfiler*>(cpu_profiler); + + if (instance->filter_ == NULL || + (*instance->filter_)(instance->filter_arg_)) { + void* stack[ProfileData::kMaxStackDepth]; + + // The top-most active routine doesn't show up as a normal + // frame, but as the "pc" value in the signal handler context. + stack[0] = GetPC(*reinterpret_cast<ucontext_t*>(signal_ucontext)); + + // We skip the top two stack trace entries (this function and one + // signal handler frame) since they are artifacts of profiling and + // should not be measured. Other profiling related frames may be + // removed by "pprof" at analysis time. Instead of skipping the top + // frames, we could skip nothing, but that would increase the + // profile size unnecessarily. + int depth = GetStackTraceWithContext(stack + 1, arraysize(stack) - 1, + 2, signal_ucontext); + depth++; // To account for pc value in stack[0]; + + instance->collector_.Add(depth, stack); } - - errno = saved_errno; } +#if !(defined(__CYGWIN__) || defined(__CYGWIN32__)) + extern "C" void ProfilerRegisterThread() { - CpuProfiler::instance_.RegisterThread(); + ProfileHandlerRegisterThread(); } -// DEPRECATED routines -extern "C" void ProfilerEnable() { } -extern "C" void ProfilerDisable() { } - extern "C" void ProfilerFlush() { CpuProfiler::instance_.FlushTable(); } @@ -488,9 +310,27 @@ extern "C" void ProfilerGetCurrentState(ProfilerState* state) { CpuProfiler::instance_.GetCurrentState(state); } +#else // OS_CYGWIN -REGISTER_MODULE_INITIALIZER(profiler, { - if (!FLAGS_cpu_profile.empty()) { - ProfilerStart(FLAGS_cpu_profile.c_str()); - } -}); +// ITIMER_PROF doesn't work under cygwin. ITIMER_REAL is available, but doesn't +// work as well for profiling, and also interferes with alarm(). Because of +// these issues, unless a specific need is identified, profiler support is +// disabled under Cygwin. +extern "C" void ProfilerRegisterThread() { } +extern "C" void ProfilerFlush() { } +extern "C" int ProfilingIsEnabledForAllThreads() { return 0; } +extern "C" int ProfilerStart(const char* fname) { return 0; } +extern "C" int ProfilerStartWithOptions(const char *fname, + const ProfilerOptions *options) { + return 0; +} +extern "C" void ProfilerStop() { } +extern "C" void ProfilerGetCurrentState(ProfilerState* state) { + memset(state, 0, sizeof(*state)); +} + +#endif // OS_CYGWIN + +// DEPRECATED routines +extern "C" void ProfilerEnable() { } +extern "C" void ProfilerDisable() { } |