Browse Source

[master] Merge branch 'trac3181'

Marcin Siodelski 11 years ago
parent
commit
684524bc13

+ 1 - 0
tests/tools/perfdhcp/Makefile.am

@@ -25,6 +25,7 @@ perfdhcp_SOURCES += perf_pkt6.cc perf_pkt6.h
 perfdhcp_SOURCES += perf_pkt4.cc perf_pkt4.h
 perfdhcp_SOURCES += perf_pkt4.cc perf_pkt4.h
 perfdhcp_SOURCES += packet_storage.h
 perfdhcp_SOURCES += packet_storage.h
 perfdhcp_SOURCES += pkt_transform.cc pkt_transform.h
 perfdhcp_SOURCES += pkt_transform.cc pkt_transform.h
+perfdhcp_SOURCES += rate_control.cc rate_control.h
 perfdhcp_SOURCES += stats_mgr.h
 perfdhcp_SOURCES += stats_mgr.h
 perfdhcp_SOURCES += test_control.cc test_control.h
 perfdhcp_SOURCES += test_control.cc test_control.h
 libb10_perfdhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
 libb10_perfdhcp___la_CXXFLAGS = $(AM_CXXFLAGS)

+ 40 - 15
tests/tools/perfdhcp/command_options.cc

@@ -114,6 +114,7 @@ CommandOptions::reset() {
     lease_type_.set(LeaseType::ADDRESS);
     lease_type_.set(LeaseType::ADDRESS);
     rate_ = 0;
     rate_ = 0;
     renew_rate_ = 0;
     renew_rate_ = 0;
+    release_rate_ = 0;
     report_delay_ = 0;
     report_delay_ = 0;
     clients_num_ = 0;
     clients_num_ = 0;
     mac_template_.assign(mac, mac + 6);
     mac_template_.assign(mac, mac + 6);
@@ -211,7 +212,7 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
     // In this section we collect argument values from command line
     // In this section we collect argument values from command line
     // they will be tuned and validated elsewhere
     // they will be tuned and validated elsewhere
     while((opt = getopt(argc, argv, "hv46r:t:R:b:n:p:d:D:l:P:a:L:"
     while((opt = getopt(argc, argv, "hv46r:t:R:b:n:p:d:D:l:P:a:L:"
-                        "s:iBc1T:X:O:E:S:I:x:w:e:f:")) != -1) {
+                        "s:iBc1T:X:O:E:S:I:x:w:e:f:F:")) != -1) {
         stream << " -" << static_cast<char>(opt);
         stream << " -" << static_cast<char>(opt);
         if (optarg) {
         if (optarg) {
             stream << " " << optarg;
             stream << " " << optarg;
@@ -307,6 +308,12 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
                                           " must be a positive integer");
                                           " must be a positive integer");
             break;
             break;
 
 
+        case 'F':
+            release_rate_ = positiveInteger("value of the release rate:"
+                                            " -F<release-rate> must be a"
+                                            " positive integer");
+            break;
+
         case 'h':
         case 'h':
             usage();
             usage();
             return (true);
             return (true);
@@ -690,6 +697,8 @@ CommandOptions::validate() const {
           "-6 (IPv6) must be set to use -c");
           "-6 (IPv6) must be set to use -c");
     check((getIpVersion() != 6) && (getRenewRate() !=0),
     check((getIpVersion() != 6) && (getRenewRate() !=0),
           "-f<renew-rate> may be used with -6 (IPv6) only");
           "-f<renew-rate> may be used with -6 (IPv6) only");
+    check((getIpVersion() != 6) && (getReleaseRate() != 0),
+          "-F<release-rate> may be used with -6 (IPv6) only");
     check((getExchangeMode() == DO_SA) && (getNumRequests().size() > 1),
     check((getExchangeMode() == DO_SA) && (getNumRequests().size() > 1),
           "second -n<num-request> is not compatible with -i");
           "second -n<num-request> is not compatible with -i");
     check((getIpVersion() == 4) && !getLeaseType().is(LeaseType::ADDRESS),
     check((getIpVersion() == 4) && !getLeaseType().is(LeaseType::ADDRESS),
@@ -719,6 +728,8 @@ CommandOptions::validate() const {
           "-I<ip-offset> is not compatible with -i");
           "-I<ip-offset> is not compatible with -i");
     check((getExchangeMode() == DO_SA) && (getRenewRate() != 0),
     check((getExchangeMode() == DO_SA) && (getRenewRate() != 0),
           "-f<renew-rate> is not compatible with -i");
           "-f<renew-rate> is not compatible with -i");
+    check((getExchangeMode() == DO_SA) && (getReleaseRate() != 0),
+          "-F<release-rate> is not compatible with -i");
     check((getExchangeMode() != DO_SA) && (isRapidCommit() != 0),
     check((getExchangeMode() != DO_SA) && (isRapidCommit() != 0),
           "-i must be set to use -c");
           "-i must be set to use -c");
     check((getRate() == 0) && (getReportDelay() != 0),
     check((getRate() == 0) && (getReportDelay() != 0),
@@ -730,12 +741,16 @@ CommandOptions::validate() const {
     check((getRate() == 0) &&
     check((getRate() == 0) &&
           ((getMaxDrop().size() > 0) || getMaxDropPercentage().size() > 0),
           ((getMaxDrop().size() > 0) || getMaxDropPercentage().size() > 0),
           "-r<rate> must be set to use -D<max-drop>");
           "-r<rate> must be set to use -D<max-drop>");
-    check((getRate() != 0) && (getRenewRate() > getRate()),
-          "Renew rate specified as -f<renew-rate> must not be greater than"
-          " the rate specified as -r<rate>");
+    check((getRate() != 0) && (getRenewRate() + getReleaseRate() > getRate()),
+          "The sum of Renew rate (-f<renew-rate>) and Release rate"
+          " (-F<release-rate>) must not be greater than the exchange"
+          " rate specified as -r<rate>");
     check((getRate() == 0) && (getRenewRate() != 0),
     check((getRate() == 0) && (getRenewRate() != 0),
           "Renew rate specified as -f<renew-rate> must not be specified"
           "Renew rate specified as -f<renew-rate> must not be specified"
           " when -r<rate> parameter is not specified");
           " when -r<rate> parameter is not specified");
+    check((getRate() == 0) && (getReleaseRate() != 0),
+          "Release rate specified as -F<release-rate> must not be specified"
+          " when -r<rate> parameter is not specified");
     check((getTemplateFiles().size() < getTransactionIdOffset().size()),
     check((getTemplateFiles().size() < getTransactionIdOffset().size()),
           "-T<template-file> must be set to use -X<xid-offset>");
           "-T<template-file> must be set to use -X<xid-offset>");
     check((getTemplateFiles().size() < getRandomOffset().size()),
     check((getTemplateFiles().size() < getRandomOffset().size()),
@@ -816,6 +831,9 @@ CommandOptions::printCommandLine() const {
     if (getRenewRate() != 0) {
     if (getRenewRate() != 0) {
         std::cout << "renew-rate[1/s]=" << getRenewRate() << std::endl;
         std::cout << "renew-rate[1/s]=" << getRenewRate() << std::endl;
     }
     }
+    if (getReleaseRate() != 0) {
+        std::cout << "release-rate[1/s]=" << getReleaseRate() << std::endl;
+    }
     if (report_delay_ != 0) {
     if (report_delay_ != 0) {
         std::cout << "report[s]=" << report_delay_ << std::endl;
         std::cout << "report[s]=" << report_delay_ << std::endl;
     }
     }
@@ -899,13 +917,14 @@ void
 CommandOptions::usage() const {
 CommandOptions::usage() const {
     std::cout <<
     std::cout <<
         "perfdhcp [-hv] [-4|-6] [-e<lease-type>] [-r<rate>] [-f<renew-rate>]\n"
         "perfdhcp [-hv] [-4|-6] [-e<lease-type>] [-r<rate>] [-f<renew-rate>]\n"
-        "         [-t<report>] [-R<range>] [-b<base>] [-n<num-request>]\n"
-        "         [-p<test-period>] [-d<drop-time>] [-D<max-drop>]\n"
-        "         [-l<local-addr|interface>] [-P<preload>] [-a<aggressivity>]\n"
-        "         [-L<local-port>] [-s<seed>] [-i] [-B] [-c] [-1]\n"
-        "         [-T<template-file>] [-X<xid-offset>] [-O<random-offset]\n"
-        "         [-E<time-offset>] [-S<srvid-offset>] [-I<ip-offset>]\n"
-        "         [-x<diagnostic-selector>] [-w<wrapped>] [server]\n"
+        "         [-F<release-rate>] [-t<report>] [-R<range>] [-b<base>]\n"
+        "         [-n<num-request>] [-p<test-period>] [-d<drop-time>]\n"
+        "         [-D<max-drop>] [-l<local-addr|interface>] [-P<preload>]\n"
+        "         [-a<aggressivity>] [-L<local-port>] [-s<seed>] [-i] [-B]\n"
+        "         [-c] [-1] [-T<template-file>] [-X<xid-offset>]\n"
+        "         [-O<random-offset] [-E<time-offset>] [-S<srvid-offset>]\n"
+        "         [-I<ip-offset>] [-x<diagnostic-selector>] [-w<wrapped>]\n"
+        "         [server]\n"
         "\n"
         "\n"
         "The [server] argument is the name/address of the DHCP server to\n"
         "The [server] argument is the name/address of the DHCP server to\n"
         "contact.  For DHCPv4 operation, exchanges are initiated by\n"
         "contact.  For DHCPv4 operation, exchanges are initiated by\n"
@@ -948,10 +967,6 @@ CommandOptions::usage() const {
         "-E<time-offset>: Offset of the (DHCPv4) secs field / (DHCPv6)\n"
         "-E<time-offset>: Offset of the (DHCPv4) secs field / (DHCPv6)\n"
         "    elapsed-time option in the (second/request) template.\n"
         "    elapsed-time option in the (second/request) template.\n"
         "    The value 0 disables it.\n"
         "    The value 0 disables it.\n"
-        "-f<renew-rate>: A rate at which IPv6 Renew requests are sent to\n"
-        "    a server. This value must not be equal or lower than the rate\n"
-        "    specified as -r<rate>. If -r<rate> is not specified, this\n"
-        "    parameter must not be specified too.\n"
         "-h: Print this help.\n"
         "-h: Print this help.\n"
         "-i: Do only the initial part of an exchange: DO or SA, depending on\n"
         "-i: Do only the initial part of an exchange: DO or SA, depending on\n"
         "    whether -6 is given.\n"
         "    whether -6 is given.\n"
@@ -999,6 +1014,16 @@ CommandOptions::usage() const {
         "\n"
         "\n"
         "DHCPv6 only options:\n"
         "DHCPv6 only options:\n"
         "-c: Add a rapid commit option (exchanges will be SA).\n"
         "-c: Add a rapid commit option (exchanges will be SA).\n"
+        "-f<renew-rate>: Rate at which IPv6 Renew requests are sent to\n"
+        "    a server. This value is only valid when used in conjunction with\n"
+        "    the exchange rate (given by -r<rate>).  Furthermore the sum of\n"
+        "    this value and the release-rate (given by -F<rate) must be equal\n"
+        "    to or less than the exchange rate.\n"
+        "-F<release-rate>: Rate at which IPv6 Release requests are sent to\n"
+        "    a server. This value is only valid when used in conjunction with\n"
+        "    the exchange rate (given by -r<rate>).  Furthermore the sum of\n"
+        "    this value and the renew-rate (given by -f<rate) must be equal\n"
+        "    to or less than the exchange rate.\n"
         "\n"
         "\n"
         "The remaining options are used only in conjunction with -r:\n"
         "The remaining options are used only in conjunction with -r:\n"
         "\n"
         "\n"

+ 8 - 2
tests/tools/perfdhcp/command_options.h

@@ -1,4 +1,3 @@
-
 // Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
 // Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
@@ -156,11 +155,16 @@ public:
     /// \return exchange rate per second.
     /// \return exchange rate per second.
     int getRate() const { return rate_; }
     int getRate() const { return rate_; }
 
 
-    /// \brief Returns a rate at which IPv6 Renew messages are sent.
+    /// \brief Returns a rate at which DHCPv6 Renew messages are sent.
     ///
     ///
     /// \return A rate at which IPv6 Renew messages are sent.
     /// \return A rate at which IPv6 Renew messages are sent.
     int getRenewRate() const { return (renew_rate_); }
     int getRenewRate() const { return (renew_rate_); }
 
 
+    /// \brief Returns a rate at which DHCPv6 Release messages are sent.
+    ///
+    /// \return A rate at which DHCPv6 Release messages are sent.
+    int getReleaseRate() const { return (release_rate_); }
+
     /// \brief Returns delay between two performance reports.
     /// \brief Returns delay between two performance reports.
     ///
     ///
     /// \return delay between two consecutive performance reports.
     /// \return delay between two consecutive performance reports.
@@ -469,6 +473,8 @@ private:
     int rate_;
     int rate_;
     /// A rate at which DHCPv6 Renew messages are sent.
     /// A rate at which DHCPv6 Renew messages are sent.
     int renew_rate_;
     int renew_rate_;
+    /// A rate at which DHCPv6 Release messages are sent.
+    int release_rate_;
     /// Delay between generation of two consecutive
     /// Delay between generation of two consecutive
     /// performance reports
     /// performance reports
     int report_delay_;
     int report_delay_;

+ 158 - 0
tests/tools/perfdhcp/rate_control.cc

@@ -0,0 +1,158 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+#include <tests/tools/perfdhcp/rate_control.h>
+
+namespace isc {
+namespace perfdhcp {
+
+using namespace boost::posix_time;
+
+RateControl::RateControl()
+    : send_due_(currentTime()), last_sent_(currentTime()),
+      aggressivity_(1), rate_(0), late_sent_(false) {
+}
+
+RateControl::RateControl(const int rate, const int aggressivity)
+    : send_due_(currentTime()), last_sent_(currentTime()),
+      aggressivity_(aggressivity), rate_(rate), late_sent_(false) {
+    if (aggressivity_ < 1) {
+        isc_throw(isc::BadValue, "invalid value of aggressivity "
+                  << aggressivity << ", expected value is greater than 0");
+    }
+    if (rate_ < 0) {
+        isc_throw(isc::BadValue, "invalid value of rate " << rate
+                  << ", expected non-negative value");
+    }
+}
+
+uint64_t
+RateControl::getOutboundMessageCount() {
+
+    // We need calculate the due time for sending next set of messages.
+    updateSendDue();
+
+    // Get current time. If we are behind due time, we have to calculate
+    // how many messages to send to catch up with the rate.
+    ptime now = currentTime();
+    if (now >= send_due_) {
+        // Reset number of exchanges.
+        uint64_t due_exchanges = 0;
+        // If rate is specified from the command line we have to
+        // synchornize with it.
+        if (getRate() != 0) {
+            time_period period(send_due_, now);
+            time_duration duration = period.length();
+            // due_factor indicates the number of seconds that
+            // sending next chunk of packets will take.
+            double due_factor = duration.fractional_seconds() /
+                time_duration::ticks_per_second();
+            due_factor += duration.total_seconds();
+            // Multiplying due_factor by expected rate gives the number
+            // of exchanges to be initiated.
+            due_exchanges = static_cast<uint64_t>(due_factor * getRate());
+            // We want to make sure that at least one packet goes out.
+            if (due_exchanges == 0) {
+                due_exchanges = 1;
+            }
+            // We should not exceed aggressivity as it could have been
+            // restricted from command line.
+            if (due_exchanges > getAggressivity()) {
+                due_exchanges = getAggressivity();
+            }
+        } else {
+            // Rate is not specified so we rely on aggressivity
+            // which is the number of packets to be sent in
+            // one chunk.
+            due_exchanges = getAggressivity();
+        }
+        return (due_exchanges);
+    }
+    return (0);
+}
+
+boost::posix_time::ptime
+RateControl::currentTime() {
+    return (microsec_clock::universal_time());
+}
+
+void
+RateControl::updateSendDue() {
+    // There is no sense to update due time if the current due time is in the
+    // future. The due time is calculated as a duration between the moment
+    // when the last message of the given type was sent and the time when
+    // next one is supposed to be sent based on a given rate. The former value
+    // will not change until we send the next message, which we don't do
+    // until we reach the due time.
+    if (send_due_ > currentTime()) {
+        return;
+    }
+    // This is initialized in the class constructor, so if it is not initialized
+    // it is a programmatic error.
+    if (last_sent_.is_not_a_date_time()) {
+        isc_throw(isc::Unexpected, "timestamp of the last sent packet not"
+                  " initialized");
+    }
+    // If rate was not specified we will wait just one clock tick to
+    // send next packet. This simulates best effort conditions.
+    long duration = 1;
+    if (getRate() != 0) {
+        // We use number of ticks instead of nanoseconds because
+        // nanosecond resolution may not be available on some
+        // machines. Number of ticks guarantees the highest possible
+        // timer resolution.
+        duration = time_duration::ticks_per_second() / getRate();
+    }
+    // Calculate due time to initiate next chunk of exchanges.
+    send_due_ = last_sent_ + time_duration(0, 0, 0, duration);
+    if (send_due_ > currentTime()) {
+        late_sent_ = true;
+    } else {
+        late_sent_ = false;
+    }
+}
+
+void
+RateControl::setAggressivity(const int aggressivity) {
+    if (aggressivity < 1) {
+        isc_throw(isc::BadValue, "invalid value of aggressivity "
+                  << aggressivity << ", expected value is greater than 0");
+    }
+    aggressivity_ = aggressivity;
+}
+
+void
+RateControl::setRate(const int rate) {
+    if (rate < 0) {
+        isc_throw(isc::BadValue, "invalid value of rate " << rate
+                  << ", expected non-negative value");
+    }
+    rate_ = rate;
+}
+
+void
+RateControl::setRelativeDue(const int offset) {
+    send_due_ = offset > 0 ?
+        currentTime() + seconds(abs(offset)) :
+        currentTime() - seconds(abs(offset));
+}
+
+void
+RateControl::updateSendTime() {
+    last_sent_ = currentTime();
+}
+
+} // namespace perfdhcp
+} // namespace isc

+ 180 - 0
tests/tools/perfdhcp/rate_control.h

@@ -0,0 +1,180 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef RATE_CONTROL_H
+#define RATE_CONTROL_H
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief A message sending rate control class for perfdhcp.
+///
+/// This class provides the means to control the rate at which messages
+/// of the specific type are sent by perfdhcp. Each message type,
+/// for which the desired rate can be specified, has a corresponding
+/// \c RateControl object. So, the perfdhcp is using up to three objects
+/// of this type at the same time, to control the rate of the following
+/// messages being sent:
+/// - Discover(DHCPv4) or Solicit (DHCPv6)
+/// - Renew (DHCPv6) or Request (DHCPv4) to renew leases.
+/// - Release
+///
+/// The purpose of the RateControl class is to track the due time for
+/// sending next message (or bunch of messages) to keep outbound rate
+/// of particular messages at the desired level. The due time is calculated
+/// using the desired rate value and the timestamp when the last message of
+/// the particular type has been sent. That puts the responsibility on the
+/// \c TestControl class to invoke the \c RateControl::updateSendDue, every
+/// time the message is sent.
+///
+/// The \c RateControl object returns the number of messages to be sent at
+/// the time. The number returned is 0, if perfdhcp shouldn't send any messages
+/// yet, or 1 (sometimes more) if the send due time has been reached.
+class RateControl {
+public:
+
+    /// \brief Default constructor.
+    RateControl();
+
+    /// \brief Constructor which sets desired rate and aggressivity.
+    ///
+    /// \param rate A desired rate.
+    /// \param aggressivity A desired aggressivity.
+    RateControl(const int rate, const int aggressivity);
+
+    /// \brief Returns the value of aggressivity.
+    int getAggressivity() const {
+        return (aggressivity_);
+    }
+
+    /// \brief Returns current due time to send next message.
+    boost::posix_time::ptime getDue() const {
+        return (send_due_);
+    }
+
+    /// \brief Returns number of messages to be sent "now".
+    ///
+    /// This function calculates how many messages of the given type should
+    /// be sent immediately when the call to the function returns, to catch
+    /// up with the desired message rate.
+    ///
+    /// The value returned depends on the due time calculated with the
+    /// \c RateControl::updateSendDue function and the current time. If
+    /// the due time has been hit, the non-zero number of messages is returned.
+    /// If the due time hasn't been hit, the number returned is 0.
+    ///
+    /// If the rate is non-zero, the number of messages to be sent is calculated
+    /// as follows:
+    /// \code
+    ///          num = duration * rate
+    /// \endcode
+    /// where <b>duration</b> is a time period between the due time to send
+    /// next set of messages and current time. The duration is expressed in
+    /// seconds with the fractional part having 6 or 9 digits (depending on
+    /// the timer resolution). If the calculated value is equal to 0, it is
+    /// rounded to 1, so as at least one message is sent.
+    ///
+    /// The value of aggressivity limits the maximal number of messages to
+    /// be sent one after another. If the number of messages calculated with
+    /// the equation above exceeds the aggressivity, this function will return
+    /// the value equal to aggressivity.
+    ///
+    /// If the rate is not specified (equal to 0), the value calculated by
+    /// this function is equal to aggressivity.
+    ///
+    /// \return A number of messages to be sent immediately.
+    uint64_t getOutboundMessageCount();
+
+    /// \brief Returns the rate.
+    int getRate() const {
+        return (rate_);
+    }
+
+    /// \brief Returns the value of the late send flag.
+    ///
+    /// The flag returned by this function indicates whether the new due time
+    /// calculated by the \c RateControl::updateSendDue is in the past.
+    /// This value is used by the \c TestControl object to increment the counter
+    /// of the late sent messages in the \c StatsMgr.
+    bool isLateSent() const {
+        return (late_sent_);
+    }
+
+    /// \brief Sets the value of aggressivity.
+    ///
+    /// \param aggressivity A new value of aggressivity. This value must be
+    /// a positive integer.
+    /// \throw isc::BadValue if new value is not a positive integer.
+    void setAggressivity(const int aggressivity);
+
+    /// \brief Sets the new rate.
+    ///
+    /// \param rate A new value of rate. This value must not be negative.
+    /// \throw isc::BadValue if new rate is negative.
+    void setRate(const int rate);
+
+    /// \brief Sets the value of the due time.
+    ///
+    /// This function is intended for unit testing. It manipulates the value of
+    /// the due time. The parameter passed to this function specifies the
+    /// (positive or negative) number of seconds relative to current time.
+    ///
+    /// \param offset A number of seconds relative to current time which
+    /// constitutes the new due time.
+    void setRelativeDue(const int offset);
+
+    /// \brief Sets the timestamp of the last sent message to current time.
+    void updateSendTime();
+
+protected:
+
+    /// \brief Convenience function returning current time.
+    ///
+    /// \return current time.
+    static boost::posix_time::ptime currentTime();
+
+    /// \brief Calculates the send due.
+    ///
+    /// This function calculates the send due timestamp using the current time
+    /// and desired rate. The due timestamp is calculated as a sum of the
+    /// timestamp when the last message was sent and the reciprocal of the rate
+    /// in micro or nanoseconds (depending on the timer resolution). If the rate
+    /// is not specified, the duration between two consecutive sends is one
+    /// timer tick.
+    void updateSendDue();
+
+    /// \brief Holds a timestamp when the next message should be sent.
+    boost::posix_time::ptime send_due_;
+
+    /// \brief Holds a timestamp when the last message was sent.
+    boost::posix_time::ptime last_sent_;
+
+    /// \brief Holds an aggressivity value.
+    int aggressivity_;
+
+    /// \brief Holds a desired rate value.
+    int rate_;
+
+    /// \brief A flag which indicates that the calculated due time is in the
+    /// past.
+    bool late_sent_;
+
+};
+
+}
+}
+
+#endif

+ 29 - 5
tests/tools/perfdhcp/stats_mgr.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -124,7 +124,8 @@ public:
         XCHG_RA,  ///< DHCPv4 REQUEST-ACK
         XCHG_RA,  ///< DHCPv4 REQUEST-ACK
         XCHG_SA,  ///< DHCPv6 SOLICIT-ADVERTISE
         XCHG_SA,  ///< DHCPv6 SOLICIT-ADVERTISE
         XCHG_RR,  ///< DHCPv6 REQUEST-REPLY
         XCHG_RR,  ///< DHCPv6 REQUEST-REPLY
-        XCHG_RN   ///< DHCPv6 RENEW-REPLY
+        XCHG_RN,  ///< DHCPv6 RENEW-REPLY
+        XCHG_RL   ///< DHCPv6 RELEASE-REPLY
     };
     };
 
 
     /// \brief Exchange Statistics.
     /// \brief Exchange Statistics.
@@ -632,12 +633,19 @@ public:
         /// Method prints main statistics for particular exchange.
         /// Method prints main statistics for particular exchange.
         /// Statistics includes: number of sent and received packets,
         /// Statistics includes: number of sent and received packets,
         /// number of dropped packets and number of orphans.
         /// 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 reponse to any of the messages sent by perfdhcp.
         void printMainStats() const {
         void printMainStats() const {
             using namespace std;
             using namespace std;
             cout << "sent packets: " << getSentPacketsNum() << endl
             cout << "sent packets: " << getSentPacketsNum() << endl
                  << "received packets: " << getRcvdPacketsNum() << endl
                  << "received packets: " << getRcvdPacketsNum() << endl
-                 << "drops: " << getDroppedPacketsNum() << endl
-                 << "orphans: " << getOrphans() << endl;
+                 << "drops: " << getDroppedPacketsNum() << endl;
+            //                 << "orphans: " << getOrphans() << endl;
         }
         }
 
 
         /// \brief Print round trip time packets statistics.
         /// \brief Print round trip time packets statistics.
@@ -871,6 +879,20 @@ public:
                                                boot_time_));
                                                boot_time_));
     }
     }
 
 
+    /// \brief Check if the exchange type has been specified.
+    ///
+    /// This method checks if the \ref ExchangeStats object of a particular type
+    /// exists (has been added using \ref addExchangeStats function).
+    ///
+    /// \param xchg_type A type of the exchange being repersented by the
+    /// \ref ExchangeStats object.
+    ///
+    /// \return true if the \ref ExchangeStats object has been added for a
+    /// specified exchange type.
+    bool hasExchangeStats(const ExchangeType xchg_type) const {
+        return (exchanges_.find(xchg_type) != exchanges_.end());
+    }
+
     /// \brief Add named custom uint64 counter.
     /// \brief Add named custom uint64 counter.
     ///
     ///
     /// Method creates new named counter and stores in counter's map under
     /// Method creates new named counter and stores in counter's map under
@@ -1159,7 +1181,7 @@ public:
     ///
     ///
     /// \param xchg_type exchange type.
     /// \param xchg_type exchange type.
     /// \return string representing name of the exchange.
     /// \return string representing name of the exchange.
-    std::string exchangeToString(ExchangeType xchg_type) const {
+    static std::string exchangeToString(ExchangeType xchg_type) {
         switch(xchg_type) {
         switch(xchg_type) {
         case XCHG_DO:
         case XCHG_DO:
             return("DISCOVER-OFFER");
             return("DISCOVER-OFFER");
@@ -1171,6 +1193,8 @@ public:
             return("REQUEST-REPLY");
             return("REQUEST-REPLY");
         case XCHG_RN:
         case XCHG_RN:
             return("RENEW-REPLY");
             return("RENEW-REPLY");
+        case XCHG_RL:
+            return("RELEASE-REPLY");
         default:
         default:
             return("Unknown exchange type");
             return("Unknown exchange type");
         }
         }

+ 148 - 140
tests/tools/perfdhcp/test_control.cc

@@ -98,6 +98,21 @@ TestControl::TestControl() {
 }
 }
 
 
 void
 void
+TestControl::checkLateMessages(RateControl& rate_control) {
+    // If diagnostics is disabled, there is no need to log late sent messages.
+    // If it is enabled and the rate control object indicates that the last
+    // sent message was late, bump up the counter in Stats Manager.
+    if (rate_control.isLateSent() && testDiags('i')) {
+        CommandOptions& options = CommandOptions::instance();
+        if (options.getIpVersion() == 4) {
+            stats_mgr4_->incrementCounter("latesend");
+        } else if (options.getIpVersion() == 6) {
+            stats_mgr6_->incrementCounter("latesend");
+        }
+    }
+}
+
+void
 TestControl::cleanCachedPackets() {
 TestControl::cleanCachedPackets() {
     CommandOptions& options = CommandOptions::instance();
     CommandOptions& options = CommandOptions::instance();
     // When Renews are not sent, Reply packets are not cached so there
     // When Renews are not sent, Reply packets are not cached so there
@@ -316,28 +331,43 @@ TestControl::checkExitConditions() const {
 }
 }
 
 
 Pkt6Ptr
 Pkt6Ptr
-TestControl::createRenew(const Pkt6Ptr& reply) {
+TestControl::createMessageFromReply(const uint16_t msg_type,
+                                    const dhcp::Pkt6Ptr& reply) {
+    // Restrict messages to Release and Renew.
+    if (msg_type != DHCPV6_RENEW && msg_type != DHCPV6_RELEASE) {
+        isc_throw(isc::BadValue, "invalid message type " << msg_type
+                  << " to be created from Reply, expected DHCPV6_RENEW or"
+                  " DHCPV6_RELEASE");
+    }
+    // Get the string representation of the message - to be used for error
+    // logging purposes.
+    const char* msg_type_str = (msg_type == DHCPV6_RENEW ? "Renew" : "Release");
+    // Reply message must be specified.
     if (!reply) {
     if (!reply) {
-        isc_throw(isc::BadValue,"Unable to create Renew packet from the Reply packet"
-                  " because the instance of the Reply is NULL");
+        isc_throw(isc::BadValue, "Unable to create " << msg_type_str
+                  << " message from the Reply message because the instance of"
+                  " the Reply message is NULL");
     }
     }
-    Pkt6Ptr renew(new Pkt6(DHCPV6_RENEW, generateTransid()));
+
+    Pkt6Ptr msg(new Pkt6(msg_type, generateTransid()));
     // Client id.
     // Client id.
     OptionPtr opt_clientid = reply->getOption(D6O_CLIENTID);
     OptionPtr opt_clientid = reply->getOption(D6O_CLIENTID);
     if (!opt_clientid) {
     if (!opt_clientid) {
-        isc_throw(isc::Unexpected, "failed to create Renew packet because client id"
-                  " option has not been found in the Reply from the server");
+        isc_throw(isc::Unexpected, "failed to create " << msg_type_str
+                  << " message because client id option has not been found"
+                  " in the Reply message");
     }
     }
-    renew->addOption(opt_clientid);
+    msg->addOption(opt_clientid);
     // Server id.
     // Server id.
     OptionPtr opt_serverid = reply->getOption(D6O_SERVERID);
     OptionPtr opt_serverid = reply->getOption(D6O_SERVERID);
     if (!opt_serverid) {
     if (!opt_serverid) {
-        isc_throw(isc::Unexpected, "failed to create Renew packet because server id"
-                  " option has not been found in the Reply from the server");
+        isc_throw(isc::Unexpected, "failed to create " << msg_type_str
+                  << " because server id option has not been found in the"
+                  " Reply message");
     }
     }
-    renew->addOption(opt_serverid);
-    copyIaOptions(reply, renew);
-    return (renew);
+    msg->addOption(opt_serverid);
+    copyIaOptions(reply, msg);
+    return (msg);
 }
 }
 
 
 OptionPtr
 OptionPtr
@@ -489,16 +519,28 @@ TestControl::getCurrentTimeout() const {
     ptime now(microsec_clock::universal_time());
     ptime now(microsec_clock::universal_time());
     // Check that we haven't passed the moment to send the next set of
     // Check that we haven't passed the moment to send the next set of
     // packets.
     // packets.
-    if (now >= send_due_ ||
-        (options.getRenewRate() != 0 && now >= renew_due_)) {
+    if (now >= basic_rate_control_.getDue() ||
+        (options.getRenewRate() != 0 && now >= renew_rate_control_.getDue()) ||
+        (options.getReleaseRate() != 0 &&
+         now >= release_rate_control_.getDue())) {
         return (0);
         return (0);
     }
     }
 
 
-    // There is a due time to send Solicit and Renew. We should adjust
-    // the timeout to the due time which occurs sooner.
-    ptime due = send_due_ > renew_due_ ? renew_due_ : send_due_;
-    time_period due_period(now, due);
-    return (due_period.length().total_microseconds());
+    // Let's assume that the due time for Solicit is the soonest.
+    ptime due = basic_rate_control_.getDue();
+    // If we are sending Renews and due time for Renew occurs sooner,
+    // set the due time to Renew due time.
+    if ((options.getRenewRate()) != 0 && (renew_rate_control_.getDue() < due)) {
+        due = renew_rate_control_.getDue();
+    }
+    // If we are sending Releases and the due time for Release occurs
+    // sooner than the current due time, let's use the due for Releases.
+    if ((options.getReleaseRate() != 0) &&
+        (release_rate_control_.getDue() < due)) {
+        due = release_rate_control_.getDue();
+    }
+    // Return the timeout in microseconds.
+    return (time_period(now, due).length().total_microseconds());
 }
 }
 
 
 int
 int
@@ -529,49 +571,6 @@ TestControl::getElapsedTime(const T& pkt1, const T& pkt2) {
     return(elapsed_period.length().total_milliseconds());
     return(elapsed_period.length().total_milliseconds());
 }
 }
 
 
-
-uint64_t
-TestControl::getNextExchangesNum(const boost::posix_time::ptime& send_due,
-                                 const int rate) {
-    CommandOptions& options = CommandOptions::instance();
-    // Get current time.
-    ptime now(microsec_clock::universal_time());
-    if (now >= send_due) {
-        // Reset number of exchanges.
-        uint64_t due_exchanges = 0;
-        // If rate is specified from the command line we have to
-        // synchornize with it.
-        if (rate != 0) {
-            time_period period(send_due, now);
-            time_duration duration = period.length();
-            // due_factor indicates the number of seconds that
-            // sending next chunk of packets will take.
-            double due_factor = duration.fractional_seconds() /
-                time_duration::ticks_per_second();
-            due_factor += duration.total_seconds();
-            // Multiplying due_factor by expected rate gives the number
-            // of exchanges to be initiated.
-            due_exchanges = static_cast<uint64_t>(due_factor * rate);
-            // We want to make sure that at least one packet goes out.
-            if (due_exchanges == 0) {
-                due_exchanges = 1;
-            }
-            // We should not exceed aggressivity as it could have been
-            // restricted from command line.
-            if (due_exchanges > options.getAggressivity()) {
-                due_exchanges = options.getAggressivity();
-            }
-        } else {
-            // Rate is not specified so we rely on aggressivity
-            // which is the number of packets to be sent in
-            // one chunk.
-            due_exchanges = options.getAggressivity();
-        }
-        return (due_exchanges);
-    }
-    return (0);
-}
-
 int
 int
 TestControl::getRandomOffset(const int arg_idx) const {
 TestControl::getRandomOffset(const int arg_idx) const {
     int rand_offset = CommandOptions::instance().getIpVersion() == 4 ?
     int rand_offset = CommandOptions::instance().getIpVersion() == 4 ?
@@ -695,6 +694,9 @@ TestControl::initializeStatsMgr() {
         if (options.getRenewRate() != 0) {
         if (options.getRenewRate() != 0) {
             stats_mgr6_->addExchangeStats(StatsMgr6::XCHG_RN);
             stats_mgr6_->addExchangeStats(StatsMgr6::XCHG_RN);
         }
         }
+        if (options.getReleaseRate() != 0) {
+            stats_mgr6_->addExchangeStats(StatsMgr6::XCHG_RL);
+        }
     }
     }
     if (testDiags('i')) {
     if (testDiags('i')) {
         if (options.getIpVersion() == 4) {
         if (options.getIpVersion() == 4) {
@@ -849,14 +851,15 @@ TestControl::sendPackets(const TestControlSocket& socket,
 }
 }
 
 
 uint64_t
 uint64_t
-TestControl::sendRenewPackets(const TestControlSocket& socket,
-                              const uint64_t packets_num) {
-    for (uint64_t i = 0; i < packets_num; ++i) {
-        if (!sendRenew(socket)) {
+TestControl::sendMultipleMessages6(const TestControlSocket& socket,
+                                   const uint32_t msg_type,
+                                   const uint64_t msg_num) {
+    for (uint64_t i = 0; i < msg_num; ++i) {
+        if (!sendMessageFromReply(msg_type, socket)) {
             return (i);
             return (i);
         }
         }
     }
     }
-    return (packets_num);
+    return (msg_num);
 }
 }
 
 
 void
 void
@@ -1114,14 +1117,36 @@ TestControl::processReceivedPacket6(const TestControlSocket& socket,
             }
             }
         }
         }
     } else if (packet_type == DHCPV6_REPLY) {
     } else if (packet_type == DHCPV6_REPLY) {
-        Pkt6Ptr sent_packet = stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RR,
-                                                          pkt6);
-        if (sent_packet) {
-            if (CommandOptions::instance().getRenewRate() != 0) {
+        // If the received message is Reply, we have to find out which exchange
+        // 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)) {
+            // 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)) {
+                // Renew or Release messages are sent, because StatsMgr has the
+                // specific exchange type specified. Let's append the Reply
+                // message to a storage.
                 reply_storage_.append(pkt6);
                 reply_storage_.append(pkt6);
             }
             }
-        } else {
-            stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RN, pkt6);
+        // The Reply message is not a server's response to the Request message
+        // sent within the 4-way exchange. It may be a response to the Renew
+        // or Release message. In the if clause 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 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)) {
+            // 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);
         }
         }
     }
     }
 }
 }
