diff options
author | Michal Nowikowski <godfryd@isc.org> | 2019-02-12 15:55:51 +0100 |
---|---|---|
committer | Michal Nowikowski <godfryd@isc.org> | 2019-02-19 21:54:31 +0100 |
commit | ad43417ae266171b7d68585ab60729506b032be0 (patch) | |
tree | 29633f679394c8238ea2e133209e9b000878ea08 | |
parent | [448-update-cb-cmds-to-handle-parameter-types] Extended SimpleRequiredKeyword... (diff) | |
download | kea-ad43417ae266171b7d68585ab60729506b032be0.tar.xz kea-ad43417ae266171b7d68585ab60729506b032be0.zip |
added avalanche scenario to perfdhcp
-rw-r--r-- | src/bin/perfdhcp/Makefile.am | 4 | ||||
-rw-r--r-- | src/bin/perfdhcp/avalanche_scen.cc | 157 | ||||
-rw-r--r-- | src/bin/perfdhcp/avalanche_scen.h | 48 | ||||
-rw-r--r-- | src/bin/perfdhcp/basic_scen.cc | 301 | ||||
-rw-r--r-- | src/bin/perfdhcp/basic_scen.h | 64 | ||||
-rw-r--r-- | src/bin/perfdhcp/command_options.cc | 42 | ||||
-rw-r--r-- | src/bin/perfdhcp/command_options.h | 17 | ||||
-rw-r--r-- | src/bin/perfdhcp/main.cc | 13 | ||||
-rw-r--r-- | src/bin/perfdhcp/perf_socket.cc | 115 | ||||
-rw-r--r-- | src/bin/perfdhcp/perf_socket.h | 30 | ||||
-rw-r--r-- | src/bin/perfdhcp/receiver.h | 7 | ||||
-rw-r--r-- | src/bin/perfdhcp/stats_mgr.cc | 358 | ||||
-rw-r--r-- | src/bin/perfdhcp/stats_mgr.h | 1460 | ||||
-rw-r--r-- | src/bin/perfdhcp/test_control.cc | 768 | ||||
-rw-r--r-- | src/bin/perfdhcp/test_control.h | 248 | ||||
-rw-r--r-- | src/bin/perfdhcp/tests/stats_mgr_unittest.cc | 18 |
16 files changed, 1888 insertions, 1762 deletions
diff --git a/src/bin/perfdhcp/Makefile.am b/src/bin/perfdhcp/Makefile.am index c1e4531912..436f7c3910 100644 --- a/src/bin/perfdhcp/Makefile.am +++ b/src/bin/perfdhcp/Makefile.am @@ -23,10 +23,12 @@ libperfdhcp_la_SOURCES += perf_pkt4.cc perf_pkt4.h libperfdhcp_la_SOURCES += packet_storage.h libperfdhcp_la_SOURCES += pkt_transform.cc pkt_transform.h libperfdhcp_la_SOURCES += rate_control.cc rate_control.h -libperfdhcp_la_SOURCES += stats_mgr.h +libperfdhcp_la_SOURCES += stats_mgr.cc stats_mgr.h libperfdhcp_la_SOURCES += test_control.cc test_control.h libperfdhcp_la_SOURCES += receiver.cc receiver.h libperfdhcp_la_SOURCES += perf_socket.cc perf_socket.h +libperfdhcp_la_SOURCES += avalanche_scen.cc avalanche_scen.h +libperfdhcp_la_SOURCES += basic_scen.cc basic_scen.h sbin_PROGRAMS = perfdhcp perfdhcp_SOURCES = main.cc diff --git a/src/bin/perfdhcp/avalanche_scen.cc b/src/bin/perfdhcp/avalanche_scen.cc new file mode 100644 index 0000000000..91f69da033 --- /dev/null +++ b/src/bin/perfdhcp/avalanche_scen.cc @@ -0,0 +1,157 @@ +// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <perfdhcp/avalanche_scen.h> + + +#include <boost/date_time/posix_time/posix_time.hpp> + +using namespace std; +using namespace boost::posix_time; +using namespace isc; +using namespace isc::dhcp; + + +namespace isc { +namespace perfdhcp { + +int +AvalancheScen::resendPackets(ExchangeType xchg_type) { + CommandOptions& options = CommandOptions::instance(); + const StatsMgr& stats_mgr(tc_.getStatsMgr()); + auto sent_packets_its = stats_mgr.getSentPackets(xchg_type); + auto begin_it = std::get<0>(sent_packets_its); + auto end_it = std::get<1>(sent_packets_its); + + auto& retrans = retransmissions_[xchg_type]; + + int still_left_cnt = 0; + int resent_cnt = 0; + for (auto it = begin_it; it != end_it; ++it) { + still_left_cnt++; + dhcp::PktPtr pkt = *it; + auto trans_id = pkt->getTransid(); + int rx_times = 0; + auto r_it = retrans.find(trans_id); + if (r_it != retrans.end()) { + rx_times = (*r_it).second; + } + + int delay = (1 << rx_times); // in seconds + if (delay > 64) { + delay = 64; + } + delay *= 1000; // to miliseconds + delay += random() % 2000 - 1000; // adjust by random from -1000..1000 range + auto now = microsec_clock::universal_time(); + if (now - pkt->getTimestamp() > milliseconds(delay)) { + //if (rx_times > 2) { + // std::cout << xchg_type << " RX " << trans_id << ", times " << rx_times << ", delay " << delay << std::endl; + //} + resent_cnt++; + total_resent_++; + + if (options.getIpVersion() == 4) { + Pkt4Ptr pkt4 = boost::dynamic_pointer_cast<Pkt4>(pkt); + IfaceMgr::instance().send(pkt4); + } else { + Pkt6Ptr pkt6 = boost::dynamic_pointer_cast<Pkt6>(pkt); + IfaceMgr::instance().send(pkt6); + } + //std::cout << "resent " << xchg_type << " " << pkt->getTransid() << " @ " << pkt->getTimestamp() << std::endl; + + rx_times++; + retrans[trans_id] = rx_times; + } + } + if (resent_cnt > 0) { + auto now = microsec_clock::universal_time(); + std::cout << now << " " << xchg_type << ": still waiting for " << still_left_cnt << " answers, resent " << resent_cnt << ", retrying " << retrans.size() << std::endl; + } + return still_left_cnt; +} + + + +int +AvalancheScen::run() { + CommandOptions& options = CommandOptions::instance(); + + uint32_t clients_num = options.getClientsNum() == 0 ? + 1 : options.getClientsNum(); + + // StatsMgr& stats_mgr(tc_.getStatsMgr()); + + tc_.start(); + + auto start = microsec_clock::universal_time(); + + // Initiate new DHCP packet exchanges. + tc_.sendPackets(clients_num); + + auto now = microsec_clock::universal_time(); + auto prev_cycle_time = now; + for (;;) { + // Pull some packets from receiver thread, process them, update some stats + // and respond to the server if needed. + tc_.consumeReceivedPackets(); + usleep(100); + + now = microsec_clock::universal_time(); + if (now - prev_cycle_time > milliseconds(200)) { // check if 0.2s elapsed + prev_cycle_time = now; + auto still_left_cnt_do = resendPackets(ExchangeType::DO); + auto still_left_cnt_ra = resendPackets(ExchangeType::RA); + if (still_left_cnt_do + still_left_cnt_ra == 0) { + break; + } + } + + // If we are sending Renews to the server, the Reply packets are cached + // so as leases for which we send Renews can be identified. The major + // issue with this approach is that most of the time we are caching + // more packets than we actually need. This function removes excessive + // Reply messages to reduce the memory and CPU utilization. Note that + // searches in the long list of Reply packets increases CPU utilization. + //tc_.cleanCachedPackets(); + } + + auto stop = microsec_clock::universal_time(); + boost::posix_time::time_period duration(start, stop); + + tc_.stop(); + + tc_.printStats(); + + // // Print packet timestamps + // if (testDiags('t')) { + // stats_mgr.printTimestamps(); + // } + + // Print server id. + if (testDiags('s') && tc_.serverIdReceived()) { + std::cout << "Server id: " << tc_.getServerId() << std::endl; + } + + // Diagnostics flag 'e' means show exit reason. + if (testDiags('e')) { + std::cout << "Interrupted" << std::endl; + } + + std::cout << "It took " << duration.length() << " to provision " << clients_num + << " clients. " << (clients_num * 2 + total_resent_) + << " packets were sent, " << total_resent_ + << " retransmissions needed, received " << (clients_num * 2) + << " responses." << std::endl; + + int ret_code = 0; + // // Check if any packet drops occurred. + // ret_code = stats_mgr.droppedPackets() ? 3 : 0; + return (ret_code); +} + +} +} diff --git a/src/bin/perfdhcp/avalanche_scen.h b/src/bin/perfdhcp/avalanche_scen.h new file mode 100644 index 0000000000..66cf040286 --- /dev/null +++ b/src/bin/perfdhcp/avalanche_scen.h @@ -0,0 +1,48 @@ +// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef AVALANCHE_SCEN_H +#define AVALANCHE_SCEN_H + +#include <config.h> + +#include <perfdhcp/test_control.h> + + +namespace isc { +namespace perfdhcp { + + +class AvalancheScen : public boost::noncopyable { +public: + AvalancheScen(): tc_(true), total_resent_(0) {}; + + /// brief\ Run performance test. + /// + /// Method runs whole performance test. Command line options must + /// be parsed prior to running this function. Otherwise function will + /// throw exception. + /// + /// \throw isc::InvalidOperation if command line options are not parsed. + /// \throw isc::Unexpected if internal Test Controller error occurred. + /// \return error_code, 3 if number of received packets is not equal + /// to number of sent packets, 0 if everything is ok. + int run(); + +private: + TestControl tc_; + + std::unordered_map<ExchangeType, std::unordered_map<uint32_t, int>> retransmissions_; + int total_resent_; + + int resendPackets(ExchangeType xchg_type); + +}; + +} +} + +#endif // AVALANCHE_SCEN_H diff --git a/src/bin/perfdhcp/basic_scen.cc b/src/bin/perfdhcp/basic_scen.cc new file mode 100644 index 0000000000..cda35f644c --- /dev/null +++ b/src/bin/perfdhcp/basic_scen.cc @@ -0,0 +1,301 @@ +// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <perfdhcp/basic_scen.h> + + +#include <boost/date_time/posix_time/posix_time.hpp> + +using namespace std; +using namespace boost::posix_time; +using namespace isc; +using namespace isc::dhcp; + + +namespace isc { +namespace perfdhcp { + + +bool +BasicScen::checkExitConditions() { + if (tc_.interrupted()) { + return (true); + } + + const StatsMgr& stats_mgr(tc_.getStatsMgr()); + + CommandOptions& options = CommandOptions::instance(); + bool test_period_reached = false; + // Check if test period passed. + if (options.getPeriod() != 0) { + time_period period(stats_mgr.getTestPeriod()); + if (period.length().total_seconds() >= options.getPeriod()) { + test_period_reached = true; + } + } + if (test_period_reached) { + if (testDiags('e')) { + std::cout << "reached test-period." << std::endl; + } + if (!tc_.waitToExit()) { + return true; + } + } + + bool max_requests = false; + // Check if we reached maximum number of DISCOVER/SOLICIT sent. + if (options.getNumRequests().size() > 0) { + if (options.getIpVersion() == 4) { + if (stats_mgr.getSentPacketsNum(ExchangeType::DO) >= + options.getNumRequests()[0]) { + max_requests = true; + } + } else if (options.getIpVersion() == 6) { + if (stats_mgr.getSentPacketsNum(ExchangeType::SA) >= + options.getNumRequests()[0]) { + max_requests = true; + } + } + } + // Check if we reached maximum number REQUEST packets. + if (options.getNumRequests().size() > 1) { + if (options.getIpVersion() == 4) { + if (stats_mgr.getSentPacketsNum(ExchangeType::RA) >= + options.getNumRequests()[1]) { + max_requests = true; + } + } else if (options.getIpVersion() == 6) { + if (stats_mgr.getSentPacketsNum(ExchangeType::RR) >= + options.getNumRequests()[1]) { + max_requests = true; + } + } + } + if (max_requests) { + if (testDiags('e')) { + std::cout << "Reached max requests limit." << std::endl; + } + if (!tc_.waitToExit()) { + return true; + } + } + + // Check if we reached maximum number of drops of OFFER/ADVERTISE packets. + bool max_drops = false; + if (options.getMaxDrop().size() > 0) { + if (options.getIpVersion() == 4) { + if (stats_mgr.getDroppedPacketsNum(ExchangeType::DO) >= + options.getMaxDrop()[0]) { + max_drops = true; + } + } else if (options.getIpVersion() == 6) { + if (stats_mgr.getDroppedPacketsNum(ExchangeType::SA) >= + options.getMaxDrop()[0]) { + max_drops = true; + } + } + } + // Check if we reached maximum number of drops of ACK/REPLY packets. + if (options.getMaxDrop().size() > 1) { + if (options.getIpVersion() == 4) { + if (stats_mgr.getDroppedPacketsNum(ExchangeType::RA) >= + options.getMaxDrop()[1]) { + max_drops = true; + } + } else if (options.getIpVersion() == 6) { + if (stats_mgr.getDroppedPacketsNum(ExchangeType::RR) >= + options.getMaxDrop()[1]) { + max_drops = true; + } + } + } + if (max_drops) { + if (testDiags('e')) { + std::cout << "Reached maximum drops number." << std::endl; + } + if (!tc_.waitToExit()) { + return true; + } + } + + // Check if we reached maximum drops percentage of OFFER/ADVERTISE packets. + bool max_pdrops = false; + if (options.getMaxDropPercentage().size() > 0) { + if (options.getIpVersion() == 4) { + if ((stats_mgr.getSentPacketsNum(ExchangeType::DO) > 10) && + ((100. * stats_mgr.getDroppedPacketsNum(ExchangeType::DO) / + stats_mgr.getSentPacketsNum(ExchangeType::DO)) >= + options.getMaxDropPercentage()[0])) { + max_pdrops = true; + + } + } else if (options.getIpVersion() == 6) { + if ((stats_mgr.getSentPacketsNum(ExchangeType::SA) > 10) && + ((100. * stats_mgr.getDroppedPacketsNum(ExchangeType::SA) / + stats_mgr.getSentPacketsNum(ExchangeType::SA)) >= + options.getMaxDropPercentage()[0])) { + max_pdrops = true; + } + } + } + // Check if we reached maximum drops percentage of ACK/REPLY packets. + if (options.getMaxDropPercentage().size() > 1) { + if (options.getIpVersion() == 4) { + if ((stats_mgr.getSentPacketsNum(ExchangeType::RA) > 10) && + ((100. * stats_mgr.getDroppedPacketsNum(ExchangeType::RA) / + stats_mgr.getSentPacketsNum(ExchangeType::RA)) >= + options.getMaxDropPercentage()[1])) { + max_pdrops = true; + } + } else if (options.getIpVersion() == 6) { + if ((stats_mgr.getSentPacketsNum(ExchangeType::RR) > 10) && + ((100. * stats_mgr.getDroppedPacketsNum(ExchangeType::RR) / + stats_mgr.getSentPacketsNum(ExchangeType::RR)) >= + options.getMaxDropPercentage()[1])) { + max_pdrops = true; + } + } + } + if (max_pdrops) { + if (testDiags('e')) { + std::cout << "Reached maximum percentage of drops." << std::endl; + } + if (!tc_.waitToExit()) { + return true; + } + } + return (false); +} + +int +BasicScen::run() { + CommandOptions& options = CommandOptions::instance(); + + basic_rate_control_.setRate(options.getRate()); + renew_rate_control_.setRate(options.getRenewRate()); + release_rate_control_.setRate(options.getReleaseRate()); + + StatsMgr& stats_mgr(tc_.getStatsMgr()); + + // Preload server with the number of packets. + if (options.getPreload() > 0) { + tc_.sendPackets(options.getPreload(), true); + } + + // Fork and run command specified with -w<wrapped-command> + if (!options.getWrapped().empty()) { + tc_.runWrapped(); + } + + tc_.start(); + + for (;;) { + // Calculate number of packets to be sent to stay + // catch up with rate. + uint64_t packets_due = basic_rate_control_.getOutboundMessageCount(); + if ((packets_due == 0) && testDiags('i')) { + stats_mgr.incrementCounter("shortwait"); + } + + // Pull some packets from receiver thread, process them, update some stats + // and respond to the server if needed. + auto pkt_count = tc_.consumeReceivedPackets(); + + // If there is nothing to do in this loop iteration then do some sleep to make + // CPU idle for a moment, to not consume 100% CPU all the time + // but only if it is not that high request rate expected. + if (options.getRate() < 10000 && packets_due == 0 && pkt_count == 0) { + /// @todo: need to implement adaptive time here, so the sleep time + /// is not fixed, but adjusts to current situation. + usleep(1); + } + + // If test period finished, maximum number of packet drops + // has been reached or test has been interrupted we have to + // finish the test. + if (checkExitConditions()) { + break; + } + + // Initiate new DHCP packet exchanges. + tc_.sendPackets(packets_due); + + // If -f<renew-rate> option was specified we have to check how many + // Renew packets should be sent to catch up with a desired rate. + if (options.getRenewRate() != 0) { + uint64_t renew_packets_due = + renew_rate_control_.getOutboundMessageCount(); + + // Send multiple renews to satisfy the desired rate. + if (options.getIpVersion() == 4) { + tc_.sendMultipleRequests(renew_packets_due); + } else { + tc_.sendMultipleMessages6(DHCPV6_RENEW, renew_packets_due); + } + } + + // If -F<release-rate> option was specified we have to check how many + // Release messages should be sent to catch up with a desired rate. + if ((options.getIpVersion() == 6) && (options.getReleaseRate() != 0)) { + uint64_t release_packets_due = + release_rate_control_.getOutboundMessageCount(); + // Send Release messages. + tc_.sendMultipleMessages6(DHCPV6_RELEASE, release_packets_due); + } + + // Report delay means that user requested printing number + // of sent/received/dropped packets repeatedly. + if (options.getReportDelay() > 0) { + tc_.printIntermediateStats(); + } + + // If we are sending Renews to the server, the Reply packets are cached + // so as leases for which we send Renews can be identified. The major + // issue with this approach is that most of the time we are caching + // more packets than we actually need. This function removes excessive + // Reply messages to reduce the memory and CPU utilization. Note that + // searches in the long list of Reply packets increases CPU utilization. + tc_.cleanCachedPackets(); + } + + tc_.stop(); + + tc_.printStats(); + + if (!options.getWrapped().empty()) { + // true means that we execute wrapped command with 'stop' argument. + tc_.runWrapped(true); + } + + // Print packet timestamps + if (testDiags('t')) { + stats_mgr.printTimestamps(); + } + + // Print server id. + if (testDiags('s') && tc_.serverIdReceived()) { + std::cout << "Server id: " << tc_.getServerId() << std::endl; + } + + // Diagnostics flag 'e' means show exit reason. + if (testDiags('e')) { + std::cout << "Interrupted" << std::endl; + } + // Print packet templates. Even if -T options have not been specified the + // dynamically build packet will be printed if at least one has been sent. + if (testDiags('T')) { + tc_.printTemplates(); + } + + int ret_code = 0; + // Check if any packet drops occurred. + ret_code = stats_mgr.droppedPackets() ? 3 : 0; + return (ret_code); +} + + +} +} diff --git a/src/bin/perfdhcp/basic_scen.h b/src/bin/perfdhcp/basic_scen.h new file mode 100644 index 0000000000..7a5d115d4e --- /dev/null +++ b/src/bin/perfdhcp/basic_scen.h @@ -0,0 +1,64 @@ +// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef BASIC_SCEN_H +#define BASIC_SCEN_H + +#include <config.h> + +#include <perfdhcp/test_control.h> + + +namespace isc { +namespace perfdhcp { + + +class BasicScen : public boost::noncopyable { +public: + BasicScen() : tc_(false) {}; + + /// \brief Check if test exit conditions fulfilled. + /// + /// Method checks if the test exit conditions are fulfilled. + /// Exit conditions are checked periodically from the + /// main loop. Program should break the main loop when + /// this method returns true. It is calling function + /// responsibility to break main loop gracefully and + /// cleanup after test execution. + /// + /// \return true if any of the exit conditions is fulfilled. + bool checkExitConditions(); + + /// brief\ Run performance test. + /// + /// Method runs whole performance test. Command line options must + /// be parsed prior to running this function. Otherwise function will + /// throw exception. + /// + /// \throw isc::InvalidOperation if command line options are not parsed. + /// \throw isc::Unexpected if internal Test Controller error occurred. + /// \return error_code, 3 if number of received packets is not equal + /// to number of sent packets, 0 if everything is ok. + int run(); + +private: + TestControl tc_; + + /// \brief A rate control class for Discover and Solicit messages. + RateControl basic_rate_control_; + /// \brief A rate control class for Renew messages. + RateControl renew_rate_control_; + /// \brief A rate control class for Release messages. + RateControl release_rate_control_; + + int resendPackets(ExchangeType xchg_type); + +}; + +} +} + +#endif // BASIC_SCEN_H diff --git a/src/bin/perfdhcp/command_options.cc b/src/bin/perfdhcp/command_options.cc index eb562d97d2..60397f6b58 100644 --- a/src/bin/perfdhcp/command_options.cc +++ b/src/bin/perfdhcp/command_options.cc @@ -24,6 +24,7 @@ #include <unistd.h> #include <fstream> #include <thread> +#include <getopt.h> #ifdef HAVE_OPTRESET extern int optreset; @@ -161,6 +162,7 @@ CommandOptions::reset() { } else { single_thread_mode_ = false; } + scenario_ = Scenario::BASIC; } bool @@ -209,6 +211,8 @@ CommandOptions::parse(int argc, char** const argv, bool print_cmd_line) { return (help_or_version_mode); } +const int LONG_OPT_SCENARIO = 300; + bool CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) { int opt = 0; // Subsequent options returned by getopt() @@ -225,10 +229,17 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) { stream << "perfdhcp"; int num_mac_list_files = 0; + struct option long_options[] = { + {"scenario", required_argument, 0, LONG_OPT_SCENARIO}, + {0, 0, 0, 0} + }; + // In this section we collect argument values from command line // they will be tuned and validated elsewhere - while((opt = getopt(argc, argv, "hv46A:r:t:R:b:n:p:d:D:l:P:a:L:N:M:" - "s:iBc1T:X:O:o:E:S:I:x:W:w:e:f:F:g:")) != -1) { + while((opt = getopt_long(argc, argv, + "hv46A:r:t:R:b:n:p:d:D:l:P:a:L:N:M:s:iBc1" + "T:X:O:o:E:S:I:x:W:w:e:f:F:g:", + long_options, NULL)) != -1) { stream << " -" << static_cast<char>(opt); if (optarg) { stream << " " << optarg; @@ -544,6 +555,17 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) { xid_offset_.push_back(offset_arg); break; + case LONG_OPT_SCENARIO: { + auto optarg_text = std::string(optarg); + if (optarg_text == "basic") { + scenario_ = Scenario::BASIC; + } else if (optarg_text == "avalanche") { + scenario_ = Scenario::AVALANCHE; + } else { + isc_throw(InvalidParameter, "scenario value '" << optarg << "' is wrong - should be 'basic' or 'avalanche'"); + } + break; + } default: isc_throw(isc::InvalidParameter, "wrong command line option"); } @@ -594,6 +616,12 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) { std::cout << "Running: " << stream.str() << std::endl; } + if (scenario_ == Scenario::BASIC) { + std::cout << "Scenario: basic." << std::endl; + } else if (scenario_ == Scenario::AVALANCHE) { + std::cout << "Scenario: avalanche." << std::endl; + } + if (!isSingleThreaded()) { std::cout << "Multi-thread mode enabled." << std::endl; } @@ -1231,5 +1259,15 @@ CommandOptions::version() const { std::cout << "VERSION: " << VERSION << std::endl; } +bool +testDiags(const char diag) { + std::string diags(CommandOptions::instance().getDiags()); + if (diags.find(diag) != std::string::npos) { + return (true); + } + return (false); +} + + } // namespace perfdhcp } // namespace isc diff --git a/src/bin/perfdhcp/command_options.h b/src/bin/perfdhcp/command_options.h index deb2808267..efbfdafbfb 100644 --- a/src/bin/perfdhcp/command_options.h +++ b/src/bin/perfdhcp/command_options.h @@ -17,6 +17,11 @@ namespace isc { namespace perfdhcp { +enum class Scenario { + BASIC, + AVALANCHE +}; + /// \brief Command Options. /// /// This class is responsible for parsing the command-line and storing the @@ -348,6 +353,8 @@ public: /// \return true if single-threaded mode is enabled. bool isSingleThreaded() const { return single_thread_mode_; } + Scenario getScenario() const { return scenario_; } + /// \brief Returns server name. /// /// \return server name. @@ -654,8 +661,18 @@ private: /// @brief Option to switch modes between single-threaded and multi-threaded. bool single_thread_mode_; + + /// @brief Selected performance scenario. Default is basic. + Scenario scenario_; }; +/// \brief Find if diagnostic flag has been set. +/// +/// \param diag diagnostic flag (a,e,i,s,r,t,T). +/// \return true if diagnostics flag has been set. +bool +testDiags(const char diag); + } // namespace perfdhcp } // namespace isc diff --git a/src/bin/perfdhcp/main.cc b/src/bin/perfdhcp/main.cc index 7548ee76cd..2ab854208a 100644 --- a/src/bin/perfdhcp/main.cc +++ b/src/bin/perfdhcp/main.cc @@ -6,7 +6,8 @@ #include <config.h> -#include <perfdhcp/test_control.h> +#include <perfdhcp/avalanche_scen.h> +#include <perfdhcp/basic_scen.h> #include <perfdhcp/command_options.h> #include <exceptions/exceptions.h> @@ -43,8 +44,14 @@ main(int argc, char* argv[]) { return (ret_code); } try{ - TestControl& test_control = TestControl::instance(); - ret_code = test_control.run(); + auto scenario = command_options.getScenario(); + if (scenario == Scenario::BASIC) { + BasicScen scen; + ret_code = scen.run(); + } else if (scenario == Scenario::AVALANCHE) { + AvalancheScen scen; + ret_code = scen.run(); + } } catch (std::exception& e) { ret_code = 1; std::cerr << "Error running perfdhcp: " << e.what() << std::endl; diff --git a/src/bin/perfdhcp/perf_socket.cc b/src/bin/perfdhcp/perf_socket.cc index c5b01cfa2e..e9a55dfa2b 100644 --- a/src/bin/perfdhcp/perf_socket.cc +++ b/src/bin/perfdhcp/perf_socket.cc @@ -6,25 +6,126 @@ #include <perfdhcp/perf_socket.h> +#include <perfdhcp/command_options.h> #include <dhcp/iface_mgr.h> +#include <asiolink/io_address.h> #include <boost/foreach.hpp> using namespace isc::dhcp; +using namespace isc::asiolink; namespace isc { namespace perfdhcp { -PerfSocket::PerfSocket(const int socket) : -SocketInfo(asiolink::IOAddress("127.0.0.1"), 0, socket), - ifindex_(0), valid_(true) { - try { - initSocketData(); - } catch (const Exception&) { - valid_ = false; +PerfSocket::PerfSocket() : + SocketInfo(asiolink::IOAddress("127.0.0.1"), 0, openSocket()), + ifindex_(0) +{ + initSocketData(); +} + + +int +PerfSocket::openSocket() const { + CommandOptions& options = CommandOptions::instance(); + std::string localname = options.getLocalName(); + std::string servername = options.getServerName(); + uint16_t port = options.getLocalPort(); + int sock = 0; + + uint8_t family = (options.getIpVersion() == 6) ? AF_INET6 : AF_INET; + IOAddress remoteaddr(servername); + + // Check for mismatch between IP option and server address + if (family != remoteaddr.getFamily()) { + isc_throw(InvalidParameter, + "Values for IP version: " << + static_cast<unsigned int>(options.getIpVersion()) << + " and server address: " << servername << " are mismatched."); + } + + if (port == 0) { + if (family == AF_INET6) { + // need server port (547) because the server is acting as a relay agent + port = DHCP6_CLIENT_PORT; + // if acting as a relay agent change port. + if (options.isUseRelayedV6()) { + port = DHCP6_SERVER_PORT; + } + } else if (options.getIpVersion() == 4) { + port = 67; /// @todo: find out why port 68 is wrong here. + } + } + + // Local name is specified along with '-l' option. + // It may point to interface name or local address. + if (!localname.empty()) { + // CommandOptions should be already aware whether local name + // is interface name or address because it uses IfaceMgr to + // scan interfaces and get's their names. + if (options.isInterface()) { + sock = IfaceMgr::instance().openSocketFromIface(localname, + port, + family); + } else { + IOAddress localaddr(localname); + sock = IfaceMgr::instance().openSocketFromAddress(localaddr, + port); + } + } else if (!servername.empty()) { + // If only server name is given we will need to try to resolve + // the local address to bind socket to based on remote address. + sock = IfaceMgr::instance().openSocketFromRemoteAddress(remoteaddr, + port); + } + if (sock <= 0) { + isc_throw(BadValue, "unable to open socket to communicate with " + "DHCP server"); + } + + // IfaceMgr does not set broadcast option on the socket. We rely + // on CommandOptions object to find out if socket has to have + // broadcast enabled. + if ((options.getIpVersion() == 4) && options.isBroadcast()) { + int broadcast_enable = 1; + int ret = setsockopt(sock, SOL_SOCKET, SO_BROADCAST, + &broadcast_enable, sizeof(broadcast_enable)); + if (ret < 0) { + isc_throw(InvalidOperation, + "unable to set broadcast option on the socket"); + } + } else if (options.getIpVersion() == 6) { + // If remote address is multicast we need to enable it on + // the socket that has been created. + if (remoteaddr.isV6Multicast()) { + int hops = 1; + int ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, + &hops, sizeof(hops)); + // If user specified interface name with '-l' the + // IPV6_MULTICAST_IF has to be set. + if ((ret >= 0) && options.isInterface()) { + IfacePtr iface = + IfaceMgr::instance().getIface(options.getLocalName()); + if (iface == NULL) { + isc_throw(Unexpected, "unknown interface " + << options.getLocalName()); + } + int idx = iface->getIndex(); + ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, + &idx, sizeof(idx)); + } + if (ret < 0) { + isc_throw(InvalidOperation, + "unable to enable multicast on socket " << sock + << ". errno = " << errno); + } + } } + + return (sock); } PerfSocket::~PerfSocket() { diff --git a/src/bin/perfdhcp/perf_socket.h b/src/bin/perfdhcp/perf_socket.h index de593d317f..a49cc92e32 100644 --- a/src/bin/perfdhcp/perf_socket.h +++ b/src/bin/perfdhcp/perf_socket.h @@ -25,19 +25,13 @@ namespace perfdhcp { struct PerfSocket : public dhcp::SocketInfo { /// Interface index. uint16_t ifindex_; - /// Is socket valid. It will not be valid if the provided socket - /// descriptor does not point to valid socket. - bool valid_; /// \brief Constructor of socket wrapper class. /// /// This constructor uses provided socket descriptor to /// find the name of the interface where socket has been - /// bound to. If provided socket descriptor is invalid then - /// valid_ field is set to false; - /// - /// \param socket socket descriptor. - PerfSocket(const int socket); + /// bound to. + PerfSocket(); /// \brief Destructor of the socket wrapper class. /// @@ -53,6 +47,26 @@ private: /// \throw isc::BadValue if interface for specified socket /// descriptor does not exist. void initSocketData(); + + /// \brief Open socket to communicate with DHCP server. + /// + /// Method opens socket and binds it to local address. Function will + /// use either interface name, local address or server address + /// to create a socket, depending on what is available (specified + /// from the command line). If socket can't be created for any + /// reason, exception is thrown. + /// If destination address is broadcast (for DHCPv4) or multicast + /// (for DHCPv6) than broadcast or multicast option is set on + /// the socket. Opened socket is registered and managed by IfaceMgr. + /// + /// \throw isc::BadValue if socket can't be created for given + /// interface, local address or remote address. + /// \throw isc::InvalidOperation if broadcast option can't be + /// set for the v4 socket or if multicast option can't be set + /// for the v6 socket. + /// \throw isc::Unexpected if internal unexpected error occurred. + /// \return socket descriptor. + int openSocket() const; }; } diff --git a/src/bin/perfdhcp/receiver.h b/src/bin/perfdhcp/receiver.h index d05ee2493a..4a0afc5a54 100644 --- a/src/bin/perfdhcp/receiver.h +++ b/src/bin/perfdhcp/receiver.h @@ -33,10 +33,6 @@ namespace perfdhcp { /// in main thread packets can be consumed from the queue using getPkt /// method. class Receiver { -public: - /// \brief Socket for receiving. - const PerfSocket& socket_; - private: /// \brief Flag indicating if thread should run (true) or not (false). boost::atomic_flag run_flag_; @@ -57,8 +53,7 @@ public: /// \brief Receiver constructor. /// /// \param socket A socket for receiving packets. - Receiver(const PerfSocket& socket) : - socket_(socket), + Receiver() : single_threaded_(CommandOptions::instance().isSingleThreaded()) { } diff --git a/src/bin/perfdhcp/stats_mgr.cc b/src/bin/perfdhcp/stats_mgr.cc new file mode 100644 index 0000000000..bc8fe641a1 --- /dev/null +++ b/src/bin/perfdhcp/stats_mgr.cc @@ -0,0 +1,358 @@ +// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <perfdhcp/stats_mgr.h> +#include <perfdhcp/command_options.h> + + +namespace isc { +namespace perfdhcp { + +std::ostream& operator<<(std::ostream& os, ExchangeType xchg_type) +{ + switch(xchg_type) { + case ExchangeType::DO: + return(os << "DISCOVER-OFFER"); + case ExchangeType::RA: + return(os << "REQUEST-ACK"); + case ExchangeType::RNA: + return(os << "REQUEST-ACK (renewal)"); + case ExchangeType::SA: + return(os << "SOLICIT-ADVERTISE"); + case ExchangeType::RR: + return(os << "REQUEST-REPLY"); + case ExchangeType::RN: + return(os << "RENEW-REPLY"); + case ExchangeType::RL: + return(os << "RELEASE-REPLY"); + default: + return(os << "Unknown exchange type"); + } +} + + +ExchangeStats::ExchangeStats(const ExchangeType xchg_type, + const double drop_time, + const bool archive_enabled, + const boost::posix_time::ptime boot_time, + bool ignore_timestamp_reorder) + : xchg_type_(xchg_type), + sent_packets_(), + rcvd_packets_(), + archived_packets_(), + archive_enabled_(archive_enabled), + drop_time_(drop_time), + min_delay_(std::numeric_limits<double>::max()), + max_delay_(0.), + sum_delay_(0.), + sum_delay_squared_(0.), + orphans_(0), + collected_(0), + unordered_lookup_size_sum_(0), + unordered_lookups_(0), + ordered_lookups_(0), + sent_packets_num_(0), + rcvd_packets_num_(0), + boot_time_(boot_time), + ignore_timestamp_reorder_(ignore_timestamp_reorder) +{ + next_sent_ = sent_packets_.begin(); +} + + +void +ExchangeStats::updateDelays(const dhcp::PktPtr& sent_packet, + const dhcp::PktPtr& rcvd_packet) { + if (!sent_packet) { + isc_throw(BadValue, "Sent packet is null"); + } + if (!rcvd_packet) { + isc_throw(BadValue, "Received packet is null"); + } + + boost::posix_time::ptime sent_time = sent_packet->getTimestamp(); + boost::posix_time::ptime rcvd_time = rcvd_packet->getTimestamp(); + + if (sent_time.is_not_a_date_time() || + rcvd_time.is_not_a_date_time()) { + isc_throw(Unexpected, + "Timestamp must be set for sent and " + "received packet to measure RTT"); + } + boost::posix_time::time_period period(sent_time, rcvd_time); + // We don't bother calculating deltas in nanoseconds. It is much + // more convenient to use seconds instead because we are going to + // sum them up. + double delta = + static_cast<double>(period.length().total_nanoseconds()) / 1e9; + + if (!ignore_timestamp_reorder_ && delta < 0) { + isc_throw(Unexpected, "Sent packet's timestamp must not be " + "greater than received packet's timestamp in " + << xchg_type_ << ".\nTime difference: " + << delta << ", sent: " << sent_time << ", rcvd: " + << rcvd_time << ".\nTrans ID: " << sent_packet->getTransid() + << "."); + } + + // Record the minimum delay between sent and received packets. + if (delta < min_delay_) { + min_delay_ = delta; + } + // Record the maximum delay between sent and received packets. + if (delta > max_delay_) { + max_delay_ = delta; + } + // Update delay sum and square sum. That will be used to calculate + // mean delays. + sum_delay_ += delta; + sum_delay_squared_ += delta * delta; +} + +dhcp::PktPtr +ExchangeStats::matchPackets(const dhcp::PktPtr& rcvd_packet) { + using namespace boost::posix_time; + + if (!rcvd_packet) { + isc_throw(BadValue, "Received packet is null"); + } + + if (sent_packets_.size() == 0) { + // List of sent packets is empty so there is no sense + // to continue looking fo the packet. It also means + // that the received packet we got has no corresponding + // sent packet so orphans counter has to be updated. + ++orphans_; + return(dhcp::PktPtr()); + } else if (next_sent_ == sent_packets_.end()) { + // Even if there are still many unmatched packets on the + // list we might hit the end of it because of unordered + // lookups. The next logical step is to reset iterator. + next_sent_ = sent_packets_.begin(); + } + + // With this variable we will be signalling success or failure + // to find the packet. + bool packet_found = false; + // Most likely responses are sent from the server in the same + // order as client's requests to the server. We are caching + // next sent packet and first try to match it with the next + // incoming packet. We are successful if there is no + // packet drop or out of order packets sent. This is actually + // the fastest way to look for packets. + if ((*next_sent_)->getTransid() == rcvd_packet->getTransid()) { + ++ordered_lookups_; + packet_found = true; + } else { + // If we are here, it means that we were unable to match the + // next incoming packet with next sent packet so we need to + // take a little more expensive approach to look packets using + // alternative index (transaction id & 1023). + PktListTransidHashIndex& idx = sent_packets_.template get<1>(); + // Packets are grouped using transaction id masked with value + // of 1023. For instance, packets with transaction id equal to + // 1, 1024 ... will belong to the same group (a.k.a. bucket). + // When using alternative index we don't find the packet but + // bucket of packets and we need to iterate through the bucket + // to find the one that has desired transaction id. + std::pair<PktListTransidHashIterator,PktListTransidHashIterator> p = + idx.equal_range(hashTransid(rcvd_packet)); + // We want to keep statistics of unordered lookups to make + // sure that there is a right balance between number of + // unordered lookups and ordered lookups. If number of unordered + // lookups is high it may mean that many packets are lost or + // sent out of order. + ++unordered_lookups_; + // We also want to keep the mean value of the bucket. The lower + // bucket size the better. If bucket sizes appear to big we + // might want to increase number of buckets. + unordered_lookup_size_sum_ += std::distance(p.first, p.second); + bool non_expired_found = false; + // Removal can be done only after the loop + PktListRemovalQueue to_remove; + for (PktListTransidHashIterator it = p.first; it != p.second; ++it) { + // If transaction id is matching, we found the original + // packet sent to the server. Therefore, we reset the + // 'next sent' pointer to point to this location. We + // also indicate that the matching packet is found. + // Even though the packet has been found, we continue + // iterating over the bucket to remove all those packets + // that are timed out. + if (!packet_found && ((*it)->getTransid() == rcvd_packet->getTransid())) { + packet_found = true; + next_sent_ = sent_packets_.template project<0>(it); + } + + if (!non_expired_found) { + // Check if the packet should be removed due to timeout. + // This includes the packet matching the received one. + ptime now = microsec_clock::universal_time(); + ptime packet_time = (*it)->getTimestamp(); + time_period packet_period(packet_time, now); + if (!packet_period.is_null()) { + double period_fractional = + packet_period.length().total_seconds() + + (static_cast<double>(packet_period.length().fractional_seconds()) + / packet_period.length().ticks_per_second()); + if (drop_time_ > 0 && (period_fractional > drop_time_)) { + // Push the iterator on the removal queue. + to_remove.push(it); + + } else { + // We found first non-expired transaction. All other + // transactions within this bucket are considered + // non-expired because packets are held in the + // order of addition within the bucket. + non_expired_found = true; + } + } + } + + // If we found the packet and all expired transactions, + // there is nothing more to do. + if (non_expired_found && packet_found) { + break; + } + } + + // Deal with the removal queue. + while (!to_remove.empty()) { + PktListTransidHashIterator it = to_remove.front(); + to_remove.pop(); + // If timed out packet is not the one matching server response, + // we simply remove it and keep the pointer to the 'next sent' + // packet as it was. If the timed out packet appears to be the + // one that is matching the server response, we still want to + // remove it, but we need to update the 'next sent' pointer to + // point to a valid location. + if (sent_packets_.template project<0>(it) != next_sent_) { + eraseSent(sent_packets_.template project<0>(it)); + } else { + next_sent_ = eraseSent(sent_packets_.template project<0>(it)); + // We removed the matching packet because of the timeout. It + // means that there is no match anymore. + packet_found = false; + } + ++collected_; + } + } + + if (!packet_found) { + // If we are here, it means that both ordered lookup and + // unordered lookup failed. Searched packet is not on the list. + ++orphans_; + return(dhcp::PktPtr()); + } + + // Packet is matched so we count it. We don't count unmatched packets + // as they are counted as orphans with a separate counter. + ++rcvd_packets_num_; + dhcp::PktPtr sent_packet(*next_sent_); + // If packet was found, we assume it will be never searched + // again. We want to delete this packet from the list to + // improve performance of future searches. + next_sent_ = eraseSent(next_sent_); + return(sent_packet); +} + + +void +ExchangeStats::printTimestamps() { + // If archive mode is disabled there is no sense to proceed + // because we don't have packets and their timestamps. + if (!archive_enabled_) { + isc_throw(isc::InvalidOperation, + "packets archive mode is disabled"); + } + if (rcvd_packets_num_ == 0) { + std::cout << "Unavailable! No packets received." << std::endl; + } + // We will be using boost::posix_time extensively here + using namespace boost::posix_time; + + // Iterate through all received packets. + for (PktListIterator it = rcvd_packets_.begin(); + it != rcvd_packets_.end(); + ++it) { + dhcp::PktPtr rcvd_packet = *it; + PktListTransidHashIndex& idx = + archived_packets_.template get<1>(); + std::pair<PktListTransidHashIterator, + PktListTransidHashIterator> p = + idx.equal_range(hashTransid(rcvd_packet)); + for (PktListTransidHashIterator it_archived = p.first; + it_archived != p.second; + ++it_archived) { + if ((*it_archived)->getTransid() == + rcvd_packet->getTransid()) { + dhcp::PktPtr sent_packet = *it_archived; + // Get sent and received packet times. + ptime sent_time = sent_packet->getTimestamp(); + ptime rcvd_time = rcvd_packet->getTimestamp(); + // All sent and received packets should have timestamps + // set but if there is a bug somewhere and packet does + // not have timestamp we want to catch this here. + if (sent_time.is_not_a_date_time() || + rcvd_time.is_not_a_date_time()) { + isc_throw(InvalidOperation, + "packet time is not set"); + } + // Calculate durations of packets from beginning of epoch. + time_period sent_period(boot_time_, sent_time); + time_period rcvd_period(boot_time_, rcvd_time); + // Print timestamps for sent and received packet. + std::cout << "sent / received: " + << to_iso_string(sent_period.length()) + << " / " + << to_iso_string(rcvd_period.length()) + << std::endl; + break; + } + } + } +} + +StatsMgr::StatsMgr(bool ignore_timestamp_reorder) : + exchanges_(), + boot_time_(boost::posix_time::microsec_clock::universal_time()), + ignore_timestamp_reorder_(ignore_timestamp_reorder) +{ + CommandOptions& options = CommandOptions::instance(); + + // Check if packet archive mode is required. If user + // requested diagnostics option -x t we have to enable + // it so as StatsMgr preserves all packets. + archive_enabled_ = testDiags('t') ? true : false; + + if (options.getIpVersion() == 4) { + addExchangeStats(ExchangeType::DO, options.getDropTime()[0]); + if (options.getExchangeMode() == CommandOptions::DORA_SARR) { + addExchangeStats(ExchangeType::RA, options.getDropTime()[1]); + } + if (options.getRenewRate() != 0) { + addExchangeStats(ExchangeType::RNA); + } + + } else if (options.getIpVersion() == 6) { + addExchangeStats(ExchangeType::SA, options.getDropTime()[0]); + if (options.getExchangeMode() == CommandOptions::DORA_SARR) { + addExchangeStats(ExchangeType::RR, options.getDropTime()[1]); + } + if (options.getRenewRate() != 0) { + addExchangeStats(ExchangeType::RN); + } + if (options.getReleaseRate() != 0) { + addExchangeStats(ExchangeType::RL); + } + } + if (testDiags('i')) { + addCustomCounter("shortwait", "Short waits for packets"); + } +} + + +} +} diff --git a/src/bin/perfdhcp/stats_mgr.h b/src/bin/perfdhcp/stats_mgr.h index 00a58904f9..7bb1bf3a21 100644 --- a/src/bin/perfdhcp/stats_mgr.h +++ b/src/bin/perfdhcp/stats_mgr.h @@ -7,8 +7,7 @@ #ifndef STATS_MGR_H #define STATS_MGR_H -#include <dhcp/pkt4.h> -#include <dhcp/pkt6.h> +#include <dhcp/pkt.h> #include <exceptions/exceptions.h> #include <boost/noncopyable.hpp> @@ -28,861 +27,622 @@ namespace isc { namespace perfdhcp { -/// \brief Statistics Manager +/// DHCP packet exchange types. +enum class ExchangeType { + DO, ///< DHCPv4 DISCOVER-OFFER + RA, ///< DHCPv4 REQUEST-ACK + RNA, ///< DHCPv4 REQUEST-ACK (renewal) + SA, ///< DHCPv6 SOLICIT-ADVERTISE + RR, ///< DHCPv6 REQUEST-REPLY + RN, ///< DHCPv6 RENEW-REPLY + RL ///< DHCPv6 RELEASE-REPLY +}; + +/// \brief Return name of the exchange. /// -/// This class template is a storage for various performance statistics -/// collected during performance tests execution with perfdhcp tool. +/// Function returns name of the specified exchange type. +/// This function is mainly for logging purposes. /// -/// Statistics Manager holds lists of sent and received packets and -/// groups them into exchanges. For example: DHCPDISCOVER message and -/// corresponding DHCPOFFER messages belong to one exchange, DHCPREQUEST -/// and corresponding DHCPACK message belong to another exchange etc. -/// In order to update statistics for a particular exchange type, client -/// class passes sent and received packets. Internally, Statistics Manager -/// tries to match transaction id of received packet with sent packet -/// stored on the list of sent packets. When packets are matched the -/// round trip time can be calculated. +/// \param xchg_type exchange type. +/// \return string representing name of the exchange. +std::ostream& operator<<(std::ostream& os, ExchangeType xchg_type); + +/// \brief Custom Counter /// -/// \param T class representing DHCPv4 or DHCPv6 packet. -template <class T = dhcp::Pkt4> -class StatsMgr : public boost::noncopyable { +/// This class represents custom statistics counters. Client class +/// may create unlimited number of counters. Such counters are +/// being stored in map in Statistics Manager and access using +/// unique string key. +class CustomCounter { public: + /// \brief Constructor. + /// + /// This constructor sets counter name. This name is used in + /// log file to report value of each counter. + /// + /// \param name name of the counter used in log file. + CustomCounter(const std::string& name) : + counter_(0), + name_(name) { }; + + /// \brief Increment operator. + const CustomCounter& operator++() { + ++counter_; + return (*this); + } - /// \brief Custom Counter - /// - /// This class represents custom statistics counters. Client class - /// may create unlimited number of counters. Such counters are - /// being stored in map in Statistics Manager and access using - /// unique string key. - class CustomCounter { - public: - /// \brief Constructor. - /// - /// This constructor sets counter name. This name is used in - /// log file to report value of each counter. - /// - /// \param name name of the counter used in log file. - CustomCounter(const std::string& name) : - counter_(0), - name_(name) { }; - - /// \brief Increment operator. - const CustomCounter& operator++() { - ++counter_; - return (*this); - } - - /// \brief Increment operator. - const CustomCounter& operator++(int) { - CustomCounter& this_counter(*this); - operator++(); - return (this_counter); - } + /// \brief Increment operator. + const CustomCounter& operator++(int) { + CustomCounter& this_counter(*this); + operator++(); + return (this_counter); + } - const CustomCounter& operator+=(int val) { - counter_ += val; - return (*this); - } + const CustomCounter& operator+=(int val) { + counter_ += val; + return (*this); + } - /// \brief Return counter value. - /// - /// Method returns counter value. - /// + /// \brief Return counter value. + /// + /// Method returns counter value. + /// /// \return counter value. - uint64_t getValue() const { return(counter_); } + uint64_t getValue() const { return(counter_); } - /// \brief Return counter name. - /// - /// Method returns counter name. - /// + /// \brief Return counter name. + /// + /// Method returns counter name. + /// /// \return counter name. - const std::string& getName() const { return(name_); } - private: - /// \brief Default constructor. - /// - /// Default constructor is private because we don't want client - /// class to call it because we want client class to specify - /// counter's name. - CustomCounter() { }; - - uint64_t counter_; ///< Counter's value. - std::string name_; ///< Counter's name. - }; - - typedef typename boost::shared_ptr<CustomCounter> CustomCounterPtr; - - /// DHCP packet exchange types. - enum ExchangeType { - XCHG_DO, ///< DHCPv4 DISCOVER-OFFER - XCHG_RA, ///< DHCPv4 REQUEST-ACK - XCHG_RNA, ///< DHCPv4 REQUEST-ACK (renewal) - XCHG_SA, ///< DHCPv6 SOLICIT-ADVERTISE - XCHG_RR, ///< DHCPv6 REQUEST-REPLY - XCHG_RN, ///< DHCPv6 RENEW-REPLY - XCHG_RL ///< DHCPv6 RELEASE-REPLY - }; - - /// \brief Exchange Statistics. - /// - /// This class collects statistics for exchanges. Parent class - /// may define number of different packet exchanges like: - /// DHCPv4 DISCOVER-OFFER, DHCPv6 SOLICIT-ADVERTISE etc. Performance - /// statistics will be collected for each of those separately in - /// corresponding instance of ExchangeStats. - class ExchangeStats { - public: - - /// \brief Hash transaction id of the packet. - /// - /// Function hashes transaction id of the packet. Hashing is - /// non-unique. Many packets may have the same hash value and thus - /// they belong to the same packet buckets. Packet buckets are - /// used for unordered packets search with multi index container. - /// - /// \param packet packet which transaction id is to be hashed. - /// \throw isc::BadValue if packet is null. - /// \return transaction id hash. - static uint32_t hashTransid(const boost::shared_ptr<T>& packet) { - if (!packet) { - isc_throw(BadValue, "Packet is null"); - } - return(packet->getTransid() & 1023); + const std::string& getName() const { return(name_); } +private: + /// \brief Default constructor. + /// + /// Default constructor is private because we don't want client + /// class to call it because we want client class to specify + /// counter's name. + CustomCounter() { }; + + uint64_t counter_; ///< Counter's value. + std::string name_; ///< Counter's name. +}; + +typedef typename boost::shared_ptr<CustomCounter> CustomCounterPtr; + +/// Map containing custom counters. +typedef typename std::map<std::string, CustomCounterPtr> CustomCountersMap; + +/// Iterator for \ref CustomCountersMap. +typedef typename CustomCountersMap::const_iterator CustomCountersMapIterator; + + +/// \brief Exchange Statistics. +/// +/// This class collects statistics for exchanges. Parent class +/// may define number of different packet exchanges like: +/// DHCPv4 DISCOVER-OFFER, DHCPv6 SOLICIT-ADVERTISE etc. Performance +/// statistics will be collected for each of those separately in +/// corresponding instance of ExchangeStats. +class ExchangeStats { +public: + + /// \brief Hash transaction id of the packet. + /// + /// Function hashes transaction id of the packet. Hashing is + /// non-unique. Many packets may have the same hash value and thus + /// they belong to the same packet buckets. Packet buckets are + /// used for unordered packets search with multi index container. + /// + /// \param packet packet which transaction id is to be hashed. + /// \throw isc::BadValue if packet is null. + /// \return transaction id hash. + static uint32_t hashTransid(const dhcp::PktPtr& packet) { + if (!packet) { + isc_throw(BadValue, "Packet is null"); } + return(packet->getTransid() & 1023); + } - /// \brief List of packets (sent or received). - /// - /// List of packets based on multi index container allows efficient - /// search of packets based on their sequence (order in which they - /// were inserted) as well as based on their hashed transaction id. - /// The first index (sequenced) provides the way to use container - /// as a regular list (including iterators, removal of elements from - /// the middle of the collection etc.). This index is meant to be used - /// more frequently than the latter one and it is based on the - /// assumption that responses from the DHCP server are received in - /// order. In this case, when next packet is received it can be - /// matched with next packet on the list of sent packets. This - /// prevents intensive searches on the list of sent packets every - /// time new packet arrives. In many cases however packets can be - /// dropped by the server or may be sent out of order and we still - /// want to have ability to search packets using transaction id. - /// The second index can be used for this purpose. This index is - /// hashing transaction ids using custom function \ref hashTransid. - /// Note that other possibility would be to simply specify index - /// that uses transaction id directly (instead of hashing with - /// \ref hashTransid). In this case however we have chosen to use - /// hashing function because it shortens the index size to just - /// 1023 values maximum. Search operation on this index generally - /// returns the range of packets that have the same transaction id - /// hash assigned but most often these ranges will be short so further - /// search within a range to find a packet with particular transaction - /// id will not be intensive. - /// - /// Example 1: Add elements to the list - /// \code - /// PktList packets_collection(); - /// boost::shared_ptr<Pkt4> pkt1(new Pkt4(...)); - /// boost::shared_ptr<Pkt4> pkt2(new Pkt4(...)); - /// // Add new packet to the container, it will be available through - /// // both indexes - /// packets_collection.push_back(pkt1); - /// // Here is another way to add packet to the container. The result - /// // is exactly the same as previously. - /// packets_collection.template get<0>().push_back(pkt2); - /// \endcode - /// - /// Example 2: Access elements through sequential index - /// \code - /// PktList packets_collection(); - /// ... # Add elements to the container - /// for (PktListIterator it = packets_collection.begin(); - /// it != packets_collection.end(); - /// ++it) { - /// boost::shared_ptr<Pkt4> pkt = *it; - /// # Do something with packet; - /// } - /// \endcode - /// - /// Example 3: Access elements through ordered index by hash - /// \code - /// // Get the instance of the second search index. - /// PktListTransidHashIndex& idx = sent_packets_.template get<1>(); - /// // Get the range (bucket) of packets sharing the same transaction - /// // id hash. - /// std::pair<PktListTransidHashIterator,PktListTransidHashIterator> p = - /// idx.equal_range(hashTransid(rcvd_packet)); - /// // Iterate through the returned bucket. - /// for (PktListTransidHashIterator it = p.first; it != p.second; - /// ++it) { - /// boost::shared_ptr pkt = *it; - /// ... # Do something with the packet (e.g. check transaction id) - /// } - /// \endcode - typedef boost::multi_index_container< - // Container holds shared_ptr<Pkt4> or shared_ptr<Pkt6> objects. - boost::shared_ptr<T>, - // List container indexes. - boost::multi_index::indexed_by< - // Sequenced index provides the way to use this container - // in the same way as std::list. - boost::multi_index::sequenced<>, - // The other index keeps products of transaction id. - // Elements with the same hash value are grouped together - // into buckets and transactions are ordered from the - // oldest to latest within a bucket. - boost::multi_index::ordered_non_unique< - // Specify hash function to get the product of - // transaction id. This product is obtained by calling - // hashTransid() function. - boost::multi_index::global_fun< - // Hashing function takes shared_ptr<Pkt4> or - // shared_ptr<Pkt6> as argument. - const boost::shared_ptr<T>&, - // ... and returns uint32 value. - uint32_t, - // ... and here is a reference to it. - &ExchangeStats::hashTransid + /// \brief List of packets (sent or received). + /// + /// List of packets based on multi index container allows efficient + /// search of packets based on their sequence (order in which they + /// were inserted) as well as based on their hashed transaction id. + /// The first index (sequenced) provides the way to use container + /// as a regular list (including iterators, removal of elements from + /// the middle of the collection etc.). This index is meant to be used + /// more frequently than the latter one and it is based on the + /// assumption that responses from the DHCP server are received in + /// order. In this case, when next packet is received it can be + /// matched with next packet on the list of sent packets. This + /// prevents intensive searches on the list of sent packets every + /// time new packet arrives. In many cases however packets can be + /// dropped by the server or may be sent out of order and we still + /// want to have ability to search packets using transaction id. + /// The second index can be used for this purpose. This index is + /// hashing transaction ids using custom function \ref hashTransid. + /// Note that other possibility would be to simply specify index + /// that uses transaction id directly (instead of hashing with + /// \ref hashTransid). In this case however we have chosen to use + /// hashing function because it shortens the index size to just + /// 1023 values maximum. Search operation on this index generally + /// returns the range of packets that have the same transaction id + /// hash assigned but most often these ranges will be short so further + /// search within a range to find a packet with particular transaction + /// id will not be intensive. + /// + /// Example 1: Add elements to the list + /// \code + /// PktList packets_collection(); + /// boost::shared_ptr<Pkt4> pkt1(new Pkt4(...)); + /// boost::shared_ptr<Pkt4> pkt2(new Pkt4(...)); + /// // Add new packet to the container, it will be available through + /// // both indexes + /// packets_collection.push_back(pkt1); + /// // Here is another way to add packet to the container. The result + /// // is exactly the same as previously. + /// packets_collection.template get<0>().push_back(pkt2); + /// \endcode + /// + /// Example 2: Access elements through sequential index + /// \code + /// PktList packets_collection(); + /// ... # Add elements to the container + /// for (PktListIterator it = packets_collection.begin(); + /// it != packets_collection.end(); + /// ++it) { + /// boost::shared_ptr<Pkt4> pkt = *it; + /// # Do something with packet; + /// } + /// \endcode + /// + /// Example 3: Access elements through ordered index by hash + /// \code + /// // Get the instance of the second search index. + /// PktListTransidHashIndex& idx = sent_packets_.template get<1>(); + /// // Get the range (bucket) of packets sharing the same transaction + /// // id hash. + /// std::pair<PktListTransidHashIterator,PktListTransidHashIterator> p = + /// idx.equal_range(hashTransid(rcvd_packet)); + /// // Iterate through the returned bucket. + /// for (PktListTransidHashIterator it = p.first; it != p.second; + /// ++it) { + /// boost::shared_ptr pkt = *it; + /// ... # Do something with the packet (e.g. check transaction id) + /// } + /// \endcode + typedef boost::multi_index_container< + // Container holds shared_ptr<Pkt4> or shared_ptr<Pkt6> objects. + dhcp::PktPtr, + // List container indexes. + boost::multi_index::indexed_by< + // Sequenced index provides the way to use this container + // in the same way as std::list. + boost::multi_index::sequenced<>, + // The other index keeps products of transaction id. + // Elements with the same hash value are grouped together + // into buckets and transactions are ordered from the + // oldest to latest within a bucket. + boost::multi_index::ordered_non_unique< + // Specify hash function to get the product of + // transaction id. This product is obtained by calling + // hashTransid() function. + boost::multi_index::global_fun< + // Hashing function takes shared_ptr<Pkt4> or + // shared_ptr<Pkt6> as argument. + const dhcp::PktPtr&, + // ... and returns uint32 value. + uint32_t, + // ... and here is a reference to it. + &ExchangeStats::hashTransid > > > > PktList; - /// Packet list iterator for sequential access to elements. - typedef typename PktList::iterator PktListIterator; - /// Packet list index to search packets using transaction id hash. - typedef typename PktList::template nth_index<1>::type - PktListTransidHashIndex; - /// Packet list iterator to access packets using transaction id hash. - typedef typename PktListTransidHashIndex::const_iterator - PktListTransidHashIterator; - /// Packet list iterator queue for removal. - typedef typename std::queue<PktListTransidHashIterator> - PktListRemovalQueue; - - /// \brief Constructor - /// - /// \param xchg_type exchange type - /// \param drop_time maximum time elapsed before packet is - /// assumed dropped. Negative value disables it. - /// \param archive_enabled if true packets archive mode is enabled. - /// In this mode all packets are stored throughout the test execution. - /// \param boot_time Holds the timestamp when perfdhcp has been started. - ExchangeStats(const ExchangeType xchg_type, - const double drop_time, - const bool archive_enabled, - const boost::posix_time::ptime boot_time) - : xchg_type_(xchg_type), - sent_packets_(), - rcvd_packets_(), - archived_packets_(), - archive_enabled_(archive_enabled), - drop_time_(drop_time), - min_delay_(std::numeric_limits<double>::max()), - max_delay_(0.), - sum_delay_(0.), - sum_delay_squared_(0.), - orphans_(0), - collected_(0), - unordered_lookup_size_sum_(0), - unordered_lookups_(0), - ordered_lookups_(0), - sent_packets_num_(0), - rcvd_packets_num_(0), - boot_time_(boot_time) - { - next_sent_ = sent_packets_.begin(); + /// Packet list iterator for sequential access to elements. + typedef typename PktList::iterator PktListIterator; + /// Packet list index to search packets using transaction id hash. + typedef typename PktList::template nth_index<1>::type + PktListTransidHashIndex; + /// Packet list iterator to access packets using transaction id hash. + typedef typename PktListTransidHashIndex::const_iterator + PktListTransidHashIterator; + /// Packet list iterator queue for removal. + typedef typename std::queue<PktListTransidHashIterator> + PktListRemovalQueue; + + /// \brief Constructor + /// + /// \param xchg_type exchange type + /// \param drop_time maximum time elapsed before packet is + /// assumed dropped. Negative value disables it. + /// \param archive_enabled if true packets archive mode is enabled. + /// In this mode all packets are stored throughout the test execution. + /// \param boot_time Holds the timestamp when perfdhcp has been started. + ExchangeStats(const ExchangeType xchg_type, + const double drop_time, + const bool archive_enabled, + const boost::posix_time::ptime boot_time, + bool ignore_timestamp_reorder); + + /// \brief Add new packet to list of sent packets. + /// + /// Method adds new packet to list of sent packets. + /// + /// \param packet packet object to be added. + /// \throw isc::BadValue if packet is null. + void appendSent(const dhcp::PktPtr& packet) { + if (!packet) { + isc_throw(BadValue, "Packet is null"); } + ++sent_packets_num_; + sent_packets_.template get<0>().push_back(packet); + } - /// \brief Add new packet to list of sent packets. - /// - /// Method adds new packet to list of sent packets. - /// - /// \param packet packet object to be added. - /// \throw isc::BadValue if packet is null. - void appendSent(const boost::shared_ptr<T>& packet) { - if (!packet) { - isc_throw(BadValue, "Packet is null"); - } - ++sent_packets_num_; - sent_packets_.template get<0>().push_back(packet); + /// \brief Add new packet to list of received packets. + /// + /// Method adds new packet to list of received packets. + /// + /// \param packet packet object to be added. + /// \throw isc::BadValue if packet is null. + void appendRcvd(const dhcp::PktPtr& packet) { + if (!packet) { + isc_throw(BadValue, "Packet is null"); } + rcvd_packets_.push_back(packet); + } - /// \brief Add new packet to list of received packets. - /// - /// Method adds new packet to list of received packets. - /// - /// \param packet packet object to be added. - /// \throw isc::BadValue if packet is null. - void appendRcvd(const boost::shared_ptr<T>& packet) { - if (!packet) { - isc_throw(BadValue, "Packet is null"); - } - rcvd_packets_.push_back(packet); + /// \brief Update delay counters. + /// + /// Method updates delay counters based on timestamps of + /// sent and received packets. + /// + /// \param sent_packet sent packet + /// \param rcvd_packet received packet + /// \throw isc::BadValue if sent or received packet is null. + /// \throw isc::Unexpected if failed to calculate timestamps + void updateDelays(const dhcp::PktPtr& sent_packet, + const dhcp::PktPtr& rcvd_packet); + + /// \brief Match received packet with the corresponding sent packet. + /// + /// Method finds packet with specified transaction id on the list + /// of sent packets. It is used to match received packet with + /// corresponding sent packet. + /// Since packets from the server most often come in the same order + /// as they were sent by client, this method will first check if + /// next sent packet matches. If it doesn't, function will search + /// the packet using indexing by transaction id. This reduces + /// packet search time significantly. + /// + /// \param rcvd_packet received packet to be matched with sent packet. + /// \throw isc::BadValue if received packet is null. + /// \return packet having specified transaction or NULL if packet + /// not found + dhcp::PktPtr matchPackets(const dhcp::PktPtr& rcvd_packet); + + /// \brief Return minimum delay between sent and received packet. + /// + /// Method returns minimum delay between sent and received packet. + /// + /// \return minimum delay between packets. + double getMinDelay() const { return(min_delay_); } + + /// \brief Return maximum delay between sent and received packet. + /// + /// Method returns maximum delay between sent and received packet. + /// + /// \return maximum delay between packets. + double getMaxDelay() const { return(max_delay_); } + + /// \brief Return average packet delay. + /// + /// Method returns average packet delay. If no packets have been + /// received for this exchange avg delay can't be calculated and + /// thus method throws exception. + /// + /// \throw isc::InvalidOperation if no packets for this exchange + /// have been received yet. + /// \return average packet delay. + double getAvgDelay() const { + if (rcvd_packets_num_ == 0) { + isc_throw(InvalidOperation, "no packets received"); } + return(sum_delay_ / rcvd_packets_num_); + } - /// \brief Update delay counters. - /// - /// Method updates delay counters based on timestamps of - /// sent and received packets. - /// - /// \param sent_packet sent packet - /// \param rcvd_packet received packet - /// \throw isc::BadValue if sent or received packet is null. - /// \throw isc::Unexpected if failed to calculate timestamps - void updateDelays(const boost::shared_ptr<T>& sent_packet, - const boost::shared_ptr<T>& rcvd_packet) { - if (!sent_packet) { - isc_throw(BadValue, "Sent packet is null"); - } - if (!rcvd_packet) { - isc_throw(BadValue, "Received packet is null"); - } + /// \brief Return standard deviation of packet delay. + /// + /// Method returns standard deviation of packet delay. If no + /// packets have been received for this exchange, the standard + /// deviation can't be calculated and thus method throws + /// exception. + /// + /// \throw isc::InvalidOperation if number of received packets + /// for the exchange is equal to zero. + /// \return standard deviation of packet delay. + double getStdDevDelay() const { + if (rcvd_packets_num_ == 0) { + isc_throw(InvalidOperation, "no packets received"); + } + return(sqrt(sum_delay_squared_ / rcvd_packets_num_ - + getAvgDelay() * getAvgDelay())); + } - boost::posix_time::ptime sent_time = sent_packet->getTimestamp(); - boost::posix_time::ptime rcvd_time = rcvd_packet->getTimestamp(); + /// \brief Return number of orphan packets. + /// + /// Method returns number of received packets that had no matching + /// sent packet. It is possible that such packet was late or not + /// for us. + /// + /// \return number of orphan received packets. + uint64_t getOrphans() const { return(orphans_); } - if (sent_time.is_not_a_date_time() || - rcvd_time.is_not_a_date_time()) { - isc_throw(Unexpected, - "Timestamp must be set for sent and " - "received packet to measure RTT"); - } - boost::posix_time::time_period period(sent_time, rcvd_time); - // We don't bother calculating deltas in nanoseconds. It is much - // more convenient to use seconds instead because we are going to - // sum them up. - double delta = - static_cast<double>(period.length().total_nanoseconds()) / 1e9; - - if (delta < 0) { - isc_throw(Unexpected, "Sent packet's timestamp must not be " - "greater than received packet's timestamp"); - } + /// \brief Return number of garbage collected packets. + /// + /// Method returns number of garbage collected timed out + /// packets. Packet is assumed timed out when duration + /// between sending it to server and receiving server's + /// response is greater than value specified with -d<value> + /// command line argument. + /// + /// \return number of garbage collected packets. + uint64_t getCollectedNum() const { return(collected_); } - // Record the minimum delay between sent and received packets. - if (delta < min_delay_) { - min_delay_ = delta; - } - // Record the maximum delay between sent and received packets. - if (delta > max_delay_) { - max_delay_ = delta; - } - // Update delay sum and square sum. That will be used to calculate - // mean delays. - sum_delay_ += delta; - sum_delay_squared_ += delta * delta; + /// \brief Return average unordered lookup set size. + /// + /// Method returns average unordered lookup set size. + /// This value changes every time \ref ExchangeStats::matchPackets + /// function performs unordered packet lookup. + /// + /// \throw isc::InvalidOperation if there have been no unordered + /// lookups yet. + /// \return average unordered lookup set size. + double getAvgUnorderedLookupSetSize() const { + if (unordered_lookups_ == 0) { + isc_throw(InvalidOperation, "no unordered lookups"); } + return(static_cast<double>(unordered_lookup_size_sum_) / + static_cast<double>(unordered_lookups_)); + } - /// \brief Match received packet with the corresponding sent packet. - /// - /// Method finds packet with specified transaction id on the list - /// of sent packets. It is used to match received packet with - /// corresponding sent packet. - /// Since packets from the server most often come in the same order - /// as they were sent by client, this method will first check if - /// next sent packet matches. If it doesn't, function will search - /// the packet using indexing by transaction id. This reduces - /// packet search time significantly. - /// - /// \param rcvd_packet received packet to be matched with sent packet. - /// \throw isc::BadValue if received packet is null. - /// \return packet having specified transaction or NULL if packet - /// not found - boost::shared_ptr<T> - matchPackets(const boost::shared_ptr<T>& rcvd_packet) { - using namespace boost::posix_time; - - if (!rcvd_packet) { - isc_throw(BadValue, "Received packet is null"); - } + /// \brief Return number of unordered sent packets lookups + /// + /// Method returns number of unordered sent packet lookups. + /// Unordered lookup is used when received packet was sent + /// out of order by server - transaction id of received + /// packet does not match transaction id of next sent packet. + /// + /// \return number of unordered lookups. + uint64_t getUnorderedLookups() const { return(unordered_lookups_); } - if (sent_packets_.size() == 0) { - // List of sent packets is empty so there is no sense - // to continue looking fo the packet. It also means - // that the received packet we got has no corresponding - // sent packet so orphans counter has to be updated. - ++orphans_; - return(boost::shared_ptr<T>()); - } else if (next_sent_ == sent_packets_.end()) { - // Even if there are still many unmatched packets on the - // list we might hit the end of it because of unordered - // lookups. The next logical step is to reset iterator. - next_sent_ = sent_packets_.begin(); - } + /// \brief Return number of ordered sent packets lookups + /// + /// Method returns number of ordered sent packet lookups. + /// Ordered lookup is used when packets are received in the + /// same order as they were sent to the server. + /// If packets are skipped or received out of order, lookup + /// function will use unordered lookup (with hash table). + /// + /// \return number of ordered lookups. + uint64_t getOrderedLookups() const { return(ordered_lookups_); } - // With this variable we will be signalling success or failure - // to find the packet. - bool packet_found = false; - // Most likely responses are sent from the server in the same - // order as client's requests to the server. We are caching - // next sent packet and first try to match it with the next - // incoming packet. We are successful if there is no - // packet drop or out of order packets sent. This is actually - // the fastest way to look for packets. - if ((*next_sent_)->getTransid() == rcvd_packet->getTransid()) { - ++ordered_lookups_; - packet_found = true; - } else { - // If we are here, it means that we were unable to match the - // next incoming packet with next sent packet so we need to - // take a little more expensive approach to look packets using - // alternative index (transaction id & 1023). - PktListTransidHashIndex& idx = sent_packets_.template get<1>(); - // Packets are grouped using transaction id masked with value - // of 1023. For instance, packets with transaction id equal to - // 1, 1024 ... will belong to the same group (a.k.a. bucket). - // When using alternative index we don't find the packet but - // bucket of packets and we need to iterate through the bucket - // to find the one that has desired transaction id. - std::pair<PktListTransidHashIterator,PktListTransidHashIterator> p = - idx.equal_range(hashTransid(rcvd_packet)); - // We want to keep statistics of unordered lookups to make - // sure that there is a right balance between number of - // unordered lookups and ordered lookups. If number of unordered - // lookups is high it may mean that many packets are lost or - // sent out of order. - ++unordered_lookups_; - // We also want to keep the mean value of the bucket. The lower - // bucket size the better. If bucket sizes appear to big we - // might want to increase number of buckets. - unordered_lookup_size_sum_ += std::distance(p.first, p.second); - bool non_expired_found = false; - // Removal can be done only after the loop - PktListRemovalQueue to_remove; - for (PktListTransidHashIterator it = p.first; it != p.second; - ++it) { - // If transaction id is matching, we found the original - // packet sent to the server. Therefore, we reset the - // 'next sent' pointer to point to this location. We - // also indicate that the matching packet is found. - // Even though the packet has been found, we continue - // iterating over the bucket to remove all those packets - // that are timed out. - if (!packet_found && ((*it)->getTransid() == rcvd_packet->getTransid())) { - packet_found = true; - next_sent_ = sent_packets_.template project<0>(it); - } - - if (!non_expired_found) { - // Check if the packet should be removed due to timeout. - // This includes the packet matching the received one. - ptime now = microsec_clock::universal_time(); - ptime packet_time = (*it)->getTimestamp(); - time_period packet_period(packet_time, now); - if (!packet_period.is_null()) { - double period_fractional = - packet_period.length().total_seconds() + - (static_cast<double>(packet_period.length().fractional_seconds()) - / packet_period.length().ticks_per_second()); - if (drop_time_ > 0 && (period_fractional > drop_time_)) { - // Push the iterator on the removal queue. - to_remove.push(it); - - } else { - // We found first non-expired transaction. All other - // transactions within this bucket are considered - // non-expired because packets are held in the - // order of addition within the bucket. - non_expired_found = true; - } - } - } - - // If we found the packet and all expired transactions, - // there is nothing more to do. - if (non_expired_found && packet_found) { - break; - } - } - - // Deal with the removal queue. - while (!to_remove.empty()) { - PktListTransidHashIterator it = to_remove.front(); - to_remove.pop(); - // If timed out packet is not the one matching server response, - // we simply remove it and keep the pointer to the 'next sent' - // packet as it was. If the timed out packet appears to be the - // one that is matching the server response, we still want to - // remove it, but we need to update the 'next sent' pointer to - // point to a valid location. - if (sent_packets_.template project<0>(it) != next_sent_) { - eraseSent(sent_packets_.template project<0>(it)); - } else { - next_sent_ = eraseSent(sent_packets_.template project<0>(it)); - // We removed the matching packet because of the timeout. It - // means that there is no match anymore. - packet_found = false; - } - ++collected_; - } - } + /// \brief Return total number of sent packets + /// + /// Method returns total number of sent packets. + /// + /// \return number of sent packets. + uint64_t getSentPacketsNum() const { return(sent_packets_num_); } - if (!packet_found) { - // If we are here, it means that both ordered lookup and - // unordered lookup failed. Searched packet is not on the list. - ++orphans_; - return(boost::shared_ptr<T>()); - } + /// \brief Return total number of received packets + /// + /// Method returns total number of received packets. + /// + /// \return number of received packets. + uint64_t getRcvdPacketsNum() const { return(rcvd_packets_num_); } - // Packet is matched so we count it. We don't count unmatched packets - // as they are counted as orphans with a separate counter. - ++rcvd_packets_num_; - boost::shared_ptr<T> sent_packet(*next_sent_); - // If packet was found, we assume it will be never searched - // again. We want to delete this packet from the list to - // improve performance of future searches. - next_sent_ = eraseSent(next_sent_); - return(sent_packet); + /// \brief Return number of dropped packets. + /// + /// Method returns number of dropped packets. + /// + /// \return number of dropped packets. + uint64_t getDroppedPacketsNum() const { + uint64_t drops = 0; + if (getSentPacketsNum() > getRcvdPacketsNum()) { + drops = getSentPacketsNum() - getRcvdPacketsNum(); } + return(drops); + } - /// \brief Return minimum delay between sent and received packet. - /// - /// Method returns minimum delay between sent and received packet. - /// - /// \return minimum delay between packets. - double getMinDelay() const { return(min_delay_); } - - /// \brief Return maximum delay between sent and received packet. - /// - /// Method returns maximum delay between sent and received packet. - /// - /// \return maximum delay between packets. - double getMaxDelay() const { return(max_delay_); } - - /// \brief Return average packet delay. - /// - /// Method returns average packet delay. If no packets have been - /// received for this exchange avg delay can't be calculated and - /// thus method throws exception. - /// - /// \throw isc::InvalidOperation if no packets for this exchange - /// have been received yet. - /// \return average packet delay. - double getAvgDelay() const { - if (rcvd_packets_num_ == 0) { - isc_throw(InvalidOperation, "no packets received"); - } - return(sum_delay_ / rcvd_packets_num_); - } + /// \brief Print main statistics for packet exchange. + /// + /// Method prints main statistics for particular exchange. + /// Statistics includes: number of sent and received packets, + /// number of dropped packets and number of orphans. + /// + /// \todo Currently the number of orphans is not displayed because + /// Reply messages received for Renew and Releases are counted as + /// orphans for the 4-way exchanges, which is wrong. We will need to + /// move the orphans counting out of the Statistics Manager so as + /// orphans counter is increased only if the particular message is + /// not identified as a response to any of the messages sent by + /// perfdhcp. + void printMainStats() const { + using namespace std; + auto sent = getSentPacketsNum(); + auto drops = getDroppedPacketsNum(); + double drops_ratio = 100.0 * static_cast<double>(drops) / static_cast<double>(sent); + + cout << "sent packets: " << sent << endl + << "received packets: " << getRcvdPacketsNum() << endl + << "drops: " << drops << endl + << "drops ratio: " << drops_ratio << " %" << endl + << "orphans: " << getOrphans() << endl; + } - /// \brief Return standard deviation of packet delay. - /// - /// Method returns standard deviation of packet delay. If no - /// packets have been received for this exchange, the standard - /// deviation can't be calculated and thus method throws - /// exception. - /// - /// \throw isc::InvalidOperation if number of received packets - /// for the exchange is equal to zero. - /// \return standard deviation of packet delay. - double getStdDevDelay() const { - if (rcvd_packets_num_ == 0) { - isc_throw(InvalidOperation, "no packets received"); - } - return(sqrt(sum_delay_squared_ / rcvd_packets_num_ - - getAvgDelay() * getAvgDelay())); + /// \brief Print round trip time packets statistics. + /// + /// Method prints round trip time packets statistics. Statistics + /// includes minimum packet delay, maximum packet delay, average + /// packet delay and standard deviation of delays. Packet delay + /// is a duration between sending a packet to server and receiving + /// response from server. + void printRTTStats() const { + using namespace std; + try { + cout << fixed << setprecision(3) + << "min delay: " << getMinDelay() * 1e3 << " ms" << endl + << "avg delay: " << getAvgDelay() * 1e3 << " ms" << endl + << "max delay: " << getMaxDelay() * 1e3 << " ms" << endl + << "std deviation: " << getStdDevDelay() * 1e3 << " ms" + << endl + << "collected packets: " << getCollectedNum() << endl; + } catch (const Exception&) { + cout << "Delay summary unavailable! No packets received." << endl; } + } - /// \brief Return number of orphan packets. - /// - /// Method returns number of received packets that had no matching - /// sent packet. It is possible that such packet was late or not - /// for us. - /// - /// \return number of orphan received packets. - uint64_t getOrphans() const { return(orphans_); } - - /// \brief Return number of garbage collected packets. - /// - /// Method returns number of garbage collected timed out - /// packets. Packet is assumed timed out when duration - /// between sending it to server and receiving server's - /// response is greater than value specified with -d<value> - /// command line argument. - /// - /// \return number of garbage collected packets. - uint64_t getCollectedNum() const { return(collected_); } - - /// \brief Return average unordered lookup set size. - /// - /// Method returns average unordered lookup set size. - /// This value changes every time \ref ExchangeStats::matchPackets - /// function performs unordered packet lookup. - /// - /// \throw isc::InvalidOperation if there have been no unordered - /// lookups yet. - /// \return average unordered lookup set size. - double getAvgUnorderedLookupSetSize() const { - if (unordered_lookups_ == 0) { - isc_throw(InvalidOperation, "no unordered lookups"); - } - return(static_cast<double>(unordered_lookup_size_sum_) / - static_cast<double>(unordered_lookups_)); - } + //// \brief Print timestamps for sent and received packets. + /// + /// Method prints timestamps for all sent and received packets for + /// packet exchange. In order to run this method the packets + /// archiving mode has to be enabled during object constructions. + /// Otherwise sent packets are not stored during tests execution + /// and this method has no ability to get and print their timestamps. + /// + /// \throw isc::InvalidOperation if found packet with no timestamp or + /// if packets archive mode is disabled. + void printTimestamps(); - /// \brief Return number of unordered sent packets lookups - /// - /// Method returns number of unordered sent packet lookups. - /// Unordered lookup is used when received packet was sent - /// out of order by server - transaction id of received - /// packet does not match transaction id of next sent packet. - /// - /// \return number of unordered lookups. - uint64_t getUnorderedLookups() const { return(unordered_lookups_); } - - /// \brief Return number of ordered sent packets lookups - /// - /// Method returns number of ordered sent packet lookups. - /// Ordered lookup is used when packets are received in the - /// same order as they were sent to the server. - /// If packets are skipped or received out of order, lookup - /// function will use unordered lookup (with hash table). - /// - /// \return number of ordered lookups. - uint64_t getOrderedLookups() const { return(ordered_lookups_); } - - /// \brief Return total number of sent packets - /// - /// Method returns total number of sent packets. - /// - /// \return number of sent packets. - uint64_t getSentPacketsNum() const { return(sent_packets_num_); } - - /// \brief Return total number of received packets - /// - /// Method returns total number of received packets. - /// - /// \return number of received packets. - uint64_t getRcvdPacketsNum() const { return(rcvd_packets_num_); } - - /// \brief Return number of dropped packets. - /// - /// Method returns number of dropped packets. - /// - /// \return number of dropped packets. - uint64_t getDroppedPacketsNum() const { - uint64_t drops = 0; - if (getSentPacketsNum() > getRcvdPacketsNum()) { - drops = getSentPacketsNum() - getRcvdPacketsNum(); - } - return(drops); - } + std::tuple<PktListIterator, PktListIterator> getSentPackets() { + return(std::make_tuple(sent_packets_.begin(), sent_packets_.end())); + } - /// \brief Print main statistics for packet exchange. - /// - /// Method prints main statistics for particular exchange. - /// Statistics includes: number of sent and received packets, - /// number of dropped packets and number of orphans. - /// - /// \todo Currently the number of orphans is not displayed because - /// Reply messages received for Renew and Releases are counted as - /// orphans for the 4-way exchanges, which is wrong. We will need to - /// move the orphans counting out of the Statistics Manager so as - /// orphans counter is increased only if the particular message is - /// not identified as a response to any of the messages sent by - /// perfdhcp. - void printMainStats() const { - using namespace std; - auto sent = getSentPacketsNum(); - auto drops = getDroppedPacketsNum(); - double drops_ratio = 100.0 * static_cast<double>(drops) / static_cast<double>(sent); - - cout << "sent packets: " << sent << endl - << "received packets: " << getRcvdPacketsNum() << endl - << "drops: " << drops << endl - << "drops ratio: " << drops_ratio << " %" << endl; - // << "orphans: " << getOrphans() << endl; - } - /// \brief Print round trip time packets statistics. - /// - /// Method prints round trip time packets statistics. Statistics - /// includes minimum packet delay, maximum packet delay, average - /// packet delay and standard deviation of delays. Packet delay - /// is a duration between sending a packet to server and receiving - /// response from server. - void printRTTStats() const { - using namespace std; - try { - cout << fixed << setprecision(3) - << "min delay: " << getMinDelay() * 1e3 << " ms" << endl - << "avg delay: " << getAvgDelay() * 1e3 << " ms" << endl - << "max delay: " << getMaxDelay() * 1e3 << " ms" << endl - << "std deviation: " << getStdDevDelay() * 1e3 << " ms" - << endl - << "collected packets: " << getCollectedNum() << endl; - } catch (const Exception&) { - cout << "Delay summary unavailable! No packets received." << endl; - } - } +// Private stuff of ExchangeStats class +private: - //// \brief Print timestamps for sent and received packets. - /// - /// Method prints timestamps for all sent and received packets for - /// packet exchange. In order to run this method the packets - /// archiving mode has to be enabled during object constructions. - /// Otherwise sent packets are not stored during tests execution - /// and this method has no ability to get and print their timestamps. - /// - /// \throw isc::InvalidOperation if found packet with no timestamp or - /// if packets archive mode is disabled. - void printTimestamps() { - // If archive mode is disabled there is no sense to proceed - // because we don't have packets and their timestamps. - if (!archive_enabled_) { - isc_throw(isc::InvalidOperation, - "packets archive mode is disabled"); - } - if (rcvd_packets_num_ == 0) { - std::cout << "Unavailable! No packets received." << std::endl; - } - // We will be using boost::posix_time extensively here - using namespace boost::posix_time; - - // Iterate through all received packets. - for (PktListIterator it = rcvd_packets_.begin(); - it != rcvd_packets_.end(); - ++it) { - boost::shared_ptr<T> rcvd_packet = *it; - PktListTransidHashIndex& idx = - archived_packets_.template get<1>(); - std::pair<PktListTransidHashIterator, - PktListTransidHashIterator> p = - idx.equal_range(hashTransid(rcvd_packet)); - for (PktListTransidHashIterator it_archived = p.first; - it_archived != p.second; - ++it_archived) { - if ((*it_archived)->getTransid() == - rcvd_packet->getTransid()) { - boost::shared_ptr<T> sent_packet = *it_archived; - // Get sent and received packet times. - ptime sent_time = sent_packet->getTimestamp(); - ptime rcvd_time = rcvd_packet->getTimestamp(); - // All sent and received packets should have timestamps - // set but if there is a bug somewhere and packet does - // not have timestamp we want to catch this here. - if (sent_time.is_not_a_date_time() || - rcvd_time.is_not_a_date_time()) { - isc_throw(InvalidOperation, - "packet time is not set"); - } - // Calculate durations of packets from beginning of epoch. - time_period sent_period(boot_time_, sent_time); - time_period rcvd_period(boot_time_, rcvd_time); - // Print timestamps for sent and received packet. - std::cout << "sent / received: " - << to_iso_string(sent_period.length()) - << " / " - << to_iso_string(rcvd_period.length()) - << std::endl; - break; - } - } - } - } + /// \brief Private default constructor. + /// + /// Default constructor is private because we want the client + /// class to specify exchange type explicitly. + ExchangeStats(); - private: - - /// \brief Private default constructor. - /// - /// Default constructor is private because we want the client - /// class to specify exchange type explicitly. - ExchangeStats(); - - /// \brief Erase packet from the list of sent packets. - /// - /// Method erases packet from the list of sent packets. - /// - /// \param it iterator pointing to packet to be erased. - /// \return iterator pointing to packet following erased - /// packet or sent_packets_.end() if packet not found. - PktListIterator eraseSent(const PktListIterator it) { - if (archive_enabled_) { - // We don't want to keep list of all sent packets - // because it will affect packet lookup performance. - // If packet is matched with received packet we - // move it to list of archived packets. List of - // archived packets may be used for diagnostics - // when test is completed. - archived_packets_.push_back(*it); - } - // get<0>() template returns sequential index to - // container. - return(sent_packets_.template get<0>().erase(it)); + /// \brief Erase packet from the list of sent packets. + /// + /// Method erases packet from the list of sent packets. + /// + /// \param it iterator pointing to packet to be erased. + /// \return iterator pointing to packet following erased + /// packet or sent_packets_.end() if packet not found. + PktListIterator eraseSent(const PktListIterator it) { + if (archive_enabled_) { + // We don't want to keep list of all sent packets + // because it will affect packet lookup performance. + // If packet is matched with received packet we + // move it to list of archived packets. List of + // archived packets may be used for diagnostics + // when test is completed. + archived_packets_.push_back(*it); } + // get<0>() template returns sequential index to + // container. + return(sent_packets_.template get<0>().erase(it)); + } - ExchangeType xchg_type_; ///< Packet exchange type. - PktList sent_packets_; ///< List of sent packets. - - /// Iterator pointing to the packet on sent list which will most - /// likely match next received packet. This is based on the - /// assumption that server responds in order to incoming packets. - PktListIterator next_sent_; - - PktList rcvd_packets_; ///< List of received packets. - - /// List of archived packets. All sent packets that have - /// been matched with received packet are moved to this - /// list for diagnostics purposes. - PktList archived_packets_; - - /// Indicates all packets have to be preserved after matching. - /// By default this is disabled which means that when received - /// packet is matched with sent packet both are deleted. This - /// is important when test is executed for extended period of - /// time and high memory usage might be the issue. - /// When timestamps listing is specified from the command line - /// (using diagnostics selector), all packets have to be preserved - /// so as the printing method may read their timestamps and - /// print it to user. In such usage model it will be rare to - /// run test for extended period of time so it should be fine - /// to keep all packets archived throughout the test. - bool archive_enabled_; - - /// Maximum time elapsed between sending and receiving packet - /// before packet is assumed dropped. - double drop_time_; - - double min_delay_; ///< Minimum delay between sent - ///< and received packets. - double max_delay_; ///< Maximum delay between sent - ///< and received packets. - double sum_delay_; ///< Sum of delays between sent - ///< and received packets. - double sum_delay_squared_; ///< Squared sum of delays between - ///< sent and received packets. - - uint64_t orphans_; ///< Number of orphan received packets. - - uint64_t collected_; ///< Number of garbage collected packets. - - /// Sum of unordered lookup sets. Needed to calculate mean size of - /// lookup set. It is desired that number of unordered lookups is - /// minimal for performance reasons. Tracking number of lookups and - /// mean size of the lookup set should give idea of packets search - /// complexity. - uint64_t unordered_lookup_size_sum_; - - uint64_t unordered_lookups_; ///< Number of unordered sent packets - ///< lookups. - uint64_t ordered_lookups_; ///< Number of ordered sent packets - ///< lookups. - - uint64_t sent_packets_num_; ///< Total number of sent packets. - uint64_t rcvd_packets_num_; ///< Total number of received packets. - boost::posix_time::ptime boot_time_; ///< Time when test is started. - }; - - /// Pointer to ExchangeStats. - typedef boost::shared_ptr<ExchangeStats> ExchangeStatsPtr; - /// Map containing all specified exchange types. - typedef typename std::map<ExchangeType, ExchangeStatsPtr> ExchangesMap; - /// Iterator pointing to \ref ExchangesMap - typedef typename ExchangesMap::const_iterator ExchangesMapIterator; - /// Map containing custom counters. - typedef typename std::map<std::string, CustomCounterPtr> CustomCountersMap; - /// Iterator for \ref CustomCountersMap. - typedef typename CustomCountersMap::const_iterator CustomCountersMapIterator; + ExchangeType xchg_type_; ///< Packet exchange type. + PktList sent_packets_; ///< List of sent packets. + + /// Iterator pointing to the packet on sent list which will most + /// likely match next received packet. This is based on the + /// assumption that server responds in order to incoming packets. + PktListIterator next_sent_; + + PktList rcvd_packets_; ///< List of received packets. + + /// List of archived packets. All sent packets that have + /// been matched with received packet are moved to this + /// list for diagnostics purposes. + PktList archived_packets_; + + /// Indicates all packets have to be preserved after matching. + /// By default this is disabled which means that when received + /// packet is matched with sent packet both are deleted. This + /// is important when test is executed for extended period of + /// time and high memory usage might be the issue. + /// When timestamps listing is specified from the command line + /// (using diagnostics selector), all packets have to be preserved + /// so as the printing method may read their timestamps and + /// print it to user. In such usage model it will be rare to + /// run test for extended period of time so it should be fine + /// to keep all packets archived throughout the test. + bool archive_enabled_; + + /// Maximum time elapsed between sending and receiving packet + /// before packet is assumed dropped. + double drop_time_; + + double min_delay_; ///< Minimum delay between sent + ///< and received packets. + double max_delay_; ///< Maximum delay between sent + ///< and received packets. + double sum_delay_; ///< Sum of delays between sent + ///< and received packets. + double sum_delay_squared_; ///< Squared sum of delays between + ///< sent and received packets. + + uint64_t orphans_; ///< Number of orphan received packets. + + uint64_t collected_; ///< Number of garbage collected packets. + + /// Sum of unordered lookup sets. Needed to calculate mean size of + /// lookup set. It is desired that number of unordered lookups is + /// minimal for performance reasons. Tracking number of lookups and + /// mean size of the lookup set should give idea of packets search + /// complexity. + uint64_t unordered_lookup_size_sum_; + + uint64_t unordered_lookups_; ///< Number of unordered sent packets + ///< lookups. + uint64_t ordered_lookups_; ///< Number of ordered sent packets + ///< lookups. + + uint64_t sent_packets_num_; ///< Total number of sent packets. + uint64_t rcvd_packets_num_; ///< Total number of received packets. + boost::posix_time::ptime boot_time_; ///< Time when test is started. + + bool ignore_timestamp_reorder_; +}; + +/// Pointer to ExchangeStats. +typedef boost::shared_ptr<ExchangeStats> ExchangeStatsPtr; + +/// Map containing all specified exchange types. +typedef typename std::map<ExchangeType, ExchangeStatsPtr> ExchangesMap; +/// Iterator pointing to \ref ExchangesMap +typedef typename ExchangesMap::const_iterator ExchangesMapIterator; + + +/// \brief Statistics Manager +/// +/// This class template is a storage for various performance statistics +/// collected during performance tests execution with perfdhcp tool. +/// +/// Statistics Manager holds lists of sent and received packets and +/// groups them into exchanges. For example: DHCPDISCOVER message and +/// corresponding DHCPOFFER messages belong to one exchange, DHCPREQUEST +/// and corresponding DHCPACK message belong to another exchange etc. +/// In order to update statistics for a particular exchange type, client +/// class passes sent and received packets. Internally, Statistics Manager +/// tries to match transaction id of received packet with sent packet +/// stored on the list of sent packets. When packets are matched the +/// round trip time can be calculated. +/// +class StatsMgr : public boost::noncopyable { +public: /// \brief Constructor. /// /// This constructor by default disables packets archiving mode. @@ -893,14 +653,7 @@ public: /// the test. If this is not selected archiving should be disabled /// for performance reasons and to avoid waste of memory for storing /// large list of archived packets. - /// - /// \param archive_enabled true indicates that packets - /// archive mode is enabled. - StatsMgr(const bool archive_enabled = false) : - exchanges_(), - archive_enabled_(archive_enabled), - boot_time_(boost::posix_time::microsec_clock::universal_time()) { - } + StatsMgr(bool ignore_timestamp_reorder); /// \brief Specify new exchange type. /// @@ -921,7 +674,8 @@ public: ExchangeStatsPtr(new ExchangeStats(xchg_type, drop_time, archive_enabled_, - boot_time_)); + boot_time_, + ignore_timestamp_reorder_)); } /// \brief Check if the exchange type has been specified. @@ -1010,7 +764,7 @@ public: /// \throw isc::BadValue if invalid exchange type specified or /// packet is null. void passSentPacket(const ExchangeType xchg_type, - const boost::shared_ptr<T>& packet) { + const dhcp::PktPtr& packet) { ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); xchg_stats->appendSent(packet); } @@ -1028,12 +782,11 @@ public: /// or packet is null. /// \throw isc::Unexpected if corresponding packet was not /// found on the list of sent packets. - boost::shared_ptr<T> + dhcp::PktPtr passRcvdPacket(const ExchangeType xchg_type, - const boost::shared_ptr<T>& packet) { + const dhcp::PktPtr& packet) { ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); - boost::shared_ptr<T> sent_packet - = xchg_stats->matchPackets(packet); + dhcp::PktPtr sent_packet = xchg_stats->matchPackets(packet); if (sent_packet) { xchg_stats->updateDelays(sent_packet, packet); @@ -1219,35 +972,7 @@ public: return test_period; } - /// \brief Return name of the exchange. - /// - /// Method returns name of the specified exchange type. - /// This function is mainly for logging purposes. - /// - /// \param xchg_type exchange type. - /// \return string representing name of the exchange. - static std::string exchangeToString(ExchangeType xchg_type) { - switch(xchg_type) { - case XCHG_DO: - return("DISCOVER-OFFER"); - case XCHG_RA: - return("REQUEST-ACK"); - case XCHG_RNA: - return("REQUEST-ACK (renewal)"); - case XCHG_SA: - return("SOLICIT-ADVERTISE"); - case XCHG_RR: - return("REQUEST-REPLY"); - case XCHG_RN: - return("RENEW-REPLY"); - case XCHG_RL: - return("RELEASE-REPLY"); - default: - return("Unknown exchange type"); - } - } - - /// \brief Print statistics counters for all exchange types. + /// \brief Print statistics counters for all exchange types. /// /// Method prints statistics for all exchange types. /// Statistics includes: @@ -1260,7 +985,7 @@ public: /// /// \throw isc::InvalidOperation if no exchange type added to /// track statistics. - void printStats() const { + void printStats() const { if (exchanges_.empty()) { isc_throw(isc::InvalidOperation, "no exchange type added for tracking"); @@ -1269,7 +994,7 @@ public: it != exchanges_.end(); ++it) { ExchangeStatsPtr xchg_stats = it->second; - std::cout << "***Statistics for: " << exchangeToString(it->first) + std::cout << "***Statistics for: " << it->first << "***" << std::endl; xchg_stats->printMainStats(); std::cout << std::endl; @@ -1325,7 +1050,7 @@ public: ++it) { ExchangeStatsPtr xchg_stats = it->second; std::cout << "***Timestamps for packets: " - << exchangeToString(it->first) + << it->first << "***" << std::endl; xchg_stats->printTimestamps(); std::cout << std::endl; @@ -1351,6 +1076,12 @@ public: } } + std::tuple<typename ExchangeStats::PktListIterator, typename ExchangeStats::PktListIterator> getSentPackets(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + std::tuple<typename ExchangeStats::PktListIterator, typename ExchangeStats::PktListIterator> sent_packets_its = xchg_stats->getSentPackets(); + return(sent_packets_its); + } + private: /// \brief Return exchange stats object for given exchange type @@ -1383,18 +1114,13 @@ private: bool archive_enabled_; boost::posix_time::ptime boot_time_; ///< Time when test is started. + + bool ignore_timestamp_reorder_; }; -/// Statistics Manager for DHCPv4. -typedef StatsMgr<dhcp::Pkt4> StatsMgr4; /// Pointer to Statistics Manager for DHCPv4; -typedef boost::shared_ptr<StatsMgr4> StatsMgr4Ptr; -/// Statistics Manager for DHCPv6. -typedef StatsMgr<dhcp::Pkt6> StatsMgr6; -/// Pointer to Statistics Manager for DHCPv6. -typedef boost::shared_ptr<StatsMgr6> StatsMgr6Ptr; -/// Packet exchange type. -typedef StatsMgr<>::ExchangeType ExchangeType; +typedef boost::shared_ptr<StatsMgr> StatsMgrPtr; + } // namespace perfdhcp } // namespace isc diff --git a/src/bin/perfdhcp/test_control.cc b/src/bin/perfdhcp/test_control.cc index 55aaacbc2d..a37394c78c 100644 --- a/src/bin/perfdhcp/test_control.cc +++ b/src/bin/perfdhcp/test_control.cc @@ -13,7 +13,6 @@ #include <perfdhcp/perf_pkt6.h> #include <exceptions/exceptions.h> -#include <asiolink/io_address.h> #include <dhcp/libdhcp++.h> #include <dhcp/iface_mgr.h> #include <dhcp/dhcp4.h> @@ -43,19 +42,6 @@ namespace perfdhcp { bool TestControl::interrupted_ = false; -/// \brief Find if diagnostic flag has been set. -/// -/// \param diag diagnostic flag (a,e,i,s,r,t,T). -/// \return true if diagnostics flag has been set. -bool -testDiags(const char diag) { - std::string diags(CommandOptions::instance().getDiags()); - if (diags.find(diag) != std::string::npos) { - return (true); - } - return (false); -} - bool TestControl::waitToExit() const { static ptime exit_time = ptime(not_a_date_time); @@ -97,27 +83,16 @@ TestControl::haveAllPacketsBeenReceived() const { } if (ipversion == 4) { - responses = stats_mgr4_->getRcvdPacketsNum(StatsMgr4::XCHG_DO) + - stats_mgr4_->getRcvdPacketsNum(StatsMgr4::XCHG_RA); + responses = stats_mgr_.getRcvdPacketsNum(ExchangeType::DO) + + stats_mgr_.getRcvdPacketsNum(ExchangeType::RA); } else { - responses = stats_mgr6_->getRcvdPacketsNum(StatsMgr6::XCHG_SA) + - stats_mgr6_->getRcvdPacketsNum(StatsMgr6::XCHG_RR); + responses = stats_mgr_.getRcvdPacketsNum(ExchangeType::SA) + + stats_mgr_.getRcvdPacketsNum(ExchangeType::RR); } return (responses == requests); } -TestControl& -TestControl::instance() { - static TestControl test_control; - return (test_control); -} - -TestControl::TestControl() - : number_generator_(0, CommandOptions::instance().getMacsFromFile().size()) { - reset(); -} - void TestControl::cleanCachedPackets() { CommandOptions& options = CommandOptions::instance(); @@ -187,161 +162,6 @@ TestControl::byte2Hex(const uint8_t b) const { return (stream.str()); } -bool -TestControl::checkExitConditions() const { - if (interrupted_) { - return (true); - } - CommandOptions& options = CommandOptions::instance(); - bool test_period_reached = false; - // Check if test period passed. - if (options.getPeriod() != 0) { - if (options.getIpVersion() == 4) { - time_period period(stats_mgr4_->getTestPeriod()); - if (period.length().total_seconds() >= options.getPeriod()) { - test_period_reached = true; - } - } else if (options.getIpVersion() == 6) { - time_period period = stats_mgr6_->getTestPeriod(); - if (period.length().total_seconds() >= options.getPeriod()) { - test_period_reached = true; - } - } - } - if (test_period_reached) { - if (testDiags('e')) { - std::cout << "reached test-period." << std::endl; - } - if (!waitToExit()) { - return true; - } - } - - bool max_requests = false; - // Check if we reached maximum number of DISCOVER/SOLICIT sent. - if (options.getNumRequests().size() > 0) { - if (options.getIpVersion() == 4) { - if (getSentPacketsNum(StatsMgr4::XCHG_DO) >= - options.getNumRequests()[0]) { - max_requests = true; - } - } else if (options.getIpVersion() == 6) { - if (stats_mgr6_->getSentPacketsNum(StatsMgr6::XCHG_SA) >= - options.getNumRequests()[0]) { - max_requests = true; - } - } - } - // Check if we reached maximum number REQUEST packets. - if (options.getNumRequests().size() > 1) { - if (options.getIpVersion() == 4) { - if (stats_mgr4_->getSentPacketsNum(StatsMgr4::XCHG_RA) >= - options.getNumRequests()[1]) { - max_requests = true; - } - } else if (options.getIpVersion() == 6) { - if (stats_mgr6_->getSentPacketsNum(StatsMgr6::XCHG_RR) >= - options.getNumRequests()[1]) { - max_requests = true; - } - } - } - if (max_requests) { - if (testDiags('e')) { - std::cout << "Reached max requests limit." << std::endl; - } - if (!waitToExit()) { - return true; - } - } - - // Check if we reached maximum number of drops of OFFER/ADVERTISE packets. - bool max_drops = false; - if (options.getMaxDrop().size() > 0) { - if (options.getIpVersion() == 4) { - if (stats_mgr4_->getDroppedPacketsNum(StatsMgr4::XCHG_DO) >= - options.getMaxDrop()[0]) { - max_drops = true; - } - } else if (options.getIpVersion() == 6) { - if (stats_mgr6_->getDroppedPacketsNum(StatsMgr6::XCHG_SA) >= - options.getMaxDrop()[0]) { - max_drops = true; - } - } - } - // Check if we reached maximum number of drops of ACK/REPLY packets. - if (options.getMaxDrop().size() > 1) { - if (options.getIpVersion() == 4) { - if (stats_mgr4_->getDroppedPacketsNum(StatsMgr4::XCHG_RA) >= - options.getMaxDrop()[1]) { - max_drops = true; - } - } else if (options.getIpVersion() == 6) { - if (stats_mgr6_->getDroppedPacketsNum(StatsMgr6::XCHG_RR) >= - options.getMaxDrop()[1]) { - max_drops = true; - } - } - } - if (max_drops) { - if (testDiags('e')) { - std::cout << "Reached maximum drops number." << std::endl; - } - if (!waitToExit()) { - return true; - } - } - - // Check if we reached maximum drops percentage of OFFER/ADVERTISE packets. - bool max_pdrops = false; - if (options.getMaxDropPercentage().size() > 0) { - if (options.getIpVersion() == 4) { - if ((stats_mgr4_->getSentPacketsNum(StatsMgr4::XCHG_DO) > 10) && - ((100. * stats_mgr4_->getDroppedPacketsNum(StatsMgr4::XCHG_DO) / - stats_mgr4_->getSentPacketsNum(StatsMgr4::XCHG_DO)) >= - options.getMaxDropPercentage()[0])) { - max_pdrops = true; - - } - } else if (options.getIpVersion() == 6) { - if ((stats_mgr6_->getSentPacketsNum(StatsMgr6::XCHG_SA) > 10) && - ((100. * stats_mgr6_->getDroppedPacketsNum(StatsMgr6::XCHG_SA) / - stats_mgr6_->getSentPacketsNum(StatsMgr6::XCHG_SA)) >= - options.getMaxDropPercentage()[0])) { - max_pdrops = true; - } - } - } - // Check if we reached maximum drops percentage of ACK/REPLY packets. - if (options.getMaxDropPercentage().size() > 1) { - if (options.getIpVersion() == 4) { - if ((stats_mgr4_->getSentPacketsNum(StatsMgr4::XCHG_RA) > 10) && - ((100. * stats_mgr4_->getDroppedPacketsNum(StatsMgr4::XCHG_RA) / - stats_mgr4_->getSentPacketsNum(StatsMgr4::XCHG_RA)) >= - options.getMaxDropPercentage()[1])) { - max_pdrops = true; - } - } else if (options.getIpVersion() == 6) { - if ((stats_mgr6_->getSentPacketsNum(StatsMgr6::XCHG_RR) > 10) && - ((100. * stats_mgr6_->getDroppedPacketsNum(StatsMgr6::XCHG_RR) / - stats_mgr6_->getSentPacketsNum(StatsMgr6::XCHG_RR)) >= - options.getMaxDropPercentage()[1])) { - max_pdrops = true; - } - } - } - if (max_pdrops) { - if (testDiags('e')) { - std::cout << "Reached maximum percentage of drops." << std::endl; - } - if (!waitToExit()) { - return true; - } - } - return (false); -} - Pkt4Ptr TestControl::createRequestFromAck(const dhcp::Pkt4Ptr& ack) { if (!ack) { @@ -638,26 +458,6 @@ TestControl::getRequestedIpOffset() const { return (rip_offset); } -uint64_t -TestControl::getRcvdPacketsNum(ExchangeType xchg_type) const { - uint8_t ip_version = CommandOptions::instance().getIpVersion(); - if (ip_version == 4) { - return (stats_mgr4_->getRcvdPacketsNum(xchg_type)); - } - return (stats_mgr6_-> - getRcvdPacketsNum(static_cast<StatsMgr6::ExchangeType>(xchg_type))); -} - -uint64_t -TestControl::getSentPacketsNum(ExchangeType xchg_type) const { - uint8_t ip_version = CommandOptions::instance().getIpVersion(); - if (ip_version == 4) { - return (stats_mgr4_->getSentPacketsNum(xchg_type)); - } - return (stats_mgr6_-> - getSentPacketsNum(static_cast<StatsMgr6::ExchangeType>(xchg_type))); -} - int TestControl::getServerIdOffset() const { int srvid_offset = CommandOptions::instance().getIpVersion() == 4 ? @@ -713,153 +513,7 @@ TestControl::initPacketTemplates() { } void -TestControl::initializeStatsMgr() { - CommandOptions& options = CommandOptions::instance(); - // Check if packet archive mode is required. If user - // requested diagnostics option -x t we have to enable - // it so as StatsMgr preserves all packets. - const bool archive_mode = testDiags('t') ? true : false; - if (options.getIpVersion() == 4) { - stats_mgr4_.reset(); - stats_mgr4_ = StatsMgr4Ptr(new StatsMgr4(archive_mode)); - stats_mgr4_->addExchangeStats(StatsMgr4::XCHG_DO, - options.getDropTime()[0]); - if (options.getExchangeMode() == CommandOptions::DORA_SARR) { - stats_mgr4_->addExchangeStats(StatsMgr4::XCHG_RA, - options.getDropTime()[1]); - } - if (options.getRenewRate() != 0) { - stats_mgr4_->addExchangeStats(StatsMgr4::XCHG_RNA); - } - - } else if (options.getIpVersion() == 6) { - stats_mgr6_.reset(); - stats_mgr6_ = StatsMgr6Ptr(new StatsMgr6(archive_mode)); - stats_mgr6_->addExchangeStats(StatsMgr6::XCHG_SA, - options.getDropTime()[0]); - if (options.getExchangeMode() == CommandOptions::DORA_SARR) { - stats_mgr6_->addExchangeStats(StatsMgr6::XCHG_RR, - options.getDropTime()[1]); - } - if (options.getRenewRate() != 0) { - stats_mgr6_->addExchangeStats(StatsMgr6::XCHG_RN); - } - if (options.getReleaseRate() != 0) { - stats_mgr6_->addExchangeStats(StatsMgr6::XCHG_RL); - } - } - if (testDiags('i')) { - if (options.getIpVersion() == 4) { - stats_mgr4_->addCustomCounter("shortwait", "Short waits for packets"); - } else if (options.getIpVersion() == 6) { - stats_mgr6_->addCustomCounter("shortwait", "Short waits for packets"); - } - } -} - -int -TestControl::openSocket() const { - CommandOptions& options = CommandOptions::instance(); - std::string localname = options.getLocalName(); - std::string servername = options.getServerName(); - uint16_t port = options.getLocalPort(); - int sock = 0; - - uint8_t family = (options.getIpVersion() == 6) ? AF_INET6 : AF_INET; - IOAddress remoteaddr(servername); - - // Check for mismatch between IP option and server address - if (family != remoteaddr.getFamily()) { - isc_throw(InvalidParameter, - "Values for IP version: " << - static_cast<unsigned int>(options.getIpVersion()) << - " and server address: " << servername << " are mismatched."); - } - - if (port == 0) { - if (family == AF_INET6) { - // need server port (547) because the server is acting as a relay agent - port = DHCP6_CLIENT_PORT; - // if acting as a relay agent change port. - if (options.isUseRelayedV6()) { - port = DHCP6_SERVER_PORT; - } - } else if (options.getIpVersion() == 4) { - port = 67; /// @todo: find out why port 68 is wrong here. - } - } - - // Local name is specified along with '-l' option. - // It may point to interface name or local address. - if (!localname.empty()) { - // CommandOptions should be already aware whether local name - // is interface name or address because it uses IfaceMgr to - // scan interfaces and get's their names. - if (options.isInterface()) { - sock = IfaceMgr::instance().openSocketFromIface(localname, - port, - family); - } else { - IOAddress localaddr(localname); - sock = IfaceMgr::instance().openSocketFromAddress(localaddr, - port); - } - } else if (!servername.empty()) { - // If only server name is given we will need to try to resolve - // the local address to bind socket to based on remote address. - sock = IfaceMgr::instance().openSocketFromRemoteAddress(remoteaddr, - port); - } - if (sock <= 0) { - isc_throw(BadValue, "unable to open socket to communicate with " - "DHCP server"); - } - - // IfaceMgr does not set broadcast option on the socket. We rely - // on CommandOptions object to find out if socket has to have - // broadcast enabled. - if ((options.getIpVersion() == 4) && options.isBroadcast()) { - int broadcast_enable = 1; - int ret = setsockopt(sock, SOL_SOCKET, SO_BROADCAST, - &broadcast_enable, sizeof(broadcast_enable)); - if (ret < 0) { - isc_throw(InvalidOperation, - "unable to set broadcast option on the socket"); - } - } else if (options.getIpVersion() == 6) { - // If remote address is multicast we need to enable it on - // the socket that has been created. - if (remoteaddr.isV6Multicast()) { - int hops = 1; - int ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, - &hops, sizeof(hops)); - // If user specified interface name with '-l' the - // IPV6_MULTICAST_IF has to be set. - if ((ret >= 0) && options.isInterface()) { - IfacePtr iface = - IfaceMgr::instance().getIface(options.getLocalName()); - if (iface == NULL) { - isc_throw(Unexpected, "unknown interface " - << options.getLocalName()); - } - int idx = iface->getIndex(); - ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, - &idx, sizeof(idx)); - } - if (ret < 0) { - isc_throw(InvalidOperation, - "unable to enable multicast on socket " << sock - << ". errno = " << errno); - } - } - } - - return (sock); -} - -void -TestControl::sendPackets(const PerfSocket& socket, - const uint64_t packets_num, +TestControl::sendPackets(const uint64_t packets_num, const bool preload /* = false */) { CommandOptions& options = CommandOptions::instance(); for (uint64_t i = packets_num; i > 0; --i) { @@ -867,31 +521,30 @@ TestControl::sendPackets(const PerfSocket& socket, // No template packets means that no -T option was specified. // We have to build packets ourselves. if (template_buffers_.empty()) { - sendDiscover4(socket, preload); + sendDiscover4(preload); } else { /// @todo add defines for packet type index that can be /// used to access template_buffers_. - sendDiscover4(socket, template_buffers_[0], preload); + sendDiscover4(template_buffers_[0], preload); } } else { // No template packets means that no -T option was specified. // We have to build packets ourselves. if (template_buffers_.empty()) { - sendSolicit6(socket, preload); + sendSolicit6(preload); } else { /// @todo add defines for packet type index that can be /// used to access template_buffers_. - sendSolicit6(socket, template_buffers_[0], preload); + sendSolicit6(template_buffers_[0], preload); } } } } uint64_t -TestControl::sendMultipleRequests(const PerfSocket& socket, - const uint64_t msg_num) { +TestControl::sendMultipleRequests(const uint64_t msg_num) { for (uint64_t i = 0; i < msg_num; ++i) { - if (!sendRequestFromAck(socket)) { + if (!sendRequestFromAck()) { return (i); } } @@ -899,11 +552,10 @@ TestControl::sendMultipleRequests(const PerfSocket& socket, } uint64_t -TestControl::sendMultipleMessages6(const PerfSocket& socket, - const uint32_t msg_type, +TestControl::sendMultipleMessages6(const uint32_t msg_type, const uint64_t msg_num) { for (uint64_t i = 0; i < msg_num; ++i) { - if (!sendMessageFromReply(msg_type, socket)) { + if (!sendMessageFromReply(msg_type)) { return (i); } } @@ -999,28 +651,26 @@ TestControl::printRate() const { double rate = 0; CommandOptions& options = CommandOptions::instance(); std::string exchange_name = "4-way exchanges"; + ExchangeType xchg_type = ExchangeType::DO; if (options.getIpVersion() == 4) { - StatsMgr4::ExchangeType xchg_type = + xchg_type = options.getExchangeMode() == CommandOptions::DO_SA ? - StatsMgr4::XCHG_DO : StatsMgr4::XCHG_RA; - if (xchg_type == StatsMgr4::XCHG_DO) { + ExchangeType::DO : ExchangeType::RA; + if (xchg_type == ExchangeType::DO) { exchange_name = "DISCOVER-OFFER"; } - double duration = - stats_mgr4_->getTestPeriod().length().total_nanoseconds() / 1e9; - rate = stats_mgr4_->getRcvdPacketsNum(xchg_type) / duration; } else if (options.getIpVersion() == 6) { - StatsMgr6::ExchangeType xchg_type = + xchg_type = options.getExchangeMode() == CommandOptions::DO_SA ? - StatsMgr6::XCHG_SA : StatsMgr6::XCHG_RR; - if (xchg_type == StatsMgr6::XCHG_SA) { + ExchangeType::SA : ExchangeType::RR; + if (xchg_type == ExchangeType::SA) { exchange_name = options.isRapidCommit() ? "Solicit-Reply" : "Solicit-Advertise"; } - double duration = - stats_mgr6_->getTestPeriod().length().total_nanoseconds() / 1e9; - rate = stats_mgr6_->getRcvdPacketsNum(xchg_type) / duration; } + double duration = + stats_mgr_.getTestPeriod().length().total_nanoseconds() / 1e9; + rate = stats_mgr_.getRcvdPacketsNum(xchg_type) / duration; std::ostringstream s; s << "***Rate statistics***" << std::endl; s << "Rate: " << rate << " " << exchange_name << "/second"; @@ -1038,11 +688,7 @@ TestControl::printIntermediateStats() { ptime now = microsec_clock::universal_time(); time_period time_since_report(last_report_, now); if (time_since_report.length().total_seconds() >= delay) { - if (options.getIpVersion() == 4) { - stats_mgr4_->printIntermediateStats(); - } else if (options.getIpVersion() == 6) { - stats_mgr6_->printIntermediateStats(); - } + stats_mgr_.printIntermediateStats(); last_report_ = now; } } @@ -1050,25 +696,9 @@ TestControl::printIntermediateStats() { void TestControl::printStats() const { printRate(); - CommandOptions& options = CommandOptions::instance(); - if (options.getIpVersion() == 4) { - if (!stats_mgr4_) { - isc_throw(InvalidOperation, "Statistics Manager for DHCPv4 " - "hasn't been initialized"); - } - stats_mgr4_->printStats(); - if (testDiags('i')) { - stats_mgr4_->printCustomCounters(); - } - } else if (options.getIpVersion() == 6) { - if (!stats_mgr6_) { - isc_throw(InvalidOperation, "Statistics Manager for DHCPv6 " - "hasn't been initialized"); - } - stats_mgr6_->printStats(); - if (testDiags('i')) { - stats_mgr6_->printCustomCounters(); - } + stats_mgr_.printStats(); + if (testDiags('i')) { + stats_mgr_.printCustomCounters(); } } @@ -1137,32 +767,31 @@ TestControl::readPacketTemplate(const std::string& file_name) { } void -TestControl::processReceivedPacket4(const PerfSocket& socket, - const Pkt4Ptr& pkt4) { +TestControl::processReceivedPacket4(const Pkt4Ptr& pkt4) { if (pkt4->getType() == DHCPOFFER) { - Pkt4Ptr discover_pkt4(stats_mgr4_->passRcvdPacket(StatsMgr4::XCHG_DO, - pkt4)); + PktPtr pkt = stats_mgr_.passRcvdPacket(ExchangeType::DO, pkt4); + Pkt4Ptr discover_pkt4(boost::dynamic_pointer_cast<Pkt4>(pkt)); CommandOptions::ExchangeMode xchg_mode = CommandOptions::instance().getExchangeMode(); if ((xchg_mode == CommandOptions::DORA_SARR) && discover_pkt4) { if (template_buffers_.size() < 2) { - sendRequest4(socket, discover_pkt4, pkt4); + sendRequest4(discover_pkt4, pkt4); } else { /// @todo add defines for packet type index that can be /// used to access template_buffers_. - sendRequest4(socket, template_buffers_[1], discover_pkt4, pkt4); + sendRequest4(template_buffers_[1], discover_pkt4, pkt4); } } } else if (pkt4->getType() == DHCPACK) { // If received message is DHCPACK, we have to check if this is // a response to 4-way exchange. We'll match this packet with // a DHCPREQUEST sent as part of the 4-way exchanges. - if (stats_mgr4_->passRcvdPacket(StatsMgr4::XCHG_RA, pkt4)) { + if (stats_mgr_.passRcvdPacket(ExchangeType::RA, pkt4)) { // The DHCPACK belongs to DHCPREQUEST-DHCPACK exchange type. // So, we may need to keep this DHCPACK in the storage if renews. // Note that, DHCPACK messages hold the information about // leases assigned. We use this information to renew. - if (stats_mgr4_->hasExchangeStats(StatsMgr4::XCHG_RNA)) { + if (stats_mgr_.hasExchangeStats(ExchangeType::RNA)) { // Renew messages are sent, because StatsMgr has the // specific exchange type specified. Let's append the DHCPACK. // message to a storage @@ -1173,19 +802,18 @@ TestControl::processReceivedPacket4(const PerfSocket& socket, // renewal. In this case we first check if StatsMgr has exchange type // for renew specified, and if it has, if there is a corresponding // renew message for the received DHCPACK. - } else if (stats_mgr4_->hasExchangeStats(StatsMgr4::XCHG_RNA)) { - stats_mgr4_->passRcvdPacket(StatsMgr4::XCHG_RNA, pkt4); + } else if (stats_mgr_.hasExchangeStats(ExchangeType::RNA)) { + stats_mgr_.passRcvdPacket(ExchangeType::RNA, pkt4); } } } void -TestControl::processReceivedPacket6(const PerfSocket& socket, - const Pkt6Ptr& pkt6) { +TestControl::processReceivedPacket6(const Pkt6Ptr& pkt6) { uint8_t packet_type = pkt6->getType(); if (packet_type == DHCPV6_ADVERTISE) { - Pkt6Ptr solicit_pkt6(stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_SA, - pkt6)); + PktPtr pkt = stats_mgr_.passRcvdPacket(ExchangeType::SA, pkt6); + Pkt6Ptr solicit_pkt6(boost::dynamic_pointer_cast<Pkt6>(pkt)); CommandOptions::ExchangeMode xchg_mode = CommandOptions::instance().getExchangeMode(); if ((xchg_mode == CommandOptions::DORA_SARR) && solicit_pkt6) { @@ -1193,11 +821,11 @@ TestControl::processReceivedPacket6(const PerfSocket& socket, /// We might want to check if STATUS_CODE option is non-zero /// and if there is IAADR option in IA_NA. if (template_buffers_.size() < 2) { - sendRequest6(socket, pkt6); + sendRequest6(pkt6); } else { /// @todo add defines for packet type index that can be /// used to access template_buffers_. - sendRequest6(socket, template_buffers_[1], pkt6); + sendRequest6(template_buffers_[1], pkt6); } } } else if (packet_type == DHCPV6_REPLY) { @@ -1205,14 +833,14 @@ TestControl::processReceivedPacket6(const PerfSocket& socket, // type the Reply message belongs to. It is doable by matching the Reply // transaction id with the transaction id of the sent Request, Renew // or Release. First we start with the Request. - if (stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RR, pkt6)) { + if (stats_mgr_.passRcvdPacket(ExchangeType::RR, pkt6)) { // The Reply belongs to Request-Reply exchange type. So, we may need // to keep this Reply in the storage if Renews or/and Releases are // being sent. Note that, Reply messages hold the information about // leases assigned. We use this information to construct Renew and // Release messages. - if (stats_mgr6_->hasExchangeStats(StatsMgr6::XCHG_RN) || - stats_mgr6_->hasExchangeStats(StatsMgr6::XCHG_RL)) { + if (stats_mgr_.hasExchangeStats(ExchangeType::RN) || + stats_mgr_.hasExchangeStats(ExchangeType::RL)) { // Renew or Release messages are sent, because StatsMgr has the // specific exchange type specified. Let's append the Reply // message to a storage. @@ -1225,28 +853,28 @@ TestControl::processReceivedPacket6(const PerfSocket& socket, // a corresponding Renew message for the received Reply. If not, // we check that StatsMgr has exchange type for Release specified, // as possibly the Reply has been sent in response to Release. - } else if (!(stats_mgr6_->hasExchangeStats(StatsMgr6::XCHG_RN) && - stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RN, pkt6)) && - stats_mgr6_->hasExchangeStats(StatsMgr6::XCHG_RL)) { + } else if (!(stats_mgr_.hasExchangeStats(ExchangeType::RN) && + stats_mgr_.passRcvdPacket(ExchangeType::RN, pkt6)) && + stats_mgr_.hasExchangeStats(ExchangeType::RL)) { // At this point, it is only possible that the Reply has been sent // in response to a Release. Try to match the Reply with Release. - stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RL, pkt6); + stats_mgr_.passRcvdPacket(ExchangeType::RL, pkt6); } } } unsigned int -TestControl::consumeReceivedPackets(Receiver& receiver, const PerfSocket& socket) { +TestControl::consumeReceivedPackets() { unsigned int pkt_count = 0; PktPtr pkt; - while ((pkt = receiver.getPkt())) { + while ((pkt = receiver_.getPkt())) { pkt_count += 1; if (CommandOptions::instance().getIpVersion() == 4) { Pkt4Ptr pkt4 = boost::dynamic_pointer_cast<Pkt4>(pkt); - processReceivedPacket4(socket, pkt4); + processReceivedPacket4(pkt4); } else { Pkt6Ptr pkt6 = boost::dynamic_pointer_cast<Pkt6>(pkt); - processReceivedPacket6(socket, pkt6); + processReceivedPacket6(pkt6); } } return pkt_count; @@ -1328,11 +956,6 @@ TestControl::registerOptionFactories() const { void TestControl::reset() { - CommandOptions& options = CommandOptions::instance(); - basic_rate_control_.setRate(options.getRate()); - renew_rate_control_.setRate(options.getRenewRate()); - release_rate_control_.setRate(options.getReleaseRate()); - transid_gen_.reset(); last_report_ = microsec_clock::universal_time(); // Actual generators will have to be set later on because we need to @@ -1343,8 +966,10 @@ TestControl::reset() { interrupted_ = false; } -int -TestControl::run() { +TestControl::TestControl(bool ignore_timestamp_reorder) : + number_generator_(0, CommandOptions::instance().getMacsFromFile().size()), + stats_mgr_(ignore_timestamp_reorder) +{ // Reset singleton state before test starts. reset(); @@ -1374,10 +999,6 @@ TestControl::run() { printDiagnostics(); // Option factories have to be registered. registerOptionFactories(); - PerfSocket socket(openSocket()); - if (!socket.valid_) { - isc_throw(Unexpected, "invalid socket descriptor"); - } // Initialize packet templates. initPacketTemplates(); // Initialize randomization seed. @@ -1392,138 +1013,6 @@ TestControl::run() { } // If user interrupts the program we will exit gracefully. signal(SIGINT, TestControl::handleInterrupt); - - // Initialize Statistics Manager. Release previous if any. - initializeStatsMgr(); - - Receiver receiver(socket); - - // Preload server with the number of packets. - if (options.getPreload() > 0) { - sendPackets(socket, options.getPreload(), true); - } - - // Fork and run command specified with -w<wrapped-command> - if (!options.getWrapped().empty()) { - runWrapped(); - } - - receiver.start(); - - for (;;) { - // Calculate number of packets to be sent to stay - // catch up with rate. - uint64_t packets_due = basic_rate_control_.getOutboundMessageCount(); - if ((packets_due == 0) && testDiags('i')) { - if (options.getIpVersion() == 4) { - stats_mgr4_->incrementCounter("shortwait"); - } else if (options.getIpVersion() == 6) { - stats_mgr6_->incrementCounter("shortwait"); - } - } - - // Pull some packets from receiver thread, process them, update some stats - // and respond to the server if needed. - auto pkt_count = consumeReceivedPackets(receiver, socket); - - // If there is nothing to do in this loop iteration then do some sleep to make - // CPU idle for a moment, to not consume 100% CPU all the time - // but only if it is not that high request rate expected. - if (options.getRate() < 10000 && packets_due == 0 && pkt_count == 0) { - /// @todo: need to implement adaptive time here, so the sleep time - /// is not fixed, but adjusts to current situation. - usleep(1); - } - - // If test period finished, maximum number of packet drops - // has been reached or test has been interrupted we have to - // finish the test. - if (checkExitConditions()) { - break; - } - - // Initiate new DHCP packet exchanges. - sendPackets(socket, packets_due); - - // If -f<renew-rate> option was specified we have to check how many - // Renew packets should be sent to catch up with a desired rate. - if (options.getRenewRate() != 0) { - uint64_t renew_packets_due = - renew_rate_control_.getOutboundMessageCount(); - - // Send multiple renews to satisfy the desired rate. - if (options.getIpVersion() == 4) { - sendMultipleRequests(socket, renew_packets_due); - } else { - sendMultipleMessages6(socket, DHCPV6_RENEW, renew_packets_due); - } - } - - // If -F<release-rate> option was specified we have to check how many - // Release messages should be sent to catch up with a desired rate. - if ((options.getIpVersion() == 6) && (options.getReleaseRate() != 0)) { - uint64_t release_packets_due = - release_rate_control_.getOutboundMessageCount(); - // Send Release messages. - sendMultipleMessages6(socket, DHCPV6_RELEASE, release_packets_due); - } - - // Report delay means that user requested printing number - // of sent/received/dropped packets repeatedly. - if (options.getReportDelay() > 0) { - printIntermediateStats(); - } - - // If we are sending Renews to the server, the Reply packets are cached - // so as leases for which we send Renews can be identified. The major - // issue with this approach is that most of the time we are caching - // more packets than we actually need. This function removes excessive - // Reply messages to reduce the memory and CPU utilization. Note that - // searches in the long list of Reply packets increases CPU utilization. - cleanCachedPackets(); - } - - receiver.stop(); - - printStats(); - - if (!options.getWrapped().empty()) { - // true means that we execute wrapped command with 'stop' argument. - runWrapped(true); - } - - // Print packet timestamps - if (testDiags('t')) { - if (options.getIpVersion() == 4) { - stats_mgr4_->printTimestamps(); - } else if (options.getIpVersion() == 6) { - stats_mgr6_->printTimestamps(); - } - } - - // Print server id. - if (testDiags('s') && (first_packet_serverid_.size() > 0)) { - std::cout << "Server id: " << vector2Hex(first_packet_serverid_) << std::endl; - } - - // Diagnostics flag 'e' means show exit reason. - if (testDiags('e')) { - std::cout << "Interrupted" << std::endl; - } - // Print packet templates. Even if -T options have not been specified the - // dynamically build packet will be printed if at least one has been sent. - if (testDiags('T')) { - printTemplates(); - } - - int ret_code = 0; - // Check if any packet drops occurred. - if (options.getIpVersion() == 4) { - ret_code = stats_mgr4_->droppedPackets() ? 3 : 0; - } else if (options.getIpVersion() == 6) { - ret_code = stats_mgr6_->droppedPackets() ? 3 : 0; - } - return (ret_code); } void @@ -1562,8 +1051,7 @@ TestControl::saveFirstPacket(const Pkt6Ptr& pkt) { } void -TestControl::sendDiscover4(const PerfSocket& socket, - const bool preload /*= false*/) { +TestControl::sendDiscover4(const bool preload /*= false*/) { // Generate the MAC address to be passed in the packet. uint8_t randomized = 0; std::vector<uint8_t> mac_address = generateMacAddress(randomized); @@ -1587,7 +1075,7 @@ TestControl::sendDiscover4(const PerfSocket& socket, // Set client's and server's ports as well as server's address, // and local (relay) address. - setDefaults4(socket, pkt4); + setDefaults4(pkt4); // Set hardware address pkt4->setHWAddr(HTYPE_ETHER, mac_address.size(), mac_address); @@ -1601,18 +1089,13 @@ TestControl::sendDiscover4(const PerfSocket& socket, pkt4->pack(); IfaceMgr::instance().send(pkt4); if (!preload) { - if (!stats_mgr4_) { - isc_throw(InvalidOperation, "Statistics Manager for DHCPv4 " - "hasn't been initialized"); - } - stats_mgr4_->passSentPacket(StatsMgr4::XCHG_DO, pkt4); + stats_mgr_.passSentPacket(ExchangeType::DO, pkt4); } saveFirstPacket(pkt4); } void -TestControl::sendDiscover4(const PerfSocket& socket, - const std::vector<uint8_t>& template_buf, +TestControl::sendDiscover4(const std::vector<uint8_t>& template_buf, const bool preload /* = false */) { // Get the first argument if multiple the same arguments specified // in the command line. First one refers to DISCOVER packets. @@ -1644,25 +1127,21 @@ TestControl::sendDiscover4(const PerfSocket& socket, // Replace MAC address in the template with actual MAC address. pkt4->writeAt(rand_offset, mac_address.begin(), mac_address.end()); // Create a packet from the temporary buffer. - setDefaults4(socket, boost::static_pointer_cast<Pkt4>(pkt4)); + setDefaults4(boost::static_pointer_cast<Pkt4>(pkt4)); // Pack the input packet buffer to output buffer so as it can // be sent to server. pkt4->rawPack(); IfaceMgr::instance().send(boost::static_pointer_cast<Pkt4>(pkt4)); if (!preload) { - if (!stats_mgr4_) { - isc_throw(InvalidOperation, "Statistics Manager for DHCPv4 " - "hasn't been initialized"); - } // Update packet stats. - stats_mgr4_->passSentPacket(StatsMgr4::XCHG_DO, + stats_mgr_.passSentPacket(ExchangeType::DO, boost::static_pointer_cast<Pkt4>(pkt4)); } saveFirstPacket(pkt4); } bool -TestControl::sendRequestFromAck(const PerfSocket& socket) { +TestControl::sendRequestFromAck() { // Get one of the recorded DHCPACK messages. Pkt4Ptr ack = ack_storage_.getRandom(); if (!ack) { @@ -1671,7 +1150,7 @@ TestControl::sendRequestFromAck(const PerfSocket& socket) { // Create message of the specified type. Pkt4Ptr msg = createRequestFromAck(ack); - setDefaults4(socket, msg); + setDefaults4(msg); // Add any extra options that user may have specified. addExtraOpts(msg); @@ -1679,18 +1158,13 @@ TestControl::sendRequestFromAck(const PerfSocket& socket) { msg->pack(); // And send it. IfaceMgr::instance().send(msg); - if (!stats_mgr4_) { - isc_throw(Unexpected, "Statistics Manager for DHCPv4 " - "hasn't been initialized"); - } - stats_mgr4_->passSentPacket(StatsMgr4::XCHG_RNA, msg); + stats_mgr_.passSentPacket(ExchangeType::RNA, msg); return (true); } bool -TestControl::sendMessageFromReply(const uint16_t msg_type, - const PerfSocket& socket) { +TestControl::sendMessageFromReply(const uint16_t msg_type) { // We only permit Release or Renew messages to be sent using this function. if (msg_type != DHCPV6_RENEW && msg_type != DHCPV6_RELEASE) { isc_throw(isc::BadValue, "invalid message type " << msg_type @@ -1702,7 +1176,7 @@ TestControl::sendMessageFromReply(const uint16_t msg_type, } // Prepare the message of the specified type. Pkt6Ptr msg = createMessageFromReply(msg_type, reply); - setDefaults6(socket, msg); + setDefaults6(msg); // Add any extra options that user may have specified. addExtraOpts(msg); @@ -1710,18 +1184,13 @@ TestControl::sendMessageFromReply(const uint16_t msg_type, msg->pack(); // And send it. IfaceMgr::instance().send(msg); - if (!stats_mgr6_) { - isc_throw(Unexpected, "Statistics Manager for DHCPv6 " - "hasn't been initialized"); - } - stats_mgr6_->passSentPacket((msg_type == DHCPV6_RENEW ? StatsMgr6::XCHG_RN - : StatsMgr6::XCHG_RL), msg); + stats_mgr_.passSentPacket((msg_type == DHCPV6_RENEW ? ExchangeType::RN + : ExchangeType::RL), msg); return (true); } void -TestControl::sendRequest4(const PerfSocket& socket, - const dhcp::Pkt4Ptr& discover_pkt4, +TestControl::sendRequest4(const dhcp::Pkt4Ptr& discover_pkt4, const dhcp::Pkt4Ptr& offer_pkt4) { // Use the same transaction id as the one used in the discovery packet. const uint32_t transid = discover_pkt4->getTransid(); @@ -1740,7 +1209,7 @@ TestControl::sendRequest4(const PerfSocket& socket, isc_throw(BadValue, "there is no SERVER_IDENTIFIER option " << "in OFFER message"); } - if (stats_mgr4_->getRcvdPacketsNum(StatsMgr4::XCHG_DO) == 1) { + if (stats_mgr_.getRcvdPacketsNum(ExchangeType::DO) == 1) { first_packet_serverid_ = opt_serverid->getData(); } pkt4->addOption(opt_serverid); @@ -1762,7 +1231,7 @@ TestControl::sendRequest4(const PerfSocket& socket, pkt4->addOption(opt_parameter_list); // Set client's and server's ports as well as server's address, // and local (relay) address. - setDefaults4(socket, pkt4); + setDefaults4(pkt4); // Add any extra options that user may have specified. addExtraOpts(pkt4); @@ -1777,17 +1246,12 @@ TestControl::sendRequest4(const PerfSocket& socket, // Prepare on wire data to send. pkt4->pack(); IfaceMgr::instance().send(pkt4); - if (!stats_mgr4_) { - isc_throw(InvalidOperation, "Statistics Manager for DHCPv4 " - "hasn't been initialized"); - } - stats_mgr4_->passSentPacket(StatsMgr4::XCHG_RA, pkt4); + stats_mgr_.passSentPacket(ExchangeType::RA, pkt4); saveFirstPacket(pkt4); } void -TestControl::sendRequest4(const PerfSocket& socket, - const std::vector<uint8_t>& template_buf, +TestControl::sendRequest4(const std::vector<uint8_t>& template_buf, const dhcp::Pkt4Ptr& discover_pkt4, const dhcp::Pkt4Ptr& offer_pkt4) { // Get the second argument if multiple the same arguments specified @@ -1857,7 +1321,7 @@ TestControl::sendRequest4(const PerfSocket& socket, opt_serverid_offer->getData(), sid_offset)); pkt4->addOption(opt_serverid); - if (stats_mgr4_->getRcvdPacketsNum(StatsMgr4::XCHG_DO) == 1) { + if (stats_mgr_.getRcvdPacketsNum(ExchangeType::DO) == 1) { first_packet_serverid_ = opt_serverid_offer->getData(); } } @@ -1881,7 +1345,7 @@ TestControl::sendRequest4(const PerfSocket& socket, opt_requested_ip->setUint32(yiaddr.toUint32()); pkt4->addOption(opt_requested_ip); - setDefaults4(socket, boost::static_pointer_cast<Pkt4>(pkt4)); + setDefaults4(boost::static_pointer_cast<Pkt4>(pkt4)); // Add any extra options that user may have specified. addExtraOpts(pkt4); @@ -1889,19 +1353,14 @@ TestControl::sendRequest4(const PerfSocket& socket, // Prepare on-wire data. pkt4->rawPack(); IfaceMgr::instance().send(boost::static_pointer_cast<Pkt4>(pkt4)); - if (!stats_mgr4_) { - isc_throw(InvalidOperation, "Statistics Manager for DHCPv4 " - "hasn't been initialized"); - } // Update packet stats. - stats_mgr4_->passSentPacket(StatsMgr4::XCHG_RA, - boost::static_pointer_cast<Pkt4>(pkt4)); + stats_mgr_.passSentPacket(ExchangeType::RA, + boost::static_pointer_cast<Pkt4>(pkt4)); saveFirstPacket(pkt4); } void -TestControl::sendRequest6(const PerfSocket& socket, - const Pkt6Ptr& advertise_pkt6) { +TestControl::sendRequest6(const Pkt6Ptr& advertise_pkt6) { const uint32_t transid = generateTransid(); Pkt6Ptr pkt6(new Pkt6(DHCPV6_REQUEST, transid)); // Set elapsed time. @@ -1926,7 +1385,7 @@ TestControl::sendRequest6(const PerfSocket& socket, if (!opt_serverid) { isc_throw(Unexpected, "server id not found in received packet"); } - if (stats_mgr6_->getRcvdPacketsNum(StatsMgr6::XCHG_SA) == 1) { + if (stats_mgr_.getRcvdPacketsNum(ExchangeType::SA) == 1) { first_packet_serverid_ = opt_serverid->getData(); } pkt6->addOption(opt_serverid); @@ -1940,7 +1399,7 @@ TestControl::sendRequest6(const PerfSocket& socket, copyIaOptions(advertise_pkt6, pkt6); // Set default packet data. - setDefaults6(socket, pkt6); + setDefaults6(pkt6); // Add any extra options that user may have specified. addExtraOpts(pkt6); @@ -1948,17 +1407,12 @@ TestControl::sendRequest6(const PerfSocket& socket, // Prepare on-wire data. pkt6->pack(); IfaceMgr::instance().send(pkt6); - if (!stats_mgr6_) { - isc_throw(InvalidOperation, "Statistics Manager for DHCPv6 " - "hasn't been initialized"); - } - stats_mgr6_->passSentPacket(StatsMgr6::XCHG_RR, pkt6); + stats_mgr_.passSentPacket(ExchangeType::RR, pkt6); saveFirstPacket(pkt6); } void -TestControl::sendRequest6(const PerfSocket& socket, - const std::vector<uint8_t>& template_buf, +TestControl::sendRequest6(const std::vector<uint8_t>& template_buf, const Pkt6Ptr& advertise_pkt6) { // Get the second argument if multiple the same arguments specified // in the command line. Second one refers to REQUEST packets. @@ -2004,7 +1458,7 @@ TestControl::sendRequest6(const PerfSocket& socket, opt_serverid_advertise->getData(), sid_offset)); pkt6->addOption(opt_serverid); - if (stats_mgr6_->getRcvdPacketsNum(StatsMgr6::XCHG_SA) == 1) { + if (stats_mgr_.getRcvdPacketsNum(ExchangeType::SA) == 1) { first_packet_serverid_ = opt_serverid_advertise->getData(); } } @@ -2048,7 +1502,7 @@ TestControl::sendRequest6(const PerfSocket& socket, rand_offset)); pkt6->addOption(opt_clientid); // Set default packet data. - setDefaults6(socket, pkt6); + setDefaults6(pkt6); // Add any extra options that user may have specified. addExtraOpts(pkt6); @@ -2057,12 +1511,8 @@ TestControl::sendRequest6(const PerfSocket& socket, pkt6->rawPack(); // Send packet. IfaceMgr::instance().send(pkt6); - if (!stats_mgr6_) { - isc_throw(InvalidOperation, "Statistics Manager for DHCPv6 " - "hasn't been initialized"); - } // Update packet stats. - stats_mgr6_->passSentPacket(StatsMgr6::XCHG_RR, pkt6); + stats_mgr_.passSentPacket(ExchangeType::RR, pkt6); // When 'T' diagnostics flag is specified it means that user requested // printing packet contents. It will be just one (first) packet which @@ -2076,8 +1526,7 @@ TestControl::sendRequest6(const PerfSocket& socket, } void -TestControl::sendSolicit6(const PerfSocket& socket, - const bool preload /*= false*/) { +TestControl::sendSolicit6(const bool preload /*= false*/) { // Generate DUID to be passed to the packet uint8_t randomized = 0; std::vector<uint8_t> duid = generateDuid(randomized); @@ -2108,7 +1557,7 @@ TestControl::sendSolicit6(const PerfSocket& socket, pkt6->addOption(Option::factory(Option::V6, D6O_IA_PD)); } - setDefaults6(socket, pkt6); + setDefaults6(pkt6); // Add any extra options that user may have specified. addExtraOpts(pkt6); @@ -2116,19 +1565,14 @@ TestControl::sendSolicit6(const PerfSocket& socket, pkt6->pack(); IfaceMgr::instance().send(pkt6); if (!preload) { - if (!stats_mgr6_) { - isc_throw(InvalidOperation, "Statistics Manager for DHCPv6 " - "hasn't been initialized"); - } - stats_mgr6_->passSentPacket(StatsMgr6::XCHG_SA, pkt6); + stats_mgr_.passSentPacket(ExchangeType::SA, pkt6); } saveFirstPacket(pkt6); } void -TestControl::sendSolicit6(const PerfSocket& socket, - const std::vector<uint8_t>& template_buf, +TestControl::sendSolicit6(const std::vector<uint8_t>& template_buf, const bool preload /*= false*/) { const int arg_idx = 0; // Get transaction id offset. @@ -2156,7 +1600,7 @@ TestControl::sendSolicit6(const PerfSocket& socket, // Prepare on-wire data. pkt6->rawPack(); - setDefaults6(socket, pkt6); + setDefaults6(pkt6); // Add any extra options that user may have specified. addExtraOpts(pkt6); @@ -2164,29 +1608,24 @@ TestControl::sendSolicit6(const PerfSocket& socket, // Send solicit packet. IfaceMgr::instance().send(pkt6); if (!preload) { - if (!stats_mgr6_) { - isc_throw(InvalidOperation, "Statistics Manager for DHCPv6 " - "hasn't been initialized"); - } // Update packet stats. - stats_mgr6_->passSentPacket(StatsMgr6::XCHG_SA, pkt6); + stats_mgr_.passSentPacket(ExchangeType::SA, pkt6); } saveFirstPacket(pkt6); } void -TestControl::setDefaults4(const PerfSocket& socket, - const Pkt4Ptr& pkt) { +TestControl::setDefaults4(const Pkt4Ptr& pkt) { CommandOptions& options = CommandOptions::instance(); // Interface name. - IfacePtr iface = IfaceMgr::instance().getIface(socket.ifindex_); + IfacePtr iface = IfaceMgr::instance().getIface(socket_.ifindex_); if (iface == NULL) { isc_throw(BadValue, "unable to find interface with given index"); } pkt->setIface(iface->getName()); // Interface index. - pkt->setIndex(socket.ifindex_); + pkt->setIndex(socket_.ifindex_); // Local client's port (68) pkt->setLocalPort(DHCP4_CLIENT_PORT); // Server's port (67) @@ -2198,25 +1637,24 @@ TestControl::setDefaults4(const PerfSocket& socket, // The remote server's name or IP. pkt->setRemoteAddr(IOAddress(options.getServerName())); // Set local address. - pkt->setLocalAddr(IOAddress(socket.addr_)); + pkt->setLocalAddr(IOAddress(socket_.addr_)); // Set relay (GIADDR) address to local address. - pkt->setGiaddr(IOAddress(socket.addr_)); + pkt->setGiaddr(IOAddress(socket_.addr_)); // Pretend that we have one relay (which is us). pkt->setHops(1); } void -TestControl::setDefaults6(const PerfSocket& socket, - const Pkt6Ptr& pkt) { +TestControl::setDefaults6(const Pkt6Ptr& pkt) { CommandOptions& options = CommandOptions::instance(); // Interface name. - IfacePtr iface = IfaceMgr::instance().getIface(socket.ifindex_); + IfacePtr iface = IfaceMgr::instance().getIface(socket_.ifindex_); if (iface == NULL) { isc_throw(BadValue, "unable to find interface with given index"); } pkt->setIface(iface->getName()); // Interface index. - pkt->setIndex(socket.ifindex_); + pkt->setIndex(socket_.ifindex_); // Local client's port (547) pkt->setLocalPort(DHCP6_CLIENT_PORT); // Server's port (548) @@ -2226,7 +1664,7 @@ TestControl::setDefaults6(const PerfSocket& socket, pkt->setRemotePort(DHCP6_SERVER_PORT); } // Set local address. - pkt->setLocalAddr(socket.addr_); + pkt->setLocalAddr(socket_.addr_); // The remote server's name or IP. pkt->setRemoteAddr(IOAddress(options.getServerName())); @@ -2237,8 +1675,8 @@ TestControl::setDefaults6(const PerfSocket& socket, Pkt6::RelayInfo relay_info; relay_info.msg_type_ = DHCPV6_RELAY_FORW; relay_info.hop_count_ = 1; - relay_info.linkaddr_ = IOAddress(socket.addr_); - relay_info.peeraddr_ = IOAddress(socket.addr_); + relay_info.linkaddr_ = IOAddress(socket_.addr_); + relay_info.peeraddr_ = IOAddress(socket_.addr_); pkt->addRelayInfo(relay_info); } } diff --git a/src/bin/perfdhcp/test_control.h b/src/bin/perfdhcp/test_control.h index 0606a21de8..0be0b1b478 100644 --- a/src/bin/perfdhcp/test_control.h +++ b/src/bin/perfdhcp/test_control.h @@ -25,6 +25,7 @@ #include <string> #include <vector> +#include <unordered_map> namespace isc { namespace perfdhcp { @@ -122,6 +123,8 @@ public: /// DHCPv4 or DHCPv6 option. class TestControl : public boost::noncopyable { public: + /// \brief Default constructor. + TestControl(bool ignore_timestamp_reorder); /// Packet template buffer. typedef std::vector<uint8_t> TemplateBuffer; @@ -192,24 +195,6 @@ public: /// address is longer than this (e.g. 20 bytes). static const uint8_t HW_ETHER_LEN = 6; - /// TestControl is a singleton class. This method returns reference - /// to its sole instance. - /// - /// \return the only existing instance of test control - static TestControl& instance(); - - /// brief\ Run performance test. - /// - /// Method runs whole performance test. Command line options must - /// be parsed prior to running this function. Otherwise function will - /// throw exception. - /// - /// \throw isc::InvalidOperation if command line options are not parsed. - /// \throw isc::Unexpected if internal Test Controller error occurred. - /// \return error_code, 3 if number of received packets is not equal - /// to number of sent packets, 0 if everything is ok. - int run(); - /// \brief Set new transaction id generator. /// /// \param generator generator object to be used. @@ -233,28 +218,11 @@ public: // they have to be accessible for unit-testing. Another, possibly better, // solution is to make this class friend of test class but this is not // what's followed in other classes. -protected: - /// \brief Default constructor. - /// - /// Default constructor is protected as the object can be created - /// only via \ref instance method. - TestControl(); - +//protected: +public: // TODO clean up what should be and what should not be protected /// Generate uniformly distributed integers in range of [min, max] isc::util::random::UniformRandomIntegerGenerator number_generator_; - /// \brief Check if test exit conditions fulfilled. - /// - /// Method checks if the test exit conditions are fulfilled. - /// Exit conditions are checked periodically from the - /// main loop. Program should break the main loop when - /// this method returns true. It is calling function - /// responsibility to break main loop gracefully and - /// cleanup after test execution. - /// - /// \return true if any of the exit conditions is fulfilled. - bool checkExitConditions() const; - /// \brief Removes cached DHCPv6 Reply packets every second. /// /// This function wipes cached Reply packets from the storage. @@ -476,32 +444,6 @@ protected: /// odd number of hexadecimal digits. void initPacketTemplates(); - /// \brief Initializes Statistics Manager. - /// - /// This function initializes Statistics Manager. If there is - /// the one initialized already it is released. - void initializeStatsMgr(); - - /// \brief Open socket to communicate with DHCP server. - /// - /// Method opens socket and binds it to local address. Function will - /// use either interface name, local address or server address - /// to create a socket, depending on what is available (specified - /// from the command line). If socket can't be created for any - /// reason, exception is thrown. - /// If destination address is broadcast (for DHCPv4) or multicast - /// (for DHCPv6) than broadcast or multicast option is set on - /// the socket. Opened socket is registered and managed by IfaceMgr. - /// - /// \throw isc::BadValue if socket can't be created for given - /// interface, local address or remote address. - /// \throw isc::InvalidOperation if broadcast option can't be - /// set for the v4 socket or if multicast option can't be set - /// for the v6 socket. - /// \throw isc::Unexpected if internal unexpected error occurred. - /// \return socket descriptor. - int openSocket() const; - /// \brief Print intermediate statistics. /// /// Print brief statistics regarding number of sent packets, @@ -523,7 +465,7 @@ protected: /// \brief Pull packets from receiver and process them. /// It runs in a loop until there are no packets in receiver. - unsigned int consumeReceivedPackets(Receiver& receiver, const PerfSocket& socket); + unsigned int consumeReceivedPackets(); /// \brief Process received DHCPv4 packet. /// @@ -535,12 +477,10 @@ protected: /// \warning this method does not check if provided socket is /// valid (specifically if v4 socket for received v4 packet). /// - /// \param [in] socket socket to be used. /// \param [in] pkt4 object representing DHCPv4 packet received. /// \throw isc::BadValue if unknown message type received. /// \throw isc::Unexpected if unexpected error occurred. - void processReceivedPacket4(const PerfSocket& socket, - const dhcp::Pkt4Ptr& pkt4); + void processReceivedPacket4(const dhcp::Pkt4Ptr& pkt4); /// \brief Process received DHCPv6 packet. /// @@ -549,15 +489,10 @@ protected: /// e.g. when ADVERTISE packet arrives, this function will initiate /// REQUEST message to the server. /// - /// \warning this method does not check if provided socket is - /// valid (specifically if v4 socket for received v4 packet). - /// - /// \param [in] socket socket to be used. /// \param [in] pkt6 object representing DHCPv6 packet received. /// \throw isc::BadValue if unknown message type received. /// \throw isc::Unexpected if unexpected error occurred. - void processReceivedPacket6(const PerfSocket& socket, - const dhcp::Pkt6Ptr& pkt6); + void processReceivedPacket6(const dhcp::Pkt6Ptr& pkt6); /// \brief Register option factory functions for DHCPv4 /// @@ -630,17 +565,15 @@ protected: /// The transaction id and MAC address are randomly generated for /// the message. Range of unique MAC addresses generated depends /// on the number of clients specified from the command line. - /// Copy of sent packet is stored in the stats_mgr4_ object to + /// Copy of sent packet is stored in the stats_mgr_ object to /// update statistics. /// - /// \param socket socket to be used to send the message. /// \param preload preload mode, packets not included in statistics. /// /// \throw isc::Unexpected if failed to create new packet instance. /// \throw isc::BadValue if MAC address has invalid length. /// \throw isc::dhcp::SocketWriteError if failed to send the packet. - void sendDiscover4(const PerfSocket& socket, - const bool preload = false); + void sendDiscover4(const bool preload = false); /// \brief Send DHCPv4 DISCOVER message from template. /// @@ -648,17 +581,15 @@ protected: /// template data is expected to be in binary format. Provided /// buffer is copied and parts of it are replaced with actual /// data (e.g. MAC address, transaction id etc.). - /// Copy of sent packet is stored in the stats_mgr4_ object to + /// Copy of sent packet is stored in the stats_mgr_ object to /// update statistics. /// - /// \param socket socket to be used to send the message. /// \param template_buf buffer holding template packet. /// \param preload preload mode, packets not included in statistics. /// /// \throw isc::OutOfRange if randomization offset is out of bounds. /// \throw isc::dhcp::SocketWriteError if failed to send the packet. - void sendDiscover4(const PerfSocket& socket, - const std::vector<uint8_t>& template_buf, + void sendDiscover4(const std::vector<uint8_t>& template_buf, const bool preload = false); /// \brief Send number of packets to initiate new exchanges. @@ -676,46 +607,37 @@ protected: /// /// \todo do not count responses in preload mode as orphans. /// - /// \param socket socket to be used to send packets. /// \param packets_num number of packets to be sent. /// \param preload preload mode, packets not included in statistics. /// \throw isc::Unexpected if thrown by packet sending method. /// \throw isc::InvalidOperation if thrown by packet sending method. /// \throw isc::OutOfRange if thrown by packet sending method. - void sendPackets(const PerfSocket &socket, - const uint64_t packets_num, + void sendPackets(const uint64_t packets_num, const bool preload = false); /// \brief Send number of DHCPREQUEST (renew) messages to a server. /// - /// \param socket An object representing socket to be used to send packets. /// \param msg_num A number of messages to be sent. /// /// \return A number of messages actually sent. - uint64_t sendMultipleRequests(const PerfSocket& socket, - const uint64_t msg_num); + uint64_t sendMultipleRequests(const uint64_t msg_num); /// \brief Send number of DHCPv6 Renew or Release messages to the server. /// - /// \param socket An object representing socket to be used to send packets. /// \param msg_type A type of the messages to be sent (DHCPV6_RENEW or /// DHCPV6_RELEASE). /// \param msg_num A number of messages to be sent. /// /// \return A number of messages actually sent. - uint64_t sendMultipleMessages6(const PerfSocket& socket, - const uint32_t msg_type, + uint64_t sendMultipleMessages6(const uint32_t msg_type, const uint64_t msg_num); - /// \brief Send DHCPv4 renew (DHCPREQUEST) using specified socket. - /// - /// \param socket An object encapsulating socket to be used to send - /// a packet. + /// \brief Send DHCPv4 renew (DHCPREQUEST). /// /// \return true if the message has been sent, false otherwise. - bool sendRequestFromAck(const PerfSocket& socket); + bool sendRequestFromAck(); - /// \brief Send DHCPv6 Renew or Release message using specified socket. + /// \brief Send DHCPv6 Renew or Release message. /// /// This method will select an existing lease from the Reply packet cache /// If there is no lease that can be renewed or released this method will @@ -723,20 +645,16 @@ protected: /// /// \param msg_type A type of the message to be sent (DHCPV6_RENEW or /// DHCPV6_RELEASE). - /// \param socket An object encapsulating socket to be used to send - /// a packet. /// /// \return true if the message has been sent, false otherwise. - bool sendMessageFromReply(const uint16_t msg_type, - const PerfSocket& socket); + bool sendMessageFromReply(const uint16_t msg_type); /// \brief Send DHCPv4 REQUEST message. /// /// Method creates and sends DHCPv4 REQUEST message to the server. - /// Copy of sent packet is stored in the stats_mgr4_ object to + /// Copy of sent packet is stored in the stats_mgr_ object to /// update statistics. /// - /// \param socket socket to be used to send message. /// \param discover_pkt4 DISCOVER packet sent. /// \param offer_pkt4 OFFER packet object. /// @@ -744,24 +662,21 @@ protected: /// \throw isc::InvalidOperation if Statistics Manager has not been /// initialized. /// \throw isc::dhcp::SocketWriteError if failed to send the packet. - void sendRequest4(const PerfSocket& socket, - const dhcp::Pkt4Ptr& discover_pkt4, + void sendRequest4(const dhcp::Pkt4Ptr& discover_pkt4, const dhcp::Pkt4Ptr& offer_pkt4); /// \brief Send DHCPv4 REQUEST message from template. /// /// Method sends DHCPv4 REQUEST message from template. - /// Copy of sent packet is stored in the stats_mgr4_ object to + /// Copy of sent packet is stored in the stats_mgr_ object to /// update statistics. /// - /// \param socket socket to be used to send message. /// \param template_buf buffer holding template packet. /// \param discover_pkt4 DISCOVER packet sent. /// \param offer_pkt4 OFFER packet received. /// /// \throw isc::dhcp::SocketWriteError if failed to send the packet. - void sendRequest4(const PerfSocket& socket, - const std::vector<uint8_t>& template_buf, + void sendRequest4(const std::vector<uint8_t>& template_buf, const dhcp::Pkt4Ptr& discover_pkt4, const dhcp::Pkt4Ptr& offer_pkt4); @@ -772,32 +687,28 @@ protected: /// - D6O_ELAPSED_TIME /// - D6O_CLIENTID /// - D6O_SERVERID - /// Copy of sent packet is stored in the stats_mgr6_ object to + /// Copy of sent packet is stored in the stats_mgr_ object to /// update statistics. /// - /// \param socket socket to be used to send message. /// \param advertise_pkt6 ADVERTISE packet object. /// \throw isc::Unexpected if unexpected error occurred. /// \throw isc::InvalidOperation if Statistics Manager has not been /// initialized. /// /// \throw isc::dhcp::SocketWriteError if failed to send the packet. - void sendRequest6(const PerfSocket& socket, - const dhcp::Pkt6Ptr& advertise_pkt6); + void sendRequest6(const dhcp::Pkt6Ptr& advertise_pkt6); /// \brief Send DHCPv6 REQUEST message from template. /// /// Method sends DHCPv6 REQUEST message from template. - /// Copy of sent packet is stored in the stats_mgr6_ object to + /// Copy of sent packet is stored in the stats_mgr_ object to /// update statistics. /// - /// \param socket socket to be used to send message. /// \param template_buf packet template buffer. /// \param advertise_pkt6 ADVERTISE packet object. /// /// \throw isc::dhcp::SocketWriteError if failed to send the packet. - void sendRequest6(const PerfSocket& socket, - const std::vector<uint8_t>& template_buf, + void sendRequest6(const std::vector<uint8_t>& template_buf, const dhcp::Pkt6Ptr& advertise_pkt6); /// \brief Send DHCPv6 SOLICIT message. @@ -809,30 +720,26 @@ protected: /// - D6O_CLIENTID, /// - D6O_ORO (Option Request Option), /// - D6O_IA_NA. - /// Copy of sent packet is stored in the stats_mgr6_ object to + /// Copy of sent packet is stored in the stats_mgr_ object to /// update statistics. /// - /// \param socket socket to be used to send the message. /// \param preload mode, packets not included in statistics. /// /// \throw isc::Unexpected if failed to create new packet instance. /// \throw isc::dhcp::SocketWriteError if failed to send the packet. - void sendSolicit6(const PerfSocket& socket, - const bool preload = false); + void sendSolicit6(const bool preload = false); /// \brief Send DHCPv6 SOLICIT message from template. /// /// Method sends DHCPv6 SOLICIT message from template. - /// Copy of sent packet is stored in the stats_mgr6_ object to + /// Copy of sent packet is stored in the stats_mgr_ object to /// update statistics. /// - /// \param socket socket to be used to send the message. /// \param template_buf packet template buffer. /// \param preload mode, packets not included in statistics. /// /// \throw isc::dhcp::SocketWriteError if failed to send the packet. - void sendSolicit6(const PerfSocket& socket, - const std::vector<uint8_t>& template_buf, + void sendSolicit6(const std::vector<uint8_t>& template_buf, const bool preload = false); /// \brief Set default DHCPv4 packet parameters. @@ -845,10 +752,8 @@ protected: /// - GIADDR = local address where socket is bound to, /// - hops = 1 (pretending that we are a relay) /// - /// \param socket socket used to send the packet. /// \param pkt reference to packet to be configured. - void setDefaults4(const PerfSocket& socket, - const dhcp::Pkt4Ptr& pkt); + void setDefaults4(const dhcp::Pkt4Ptr& pkt); /// \brief Set default DHCPv6 packet parameters. /// @@ -860,10 +765,8 @@ protected: /// - local address, /// - remote address (server). /// - /// \param socket socket used to send the packet. /// \param pkt reference to packet to be configured. - void setDefaults6(const PerfSocket& socket, - const dhcp::Pkt6Ptr& pkt); + void setDefaults6(const dhcp::Pkt6Ptr& pkt); /// @brief Inserts extra options specified by user. /// @@ -885,6 +788,27 @@ protected: /// @param pkt6 options will be added here void addExtraOpts(const dhcp::Pkt6Ptr& pkt6); + StatsMgr& getStatsMgr() { return stats_mgr_; }; + + void start() { receiver_.start(); } + void stop() { receiver_.stop(); } + + /// \brief Print templates information. + /// + /// Method prints information about data offsets + /// in packet templates and their contents. + void printTemplates() const; + + /// \brief Run wrapped command. + /// + /// \param do_stop execute wrapped command with "stop" argument. + void runWrapped(bool do_stop = false) const; + + bool serverIdReceived() const { return first_packet_serverid_.size() > 0; } + std::string getServerId() const { return vector2Hex(first_packet_serverid_); } + + bool interrupted() const { return interrupted_; } + protected: /// \brief Copies IA_NA or IA_PD option from one packet to another. @@ -956,35 +880,14 @@ protected: /// \return transaction id offset in packet. int getTransactionIdOffset(const int arg_idx) const; - /// \brief Get number of received packets. - /// - /// Get the number of received packets from the Statistics Manager. - /// Function may throw if Statistics Manager object is not - /// initialized. - /// - /// \note The method parameter is non-const to suppress the cppcheck - /// warning about the object being passed by value. However, passing - /// an enum by reference doesn't make much sense. At the same time, - /// removing the constness should be pretty safe for this function. - /// - /// \param xchg_type packet exchange type. - /// \return number of received packets. - uint64_t getRcvdPacketsNum(ExchangeType xchg_type) const; - - /// \brief Get number of sent packets. - /// - /// Get the number of sent packets from the Statistics Manager. - /// Function may throw if Statistics Manager object is not - /// initialized. + /// \brief Convert vector in hexadecimal string. /// - /// \note The method parameter is non-const to suppress the cppcheck - /// warning about the object being passed by value. However, passing - /// an enum by reference doesn't make much sense. At the same time, - /// removing the constness should be pretty safe for this function. + /// \todo Consider moving this function to src/lib/util. /// - /// \param xchg_type packet exchange type. - /// \return number of sent packets. - uint64_t getSentPacketsNum(ExchangeType xchg_type) const; + /// \param vec vector to be converted. + /// \param separator separator. + std::string vector2Hex(const std::vector<uint8_t>& vec, + const std::string& separator = "") const; /// \brief Handle child signal. /// @@ -1012,12 +915,6 @@ protected: /// \param packet_type packet type. void printTemplate(const uint8_t packet_type) const; - /// \brief Print templates information. - /// - /// Method prints information about data offsets - /// in packet templates and their contents. - void printTemplates() const; - /// \brief Read DHCP message template from file. /// /// Method reads DHCP message template from file and @@ -1031,31 +928,12 @@ protected: /// spaces or hexadecimal digits. void readPacketTemplate(const std::string& file_name); - /// \brief Run wrapped command. - /// - /// \param do_stop execute wrapped command with "stop" argument. - void runWrapped(bool do_stop = false) const; - - /// \brief Convert vector in hexadecimal string. - /// - /// \todo Consider moving this function to src/lib/util. - /// - /// \param vec vector to be converted. - /// \param separator separator. - std::string vector2Hex(const std::vector<uint8_t>& vec, - const std::string& separator = "") const; - - /// \brief A rate control class for Discover and Solicit messages. - RateControl basic_rate_control_; - /// \brief A rate control class for Renew messages. - RateControl renew_rate_control_; - /// \brief A rate control class for Release messages. - RateControl release_rate_control_; + PerfSocket socket_; + Receiver receiver_; boost::posix_time::ptime last_report_; ///< Last intermediate report time. - StatsMgr4Ptr stats_mgr4_; ///< Statistics Manager 4. - StatsMgr6Ptr stats_mgr6_; ///< Statistics Manager 6. + StatsMgr stats_mgr_; ///< Statistics Manager. PacketStorage<dhcp::Pkt4> ack_storage_; ///< A storage for DHCPACK messages. PacketStorage<dhcp::Pkt6> reply_storage_; ///< A storage for reply messages. diff --git a/src/bin/perfdhcp/tests/stats_mgr_unittest.cc b/src/bin/perfdhcp/tests/stats_mgr_unittest.cc index 37f0361a03..41de29527f 100644 --- a/src/bin/perfdhcp/tests/stats_mgr_unittest.cc +++ b/src/bin/perfdhcp/tests/stats_mgr_unittest.cc @@ -368,24 +368,6 @@ TEST_F(StatsMgrTest, MultipleExchanges) { stats_mgr->getRcvdPacketsNum(StatsMgr6::XCHG_RR)); } -TEST_F(StatsMgrTest, ExchangeToString) { - // Test DHCPv4 specific exchange names. - EXPECT_EQ("DISCOVER-OFFER", - StatsMgr4::exchangeToString(StatsMgr4::XCHG_DO)); - EXPECT_EQ("REQUEST-ACK", StatsMgr4::exchangeToString(StatsMgr4::XCHG_RA)); - EXPECT_EQ("REQUEST-ACK (renewal)", - StatsMgr4::exchangeToString(StatsMgr4::XCHG_RNA)); - - - // Test DHCPv6 specific exchange names. - EXPECT_EQ("SOLICIT-ADVERTISE", - StatsMgr6::exchangeToString(StatsMgr6::XCHG_SA)); - EXPECT_EQ("REQUEST-REPLY", StatsMgr6::exchangeToString(StatsMgr6::XCHG_RR)); - EXPECT_EQ("RENEW-REPLY", StatsMgr6::exchangeToString(StatsMgr6::XCHG_RN)); - EXPECT_EQ("RELEASE-REPLY", StatsMgr6::exchangeToString(StatsMgr6::XCHG_RL)); - -} - TEST_F(StatsMgrTest, SendReceiveSimple) { boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4()); boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER, |