stats_mgr_unittest.cc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. // Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <boost/shared_ptr.hpp>
  15. #include <exceptions/exceptions.h>
  16. #include <dhcp/dhcp4.h>
  17. #include <dhcp/dhcp6.h>
  18. #include <dhcp/pkt4.h>
  19. #include <dhcp/pkt6.h>
  20. #include <gtest/gtest.h>
  21. #include "../stats_mgr.h"
  22. using namespace std;
  23. using namespace isc;
  24. using namespace isc::dhcp;
  25. using namespace isc::perfdhcp;
  26. namespace {
  27. typedef StatsMgr<dhcp::Pkt4> StatsMgr4;
  28. typedef StatsMgr<dhcp::Pkt6> StatsMgr6;
  29. const uint32_t common_transid = 123;
  30. class StatsMgrTest : public ::testing::Test {
  31. public:
  32. StatsMgrTest() {
  33. }
  34. /// \brief Create DHCPv4 packet.
  35. ///
  36. /// Method creates DHCPv4 packet and updates its timestamp.
  37. ///
  38. /// \param msg_type DHCPv4 message type.
  39. /// \param transid transaction id for the packet.
  40. /// \return DHCPv4 packet.
  41. Pkt4* createPacket4(const uint8_t msg_type,
  42. const uint32_t transid) {
  43. Pkt4* pkt = new Pkt4(msg_type, transid);
  44. // Packet timestamp is normally updated by interface
  45. // manager on packets reception or send. Unit tests
  46. // do not use interface manager so we need to do it
  47. // ourselfs.
  48. pkt->updateTimestamp();
  49. return pkt;
  50. }
  51. /// \brief Create DHCPv6 packet.
  52. ///
  53. /// Method creates DHCPv6 packet and updates its timestamp.
  54. ///
  55. /// \param msg_type DHCPv6 message type.
  56. /// \param transid transaction id.
  57. /// \return DHCPv6 packet.
  58. Pkt6* createPacket6(const uint8_t msg_type,
  59. const uint32_t transid) {
  60. Pkt6* pkt = new Pkt6(msg_type, transid);
  61. // Packet timestamp is normally updated by interface
  62. // manager on packets reception or send. Unit tests
  63. // do not use interface manager so we need to do it
  64. // ourselfs.
  65. pkt->updateTimestamp();
  66. return pkt;
  67. }
  68. /// \brief Pass multiple DHCPv6 packets to Statistics Manager.
  69. ///
  70. /// Method simulates sending or receiving multiple DHCPv6 packets.
  71. ///
  72. /// \note The xchg_type parameter is passed as non-const value to avoid
  73. /// false cppcheck errors which expect enum value being passed by reference.
  74. /// This error is not reported when non-const enum is passed by value.
  75. ///
  76. /// \param stats_mgr Statistics Manager instance to be used.
  77. /// \param xchg_type packet exchange types.
  78. /// \param packet_type DHCPv6 packet type.
  79. /// \param num_packets packets to be passed to Statistics Manager.
  80. /// \param receive simulated packets are received (if true)
  81. /// or sent (if false)
  82. void passMultiplePackets6(const boost::shared_ptr<StatsMgr6> stats_mgr,
  83. StatsMgr6::ExchangeType xchg_type,
  84. const uint8_t packet_type,
  85. const int num_packets,
  86. const bool receive = false) {
  87. for (int i = 0; i < num_packets; ++i) {
  88. boost::shared_ptr<Pkt6>
  89. packet(createPacket6(packet_type, i));
  90. if (receive) {
  91. ASSERT_NO_THROW(
  92. stats_mgr->passRcvdPacket(xchg_type, packet);
  93. );
  94. } else {
  95. ASSERT_NO_THROW(
  96. stats_mgr->passSentPacket(xchg_type, packet)
  97. );
  98. }
  99. }
  100. }
  101. /// \brief Simulate DHCPv4 DISCOVER-OFFER with delay.
  102. ///
  103. /// Method simulates DHCPv4 DISCOVER-OFFER exchange. The OFFER packet
  104. /// creation is delayed by the specified number of seconds. This imposes
  105. /// different packet timestamps and affects delay counters in Statistics
  106. /// Manager.
  107. ///
  108. /// \param stats_mgr Statistics Manager instance.
  109. /// \param delay delay in seconds between DISCOVER and OFFER packets.
  110. void passDOPacketsWithDelay(const boost::shared_ptr<StatsMgr4> stats_mgr,
  111. unsigned int delay,
  112. uint32_t transid) {
  113. boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER,
  114. transid));
  115. ASSERT_NO_THROW(
  116. stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet)
  117. );
  118. // There is way to differentiate timstamps of two packets other than
  119. // sleep for before we create another packet. Packet is using current
  120. // time to update its timestamp.
  121. // Sleeping for X seconds will guarantee that delay between packets
  122. // will be greater than 1 second. Note that posix time value is
  123. // transformed to double value and it makes it hard to determine
  124. // actual value to expect.
  125. std::cout << "Sleeping for " << delay << "s to test packet delays"
  126. << std::endl;
  127. sleep(delay);
  128. boost::shared_ptr<Pkt4> rcvd_packet(createPacket4(DHCPOFFER,
  129. transid));
  130. ASSERT_NO_THROW(
  131. stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet);
  132. );
  133. // Calculate period between packets.
  134. boost::posix_time::ptime sent_time = sent_packet->getTimestamp();
  135. boost::posix_time::ptime rcvd_time = rcvd_packet->getTimestamp();
  136. ASSERT_FALSE(sent_time.is_not_a_date_time());
  137. ASSERT_FALSE(rcvd_time.is_not_a_date_time());
  138. }
  139. };
  140. TEST_F(StatsMgrTest, Constructor) {
  141. boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
  142. stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
  143. EXPECT_DOUBLE_EQ(
  144. std::numeric_limits<double>::max(),
  145. stats_mgr->getMinDelay(StatsMgr4::XCHG_DO)
  146. );
  147. EXPECT_DOUBLE_EQ(0, stats_mgr->getMaxDelay(StatsMgr4::XCHG_DO));
  148. EXPECT_EQ(0, stats_mgr->getOrphans(StatsMgr4::XCHG_DO));
  149. EXPECT_EQ(0, stats_mgr->getOrderedLookups(StatsMgr4::XCHG_DO));
  150. EXPECT_EQ(0, stats_mgr->getUnorderedLookups(StatsMgr4::XCHG_DO));
  151. EXPECT_EQ(0, stats_mgr->getSentPacketsNum(StatsMgr4::XCHG_DO));
  152. EXPECT_EQ(0, stats_mgr->getRcvdPacketsNum(StatsMgr4::XCHG_DO));
  153. EXPECT_EQ(0, stats_mgr->getCollectedNum(StatsMgr4::XCHG_DO));
  154. EXPECT_THROW(stats_mgr->getAvgDelay(StatsMgr4::XCHG_DO), InvalidOperation);
  155. EXPECT_THROW(stats_mgr->getStdDevDelay(StatsMgr4::XCHG_DO),
  156. InvalidOperation);
  157. EXPECT_THROW(stats_mgr->getAvgUnorderedLookupSetSize(StatsMgr4::XCHG_DO),
  158. InvalidOperation);
  159. }
  160. TEST_F(StatsMgrTest, Exchange) {
  161. boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
  162. boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER,
  163. common_transid));
  164. boost::shared_ptr<Pkt4> rcvd_packet(createPacket4(DHCPOFFER,
  165. common_transid));
  166. // This is expected to throw because XCHG_DO was not yet
  167. // added to Stats Manager for tracking.
  168. ASSERT_FALSE(stats_mgr->hasExchangeStats(StatsMgr4::XCHG_DO));
  169. ASSERT_FALSE(stats_mgr->hasExchangeStats(StatsMgr4::XCHG_RA));
  170. EXPECT_THROW(
  171. stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet),
  172. BadValue
  173. );
  174. EXPECT_THROW(
  175. stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet),
  176. BadValue
  177. );
  178. // Adding DISCOVER-OFFER exchanges to be tracked by Stats Manager.
  179. stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
  180. ASSERT_TRUE(stats_mgr->hasExchangeStats(StatsMgr4::XCHG_DO));
  181. ASSERT_FALSE(stats_mgr->hasExchangeStats(StatsMgr4::XCHG_RA));
  182. // The following two attempts are expected to throw because
  183. // invalid exchange types are passed (XCHG_RA instead of XCHG_DO)
  184. EXPECT_THROW(
  185. stats_mgr->passSentPacket(StatsMgr4::XCHG_RA, sent_packet),
  186. BadValue
  187. );
  188. EXPECT_THROW(
  189. stats_mgr->passRcvdPacket(StatsMgr4::XCHG_RA, rcvd_packet),
  190. BadValue
  191. );
  192. // The following two attempts are expected to run fine because
  193. // right exchange type is specified.
  194. EXPECT_NO_THROW(
  195. stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet)
  196. );
  197. EXPECT_NO_THROW(
  198. stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet)
  199. );
  200. }
  201. TEST_F(StatsMgrTest, MultipleExchanges) {
  202. boost::shared_ptr<StatsMgr6> stats_mgr(new StatsMgr6());
  203. stats_mgr->addExchangeStats(StatsMgr6::XCHG_SA);
  204. stats_mgr->addExchangeStats(StatsMgr6::XCHG_RR);
  205. // Simulate sending number of solicit packets.
  206. const int solicit_packets_num = 10;
  207. passMultiplePackets6(stats_mgr, StatsMgr6::XCHG_SA, DHCPV6_SOLICIT,
  208. solicit_packets_num);
  209. // Simulate sending number of request packets. It is important that
  210. // number of request packets is different then number of solicit
  211. // packets. We can now check if right number packets went to
  212. // the right exchange type group.
  213. const int request_packets_num = 5;
  214. passMultiplePackets6(stats_mgr, StatsMgr6::XCHG_RR, DHCPV6_REQUEST,
  215. request_packets_num);
  216. // Check if all packets are successfuly passed to packet lists.
  217. EXPECT_EQ(solicit_packets_num,
  218. stats_mgr->getSentPacketsNum(StatsMgr6::XCHG_SA));
  219. EXPECT_EQ(request_packets_num,
  220. stats_mgr->getSentPacketsNum(StatsMgr6::XCHG_RR));
  221. // Simulate reception of multiple packets for both SOLICIT-ADVERTISE
  222. // and REQUEST-REPLY exchanges. Assume no packet drops.
  223. const bool receive_packets = true;
  224. passMultiplePackets6(stats_mgr, StatsMgr6::XCHG_SA, DHCPV6_ADVERTISE,
  225. solicit_packets_num, receive_packets);
  226. passMultiplePackets6(stats_mgr, StatsMgr6::XCHG_RR, DHCPV6_REPLY,
  227. request_packets_num, receive_packets);
  228. // Verify that all received packets are counted.
  229. EXPECT_EQ(solicit_packets_num,
  230. stats_mgr->getRcvdPacketsNum(StatsMgr6::XCHG_SA));
  231. EXPECT_EQ(request_packets_num,
  232. stats_mgr->getRcvdPacketsNum(StatsMgr6::XCHG_RR));
  233. }
  234. TEST_F(StatsMgrTest, ExchangeToString) {
  235. // Test DHCPv4 specific exchange names.
  236. EXPECT_EQ("DISCOVER-OFFER",
  237. StatsMgr4::exchangeToString(StatsMgr4::XCHG_DO));
  238. EXPECT_EQ("REQUEST-ACK", StatsMgr4::exchangeToString(StatsMgr4::XCHG_RA));
  239. // Test DHCPv6 specific exchange names.
  240. EXPECT_EQ("SOLICIT-ADVERTISE",
  241. StatsMgr6::exchangeToString(StatsMgr6::XCHG_SA));
  242. EXPECT_EQ("REQUEST-REPLY", StatsMgr6::exchangeToString(StatsMgr6::XCHG_RR));
  243. EXPECT_EQ("RENEW-REPLY", StatsMgr6::exchangeToString(StatsMgr6::XCHG_RN));
  244. EXPECT_EQ("RELEASE-REPLY", StatsMgr6::exchangeToString(StatsMgr6::XCHG_RL));
  245. }
  246. TEST_F(StatsMgrTest, SendReceiveSimple) {
  247. boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
  248. boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER,
  249. common_transid));
  250. boost::shared_ptr<Pkt4> rcvd_packet(createPacket4(DHCPOFFER,
  251. common_transid));
  252. stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
  253. // The following attempt is expected to pass because the right
  254. // exchange type is used.
  255. ASSERT_NO_THROW(
  256. stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet)
  257. );
  258. // It is ok, to pass to received packets here. First one will
  259. // be matched with sent packet. The latter one will not be
  260. // matched with sent packet but orphans counter will simply
  261. // increase.
  262. ASSERT_NO_THROW(
  263. stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet)
  264. );
  265. ASSERT_NO_THROW(
  266. stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet)
  267. );
  268. EXPECT_EQ(1, stats_mgr->getOrphans(StatsMgr4::XCHG_DO));
  269. }
  270. TEST_F(StatsMgrTest, SendReceiveUnordered) {
  271. const int packets_num = 10;
  272. boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
  273. stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
  274. // Transaction ids of 10 packets to be sent and received.
  275. uint32_t transid[packets_num] =
  276. { 1, 1024, 2, 1025, 3, 1026, 4, 1027, 5, 1028 };
  277. for (int i = 0; i < packets_num; ++i) {
  278. boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER,
  279. transid[i]));
  280. ASSERT_NO_THROW(
  281. stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet)
  282. );
  283. }
  284. // We are simulating that received packets are coming in reverse order:
  285. // 1028, 5, 1027 ....
  286. for (int i = 0; i < packets_num; ++i) {
  287. boost::shared_ptr<Pkt4>
  288. rcvd_packet(createPacket4(DHCPDISCOVER,
  289. transid[packets_num - 1 - i]));
  290. ASSERT_NO_THROW(
  291. stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet);
  292. );
  293. }
  294. // All packets are expected to match (we did not drop any)
  295. EXPECT_EQ(0, stats_mgr->getOrphans(StatsMgr4::XCHG_DO));
  296. // Most of the time we have to do unordered lookups except for the last
  297. // one. Packets are removed from the sent list every time we have a match
  298. // so eventually we come up with the single packet that caching iterator
  299. // is pointing to. This is counted as ordered lookup.
  300. EXPECT_EQ(1, stats_mgr->getOrderedLookups(StatsMgr4::XCHG_DO));
  301. EXPECT_EQ(9, stats_mgr->getUnorderedLookups(StatsMgr4::XCHG_DO));
  302. }
  303. TEST_F(StatsMgrTest, Orphans) {
  304. const int packets_num = 6;
  305. boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
  306. stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
  307. // We skip every second packet to simulate drops.
  308. for (int i = 0; i < packets_num; i += 2) {
  309. boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER, i));
  310. ASSERT_NO_THROW(
  311. stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet)
  312. );
  313. }
  314. // We pass all received packets.
  315. for (int i = 0; i < packets_num; ++i) {
  316. boost::shared_ptr<Pkt4> rcvd_packet(createPacket4(DHCPOFFER, i));
  317. ASSERT_NO_THROW(
  318. stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet);
  319. );
  320. }
  321. // The half of received packets are expected not to have matching
  322. // sent packet.
  323. EXPECT_EQ(packets_num / 2, stats_mgr->getOrphans(StatsMgr4::XCHG_DO));
  324. }
  325. TEST_F(StatsMgrTest, Delays) {
  326. boost::shared_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
  327. stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO, 5);
  328. // Send DISCOVER, wait 2s and receive OFFER. This will affect
  329. // counters in Stats Manager.
  330. passDOPacketsWithDelay(stats_mgr, 2, common_transid);
  331. // Initially min delay is equal to MAX_DOUBLE. After first packets
  332. // are passed, it is expected to set to actual value.
  333. EXPECT_LT(stats_mgr->getMinDelay(StatsMgr4::XCHG_DO),
  334. std::numeric_limits<double>::max());
  335. EXPECT_GT(stats_mgr->getMinDelay(StatsMgr4::XCHG_DO), 1);
  336. // Max delay is supposed to the same value as mininimum
  337. // or maximum delay.
  338. EXPECT_GT(stats_mgr->getMaxDelay(StatsMgr4::XCHG_DO), 1);
  339. // Delay sums are now the same as minimum or maximum delay.
  340. EXPECT_GT(stats_mgr->getAvgDelay(StatsMgr4::XCHG_DO), 1);
  341. // Simulate another DISCOVER-OFFER exchange with delay between
  342. // sent and received packets. Delay is now shorter than earlier
  343. // so standard deviation of delay will now increase.
  344. const unsigned int delay2 = 1;
  345. passDOPacketsWithDelay(stats_mgr, delay2, common_transid + 1);
  346. // Standard deviation is expected to be non-zero.
  347. EXPECT_GT(stats_mgr->getStdDevDelay(StatsMgr4::XCHG_DO), 0);
  348. }
  349. TEST_F(StatsMgrTest, CustomCounters) {
  350. boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
  351. // Specify counter keys and names.
  352. const std::string too_short_key("tooshort");
  353. const std::string too_short_name("Too short packets");
  354. const std::string too_late_key("toolate");
  355. const std::string too_late_name("Packets sent too late");
  356. // Add two custom counters.
  357. stats_mgr->addCustomCounter(too_short_key, too_short_name);
  358. stats_mgr->addCustomCounter(too_late_key, too_late_name);
  359. // Increment one of the counters 10 times.
  360. const uint64_t tooshort_num = 10;
  361. for (uint64_t i = 0; i < tooshort_num; ++i) {
  362. stats_mgr->incrementCounter(too_short_key);
  363. }
  364. // Increment another counter by 5 times.
  365. const uint64_t toolate_num = 5;
  366. for (uint64_t i = 0; i < toolate_num; ++i) {
  367. stats_mgr->incrementCounter(too_late_key);
  368. }
  369. // Check counter's current value and name.
  370. StatsMgr4::CustomCounterPtr tooshort_counter =
  371. stats_mgr->getCounter(too_short_key);
  372. EXPECT_EQ(too_short_name, tooshort_counter->getName());
  373. EXPECT_EQ(tooshort_num, tooshort_counter->getValue());
  374. // Check counter's current value and name.
  375. StatsMgr4::CustomCounterPtr toolate_counter =
  376. stats_mgr->getCounter(too_late_key);
  377. EXPECT_EQ(too_late_name, toolate_counter->getName());
  378. EXPECT_EQ(toolate_num, toolate_counter->getValue());
  379. }
  380. TEST_F(StatsMgrTest, PrintStats) {
  381. std::cout << "This unit test is checking statistics printing "
  382. << "capabilities. It is expected that some counters "
  383. << "will be printed during this test. It may also "
  384. << "cause spurious errors." << std::endl;
  385. boost::shared_ptr<StatsMgr6> stats_mgr(new StatsMgr6());
  386. stats_mgr->addExchangeStats(StatsMgr6::XCHG_SA);
  387. // Simulate sending and receiving one packet. Otherwise printing
  388. // functions will complain about lack of packets.
  389. const int packets_num = 1;
  390. passMultiplePackets6(stats_mgr, StatsMgr6::XCHG_SA, DHCPV6_SOLICIT,
  391. packets_num);
  392. passMultiplePackets6(stats_mgr, StatsMgr6::XCHG_SA, DHCPV6_ADVERTISE,
  393. packets_num, true);
  394. // This function will print statistics even if packets are not
  395. // archived because it relies on counters. There is at least one
  396. // exchange needed to count the average delay and std deviation.
  397. EXPECT_NO_THROW(stats_mgr->printStats());
  398. // Printing timestamps is expected to fail because by default we
  399. // disable packets archiving mode. Without packets we can't get
  400. // timestamps.
  401. EXPECT_THROW(stats_mgr->printTimestamps(), isc::InvalidOperation);
  402. // Now, we create another statistics manager instance and enable
  403. // packets archiving mode.
  404. const bool archive_packets = true;
  405. boost::shared_ptr<StatsMgr6> stats_mgr2(new StatsMgr6(archive_packets));
  406. stats_mgr2->addExchangeStats(StatsMgr6::XCHG_SA);
  407. // Timestamps should now get printed because packets have been preserved.
  408. EXPECT_NO_THROW(stats_mgr2->printTimestamps());
  409. }
  410. }