Commit ad43417a authored by Michal Nowikowski's avatar Michal Nowikowski

added avalanche scenario to perfdhcp

parent e7432d06
......@@ -23,10 +23,12 @@ libperfdhcp_la_SOURCES += perf_pkt4.cc perf_pkt4.h
libperfdhcp_la_SOURCES += packet_storage.h
libperfdhcp_la_SOURCES += pkt_transform.cc pkt_transform.h
libperfdhcp_la_SOURCES += rate_control.cc rate_control.h
libperfdhcp_la_SOURCES += stats_mgr.h
libperfdhcp_la_SOURCES += stats_mgr.cc stats_mgr.h
libperfdhcp_la_SOURCES += test_control.cc test_control.h
libperfdhcp_la_SOURCES += receiver.cc receiver.h
libperfdhcp_la_SOURCES += perf_socket.cc perf_socket.h
libperfdhcp_la_SOURCES += avalanche_scen.cc avalanche_scen.h
libperfdhcp_la_SOURCES += basic_scen.cc basic_scen.h
sbin_PROGRAMS = perfdhcp
perfdhcp_SOURCES = main.cc
......
// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <perfdhcp/avalanche_scen.h>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace std;
using namespace boost::posix_time;
using namespace isc;
using namespace isc::dhcp;
namespace isc {
namespace perfdhcp {
int
AvalancheScen::resendPackets(ExchangeType xchg_type) {
CommandOptions& options = CommandOptions::instance();
const StatsMgr& stats_mgr(tc_.getStatsMgr());
auto sent_packets_its = stats_mgr.getSentPackets(xchg_type);
auto begin_it = std::get<0>(sent_packets_its);
auto end_it = std::get<1>(sent_packets_its);
auto& retrans = retransmissions_[xchg_type];
int still_left_cnt = 0;
int resent_cnt = 0;
for (auto it = begin_it; it != end_it; ++it) {
still_left_cnt++;
dhcp::PktPtr pkt = *it;
auto trans_id = pkt->getTransid();
int rx_times = 0;
auto r_it = retrans.find(trans_id);
if (r_it != retrans.end()) {
rx_times = (*r_it).second;
}
int delay = (1 << rx_times); // in seconds
if (delay > 64) {
delay = 64;
}
delay *= 1000; // to miliseconds
delay += random() % 2000 - 1000; // adjust by random from -1000..1000 range
auto now = microsec_clock::universal_time();
if (now - pkt->getTimestamp() > milliseconds(delay)) {
//if (rx_times > 2) {
// std::cout << xchg_type << " RX " << trans_id << ", times " << rx_times << ", delay " << delay << std::endl;
//}
resent_cnt++;
total_resent_++;
if (options.getIpVersion() == 4) {
Pkt4Ptr pkt4 = boost::dynamic_pointer_cast<Pkt4>(pkt);
IfaceMgr::instance().send(pkt4);
} else {
Pkt6Ptr pkt6 = boost::dynamic_pointer_cast<Pkt6>(pkt);
IfaceMgr::instance().send(pkt6);
}
//std::cout << "resent " << xchg_type << " " << pkt->getTransid() << " @ " << pkt->getTimestamp() << std::endl;
rx_times++;
retrans[trans_id] = rx_times;
}
}
if (resent_cnt > 0) {
auto now = microsec_clock::universal_time();
std::cout << now << " " << xchg_type << ": still waiting for " << still_left_cnt << " answers, resent " << resent_cnt << ", retrying " << retrans.size() << std::endl;
}
return still_left_cnt;
}
int
AvalancheScen::run() {
CommandOptions& options = CommandOptions::instance();
uint32_t clients_num = options.getClientsNum() == 0 ?
1 : options.getClientsNum();
// StatsMgr& stats_mgr(tc_.getStatsMgr());
tc_.start();
auto start = microsec_clock::universal_time();
// Initiate new DHCP packet exchanges.
tc_.sendPackets(clients_num);
auto now = microsec_clock::universal_time();
auto prev_cycle_time = now;
for (;;) {
// Pull some packets from receiver thread, process them, update some stats
// and respond to the server if needed.
tc_.consumeReceivedPackets();
usleep(100);
now = microsec_clock::universal_time();
if (now - prev_cycle_time > milliseconds(200)) { // check if 0.2s elapsed
prev_cycle_time = now;
auto still_left_cnt_do = resendPackets(ExchangeType::DO);
auto still_left_cnt_ra = resendPackets(ExchangeType::RA);
if (still_left_cnt_do + still_left_cnt_ra == 0) {
break;
}
}
// If we are sending Renews to the server, the Reply packets are cached
// so as leases for which we send Renews can be identified. The major
// issue with this approach is that most of the time we are caching
// more packets than we actually need. This function removes excessive
// Reply messages to reduce the memory and CPU utilization. Note that
// searches in the long list of Reply packets increases CPU utilization.
//tc_.cleanCachedPackets();
}
auto stop = microsec_clock::universal_time();
boost::posix_time::time_period duration(start, stop);
tc_.stop();
tc_.printStats();
// // Print packet timestamps
// if (testDiags('t')) {
// stats_mgr.printTimestamps();
// }
// Print server id.
if (testDiags('s') && tc_.serverIdReceived()) {
std::cout << "Server id: " << tc_.getServerId() << std::endl;
}
// Diagnostics flag 'e' means show exit reason.
if (testDiags('e')) {
std::cout << "Interrupted" << std::endl;
}
std::cout << "It took " << duration.length() << " to provision " << clients_num
<< " clients. " << (clients_num * 2 + total_resent_)
<< " packets were sent, " << total_resent_
<< " retransmissions needed, received " << (clients_num * 2)
<< " responses." << std::endl;
int ret_code = 0;
// // Check if any packet drops occurred.
// ret_code = stats_mgr.droppedPackets() ? 3 : 0;
return (ret_code);
}
}
}
// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifndef AVALANCHE_SCEN_H
#define AVALANCHE_SCEN_H
#include <config.h>
#include <perfdhcp/test_control.h>
namespace isc {
namespace perfdhcp {
class AvalancheScen : public boost::noncopyable {
public:
AvalancheScen(): tc_(true), total_resent_(0) {};
/// brief\ Run performance test.
///
/// Method runs whole performance test. Command line options must
/// be parsed prior to running this function. Otherwise function will
/// throw exception.
///
/// \throw isc::InvalidOperation if command line options are not parsed.
/// \throw isc::Unexpected if internal Test Controller error occurred.
/// \return error_code, 3 if number of received packets is not equal
/// to number of sent packets, 0 if everything is ok.
int run();
private:
TestControl tc_;
std::unordered_map<ExchangeType, std::unordered_map<uint32_t, int>> retransmissions_;
int total_resent_;
int resendPackets(ExchangeType xchg_type);
};
}
}
#endif // AVALANCHE_SCEN_H
// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <perfdhcp/basic_scen.h>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace std;
using namespace boost::posix_time;
using namespace isc;
using namespace isc::dhcp;
namespace isc {
namespace perfdhcp {
bool
BasicScen::checkExitConditions() {
if (tc_.interrupted()) {
return (true);
}
const StatsMgr& stats_mgr(tc_.getStatsMgr());
CommandOptions& options = CommandOptions::instance();
bool test_period_reached = false;
// Check if test period passed.
if (options.getPeriod() != 0) {
time_period period(stats_mgr.getTestPeriod());
if (period.length().total_seconds() >= options.getPeriod()) {
test_period_reached = true;
}
}
if (test_period_reached) {
if (testDiags('e')) {
std::cout << "reached test-period." << std::endl;
}
if (!tc_.waitToExit()) {
return true;
}
}
bool max_requests = false;
// Check if we reached maximum number of DISCOVER/SOLICIT sent.
if (options.getNumRequests().size() > 0) {
if (options.getIpVersion() == 4) {
if (stats_mgr.getSentPacketsNum(ExchangeType::DO) >=
options.getNumRequests()[0]) {
max_requests = true;
}
} else if (options.getIpVersion() == 6) {
if (stats_mgr.getSentPacketsNum(ExchangeType::SA) >=
options.getNumRequests()[0]) {
max_requests = true;
}
}
}
// Check if we reached maximum number REQUEST packets.
if (options.getNumRequests().size() > 1) {
if (options.getIpVersion() == 4) {
if (stats_mgr.getSentPacketsNum(ExchangeType::RA) >=
options.getNumRequests()[1]) {
max_requests = true;
}
} else if (options.getIpVersion() == 6) {
if (stats_mgr.getSentPacketsNum(ExchangeType::RR) >=
options.getNumRequests()[1]) {
max_requests = true;
}
}
}
if (max_requests) {
if (testDiags('e')) {
std::cout << "Reached max requests limit." << std::endl;
}
if (!tc_.waitToExit()) {
return true;
}
}
// Check if we reached maximum number of drops of OFFER/ADVERTISE packets.
bool max_drops = false;
if (options.getMaxDrop().size() > 0) {
if (options.getIpVersion() == 4) {
if (stats_mgr.getDroppedPacketsNum(ExchangeType::DO) >=
options.getMaxDrop()[0]) {
max_drops = true;
}
} else if (options.getIpVersion() == 6) {
if (stats_mgr.getDroppedPacketsNum(ExchangeType::SA) >=
options.getMaxDrop()[0]) {
max_drops = true;
}
}
}
// Check if we reached maximum number of drops of ACK/REPLY packets.
if (options.getMaxDrop().size() > 1) {
if (options.getIpVersion() == 4) {
if (stats_mgr.getDroppedPacketsNum(ExchangeType::RA) >=
options.getMaxDrop()[1]) {
max_drops = true;
}
} else if (options.getIpVersion() == 6) {
if (stats_mgr.getDroppedPacketsNum(ExchangeType::RR) >=
options.getMaxDrop()[1]) {
max_drops = true;
}
}
}
if (max_drops) {
if (testDiags('e')) {
std::cout << "Reached maximum drops number." << std::endl;
}
if (!tc_.waitToExit()) {
return true;
}
}
// Check if we reached maximum drops percentage of OFFER/ADVERTISE packets.
bool max_pdrops = false;
if (options.getMaxDropPercentage().size() > 0) {
if (options.getIpVersion() == 4) {
if ((stats_mgr.getSentPacketsNum(ExchangeType::DO) > 10) &&
((100. * stats_mgr.getDroppedPacketsNum(ExchangeType::DO) /
stats_mgr.getSentPacketsNum(ExchangeType::DO)) >=
options.getMaxDropPercentage()[0])) {
max_pdrops = true;
}
} else if (options.getIpVersion() == 6) {
if ((stats_mgr.getSentPacketsNum(ExchangeType::SA) > 10) &&
((100. * stats_mgr.getDroppedPacketsNum(ExchangeType::SA) /
stats_mgr.getSentPacketsNum(ExchangeType::SA)) >=
options.getMaxDropPercentage()[0])) {
max_pdrops = true;
}
}
}
// Check if we reached maximum drops percentage of ACK/REPLY packets.
if (options.getMaxDropPercentage().size() > 1) {
if (options.getIpVersion() == 4) {
if ((stats_mgr.getSentPacketsNum(ExchangeType::RA) > 10) &&
((100. * stats_mgr.getDroppedPacketsNum(ExchangeType::RA) /
stats_mgr.getSentPacketsNum(ExchangeType::RA)) >=
options.getMaxDropPercentage()[1])) {
max_pdrops = true;
}
} else if (options.getIpVersion() == 6) {
if ((stats_mgr.getSentPacketsNum(ExchangeType::RR) > 10) &&
((100. * stats_mgr.getDroppedPacketsNum(ExchangeType::RR) /
stats_mgr.getSentPacketsNum(ExchangeType::RR)) >=
options.getMaxDropPercentage()[1])) {
max_pdrops = true;
}
}
}
if (max_pdrops) {
if (testDiags('e')) {
std::cout << "Reached maximum percentage of drops." << std::endl;
}
if (!tc_.waitToExit()) {
return true;
}
}
return (false);
}
int
BasicScen::run() {
CommandOptions& options = CommandOptions::instance();
basic_rate_control_.setRate(options.getRate());
renew_rate_control_.setRate(options.getRenewRate());
release_rate_control_.setRate(options.getReleaseRate());
StatsMgr& stats_mgr(tc_.getStatsMgr());
// Preload server with the number of packets.
if (options.getPreload() > 0) {
tc_.sendPackets(options.getPreload(), true);
}
// Fork and run command specified with -w<wrapped-command>
if (!options.getWrapped().empty()) {
tc_.runWrapped();
}
tc_.start();
for (;;) {
// Calculate number of packets to be sent to stay
// catch up with rate.
uint64_t packets_due = basic_rate_control_.getOutboundMessageCount();
if ((packets_due == 0) && testDiags('i')) {
stats_mgr.incrementCounter("shortwait");
}
// Pull some packets from receiver thread, process them, update some stats
// and respond to the server if needed.
auto pkt_count = tc_.consumeReceivedPackets();
// If there is nothing to do in this loop iteration then do some sleep to make
// CPU idle for a moment, to not consume 100% CPU all the time
// but only if it is not that high request rate expected.
if (options.getRate() < 10000 && packets_due == 0 && pkt_count == 0) {
/// @todo: need to implement adaptive time here, so the sleep time
/// is not fixed, but adjusts to current situation.
usleep(1);
}
// If test period finished, maximum number of packet drops
// has been reached or test has been interrupted we have to
// finish the test.
if (checkExitConditions()) {
break;
}
// Initiate new DHCP packet exchanges.
tc_.sendPackets(packets_due);
// If -f<renew-rate> option was specified we have to check how many
// Renew packets should be sent to catch up with a desired rate.
if (options.getRenewRate() != 0) {
uint64_t renew_packets_due =
renew_rate_control_.getOutboundMessageCount();
// Send multiple renews to satisfy the desired rate.
if (options.getIpVersion() == 4) {
tc_.sendMultipleRequests(renew_packets_due);
} else {
tc_.sendMultipleMessages6(DHCPV6_RENEW, renew_packets_due);
}
}
// If -F<release-rate> option was specified we have to check how many
// Release messages should be sent to catch up with a desired rate.
if ((options.getIpVersion() == 6) && (options.getReleaseRate() != 0)) {
uint64_t release_packets_due =
release_rate_control_.getOutboundMessageCount();
// Send Release messages.
tc_.sendMultipleMessages6(DHCPV6_RELEASE, release_packets_due);
}
// Report delay means that user requested printing number
// of sent/received/dropped packets repeatedly.
if (options.getReportDelay() > 0) {
tc_.printIntermediateStats();
}
// If we are sending Renews to the server, the Reply packets are cached
// so as leases for which we send Renews can be identified. The major
// issue with this approach is that most of the time we are caching
// more packets than we actually need. This function removes excessive
// Reply messages to reduce the memory and CPU utilization. Note that
// searches in the long list of Reply packets increases CPU utilization.
tc_.cleanCachedPackets();
}
tc_.stop();
tc_.printStats();
if (!options.getWrapped().empty()) {
// true means that we execute wrapped command with 'stop' argument.
tc_.runWrapped(true);
}
// Print packet timestamps
if (testDiags('t')) {
stats_mgr.printTimestamps();
}
// Print server id.
if (testDiags('s') && tc_.serverIdReceived()) {
std::cout << "Server id: " << tc_.getServerId() << std::endl;
}
// Diagnostics flag 'e' means show exit reason.
if (testDiags('e')) {
std::cout << "Interrupted" << std::endl;
}
// Print packet templates. Even if -T options have not been specified the
// dynamically build packet will be printed if at least one has been sent.
if (testDiags('T')) {
tc_.printTemplates();
}
int ret_code = 0;
// Check if any packet drops occurred.
ret_code = stats_mgr.droppedPackets() ? 3 : 0;
return (ret_code);
}
}
}
// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifndef BASIC_SCEN_H
#define BASIC_SCEN_H
#include <config.h>
#include <perfdhcp/test_control.h>
namespace isc {
namespace perfdhcp {
class BasicScen : public boost::noncopyable {
public:
BasicScen() : tc_(false) {};
/// \brief Check if test exit conditions fulfilled.
///
/// Method checks if the test exit conditions are fulfilled.
/// Exit conditions are checked periodically from the
/// main loop. Program should break the main loop when
/// this method returns true. It is calling function
/// responsibility to break main loop gracefully and
/// cleanup after test execution.
///
/// \return true if any of the exit conditions is fulfilled.
bool checkExitConditions();
/// brief\ Run performance test.
///
/// Method runs whole performance test. Command line options must
/// be parsed prior to running this function. Otherwise function will
/// throw exception.
///
/// \throw isc::InvalidOperation if command line options are not parsed.
/// \throw isc::Unexpected if internal Test Controller error occurred.
/// \return error_code, 3 if number of received packets is not equal
/// to number of sent packets, 0 if everything is ok.
int run();
private:
TestControl tc_;
/// \brief A rate control class for Discover and Solicit messages.
RateControl basic_rate_control_;
/// \brief A rate control class for Renew messages.
RateControl renew_rate_control_;
/// \brief A rate control class for Release messages.
RateControl release_rate_control_;
int resendPackets(ExchangeType xchg_type);
};
}
}
#endif // BASIC_SCEN_H
......@@ -24,6 +24,7 @@
#include <unistd.h>
#include <fstream>
#include <thread>
#include <getopt.h>
#ifdef HAVE_OPTRESET
extern int optreset;
......@@ -161,6 +162,7 @@ CommandOptions::reset() {
} else {
single_thread_mode_ = false;
}
scenario_ = Scenario::BASIC;
}
bool
......@@ -209,6 +211,8 @@ CommandOptions::parse(int argc, char** const argv, bool print_cmd_line) {
return (help_or_version_mode);
}
const int LONG_OPT_SCENARIO = 300;
bool
CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
int opt = 0; // Subsequent options returned by getopt()
......@@ -225,10 +229,17 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
stream << "perfdhcp";
int num_mac_list_files = 0;
struct option long_options[] = {
{"scenario", required_argument, 0, LONG_OPT_SCENARIO},
{0, 0, 0, 0}
};
// In this section we collect argument values from command line
// they will be tuned and validated elsewhere
while((opt = getopt(argc, argv, "hv46A:r:t:R:b:n:p:d:D:l:P:a:L:N:M:"
"s:iBc1T:X:O:o:E:S:I:x:W:w:e:f:F:g:")) != -1) {
while((opt = getopt_long(argc, argv,
"hv46A:r:t:R:b:n:p:d:D:l:P:a:L:N:M:s:iBc1"
"T:X:O:o:E:S:I:x:W:w:e:f:F:g:",
long_options, NULL)) != -1) {
stream << " -" << static_cast<char>(opt);
if (optarg) {
stream << " " << optarg;
......@@ -544,6 +555,17 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
xid_offset_.push_back(offset_arg);
break;
case LONG_OPT_SCENARIO: {
auto optarg_text = std::string(optarg);
if (optarg_text == "basic") {
scenario_ = Scenario::BASIC;