mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-30 05:15:59 +02:00
507 lines
16 KiB
C++
507 lines
16 KiB
C++
![]() |
// Copyright 2009 Google Inc. All Rights Reserved.
|
||
|
// Author: Nabeel Mian (nabeelmian@google.com)
|
||
|
// Chris Demetriou (cgd@google.com)
|
||
|
//
|
||
|
// This file contains the unit tests for profile-handler.h interface.
|
||
|
//
|
||
|
// It is linked into three separate unit tests:
|
||
|
// profile-handler_unittest tests basic functionality
|
||
|
// profile-handler_disable_test tests that the profiler
|
||
|
// is disabled with --install_signal_handlers=false
|
||
|
// profile-handler_conflict_test tests that the profiler
|
||
|
// is disabled when a SIGPROF handler is registered before InitGoogle.
|
||
|
|
||
|
#include "config.h"
|
||
|
#include "profile-handler.h"
|
||
|
|
||
|
#include <assert.h>
|
||
|
#include <pthread.h>
|
||
|
#include <sys/time.h>
|
||
|
#include <time.h>
|
||
|
#include "base/logging.h"
|
||
|
#include "base/simple_mutex.h"
|
||
|
|
||
|
// Some helpful macros for the test class
|
||
|
#define TEST_F(cls, fn) void cls :: fn()
|
||
|
|
||
|
// Do we expect the profiler to be enabled?
|
||
|
DEFINE_bool(test_profiler_enabled, true,
|
||
|
"expect profiler to be enabled during tests");
|
||
|
|
||
|
// Should we look at the kernel signal handler settings during the test?
|
||
|
// Not if we're in conflict_test, because we can't distinguish its nop
|
||
|
// handler from the real one.
|
||
|
DEFINE_bool(test_profiler_signal_handler, true,
|
||
|
"check profiler signal handler during tests");
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
// TODO(csilvers): error-checking on the pthreads routines
|
||
|
class Thread {
|
||
|
public:
|
||
|
Thread() : joinable_(false) { }
|
||
|
void SetJoinable(bool value) { joinable_ = value; }
|
||
|
void Start() {
|
||
|
pthread_attr_t attr;
|
||
|
pthread_attr_init(&attr);
|
||
|
pthread_attr_setdetachstate(&attr, joinable_ ? PTHREAD_CREATE_JOINABLE
|
||
|
: PTHREAD_CREATE_DETACHED);
|
||
|
pthread_create(&thread_, &attr, &DoRun, this);
|
||
|
pthread_attr_destroy(&attr);
|
||
|
}
|
||
|
void Join() {
|
||
|
assert(joinable_);
|
||
|
pthread_join(thread_, NULL);
|
||
|
}
|
||
|
virtual void Run() = 0;
|
||
|
private:
|
||
|
static void* DoRun(void* cls) {
|
||
|
ProfileHandlerRegisterThread();
|
||
|
reinterpret_cast<Thread*>(cls)->Run();
|
||
|
return NULL;
|
||
|
}
|
||
|
pthread_t thread_;
|
||
|
bool joinable_;
|
||
|
};
|
||
|
|
||
|
// Sleep interval in nano secs. ITIMER_PROF goes off only afer the specified CPU
|
||
|
// time is consumed. Under heavy load this process may no get scheduled in a
|
||
|
// timely fashion. Therefore, give enough time (20x of ProfileHandle timer
|
||
|
// interval 10ms (100Hz)) for this process to accumulate enought CPU time to get
|
||
|
// a profile tick.
|
||
|
int kSleepInterval = 200000000;
|
||
|
|
||
|
// Sleep interval in nano secs. To ensure that if the timer has expired it is
|
||
|
// reset.
|
||
|
int kTimerResetInterval = 5000000;
|
||
|
|
||
|
// Whether each thread has separate timers.
|
||
|
static bool timer_separate_ = false;
|
||
|
static int timer_type_ = ITIMER_PROF;
|
||
|
static int signal_number_ = SIGPROF;
|
||
|
|
||
|
// Delays processing by the specified number of nano seconds. 'delay_ns'
|
||
|
// must be less than the number of nano seconds in a second (1000000000).
|
||
|
void Delay(int delay_ns) {
|
||
|
static const int kNumNSecInSecond = 1000000000;
|
||
|
EXPECT_LT(delay_ns, kNumNSecInSecond);
|
||
|
struct timespec delay = { 0, delay_ns };
|
||
|
nanosleep(&delay, 0);
|
||
|
}
|
||
|
|
||
|
// Checks whether the profile timer is enabled for the current thread.
|
||
|
bool IsTimerEnabled() {
|
||
|
itimerval current_timer;
|
||
|
EXPECT_EQ(0, getitimer(timer_type_, ¤t_timer));
|
||
|
if ((current_timer.it_value.tv_sec == 0) &&
|
||
|
(current_timer.it_value.tv_usec != 0)) {
|
||
|
// May be the timer has expired. Sleep for a bit and check again.
|
||
|
Delay(kTimerResetInterval);
|
||
|
EXPECT_EQ(0, getitimer(timer_type_, ¤t_timer));
|
||
|
}
|
||
|
return (current_timer.it_value.tv_sec != 0 ||
|
||
|
current_timer.it_value.tv_usec != 0);
|
||
|
}
|
||
|
|
||
|
class VirtualTimerGetterThread : public Thread {
|
||
|
public:
|
||
|
VirtualTimerGetterThread() {
|
||
|
memset(&virtual_timer_, 0, sizeof virtual_timer_);
|
||
|
}
|
||
|
struct itimerval virtual_timer_;
|
||
|
|
||
|
private:
|
||
|
void Run() {
|
||
|
CHECK_EQ(0, getitimer(ITIMER_VIRTUAL, &virtual_timer_));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// This function checks whether the timers are shared between thread. This
|
||
|
// function spawns a thread, so use it carefully when testing thread-dependent
|
||
|
// behaviour.
|
||
|
static bool threads_have_separate_timers() {
|
||
|
struct itimerval new_timer_val;
|
||
|
|
||
|
// Enable the virtual timer in the current thread.
|
||
|
memset(&new_timer_val, 0, sizeof new_timer_val);
|
||
|
new_timer_val.it_value.tv_sec = 1000000; // seconds
|
||
|
CHECK_EQ(0, setitimer(ITIMER_VIRTUAL, &new_timer_val, NULL));
|
||
|
|
||
|
// Spawn a thread, get the virtual timer's value there.
|
||
|
VirtualTimerGetterThread thread;
|
||
|
thread.SetJoinable(true);
|
||
|
thread.Start();
|
||
|
thread.Join();
|
||
|
|
||
|
// Disable timer here.
|
||
|
memset(&new_timer_val, 0, sizeof new_timer_val);
|
||
|
CHECK_EQ(0, setitimer(ITIMER_VIRTUAL, &new_timer_val, NULL));
|
||
|
|
||
|
bool target_timer_enabled = (thread.virtual_timer_.it_value.tv_sec != 0 ||
|
||
|
thread.virtual_timer_.it_value.tv_usec != 0);
|
||
|
if (!target_timer_enabled) {
|
||
|
LOG(INFO, "threads have separate timers");
|
||
|
return true;
|
||
|
} else {
|
||
|
LOG(INFO, "threads have shared timers");
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Dummy worker thread to accumulate cpu time.
|
||
|
class BusyThread : public Thread {
|
||
|
public:
|
||
|
BusyThread() : stop_work_(false) {
|
||
|
}
|
||
|
|
||
|
// Setter/Getters
|
||
|
bool stop_work() {
|
||
|
MutexLock lock(&mu_);
|
||
|
return stop_work_;
|
||
|
}
|
||
|
void set_stop_work(bool stop_work) {
|
||
|
MutexLock lock(&mu_);
|
||
|
stop_work_ = stop_work;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
// Protects stop_work_ below.
|
||
|
Mutex mu_;
|
||
|
// Whether to stop work?
|
||
|
bool stop_work_;
|
||
|
|
||
|
// Do work until asked to stop.
|
||
|
void Run() {
|
||
|
while (!stop_work()) {
|
||
|
}
|
||
|
// If timers are separate, check that timer is enabled for this thread.
|
||
|
EXPECT_TRUE(!timer_separate_ || IsTimerEnabled());
|
||
|
}
|
||
|
};
|
||
|
|
||
|
class NullThread : public Thread {
|
||
|
private:
|
||
|
void Run() {
|
||
|
// If timers are separate, check that timer is enabled for this thread.
|
||
|
EXPECT_TRUE(!timer_separate_ || IsTimerEnabled());
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Signal handler which tracks the profile timer ticks.
|
||
|
static void TickCounter(int sig, siginfo_t* sig_info, void *vuc,
|
||
|
void* tick_counter) {
|
||
|
int* counter = static_cast<int*>(tick_counter);
|
||
|
++(*counter);
|
||
|
}
|
||
|
|
||
|
// This class tests the profile-handler.h interface.
|
||
|
class ProfileHandlerTest {
|
||
|
protected:
|
||
|
|
||
|
// Determines whether threads have separate timers.
|
||
|
static void SetUpTestCase() {
|
||
|
timer_type_ = (getenv("CPUPROFILE_REALTIME") ? ITIMER_REAL : ITIMER_PROF);
|
||
|
signal_number_ = (getenv("CPUPROFILE_REALTIME") ? SIGALRM : SIGPROF);
|
||
|
|
||
|
timer_separate_ = threads_have_separate_timers();
|
||
|
Delay(kTimerResetInterval);
|
||
|
}
|
||
|
|
||
|
// Sets up the profile timers and SIGPROF/SIGALRM handler in a known state.
|
||
|
// It does the following:
|
||
|
// 1. Unregisters all the callbacks, stops the timer (if shared) and
|
||
|
// clears out timer_sharing state in the ProfileHandler. This clears
|
||
|
// out any state left behind by the previous test or during module
|
||
|
// initialization when the test program was started.
|
||
|
// 2. Spawns two threads which will be registered with the ProfileHandler.
|
||
|
// At this time ProfileHandler knows if the timers are shared.
|
||
|
// 3. Starts a busy worker thread to accumulate CPU usage.
|
||
|
virtual void SetUp() {
|
||
|
// Reset the state of ProfileHandler between each test. This unregisters
|
||
|
// all callbacks, stops timer (if shared) and clears timer sharing state.
|
||
|
ProfileHandlerReset();
|
||
|
EXPECT_EQ(0, GetCallbackCount());
|
||
|
VerifyDisabled();
|
||
|
// ProfileHandler requires at least two threads to be registerd to determine
|
||
|
// whether timers are shared.
|
||
|
RegisterThread();
|
||
|
RegisterThread();
|
||
|
// Now that two threads are started, verify that the signal handler is
|
||
|
// disabled and the timers are correctly enabled/disabled.
|
||
|
VerifyDisabled();
|
||
|
// Start worker to accumulate cpu usage.
|
||
|
StartWorker();
|
||
|
}
|
||
|
|
||
|
virtual void TearDown() {
|
||
|
ProfileHandlerReset();
|
||
|
// Stops the worker thread.
|
||
|
StopWorker();
|
||
|
}
|
||
|
|
||
|
// Starts a no-op thread that gets registered with the ProfileHandler. Waits
|
||
|
// for the thread to stop.
|
||
|
void RegisterThread() {
|
||
|
NullThread t;
|
||
|
t.SetJoinable(true);
|
||
|
t.Start();
|
||
|
t.Join();
|
||
|
}
|
||
|
|
||
|
// Starts a busy worker thread to accumulate cpu time. There should be only
|
||
|
// one busy worker running. This is required for the case where there are
|
||
|
// separate timers for each thread.
|
||
|
void StartWorker() {
|
||
|
busy_worker_ = new BusyThread();
|
||
|
busy_worker_->SetJoinable(true);
|
||
|
busy_worker_->Start();
|
||
|
// Wait for worker to start up and register with the ProfileHandler.
|
||
|
// TODO(nabeelmian) This may not work under very heavy load.
|
||
|
Delay(kSleepInterval);
|
||
|
}
|
||
|
|
||
|
// Stops the worker thread.
|
||
|
void StopWorker() {
|
||
|
busy_worker_->set_stop_work(true);
|
||
|
busy_worker_->Join();
|
||
|
delete busy_worker_;
|
||
|
}
|
||
|
|
||
|
// Checks whether SIGPROF/SIGALRM signal handler is enabled.
|
||
|
bool IsSignalEnabled() {
|
||
|
struct sigaction sa;
|
||
|
CHECK_EQ(sigaction(signal_number_, NULL, &sa), 0);
|
||
|
return ((sa.sa_handler == SIG_IGN) || (sa.sa_handler == SIG_DFL)) ?
|
||
|
false : true;
|
||
|
}
|
||
|
|
||
|
// Gets the number of callbacks registered with the ProfileHandler.
|
||
|
uint32 GetCallbackCount() {
|
||
|
ProfileHandlerState state;
|
||
|
ProfileHandlerGetState(&state);
|
||
|
return state.callback_count;
|
||
|
}
|
||
|
|
||
|
// Gets the current ProfileHandler interrupt count.
|
||
|
uint64 GetInterruptCount() {
|
||
|
ProfileHandlerState state;
|
||
|
ProfileHandlerGetState(&state);
|
||
|
return state.interrupts;
|
||
|
}
|
||
|
|
||
|
// Verifies that a callback is correctly registered and receiving
|
||
|
// profile ticks.
|
||
|
void VerifyRegistration(const int& tick_counter) {
|
||
|
// Check the callback count.
|
||
|
EXPECT_GT(GetCallbackCount(), 0);
|
||
|
// Check that the profile timer is enabled.
|
||
|
EXPECT_EQ(FLAGS_test_profiler_enabled, IsTimerEnabled());
|
||
|
// Check that the signal handler is enabled.
|
||
|
if (FLAGS_test_profiler_signal_handler) {
|
||
|
EXPECT_EQ(FLAGS_test_profiler_enabled, IsSignalEnabled());
|
||
|
}
|
||
|
uint64 interrupts_before = GetInterruptCount();
|
||
|
// Sleep for a bit and check that tick counter is making progress.
|
||
|
int old_tick_count = tick_counter;
|
||
|
Delay(kSleepInterval);
|
||
|
int new_tick_count = tick_counter;
|
||
|
uint64 interrupts_after = GetInterruptCount();
|
||
|
if (FLAGS_test_profiler_enabled) {
|
||
|
EXPECT_GT(new_tick_count, old_tick_count);
|
||
|
EXPECT_GT(interrupts_after, interrupts_before);
|
||
|
} else {
|
||
|
EXPECT_EQ(new_tick_count, old_tick_count);
|
||
|
EXPECT_EQ(interrupts_after, interrupts_before);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Verifies that a callback is not receiving profile ticks.
|
||
|
void VerifyUnregistration(const int& tick_counter) {
|
||
|
// Sleep for a bit and check that tick counter is not making progress.
|
||
|
int old_tick_count = tick_counter;
|
||
|
Delay(kSleepInterval);
|
||
|
int new_tick_count = tick_counter;
|
||
|
EXPECT_EQ(old_tick_count, new_tick_count);
|
||
|
// If no callbacks, signal handler and shared timer should be disabled.
|
||
|
if (GetCallbackCount() == 0) {
|
||
|
if (FLAGS_test_profiler_signal_handler) {
|
||
|
EXPECT_FALSE(IsSignalEnabled());
|
||
|
}
|
||
|
if (timer_separate_) {
|
||
|
EXPECT_TRUE(IsTimerEnabled());
|
||
|
} else {
|
||
|
EXPECT_FALSE(IsTimerEnabled());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Verifies that the SIGPROF/SIGALRM interrupt handler is disabled and the
|
||
|
// timer, if shared, is disabled. Expects the worker to be running.
|
||
|
void VerifyDisabled() {
|
||
|
// Check that the signal handler is disabled.
|
||
|
if (FLAGS_test_profiler_signal_handler) {
|
||
|
EXPECT_FALSE(IsSignalEnabled());
|
||
|
}
|
||
|
// Check that the callback count is 0.
|
||
|
EXPECT_EQ(0, GetCallbackCount());
|
||
|
// Check that the timer is disabled if shared, enabled otherwise.
|
||
|
if (timer_separate_) {
|
||
|
EXPECT_TRUE(IsTimerEnabled());
|
||
|
} else {
|
||
|
EXPECT_FALSE(IsTimerEnabled());
|
||
|
}
|
||
|
// Verify that the ProfileHandler is not accumulating profile ticks.
|
||
|
uint64 interrupts_before = GetInterruptCount();
|
||
|
Delay(kSleepInterval);
|
||
|
uint64 interrupts_after = GetInterruptCount();
|
||
|
EXPECT_EQ(interrupts_before, interrupts_after);
|
||
|
}
|
||
|
|
||
|
// Registers a callback and waits for kTimerResetInterval for timers to get
|
||
|
// reset.
|
||
|
ProfileHandlerToken* RegisterCallback(void* callback_arg) {
|
||
|
ProfileHandlerToken* token = ProfileHandlerRegisterCallback(
|
||
|
TickCounter, callback_arg);
|
||
|
Delay(kTimerResetInterval);
|
||
|
return token;
|
||
|
}
|
||
|
|
||
|
// Unregisters a callback and waits for kTimerResetInterval for timers to get
|
||
|
// reset.
|
||
|
void UnregisterCallback(ProfileHandlerToken* token) {
|
||
|
ProfileHandlerUnregisterCallback(token);
|
||
|
Delay(kTimerResetInterval);
|
||
|
}
|
||
|
|
||
|
// Busy worker thread to accumulate cpu usage.
|
||
|
BusyThread* busy_worker_;
|
||
|
|
||
|
private:
|
||
|
// The tests to run
|
||
|
void RegisterUnregisterCallback();
|
||
|
void MultipleCallbacks();
|
||
|
void Reset();
|
||
|
void RegisterCallbackBeforeThread();
|
||
|
|
||
|
public:
|
||
|
#define RUN(test) do { \
|
||
|
printf("Running %s\n", #test); \
|
||
|
ProfileHandlerTest pht; \
|
||
|
pht.SetUp(); \
|
||
|
pht.test(); \
|
||
|
pht.TearDown(); \
|
||
|
} while (0)
|
||
|
|
||
|
static int RUN_ALL_TESTS() {
|
||
|
SetUpTestCase();
|
||
|
RUN(RegisterUnregisterCallback);
|
||
|
RUN(MultipleCallbacks);
|
||
|
RUN(Reset);
|
||
|
RUN(RegisterCallbackBeforeThread);
|
||
|
printf("Done\n");
|
||
|
return 0;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Verifies ProfileHandlerRegisterCallback and
|
||
|
// ProfileHandlerUnregisterCallback.
|
||
|
TEST_F(ProfileHandlerTest, RegisterUnregisterCallback) {
|
||
|
int tick_count = 0;
|
||
|
ProfileHandlerToken* token = RegisterCallback(&tick_count);
|
||
|
VerifyRegistration(tick_count);
|
||
|
UnregisterCallback(token);
|
||
|
VerifyUnregistration(tick_count);
|
||
|
}
|
||
|
|
||
|
// Verifies that multiple callbacks can be registered.
|
||
|
TEST_F(ProfileHandlerTest, MultipleCallbacks) {
|
||
|
// Register first callback.
|
||
|
int first_tick_count;
|
||
|
ProfileHandlerToken* token1 = RegisterCallback(&first_tick_count);
|
||
|
// Check that callback was registered correctly.
|
||
|
VerifyRegistration(first_tick_count);
|
||
|
EXPECT_EQ(1, GetCallbackCount());
|
||
|
|
||
|
// Register second callback.
|
||
|
int second_tick_count;
|
||
|
ProfileHandlerToken* token2 = RegisterCallback(&second_tick_count);
|
||
|
// Check that callback was registered correctly.
|
||
|
VerifyRegistration(second_tick_count);
|
||
|
EXPECT_EQ(2, GetCallbackCount());
|
||
|
|
||
|
// Unregister first callback.
|
||
|
UnregisterCallback(token1);
|
||
|
VerifyUnregistration(first_tick_count);
|
||
|
EXPECT_EQ(1, GetCallbackCount());
|
||
|
// Verify that second callback is still registered.
|
||
|
VerifyRegistration(second_tick_count);
|
||
|
|
||
|
// Unregister second callback.
|
||
|
UnregisterCallback(token2);
|
||
|
VerifyUnregistration(second_tick_count);
|
||
|
EXPECT_EQ(0, GetCallbackCount());
|
||
|
|
||
|
// Verify that the signal handler and timers are correctly disabled.
|
||
|
VerifyDisabled();
|
||
|
}
|
||
|
|
||
|
// Verifies ProfileHandlerReset
|
||
|
TEST_F(ProfileHandlerTest, Reset) {
|
||
|
// Verify that the profile timer interrupt is disabled.
|
||
|
VerifyDisabled();
|
||
|
int first_tick_count;
|
||
|
RegisterCallback(&first_tick_count);
|
||
|
VerifyRegistration(first_tick_count);
|
||
|
EXPECT_EQ(1, GetCallbackCount());
|
||
|
|
||
|
// Register second callback.
|
||
|
int second_tick_count;
|
||
|
RegisterCallback(&second_tick_count);
|
||
|
VerifyRegistration(second_tick_count);
|
||
|
EXPECT_EQ(2, GetCallbackCount());
|
||
|
|
||
|
// Reset the profile handler and verify that callback were correctly
|
||
|
// unregistered and timer/signal are disabled.
|
||
|
ProfileHandlerReset();
|
||
|
VerifyUnregistration(first_tick_count);
|
||
|
VerifyUnregistration(second_tick_count);
|
||
|
VerifyDisabled();
|
||
|
}
|
||
|
|
||
|
// Verifies that ProfileHandler correctly handles a case where a callback was
|
||
|
// registered before the second thread started.
|
||
|
TEST_F(ProfileHandlerTest, RegisterCallbackBeforeThread) {
|
||
|
// Stop the worker.
|
||
|
StopWorker();
|
||
|
// Unregister all existing callbacks, stop the timer (if shared), disable
|
||
|
// the signal handler and reset the timer sharing state in the Profile
|
||
|
// Handler.
|
||
|
ProfileHandlerReset();
|
||
|
EXPECT_EQ(0, GetCallbackCount());
|
||
|
VerifyDisabled();
|
||
|
|
||
|
// Start the worker. At this time ProfileHandler doesn't know if timers are
|
||
|
// shared as only one thread has registered so far.
|
||
|
StartWorker();
|
||
|
// Register a callback and check that profile ticks are being delivered.
|
||
|
int tick_count;
|
||
|
RegisterCallback(&tick_count);
|
||
|
EXPECT_EQ(1, GetCallbackCount());
|
||
|
VerifyRegistration(tick_count);
|
||
|
|
||
|
// Register a second thread and verify that timer and signal handler are
|
||
|
// correctly enabled.
|
||
|
RegisterThread();
|
||
|
EXPECT_EQ(1, GetCallbackCount());
|
||
|
EXPECT_EQ(FLAGS_test_profiler_enabled, IsTimerEnabled());
|
||
|
if (FLAGS_test_profiler_signal_handler) {
|
||
|
EXPECT_EQ(FLAGS_test_profiler_enabled, IsSignalEnabled());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
int main(int argc, char** argv) {
|
||
|
return ProfileHandlerTest::RUN_ALL_TESTS();
|
||
|
}
|