1 // Copyright (C) 2015-2017,2021 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 
9 #include <util/stopwatch.h>
10 #include <util/stopwatch_impl.h>
11 #include <boost/date_time/posix_time/posix_time.hpp>
12 #include <gtest/gtest.h>
13 #include <unistd.h>
14 
15 namespace {
16 
17 using namespace isc;
18 using namespace isc::util;
19 using namespace boost::posix_time;
20 
21 /// @brief @c StopwatchImpl mock object.
22 ///
23 /// This class derives from the @c StopwatchImpl to override the
24 /// @c StopwatchImpl::getCurrentTime. This method is internally called by
25 /// the @c StopwatchImpl to determine the current time. By providing the
26 /// implementation of this method which returns the fixed (well known)
27 /// timestamp value we can obtain the deterministic values from the accessors
28 /// of this class.
29 ///
30 /// This class also includes some convenience methods to return the time
31 /// durations in milliseconds.
32 class StopwatchMock : public StopwatchImpl {
33 public:
34 
35     /// @brief Constructor.
36     ///
37     /// @param ref_time Reference time, i.e. the arbitrary time value from
38     /// which time is measured. The @c current_time_ value returned by the
39     /// @c StopwatchMock::getCurrentTime is initialized to this value.
40     /// Subsequent calls to the @c StopwatchMock::ffwd move the value of
41     /// the @c current_time_ forward.
42     StopwatchMock(const ptime& ref_time);
43 
44     /// @brief Fast forward time.
45     ///
46     /// Moves the value of the @c current_time_ forward by the specified
47     /// number of milliseconds (microseconds). As a result the timestamp
48     /// returned by the @c StopwatchMock::getCurrentTime moves by this value.
49     /// This simulates the time progress.
50     ///
51     /// @param ms Specifies the number of milliseconds to move current time.
52     /// @param us Specifies the number of fractional microseconds to move
53     /// current time.
54     void ffwd(const uint32_t ms, const uint32_t us = 0);
55 
56     /// @brief Returns the last duration in milliseconds.
57     uint32_t getLastDurationInMs() const;
58 
59     /// @brief Returns the total duration in milliseconds.
60     uint32_t getTotalDurationInMs() const;
61 
62 protected:
63 
64     /// @brief Returns the current time.
65     ///
66     /// This method returns the fixed @c current_time_ timestamp.
67     virtual ptime getCurrentTime() const;
68 
69 private:
70 
71     /// @brief Holds the current time to be returned by the
72     /// @c StopwatchMock::getCurrentTime.
73     ptime current_time_;
74 
75 };
76 
StopwatchMock(const ptime & ref_time)77 StopwatchMock::StopwatchMock(const ptime& ref_time)
78     : StopwatchImpl(), current_time_(ref_time) {
79 }
80 
81 void
ffwd(const uint32_t ms,const uint32_t us)82 StopwatchMock::ffwd(const uint32_t ms, const uint32_t us) {
83     current_time_ += milliseconds(ms);
84     current_time_ += microseconds(us);
85 }
86 
87 uint32_t
getLastDurationInMs() const88 StopwatchMock::getLastDurationInMs() const {
89     return (getLastDuration().total_milliseconds());
90 }
91 
92 uint32_t
getTotalDurationInMs() const93 StopwatchMock::getTotalDurationInMs() const {
94     return (getTotalDuration().total_milliseconds());
95 }
96 
97 ptime
getCurrentTime() const98 StopwatchMock::getCurrentTime() const {
99     return (current_time_);
100 }
101 
102 /// @brief Test fixture class for testing @c StopwatchImpl.
103 class StopwatchTest : public ::testing::Test {
104 public:
105 
106     /// @brief Constructor
107     StopwatchTest() = default;
108 
109     /// @brief Destructor
110     virtual ~StopwatchTest() = default;
111 
112 protected:
113 
114     /// @brief Set up the test.
115     ///
116     /// Initializes the reference time to be used to create the instances
117     /// of the @c StopwatchMock objects.
118     virtual void SetUp();
119 
120     /// @brief Holds the reference time to be used to create the instances
121     /// of the @c StopwatchMock objects.
122     ptime ref_time_;
123 };
124 
125 void
SetUp()126 StopwatchTest::SetUp() {
127     ref_time_ = microsec_clock::universal_time();
128 }
129 
130 /// This test checks the behavior of the stopwatch when it is started
131 /// and stopped multiple times. It uses the StopwatchMock object to
132 /// control the "time flow" by setting the current time to arbitrary
133 /// values using the StopwatchMock::ffwd. In addition, this test
134 /// checks that the stopwatch can be reset.
TEST_F(StopwatchTest,multipleMeasurements)135 TEST_F(StopwatchTest, multipleMeasurements) {
136     StopwatchMock stopwatch(ref_time_);
137     // The stopwatch shouldn't automatically start. The initial
138     // durations should be set to 0.
139     EXPECT_EQ(0, stopwatch.getLastDurationInMs());
140     EXPECT_EQ(0, stopwatch.getTotalDurationInMs());
141 
142     stopwatch.start();
143 
144     // Even though the stopwatch is started, the time is still set to
145     // the initial value. The durations should not be affected.
146     EXPECT_EQ(0, stopwatch.getLastDurationInMs());
147     EXPECT_EQ(0, stopwatch.getTotalDurationInMs());
148 
149     // Move the time by 10 ms.
150     stopwatch.ffwd(10);
151 
152     // It should be possible to retrieve the durations even when the
153     // stopwatch is running.
154     EXPECT_EQ(10, stopwatch.getLastDurationInMs());
155     EXPECT_EQ(10, stopwatch.getTotalDurationInMs());
156 
157     // Now stop it and make sure that the same values are returned.
158     stopwatch.stop();
159 
160     EXPECT_EQ(10, stopwatch.getLastDurationInMs());
161     EXPECT_EQ(10, stopwatch.getTotalDurationInMs());
162 
163     // Start it again, but don't move the time forward yet.
164     stopwatch.start();
165 
166     // The new duration should be 0, but the total should be equal to
167     // the previously measured duration.
168     EXPECT_EQ(0, stopwatch.getLastDurationInMs());
169     EXPECT_EQ(10, stopwatch.getTotalDurationInMs());
170 
171     // Move time by 5 ms.
172     stopwatch.ffwd(5);
173 
174     // New measured duration should be 5 ms. The total should be 15 ms.
175     EXPECT_EQ(5, stopwatch.getLastDurationInMs());
176     EXPECT_EQ(15, stopwatch.getTotalDurationInMs());
177 
178     // Stop it again and make sure the values returned are the same.
179     stopwatch.stop();
180 
181     EXPECT_EQ(5, stopwatch.getLastDurationInMs());
182     EXPECT_EQ(15, stopwatch.getTotalDurationInMs());
183 
184     // Move the time forward while the stopwatch is stopped.
185     stopwatch.ffwd(8);
186 
187     // The measured values should not be affected.
188     EXPECT_EQ(5, stopwatch.getLastDurationInMs());
189     EXPECT_EQ(15, stopwatch.getTotalDurationInMs());
190 
191     // Stop should be no-op in this case.
192     stopwatch.stop();
193 
194     EXPECT_EQ(5, stopwatch.getLastDurationInMs());
195     EXPECT_EQ(15, stopwatch.getTotalDurationInMs());
196 
197     // Start the stopwatch again.
198     stopwatch.start();
199 
200     // Move time by 3 ms.
201     stopwatch.ffwd(3);
202 
203     // Since the stopwatch is running, the measured duration should
204     // get updated again.
205     EXPECT_EQ(3, stopwatch.getLastDurationInMs());
206     EXPECT_EQ(18, stopwatch.getTotalDurationInMs());
207 
208     // Move the time by 2 ms.
209     stopwatch.ffwd(2);
210 
211     // Start should be no-op in this case.
212     stopwatch.start();
213 
214     // But the durations should be updated.
215     EXPECT_EQ(5, stopwatch.getLastDurationInMs());
216     EXPECT_EQ(20, stopwatch.getTotalDurationInMs());
217 
218     // Make sure we can reset.
219     stopwatch.reset();
220 
221     EXPECT_EQ(0, stopwatch.getLastDurationInMs());
222     EXPECT_EQ(0, stopwatch.getTotalDurationInMs());
223 }
224 
225 // This test checks that the stopwatch works when the real clock is in use.
TEST_F(StopwatchTest,realTime)226 TEST_F(StopwatchTest, realTime) {
227     // Initially, the measured time should be 0.
228     Stopwatch stopwatch;
229     EXPECT_EQ(0, stopwatch.getLastMilliseconds());
230     EXPECT_EQ(0, stopwatch.getTotalMilliseconds());
231 
232     // Start the stopwatch.
233     stopwatch.start();
234 
235     // Sleep for 1 ms. The stopwatch should measure this duration.
236     usleep(1000);
237 
238     stopwatch.stop();
239 
240     // The measured duration should be greater or equal 1 ms.
241     long current_duration = stopwatch.getLastMilliseconds();
242     EXPECT_GE(current_duration, 1);
243     EXPECT_EQ(current_duration, stopwatch.getTotalMilliseconds());
244 
245     // Sleep for another 2 ms while the stopwatch is in the stopped state.
246     usleep(2000);
247 
248     // In the stopped state, we should still have old durations measured.
249     EXPECT_EQ(current_duration, stopwatch.getLastMilliseconds());
250     EXPECT_EQ(current_duration, stopwatch.getTotalMilliseconds());
251 
252     // Start it again.
253     stopwatch.start();
254 
255     // Sleep for 1 ms.
256     usleep(1000);
257 
258     // The durations should get updated as appropriate.
259     EXPECT_GE(stopwatch.getLastMilliseconds(), 1);
260     EXPECT_GE(stopwatch.getTotalMilliseconds(), 2);
261 }
262 
263 // Make sure that we can obtain the durations as microseconds.
TEST_F(StopwatchTest,getLastMicroseconds)264 TEST_F(StopwatchTest, getLastMicroseconds) {
265     Stopwatch stopwatch;
266     stopwatch.start();
267 
268     usleep(1000);
269 
270     stopwatch.stop();
271 
272     long current_duration = stopwatch.getLastMicroseconds();
273     EXPECT_GE(current_duration, 1000);
274     EXPECT_EQ(current_duration, stopwatch.getTotalMicroseconds());
275 }
276 
277 // Make sure that we can use the "autostart" option to start the time
278 // measurement in the constructor.
TEST_F(StopwatchTest,autostart)279 TEST_F(StopwatchTest, autostart) {
280     Stopwatch stopwatch(true);
281     usleep(1000);
282 
283     stopwatch.stop();
284 
285     EXPECT_GE(stopwatch.getLastMilliseconds(), 1);
286     EXPECT_EQ(stopwatch.getLastMilliseconds(), stopwatch.getTotalMilliseconds());
287 }
288 
289 // Make sure that the conversion to the loggable string works as expected.
TEST_F(StopwatchTest,logFormat)290 TEST_F(StopwatchTest, logFormat) {
291     time_duration duration = microseconds(223043);
292     EXPECT_EQ("223.043 ms", StopwatchImpl::logFormat(duration));
293 
294     duration = microseconds(1234);
295     EXPECT_EQ("1.234 ms", StopwatchImpl::logFormat(duration));
296 
297     duration = microseconds(2000);
298     EXPECT_EQ("2.000 ms", StopwatchImpl::logFormat(duration));
299 }
300 
301 } // end of anonymous namespace
302