blob: 3531bef8285e081657edb03174ccbf59fba9431e [file] [log] [blame]
// -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*-
// Copyright 2009 Google Inc. All Rights Reserved.
// Author: Nabeel Mian (nabeelmian@google.com)
// Chris Demetriou (cgd@google.com)
//
// Use of this source code is governed by a BSD-style license that can
// be found in the LICENSE file.
//
//
// This file contains the unit tests for profile-handler.h interface.
#include "config_for_unittests.h"
#include "profile-handler.h"
#include <atomic>
#include <assert.h>
#include <pthread.h>
#include <sys/time.h>
#include <stdint.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");
// In order to cover fix for github issue at:
// https://github.com/gperftools/gperftools/issues/412 we override
// operators new/delete to simulate condition where another thread is
// having malloc lock and making sure that profiler handler can
// unregister callbacks without deadlocking. Thus this
// "infrastructure" below.
namespace {
std::atomic<uint64_t> allocate_count;
std::atomic<uint64_t> free_count;
// We also "frob" this lock down in BusyThread.
Mutex allocate_lock;
void* do_allocate(size_t sz) {
MutexLock h(&allocate_lock);
allocate_count++;
return malloc(sz);
}
void do_free(void* p) {
MutexLock h(&allocate_lock);
free_count++;
free(p);
}
} // namespace
void* operator new(size_t sz) { return do_allocate(sz); }
void* operator new[](size_t sz) { return do_allocate(sz); }
void operator delete(void* p) { do_free(p); }
void operator delete[](void* p) { do_free(p); }
void operator delete(void* p, size_t sz) { do_free(p); }
void operator delete[](void* p, size_t sz) { do_free(p); }
void* operator new(size_t sz, const std::nothrow_t& nt) { return do_allocate(sz); }
void* operator new[](size_t sz, const std::nothrow_t& nt) { return do_allocate(sz); };
void operator delete(void* p, const std::nothrow_t& nt) { do_free(p); }
void operator delete[](void* p, const std::nothrow_t& nt) { do_free(p); }
namespace {
// TODO(csilvers): error-checking on the pthreads routines
class Thread {
public:
Thread() : joinable_(false) { }
virtual ~Thread() { }
void SetJoinable(bool value) { joinable_ = value; }
void Start() {
pthread_attr_t attr;
pthread_attr_init(&attr);
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) {
Thread* self = static_cast<Thread*>(cls);
if (!self->joinable_) {
CHECK_EQ(0, pthread_detach(pthread_self()));
}
ProfileHandlerRegisterThread();
self->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;
static bool linux_per_thread_timers_mode_ = false;
static int timer_type_ = ITIMER_PROF;
// 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_, &current_timer));
return (current_timer.it_interval.tv_sec != 0 ||
current_timer.it_interval.tv_usec != 0);
}
// 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. We also stump on allocate_lock to
// verify that perf handler re/unre-gistration doesn't deadlock with
// malloc locks.
void Run() {
for (;;) {
MutexLock h(&allocate_lock);
for (int i = 1000; i > 0; i--) {
if (stop_work()) {
return;
}
(void)*(const_cast<volatile bool*>(&stop_work_));
}
}
}
};
class NullThread : public Thread {
private:
void Run() {
}
};
// 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 the timer type.
static void SetUpTestCase() {
timer_type_ = (getenv("CPUPROFILE_REALTIME") ? ITIMER_REAL : ITIMER_PROF);
#if HAVE_LINUX_SIGEV_THREAD_ID
linux_per_thread_timers_mode_ = (getenv("CPUPROFILE_PER_THREAD_TIMERS") != NULL);
const char *signal_number = getenv("CPUPROFILE_TIMER_SIGNAL");
if (signal_number) {
//signal_number_ = strtol(signal_number, NULL, 0);
linux_per_thread_timers_mode_ = true;
Delay(kTimerResetInterval);
}
#endif
}
// 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 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.
// 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 and stops the timer.
ProfileHandlerReset();
EXPECT_EQ(0, GetCallbackCount());
VerifyDisabled();
// Start worker to accumulate cpu usage.
StartWorker();
}
virtual void TearDown() {
ProfileHandlerReset();
// Stops the worker thread.
StopWorker();
}
// 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_;
}
// 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, linux_per_thread_timers_mode_ || IsTimerEnabled());
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, timer should be disabled.
if (GetCallbackCount() == 0) {
EXPECT_FALSE(IsTimerEnabled());
}
}
// Verifies that the timer is disabled. Expects the worker to be running.
void VerifyDisabled() {
// Check that the callback count is 0.
EXPECT_EQ(0, GetCallbackCount());
// Check that the timer is disabled.
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) {
allocate_count.store(0);
free_count.store(0);
ProfileHandlerUnregisterCallback(token);
Delay(kTimerResetInterval);
CHECK(free_count.load() > 0);
}
// 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 = 0;
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 = 0;
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 timers is correctly disabled.
if (!linux_per_thread_timers_mode_) VerifyDisabled();
}
// Verifies ProfileHandlerReset
TEST_F(ProfileHandlerTest, Reset) {
// Verify that the profile timer interrupt is disabled.
if (!linux_per_thread_timers_mode_) VerifyDisabled();
int first_tick_count = 0;
RegisterCallback(&first_tick_count);
VerifyRegistration(first_tick_count);
EXPECT_EQ(1, GetCallbackCount());
// Register second callback.
int second_tick_count = 0;
RegisterCallback(&second_tick_count);
VerifyRegistration(second_tick_count);
EXPECT_EQ(2, GetCallbackCount());
// Reset the profile handler and verify that callback were correctly
// unregistered and the timer is disabled.
ProfileHandlerReset();
VerifyUnregistration(first_tick_count);
VerifyUnregistration(second_tick_count);
if (!linux_per_thread_timers_mode_) 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 and stop the timer.
ProfileHandlerReset();
EXPECT_EQ(0, GetCallbackCount());
VerifyDisabled();
// Start the worker.
StartWorker();
// Register a callback and check that profile ticks are being delivered and
// the timer is enabled.
int tick_count = 0;
RegisterCallback(&tick_count);
EXPECT_EQ(1, GetCallbackCount());
VerifyRegistration(tick_count);
EXPECT_EQ(FLAGS_test_profiler_enabled, linux_per_thread_timers_mode_ || IsTimerEnabled());
}
} // namespace
int main(int argc, char** argv) {
return ProfileHandlerTest::RUN_ALL_TESTS();
}