// Copyright 2019 The MediaPipe Authors. // // Licensed 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. #include "mediapipe/framework/deps/monotonic_clock.h" #include #include #include #include #include "absl/base/thread_annotations.h" #include "absl/memory/memory.h" #include "absl/synchronization/mutex.h" #include "absl/time/clock.h" #include "absl/time/time.h" #include "mediapipe/framework/port/gtest.h" #include "mediapipe/framework/port/integral_types.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/threadpool.h" #include "mediapipe/framework/tool/simulation_clock.h" namespace mediapipe { using RandomEngine = std::mt19937_64; using State = MonotonicClock::State; // absl::Now() recomputes clock drift approx. every 2 seconds, so run real // clock tests for at least that long. static const absl::Duration kDefaultRealTest = absl::Seconds(2.5); class MonotonicClockTest : public testing::Test { protected: MonotonicClockTest() {} virtual ~MonotonicClockTest() {} void SetUp() override { MonotonicClockAccess::SynchronizedMonotonicClockReset(); } void VerifyCorrectionMetrics(MonotonicClock* clock, int num_corrections_expect, double max_correction_expect) { int clock_num_corrections; double clock_max_correction; clock->GetCorrectionMetrics(&clock_num_corrections, &clock_max_correction); ASSERT_EQ(num_corrections_expect, clock_num_corrections); ASSERT_DOUBLE_EQ(max_correction_expect, clock_max_correction); } // This test produces no time corrections. void TestSimulatedForwardTime(SimulationClock* sim_clock, MonotonicClock* mono_clock) { absl::Time base_time = sim_clock->TimeNow(); ASSERT_EQ(base_time, mono_clock->TimeNow()); sim_clock->Sleep(absl::Seconds(10)); ASSERT_EQ(base_time + absl::Seconds(10), sim_clock->TimeNow()); ASSERT_EQ(base_time + absl::Seconds(10), mono_clock->TimeNow()); sim_clock->Sleep(absl::Seconds(10)); ASSERT_EQ(base_time + absl::Seconds(20), sim_clock->TimeNow()); ASSERT_EQ(base_time + absl::Seconds(20), mono_clock->TimeNow()); sim_clock->Sleep(absl::Seconds(5)); ASSERT_EQ(base_time + absl::Seconds(25), sim_clock->TimeNow()); ASSERT_EQ(base_time + absl::Seconds(25), mono_clock->TimeNow()); VerifyCorrectionMetrics(mono_clock, 0, 0.0); } // This test produces three corrections: one with arguments // (50, 100, 100), one with (80, 90, 100), and one with (60, 105, 105). void TestSimulatedBackwardTime(SimulationClock* sim_clock, MonotonicClock* mono_clock) { absl::Time base_time = sim_clock->TimeNow(); sim_clock->Sleep(absl::Seconds(100)); ASSERT_EQ(base_time + absl::Seconds(100), sim_clock->TimeNow()); ASSERT_EQ(base_time + absl::Seconds(100), mono_clock->TimeNow()); VerifyCorrectionMetrics(mono_clock, 0, 0.0); // Time moves backward -- expect a correction. sim_clock->Sleep(absl::Seconds(-50)); ASSERT_EQ(base_time + absl::Seconds(50), sim_clock->TimeNow()); ASSERT_EQ(base_time + absl::Seconds(100), // correction mono_clock->TimeNow()); VerifyCorrectionMetrics(mono_clock, 1, 50.0); // Time moves forward, but not enough to exceed the last value returned by // TimeNow(). No correction in this case. sim_clock->Sleep(absl::Seconds(20)); ASSERT_EQ(base_time + absl::Seconds(70), sim_clock->TimeNow()); ASSERT_EQ(base_time + absl::Seconds(100), mono_clock->TimeNow()); VerifyCorrectionMetrics(mono_clock, 1, 50.0); sim_clock->Sleep(absl::Seconds(20)); ASSERT_EQ(base_time + absl::Seconds(90), sim_clock->TimeNow()); ASSERT_EQ(base_time + absl::Seconds(100), mono_clock->TimeNow()); VerifyCorrectionMetrics(mono_clock, 1, 50.0); // Time moves backwards again -- expect a correction. sim_clock->Sleep(absl::Seconds(-10)); ASSERT_EQ(base_time + absl::Seconds(80), sim_clock->TimeNow()); ASSERT_EQ(base_time + absl::Seconds(100), // correction mono_clock->TimeNow()); VerifyCorrectionMetrics(mono_clock, 2, 50.0); // Time moves forward enough to advance monotonic time. sim_clock->Sleep(absl::Seconds(25)); ASSERT_EQ(base_time + absl::Seconds(105), sim_clock->TimeNow()); ASSERT_EQ(base_time + absl::Seconds(105), mono_clock->TimeNow()); VerifyCorrectionMetrics(mono_clock, 2, 50.0); // Time moves backward again. sim_clock->Sleep(absl::Seconds(-45)); ASSERT_EQ(base_time + absl::Seconds(60), sim_clock->TimeNow()); ASSERT_EQ(base_time + absl::Seconds(105), // correction mono_clock->TimeNow()); VerifyCorrectionMetrics(mono_clock, 3, 50.0); // Reset metrics and re-verify. mono_clock->ResetCorrectionMetrics(); VerifyCorrectionMetrics(mono_clock, 0, 0.0); } // Test that the Sleep/SleepUntil calls do not return until monotonic time // passes the requested wakeup time. void TestRandomSleep(MonotonicClock* mono_clock) { RandomEngine random(testing::UnitTest::GetInstance()->random_seed()); const int kNumSamples = 5; // Sleep. for (int i = 0; i < kNumSamples; i++) { absl::Duration sleep_time = absl::Seconds( std::uniform_real_distribution(0.0f, 0.2f)(random)); absl::Time before = mono_clock->TimeNow(); absl::Time wakeup_time = before + sleep_time; mono_clock->Sleep(sleep_time); absl::Time after = mono_clock->TimeNow(); ASSERT_LE(wakeup_time, after); } // SleepUntil. for (int i = 0; i < kNumSamples; i++) { absl::Duration sleep_time = absl::Seconds( std::uniform_real_distribution(0.0f, 0.2f)(random)); absl::Time before = mono_clock->TimeNow(); absl::Time wakeup_time = before + sleep_time; mono_clock->SleepUntil(wakeup_time); absl::Time after = mono_clock->TimeNow(); ASSERT_LE(wakeup_time, after); } } static State* CreateMonotonicClockState(Clock* raw_clock) { return MonotonicClockAccess::CreateMonotonicClockState(raw_clock); } static MonotonicClock* CreateMonotonicClock(State* state) { return MonotonicClockAccess::CreateMonotonicClock(state); } static void DeleteMonotonicClockState(State* state) { MonotonicClockAccess::DeleteMonotonicClockState(state); } }; // Time moves forward only -- there should be no time corrections. TEST_F(MonotonicClockTest, SimulatedForwardTime) { SimulationClock sim_clock; sim_clock.ThreadStart(); MonotonicClock* mono_clock = MonotonicClock::CreateMonotonicClock(&sim_clock); TestSimulatedForwardTime(&sim_clock, mono_clock); sim_clock.ThreadFinish(); delete mono_clock; } // Time moves forward and backward. TEST_F(MonotonicClockTest, SimulatedBackwardTime) { SimulationClock sim_clock; sim_clock.ThreadStart(); MonotonicClock* mono_clock = MonotonicClock::CreateMonotonicClock(&sim_clock); TestSimulatedBackwardTime(&sim_clock, mono_clock); sim_clock.ThreadFinish(); delete mono_clock; } // Time moves forward and backward. TEST_F(MonotonicClockTest, SimulatedTime) { SimulationClock sim_clock; sim_clock.ThreadStart(); MonotonicClock* mono_clock = MonotonicClock::CreateMonotonicClock(&sim_clock); TestSimulatedBackwardTime(&sim_clock, mono_clock); absl::Time mono_time = mono_clock->TimeNow(); sim_clock.Sleep(absl::Seconds(-1)); ASSERT_EQ(mono_time, mono_clock->TimeNow()); sim_clock.ThreadFinish(); delete mono_clock; } // Take a random walk through time. TEST_F(MonotonicClockTest, SimulatedRandomWalk) { SimulationClock sim_clock; sim_clock.ThreadStart(); MonotonicClock* mono_clock = MonotonicClock::CreateMonotonicClock(&sim_clock); sim_clock.Sleep(absl::Now() - sim_clock.TimeNow()); ASSERT_EQ(sim_clock.TimeNow(), mono_clock->TimeNow()); // Generate kNumSamples random clock adjustments. const int kNumSamples = 5; RandomEngine random(testing::UnitTest::GetInstance()->random_seed()); // Keep track of maximum time on clock and corrections. absl::Time max_time = sim_clock.TimeNow(); int num_corrections = 0; absl::Duration max_correction = absl::ZeroDuration(); for (int i = 0; i < kNumSamples; i++) { absl::Duration jump = absl::Seconds(std::uniform_real_distribution(-0.5, 0.5)(random)); sim_clock.Sleep(jump); absl::Time sim_time = sim_clock.TimeNow(); if (jump < absl::ZeroDuration()) { ASSERT_LT(sim_time, max_time); absl::Duration correction = max_time - sim_time; if (correction > max_correction) { max_correction = correction; } ++num_corrections; } if (sim_clock.TimeNow() > max_time) { max_time = sim_clock.TimeNow(); } ASSERT_EQ(max_time, mono_clock->TimeNow()); } VerifyCorrectionMetrics(mono_clock, num_corrections, absl::FDivDuration(max_correction, absl::Seconds(1))); sim_clock.ThreadFinish(); delete mono_clock; } TEST_F(MonotonicClockTest, RealTime) { MonotonicClock* mono_clock = MonotonicClock::CreateMonotonicClock(Clock::RealClock()); // Call mono_clock->Now() continuously for FLAGS_real_test_secs seconds. absl::Time start = absl::Now(); absl::Time time = start; int64 num_calls = 0; do { absl::Time last_time = time; time = mono_clock->TimeNow(); ASSERT_LE(last_time, time); ++num_calls; } while (time - start < kDefaultRealTest); // Just out of curiousity -- did real clock go backwards? int clock_num_corrections; mono_clock->GetCorrectionMetrics(&clock_num_corrections, NULL); LOG(INFO) << clock_num_corrections << " corrections in " << num_calls << " calls to mono_clock->Now()"; delete mono_clock; } // Test the Sleep interface using a MonotonicClock. TEST_F(MonotonicClockTest, RandomSleep) { MonotonicClock* mono_clock = MonotonicClock::CreateMonotonicClock(Clock::RealClock()); TestRandomSleep(mono_clock); delete mono_clock; } // Test the Sleep interface using a SynchronizedMonotonicClock. TEST_F(MonotonicClockTest, RandomSleepSynced) { MonotonicClock* mono_clock = MonotonicClock::CreateSynchronizedMonotonicClock(); TestRandomSleep(mono_clock); delete mono_clock; } // Test that SleepUntil has no effect if monotonic time has passed the // requested wakeup time. TEST_F(MonotonicClockTest, SimulatedInsomnia) { SimulationClock sim_clock; sim_clock.ThreadStart(); MonotonicClock* mono_clock = MonotonicClock::CreateMonotonicClock(&sim_clock); sim_clock.Sleep(absl::Now() - sim_clock.TimeNow()); ASSERT_EQ(sim_clock.TimeNow(), mono_clock->TimeNow()); sim_clock.Sleep(absl::Seconds(-3.14159)); // Even though sim_clock will never advance, this call will not sleep // because monotonic_time has already advanced beyond the wakeup time. mono_clock->SleepUntil(sim_clock.TimeNow() + absl::Seconds(1)); // Note that the same test can't be performed with Sleep because the argument // to sleep is an offset from monotonic time, not raw time. sim_clock.ThreadFinish(); delete mono_clock; } // Two monotonic clocks, clock1 and clock2, each synced to the same // raw clock. Advance simulated time, read one clock, regress simulated // time, and read the other clock. The values should be the same. TEST_F(MonotonicClockTest, SyncedPair) { SimulationClock sim_clock; sim_clock.ThreadStart(); State* state = CreateMonotonicClockState(&sim_clock); MonotonicClock* clock1 = CreateMonotonicClock(state); MonotonicClock* clock2 = CreateMonotonicClock(state); sim_clock.Sleep(absl::Seconds(1000)); ASSERT_EQ(sim_clock.TimeNow(), clock1->TimeNow()); ASSERT_EQ(sim_clock.TimeNow(), clock2->TimeNow()); absl::Time time1, time2; sim_clock.Sleep(absl::Seconds(2)); time1 = clock1->TimeNow(); ASSERT_EQ(sim_clock.TimeNow(), time1); sim_clock.Sleep(absl::Seconds(-5)); time2 = clock2->TimeNow(); ASSERT_EQ(time1, time2); VerifyCorrectionMetrics(clock1, 0, 0.0); VerifyCorrectionMetrics(clock2, 1, 5.0); clock1->ResetCorrectionMetrics(); clock2->ResetCorrectionMetrics(); VerifyCorrectionMetrics(clock1, 0, 0.0); VerifyCorrectionMetrics(clock2, 0, 0.0); // In this example, time on clock1 goes forward by a greater amount than // time goes backward on clock2. Although clock2 still reports the global // monotonic time, it does not report a correction because it never // observed a raw clock reading that went backward. sim_clock.Sleep(absl::Seconds(10)); time1 = clock1->TimeNow(); ASSERT_EQ(sim_clock.TimeNow(), time1); sim_clock.Sleep(absl::Seconds(-1)); time2 = clock2->TimeNow(); ASSERT_EQ(time1, time2); VerifyCorrectionMetrics(clock1, 0, 0.0); VerifyCorrectionMetrics(clock2, 0, 0.0); sim_clock.ThreadFinish(); delete clock1; delete clock2; DeleteMonotonicClockState(state); } // Test that a globally-synchronized MonotonicClock is unaffected by clock // behavior of a vanilla MonotonicClock. TEST_F(MonotonicClockTest, UnsyncedPair) { SimulationClock sim_clock; sim_clock.ThreadStart(); MonotonicClock* sync_clock = MonotonicClock::CreateSynchronizedMonotonicClock(); MonotonicClock* mono_clock = MonotonicClock::CreateMonotonicClock(&sim_clock); absl::Time before = sync_clock->TimeNow(); sim_clock.Sleep(before - sim_clock.TimeNow()); ASSERT_EQ(before, mono_clock->TimeNow()); sim_clock.Sleep(absl::Seconds(61)); ASSERT_LT(sync_clock->TimeNow(), mono_clock->TimeNow()); sim_clock.ThreadFinish(); delete sync_clock; delete mono_clock; } // The factory method CreateSynchronizedMonotonicClock should return a // MonotonicClock based on real time. Since time waits for no unit test, // we can't test equality of the time read from the factory-produced clock // and the time read from a real clock. But we can verifying that, as long // as the real clock moves forward, the time read from the factory-produced // clock is bounded by consecutive readings of the real clock. TEST_F(MonotonicClockTest, CreateSynchronizedMonotonicClock) { Clock* real_clock = Clock::RealClock(); MonotonicClock* mono_clock = MonotonicClock::CreateSynchronizedMonotonicClock(); const int kNumSamples = 100; for (int i = 0; i < kNumSamples; ++i) { absl::Time before = real_clock->TimeNow(); absl::Time now = mono_clock->TimeNow(); absl::Time after = real_clock->TimeNow(); if (after < before) { // Real clock moved backward -- test is invalid. continue; } ASSERT_LE(before, now); ASSERT_LE(now, after); } delete mono_clock; } // Start up a number of threads to beat on the interface to verify that // (a) nothing crashes and (b) nothing deadlocks. class ClockFrenzy { public: ClockFrenzy() : real_clock_(Clock::RealClock()), random_( new RandomEngine(testing::UnitTest::GetInstance()->random_seed())) { } void AddSimulationClock(SimulationClock* clock) { sim_clocks_.push_back(clock); } void AddMonotonicClock(MonotonicClock* clock) { mono_clocks_.push_back(clock); } void Feed() { while (Running()) { // 40% of the time, advance a simulated clock. // 50% of the time, read a monotonic clock. const int32 u = UniformRandom(100); if (u < 40) { // Pick a simulated clock and advance it. const int nclocks = sim_clocks_.size(); if (nclocks == 0) continue; SimulationClock* sim_clock = sim_clocks_[UniformRandom(nclocks)]; // Bias the clock towards forward movement. sim_clock->Sleep(absl::Seconds(RndFloatRandom() - 0.2)); } else if (u < 90) { // Pick a monotonic clock and read it. const int nclocks = mono_clocks_.size(); if (nclocks == 0) continue; MonotonicClock* mono_clock = mono_clocks_[UniformRandom(nclocks)]; mono_clock->TimeNow(); } } } // Start Feed-ing threads. void Start(int nthreads) { absl::MutexLock l(&lock_); running_ = true; threads_ = absl::make_unique("Frenzy", nthreads); threads_->StartWorkers(); for (int i = 0; i < nthreads; ++i) { threads_->Schedule([&]() { Feed(); }); } } void Stop() { absl::MutexLock l(&lock_); running_ = false; } bool Running() { absl::MutexLock l(&lock_); return running_; } // Wait for all threads to finish. void Wait() { threads_.reset(); } private: Clock* real_clock_; std::vector sim_clocks_; std::vector mono_clocks_; std::unique_ptr threads_; // Provide a lock to avoid race conditions in non-threadsafe ACMRandom. mutable absl::Mutex lock_; std::unique_ptr random_ ABSL_GUARDED_BY(lock_); // The stopping notification. bool running_; // Thread-safe random number generation functions for use by other class // member functions. int32 UniformRandom(int32 n) { absl::MutexLock l(&lock_); return std::uniform_int_distribution(0, n - 1)(*random_); } float RndFloatRandom() { absl::MutexLock l(&lock_); return std::uniform_real_distribution(0.0f, 1.0f)(*random_); } }; TEST_F(MonotonicClockTest, SimulatedFrenzy) { ClockFrenzy f; SimulationClock s1, s2; s1.ThreadStart(); s2.ThreadStart(); f.AddSimulationClock(&s1); f.AddSimulationClock(&s2); MonotonicClock* m11 = MonotonicClock::CreateMonotonicClock(&s1); State* state = CreateMonotonicClockState(&s1); MonotonicClock* m12 = CreateMonotonicClock(state); MonotonicClock* m13 = CreateMonotonicClock(state); MonotonicClock* m21 = MonotonicClock::CreateMonotonicClock(&s2); MonotonicClock* m22 = MonotonicClock::CreateMonotonicClock(&s2); f.AddMonotonicClock(m11); f.AddMonotonicClock(m12); f.AddMonotonicClock(m13); f.AddMonotonicClock(m21); f.AddMonotonicClock(m22); f.Start(10); Clock::RealClock()->Sleep(absl::Seconds(1)); f.Stop(); f.Wait(); s2.ThreadFinish(); s1.ThreadFinish(); delete m11; delete m12; delete m13; delete m21; delete m22; DeleteMonotonicClockState(state); } // Just for completeness, a frenzy with only real-time // SynchronizedMonotonicClock instances. TEST_F(MonotonicClockTest, RealFrenzy) { ClockFrenzy f; MonotonicClock* m1 = MonotonicClock::CreateSynchronizedMonotonicClock(); MonotonicClock* m2 = MonotonicClock::CreateSynchronizedMonotonicClock(); MonotonicClock* m3 = MonotonicClock::CreateSynchronizedMonotonicClock(); f.AddMonotonicClock(m1); f.AddMonotonicClock(m2); f.AddMonotonicClock(m3); f.Start(10); Clock::RealClock()->Sleep(kDefaultRealTest); f.Stop(); f.Wait(); // Just out of curiousity -- did real clock go backwards? int clock_num_corrections; m1->GetCorrectionMetrics(&clock_num_corrections, NULL); LOG_IF(INFO, clock_num_corrections > 0) << clock_num_corrections << " corrections"; m2->GetCorrectionMetrics(&clock_num_corrections, NULL); LOG_IF(INFO, clock_num_corrections > 0) << clock_num_corrections << " corrections"; m3->GetCorrectionMetrics(&clock_num_corrections, NULL); LOG_IF(INFO, clock_num_corrections > 0) << clock_num_corrections << " corrections"; delete m1; delete m2; delete m3; } } // namespace mediapipe