summaryrefslogtreecommitdiff
path: root/src/profiler.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/profiler.cc')
-rw-r--r--src/profiler.cc400
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, &current_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() { }