@@ -1250,12 +1275,16 @@ TestControl::registerOptionFactories() const {
 
 
 void
 void
 TestControl::reset() {
 TestControl::reset() {
-    send_due_ = microsec_clock::universal_time();
-    last_sent_ = send_due_;
-    last_report_ = send_due_;
-    renew_due_ = send_due_;
-    last_renew_ = send_due_;
+    CommandOptions& options = CommandOptions::instance();
+    basic_rate_control_.setAggressivity(options.getAggressivity());
+    basic_rate_control_.setRate(options.getRate());
+    renew_rate_control_.setAggressivity(options.getAggressivity());
+    renew_rate_control_.setRate(options.getRenewRate());
+    release_rate_control_.setAggressivity(options.getAggressivity());
+    release_rate_control_.setRate(options.getReleaseRate());
+
     transid_gen_.reset();
     transid_gen_.reset();
+    last_report_ = microsec_clock::universal_time();
     // Actual generators will have to be set later on because we need to
     // Actual generators will have to be set later on because we need to
     // get command line parameters first.
     // get command line parameters first.
     setTransidGenerator(NumberGeneratorPtr());
     setTransidGenerator(NumberGeneratorPtr());
@@ -1321,11 +1350,10 @@ TestControl::run() {
     // Initialize Statistics Manager. Release previous if any.
     // Initialize Statistics Manager. Release previous if any.
     initializeStatsMgr();
     initializeStatsMgr();
     for (;;) {
     for (;;) {
-        // Calculate send due based on when last exchange was initiated.
-        updateSendDue(last_sent_, options.getRate(), send_due_);
         // Calculate number of packets to be sent to stay
         // Calculate number of packets to be sent to stay
         // catch up with rate.
         // catch up with rate.
-        uint64_t packets_due = getNextExchangesNum(send_due_, options.getRate());
+        uint64_t packets_due = basic_rate_control_.getOutboundMessageCount();
+        checkLateMessages(basic_rate_control_);
         if ((packets_due == 0) && testDiags('i')) {
         if ((packets_due == 0) && testDiags('i')) {
             if (options.getIpVersion() == 4) {
             if (options.getIpVersion() == 4) {
                 stats_mgr4_->incrementCounter("shortwait");
                 stats_mgr4_->incrementCounter("shortwait");
@@ -1351,11 +1379,21 @@ TestControl::run() {
         // If -f<renew-rate> option was specified we have to check how many
         // 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.
         // Renew packets should be sent to catch up with a desired rate.
         if ((options.getIpVersion() == 6) && (options.getRenewRate() != 0)) {
         if ((options.getIpVersion() == 6) && (options.getRenewRate() != 0)) {
-            updateSendDue(last_renew_, options.getRenewRate(), renew_due_);
             uint64_t renew_packets_due =
             uint64_t renew_packets_due =
-                getNextExchangesNum(renew_due_, options.getRenewRate());
-            // Send renew packets.
-            sendRenewPackets(socket, renew_packets_due);
+                renew_rate_control_.getOutboundMessageCount();
+            checkLateMessages(renew_rate_control_);
+            // Send Renew messages.
+            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();
+            checkLateMessages(release_rate_control_);
+            // Send Release messages.
+            sendMultipleMessages6(socket, DHCPV6_RELEASE, release_packets_due);
         }
         }
 
 
         // Report delay means that user requested printing number
         // Report delay means that user requested printing number
@@ -1451,7 +1489,7 @@ TestControl::saveFirstPacket(const Pkt6Ptr& pkt) {
 void
 void
 TestControl::sendDiscover4(const TestControlSocket& socket,
 TestControl::sendDiscover4(const TestControlSocket& socket,
                            const bool preload /*= false*/) {
                            const bool preload /*= false*/) {
-    last_sent_ = microsec_clock::universal_time();
+    basic_rate_control_.updateSendTime();
     // Generate the MAC address to be passed in the packet.
     // Generate the MAC address to be passed in the packet.
     uint8_t randomized = 0;
     uint8_t randomized = 0;
     std::vector<uint8_t> mac_address = generateMacAddress(randomized);
     std::vector<uint8_t> mac_address = generateMacAddress(randomized);
@@ -1496,9 +1534,7 @@ void
 TestControl::sendDiscover4(const TestControlSocket& socket,
 TestControl::sendDiscover4(const TestControlSocket& socket,
                            const std::vector<uint8_t>& template_buf,
                            const std::vector<uint8_t>& template_buf,
                            const bool preload /* = false */) {
                            const bool preload /* = false */) {
-    // last_sent_ has to be updated for each function that initiates
-    // new transaction. The packet exchange synchronization relies on this.
-    last_sent_ = microsec_clock::universal_time();
+    basic_rate_control_.updateSendTime();
     // Get the first argument if mulitple the same arguments specified
     // Get the first argument if mulitple the same arguments specified
     // in the command line. First one refers to DISCOVER packets.
     // in the command line. First one refers to DISCOVER packets.
     const uint8_t arg_idx = 0;
     const uint8_t arg_idx = 0;
@@ -1547,21 +1583,35 @@ TestControl::sendDiscover4(const TestControlSocket& socket,
 }
 }
 
 
 bool
 bool
-TestControl::sendRenew(const TestControlSocket& socket) {
-    last_renew_ = microsec_clock::universal_time();
+TestControl::sendMessageFromReply(const uint16_t msg_type,
+                                  const TestControlSocket& socket) {
+    // 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
+                  << " to be sent, expected DHCPV6_RENEW or DHCPV6_RELEASE");
+    }
+    // We track the timestamp of last Release and Renew in different variables.
+    if (msg_type == DHCPV6_RENEW) {
+        renew_rate_control_.updateSendTime();
+    } else {
+        release_rate_control_.updateSendTime();
+    }
     Pkt6Ptr reply = reply_storage_.getRandom();
     Pkt6Ptr reply = reply_storage_.getRandom();
     if (!reply) {
     if (!reply) {
         return (false);
         return (false);
     }
     }
-    Pkt6Ptr renew = createRenew(reply);
-    setDefaults6(socket, renew);
-    renew->pack();
-    IfaceMgr::instance().send(renew);
+    // Prepare the message of the specified type.
+    Pkt6Ptr msg = createMessageFromReply(msg_type, reply);
+    setDefaults6(socket, msg);
+    msg->pack();
+    // And send it.
+    IfaceMgr::instance().send(msg);
     if (!stats_mgr6_) {
     if (!stats_mgr6_) {
         isc_throw(Unexpected, "Statistics Manager for DHCPv6 "
         isc_throw(Unexpected, "Statistics Manager for DHCPv6 "
                   "hasn't been initialized");
                   "hasn't been initialized");
     }
     }
-    stats_mgr6_->passSentPacket(StatsMgr6::XCHG_RN, renew);
+    stats_mgr6_->passSentPacket((msg_type == DHCPV6_RENEW ? StatsMgr6::XCHG_RN
+                                 : StatsMgr6::XCHG_RL), msg);
     return (true);
     return (true);
 }
 }
 
 
@@ -1903,7 +1953,7 @@ TestControl::sendRequest6(const TestControlSocket& socket,
 void
 void
 TestControl::sendSolicit6(const TestControlSocket& socket,
 TestControl::sendSolicit6(const TestControlSocket& socket,
                           const bool preload /*= false*/) {
                           const bool preload /*= false*/) {
-    last_sent_ = microsec_clock::universal_time();
+    basic_rate_control_.updateSendTime();
     // Generate DUID to be passed to the packet
     // Generate DUID to be passed to the packet
     uint8_t randomized = 0;
     uint8_t randomized = 0;
     std::vector<uint8_t> duid = generateDuid(randomized);
     std::vector<uint8_t> duid = generateDuid(randomized);
@@ -1952,7 +2002,7 @@ void
 TestControl::sendSolicit6(const TestControlSocket& socket,
 TestControl::sendSolicit6(const TestControlSocket& socket,
                           const std::vector<uint8_t>& template_buf,
                           const std::vector<uint8_t>& template_buf,
                           const bool preload /*= false*/) {
                           const bool preload /*= false*/) {
-    last_sent_ = microsec_clock::universal_time();
+    basic_rate_control_.updateSendTime();
     const int arg_idx = 0;
     const int arg_idx = 0;
     // Get transaction id offset.
     // Get transaction id offset.
     size_t transid_offset = getTransactionIdOffset(arg_idx);
     size_t transid_offset = getTransactionIdOffset(arg_idx);
@@ -2051,47 +2101,5 @@ TestControl::testDiags(const char diag) const {
     return (false);
     return (false);
 }
 }
 
 
-void
-TestControl::updateSendDue(const boost::posix_time::ptime& last_sent,
-                           const int rate,
-                           boost::posix_time::ptime& send_due) {
-    // If default constructor was called, this should not happen but
-    // if somebody has changed default constructor it is better to
-    // keep this check.
-    if (last_sent.is_not_a_date_time()) {
-        isc_throw(Unexpected, "time of last sent packet not initialized");
-    }
-    // Get the expected exchange rate.
-    CommandOptions& options = CommandOptions::instance();
-    // If rate was not specified we will wait just one clock tick to
-    // send next packet. This simulates best effort conditions.
-    long duration = 1;
-    if (rate != 0) {
-        // We use number of ticks instead of nanoseconds because
-        // nanosecond resolution may not be available on some
-        // machines. Number of ticks guarantees the highest possible
-        // timer resolution.
-        duration = time_duration::ticks_per_second() / rate;
-    }
-    // Calculate due time to initiate next chunk of exchanges.
-    send_due = last_sent + time_duration(0, 0, 0, duration);
-    // Check if it is already due.
-    ptime now(microsec_clock::universal_time());
-    // \todo verify if this condition is not too tight. In other words
-    // verify if this will not produce too many late sends.
-    // We might want to look at this once we are done implementing
-    // microsecond timeouts in IfaceMgr.
-    if (now > send_due) {
-        if (testDiags('i')) {
-            if (options.getIpVersion() == 4) {
-                stats_mgr4_->incrementCounter("latesend");
-            } else if (options.getIpVersion() == 6) {
-                stats_mgr6_->incrementCounter("latesend");
-            }
-        }
-    }
-}
-
-
 } // namespace perfdhcp
 } // namespace perfdhcp
 } // namespace isc
 } // namespace isc

+ 50 - 52
tests/tools/perfdhcp/test_control.h

@@ -15,8 +15,9 @@
 #ifndef TEST_CONTROL_H
 #ifndef TEST_CONTROL_H
 #define TEST_CONTROL_H
 #define TEST_CONTROL_H
 
 
-#include "packet_storage.h"
-#include "stats_mgr.h"
+#include <tests/tools/perfdhcp/packet_storage.h>
+#include <tests/tools/perfdhcp/rate_control.h>
+#include <tests/tools/perfdhcp/stats_mgr.h>
 
 
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/dhcp6.h>
@@ -313,13 +314,23 @@ protected:
     /// has been reached.
     /// has been reached.
     void cleanCachedPackets();
     void cleanCachedPackets();
 
 
-    /// \brief Creates IPv6 packet using options from Reply packet.
+    /// \brief Creates DHCPv6 message from the Reply packet.
     ///
     ///
+    /// This function creates DHCPv6 Renew or Release message using the
+    /// data from the Reply message by copying options from the Reply
+    /// message.
+    ///
+    /// \param msg_type A type of the message to be createad.
     /// \param reply An instance of the Reply packet which contents should
     /// \param reply An instance of the Reply packet which contents should
-    /// be used to create an instance of the Renew packet.
+    /// be used to create an instance of the new message.
     ///
     ///
-    /// \return created Renew packet.
-    dhcp::Pkt6Ptr createRenew(const dhcp::Pkt6Ptr& reply);
+    /// \return created Release or Renew message
+    /// \throw isc::BadValue if the msg_type is neither DHCPV6_RENEW nor
+    /// DHCPV6_RELEASE or if the reply is NULL.
+    /// \throw isc::Unexpected if mandatory options are missing in the
+    /// Reply message.
+    dhcp::Pkt6Ptr createMessageFromReply(const uint16_t msg_type,
+                                         const dhcp::Pkt6Ptr& reply);
 
 
     /// \brief Factory function to create DHCPv6 ELAPSED_TIME option.
     /// \brief Factory function to create DHCPv6 ELAPSED_TIME option.
     ///
     ///
@@ -481,21 +492,6 @@ protected:
     /// \return A current timeout in microseconds.
     /// \return A current timeout in microseconds.
     uint32_t getCurrentTimeout() const;
     uint32_t getCurrentTimeout() const;
 
 
-    /// \brief Returns number of exchanges to be started.
-    ///
-    /// Method returns number of new exchanges to be started as soon
-    /// as possible to satisfy expected rate. Calculation used here
-    /// is based on current time, due time calculated with
-    /// \ref updateSendDue function and expected rate.
-    ///
-    /// \param send_due Due time to initiate next chunk set exchanges.
-    /// \param rate A rate at which exchanges are initiated.
-    ///
-    /// \return number of exchanges to be started immediately.
-    static uint64_t
-    getNextExchangesNum(const boost::posix_time::ptime& send_due,
-                        const int rate);
-
     /// \brief Return template buffer.
     /// \brief Return template buffer.
     ///
     ///
     /// Method returns template buffer at specified index.
     /// Method returns template buffer at specified index.
@@ -738,25 +734,32 @@ protected:
                      const uint64_t packets_num,
                      const uint64_t packets_num,
                      const bool preload = false);
                      const bool preload = false);
 
 
-    /// \brief Send number of DHCPv6 Renew packets to the server.
+    /// \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 socket An object representing socket to be used to send packets.
-    /// \param packets_num A number of Renew packets to be send.
+    /// \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 packets actually sent.
-    uint64_t sendRenewPackets(const TestControlSocket& socket,
-                              const uint64_t packets_num);
+    /// \return A number of messages actually sent.
+    uint64_t sendMultipleMessages6(const TestControlSocket& socket,
+                                   const uint32_t msg_type,
+                                   const uint64_t msg_num);
 
 
-    /// \brief Send a renew message using provided socket.
+    /// \brief Send DHCPv6 Renew or Release message using specified socket.
     ///
     ///
     /// This method will select an existing lease from the Reply packet cache
     /// This method will select an existing lease from the Reply packet cache
-    /// If there is no lease that can be renewed this method will return false.
+    /// If there is no lease that can be renewed or released this method will
+    /// return false.
     ///
     ///
+    /// \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
     /// \param socket An object encapsulating socket to be used to send
     /// a packet.
     /// a packet.
     ///
     ///
-    /// \return true if packet has been sent, false otherwise.
-    bool sendRenew(const TestControlSocket& socket);
+    /// \return true if the message has been sent, false otherwise.
+    bool sendMessageFromReply(const uint16_t msg_type,
+                              const TestControlSocket& socket);
 
 
     /// \brief Send DHCPv4 REQUEST message.
     /// \brief Send DHCPv4 REQUEST message.
     ///
     ///
@@ -899,21 +902,18 @@ protected:
     /// \return true if diagnostics flag has been set.
     /// \return true if diagnostics flag has been set.
     bool testDiags(const char diag) const;
     bool testDiags(const char diag) const;
 
 
-    /// \brief Update due time to initiate next chunk of exchanges.
+protected:
+
+    /// \brief Increments counter of late sent messages if required.
     ///
     ///
-    /// Method updates due time to initiate next chunk of exchanges.
-    /// Function takes current time, last sent packet's time and
-    /// expected rate in its calculations.
+    /// This function checks if the message or set of messages of a given type,
+    /// were sent later than their due time. If they were sent late, it is
+    /// an indication that the perfdhcp doesn't catch up with the desired rate
+    /// for sending messages.
     ///
     ///
-    /// \param last_sent A time when the last exchange was initiated.
-    /// \param rate A rate at which exchangesa re initiated
-    /// \param [out] send_due A reference to the time object to be updated
-    /// with the next due time.
-    void updateSendDue(const boost::posix_time::ptime& last_sent,
-                       const int rate,
-                       boost::posix_time::ptime& send_due);
-
-private:
+    /// \param rate_control An object tracking due times for a particular
+    /// type of messages.
+    void checkLateMessages(RateControl& rate_control);
 
 
     /// \brief Copies IA_NA or IA_PD option from one packet to another.
     /// \brief Copies IA_NA or IA_PD option from one packet to another.
     ///
     ///
@@ -943,7 +943,7 @@ private:
 
 
     /// \brief Calculate elapsed time between two packets.
     /// \brief Calculate elapsed time between two packets.
     ///
     ///
-    /// \param T Pkt4Ptr or Pkt6Ptr class.
+    /// \tparam T Pkt4Ptr or Pkt6Ptr class.
     /// \param pkt1 first packet.
     /// \param pkt1 first packet.
     /// \param pkt2 second packet.
     /// \param pkt2 second packet.
     /// \throw InvalidOperation if packet timestamps are invalid.
     /// \throw InvalidOperation if packet timestamps are invalid.
@@ -1056,14 +1056,12 @@ private:
     std::string vector2Hex(const std::vector<uint8_t>& vec,
     std::string vector2Hex(const std::vector<uint8_t>& vec,
                            const std::string& separator = "") const;
                            const std::string& separator = "") const;
 
 
-    boost::posix_time::ptime send_due_;    ///< Due time to initiate next chunk
-                                           ///< of exchanges.
-    boost::posix_time::ptime last_sent_;   ///< Indicates when the last exchange
-                                           ///< was initiated.
-    boost::posix_time::ptime renew_due_;   ///< Due time to send next set of
-                                           ///< Renew requests.
-    boost::posix_time::ptime last_renew_;  ///< Indicates when the last Renew
-                                           ///< was attempted.
+    /// \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_;
 
 
     boost::posix_time::ptime last_report_; ///< Last intermediate report time.
     boost::posix_time::ptime last_report_; ///< Last intermediate report time.
 
 

+ 2 - 0
tests/tools/perfdhcp/tests/Makefile.am

@@ -26,6 +26,7 @@ run_unittests_SOURCES += perf_pkt6_unittest.cc
 run_unittests_SOURCES += perf_pkt4_unittest.cc
 run_unittests_SOURCES += perf_pkt4_unittest.cc
 run_unittests_SOURCES += localized_option_unittest.cc
 run_unittests_SOURCES += localized_option_unittest.cc
 run_unittests_SOURCES += packet_storage_unittest.cc
 run_unittests_SOURCES += packet_storage_unittest.cc
+run_unittests_SOURCES += rate_control_unittest.cc
 run_unittests_SOURCES += stats_mgr_unittest.cc
 run_unittests_SOURCES += stats_mgr_unittest.cc
 run_unittests_SOURCES += test_control_unittest.cc
 run_unittests_SOURCES += test_control_unittest.cc
 run_unittests_SOURCES += command_options_helper.h
 run_unittests_SOURCES += command_options_helper.h
@@ -33,6 +34,7 @@ run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/command_options.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/pkt_transform.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/pkt_transform.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt6.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt6.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt4.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt4.cc
+run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/rate_control.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/test_control.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/test_control.cc
 
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)

+ 66 - 2
tests/tools/perfdhcp/tests/command_options_unittest.cc

@@ -168,6 +168,8 @@ protected:
         EXPECT_EQ(CommandOptions::DORA_SARR, opt.getExchangeMode());
         EXPECT_EQ(CommandOptions::DORA_SARR, opt.getExchangeMode());
         EXPECT_TRUE(opt.getLeaseType().is(CommandOptions::LeaseType::ADDRESS));
         EXPECT_TRUE(opt.getLeaseType().is(CommandOptions::LeaseType::ADDRESS));
         EXPECT_EQ(0, opt.getRate());
         EXPECT_EQ(0, opt.getRate());
+        EXPECT_EQ(0, opt.getRenewRate());
+        EXPECT_EQ(0, opt.getReleaseRate());
         EXPECT_EQ(0, opt.getReportDelay());
         EXPECT_EQ(0, opt.getReportDelay());
         EXPECT_EQ(0, opt.getClientsNum());
         EXPECT_EQ(0, opt.getClientsNum());
 
 
@@ -341,12 +343,18 @@ TEST_F(CommandOptionsTest, RenewRate) {
     EXPECT_NO_THROW(process("perfdhcp -6 -r 10 -f 10 -l ethx all"));
     EXPECT_NO_THROW(process("perfdhcp -6 -r 10 -f 10 -l ethx all"));
     EXPECT_EQ(10, opt.getRenewRate());
     EXPECT_EQ(10, opt.getRenewRate());
     // Check that the release rate can be set to different value than
     // Check that the release rate can be set to different value than
-    // rate specified as -r<rate>. Also, swap -f na d-r to make sure
+    // rate specified as -r<rate>. Also, swap -f and -r to make sure
     // that order doesn't matter.
     // that order doesn't matter.
     EXPECT_NO_THROW(process("perfdhcp -6 -f 5 -r 10 -l ethx all"));
     EXPECT_NO_THROW(process("perfdhcp -6 -f 5 -r 10 -l ethx all"));
     EXPECT_EQ(5, opt.getRenewRate());
     EXPECT_EQ(5, opt.getRenewRate());
+    // The renew rate should not be greater than the rate.
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -f 11 -l ethx all"),
+                 isc::InvalidParameter);
     // The renew-rate of 0 is invalid.
     // The renew-rate of 0 is invalid.
-    EXPECT_THROW(process("perfdhcp -6 -r 10 -f 0 - l ethx all"),
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -f 0 -l ethx all"),
+                 isc::InvalidParameter);
+    // The negative renew-rate is invalid.
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -f -5 -l ethx all"),
                  isc::InvalidParameter);
                  isc::InvalidParameter);
     // If -r<rate> is not specified the -f<renew-rate> should not
     // If -r<rate> is not specified the -f<renew-rate> should not
     // be accepted.
     // be accepted.
@@ -365,6 +373,62 @@ TEST_F(CommandOptionsTest, RenewRate) {
                  isc::InvalidParameter);
                  isc::InvalidParameter);
 }
 }
 
 
+TEST_F(CommandOptionsTest, ReleaseRate) {
+    CommandOptions& opt = CommandOptions::instance();
+    // If -F is specified together with -r the command line should
+    // be accepted and the release rate should be set.
+    EXPECT_NO_THROW(process("perfdhcp -6 -r 10 -F 10 -l ethx all"));
+    EXPECT_EQ(10, opt.getReleaseRate());
+    // Check that the release rate can be set to different value than
+    // rate specified as -r<rate>. Also, swap -F and -r to make sure
+    // that order doesn't matter.
+    EXPECT_NO_THROW(process("perfdhcp -6 -F 5 -r 10 -l ethx all"));
+    EXPECT_EQ(5, opt.getReleaseRate());
+    // The release rate should not be greater than the rate.
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -F 11 -l ethx all"),
+                 isc::InvalidParameter);
+    // The release-rate of 0 is invalid.
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -F 0 -l ethx all"),
+                 isc::InvalidParameter);
+    // The negative rlease-rate is invalid.
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -F -5 -l ethx all"),
+                 isc::InvalidParameter);
+    // If -r<rate> is not specified the -F<release-rate> should not
+    // be accepted.
+    EXPECT_THROW(process("perfdhcp -6 -F 10 -l ethx all"),
+                 isc::InvalidParameter);
+    // Currently the -F<release-rate> can be specified for IPv6 mode
+    // only.
+    EXPECT_THROW(process("perfdhcp -4 -r 10 -F 10 -l ethx all"),
+                 isc::InvalidParameter);
+    // Release rate should be specified.
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -F -l ethx all"),
+                 isc::InvalidParameter);
+
+    // -F and -i are mutually exclusive
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -F 10 -l ethx -i all"),
+                 isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, ReleaseRenew) {
+    CommandOptions& opt = CommandOptions::instance();
+    // It should be possible to specify the -F, -f and -r options.
+    EXPECT_NO_THROW(process("perfdhcp -6 -r 10 -F 3 -f 5 -l ethx all"));
+    EXPECT_EQ(10, opt.getRate());
+    EXPECT_EQ(3, opt.getReleaseRate());
+    EXPECT_EQ(5, opt.getRenewRate());
+    // It should be possible to specify the -F and -f with the values which
+    // sum is equal to the rate specified as -r<rate>.
+    EXPECT_NO_THROW(process("perfdhcp -6 -r 8 -F 3 -f 5 -l ethx all"));
+    EXPECT_EQ(8, opt.getRate());
+    EXPECT_EQ(3, opt.getReleaseRate());
+    EXPECT_EQ(5, opt.getRenewRate());
+    // Check that the sum of the release and renew rate is not greater
+    // than the rate specified as -r<rate>.
+    EXPECT_THROW(process("perfdhcp -6 -F 6 -f 5 -r 10 -l ethx all"),
+                 isc::InvalidParameter);
+}
+
 TEST_F(CommandOptionsTest, ReportDelay) {
 TEST_F(CommandOptionsTest, ReportDelay) {
     CommandOptions& opt = CommandOptions::instance();
     CommandOptions& opt = CommandOptions::instance();
     EXPECT_NO_THROW(process("perfdhcp -r 100 -t 17 -l ethx all"));
     EXPECT_NO_THROW(process("perfdhcp -r 100 -t 17 -l ethx all"));

+ 207 - 0
tests/tools/perfdhcp/tests/rate_control_unittest.cc

@@ -0,0 +1,207 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+#include <tests/tools/perfdhcp/rate_control.h>
+#include <gtest/gtest.h>
+
+
+using namespace isc;
+using namespace isc::perfdhcp;
+
+/// \brief A class which exposes protected methods and members of the
+/// RateControl class (under test).
+class NakedRateControl : public RateControl {
+public:
+
+    /// \brief Default constructor.
+    NakedRateControl()
+        : RateControl() {
+    }
+
+    /// \brief Constructor which sets up the rate and aggressivity.
+    ///
+    /// \param rate A rate at which messages are sent.
+    /// \param aggressivity A value of aggressivity. This value controls the
+    /// maximal number of messages sent in one chunk.
+    NakedRateControl(const int rate, const int aggressivity)
+        : RateControl(rate, aggressivity) {
+    }
+
+    using RateControl::currentTime;
+    using RateControl::updateSendTime;
+    using RateControl::updateSendDue;
+    using RateControl::send_due_;
+    using RateControl::last_sent_;
+    using RateControl::late_sent_;
+
+};
+
+// Test default constructor.
+TEST(RateControl, constructorDefault) {
+    NakedRateControl rc;
+    EXPECT_EQ(1, rc.getAggressivity());
+    EXPECT_EQ(0, rc.getRate());
+    EXPECT_FALSE(rc.getDue().is_not_a_date_time());
+    EXPECT_FALSE(rc.last_sent_.is_not_a_date_time());
+    EXPECT_FALSE(rc.isLateSent());
+}
+
+// Test the constructor which sets the rate and aggressivity.
+TEST(RateControl, constructor) {
+    // Call the constructor and verify that it sets the appropriate
+    // values.
+    NakedRateControl rc1(3, 2);
+    EXPECT_EQ(2, rc1.getAggressivity());
+    EXPECT_EQ(3, rc1.getRate());
+    EXPECT_FALSE(rc1.getDue().is_not_a_date_time());
+    EXPECT_FALSE(rc1.last_sent_.is_not_a_date_time());
+    EXPECT_FALSE(rc1.isLateSent());
+
+    // Call the constructor again and make sure that different values
+    // will be set correctly.
+    NakedRateControl rc2(5, 6);
+    EXPECT_EQ(6, rc2.getAggressivity());
+    EXPECT_EQ(5, rc2.getRate());
+    EXPECT_FALSE(rc2.getDue().is_not_a_date_time());
+    EXPECT_FALSE(rc2.last_sent_.is_not_a_date_time());
+    EXPECT_FALSE(rc2.isLateSent());
+
+    // The 0 value of aggressivity < 1 is not acceptable.
+    EXPECT_THROW(RateControl(3, 0), isc::BadValue);
+    // The negative value of rate is not acceptable.
+    EXPECT_THROW(RateControl(-1, 3), isc::BadValue);
+}
+
+// Check the aggressivity accessor.
+TEST(RateControl, getAggressivity) {
+    RateControl rc;
+    ASSERT_EQ(1, rc.getAggressivity());
+    rc.setAggressivity(5);
+    ASSERT_EQ(5, rc.getAggressivity());
+    rc.setAggressivity(10);
+    EXPECT_EQ(10, rc.getAggressivity());
+}
+
+// Check the due time accessor.
+TEST(RateControl, getDue) {
+    NakedRateControl rc;
+    ASSERT_FALSE(rc.getDue().is_not_a_date_time());
+    rc.send_due_ = NakedRateControl::currentTime();
+    EXPECT_TRUE(NakedRateControl::currentTime() >= rc.getDue());
+    rc.send_due_ = NakedRateControl::currentTime() +
+        boost::posix_time::seconds(10);
+    EXPECT_TRUE(NakedRateControl::currentTime() < rc.getDue());
+}
+
+// Check the rate accessor.
+TEST(RateControl, getRate) {
+    RateControl rc;
+    ASSERT_EQ(0, rc.getRate());
+    rc.setRate(5);
+    ASSERT_EQ(5, rc.getRate());
+    rc.setRate(10);
+    EXPECT_EQ(10, rc.getRate());
+}
+
+// Check if late send flag accessor.
+TEST(RateControl, isLateSent) {
+    NakedRateControl rc;
+    ASSERT_FALSE(rc.isLateSent());
+    rc.late_sent_ = true;
+    EXPECT_TRUE(rc.isLateSent());
+}
+
+// Check that the function returns the number of messages to be sent "now"
+// correctly.
+// @todo Possibly extend this test to cover more complex scenarios. Note that
+// it is quite hard to fully test this function as its behaviour strongly
+// depends on time.
+TEST(RateControl, getOutboundMessageCount) {
+    NakedRateControl rc1(1000, 1);
+    // Set the timestamp of the last sent message well to the past.
+    // The resulting due time will be in the past too.
+    rc1.last_sent_ =
+        NakedRateControl::currentTime() - boost::posix_time::seconds(5);
+    // The number of messages to be sent must be greater than 0.
+    uint64_t count;
+    ASSERT_NO_THROW(count = rc1.getOutboundMessageCount());
+    EXPECT_GT(count, 0);
+    // Now, don't specify the rate. In this case the aggressivity dictates
+    // how many messages to send.
+    NakedRateControl rc2(0, 3);
+    rc2.last_sent_ =
+        NakedRateControl::currentTime() - boost::posix_time::seconds(5);
+    ASSERT_NO_THROW(count = rc2.getOutboundMessageCount());
+    EXPECT_EQ(3, count);
+    // Specify the rate and set the timestamp of the last sent message well
+    // to the future. If the resulting due time is well in the future too,
+    // the number of messages to be sent must be 0.
+    NakedRateControl rc3(10, 3);
+    rc3.last_sent_ = NakedRateControl::currentTime() +
+        boost::posix_time::seconds(5);
+    ASSERT_NO_THROW(count = rc3.getOutboundMessageCount());
+    EXPECT_EQ(0, count);
+
+}
+
+// Test the aggressivity modifier for valid and invalid values.
+TEST(RateControl, setAggressivity) {
+    NakedRateControl rc;
+    ASSERT_NO_THROW(rc.setAggressivity(1));
+    EXPECT_THROW(rc.setAggressivity(0), isc::BadValue);
+    EXPECT_THROW(rc.setAggressivity(-1), isc::BadValue);
+}
+
+// Test the rate modifier for valid and invalid rate values.
+TEST(RateControl, setRate) {
+    NakedRateControl rc;
+    EXPECT_NO_THROW(rc.setRate(1));
+    EXPECT_NO_THROW(rc.setRate(0));
+    EXPECT_THROW(rc.setRate(-1), isc::BadValue);
+}
+
+// Test the function which calculates the due time to send next set of
+// messages.
+TEST(RateControl, updateSendDue) {
+    NakedRateControl rc;
+    // Set the send due timestamp to the value which is well in the future.
+    // If we don't hit the due time, the function should not modify the
+    // due time.
+    rc.send_due_ =
+        NakedRateControl::currentTime() + boost::posix_time::seconds(10);
+    boost::posix_time::ptime last_send_due = rc.send_due_;
+    ASSERT_NO_THROW(rc.updateSendDue());
+    EXPECT_TRUE(rc.send_due_ == last_send_due);
+    // Set the due time to the value which is already behind.
+    rc.send_due_ =
+        NakedRateControl::currentTime() - boost::posix_time::seconds(10);
+    last_send_due = rc.send_due_;
+    ASSERT_NO_THROW(rc.updateSendDue());
+    // The value should be modified to the new value.
+    EXPECT_TRUE(rc.send_due_ > last_send_due);
+
+}
+
+// Test that the message send time is updated to the current time.
+TEST(RateControl, updateSendTime) {
+    NakedRateControl rc;
+    // Set the timestamp to the future.
+    rc.last_sent_ =
+        NakedRateControl::currentTime() + boost::posix_time::seconds(5);
+    rc.updateSendTime();
+    // Updated timestamp should be set to now or to the past.
+    EXPECT_TRUE(rc.last_sent_ <= NakedRateControl::currentTime());
+
+}

+ 20 - 0
tests/tools/perfdhcp/tests/stats_mgr_unittest.cc

@@ -187,6 +187,8 @@ TEST_F(StatsMgrTest, Exchange) {
                                                       common_transid));
                                                       common_transid));
     // This is expected to throw because XCHG_DO was not yet
     // This is expected to throw because XCHG_DO was not yet
     // added to Stats Manager for tracking.
     // added to Stats Manager for tracking.
