Commit 5d2116c1 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[1958] Provided access to all basic counters and created corresponding unit tests.

parent df4d0be8
......@@ -85,7 +85,9 @@ public:
boost::multi_index::sequenced<>,
boost::multi_index::hashed_non_unique<
boost::multi_index::global_fun<
boost::shared_ptr<T>, uint32_t, &ExchangeStats::transid_hash
boost::shared_ptr<T>,
uint32_t,
&ExchangeStats::transid_hash
>
>
>
......@@ -111,7 +113,9 @@ public:
square_sum_delay_(0.),
ordered_lookups_(0),
unordered_lookup_size_sum_(0),
unordered_lookups_(0) {
unordered_lookups_(0),
sent_packets_num_(0),
rcvd_packets_num_(0) {
sent_packets_cache_ = sent_packets_.begin();
}
......@@ -121,6 +125,7 @@ public:
///
/// \param packet packet object to be added.
void appendSent(const boost::shared_ptr<T> packet) {
++sent_packets_num_;
sent_packets_.template get<0>().push_back(packet);
}
......@@ -130,9 +135,55 @@ public:
///
/// \param packet packet object to be added.
void appendRcvd(const boost::shared_ptr<T> packet) {
++rcvd_packets_num_;
rcvd_packets_.template get<0>().push_back(packet);
}
/// \brief Update delay counters.
///
/// Method updates delay counters based on timestamps of
/// sent and received packets.
///
/// \param sent_packet sent packet
/// \param rcvd_packet received packet
/// \throw isc::Unexpected if failed to calculate timestamps
void updateDelays(const boost::shared_ptr<T> sent_packet,
const boost::shared_ptr<T> rcvd_packet) {
boost::posix_time::ptime sent_time = sent_packet->getTimestamp();
boost::posix_time::ptime rcvd_time = rcvd_packet->getTimestamp();
if (sent_time.is_not_a_date_time() ||
rcvd_time.is_not_a_date_time()) {
isc_throw(Unexpected,
"Timestamp must be set for sent and "
"received packet to measure RTT");
}
boost::posix_time::time_period period(sent_time, rcvd_time);
// We don't bother calculating deltas in nanoseconds. It is much
// more convenient to use seconds instead because we are going to
// sum them up.
double delta =
static_cast<double>(period.length().total_nanoseconds()) / 1e9;
if (delta < 0) {
isc_throw(Unexpected, "Sent packet's timestamp must not be "
"greater than received packet's timestamp");
}
// Record the minimum delay between sent and received packets.
if (delta < min_delay_) {
min_delay_ = delta;
}
// Record the maximum delay between sent and received packets.
if (delta > max_delay_) {
max_delay_ = delta;
}
// Update delay sum and square sum. That will be used to calculate
// mean delays.
sum_delay_ += delta;
square_sum_delay_ += delta * delta;
}
/// \brief Find packet on the list of sent packets.
///
/// Method finds packet with specified transaction id on the list
......@@ -149,21 +200,54 @@ public:
/// not found
boost::shared_ptr<T> findSent(const uint32_t transid) {
if (sent_packets_.size() == 0) {
// List of sent packets is empty so there is no sense
// to continue looking fo the packet. It also means
// that the received packet we got has no corresponding
// sent packet so orphans counter has to be updated.
++orphans_;
return boost::shared_ptr<T>();
} else if (sent_packets_cache_ == sent_packets_.end()) {
// Even if there are still many unmatched packets on the
// list we might hit the end of it because of unordered
// lookups. The next logical step is to reset cache.
sent_packets_cache_ = sent_packets_.begin();
}
// With this variable we will be signalling success or failure
// to find the packet.
bool packet_found = false;
// Most likely responses are sent from the server in the same
// order as client's requests to the server. We are caching
// next sent packet and first try to match with it the next
// incoming packet. We are successful if there is no
// packet drop or out of order packets sent. This is actually
// the fastest way to look for packets.
if ((*sent_packets_cache_)->getTransid() == transid) {
++ordered_lookups_;
packet_found = true;
} else {
// If we are here, it means that we were unable to match the
// next incoming packet with next sent packet so we need to
// take a little more expensive approach to look packets using
// alternative index (transaction id & 1023).
PktListTransidIndex& idx = sent_packets_.template get<1>();
// Packets are grouped using trasaction id masking with value
// of 1023. For instance, packets with transaction id equal to
// 1, 1024 ... will belong to the same group (a.k.a. bucket).
// When using alternative index we don't find the packet but
// bucket of packets and need to iterate through the bucket
// to find the one that has desired transaction id.
std::pair<PktListTransidIterator,PktListTransidIterator> p =
idx.equal_range(transid & 1023);
// We want to keep statistics of unordered lookups to make
// sure that there is a right balance before number of
// unordered lookups and ordered lookups. If number of unordered
// lookups is high it may mean that many packets are lost or
// sent out of order.
++unordered_lookups_;
// We also want to keep the mean value of the bucket. The lower
// bucket size the better. If bucket sizes appear to big we
// might want to increase number of buckets.
unordered_lookup_size_sum_ += std::distance(p.first, p.second);
for (PktListTransidIterator it = p.first; it != p.second;
++it) {
......@@ -177,53 +261,20 @@ public:
}
if (!packet_found) {
// If we are here, it means that both ordered lookup and
// unordered lookup failed. Searched packet is not on the list.
++orphans_;
return boost::shared_ptr<T>();
}
boost::shared_ptr<T> sent_packet(*sent_packets_cache_);
++sent_packets_cache_;
// If packet was found, we assume it will be never searched
// again. We want to delete this packet from the list to
// improve performance of future searches.
sent_packets_cache_ = eraseSent(sent_packets_cache_);
return sent_packet;
}
/// \brief Update delay counters.
///
/// Method updates delay counters based on timestamps of
/// sent and received packets.
///
/// \param sent_packet sent packet
/// \param rcvd_packet received packet
/// \throw isc::Unexpected if failed to calculate timestamps
void updateDelays(const boost::shared_ptr<T> sent_packet,
const boost::shared_ptr<T> rcvd_packet) {
boost::posix_time::ptime sent_time = sent_packet->getTimestamp();
boost::posix_time::ptime rcvd_time = rcvd_packet->getTimestamp();
if (sent_time.is_not_a_date_time() ||
rcvd_time.is_not_a_date_time()) {
isc_throw(Unexpected,
"Timestamp must be set for sent and "
"received packet to measure RTT");
}
boost::posix_time::time_period period(sent_time, rcvd_time);
double delta =
static_cast<double>(period.length().total_nanoseconds()) / 1e9;
if (delta < 0) {
isc_throw(Unexpected, "Sent packet's timestamp must not be "
"greater than received packet's timestamp");
}
if (delta < min_delay_) {
min_delay_ = delta;
}
if (delta > max_delay_) {
max_delay_ = delta;
}
sum_delay_ += delta;
square_sum_delay_ += delta * delta;
}
/// \brief Return minumum delay between sent and received packet.
///
/// Method returns minimum delay between sent and received packet.
......@@ -271,6 +322,9 @@ public:
///
/// \return average unordered lookup set size.
double getAvgUnorderedLookupSetSize() const {
if (unordered_lookups_ == 0) {
return 0.;
}
return static_cast<double>(unordered_lookup_size_sum_) /
static_cast<double>(unordered_lookups_);
}
......@@ -285,7 +339,6 @@ public:
/// \return number of unordered lookups.
uint64_t getUnorderedLookups() const { return unordered_lookups_; }
/// \brief Return number of ordered sent packets lookups
///
/// Method returns number of ordered sent packet lookups.
......@@ -296,6 +349,25 @@ public:
///
/// \return number of ordered lookups.
uint64_t getOrderedLookups() const { return ordered_lookups_; }
/// \brief Return total number of sent packets
///
/// Method returns total number of sent packets.
///
/// \return number of sent packets.
uint64_t getSentPacketsNum() const {
return sent_packets_num_;
}
/// \brief Return total number of received packets
///
/// Method returns total number of received packets.
///
/// \return number of received packets.
uint64_t getRcvdPacketsNum() const {
return rcvd_packets_num_;
}
private:
/// \brief Private default constructor.
......@@ -304,6 +376,18 @@ public:
/// class to specify exchange type explicitely.
ExchangeStats();
/// \brief Erase packet from the list of sent packets.
///
/// Method erases packet from the list of sent packets.
///
/// \param it iterator pointing to packet to be erased.
/// \return iterator pointing to packet following erased
/// packet or sent_packets_.end() if packet not found.
PktListIterator eraseSent(const PktListIterator it) {
return sent_packets_.template get<0>().erase(it);
}
ExchangeType xchg_type_; ///< Packet exchange type.
PktList sent_packets_; ///< List of sent packets.
......@@ -337,6 +421,8 @@ public:
uint64_t ordered_lookups_; ///< Number of ordered sent packets
///< lookups.
uint64_t sent_packets_num_; ///< Total number of sent packets.
uint64_t rcvd_packets_num_; ///< Total number of received packets.
};
/// Pointer to ExchangeStats.
......@@ -400,6 +486,59 @@ public:
}
}
/// \brief Return minumum delay between sent and received packet.
///
/// Method returns minimum delay between sent and received packet
/// for specified exchange type.
///
/// \param xchg_type exchange type.
/// \throw isc::BadValue if invalid exchange type specified.
/// \return minimum delay between packets.
double getMinDelay(const ExchangeType xchg_type) const {
ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
return xchg_stats->getMinDelay();
}
/// \brief Return maxmimum delay between sent and received packet.
///
/// Method returns maximum delay between sent and received packet
/// for specified exchange type.
///
/// \param xchg_type exchange type.
/// \throw isc::BadValue if invalid exchange type specified.
/// \return maximum delay between packets.
double getMaxDelay(const ExchangeType xchg_type) const {
ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
return xchg_stats->getMaxDelay();
}
/// \brief Return sum of delays between sent and received packets.
///
/// Method returns sum of delays between sent and received packets
/// for specified exchange type.
///
/// \param xchg_type exchange type.
/// \throw isc::BadValue if invalid exchange type specified.
/// \return sum of delays between sent and received packets.
double getSumDelay(const ExchangeType xchg_type) const {
ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
return xchg_stats->getSumDelay();
}
/// \brief Return square sum of delays between sent and received
/// packets.
///
/// Method returns square sum of delays between sent and received
/// packets for specified exchange type.
///
/// \param xchg_type exchange type.
/// \throw isc::BadValue if invalid exchange type specified.
/// \return square sum of delays between sent and received packets.
double getSquareSumDelay(const ExchangeType xchg_type) const {
ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
return xchg_stats->getSquareSumDelay();
}
/// \brief Return number of orphant packets.
///
/// Method returns number of orphant packets for specified
......@@ -420,6 +559,7 @@ public:
/// unordered packet lookup using transaction id.
///
/// \param xchg_type exchange type.
/// \throw isc::BadValue if invalid exchange type specified.
/// \return average unordered lookup set size.
double getAvgUnorderedLookupSetSize(const ExchangeType xchg_type) const {
ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
......@@ -433,6 +573,8 @@ public:
/// out of order by server - transaction id of received
/// packet does not match transaction id of next sent packet.
///
/// \param xchg_type exchange type.
/// \throw isc::BadValue if invalid exchange type specified.
/// \return number of unordered lookups.
uint64_t getUnorderedLookups(const ExchangeType xchg_type) const {
ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
......@@ -447,14 +589,40 @@ public:
/// If packets are skipped or received out of order, lookup
/// function will use unordered lookup (with hash table).
///
/// \param xchg_type exchange type.
/// \throw isc::BadValue if invalid exchange type specified.
/// \return number of ordered lookups.
uint64_t getOrderedLookups(const ExchangeType xchg_type) const {
ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
return xchg_stats->getOrderedLookups();
}
private:
/// \brief Return total number of sent packets
///
/// Method returns total number of sent packets for specified
/// exchange type.
///
/// \param xchg_type exchange type.
/// \throw isc::BadValue if invalid exchange type specified.
/// \return number of sent packets.
uint64_t getSentPacketsNum(const ExchangeType xchg_type) const {
ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
return xchg_stats->getSentPacketsNum();
}
/// \brief Return total number of received packets
///
/// Method returns total number of received packets for specified
/// exchange type.
///
/// \param xchg_type exchange type.
/// \throw isc::BadValue if invalid exchange type specified.
/// \return number of received packets.
uint64_t getRcvdPacketsNum(const ExchangeType xchg_type) const {
ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
return xchg_stats->getRcvdPacketsNum();
}
private:
/// \brief Return exchange stats object for given exchange type
///
/// Method returns exchange stats object for given exchange type.
......
......@@ -16,6 +16,7 @@
#include <exceptions/exceptions.h>
#include <dhcp/dhcp4.h>
#include <dhcp/dhcp6.h>
#include <dhcp/pkt4.h>
#include <dhcp/pkt6.h>
......@@ -43,21 +44,79 @@ public:
Pkt4* createPacket4(const uint8_t msg_type,
const uint32_t transid) {
Pkt4* pkt = new Pkt4(msg_type, transid);
// Packet timestamp is normally updated by interface
// manager on packets reception or send. Unit tests
// do not use interface manager so we need to do it
// ourselfs.
pkt->updateTimestamp();
return pkt;
}
Pkt6* createPacket6(const uint8_t msg_type,
const uint32_t transid) {
Pkt6* pkt = new Pkt6(msg_type, transid);
// Packet timestamp is normally updated by interface
// manager on packets reception or send. Unit tests
// do not use interface manager so we need to do it
// ourselfs.
pkt->updateTimestamp();
return pkt;
}
void passMultiplePackets6(const boost::shared_ptr<StatsMgr6> stats_mgr,
const StatsMgr6::ExchangeType xchg_type,
const uint8_t packet_type,
const int num_packets,
const bool receive = false) {
for (int i = 0; i < num_packets; ++i) {
boost::shared_ptr<Pkt6>
packet(createPacket6(packet_type, i));
if (receive) {
ASSERT_NO_THROW(
stats_mgr->passRcvdPacket(xchg_type, packet);
);
} else {
ASSERT_NO_THROW(
stats_mgr->passSentPacket(xchg_type, packet)
);
}
}
}
};
TEST_F(StatsMgrTest, Constructor) {
boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
EXPECT_EQ(
std::numeric_limits<double>::max(),
stats_mgr->getMinDelay(StatsMgr4::XCHG_DO)
);
EXPECT_EQ(0, stats_mgr->getMaxDelay(StatsMgr4::XCHG_DO));
EXPECT_EQ(0, stats_mgr->getSumDelay(StatsMgr4::XCHG_DO));
EXPECT_EQ(0, stats_mgr->getOrphans(StatsMgr4::XCHG_DO));
EXPECT_EQ(0, stats_mgr->getSquareSumDelay(StatsMgr4::XCHG_DO));
EXPECT_EQ(0, stats_mgr->getOrderedLookups(StatsMgr4::XCHG_DO));
EXPECT_EQ(0, stats_mgr->getUnorderedLookups(StatsMgr4::XCHG_DO));
EXPECT_EQ(0, stats_mgr->getSentPacketsNum(StatsMgr4::XCHG_DO));
EXPECT_EQ(0, stats_mgr->getRcvdPacketsNum(StatsMgr4::XCHG_DO));
double avg_size = 0.;
ASSERT_NO_THROW(
avg_size = stats_mgr->getAvgUnorderedLookupSetSize(StatsMgr4::XCHG_DO);
);
EXPECT_EQ(0., avg_size);
}
TEST_F(StatsMgrTest, Exchanges) {
TEST_F(StatsMgrTest, Exchange) {
boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER,
common_transid));
boost::shared_ptr<Pkt4> rcvd_packet(createPacket4(DHCPOFFER,
common_transid));
// This is expected to throw because XCHG_DO was not yet
// added to Stats Manager for tracking.
EXPECT_THROW(
stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet),
BadValue
......@@ -67,7 +126,10 @@ TEST_F(StatsMgrTest, Exchanges) {
BadValue
);
// Adding DISCOVER-OFFER exchanges to be tracked by Stats Manager.
stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
// The following two attempts are expected to throw because
// invalid exchange types are passed (XCHG_RA instead of XCHG_DO)
EXPECT_THROW(
stats_mgr->passSentPacket(StatsMgr4::XCHG_RA, sent_packet),
BadValue
......@@ -77,6 +139,8 @@ TEST_F(StatsMgrTest, Exchanges) {
BadValue
);
// The following two attempts are expected to run fine because
// right exchange type is specified.
EXPECT_NO_THROW(
stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet)
);
......@@ -85,6 +149,46 @@ TEST_F(StatsMgrTest, Exchanges) {
);
}
TEST_F(StatsMgrTest, MultipleExchanges) {
boost::shared_ptr<StatsMgr6> stats_mgr(new StatsMgr6());
stats_mgr->addExchangeStats(StatsMgr6::XCHG_SA);
stats_mgr->addExchangeStats(StatsMgr6::XCHG_RR);
// Simulate sending number of solicit packets.
const int solicit_packets_num = 10;
passMultiplePackets6(stats_mgr, StatsMgr6::XCHG_SA, DHCPV6_SOLICIT,
solicit_packets_num);
// Simulate sending number of request packets. It is important that
// number of request packets is different then number of solicit
// packets. We can now check if right number packets went to
// the right exchange type group.
const int request_packets_num = 5;
passMultiplePackets6(stats_mgr, StatsMgr6::XCHG_RR, DHCPV6_REQUEST,
request_packets_num);
// Check if all packets are successfuly passed to packet lists.
EXPECT_EQ(solicit_packets_num,
stats_mgr->getSentPacketsNum(StatsMgr6::XCHG_SA));
EXPECT_EQ(request_packets_num,
stats_mgr->getSentPacketsNum(StatsMgr6::XCHG_RR));
// Simulate reception of multiple packets for both SOLICIT-ADVERTISE
// and REQUEST-REPLY exchanges. Assume no packet drops.
const bool receive_packets = true;
passMultiplePackets6(stats_mgr, StatsMgr6::XCHG_SA, DHCPV6_ADVERTISE,
solicit_packets_num, receive_packets);
passMultiplePackets6(stats_mgr, StatsMgr6::XCHG_RR, DHCPV6_REPLY,
request_packets_num, receive_packets);
// Verify that all received packets are counted.
EXPECT_EQ(solicit_packets_num,
stats_mgr->getRcvdPacketsNum(StatsMgr6::XCHG_SA));
EXPECT_EQ(request_packets_num,
stats_mgr->getRcvdPacketsNum(StatsMgr6::XCHG_RR));
}
TEST_F(StatsMgrTest, SendReceiveSimple) {
boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER,
......@@ -92,15 +196,22 @@ TEST_F(StatsMgrTest, SendReceiveSimple) {
boost::shared_ptr<Pkt4> rcvd_packet(createPacket4(DHCPOFFER,
common_transid));
stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
// The following attempt is expected to pass becase the right
// exchange type is used.
ASSERT_NO_THROW(
stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet)
);
EXPECT_NO_THROW(
// It is ok, to pass to received packets here. First one will
// be matched with sent packet. The latter one will not be
// matched with sent packet but orphans counter will simply
// increase.
ASSERT_NO_THROW(
stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet)
);
EXPECT_NO_THROW(
ASSERT_NO_THROW(
stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet)
);
EXPECT_EQ(1, stats_mgr->getOrphans(StatsMgr4::XCHG_DO));
}
TEST_F(StatsMgrTest, SendReceiveUnordered) {
......@@ -108,7 +219,9 @@ TEST_F(StatsMgrTest, SendReceiveUnordered) {
boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
uint32_t transid[packets_num] = { 1, 1024, 2, 1025, 3, 1026, 4, 1027, 5, 1028 };
// Transaction ids of 10 packets to be sent and received.
uint32_t transid[packets_num] =
{ 1, 1024, 2, 1025, 3, 1026, 4, 1027, 5, 1028 };
for (int i = 0; i < packets_num; ++i) {
boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER,
transid[i]));
......@@ -117,16 +230,24 @@ TEST_F(StatsMgrTest, SendReceiveUnordered) {
);
}
// We are simulating that received packets are coming in reverse order:
// 1028, 5, 1027 ....
for (int i = 0; i < packets_num; ++i) {
boost::shared_ptr<Pkt4> rcvd_packet(createPacket4(DHCPDISCOVER,
transid[packets_num - 1 - i]));
boost::shared_ptr<Pkt4>
rcvd_packet(createPacket4(DHCPDISCOVER,
transid[packets_num - 1 - i]));
ASSERT_NO_THROW(
stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet);
);
}
// All packets are expected to match (we did not drop any)
EXPECT_EQ(0, stats_mgr->getOrphans(StatsMgr4::XCHG_DO));
EXPECT_EQ(10, stats_mgr->getUnorderedLookups(StatsMgr4::XCHG_DO));
std::cout << stats_mgr->getAvgUnorderedLookupSetSize(StatsMgr4::XCHG_DO) << std::endl;
// Most of the time we have to do unordered lookups except for the last
// one. Packets are removed from the sent list every time we have a match
// so eventually we come up with the single packet that caching iterator
// is pointing to. This is counted as ordered lookup.
EXPECT_EQ(1, stats_mgr->getOrderedLookups(StatsMgr4::XCHG_DO));
EXPECT_EQ(9, stats_mgr->getUnorderedLookups(StatsMgr4::XCHG_DO));
}
TEST_F(StatsMgrTest, Orphans) {
......@@ -134,19 +255,73 @@ TEST_F(StatsMgrTest, Orphans) {
boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
// We skip every second packet to simulate drops.
for (int i = 0; i < packets_num; i += 2) {
boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER, i));
ASSERT_NO_THROW(
stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet)
);
}
// We pass all received packets.
for (int i = 0; i < packets_num; ++i) {
boost::shared_ptr<Pkt4> rcvd_packet(createPacket4(DHCPOFFER, i));
ASSERT_NO_THROW(
stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet);
);
}