Line data Source code
1 : /**
2 : * @file TimestampEstimatorTimeSync_test.cxx TimestampEstimatorTimeSync class Unit Tests
3 : *
4 : * This is part of the DUNE DAQ Application Framework, copyright 2020.
5 : * Licensing/copyright details are in the COPYING file that you should have
6 : * received with this code.
7 : */
8 :
9 : // #include "iomanager/IOManager.hpp"
10 : // #include "iomanager/Sender.hpp"
11 : // #include "iomanager/Receiver.hpp"
12 : #include "utilities/TimestampEstimatorTimeSync.hpp"
13 :
14 : /**
15 : * @brief Name of this test module
16 : */
17 : #define BOOST_TEST_MODULE TimestampEstimatorTimeSync_test // NOLINT
18 :
19 : #include "boost/test/unit_test.hpp"
20 :
21 : #include <atomic>
22 : #include <chrono>
23 : #include <future>
24 : #include <map>
25 : #include <memory>
26 : #include <string>
27 :
28 : struct DummyTimeSync
29 : {
30 : uint64_t daq_time{ dunedaq::utilities::TimestampEstimatorBase::s_invalid_ts }; // NOLINT(build/unsigned)
31 : /// The current system time
32 : uint64_t system_time{ 0 }; // NOLINT(build/unsigned)
33 : /// Sequence Number of this message, for debugging
34 : uint64_t sequence_number{ 0 }; // NOLINT(build/unsigned)
35 : /// Run number at time of creation
36 : uint32_t run_number{ 0 }; // NOLINT(build/unsigned)
37 : /// SourceID::id of the creating process, for debugging
38 : uint32_t source_id{ 0 }; // NOLINT(build/unsigned)
39 : };
40 :
41 : using namespace dunedaq;
42 :
43 : BOOST_AUTO_TEST_SUITE(BOOST_TEST_MODULE)
44 :
45 : // /**
46 : // * @brief Initializes the QueueRegistry
47 : // */
48 : // struct DAQSinkDAQSourceTestFixture
49 : // {
50 : // DAQSinkDAQSourceTestFixture() {}
51 :
52 : // void setup()
53 : // {
54 : // iomanager::ConnectionIds_t connections;
55 : // connections.emplace_back(
56 : // iomanager::ConnectionId{ "dummy", iomanager::ServiceType::kQueue, "TimeSync", "queue://kFollyMPMCQueue:100" });
57 :
58 : // get_iomanager()->configure(connections);
59 : // }
60 :
61 : // void teardown() {
62 : // get_iomanager()->reset();
63 : // }
64 : // };
65 :
66 : // BOOST_TEST_GLOBAL_FIXTURE(DAQSinkDAQSourceTestFixture);
67 :
68 2 : BOOST_AUTO_TEST_CASE(Basics)
69 : {
70 1 : using namespace std::chrono;
71 1 : using namespace std::chrono_literals;
72 :
73 1 : const uint64_t clock_frequency_hz = 62'500'000; // NOLINT(build/unsigned)
74 1 : const double clock_frequency_Mhz = 62.5;
75 :
76 1 : const uint32_t run_num = 5; // NOLINT(build/unsigned)
77 1 : utilities::TimestampEstimatorTimeSync te(run_num, clock_frequency_hz);
78 :
79 1 : uint64_t daq_time_start = 1'000'000; // NOLINT(build/unsigned)
80 1 : auto system_time_start =
81 1 : static_cast<uint64_t>(duration_cast<microseconds>(system_clock::now().time_since_epoch()).count()); // NOLINT
82 1 : auto steady_time_start =
83 1 : static_cast<uint64_t>(duration_cast<microseconds>(steady_clock::now().time_since_epoch()).count()); // NOLINT
84 :
85 1 : DummyTimeSync ts;
86 1 : ts.daq_time = daq_time_start;
87 1 : ts.system_time = system_time_start;
88 1 : ts.sequence_number = 1;
89 1 : ts.run_number = run_num;
90 1 : ts.source_id = 12345;
91 :
92 1 : te.timesync_callback(ts);
93 :
94 101 : for (size_t i = 0; i < 100; ++i) {
95 :
96 100 : std::this_thread::sleep_for(10ms);
97 100 : auto steady_now =
98 100 : static_cast<uint64_t>(duration_cast<microseconds>(steady_clock::now().time_since_epoch()).count()); // NOLINT
99 100 : uint64_t te_now = te.get_timestamp_estimate(); // NOLINT(build/unsigned)
100 100 : auto steady_diff = static_cast<double>(steady_now - steady_time_start);
101 100 : auto te_diff = static_cast<double>(te_now - daq_time_start);
102 100 : auto dd = static_cast<int64_t>(te_diff - (steady_diff * clock_frequency_Mhz));
103 :
104 100 : BOOST_CHECK_LT(abs(dd), 1'000);
105 : }
106 1 : }
107 :
108 : // 27-May-2025, KAB: this test case is intended to verify that an instance of
109 : // the TimestampEstimatorTimeSync behaves as expected when it first starts up.
110 2 : BOOST_AUTO_TEST_CASE(StartupBehavior)
111 : {
112 1 : using namespace std::chrono;
113 1 : using namespace std::chrono_literals;
114 :
115 1 : const uint64_t clock_frequency_hz = 62'500'000; // NOLINT(build/unsigned)
116 1 : const double clock_frequency_Mhz = 62.5;
117 :
118 1 : const uint32_t run_num = 5; // NOLINT(build/unsigned)
119 1 : utilities::TimestampEstimatorTimeSync te(run_num, clock_frequency_hz);
120 :
121 1 : uint64_t daq_time_start = 1'000'000; // NOLINT(build/unsigned)
122 1 : auto system_time_start =
123 1 : static_cast<uint64_t>(duration_cast<microseconds>(system_clock::now().time_since_epoch()).count()); // NOLINT
124 1 : auto steady_time_start =
125 1 : static_cast<uint64_t>(duration_cast<microseconds>(steady_clock::now().time_since_epoch()).count()); // NOLINT
126 :
127 : // create a dummy TimeSync message for later use
128 1 : DummyTimeSync ts;
129 1 : ts.daq_time = daq_time_start;
130 1 : ts.system_time = system_time_start;
131 1 : ts.sequence_number = 1;
132 1 : ts.run_number = run_num;
133 1 : ts.source_id = 12345;
134 :
135 1 : std::atomic<bool> do_not_continue_flag{ false };
136 1 : std::atomic<bool> do_continue_flag{ true };
137 1 : std::atomic<bool> continue_flag_for_thread{ true };
138 1 : std::atomic<bool> thread_has_finished{ false };
139 1 : std::atomic<utilities::TimestampEstimatorBase::WaitStatus> return_code_from_thread_wait{
140 : utilities::TimestampEstimatorBase::kInterrupted
141 : };
142 :
143 : // spawn a thread that waits until the TSE can provide a valid timestamp
144 3 : std::function<void()> valid_timestamp_wait_func = [&]() {
145 1 : return_code_from_thread_wait = te.wait_for_valid_timestamp(continue_flag_for_thread);
146 1 : thread_has_finished = true;
147 2 : };
148 1 : auto wait_ftr = std::async(std::launch::async, valid_timestamp_wait_func);
149 :
150 : // verify that a TSE instance that hasn't received any TimeSync messages returns
151 : // a special value that indicates that it can't yet provide a valid timestamp
152 : // when we ask it for the current timestamp estimate.
153 : // Also, verify that the wait thread is still waiting.
154 11 : for (size_t i = 0; i < 10; ++i) {
155 :
156 10 : std::this_thread::sleep_for(100ms);
157 10 : uint64_t te_now = te.get_timestamp_estimate(); // NOLINT(build/unsigned)
158 10 : BOOST_CHECK_EQUAL(te_now, utilities::TimestampEstimatorBase::s_invalid_ts);
159 :
160 10 : BOOST_CHECK_EQUAL(thread_has_finished, false);
161 : }
162 :
163 : // if we ask the TSE if it has a valid timestamp, and we tell it that it doesn't
164 : // need to wait until it gets one, AND the TSE instance has not yet received
165 : // a TimeSync message, it should return immediately and tell us that it is returning
166 : // without being able to provide a valid timestamp (kInterrupted).
167 1 : BOOST_CHECK_EQUAL(te.wait_for_valid_timestamp(do_not_continue_flag), utilities::TimestampEstimatorBase::kInterrupted);
168 1 : BOOST_CHECK_EQUAL(thread_has_finished, false);
169 :
170 : // pass a TimeSync message into the TSE instance
171 1 : te.timesync_callback(ts);
172 :
173 : // verify that the wait thread has finished and it received the expected return code
174 1 : std::this_thread::sleep_for(std::chrono::milliseconds(25));
175 1 : BOOST_CHECK_EQUAL(thread_has_finished, true);
176 1 : BOOST_CHECK_EQUAL(return_code_from_thread_wait, utilities::TimestampEstimatorBase::kFinished);
177 : // if the thread has not finished, tell it to finish now
178 1 : if (!thread_has_finished) {
179 0 : continue_flag_for_thread.store(false);
180 : }
181 :
182 : // verify that the TSE instance now provides valid timestamps and those
183 : // timestamp track closely to wallclock time (computer system time)
184 11 : for (size_t i = 0; i < 10; ++i) {
185 :
186 10 : std::this_thread::sleep_for(100ms);
187 10 : auto steady_now =
188 10 : static_cast<uint64_t>(duration_cast<microseconds>(steady_clock::now().time_since_epoch()).count()); // NOLINT
189 10 : auto te_now = te.get_timestamp_estimate();
190 10 : auto steady_diff = static_cast<double>(steady_now - steady_time_start);
191 10 : auto te_diff = static_cast<double>(te_now - daq_time_start);
192 10 : auto dd = static_cast<int64_t>(te_diff - (steady_diff * clock_frequency_Mhz));
193 :
194 10 : BOOST_CHECK_LT(abs(dd), 1'000);
195 : }
196 :
197 : // now the TSE instance "wait" method should return immediately with a status
198 : // that indicates that it *does* have a valid timestamp, independent of whether
199 : // we tell it to wait for a valid timestamp or not
200 1 : BOOST_CHECK_EQUAL(te.wait_for_valid_timestamp(do_not_continue_flag), utilities::TimestampEstimatorBase::kFinished);
201 1 : BOOST_CHECK_EQUAL(te.wait_for_valid_timestamp(do_continue_flag), utilities::TimestampEstimatorBase::kFinished);
202 1 : }
203 :
204 1 : BOOST_AUTO_TEST_CASE(AdditionalTestIdeas)
205 : {
206 : // slow clock
207 : // fast clock
208 : // non-standard clock frequency
209 : // bursts and delays
210 0 : }
211 :
212 : BOOST_AUTO_TEST_SUITE_END()
|