+    ASSERT_FALSE(stats_mgr->hasExchangeStats(StatsMgr4::XCHG_DO));
+    ASSERT_FALSE(stats_mgr->hasExchangeStats(StatsMgr4::XCHG_RA));
     EXPECT_THROW(
     EXPECT_THROW(
         stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet),
         stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet),
         BadValue
         BadValue
@@ -196,8 +198,11 @@ TEST_F(StatsMgrTest, Exchange) {
         BadValue
         BadValue
     );
     );
 
 
+
     // Adding DISCOVER-OFFER exchanges to be tracked by Stats Manager.
     // Adding DISCOVER-OFFER exchanges to be tracked by Stats Manager.
     stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
     stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
+    ASSERT_TRUE(stats_mgr->hasExchangeStats(StatsMgr4::XCHG_DO));
+    ASSERT_FALSE(stats_mgr->hasExchangeStats(StatsMgr4::XCHG_RA));
     // The following two attempts are expected to throw because
     // The following two attempts are expected to throw because
     // invalid exchange types are passed (XCHG_RA instead of XCHG_DO)
     // invalid exchange types are passed (XCHG_RA instead of XCHG_DO)
     EXPECT_THROW(
     EXPECT_THROW(
@@ -259,6 +264,21 @@ TEST_F(StatsMgrTest, MultipleExchanges) {
               stats_mgr->getRcvdPacketsNum(StatsMgr6::XCHG_RR));
               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));
