// 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 "gperftools_helper.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;
#if !WITH_BABYLON_COUNTER
    // reset() of IntRecorder with babylon counter does not work,
    // should never call reset().
    r0.reset();
    r1._latency.reset();
#endif // !WITH_BABYLON_COUNTER
    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);
    }
#if !WITH_BABYLON_COUNTER
    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());
#else
    ASSERT_EQ(OPS_PER_THREAD * ARRAY_SIZE(threads) * 2, (size_t)r0.get_value().num);
    ASSERT_EQ(OPS_PER_THREAD * ARRAY_SIZE(threads) * 2, (size_t)r1.count());
#endif // !WITH_BABYLON_COUNTER
    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
