]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
added avalanche scenario to perfdhcp
authorMichal Nowikowski <godfryd@isc.org>
Tue, 12 Feb 2019 14:55:51 +0000 (15:55 +0100)
committerMichal Nowikowski <godfryd@isc.org>
Tue, 19 Feb 2019 20:54:31 +0000 (21:54 +0100)
16 files changed:
src/bin/perfdhcp/Makefile.am
src/bin/perfdhcp/avalanche_scen.cc [new file with mode: 0644]
src/bin/perfdhcp/avalanche_scen.h [new file with mode: 0644]
src/bin/perfdhcp/basic_scen.cc [new file with mode: 0644]
src/bin/perfdhcp/basic_scen.h [new file with mode: 0644]
src/bin/perfdhcp/command_options.cc
src/bin/perfdhcp/command_options.h
src/bin/perfdhcp/main.cc
src/bin/perfdhcp/perf_socket.cc
src/bin/perfdhcp/perf_socket.h
src/bin/perfdhcp/receiver.h
src/bin/perfdhcp/stats_mgr.cc [new file with mode: 0644]
src/bin/perfdhcp/stats_mgr.h
src/bin/perfdhcp/test_control.cc
src/bin/perfdhcp/test_control.h
src/bin/perfdhcp/tests/stats_mgr_unittest.cc

index c1e45319123cb24cca9d41a6cbaab7af2caffb80..436f7c39104aac9543fb556f37c8b167d8ac0c01 100644 (file)
@@ -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 (file)
index 0000000..91f69da
--- /dev/null
@@ -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 (file)
index 0000000..66cf040
--- /dev/null
@@ -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 (file)
index 0000000..cda35f6
--- /dev/null
@@ -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 (file)
index 0000000..7a5d115
--- /dev/null
@@ -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
index eb562d97d25f7c7008eb7b58ab96abe02eddcb9c..60397f6b5882926329c74afaca02fd109e14e767 100644 (file)
@@ -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
index deb2808267b6f77d66d7cb76df552aad70b6be20..efbfdafbfb145cc6f2dc16c33c1d8afddbc9041c 100644 (file)
 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
 
index 7548ee76cd5d5b440ac1ade0e6b1c506f39f5254..2ab854208a863a9ae2bac1120be870ad11a9a0e5 100644 (file)
@@ -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;
index c5b01cfa2e4111b6ce52e7d1c43231ff85607c63..e9a55dfa2b8e2784cfbb8efdf121f39f608d7b7c 100644 (file)
 
 
 #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() {
index de593d317f48bacdb1924bd3bcbf091d0a50f557..a49cc92e32650780eee1331045ff4439e88a6c5b 100644 (file)
@@ -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;
 };
 
 }
index d05ee2493a8521684946a60521d20f0ede03f00a..4a0afc5a54e5a4aebe143e9b41de809a9ecdcda3 100644 (file)
@@ -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 (file)
index 0000000..bc8fe64
--- /dev/null
@@ -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");
+    }
+}
+
+
+}
+}
index 00a58904f9d1128002ae60b6c27bbd9b4bbdcc85..7bb1bf3a21e70b50523d1326ec8f7ca0465bff56 100644 (file)
@@ -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>
 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
index 55aaacbc2d2de94068614c3732ae15882277ede3..a37394c78ca7c38e4b552ad12061c38f4a80f3a4 100644 (file)
@@ -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);
     }
 }
index 0606a21de85003fff8fa7ff9b56536642e4a0284..0be0b1b47886f3908b39f3cbf4137d4b125cf565 100644 (file)
@@ -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.
index 37f0361a03a108e8441802f154ce84197be17c9a..41de29527fd5222292e14707e6bf29fa9a912587 100644 (file)
@@ -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,