+
+    // 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) {
 TEST_F(StatsMgrTest, SendReceiveSimple) {
     boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
     boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
     boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER,
     boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER,

+ 365 - 143
tests/tools/perfdhcp/tests/test_control_unittest.cc

@@ -74,8 +74,32 @@ public:
         uint32_t transid_; ///< Last generated transaction id.
         uint32_t transid_; ///< Last generated transaction id.
     };
     };
 
 
+    /// \brief Sets the due times for sedning Solicit, Renew and Release.
+    ///
+    /// There are three class members that hold the due time for sending DHCP
+    /// messages:
+    /// - send_due_ - due time to send Solicit,
+    /// - renew_due_ - due time to send Renew,
+    /// - release_due_ - due time to send Release.
+    /// Some tests in this test suite need to modify these values relative to
+    /// the current time. This function modifies this values using time
+    /// offset values (positive or negative) specified as a difference in
+    /// seconds between current time and the due time.
+    ///
+    /// \param send_secs An offset of the due time for Solicit.
+    /// \param renew_secs An offset of the due time for Renew.
+    /// \param release_secs An offset of the due time for Release.
+    void setRelativeDueTimes(const int send_secs, const int renew_secs = 0,
+                             const int release_secs = 0) {
+        ptime now = microsec_clock::universal_time();
+        basic_rate_control_.setRelativeDue(send_secs);
+        renew_rate_control_.setRelativeDue(renew_secs);
+        release_rate_control_.setRelativeDue(release_secs);
+
+    }
+
     using TestControl::checkExitConditions;
     using TestControl::checkExitConditions;
-    using TestControl::createRenew;
+    using TestControl::createMessageFromReply;
     using TestControl::factoryElapsedTime6;
     using TestControl::factoryElapsedTime6;
     using TestControl::factoryGeneric;
     using TestControl::factoryGeneric;
     using TestControl::factoryIana6;
     using TestControl::factoryIana6;
@@ -84,7 +108,7 @@ public:
     using TestControl::factoryRequestList4;
     using TestControl::factoryRequestList4;
     using TestControl::generateDuid;
     using TestControl::generateDuid;
     using TestControl::generateMacAddress;
     using TestControl::generateMacAddress;
-    using TestControl::getNextExchangesNum;
+    using TestControl::getCurrentTimeout;
     using TestControl::getTemplateBuffer;
     using TestControl::getTemplateBuffer;
     using TestControl::initPacketTemplates;
     using TestControl::initPacketTemplates;
     using TestControl::initializeStatsMgr;
     using TestControl::initializeStatsMgr;
@@ -92,13 +116,22 @@ public:
     using TestControl::processReceivedPacket4;
     using TestControl::processReceivedPacket4;
     using TestControl::processReceivedPacket6;
     using TestControl::processReceivedPacket6;
     using TestControl::registerOptionFactories;
     using TestControl::registerOptionFactories;
