Timer utilities (#1526)
* Timer utilities * Add tests, fix enum class and better enable_if usage
This commit is contained in:
parent
4dc668286d
commit
6736141d84
5 changed files with 338 additions and 15 deletions
|
|
@ -16,6 +16,7 @@ add_executable (core_test
|
|||
peer_container.cpp
|
||||
rpc.cpp
|
||||
signing.cpp
|
||||
timer.cpp
|
||||
uint256_union.cpp
|
||||
versioning.cpp
|
||||
wallet.cpp
|
||||
|
|
|
|||
70
nano/core_test/timer.cpp
Normal file
70
nano/core_test/timer.cpp
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
#include <chrono>
|
||||
#include <gtest/gtest.h>
|
||||
#include <nano/core_test/testutil.hpp>
|
||||
#include <nano/lib/timer.hpp>
|
||||
#include <thread>
|
||||
|
||||
/* Tests for the timer utility. Note that we use sleep_for in the tests, which
|
||||
sleeps for *at least* the given amount. We thus allow for some leeway in the
|
||||
upper bound checks (also because CI is often very slow) */
|
||||
|
||||
TEST (timer, states)
|
||||
{
|
||||
nano::timer<std::chrono::milliseconds> t1;
|
||||
ASSERT_EQ (t1.current_state (), nano::timer_state::stopped);
|
||||
t1.start ();
|
||||
ASSERT_EQ (t1.current_state (), nano::timer_state::started);
|
||||
t1.restart ();
|
||||
ASSERT_EQ (t1.current_state (), nano::timer_state::started);
|
||||
t1.pause ();
|
||||
ASSERT_EQ (t1.current_state (), nano::timer_state::stopped);
|
||||
t1.start ();
|
||||
ASSERT_EQ (t1.current_state (), nano::timer_state::started);
|
||||
t1.stop ();
|
||||
ASSERT_EQ (t1.current_state (), nano::timer_state::stopped);
|
||||
|
||||
nano::timer<std::chrono::milliseconds> t2 (nano::timer_state::started);
|
||||
ASSERT_EQ (t2.current_state (), nano::timer_state::started);
|
||||
t2.stop ();
|
||||
ASSERT_EQ (t2.current_state (), nano::timer_state::stopped);
|
||||
}
|
||||
|
||||
TEST (timer, measure_and_compare)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
nano::timer<std::chrono::milliseconds> t1 (nano::timer_state::started);
|
||||
ASSERT_EQ (t1.current_state (), nano::timer_state::started);
|
||||
std::this_thread::sleep_for (50ms);
|
||||
ASSERT_TRUE (t1.after_deadline (30ms));
|
||||
ASSERT_TRUE (t1.before_deadline (500ms));
|
||||
ASSERT_LT (t1.since_start (), 200ms);
|
||||
ASSERT_GT (t1.since_start (), 10ms);
|
||||
ASSERT_GE (t1.stop (), 50ms);
|
||||
}
|
||||
|
||||
TEST (timer, cummulative_child)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
nano::timer<std::chrono::milliseconds> t1 (nano::timer_state::started);
|
||||
auto & child1 = t1.child ();
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
child1.start ();
|
||||
std::this_thread::sleep_for (5ms);
|
||||
child1.pause ();
|
||||
}
|
||||
ASSERT_GE (child1.value (), 50ms);
|
||||
ASSERT_LT (child1.value (), 500ms);
|
||||
|
||||
auto & child2 = t1.child ();
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
child2.start ();
|
||||
std::this_thread::sleep_for (5ms);
|
||||
child2.pause ();
|
||||
}
|
||||
ASSERT_GE (child2.value (), 50ms);
|
||||
ASSERT_LT (child2.value (), 500ms);
|
||||
|
||||
ASSERT_GT (t1.stop (), 100ms);
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ add_library (nano_lib
|
|||
interface.h
|
||||
numbers.cpp
|
||||
numbers.hpp
|
||||
timer.hpp
|
||||
utility.cpp
|
||||
utility.hpp
|
||||
work.hpp
|
||||
|
|
|
|||
257
nano/lib/timer.hpp
Normal file
257
nano/lib/timer.hpp
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
#pragma once
|
||||
|
||||
namespace nano
|
||||
{
|
||||
enum class timer_state
|
||||
{
|
||||
started,
|
||||
stopped
|
||||
};
|
||||
|
||||
/** Timer utility with nesting support */
|
||||
template <typename UNIT = std::chrono::milliseconds>
|
||||
class timer
|
||||
{
|
||||
public:
|
||||
timer () = default;
|
||||
|
||||
timer (nano::timer_state state_a, std::string description_a = "timer") :
|
||||
desc (description_a)
|
||||
{
|
||||
if (state_a == nano::timer_state::started)
|
||||
{
|
||||
start ();
|
||||
}
|
||||
}
|
||||
|
||||
timer (std::string description_a) :
|
||||
desc (description_a)
|
||||
{
|
||||
}
|
||||
|
||||
timer (std::string description_a, timer * parent_a) :
|
||||
desc (description_a), parent (parent_a)
|
||||
{
|
||||
}
|
||||
|
||||
/** Do not output if measured time is below the time units threshold in \p minimum_a */
|
||||
timer & set_minimum (UNIT minimum_a)
|
||||
{
|
||||
minimum = minimum_a;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a child timer without starting it.
|
||||
* Since the timing API needs to have low overhead, this function
|
||||
* does not check if a timer with the same name already exists.
|
||||
*/
|
||||
timer & child (std::string description_a = "child timer")
|
||||
{
|
||||
children.emplace_back (description_a, this);
|
||||
return children.back ();
|
||||
}
|
||||
|
||||
/** Create and start a child timer */
|
||||
timer & start_child (std::string description_a = "child timer")
|
||||
{
|
||||
auto & child_timer = child (description_a);
|
||||
child_timer.start ();
|
||||
return child_timer;
|
||||
}
|
||||
|
||||
/** Start the timer. This will assert if the timer is already started. */
|
||||
void start ()
|
||||
{
|
||||
assert (state == nano::timer_state::stopped);
|
||||
state = nano::timer_state::started;
|
||||
begin = std::chrono::high_resolution_clock::now ();
|
||||
}
|
||||
|
||||
/** Restarts the timer */
|
||||
void restart ()
|
||||
{
|
||||
state = nano::timer_state::started;
|
||||
begin = std::chrono::high_resolution_clock::now ();
|
||||
ticks = UNIT::zero ();
|
||||
measurements = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the timer and increases the measurement count. A timer can be started and paused
|
||||
* multiple times (e.g. in a loop).
|
||||
* @return duration
|
||||
*/
|
||||
UNIT pause ()
|
||||
{
|
||||
++measurements;
|
||||
return stop ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop timer
|
||||
* @return duration
|
||||
*/
|
||||
UNIT stop ()
|
||||
{
|
||||
assert (state == nano::timer_state::started);
|
||||
state = nano::timer_state::stopped;
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now ();
|
||||
ticks += std::chrono::duration_cast<UNIT> (end - begin);
|
||||
return ticks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return current units.
|
||||
*/
|
||||
UNIT value ()
|
||||
{
|
||||
return ticks;
|
||||
}
|
||||
|
||||
/** Returns the duration in UNIT since the timer was last started. */
|
||||
UNIT since_start ()
|
||||
{
|
||||
auto end = std::chrono::high_resolution_clock::now ();
|
||||
return std::chrono::duration_cast<UNIT> (end - begin);
|
||||
}
|
||||
|
||||
/** Returns true if the timer was last started longer than \p duration_a units ago*/
|
||||
bool after_deadline (UNIT duration_a)
|
||||
{
|
||||
auto end = std::chrono::high_resolution_clock::now ();
|
||||
return std::chrono::duration_cast<UNIT> (end - begin) > duration_a;
|
||||
}
|
||||
|
||||
/** Returns true if the timer was last started shorter than \p duration_a units ago*/
|
||||
bool before_deadline (UNIT duration_a)
|
||||
{
|
||||
auto end = std::chrono::high_resolution_clock::now ();
|
||||
return std::chrono::duration_cast<UNIT> (end - begin) < duration_a;
|
||||
}
|
||||
|
||||
/** Stop timer and write measurements to \p output_a */
|
||||
void stop (std::string & output_a)
|
||||
{
|
||||
std::ostringstream stream;
|
||||
stop (stream);
|
||||
output_a = stream.str ();
|
||||
}
|
||||
|
||||
/** Stop timer and write measurements to \p stream_a */
|
||||
void stop (std::ostream & stream_a)
|
||||
{
|
||||
stop ();
|
||||
print (stream_a);
|
||||
}
|
||||
|
||||
/** Print measurements to the \p stream_a */
|
||||
void print (std::ostream & stream_a)
|
||||
{
|
||||
if (ticks >= minimum)
|
||||
{
|
||||
// Print cumulative children first. Non-cumulative children prints directly.
|
||||
for (auto & child : children)
|
||||
{
|
||||
if (child.measurements > 0)
|
||||
{
|
||||
child.print (stream_a);
|
||||
}
|
||||
}
|
||||
|
||||
auto current_parent = parent;
|
||||
while (current_parent)
|
||||
{
|
||||
stream_a << parent->desc << ".";
|
||||
current_parent = current_parent->parent;
|
||||
}
|
||||
|
||||
stream_a << desc << ": " << ticks.count () << ' ' << unit ();
|
||||
if (measurements > 0)
|
||||
{
|
||||
stream_a << " (" << measurements << " measurements, " << std::setprecision (2) << std::fixed << static_cast<double> (ticks.count ()) / static_cast<double> (measurements) << ' ' << unit () << " avg)";
|
||||
}
|
||||
stream_a << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the SI unit string */
|
||||
std::string unit () const
|
||||
{
|
||||
return typed_unit<UNIT> ();
|
||||
}
|
||||
|
||||
nano::timer_state current_state () const
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
private:
|
||||
timer * parent{ nullptr };
|
||||
std::vector<timer> children;
|
||||
nano::timer_state state{ nano::timer_state::stopped };
|
||||
std::string desc;
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> begin;
|
||||
UNIT ticks{ 0 };
|
||||
UNIT minimum{ UNIT::zero () };
|
||||
unsigned measurements{ 0 };
|
||||
|
||||
template <typename U, std::enable_if_t<std::is_same<U, std::chrono::nanoseconds>::value> * = nullptr>
|
||||
std::string typed_unit () const
|
||||
{
|
||||
return "nanoseconds";
|
||||
}
|
||||
|
||||
template <typename U, std::enable_if_t<std::is_same<U, std::chrono::microseconds>::value> * = nullptr>
|
||||
std::string typed_unit () const
|
||||
{
|
||||
return "microseconds";
|
||||
}
|
||||
|
||||
template <typename U, std::enable_if_t<std::is_same<U, std::chrono::milliseconds>::value> * = nullptr>
|
||||
std::string typed_unit () const
|
||||
{
|
||||
return "milliseconds";
|
||||
}
|
||||
|
||||
template <typename U, std::enable_if_t<std::is_same<U, std::chrono::seconds>::value> * = nullptr>
|
||||
std::string typed_unit () const
|
||||
{
|
||||
return "seconds";
|
||||
}
|
||||
|
||||
template <typename U, std::enable_if_t<std::is_same<U, std::chrono::minutes>::value> * = nullptr>
|
||||
std::string typed_unit () const
|
||||
{
|
||||
return "minutes";
|
||||
}
|
||||
|
||||
template <typename U, std::enable_if_t<std::is_same<U, std::chrono::hours>::value> * = nullptr>
|
||||
std::string typed_unit () const
|
||||
{
|
||||
return "hours";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The autotimer starts on construction, and stops and prints on destruction.
|
||||
*/
|
||||
template <typename UNIT = std::chrono::milliseconds>
|
||||
class autotimer : public nano::timer<UNIT>
|
||||
{
|
||||
public:
|
||||
autotimer (std::string description_a, std::ostream & stream_a = std::cout) :
|
||||
nano::timer<UNIT> (description_a), stream (stream_a)
|
||||
{
|
||||
nano::timer<UNIT>::start ();
|
||||
}
|
||||
~autotimer ()
|
||||
{
|
||||
nano::timer<UNIT>::stop (stream);
|
||||
}
|
||||
|
||||
private:
|
||||
std::ostream & stream;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
#include <nano/node/node.hpp>
|
||||
|
||||
#include <nano/lib/interface.h>
|
||||
#include <nano/lib/timer.hpp>
|
||||
#include <nano/lib/utility.hpp>
|
||||
#include <nano/node/common.hpp>
|
||||
#include <nano/node/rpc.hpp>
|
||||
|
|
@ -1301,7 +1302,7 @@ bool nano::block_processor::have_blocks ()
|
|||
void nano::block_processor::verify_state_blocks (std::unique_lock<std::mutex> & lock_a, size_t max_count)
|
||||
{
|
||||
assert (!mutex.try_lock ());
|
||||
auto start_time (std::chrono::steady_clock::now ());
|
||||
nano::timer<std::chrono::milliseconds> timer_l (nano::timer_state::started);
|
||||
std::deque<std::pair<std::shared_ptr<nano::block>, std::chrono::steady_clock::time_point>> items;
|
||||
if (max_count == std::numeric_limits<size_t>::max () || max_count >= state_blocks.size ())
|
||||
{
|
||||
|
|
@ -1354,31 +1355,28 @@ void nano::block_processor::verify_state_blocks (std::unique_lock<std::mutex> &
|
|||
}
|
||||
if (node.config.logging.timing_logging ())
|
||||
{
|
||||
auto end_time (std::chrono::steady_clock::now ());
|
||||
auto elapsed_time_ms (std::chrono::duration_cast<std::chrono::milliseconds> (end_time - start_time));
|
||||
auto elapsed_time_ms_int (elapsed_time_ms.count ());
|
||||
|
||||
BOOST_LOG (node.log) << boost::str (boost::format ("Batch verified %1% state blocks in %2% milliseconds") % size % elapsed_time_ms_int);
|
||||
BOOST_LOG (node.log) << boost::str (boost::format ("Batch verified %1% state blocks in %2% %3%") % size % timer_l.stop ().count () % timer_l.unit ());
|
||||
}
|
||||
}
|
||||
|
||||
void nano::block_processor::process_batch (std::unique_lock<std::mutex> & lock_a)
|
||||
{
|
||||
nano::timer<std::chrono::milliseconds> timer_l;
|
||||
lock_a.lock ();
|
||||
auto start_time (std::chrono::steady_clock::now ());
|
||||
timer_l.start ();
|
||||
// Limit state blocks verification time
|
||||
while (!state_blocks.empty () && std::chrono::steady_clock::now () - start_time < std::chrono::seconds (2))
|
||||
while (!state_blocks.empty () && timer_l.before_deadline (std::chrono::seconds (2)))
|
||||
{
|
||||
verify_state_blocks (lock_a, 2048);
|
||||
}
|
||||
lock_a.unlock ();
|
||||
auto transaction (node.store.tx_begin_write ());
|
||||
start_time = std::chrono::steady_clock::now ();
|
||||
timer_l.restart ();
|
||||
lock_a.lock ();
|
||||
// Processing blocks
|
||||
auto first_time (true);
|
||||
unsigned number_of_blocks_processed (0), number_of_forced_processed (0);
|
||||
while ((!blocks.empty () || !forced.empty ()) && std::chrono::steady_clock::now () - start_time < node.config.block_processor_batch_max_time)
|
||||
while ((!blocks.empty () || !forced.empty ()) && timer_l.before_deadline (node.config.block_processor_batch_max_time))
|
||||
{
|
||||
auto log_this_record (false);
|
||||
if (node.config.logging.timing_logging ())
|
||||
|
|
@ -1446,11 +1444,7 @@ void nano::block_processor::process_batch (std::unique_lock<std::mutex> & lock_a
|
|||
|
||||
if (node.config.logging.timing_logging ())
|
||||
{
|
||||
auto end_time (std::chrono::steady_clock::now ());
|
||||
auto elapsed_time_ms (std::chrono::duration_cast<std::chrono::milliseconds> (end_time - start_time));
|
||||
auto elapsed_time_ms_int (elapsed_time_ms.count ());
|
||||
|
||||
BOOST_LOG (node.log) << boost::str (boost::format ("Processed %1% blocks (%2% blocks were forced) in %3% milliseconds") % number_of_blocks_processed % number_of_forced_processed % elapsed_time_ms_int);
|
||||
BOOST_LOG (node.log) << boost::str (boost::format ("Processed %1% blocks (%2% blocks were forced) in %3% %4%") % number_of_blocks_processed % number_of_forced_processed % timer_l.stop ().count () % timer_l.unit ());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue