brpc/lib/include/bvar/utils/lock_timer.h
2022-12-14 19:05:52 +08:00

434 lines
13 KiB
C++

// 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 17:13:17
#ifndef BVAR_LOCK_TIMER_H
#define BVAR_LOCK_TIMER_H
#include "butil/time.h" // butil::Timer
#include "butil/scoped_lock.h" // std::lock_guard std::unique_lock
#include "butil/macros.h" // DISALLOW_COPY_AND_ASSIGN
#include "bvar/recorder.h" // IntRecorder
#include "bvar/latency_recorder.h" // LatencyRecorder
// Monitor the time for acquiring a lock.
// We provide some wrappers of mutex which can also be maintained by
// std::lock_guard and std::unique_lock to record the time it takes to wait for
// the acquisition of the very mutex (in microsecond) except for the contention
// caused by condition variables which unlock the mutex before waiting and lock
// the same mutex after waking up.
//
// About Performance:
// The utilities are designed and implemented to be suitable to measure the
// mutex from all the common scenarios. Saying that you can use them freely
// without concerning about the overhead. Except that the mutex is very
// frequently acquired (>1M/s) with very little contention, in which case, the
// overhead of timers and bvars is noticable.
//
// There are two kinds of special Mutex:
// - MutexWithRecorder: Create a mutex along with a shared IntRecorder which
// only records the average latency from intialization
// - MutexWithLatencyRecorder: Create a mutex along with a shared
// LatencyRecorder which also provides percentile
// calculation, time window management besides
// IntRecorder.
//
// Examples:
// #include "bvar/utils/lock_timer.h"
//
// bvar::LatencyRecorder
// g_mutex_contention("my_mutex_contention");
// // ^^^
// // you can replace this with a meaningful name
//
// typedef ::bvar::MutexWithLatencyRecorder<pthread_mutex_t> my_mutex_t;
// // ^^^
// // you can use std::mutex (since c++11)
// // or bthread_mutex_t (in bthread)
//
// // Define the mutex
// my_mutex_t mutex(g_mutex_contention);
//
// // Use it with std::lock_guard
// void critical_routine_with_lock_guard() {
// std::lock_guard<my_mutex_t> guard(mutex);
// // ^^^
// // Or you can use BAIDU_SCOPED_LOCK(mutex) to make it simple
// ...
// doing something inside the critical section
// ...
// // and |mutex| is auto unlocked and this contention is recorded out of
// // the scope
// }
//
// // Use it with unique_lock
// void critical_routine_with_unique_lock() {
// std::unique_lock<my_mutex_t> lck(mutex);
// std::condition_variable cond; // available since C++11
// ...
// doing something inside the critical section
// ...
// cond.wait(lck); // It's ok if my_mutex_t is defined with the template
// // parameter being std::mutex
// ...
// doing other things when come back into the critical section
// ...
// // and |mutex| is auto unlocked and this contention is recorded out of
// // the scope
// }
namespace bvar {
namespace utils {
// To be compatible with the old version
using namespace ::bvar;
} // namespace utils
} // namespace bvar
namespace bvar {
// Specialize MutexConstructor and MutexDestructor for the Non-RAII mutexes such
// as pthread_mutex_t
template <typename Mutex>
struct MutexConstructor {
bool operator()(Mutex*) const { return true; }
};
template <typename Mutex>
struct MutexDestructor {
bool operator()(Mutex*) const { return true; }
};
// Specialize for pthread_mutex_t
template <>
struct MutexConstructor<pthread_mutex_t> {
bool operator()(pthread_mutex_t* mutex) const {
#ifndef NDEBUG
const int rc = pthread_mutex_init(mutex, NULL);
CHECK_EQ(0, rc) << "Fail to init pthread_mutex, " << berror(rc);
return rc == 0;
#else
return pthread_mutex_init(mutex, NULL) == 0;
#endif
}
};
template <>
struct MutexDestructor<pthread_mutex_t> {
bool operator()(pthread_mutex_t* mutex) const {
#ifndef NDEBUG
const int rc = pthread_mutex_destroy(mutex);
CHECK_EQ(0, rc) << "Fail to destroy pthread_mutex, " << berror(rc);
return rc == 0;
#else
return pthread_mutex_destroy(mutex) == 0;
#endif
}
};
namespace detail {
template <typename Mutex, typename Recorder,
typename MCtor, typename MDtor>
class MutexWithRecorderBase {
DISALLOW_COPY_AND_ASSIGN(MutexWithRecorderBase);
public:
typedef Mutex mutex_type;
typedef Recorder recorder_type;
typedef MutexWithRecorderBase<Mutex, Recorder,
MCtor, MDtor> self_type;
explicit MutexWithRecorderBase(recorder_type &recorder)
: _recorder(&recorder) {
MCtor()(&_mutex);
}
MutexWithRecorderBase() : _recorder(NULL) {
MCtor()(&_mutex);
}
~MutexWithRecorderBase() {
MDtor()(&_mutex);
}
void set_recorder(recorder_type& recorder) {
_recorder = &recorder;
}
mutex_type& mutex() { return _mutex; }
operator mutex_type&() { return _mutex; }
template <typename T>
self_type& operator<<(T value) {
if (_recorder) {
*_recorder << value;
}
return *this;
}
private:
mutex_type _mutex;
// We don't own _recorder. Make sure it is valid before the destruction of
// this instance
recorder_type *_recorder;
};
template <typename Mutex>
class LockGuardBase {
DISALLOW_COPY_AND_ASSIGN(LockGuardBase);
public:
LockGuardBase(Mutex& m)
: _timer(m), _lock_guard(m.mutex()) {
_timer.timer.stop();
}
private:
// This trick makes the recoding happens after the destructor of _lock_guard
struct TimerAndMutex {
TimerAndMutex(Mutex &m)
: timer(butil::Timer::STARTED), mutex(&m) {}
~TimerAndMutex() {
*mutex << timer.u_elapsed();
}
butil::Timer timer;
Mutex* mutex;
};
// Don't change the order of the fields as the implementation depends on
// the order of the constructors and destructors
TimerAndMutex _timer;
std::lock_guard<typename Mutex::mutex_type> _lock_guard;
};
template <typename Mutex>
class UniqueLockBase {
DISALLOW_COPY_AND_ASSIGN(UniqueLockBase);
public:
typedef Mutex mutex_type;
explicit UniqueLockBase(mutex_type& mutex)
: _timer(butil::Timer::STARTED), _lock(mutex.mutex()),
_mutex(&mutex) {
_timer.stop();
}
UniqueLockBase(mutex_type& mutex, std::defer_lock_t defer_lock)
: _timer(), _lock(mutex.mutex(), defer_lock), _mutex(&mutex) {
}
UniqueLockBase(mutex_type& mutex, std::try_to_lock_t try_to_lock)
: _timer(butil::Timer::STARTED)
, _lock(mutex.mutex(), try_to_lock)
, _mutex(&mutex) {
_timer.stop();
if (!owns_lock()) {
*_mutex << _timer.u_elapsed();
}
}
~UniqueLockBase() {
if (_lock.owns_lock()) {
unlock();
}
}
operator std::unique_lock<typename Mutex::mutex_type>&() { return _lock; }
void lock() {
_timer.start();
_lock.lock();
_timer.stop();
}
bool try_lock() {
_timer.start();
const bool rc = _lock.try_lock();
_timer.stop();
if (!rc) {
_mutex->recorder() << _timer.u_elapsed();
}
return rc;
}
void unlock() {
_lock.unlock();
// Recorde the time out of the critical section
*_mutex << _timer.u_elapsed();
}
mutex_type* release() {
if (_lock.owns_lock()) {
// We have to recorde this time in the critical section owtherwise
// the event will be lost
*_mutex << _timer.u_elapsed();
}
mutex_type* saved_mutex = _mutex;
_mutex = NULL;
_lock.release();
return saved_mutex;
}
mutex_type* mutex() { return _mutex; }
bool owns_lock() const { return _lock.owns_lock(); }
operator bool() const { return static_cast<bool>(_lock); }
#if __cplusplus >= 201103L
template <class Rep, class Period>
bool try_lock_for(
const std::chrono::duration<Rep, Period>& timeout_duration) {
_timer.start();
const bool rc = _lock.try_lock_for(timeout_duration);
_timer.stop();
if (!rc) {
*_mutex << _timer.u_elapsed();
}
return rc;
}
template <class Clock, class Duration>
bool try_lock_until(
const std::chrono::time_point<Clock,Duration>& timeout_time ) {
_timer.start();
const bool rc = _lock.try_lock_until(timeout_time);
_timer.stop();
if (!rc) {
// Out of the criticle section. Otherwise the time will be recorded
// in unlock
*_mutex << _timer.u_elapsed();
}
return rc;
}
#endif
private:
// Don't change the order or timer and _lck;
butil::Timer _timer;
std::unique_lock<typename Mutex::mutex_type> _lock;
mutex_type* _mutex;
};
} // namespace detail
// Wappers of Mutex along with a shared LatencyRecorder
template <typename Mutex>
struct MutexWithRecorder
: public detail::MutexWithRecorderBase<
Mutex, IntRecorder,
MutexConstructor<Mutex>, MutexDestructor<Mutex> > {
typedef detail::MutexWithRecorderBase<
Mutex, IntRecorder,
MutexConstructor<Mutex>, MutexDestructor<Mutex> > Base;
explicit MutexWithRecorder(IntRecorder& recorder)
: Base(recorder)
{}
MutexWithRecorder() : Base() {}
};
// Wappers of Mutex along with a shared LatencyRecorder
template <typename Mutex>
struct MutexWithLatencyRecorder
: public detail::MutexWithRecorderBase<
Mutex, LatencyRecorder,
MutexConstructor<Mutex>, MutexDestructor<Mutex> > {
typedef detail::MutexWithRecorderBase<
Mutex, LatencyRecorder,
MutexConstructor<Mutex>, MutexDestructor<Mutex> > Base;
explicit MutexWithLatencyRecorder(LatencyRecorder& recorder)
: Base(recorder)
{}
MutexWithLatencyRecorder() : Base() {}
};
} // namespace bvar
namespace std {
// Specialize lock_guard and unique_lock
template <typename Mutex>
class lock_guard<bvar::MutexWithRecorder<Mutex> >
: public ::bvar::detail::
LockGuardBase< ::bvar::MutexWithRecorder<Mutex> > {
public:
typedef ::bvar::detail::
LockGuardBase<bvar::MutexWithRecorder<Mutex> > Base;
explicit lock_guard(::bvar::MutexWithRecorder<Mutex> &mutex)
: Base(mutex)
{}
};
template <typename Mutex>
class lock_guard<bvar::MutexWithLatencyRecorder<Mutex> >
: public ::bvar::detail::
LockGuardBase< ::bvar::MutexWithLatencyRecorder<Mutex> > {
public:
typedef ::bvar::detail::
LockGuardBase<bvar::MutexWithLatencyRecorder<Mutex> > Base;
explicit lock_guard(::bvar::MutexWithLatencyRecorder<Mutex> &mutex)
: Base(mutex)
{}
};
template <typename Mutex>
class unique_lock<bvar::MutexWithRecorder<Mutex> >
: public ::bvar::detail::
UniqueLockBase< ::bvar::MutexWithRecorder<Mutex> > {
public:
typedef ::bvar::detail::
UniqueLockBase< ::bvar::MutexWithRecorder<Mutex> > Base;
typedef typename Base::mutex_type mutex_type;
explicit unique_lock(mutex_type& mutex)
: Base(mutex)
{}
unique_lock(mutex_type& mutex, std::defer_lock_t defer_lock)
: Base(mutex, defer_lock)
{}
unique_lock(mutex_type& mutex, std::try_to_lock_t try_to_lock)
: Base(mutex, try_to_lock)
{}
};
template <typename Mutex>
class unique_lock<bvar::MutexWithLatencyRecorder<Mutex> >
: public ::bvar::detail::
UniqueLockBase< ::bvar::MutexWithLatencyRecorder<Mutex> > {
public:
typedef ::bvar::detail::
UniqueLockBase< ::bvar::MutexWithLatencyRecorder<Mutex> > Base;
typedef typename Base::mutex_type mutex_type;
explicit unique_lock(mutex_type& mutex)
: Base(mutex)
{}
unique_lock(mutex_type& mutex, std::defer_lock_t defer_lock)
: Base(mutex, defer_lock)
{}
unique_lock(mutex_type& mutex, std::try_to_lock_t try_to_lock)
: Base(mutex, try_to_lock)
{}
};
} // namespace std
#endif // BVAR_LOCK_TIMER_H