+    using TestControl::reset;
     using TestControl::sendDiscover4;
     using TestControl::sendDiscover4;
     using TestControl::sendPackets;
     using TestControl::sendPackets;
-    using TestControl::sendRenewPackets;
+    using TestControl::sendMultipleMessages6;
     using TestControl::sendRequest6;
     using TestControl::sendRequest6;
     using TestControl::sendSolicit6;
     using TestControl::sendSolicit6;
     using TestControl::setDefaults4;
     using TestControl::setDefaults4;
     using TestControl::setDefaults6;
     using TestControl::setDefaults6;
+    using TestControl::basic_rate_control_;
+    using TestControl::renew_rate_control_;
+    using TestControl::release_rate_control_;
+    using TestControl::last_report_;
+    using TestControl::transid_gen_;
+    using TestControl::macaddr_gen_;
+    using TestControl::first_packet_serverid_;
+    using TestControl::interrupted_;
 
 
     NakedTestControl() : TestControl() {
     NakedTestControl() : TestControl() {
         uint32_t clients_num = CommandOptions::instance().getClientsNum() == 0 ?
         uint32_t clients_num = CommandOptions::instance().getClientsNum() == 0 ?
@@ -626,6 +659,167 @@ public:
         }
         }
     }
     }
 
 
+    /// \brief Test that the DHCPv4 Release or Renew message is created
+    /// correctly and comprises expected options.
+    ///
+    /// \param msg_type A type of the message to be tested: DHCPV6_RELEASE
+    /// or DHCPV6_RENEW.
+    void testCreateRenewRelease(const uint16_t msg_type) {
+        // This command line specifies that the Release/Renew messages should
+        // be sent with the same rate as the Solicit messages.
+        std::ostringstream s;
+        s << "perfdhcp -6 -l lo -r 10 ";
+        s << (msg_type == DHCPV6_RELEASE ? "-F" : "-f") << " 10 ";
+        s << "-R 10 -L 10547 -n 10 -e address-and-prefix ::1";
+        ASSERT_NO_THROW(processCmdLine(s.str()));
+        // Create a test controller class.
+        NakedTestControl tc;
+        // Set the transaction id generator which will be used by the
+        // createRenew or createRelease function to generate transaction id.
+        boost::shared_ptr<NakedTestControl::IncrementalGenerator>
+            generator(new NakedTestControl::IncrementalGenerator());
+        tc.setTransidGenerator(generator);
+
+        // Create a Reply packet. The createRelease or createReply function will
+        // need Reply packet to create a corresponding Release or Reply.
+        Pkt6Ptr reply = createReplyPkt6(1);
+
+        Pkt6Ptr msg;
+        // Check that the message is created.
+        ASSERT_NO_THROW(msg = tc.createMessageFromReply(msg_type, reply));
+
+        ASSERT_TRUE(msg);
+        // Check that the message type and transaction id is correct.
+        EXPECT_EQ(msg_type, msg->getType());
+        EXPECT_EQ(1, msg->getTransid());
+
+        // Check that the message has expected options. These are the same for
+        // Release and Renew.
+
+        // Client Identifier.
+        OptionPtr opt_clientid = msg->getOption(D6O_CLIENTID);
+        ASSERT_TRUE(opt_clientid);
+        EXPECT_TRUE(reply->getOption(D6O_CLIENTID)->getData() ==
+                    opt_clientid->getData());
+
+        // Server identifier
+        OptionPtr opt_serverid = msg->getOption(D6O_SERVERID);
+        ASSERT_TRUE(opt_serverid);
+        EXPECT_TRUE(reply->getOption(D6O_SERVERID)->getData() ==
+                opt_serverid->getData());
+
+        // IA_NA
+        OptionPtr opt_ia_na = msg->getOption(D6O_IA_NA);
+        ASSERT_TRUE(opt_ia_na);
+        EXPECT_TRUE(reply->getOption(D6O_IA_NA)->getData() ==
+                    opt_ia_na->getData());
+
+        // IA_PD
+        OptionPtr opt_ia_pd = msg->getOption(D6O_IA_PD);
+        ASSERT_TRUE(opt_ia_pd);
+        EXPECT_TRUE(reply->getOption(D6O_IA_PD)->getData() ==
+                    opt_ia_pd->getData());
+
+        // Make sure that exception is thrown if the Reply message is NULL.
+        EXPECT_THROW(tc.createMessageFromReply(msg_type, Pkt6Ptr()),
+                     isc::BadValue);
+
+    }
+
+    /// \brief Test sending DHCPv6 Releases or Renews.
+    ///
+    /// This function simulates acquiring 10 leases from the server. Returned
+    /// Reply messages are cached and used to send Renew or Release messages.
+    /// The maxmimal number of Renew or Release messages which can be sent is
+    /// equal to the number of leases acquired (10). This function also checks
+    /// that an attempt to send more Renew or Release messages than the number
+    /// of leases acquired will fail.
+    ///
+    /// \param msg_type A type of the message which is simulated to be sent
+    /// (DHCPV6_RENEW or DHCPV6_RELEASE).
+    void testSendRenewRelease(const uint16_t msg_type) {
+        std::string loopback_iface(getLocalLoopback());
+        if (loopback_iface.empty()) {
+            std::cout << "Skipping the test because loopback interface could"
+                " not be detected" << std::endl;
+            return;
+        }
+        // Build a command line. Depending on the message type, we will use
+        // -f<renew-rate> or -F<release-rate> parameter.
+        std::ostringstream s;
+        s << "perfdhcp -6 -l " << loopback_iface << " -r 10 ";
+        s << (msg_type == DHCPV6_RENEW ? "-f" : "-F");
+        s << " 10 -R 10 -L 10547 -n 10 ::1";
+        ASSERT_NO_THROW(processCmdLine(s.str()));
+        // Create a test controller class.
+        NakedTestControl tc;
+        tc.initializeStatsMgr();
+        // Set the transaction id generator to sequential to control to
+        // guarantee that transaction ids are predictable.
+        boost::shared_ptr<NakedTestControl::IncrementalGenerator>
+            generator(new NakedTestControl::IncrementalGenerator());
+        tc.setTransidGenerator(generator);
+        // Socket has to be created so as we can actually send packets.
+        int sock_handle = 0;
+        ASSERT_NO_THROW(sock_handle = tc.openSocket());
+        TestControl::TestControlSocket sock(sock_handle);
+
+        // Send a number of Solicit messages. Each generated Solicit will be
+        // assigned a different transaction id, starting from 1 to 10.
+        tc.sendPackets(sock, 10);
+
+        // Simulate Advertise responses from the server. Each advertise is
+        // assigned a transaction id from the range of 1 to 10, so as they
+        // match the transaction ids from the Solicit messages.
+        for (int i = generator->getNext() - 10; i < generator->getNext(); ++i) {
+            Pkt6Ptr advertise(createAdvertisePkt6(i));
+            // If Advertise is matched with the Solicit the call below will
+            // trigger a corresponding Request. They will be assigned
+            // transaction ids from the range from 11 to 20 (the range of
+            // 1 to 10 has been used by Solicit-Advertise).
+            ASSERT_NO_THROW(tc.processReceivedPacket6(sock, advertise));
+    }
+
+        // Requests have been sent, so now let's simulate responses from the
+        // server. Generate corresponding Reply messages with the transaction
+        // ids from the range from 11 to 20.
+        for (int i = generator->getNext() - 10; i < generator->getNext(); ++i) {
+            Pkt6Ptr reply(createReplyPkt6(i));
+            // Each Reply packet corresponds to the new lease acquired. Since
+            // -f<renew-rate> option has been specified, received Reply
+            // messages are held so as Renew messages can be sent for
+            // existing leases.
+            ASSERT_NO_THROW(tc.processReceivedPacket6(sock, reply));
+        }
+
+        uint64_t msg_num;
+        // Try to send 5 messages. It should be successful because 10 Reply
+        // messages has been received. For each of them we should be able to
+        // send Renew or Release.
+        ASSERT_NO_THROW(
+            msg_num = tc.sendMultipleMessages6(sock, msg_type, 5)
+        );
+        // Make sure that we have sent 5 messages.
+        EXPECT_EQ(5, msg_num);
+
+        // Try to do it again. We should still have 5 Reply packets for
+        // which Renews or Releases haven't been sent yet.
+        ASSERT_NO_THROW(
+            msg_num = tc.sendMultipleMessages6(sock, msg_type, 5)
+        );
+        EXPECT_EQ(5, msg_num);
+
+        // We used all the Reply packets (we sent Renew or Release for each of
+        // them already). Therefore, no further Renew or Release messages should
+        // be sent before we acquire new leases.
+        ASSERT_NO_THROW(
+            msg_num = tc.sendMultipleMessages6(sock, msg_type, 5)
+        );
+        // Make sure that no message has been sent.
+        EXPECT_EQ(0, msg_num);
+
+    }
+
     /// \brief Parse command line string with CommandOptions.
     /// \brief Parse command line string with CommandOptions.
     ///
     ///
     /// \param cmdline command line string to be parsed.
     /// \param cmdline command line string to be parsed.
