Commit f4a2c50d authored by Michal Nowikowski's avatar Michal Nowikowski

perfdhcp avalanche: improvements after review

- CommandOptions is no longer a signleton - this makes testing easier
- added -i option taking into account in Avalanche scen (execute only DO exchange)
- fixed collecting stats in Avalanche scen
- improved TestControl tests, moved some of them to BasicScen tests
- made PerfSocket testable: it has a base class which is used for mocking in TestControl tests
- all references to another singleton, IfaceMgr wrapped into PerfSocket - this makes testing easier
- added unit tests for basic and avalanche scenarios, and perf socket
- added -Werror to prevent ignore warnings
- added more comments
parent 098ff98f
......@@ -3,6 +3,7 @@ SUBDIRS = . tests
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += -Werror
AM_CXXFLAGS = $(KEA_CXXFLAGS)
......
......@@ -20,15 +20,26 @@ namespace perfdhcp {
/// This class must be inherited by scenario classes.
class AbstractScen : public boost::noncopyable {
public:
/// \brief Default and the only constructor of AbstractScen.
///
/// \param options reference to command options,
/// \param socket reference to a socket.
AbstractScen(CommandOptions& options, BasePerfSocket &socket) :
options_(options),
tc_(options, socket) {};
/// \brief Run performance test.
///
/// Method runs whole performance test.
///
/// \return execution status.
virtual int run() = 0;
/// \brief Trivial virtual destructor.
virtual ~AbstractScen() {};
protected:
CommandOptions& options_;
TestControl tc_; ///< Object for controling sending and receiving packets.
};
......
......@@ -20,7 +20,6 @@ namespace perfdhcp {
int
AvalancheScen::resendPackets(ExchangeType xchg_type) {
CommandOptions& options = CommandOptions::instance();
const StatsMgr& stats_mgr(tc_.getStatsMgr());
// get list of sent packets that potentially need to be resent
......@@ -65,12 +64,12 @@ AvalancheScen::resendPackets(ExchangeType xchg_type) {
total_resent_++;
// do resend packet
if (options.getIpVersion() == 4) {
if (options_.getIpVersion() == 4) {
Pkt4Ptr pkt4 = boost::dynamic_pointer_cast<Pkt4>(pkt);
IfaceMgr::instance().send(pkt4);
socket_.send(pkt4);
} else {
Pkt6Ptr pkt6 = boost::dynamic_pointer_cast<Pkt6>(pkt);
IfaceMgr::instance().send(pkt6);
socket_.send(pkt6);
}
// restore sending time of original packet
......@@ -104,10 +103,8 @@ AvalancheScen::run() {
// and printed during runtime. The whole procedure is stopeed when
// all packets got reponses.
CommandOptions& options = CommandOptions::instance();
uint32_t clients_num = options.getClientsNum() == 0 ?
1 : options.getClientsNum();
uint32_t clients_num = options_.getClientsNum() == 0 ?
1 : options_.getClientsNum();
StatsMgr& stats_mgr(tc_.getStatsMgr());
......@@ -131,12 +128,16 @@ AvalancheScen::run() {
if (now - prev_cycle_time > milliseconds(200)) { // check if 0.2s elapsed
prev_cycle_time = now;
int still_left_cnt = 0;
if (options.getIpVersion() == 4) {
if (options_.getIpVersion() == 4) {
still_left_cnt += resendPackets(ExchangeType::DO);
still_left_cnt += resendPackets(ExchangeType::RA);
if (options_.getExchangeMode() == CommandOptions::DORA_SARR) {
still_left_cnt += resendPackets(ExchangeType::RA);
}
} else {
still_left_cnt += resendPackets(ExchangeType::SA);
still_left_cnt += resendPackets(ExchangeType::RR);
if (options_.getExchangeMode() == CommandOptions::DORA_SARR) {
still_left_cnt += resendPackets(ExchangeType::RR);
}
}
if (still_left_cnt == 0) {
......@@ -157,25 +158,44 @@ AvalancheScen::run() {
tc_.printStats();
// Print packet timestamps
if (testDiags('t')) {
if (options_.testDiags('t')) {
stats_mgr.printTimestamps();
}
// Print server id.
if (testDiags('s') && tc_.serverIdReceived()) {
if (options_.testDiags('s') && tc_.serverIdReceived()) {
std::cout << "Server id: " << tc_.getServerId() << std::endl;
}
// Diagnostics flag 'e' means show exit reason.
if (testDiags('e')) {
if (options_.testDiags('e')) {
std::cout << "Interrupted" << std::endl;
}
// Calculate total stats.
int total_sent_pkts = total_resent_;
int total_rcvd_pkts = 0;
if (options_.getIpVersion() == 4) {
total_sent_pkts += tc_.getStatsMgr().getSentPacketsNum(ExchangeType::DO);
total_rcvd_pkts += tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::DO);
if (options_.getExchangeMode() == CommandOptions::DORA_SARR) {
total_sent_pkts += tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RA);
total_rcvd_pkts += tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RA);
}
} else {
total_sent_pkts += tc_.getStatsMgr().getSentPacketsNum(ExchangeType::SA);
total_rcvd_pkts += tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::SA);
if (options_.getExchangeMode() == CommandOptions::DORA_SARR) {
total_sent_pkts += tc_.getStatsMgr().getSentPacketsNum(ExchangeType::RR);
total_rcvd_pkts += tc_.getStatsMgr().getRcvdPacketsNum(ExchangeType::RR);
}
}
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;
<< " clients. " << std::endl
<< "Requests sent + resent: " << total_sent_pkts << std::endl
<< "Requests resent: " << total_resent_ << std::endl
<< "Responses received: " << total_rcvd_pkts << std::endl;
return (0);
}
......
......@@ -24,16 +24,27 @@ namespace perfdhcp {
/// Full DORA and SARR message sequences are expected.
class AvalancheScen : public AbstractScen {
public:
/// Default and the only constructor of AvalancheScen.
AvalancheScen(): total_resent_(0) {};
/// \brief Default and the only constructor of AvalancheScen.
///
/// \param options reference to command options,
/// \param socket reference to a socket.
AvalancheScen(CommandOptions& options, BasePerfSocket &socket):
AbstractScen(options, socket),
socket_(socket),
total_resent_(0) {};
/// brief\ Run performance test.
///
/// Method runs whole performance test.
int run();
///
/// \return execution status.
int run() override;
private:
// A reference to socket;
BasePerfSocket &socket_;
/// A map xchg type -> (a map of trans id -> retransmissions count.
std::unordered_map<ExchangeType, std::unordered_map<uint32_t, int>> retransmissions_;
/// A map xchg type -> (a map of trans id -> time of sending first packet.
......@@ -42,8 +53,13 @@ private:
/// Total number of resent packets.
int total_resent_;
/// Resend packets for given exchange type that did not receive
/// \\brief Resend packets.
///
/// It resends packets for given exchange type that did not receive
/// a response yet.
///
/// \param xchg_type exchange type that should be looked for.
/// \return number of packets still waiting for resending.
int resendPackets(ExchangeType xchg_type);
};
......
......@@ -27,17 +27,16 @@ BasicScen::checkExitConditions() {
const StatsMgr& stats_mgr(tc_.getStatsMgr());
CommandOptions& options = CommandOptions::instance();
bool test_period_reached = false;
// Check if test period passed.
if (options.getPeriod() != 0) {
if (options_.getPeriod() != 0) {
time_period period(stats_mgr.getTestPeriod());
if (period.length().total_seconds() >= options.getPeriod()) {
if (period.length().total_seconds() >= options_.getPeriod()) {
test_period_reached = true;
}
}
if (test_period_reached) {
if (testDiags('e')) {
if (options_.testDiags('e')) {
std::cout << "reached test-period." << std::endl;
}
if (!tc_.waitToExit()) {
......@@ -47,35 +46,35 @@ BasicScen::checkExitConditions() {
bool max_requests = false;
// Check if we reached maximum number of DISCOVER/SOLICIT sent.
if (options.getNumRequests().size() > 0) {
if (options.getIpVersion() == 4) {
if (options_.getNumRequests().size() > 0) {
if (options_.getIpVersion() == 4) {
if (stats_mgr.getSentPacketsNum(ExchangeType::DO) >=
options.getNumRequests()[0]) {
options_.getNumRequests()[0]) {
max_requests = true;
}
} else if (options.getIpVersion() == 6) {
} else if (options_.getIpVersion() == 6) {
if (stats_mgr.getSentPacketsNum(ExchangeType::SA) >=
options.getNumRequests()[0]) {
options_.getNumRequests()[0]) {
max_requests = true;
}
}
}
// Check if we reached maximum number REQUEST packets.
if (options.getNumRequests().size() > 1) {
if (options.getIpVersion() == 4) {
if (options_.getNumRequests().size() > 1) {
if (options_.getIpVersion() == 4) {
if (stats_mgr.getSentPacketsNum(ExchangeType::RA) >=
options.getNumRequests()[1]) {
options_.getNumRequests()[1]) {
max_requests = true;
}
} else if (options.getIpVersion() == 6) {
} else if (options_.getIpVersion() == 6) {
if (stats_mgr.getSentPacketsNum(ExchangeType::RR) >=
options.getNumRequests()[1]) {
options_.getNumRequests()[1]) {
max_requests = true;
}
}
}
if (max_requests) {
if (testDiags('e')) {
if (options_.testDiags('e')) {
std::cout << "Reached max requests limit." << std::endl;
}
if (!tc_.waitToExit()) {
......@@ -85,35 +84,35 @@ BasicScen::checkExitConditions() {
// 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 (options_.getMaxDrop().size() > 0) {
if (options_.getIpVersion() == 4) {
if (stats_mgr.getDroppedPacketsNum(ExchangeType::DO) >=
options.getMaxDrop()[0]) {
options_.getMaxDrop()[0]) {
max_drops = true;
}
} else if (options.getIpVersion() == 6) {
} else if (options_.getIpVersion() == 6) {
if (stats_mgr.getDroppedPacketsNum(ExchangeType::SA) >=
options.getMaxDrop()[0]) {
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 (options_.getMaxDrop().size() > 1) {
if (options_.getIpVersion() == 4) {
if (stats_mgr.getDroppedPacketsNum(ExchangeType::RA) >=
options.getMaxDrop()[1]) {
options_.getMaxDrop()[1]) {
max_drops = true;
}
} else if (options.getIpVersion() == 6) {
} else if (options_.getIpVersion() == 6) {
if (stats_mgr.getDroppedPacketsNum(ExchangeType::RR) >=
options.getMaxDrop()[1]) {
options_.getMaxDrop()[1]) {
max_drops = true;
}
}
}
if (max_drops) {
if (testDiags('e')) {
if (options_.testDiags('e')) {
std::cout << "Reached maximum drops number." << std::endl;
}
if (!tc_.waitToExit()) {
......@@ -123,44 +122,44 @@ BasicScen::checkExitConditions() {
// 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 (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])) {
options_.getMaxDropPercentage()[0])) {
max_pdrops = true;
}
} else if (options.getIpVersion() == 6) {
} 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])) {
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 (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])) {
options_.getMaxDropPercentage()[1])) {
max_pdrops = true;
}
} else if (options.getIpVersion() == 6) {
} 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])) {
options_.getMaxDropPercentage()[1])) {
max_pdrops = true;
}
}
}
if (max_pdrops) {
if (testDiags('e')) {
if (options_.testDiags('e')) {
std::cout << "Reached maximum percentage of drops." << std::endl;
}
if (!tc_.waitToExit()) {
......@@ -172,21 +171,15 @@ BasicScen::checkExitConditions() {
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);
if (options_.getPreload() > 0) {
tc_.sendPackets(options_.getPreload(), true);
}
// Fork and run command specified with -w<wrapped-command>
if (!options.getWrapped().empty()) {
if (!options_.getWrapped().empty()) {
tc_.runWrapped();
}
......@@ -196,7 +189,7 @@ BasicScen::run() {
// Calculate number of packets to be sent to stay
// catch up with rate.
uint64_t packets_due = basic_rate_control_.getOutboundMessageCount();
if ((packets_due == 0) && testDiags('i')) {
if ((packets_due == 0) && options_.testDiags('i')) {
stats_mgr.incrementCounter("shortwait");
}
......@@ -207,7 +200,7 @@ BasicScen::run() {
// 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) {
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);
......@@ -225,12 +218,12 @@ BasicScen::run() {
// 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) {
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) {
if (options_.getIpVersion() == 4) {
tc_.sendMultipleRequests(renew_packets_due);
} else {
tc_.sendMultipleMessages6(DHCPV6_RENEW, renew_packets_due);
......@@ -239,7 +232,7 @@ BasicScen::run() {
// 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)) {
if ((options_.getIpVersion() == 6) && (options_.getReleaseRate() != 0)) {
uint64_t release_packets_due =
release_rate_control_.getOutboundMessageCount();
// Send Release messages.
......@@ -248,7 +241,7 @@ BasicScen::run() {
// Report delay means that user requested printing number
// of sent/received/dropped packets repeatedly.
if (options.getReportDelay() > 0) {
if (options_.getReportDelay() > 0) {
tc_.printIntermediateStats();
}
......@@ -265,28 +258,28 @@ BasicScen::run() {
tc_.printStats();
if (!options.getWrapped().empty()) {
if (!options_.getWrapped().empty()) {
// true means that we execute wrapped command with 'stop' argument.
tc_.runWrapped(true);
}
// Print packet timestamps
if (testDiags('t')) {
if (options_.testDiags('t')) {
stats_mgr.printTimestamps();
}
// Print server id.
if (testDiags('s') && tc_.serverIdReceived()) {
if (options_.testDiags('s') && tc_.serverIdReceived()) {
std::cout << "Server id: " << tc_.getServerId() << std::endl;
}
// Diagnostics flag 'e' means show exit reason.
if (testDiags('e')) {
if (options_.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')) {
if (options_.testDiags('T')) {
tc_.printTemplates();
}
......
......@@ -22,8 +22,17 @@ namespace perfdhcp {
/// is continuously loaded with DHCP messages according to given rate.
class BasicScen : public AbstractScen {
public:
/// Default and the only constructor of BasicScen.
BasicScen() {};
/// \brief Default and the only constructor of BasicScen.
///
/// \param options reference to command options,
/// \param socket reference to a socket.
BasicScen(CommandOptions& options, BasePerfSocket &socket):
AbstractScen(options, socket)
{
basic_rate_control_.setRate(options_.getRate());
renew_rate_control_.setRate(options_.getRenewRate());
release_rate_control_.setRate(options_.getReleaseRate());
};
/// brief\ Run performance test.
///
......@@ -33,11 +42,10 @@ public:
///
/// \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();
/// \return execution status.
int run() override;
private:
protected:
/// \brief A rate control class for Discover and Solicit messages.
RateControl basic_rate_control_;
/// \brief A rate control class for Renew messages.
......
......@@ -97,12 +97,6 @@ CommandOptions::LeaseType::toText() const {
}
}
CommandOptions&
CommandOptions::instance() {
static CommandOptions options;
return (options);
}
void
CommandOptions::reset() {
// Default mac address used in DHCP messages
......@@ -854,7 +848,7 @@ bool CommandOptions::decodeMacString(const std::string& line) {
}
void
CommandOptions::validate() const {
CommandOptions::validate() {
check((getIpVersion() != 4) && (isBroadcast() != 0),
"-B is not compatible with IPv6 (-6)");
check((getIpVersion() != 6) && (isRapidCommit() != 0),
......@@ -931,6 +925,19 @@ CommandOptions::validate() const {
<< "WARNING: Better results are achieved when run in multi-threaded mode." << std::endl
<< "WARNING: To switch use -g multi option." << std::endl;
}
if (scenario_ == Scenario::AVALANCHE) {
check(getClientsNum() <= 0,
"in case of avalanche scenario number\nof clients must be specified"
" using -R option explicitly");
// in case of AVALANCHE drops ie. long responses should not be observed by perfdhcp
double dt[2] = { 1000.0, 1000.0 };
drop_time_.assign(dt, dt + 2);
if (drop_time_set_) {
std::cout << "INFO: in avalanche scenario drop time is ignored" << std::endl;
}
}
}
void
......@@ -1265,15 +1272,6 @@ CommandOptions::version() const {
std::cout << "VERSION: " << VERSION << std::endl;
}
bool
testDiags(const char diag) {
std::string diags(CommandOptions::instance().getDiags());
if (diags.find(diag) != std::string::npos) {
return (true);
}
return (false);
}
} // namespace perfdhcp
} // namespace isc
......@@ -30,6 +30,14 @@ enum class Scenario {
class CommandOptions : public boost::noncopyable {
public:
/// \brief Default Constructor.
///
/// Private constructor as this is a singleton class.
/// Use CommandOptions::instance() to get instance of it.
CommandOptions() {
reset();
}
/// @brief A vector holding MAC addresses.
typedef std::vector<std::vector<uint8_t> > MacAddrsVector;
......@@ -111,12 +119,6 @@ public:
DORA_SARR
};
/// CommandOptions is a singleton class. This method returns reference
/// to its sole instance.
///
/// \return the only existing instance of command options
static CommandOptions& instance();
/// \brief Reset to defaults
///
/// Reset data members to default values. This is specifically
......@@ -363,6 +365,18 @@ public:
/// \return server name.
std::string getServerName() const { return server_name_; }
/// \brief Find if diagnostic flag has been set.
///
/// \param diag diagnostic flag (a,e,i,s,r,t,T).
/// \return true if diagnostics flag has been set.
bool testDiags(const char diag) {
if (getDiags().find(diag) != std::string::npos) {
return (true);
}
return (false);
}
/// \brief Print command line arguments.
void printCommandLine() const;
......@@ -377,15 +391,6 @@ public:
void version() const;
private:
/// \brief Default Constructor.
///
/// Private constructor as this is a singleton class.
/// Use CommandOptions::instance() to get instance of it.
CommandOptions() {
reset();
}
/// \brief Initializes class members based on the command line.
///
/// Reads each command line parameter and sets class member values.
......@@ -400,7 +405,7 @@ private:
/// \brief Validates initialized options.
///
/// \throws isc::InvalidParameter if command line validation fails.
void validate() const;
void validate();
/// \brief Throws !InvalidParameter exception if condition is true.
///
......@@ -669,13 +674,6 @@ private:
Scenario scenario_;