| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| // Date: 2015/03/06 18:34:03 |
| |
| #include <iostream> |
| #if __cplusplus >= 201103L |
| #include <condition_variable> |
| #endif |
| #include <gtest/gtest.h> |
| #include "butil/gperftools_profiler.h" |
| #include "bvar/utils/lock_timer.h" |
| |
| namespace { |
| struct DummyMutex {}; |
| } |
| |
| namespace std { |
| template <> |
| class lock_guard<DummyMutex> { |
| public: |
| lock_guard(DummyMutex&) {} |
| }; |
| |
| template <> |
| class unique_lock<DummyMutex> { |
| public: |
| unique_lock() {} |
| unique_lock(DummyMutex&) {} |
| template <typename T> |
| unique_lock(DummyMutex&, T) {} |
| bool try_lock() { return true; } |
| void lock() {} |
| void unlock() {} |
| }; |
| } // namespace std |
| |
| namespace { |
| using bvar::IntRecorder; |
| using bvar::LatencyRecorder; |
| using bvar::utils::MutexWithRecorder; |
| using bvar::utils::MutexWithLatencyRecorder; |
| |
| class LockTimerTest : public testing::Test { |
| }; |
| |
| #if __cplusplus >= 201103L |
| TEST_F(LockTimerTest, MutexWithRecorder) { |
| IntRecorder recorder; |
| MutexWithRecorder<std::mutex> mutex(recorder); |
| { |
| BAIDU_SCOPED_LOCK(mutex); |
| } |
| ASSERT_EQ(1u, recorder.get_value().num); |
| LOG(INFO) << recorder; |
| { |
| std::unique_lock<decltype(mutex) > lck(mutex); |
| lck.unlock(); |
| lck.lock(); |
| ASSERT_EQ(2u, recorder.get_value().num); |
| LOG(INFO) << recorder; |
| std::condition_variable cond; |
| cond.wait_for(lck, std::chrono::milliseconds(10)); |
| } |
| ASSERT_EQ(3u, recorder.get_value().num); |
| } |
| |
| TEST_F(LockTimerTest, MutexWithLatencyRecorder) { |
| LatencyRecorder recorder(10); |
| MutexWithLatencyRecorder<std::mutex> mutex(recorder); |
| { |
| BAIDU_SCOPED_LOCK(mutex); |
| } |
| ASSERT_EQ(1u, recorder.count()); |
| { |
| std::unique_lock<decltype(mutex) > lck(mutex); |
| lck.unlock(); |
| lck.lock(); |
| ASSERT_EQ(2u, recorder.count()); |
| LOG(INFO) << recorder; |
| std::condition_variable cond; |
| cond.wait_for(lck, std::chrono::milliseconds(10)); |
| } |
| ASSERT_EQ(3u, recorder.count()); |
| } |
| #endif |
| |
| TEST_F(LockTimerTest, pthread_mutex_and_cond) { |
| LatencyRecorder recorder(10); |
| MutexWithLatencyRecorder<pthread_mutex_t> mutex(recorder); |
| { |
| BAIDU_SCOPED_LOCK(mutex); |
| } |
| ASSERT_EQ(1u, recorder.count()); |
| { |
| std::unique_lock<MutexWithLatencyRecorder<pthread_mutex_t> > lck(mutex); |
| ASSERT_EQ(1u, recorder.count()); |
| timespec due_time = butil::milliseconds_from_now(10); |
| pthread_cond_t cond; |
| ASSERT_EQ(0, pthread_cond_init(&cond, NULL)); |
| pthread_cond_timedwait(&cond, &(pthread_mutex_t&)mutex, &due_time); |
| pthread_cond_timedwait(&cond, &mutex.mutex(), &due_time); |
| ASSERT_EQ(0, pthread_cond_destroy(&cond)); |
| } |
| ASSERT_EQ(2u, recorder.count()); |
| } |
| |
| const static size_t OPS_PER_THREAD = 1000; |
| |
| template <typename M> |
| void *signal_lock_thread(void *arg) { |
| M *m = (M*)arg; |
| for (size_t i = 0; i < OPS_PER_THREAD; ++i) { |
| { |
| std::unique_lock<M> lck(*m); |
| usleep(10); |
| } |
| } |
| return NULL; |
| } |
| |
| TEST_F(LockTimerTest, signal_lock_time) { |
| IntRecorder r0; |
| MutexWithRecorder<pthread_mutex_t> m0(r0); |
| pthread_t threads[4]; |
| for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { |
| ASSERT_EQ(0, pthread_create(&threads[i], NULL, |
| signal_lock_thread<MutexWithRecorder<pthread_mutex_t> >, &m0)); |
| } |
| for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { |
| pthread_join(threads[i], NULL); |
| } |
| LOG(INFO) << r0; |
| ASSERT_EQ(OPS_PER_THREAD * ARRAY_SIZE(threads), (size_t)r0.get_value().num); |
| LatencyRecorder r1; |
| MutexWithLatencyRecorder<pthread_mutex_t> m1(r1); |
| for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { |
| ASSERT_EQ(0, pthread_create(&threads[i], NULL, |
| signal_lock_thread<MutexWithLatencyRecorder<pthread_mutex_t> >, &m1)); |
| } |
| for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { |
| pthread_join(threads[i], NULL); |
| } |
| LOG(INFO) << r1._latency; |
| ASSERT_EQ(OPS_PER_THREAD * ARRAY_SIZE(threads), (size_t)r1.count()); |
| } |
| |
| template <typename M0, typename M1> |
| struct DoubleLockArg { |
| M0 m0; |
| M1 m1; |
| }; |
| |
| template <typename M0, typename M1> |
| void *double_lock_thread(void *arg) { |
| DoubleLockArg<M0, M1>* dla = (DoubleLockArg<M0, M1>*)arg; |
| for (size_t i = 0; i < OPS_PER_THREAD; ++i) { |
| std::unique_lock<M0> lck0(dla->m0, std::defer_lock); |
| std::unique_lock<M1> lck1(dla->m1, std::defer_lock); |
| butil::double_lock(lck0, lck1); |
| usleep(10); |
| } |
| return NULL; |
| } |
| |
| TEST_F(LockTimerTest, double_lock_time) { |
| typedef MutexWithRecorder<pthread_mutex_t> M0; |
| typedef MutexWithLatencyRecorder<pthread_mutex_t> M1; |
| DoubleLockArg<M0, M1> arg; |
| IntRecorder r0; |
| LatencyRecorder r1; |
| arg.m0.set_recorder(r0); |
| arg.m1.set_recorder(r1); |
| pthread_t threads[4]; |
| for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { |
| ASSERT_EQ(0, pthread_create(&threads[i], NULL, |
| double_lock_thread<M0, M1>, &arg)); |
| } |
| for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { |
| pthread_join(threads[i], NULL); |
| } |
| ASSERT_EQ(OPS_PER_THREAD * ARRAY_SIZE(threads), (size_t)r0.get_value().num); |
| ASSERT_EQ(OPS_PER_THREAD * ARRAY_SIZE(threads), (size_t)r1.count()); |
| LOG(INFO) << r0; |
| LOG(INFO) << r1._latency; |
| r0.reset(); |
| r1._latency.reset(); |
| DoubleLockArg<M1, M0> arg1; |
| arg1.m0.set_recorder(r1); |
| arg1.m1.set_recorder(r0); |
| for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { |
| ASSERT_EQ(0, pthread_create(&threads[i], NULL, |
| double_lock_thread<M1, M0>, &arg1)); |
| } |
| for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) { |
| pthread_join(threads[i], NULL); |
| } |
| ASSERT_EQ(OPS_PER_THREAD * ARRAY_SIZE(threads), (size_t)r0.get_value().num); |
| ASSERT_EQ(OPS_PER_THREAD * ARRAY_SIZE(threads), (size_t)r1.count()); |
| LOG(INFO) << r0; |
| LOG(INFO) << r1._latency; |
| } |
| |
| TEST_F(LockTimerTest, overhead) { |
| LatencyRecorder r0; |
| MutexWithLatencyRecorder<DummyMutex> m0(r0); |
| butil::Timer timer; |
| const size_t N = 1000 * 1000 * 10; |
| |
| ProfilerStart("mutex_with_latency_recorder.prof"); |
| timer.start(); |
| for (size_t i = 0; i < N; ++i) { |
| BAIDU_SCOPED_LOCK(m0); |
| } |
| timer.stop(); |
| ProfilerStop(); |
| LOG(INFO) << "The overhead of MutexWithLatencyRecorder is " |
| << timer.n_elapsed() / N << "ns"; |
| |
| IntRecorder r1; |
| MutexWithRecorder<DummyMutex> m1(r1); |
| ProfilerStart("mutex_with_recorder.prof"); |
| timer.start(); |
| for (size_t i = 0; i < N; ++i) { |
| BAIDU_SCOPED_LOCK(m1); |
| } |
| timer.stop(); |
| ProfilerStop(); |
| LOG(INFO) << "The overhead of MutexWithRecorder is " |
| << timer.n_elapsed() / N << "ns"; |
| MutexWithRecorder<DummyMutex> m2; |
| ProfilerStart("mutex_with_timer.prof"); |
| timer.start(); |
| for (size_t i = 0; i < N; ++i) { |
| BAIDU_SCOPED_LOCK(m2); |
| } |
| timer.stop(); |
| ProfilerStop(); |
| LOG(INFO) << "The overhead of timer is " |
| << timer.n_elapsed() / N << "ns"; |
| } |
| } // namespace |