@@ -710,6 +904,25 @@ public:
 
 
 };
 };
 
 
+// This test verifies that the class members are reset to expected values.
+TEST_F(TestControlTest, reset) {
+    ASSERT_NO_THROW(processCmdLine("perfdhcp -6 -l ethx -r 50 -f 30 -F 10 -a 3 all"));
+    NakedTestControl tc;
+    tc.reset();
+    EXPECT_EQ(3, tc.basic_rate_control_.getAggressivity());
+    EXPECT_EQ(3, tc.renew_rate_control_.getAggressivity());
+    EXPECT_EQ(3, tc.release_rate_control_.getAggressivity());
+    EXPECT_EQ(50, tc.basic_rate_control_.getRate());
+    EXPECT_EQ(30, tc.renew_rate_control_.getRate());
+    EXPECT_EQ(10, tc.release_rate_control_.getRate());
+    EXPECT_FALSE(tc.last_report_.is_not_a_date_time());
+    EXPECT_FALSE(tc.transid_gen_);
+    EXPECT_FALSE(tc.macaddr_gen_);
+    EXPECT_TRUE(tc.first_packet_serverid_.empty());
+    EXPECT_FALSE(tc.interrupted_);
+
+}
+
 TEST_F(TestControlTest, GenerateDuid) {
 TEST_F(TestControlTest, GenerateDuid) {
     // Simple command line that simulates one client only. Always the
     // Simple command line that simulates one client only. Always the
     // same DUID will be generated.
     // same DUID will be generated.
@@ -1226,157 +1439,166 @@ TEST_F(TestControlTest, PacketTemplates) {
     EXPECT_THROW(tc.initPacketTemplates(), isc::BadValue);
     EXPECT_THROW(tc.initPacketTemplates(), isc::BadValue);
 }
 }
 
 
-TEST_F(TestControlTest, RateControl) {
-    // We don't specify the exchange rate here so the aggressivity
-    // value will determine how many packets are to be send each
-    // time we query the getNextExchangesNum.
-    ASSERT_NO_THROW(processCmdLine("perfdhcp -l 127.0.0.1 all"));
-    CommandOptions& options = CommandOptions::instance();
+TEST_F(TestControlTest, processRenew) {
+    testSendRenewRelease(DHCPV6_RENEW);
+}
 
 
-    NakedTestControl tc1;
-    uint64_t xchgs_num = tc1.getNextExchangesNum(microsec_clock::universal_time(),
-                                                 options.getRate());
-    EXPECT_EQ(options.getAggressivity(), xchgs_num);
+TEST_F(TestControlTest, processRelease) {
+    testSendRenewRelease(DHCPV6_RELEASE);
+}
 
 
-    // The exchange rate is now 1 per second. We don't know how many
-    // exchanges have to initiated exactly but for sure it has to be
-    // non-zero value. Also, since aggressivity is very high we expect
-    // that it will not be restricted by aggressivity.
-    ASSERT_NO_THROW(
-        processCmdLine("perfdhcp -l 127.0.0.1 -a 1000000 -r 1 all")
-    );
-    NakedTestControl tc2;
-    xchgs_num = tc2.getNextExchangesNum(microsec_clock::universal_time(),
-                                        options.getRate());
-    EXPECT_GT(xchgs_num, 0);
-    EXPECT_LT(xchgs_num, options.getAggressivity());
-    // @todo add more thorough checks for rate values.
+// This test verifies that the DHCPV6 Renew message is created correctly
+// and that it comprises all required options.
+TEST_F(TestControlTest, createRenew) {
+    testCreateRenewRelease(DHCPV6_RENEW);
 }
 }
 
 
-TEST_F(TestControlTest, processRenew) {
-    std::string loopback_iface(getLocalLoopback());
-    if (loopback_iface.empty()) {
-        std::cout << "Skipping the test because loopback interface could"
-            " not be detected" << std::endl;
-        return;
-    }
-    // This command line specifies that the Renew messages should be sent
-    // with the same rate as the Solicit messages.
-    ASSERT_NO_THROW(processCmdLine("perfdhcp -6 -l " + loopback_iface +
-                                   " -r 10 -f 10 -R 10 -L 10547 -n 10 ::1"));
-    // Create a test controller class.
+// This test verifies that the DHCPv6 Release message is created correctly
+// and that it comprises all required options.
+TEST_F(TestControlTest, createRelease) {
+    testCreateRenewRelease(DHCPV6_RELEASE);
+}
+
+// This test verifies that the current timeout value for waiting for
+// the server's responses is valid. The timeout value corresponds to the
+// time period between now and the next message to be sent from the
+// perfdhcp to a server.
+TEST_F(TestControlTest, getCurrentTimeout) {
+    // Process the command line: set the rate for Discovers to 10,
+    // and set Renew rate to 0 (-f flag absent).
+    ASSERT_NO_THROW(processCmdLine("perfdhcp -4 -l lo -r 10 ::1"));
     NakedTestControl tc;
     NakedTestControl tc;
-    tc.initializeStatsMgr();
-    // Set the transaction id generator to sequential to control to guarantee
-    // that transaction ids are predictable.
-    boost::shared_ptr<NakedTestControl::IncrementalGenerator>
-        generator(new NakedTestControl::IncrementalGenerator());
-    tc.setTransidGenerator(generator);
-    // Socket has to be created so as we can actually send packets.
-    int sock_handle = 0;
-    ASSERT_NO_THROW(sock_handle = tc.openSocket());
-    TestControl::TestControlSocket sock(sock_handle);
-
-    // Send a number of Solicit messages. Each generated Solicit will be
-    // assigned a different transaction id, starting from 1 to 10.
-    tc.sendPackets(sock, 10);
-
-    // Simulate Advertise responses from the server. Each advertise is assigned
-    // a transaction id from the range of 1 to 10, so as they match the
-    // transaction ids from the Solicit messages.
-    for (int i = generator->getNext() - 10; i < generator->getNext(); ++i) {
-        Pkt6Ptr advertise(createAdvertisePkt6(i));
-        // If Advertise is matched with the Solicit the call below will
-        // trigger a corresponding Request. They will be assigned
-        // transaction ids from the range from 11 to 20 (the range of
-        // 1 to 10 has been used by Solicit-Advertise).
-        ASSERT_NO_THROW(tc.processReceivedPacket6(sock, advertise));
-    }
+    // Make sure that the renew rate is 0.
+    ASSERT_EQ(0, CommandOptions::instance().getRenewRate());
+    // Simulate the case when we are already behind the due time for
+    // the next Discover to be sent.
+    tc.setRelativeDueTimes(-3);
+    // Expected timeout value is 0, which means that perfdhcp should
+    // not wait for server's response but rather send the next
+    // message to a server immediately.
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+    // Now, let's do set the due time to a value in the future. The returned
+    // timeout value should be somewhere between now and this time in the
+    // future. The value of ten seconds ahead should be safe and guarantee
+    // that the returned timeout value is non-zero, even though there is a
+    // delay between setting the send_due_ value and invoking the function.
+    tc.setRelativeDueTimes(10);
+    uint32_t timeout = tc.getCurrentTimeout();
+    EXPECT_GT(timeout, 0);
+    EXPECT_LE(timeout, 10000000);
+}
 
 
-    // Requests have been sent, so now let's simulate responses from the server.
-    // Generate corresponding Reply messages with the transaction ids from the
-    // range from 11 to 20.
-    for (int i = generator->getNext() - 10; i < generator->getNext(); ++i) {
-        Pkt6Ptr reply(createReplyPkt6(i));
-        // Each Reply packet corresponds to the new lease acquired. Since
-        // -f<renew-rate> option has been specified, received Reply
-        // messages are held so as Renew messages can be sent for
-        // existing leases.
-        ASSERT_NO_THROW(tc.processReceivedPacket6(sock, reply));
-    }
+// This test verifies that the current timeout value for waiting for the
+// server's responses is valid. In this case, we are simulating that perfdhcp
+// sends Renew requests to the server, apart from the regular 4-way exchanges.
+// The timeout value depends on both the due time to send next Solicit and the
+// due time to send Renew - the timeout should be ajusted to the due time that
+// occurs sooner.
+TEST_F(TestControlTest, getCurrentTimeoutRenew) {
+    // Set the Solicit rate to 10 and the Renew rate 5.
+    ASSERT_NO_THROW(processCmdLine("perfdhcp -6 -l lo -r 10 -f 5 ::1"));
+    NakedTestControl tc;
+
+    // Make sure, that the Renew rate has been set to 5.
+    ASSERT_EQ(5, CommandOptions::instance().getRenewRate());
+    // The send_due_ is in the past, the renew_due_ is in the future.
+    tc.setRelativeDueTimes(-3, 3);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    // Swap the due times from the previous check. The effect should be the
+    // same.
+    tc.setRelativeDueTimes(3, -3);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    // Set both due times to the future. The renew due time is to occur
+    // sooner. The timeout should be a value between now and the
+    // renew due time.
+    tc.setRelativeDueTimes(10, 5);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 5000000);
+
+    // Repeat the same check, but swap the due times.
+    tc.setRelativeDueTimes(5, 10);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 5000000);
 
 
-    uint64_t renew_num;
-    // Try to send 5 Renew packets. It should be successful because
-    // 10 Reply messages has been received. For each of them we should
-    // be able to send Renew.
-    ASSERT_NO_THROW(renew_num = tc.sendRenewPackets(sock, 5));
-    // Make sure that we have sent 5 packets.
-    EXPECT_EQ(5, renew_num);
-
-    // Try to do it again. We should still have 5 Reply packets for
-    // which Renews haven't been sent yet.
-    ASSERT_NO_THROW(renew_num = tc.sendRenewPackets(sock, 5));
-    EXPECT_EQ(5, renew_num);
-
-    // We used all the Reply packets (we sent Renew for each of them
-    // already). Therefore, no further Renew packets should be sent before
-    // We acquire new leases.
-    ASSERT_NO_THROW(renew_num = tc.sendRenewPackets(sock, 5));
-    // Make sure that no Renew has been sent.
-    EXPECT_EQ(0, renew_num);
 }
 }
 
 
