diff --git a/libs/pbd/test/timer_test.cc b/libs/pbd/test/timer_test.cc new file mode 100644 index 0000000000..7c78b9c3c0 --- /dev/null +++ b/libs/pbd/test/timer_test.cc @@ -0,0 +1,550 @@ +#include "timer_test.h" + +#include +#include +#include + +#include "pbd/timer.h" + +#ifdef PLATFORM_WINDOWS +#include +#endif + +CPPUNIT_TEST_SUITE_REGISTRATION (TimerTest); + +using namespace std; + +#ifdef PLATFORM_WINDOWS +UINT& +min_timer_resolution () +{ + static UINT min_res_ms = 0; + return min_res_ms; +} + +bool +set_min_timer_resolution () +{ + TIMECAPS caps; + + if (timeGetDevCaps(&caps, sizeof(TIMECAPS)) != TIMERR_NOERROR) { + cerr << "Could not get timer device capabilities..." << endl; + } else { + if (timeBeginPeriod(caps.wPeriodMin) != TIMERR_NOERROR) { + cerr << "Could not set minimum timer resolution to: " << caps.wPeriodMin << "ms" << endl; + return false; + } + else { + cerr << "Multimedia timer resolution set to: " << caps.wPeriodMin << "ms" << endl; + min_timer_resolution() = caps.wPeriodMin; + return true; + } + } + return false; +} + +bool +reset_timer_resolution () +{ + if (min_timer_resolution()) { + if (timeEndPeriod(min_timer_resolution()) != TIMERR_NOERROR) { + cerr << "Could not reset timer resolution" << endl; + return false; + } else { + cerr << "Multimedia timer resolution reset" << endl; + return true; + } + } + return true; +} + +#endif + +void +TimerTest::simulate_load (const string& name, guint64 load_usecs) +{ + PBD::Timing timing; + std::ostringstream oss; + oss << name << " Load."; + + guint64 i = 0; + do { + timing.update (); + + // totally arbitrary + if (i % 10000 == 0) { + oss << "."; + } + + ++i; + } while (timing.elapsed () < load_usecs); + + oss << "Expected = " << load_usecs; + oss << ", Elapsed = " << timing.elapsed (); + oss << endl; + //cerr << oss.str(); +} + +void +TimerTest::on_second_timeout () +{ + cerr << endl; + cerr << "Timing Summary: " << m_current_test_name << endl; + + if (m_idle_timing_data.size()) { + cerr << "Idle Timing: " << m_idle_timing_data.summary(); + } + if (m_fast_timing_data.size()) { + cerr << "Fast Timing: " << m_fast_timing_data.summary(); + } + if (m_rapid1_timing_data.size()) { + cerr << "Rapid1 Timing: " << m_rapid1_timing_data.summary(); + } + if (m_rapid2_timing_data.size()) { + cerr << "Rapid2 Timing: " << m_rapid2_timing_data.summary(); + } + reset_timing (); +} + +bool +TimerTest::on_second_timeout_glibmm () +{ + TimerTest::on_second_timeout (); + return true; +} + +void +TimerTest::on_fast_timeout () +{ + m_fast_timing_data.add_interval (); + if (m_block_idle) { + // do nothing, handled in rapid timers + } else { + simulate_load ("Rapid1", 4000); + } +} + +bool +TimerTest::on_fast_timeout_glibmm () +{ + on_fast_timeout (); + return true; +} + +void +TimerTest::on_rapid1_timeout () +{ + m_rapid1_timing_data.add_interval (); + if (m_block_idle) { + simulate_load ("Rapid1", rapid1_timer_usecs () * 0.5); + } else { + simulate_load ("Rapid1", 2000); + } +} + +bool +TimerTest::on_rapid1_timeout_glibmm () +{ + on_rapid1_timeout (); + return true; +} + +void +TimerTest::on_rapid2_timeout () +{ + m_rapid2_timing_data.add_interval (); + if (m_block_idle) { + simulate_load ("Rapid2", rapid2_timer_usecs () * 0.5); + } else { + simulate_load ("Rapid2", 2000); + } +} + +bool +TimerTest::on_rapid2_timeout_glibmm () +{ + on_rapid2_timeout (); + return true; +} + +bool +TimerTest::on_idle_handler () +{ + m_idle_timing_data.add_interval (); + if (m_block_idle) { + simulate_load ("Idle", rapid2_timer_usecs ()); + } + return true; +} + +bool +TimerTest::on_quit_handler () +{ + cerr << "Quit Handler" << endl; + m_main->quit (); + return false; +} + +void +TimerTest::reset_timing () +{ + m_idle_timing_data.reset (); + m_fast_timing_data.reset (); + m_rapid1_timing_data.reset (); + m_rapid2_timing_data.reset (); +} + +void +TimerTest::start_timing () +{ + m_idle_timing_data.start_timing (); + m_fast_timing_data.start_timing (); + m_rapid1_timing_data.start_timing (); + m_rapid2_timing_data.start_timing (); +} + +gboolean +TimerTest::_second_timeout_handler (void *data) +{ + TimerTest *const tt = static_cast(data); + tt->on_second_timeout (); + return TRUE; +} + +gboolean +TimerTest::_fast_timeout_handler (void *data) +{ + TimerTest *const tt = static_cast(data); + tt->on_fast_timeout (); + return TRUE; +} + +gboolean +TimerTest::_rapid1_timeout_handler (void *data) +{ + TimerTest *const tt = static_cast(data); + tt->on_rapid1_timeout (); + return TRUE; +} + +gboolean +TimerTest::_rapid2_timeout_handler (void *data) +{ + TimerTest *const tt = static_cast(data); + tt->on_rapid2_timeout (); + return TRUE; +} + +void +TimerTest::reset_timing_run_main () +{ + reset_timing (); + start_timing (); + + connect_quit_timeout (); + + m_main = Glib::MainLoop::create (m_context); + m_main->run (); +} + +void +TimerTest::testGlibTimeoutSources () +{ + m_current_test_name = "testGlibTimeoutSources"; + _testGlibTimeoutSources (); +} + +void +TimerTest::_testGlibTimeoutSources () +{ + m_context = Glib::MainContext::create (); + + GSource * second_timeout_source = g_timeout_source_new (second_timer_ms ()); + + g_source_set_callback (second_timeout_source , &TimerTest::_second_timeout_handler, this, NULL); + + g_source_attach (second_timeout_source, m_context->gobj()); + + if (m_connect_idle) { + connect_idle_handler (); + reset_timing_run_main (); + } + + GSource * fast_timeout_source = g_timeout_source_new (fast_timer_ms ()); + + g_source_set_callback (fast_timeout_source , &TimerTest::_fast_timeout_handler, this, NULL); + + g_source_attach (fast_timeout_source, m_context->gobj()); + + // now run with fast timeout + reset_timing_run_main (); + + GSource * rapid1_timeout_source = g_timeout_source_new (rapid1_timer_ms ()); + + g_source_set_callback (rapid1_timeout_source , &TimerTest::_rapid1_timeout_handler, this, NULL); + + g_source_attach (rapid1_timeout_source, m_context->gobj()); + + // now run with fast and rapid1 timeouts + reset_timing_run_main (); + + GSource * rapid2_timeout_source = g_timeout_source_new (rapid2_timer_ms ()); + + g_source_set_callback (rapid2_timeout_source , &TimerTest::_rapid2_timeout_handler, this, NULL); + + g_source_attach (rapid2_timeout_source, m_context->gobj()); + + // now run with fast, rapid1 and rapid2 timeouts + reset_timing_run_main (); + + // cleanup + g_source_destroy (second_timeout_source); + g_source_unref (second_timeout_source); + + g_source_destroy (fast_timeout_source); + g_source_unref (fast_timeout_source); + + g_source_destroy (rapid1_timeout_source); + g_source_unref (rapid1_timeout_source); + + g_source_destroy (rapid2_timeout_source); + g_source_unref (rapid2_timeout_source); +} + +void +TimerTest::testGlibmmSignalTimeouts () +{ + m_current_test_name = "testGlibmmSignalTimeouts"; + _testGlibmmSignalTimeouts (); +} + +void +TimerTest::_testGlibmmSignalTimeouts () +{ + m_context = Glib::MainContext::get_default (); + + Glib::signal_timeout().connect(sigc::mem_fun(*this, &TimerTest::on_second_timeout_glibmm), second_timer_ms()); + + if (m_connect_idle) { + connect_idle_handler (); + reset_timing_run_main (); + } + + Glib::signal_timeout().connect(sigc::mem_fun(*this, &TimerTest::on_fast_timeout_glibmm), fast_timer_ms()); + + reset_timing_run_main (); + + Glib::signal_timeout().connect(sigc::mem_fun(*this, &TimerTest::on_rapid1_timeout_glibmm), rapid1_timer_ms()); + + reset_timing_run_main (); + + Glib::signal_timeout().connect(sigc::mem_fun(*this, &TimerTest::on_rapid2_timeout_glibmm), rapid2_timer_ms()); + + reset_timing_run_main (); +} + +void +TimerTest::testGlibmmTimeoutSources () +{ + m_current_test_name = "testGlibmmTimeoutSources"; + _testGlibmmTimeoutSources (); +} + +void +TimerTest::_testGlibmmTimeoutSources () +{ + m_context = Glib::MainContext::create (); + + const Glib::RefPtr second_source = Glib::TimeoutSource::create(second_timer_ms()); + second_source->connect(sigc::mem_fun(*this, &TimerTest::on_second_timeout_glibmm)); + + second_source->attach(m_context); + + if (m_connect_idle) { + connect_idle_handler (); + reset_timing_run_main (); + } + + const Glib::RefPtr fast_source = Glib::TimeoutSource::create(fast_timer_ms()); + fast_source->connect(sigc::mem_fun(*this, &TimerTest::on_fast_timeout_glibmm)); + + fast_source->attach(m_context); + + reset_timing_run_main (); + + const Glib::RefPtr rapid1_source = Glib::TimeoutSource::create(rapid1_timer_ms()); + sigc::connection rapid1_connection = rapid1_source->connect(sigc::mem_fun(*this, &TimerTest::on_rapid1_timeout_glibmm)); + + rapid1_source->attach(m_context); + + reset_timing_run_main (); + + const Glib::RefPtr rapid2_source = Glib::TimeoutSource::create(rapid2_timer_ms()); + sigc::connection rapid2_connection = rapid2_source->connect(sigc::mem_fun(*this, &TimerTest::on_rapid2_timeout_glibmm)); + + rapid2_source->attach(m_context); + + reset_timing_run_main (); +} + +void +TimerTest::connect_idle_handler () +{ + const Glib::RefPtr idle_source = Glib::IdleSource::create(); + idle_source->connect(sigc::mem_fun(*this, &TimerTest::on_idle_handler)); + + idle_source->attach(m_context); +} + +void +TimerTest::connect_quit_timeout () +{ + const Glib::RefPtr quit_source = Glib::TimeoutSource::create(test_length_ms()); + quit_source->connect(sigc::mem_fun(*this, &TimerTest::on_quit_handler)); + + quit_source->attach(m_context); +} + +void +TimerTest::testTimers () +{ + m_current_test_name = "testTimers"; + _testTimers (); +} + +void +TimerTest::_testTimers () +{ + m_context = Glib::MainContext::create (); + + PBD::StandardTimer second_timer (second_timer_ms (), m_context); + sigc::connection second_connection = second_timer.connect (sigc::mem_fun (this, &TimerTest::on_second_timeout)); + + if (m_connect_idle) { + connect_idle_handler (); + // let the idle handler run as fast as it can + reset_timing_run_main(); + } + + PBD::StandardTimer fast_timer (fast_timer_ms (), m_context); + sigc::connection fast_connection = fast_timer.connect (sigc::mem_fun (this, &TimerTest::on_fast_timeout)); + + reset_timing_run_main(); + + PBD::StandardTimer rapid1_timer (rapid1_timer_ms (), m_context); + sigc::connection rapid1_connection = rapid1_timer.connect (sigc::mem_fun (this, &TimerTest::on_rapid1_timeout)); + + reset_timing_run_main(); + + PBD::StandardTimer rapid2_timer (rapid2_timer_ms (), m_context); + sigc::connection rapid2_connection = rapid2_timer.connect (sigc::mem_fun (this, &TimerTest::on_rapid2_timeout)); + + reset_timing_run_main(); +} + +void +TimerTest::testTimersIdleFrequency () +{ + m_current_test_name = "testTimersIdleFrequency"; + _testTimersIdleFrequency (); +} + +void +TimerTest::_testTimersIdleFrequency () +{ + m_block_idle = false; + m_connect_idle = true; + + _testTimers (); + + m_block_idle = false; + m_connect_idle = false; +} + +void +TimerTest::testTimersBlockIdle () +{ + m_current_test_name = "testTimersBlockIdle"; + _testTimersBlockIdle (); +} + +void +TimerTest::_testTimersBlockIdle () +{ + m_block_idle = true; + m_connect_idle = true; + + _testTimers (); + + m_block_idle = false; + m_connect_idle = false; +} + +#ifdef PLATFORM_WINDOWS +void +TimerTest::testGlibTimeoutSourcesHR () +{ + CPPUNIT_ASSERT(set_min_timer_resolution()); + + m_current_test_name = "testGlibTimeoutSourcesHR"; + _testGlibTimeoutSources (); + + CPPUNIT_ASSERT(reset_timer_resolution()); +} + +void +TimerTest::testGlibmmSignalTimeoutsHR () +{ + CPPUNIT_ASSERT(set_min_timer_resolution()); + + m_current_test_name = "testGlibmmSignalTimeoutsHR"; + _testGlibmmSignalTimeouts (); + + CPPUNIT_ASSERT(reset_timer_resolution()); +} + +void +TimerTest::testGlibmmTimeoutSourcesHR () +{ + CPPUNIT_ASSERT(set_min_timer_resolution()); + + m_current_test_name = "testGlibmmTimeoutSourcesHR"; + _testGlibmmTimeoutSources (); + + CPPUNIT_ASSERT(reset_timer_resolution()); +} + +void +TimerTest::testTimersHR () +{ + CPPUNIT_ASSERT(set_min_timer_resolution()); + + m_current_test_name = "testTimersHR"; + _testTimers (); + + CPPUNIT_ASSERT(reset_timer_resolution()); +} + +void +TimerTest::testTimersIdleFrequencyHR () +{ + CPPUNIT_ASSERT(set_min_timer_resolution()); + + m_current_test_name = "testTimersIdleFrequencyHR"; + _testTimersIdleFrequency (); + + CPPUNIT_ASSERT(reset_timer_resolution()); +} + +void +TimerTest::testTimersBlockIdleHR () +{ + CPPUNIT_ASSERT(set_min_timer_resolution()); + + m_current_test_name = "testTimersIdleFrequencyHR"; + _testTimersBlockIdle (); + + CPPUNIT_ASSERT(reset_timer_resolution()); +} + +#endif diff --git a/libs/pbd/test/timer_test.h b/libs/pbd/test/timer_test.h new file mode 100644 index 0000000000..75bcfdb3d1 --- /dev/null +++ b/libs/pbd/test/timer_test.h @@ -0,0 +1,152 @@ +#include +#include + +#include "glibmm/main.h" + +#include "pbd/timing.h" + +/** + * The main point of this test is to the compare the different + * ways of setting a timeout with glib and glibmm and the + * PBD::Timers class and then to test them all again with + * the maximum multimedia timer resolution(1ms) set with + * timeBeginPeriod on Windows. + * + * The test demonstrates that when using Glibmm TimeoutSources + * the frequency of the timers is different that using Glib based + * timeouts. In Ardour that resulted in a noticable increase in + * CPU Usage, but behaviour may vary. + * + * The other thing being tested is what effect adding two short + * timeouts(<40ms) to a glib context has on the idle timeout on + * Windows. + * + * Glib Timeout sources run at a higher priority than the idle + * handler, so the more work performed in the timeout handlers + * the less frequent the idle handler will run until doesn't get + * scheduled at all. The consequence of this is blocking the UI. + * + * Similarily because timeout sources and UI updates/rendering + * occur in the same context in Gtk the length of expose/draw + * operations will affect the accuracy of the timeouts. + */ +class TimerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE (TimerTest); + CPPUNIT_TEST (testGlibTimeoutSources); + CPPUNIT_TEST (testGlibmmSignalTimeouts); + CPPUNIT_TEST (testGlibmmTimeoutSources); + CPPUNIT_TEST (testTimers); + CPPUNIT_TEST (testTimersIdleFrequency); + CPPUNIT_TEST (testTimersBlockIdle); +#ifdef PLATFORM_WINDOWS + CPPUNIT_TEST (testGlibTimeoutSourcesHR); + CPPUNIT_TEST (testGlibmmSignalTimeoutsHR); + CPPUNIT_TEST (testGlibmmTimeoutSourcesHR); + CPPUNIT_TEST (testTimersHR); + CPPUNIT_TEST (testTimersIdleFrequencyHR); + CPPUNIT_TEST (testTimersBlockIdleHR); +#endif + CPPUNIT_TEST_SUITE_END (); + +public: + + TimerTest () + : m_connect_idle(false) + , m_block_idle(false) + { } + + void _testGlibTimeoutSources (); + void _testGlibmmSignalTimeouts (); + void _testGlibmmTimeoutSources (); + void _testTimers (); + void _testTimersIdleFrequency (); + void _testTimersBlockIdle (); + + void testGlibTimeoutSources (); + void testGlibmmSignalTimeouts (); + void testGlibmmTimeoutSources (); + void testTimers (); + void testTimersIdleFrequency (); + void testTimersBlockIdle (); + +#ifdef PLATFORM_WINDOWS + void testGlibTimeoutSourcesHR (); + void testGlibmmSignalTimeoutsHR (); + void testGlibmmTimeoutSourcesHR (); + void testTimersHR (); + void testTimersIdleFrequencyHR (); + void testTimersBlockIdleHR (); +#endif + +private: + + static guint64 second_timer_usecs () + { return 1000000; } + + static guint64 fast_timer_usecs () + { return 100000; } + + static guint64 rapid1_timer_usecs () + { return 40000; } + + static guint64 rapid2_timer_usecs () + { return 15000; } + + static guint64 second_timer_ms () + { return second_timer_usecs () / 1000; } + + static guint64 fast_timer_ms () + { return fast_timer_usecs () / 1000; } + + static guint64 rapid1_timer_ms () + { return rapid1_timer_usecs () / 1000; } + + static guint64 rapid2_timer_ms () + { return rapid2_timer_usecs () / 1000; } + + static guint64 test_length_ms () + { return 10 * 1000; } + + std::string m_current_test_name; + + bool m_connect_idle; + bool m_block_idle; + + bool on_idle_handler (); + bool on_quit_handler (); + + void on_second_timeout (); + void on_fast_timeout (); + void on_rapid1_timeout (); + void on_rapid2_timeout (); + + bool on_second_timeout_glibmm (); + bool on_fast_timeout_glibmm (); + bool on_rapid1_timeout_glibmm (); + bool on_rapid2_timeout_glibmm (); + + static gboolean _second_timeout_handler (void*); + static gboolean _fast_timeout_handler (void*); + static gboolean _rapid1_timeout_handler (void*); + static gboolean _rapid2_timeout_handler (void*); + + void start_timing (); + void reset_timing (); + + void reset_timing_run_main (); + + static void + simulate_load (const std::string& name, guint64 time_usecs); + Glib::RefPtr m_main; + Glib::RefPtr m_context; + + void connect_idle_handler (); + void connect_quit_timeout (); + + PBD::TimingData m_idle_timing_data; + PBD::TimingData m_second_timing_data; + PBD::TimingData m_fast_timing_data; + PBD::TimingData m_rapid1_timing_data; + PBD::TimingData m_rapid2_timing_data; +}; diff --git a/libs/pbd/wscript b/libs/pbd/wscript index 0c8ad918f7..cb55437ae4 100644 --- a/libs/pbd/wscript +++ b/libs/pbd/wscript @@ -160,6 +160,7 @@ def build(bld): test/mutex_test.cc test/scalar_properties.cc test/signals_test.cc + test/timer_test.cc test/convert_test.cc test/filesystem_test.cc test/test_common.cc diff --git a/wscript b/wscript index 4bd7776632..80c8691428 100644 --- a/wscript +++ b/wscript @@ -841,6 +841,7 @@ def configure(conf): # needed for at least libsmf conf.check_cc(function_name='htonl', header_name='winsock2.h', lib='ws2_32') conf.env.append_value('LIB', 'ws2_32') + conf.env.append_value('LIB', 'winmm') # needed for mingw64 packages, not harmful on normal mingw build conf.env.append_value('LIB', 'intl') conf.check_cc(function_name='regcomp', header_name='regex.h',