-TEST_F(TestControlTest, createRenew) {
-    // This command line specifies that the Renew messages should be sent
-    // with the same rate as the Solicit messages.
-    ASSERT_NO_THROW(processCmdLine("perfdhcp -6 -l lo -r 10 -f 10 -R 10"
-                                   " -L 10547 -n 10 -e address-and-prefix"
-                                   " ::1"));
-    // Create a test controller class.
+// This test verifies that the current timeout value for waiting for the
+// server's responses is valid. In this case, we are simulating that perfdhcp
+// sends Release requests to the server, apart from the regular 4-way exchanges.
+TEST_F(TestControlTest, getCurrentTimeoutRelease) {
+    // Set the Solicit rate to 10 and the Release rate 5.
+    ASSERT_NO_THROW(processCmdLine("perfdhcp -6 -l lo -r 10 -F 5 ::1"));
     NakedTestControl tc;
     NakedTestControl tc;
-    // Set the transaction id generator because createRenew function requires
-    // it to generate the transaction id for the Renew packet.
-    boost::shared_ptr<NakedTestControl::IncrementalGenerator>
-        generator(new NakedTestControl::IncrementalGenerator());
-    tc.setTransidGenerator(generator);
-
-    // Create a Reply packet. The createRenew function will need Reply
-    // packet to create a corresponding Renew.
-    Pkt6Ptr reply = createReplyPkt6(1);
-    Pkt6Ptr renew;
-    // Check that Renew is created.
-    ASSERT_NO_THROW(renew = tc.createRenew(reply));
-    ASSERT_TRUE(renew);
-    EXPECT_EQ(DHCPV6_RENEW, renew->getType());
-    EXPECT_EQ(1, renew->getTransid());
-
-    // Now check that the Renew packet created, has expected options. The
-    // payload of these options should be the same as the payload of the
-    // options in the Reply.
-
-    // Client Identifier
-    OptionPtr opt_clientid = renew->getOption(D6O_CLIENTID);
-    ASSERT_TRUE(opt_clientid);
-    EXPECT_TRUE(reply->getOption(D6O_CLIENTID)->getData() ==
-                opt_clientid->getData());
-
-    // Server identifier
-    OptionPtr opt_serverid = renew->getOption(D6O_SERVERID);
-    ASSERT_TRUE(opt_serverid);
-    EXPECT_TRUE(reply->getOption(D6O_SERVERID)->getData() ==
-                opt_serverid->getData());
-
-    // IA_NA
-    OptionPtr opt_ia_na = renew->getOption(D6O_IA_NA);
-    ASSERT_TRUE(opt_ia_na);
-    EXPECT_TRUE(reply->getOption(D6O_IA_NA)->getData() ==
-                opt_ia_na->getData());
 
 
-    // IA_PD
-    OptionPtr opt_ia_pd = renew->getOption(D6O_IA_PD);
-    ASSERT_TRUE(opt_ia_pd);
-    EXPECT_TRUE(reply->getOption(D6O_IA_PD)->getData() ==
-                opt_ia_pd->getData());
+    // Make sure, that the Release rate has been set to 5.
+    ASSERT_EQ(5, CommandOptions::instance().getReleaseRate());
+    // The send_due_ is in the past, the renew_due_ is in the future.
+    tc.setRelativeDueTimes(-3, 0, 3);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    // Swap the due times from the previous check. The effect should be the
+    // same.
+    tc.setRelativeDueTimes(3, 0, -3);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    // Set both due times to the future. The renew due time is to occur
+    // sooner. The timeout should be a value between now and the
+    // release due time.
+    tc.setRelativeDueTimes(10, 0, 5);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 5000000);
+
+    // Repeat the same check, but swap the due times.
+    tc.setRelativeDueTimes(5, 0, 10);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 5000000);
 
 
 }
 }
 
 
+// This test verifies that the current timeout value for waiting for the
+// server's responses is valid. In this case, we are simulating that perfdhcp
+// sends both Renew and Release requests to the server, apart from the regular
+// 4-way exchanges.
+TEST_F(TestControlTest, getCurrentTimeoutRenewRelease) {
+    // Set the Solicit rate to 10 and, Renew rate to 5, Release rate to 3.
+    ASSERT_NO_THROW(processCmdLine("perfdhcp -6 -l lo -r 10 -f 5 -F 3 ::1"));
+    NakedTestControl tc;
+
+    // Make sure the Renew and Release rates has been set to a non-zero value.
+    ASSERT_EQ(5, CommandOptions::instance().getRenewRate());
+    ASSERT_EQ(3, CommandOptions::instance().getReleaseRate());
+
+    // If any of the due times is in the past, the timeout value should be 0,
+    // to indicate that the next message should be sent immediately.
+    tc.setRelativeDueTimes(-3, 3, 5);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    tc.setRelativeDueTimes(-3, 5, 3);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    tc.setRelativeDueTimes(3, -3, 5);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    tc.setRelativeDueTimes(3, 2, -5);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    tc.setRelativeDueTimes(-3, -2, -5);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    // If due times are in the future, the timeout value should be aligned to
+    // the due time which occurs the soonest.
+    tc.setRelativeDueTimes(10, 9, 8);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 8000000);
+
+    tc.setRelativeDueTimes(10, 8, 9);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 8000000);
+
+    tc.setRelativeDueTimes(5, 8, 9);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 5000000);
+
+}