Browse Source

[1958] Added basic implementation of perfdhcp StatsMgr and unit tests.

Marcin Siodelski 12 years ago
parent
commit
ffb9c0ae76

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

@@ -24,6 +24,7 @@ libperfdhcp___la_SOURCES += localized_option.h
 libperfdhcp___la_SOURCES += perf_pkt6.cc perf_pkt6.h
 libperfdhcp___la_SOURCES += perf_pkt4.cc perf_pkt4.h
 libperfdhcp___la_SOURCES += pkt_transform.cc pkt_transform.h
+libperfdhcp___la_SOURCES += stats_mgr.h
 
 libperfdhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
 if USE_CLANGPP

+ 254 - 0
tests/tools/perfdhcp/stats_mgr.h

@@ -0,0 +1,254 @@
+// Copyright (C) 2012 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 __STATS_MGR_H
+#define __STATS_MGR_H
+
+#include <map>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief Statistics Manager
+///
+/// This class template is a storage for various performance statistics
+/// collected during performance tests execution with perfdhcp tool.
+///
+/// Statistics Manager holds lists of sent and received packets and
+/// groups them into exchanges. For example: DHCPDISCOVER message and
+/// corresponding DHCPOFFER messages belong to one exchange, DHCPREQUEST
+/// and corresponding DHCPACK message belong to another exchange etc.
+/// In order to update statistics for a particular exchange type, client
+/// class passes sent and received packets. Internally, Statistics Manager
+/// tries to match transaction id of received packet with sent packet
+/// stored on the list of sent packets. When packets are matched the
+/// round trip time can be calculated.
+///
+/// \tparam T class representing DHCPv4 or DHCPv6 packet.
+template <class T>
+class StatsMgr : public boost::noncopyable {
+public:
+
+    /// DHCP packet exchange types.
+    enum ExchangeType {
+        XCHG_DO,  ///< DHCPv4 DISCOVER-OFFER
+        XCHG_RA,  ///< DHCPv4 REQUEST-ACK
+        XCHG_SA,  ///< DHCPv6 SOLICIT-ADVERTISE
+        XCHG_RR   ///< DHCPv6 REQUEST-REPLY
+    };
+
+    /// \brief Exchange Statistics.
+    ///
+    /// This class collects statistics for exchanges. Parent class
+    /// may define number of different packet exchanges like:
+    /// DHCPv4 DISCOVER-OFFER, DHCPv6 SOLICIT-ADVERTISE etc. Performance
+    /// statistics will be collected for each of those separately in
+    /// corresponding instance of ExchangeStats.
+    class ExchangeStats {
+    public:
+
+        /// \brief List of packets (sent or received).
+        ///
+        /// List of packets based on multi index container allows efficient
+        /// search of packets based on their sequence (order in which they
+        /// were inserted) as well as based on packet transaction id.
+        typedef boost::multi_index_container<
+            boost::shared_ptr<T>,
+            boost::multi_index::indexed_by<
+                boost::multi_index::sequenced<>,
+                boost::multi_index::ordered_unique<
+                    boost::multi_index::const_mem_fun<
+                        T, uint32_t, &T::getTransid>
+                >
+            >
+        > PktList;
+
+        /// Packet list iterator for sequencial access to elements.
+        typedef typename PktList::iterator PktListIterator;
+        /// Packet list index to search packets using transaction id.
+        typedef typename PktList::template nth_index<1>::type
+            PktListTransidIndex;
+        /// Packet list iterator to access packets using transaction id.
+        typedef typename PktListTransidIndex::iterator PktListTransidIterator;
+
+        /// \brief Constructor
+        ///
+        /// \param xchg_type exchange type
+        ExchangeStats(const ExchangeType xchg_type)
+            : xchg_type_(xchg_type) {
+            sent_packets_cache_ = sent_packets_.begin();
+        }
+
+        /// \brief Add new packet to list of sent packets.
+        ///
+        /// Method adds new packet to list of sent packets.
+        ///
+        /// \param packet packet object to be appended.
+        void appendSent(const boost::shared_ptr<T> packet) {
+            sent_packets_.template get<0>().push_back(packet);
+        }
+
+        /// \brief Find packet on the list of sent packets.
+        ///
+        /// Method finds packet with specified transaction id on the list
+        /// of sent packets. It is used to match received packet with
+        /// corresponding sent packet.
+        /// Since packets from the server most often come in the same order
+        /// as they were sent by client, this method will first check if
+        /// next sent packet matches. If it doesn't, function will search
+        /// the packet using indexing by transaction id. This reduces
+        /// packet search time significantly.
+        ///
+        /// \param transid transaction id of the packet to search
+        /// \throw isc::Unexpected if packet could not be found
+        /// \return packet having specified transaction id
+        boost::shared_ptr<T> findSent(const uint32_t transid) {
+            if (sent_packets_.size() == 0) {
+                isc_throw(Unexpected, "Sent packets list is empty.");
+            } else if (sent_packets_cache_ == sent_packets_.end()) {
+                sent_packets_cache_ = sent_packets_.begin();
+            }
+
+            bool packet_found = false;
+            if ((*sent_packets_cache_)->getTransid() == transid) {
+                packet_found = true;
+            } else {
+                PktListTransidIndex& idx = sent_packets_.template get<1>();
+                PktListTransidIterator it =  idx.find(transid);
+                if (it != idx.end()) {
+                    packet_found = true;
+                    sent_packets_cache_ = sent_packets_.template project<0>(it);
+                }
+            }
+
+            if (!packet_found) {
+                isc_throw(Unexpected, "Unable to find sent packet.");
+            }
+
+            boost::shared_ptr<T> sent_packet(*sent_packets_cache_);
+            ++sent_packets_cache_;
+            return sent_packet;
+        }
+
+        double getMinDelay() const { return min_delay_; }
+        double getMaxDelay() const { return max_delay_; }
+        double getSumDelay() const { return sum_delay_; }
+        double getSquareSumDelay() const  { return square_sum_delay_; }
+    private:
+
+        /// \brief Private default constructor.
+        ///
+        /// Default constructor is private because we want client
+        /// class to specify exchange type explicitely.
+        ExchangeStats();
+
+        ExchangeType xchg_type_;             ///< Packet exchange type.
+        PktList sent_packets_;               ///< List of sent packets.
+
+        /// Iterator pointing to the packet on sent list which will most
+        /// likely match next received packet. This is based on the
+        /// assumption that server responds in order to incoming packets.
+        PktListIterator sent_packets_cache_;
+
+        PktList rcvd_packets_;         ///< List of received packets.
+
+        double min_delay_;             ///< Minimum delay between sent
+                                       ///< and received packets.
+        double max_delay_;             ///< Maximum delay between sent
+                                       ///< and received packets.
+        double sum_delay_;             ///< Sum of delays between sent
+                                       ///< and received packets.
+        double square_sum_delay_;      ///< Square sum of delays between
+                                       ///< sent and recived packets.
+    };
+
+    /// Pointer to ExchangeStats.
+    typedef boost::shared_ptr<ExchangeStats> ExchangeStatsPtr;
+    /// Map containing all specified exchange types.
+    typedef typename std::map<ExchangeType, ExchangeStatsPtr> ExchangesMap;
+    /// Iterator poiting to \ref ExchangesMap
+    typedef typename ExchangesMap::iterator ExchangesMapIterator;
+
+    /// \brief Specify new exchange type.
+    ///
+    /// This method creates new \ref ExchangeStats object that will
+    /// collect statistics data from packets exchange of the specified
+    /// type.
+    ///
+    /// \param xchg_type exchange type.
+    /// \throw isc::BadValue if exchange of specified type exists.
+    void addExchangeStats(const ExchangeType xchg_type) {
+        if (exchanges_.find(xchg_type) != exchanges_.end()) {
+            isc_throw(BadValue, "Exchange of specified type already added.");
+        }
+        exchanges_[xchg_type] = ExchangeStatsPtr(new ExchangeStats(xchg_type));
+    }
+
+    /// \brief Adds new packet to the sent packets list.
+    ///
+    /// Method adds new packet to the sent packets list.
+    /// Packets are added to the list sequentially and
+    /// most often read sequentially.
+    ///
+    /// \param xchg_type exchange type.
+    /// \param packet packet to be added to the list
+    /// \throw isc::BadValue if invalid exchange type specified.
+    void passSentPacket(const ExchangeType xchg_type,
+                        const boost::shared_ptr<T> packet) {
+        ExchangesMapIterator it = exchanges_.find(xchg_type);
+        if (it == exchanges_.end()) {
+            isc_throw(BadValue, "Packets exchange not specified");
+        }
+        it->second->appendSent(packet);
+    }
+
+    /// \brief Add new received packet and match with sent packet.
+    ///
+    /// Method adds new packet to the list of received packets. It
+    /// also searches for corresponding packet on the list of sent
+    /// packets. When packets are matched the statistics counters
+    /// are updated accordingly for the particular exchange type.
+    ///
+    /// \param xchg_type exchange type.
+    /// \param packet received packet
+    /// \throw isc::BadValue if invalid exchange type specified.
+    /// \throw isc::Unexpected if corresponding packet was not
+    /// found on the list of sent packets.
+    void passRcvdPacket(const ExchangeType xchg_type,
+                        const boost::shared_ptr<T> packet) {
+        ExchangesMapIterator it = exchanges_.find(xchg_type);
+        if (it == exchanges_.end()) {
+            isc_throw(BadValue, "Packets exchange not specified");
+        }
+        boost::shared_ptr<T> sent_packet
+            = it->second->findSent(packet->getTransid());
+    }
+
+private:
+    ExchangesMap exchanges_;        ///< Map of exchange types.
+};
+
+} // namespace perfdhcp
+} // namespace isc
+
+#endif // __STATS_MGR_H

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

@@ -21,6 +21,7 @@ run_unittests_SOURCES += command_options_unittest.cc
 run_unittests_SOURCES += perf_pkt6_unittest.cc
 run_unittests_SOURCES += perf_pkt4_unittest.cc
 run_unittests_SOURCES += localized_option_unittest.cc
+run_unittests_SOURCES += stats_mgr_unittest.cc
 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/perf_pkt6.cc

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

@@ -0,0 +1,73 @@
+// Copyright (C) 2012 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 <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+
+#include <gtest/gtest.h>
+
+#include "../stats_mgr.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::perfdhcp;
+
+namespace {
+
+typedef StatsMgr<dhcp::Pkt4> StatsMgr4;
+typedef StatsMgr<dhcp::Pkt6> StatsMgr6;
+
+const uint32_t common_transid = 123;
+
+class StatsMgrTest : public ::testing::Test {
+public:
+    StatsMgrTest() {
+    }
+};
+
+TEST_F(StatsMgrTest, Constructor) {
+    boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
+}
+
+TEST_F(StatsMgrTest, Exchanges) {
+    boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
+    boost::shared_ptr<Pkt4> sent_packet(new Pkt4(DHCPDISCOVER, common_transid));
+    boost::shared_ptr<Pkt4> rcvd_packet(new Pkt4(DHCPOFFER, common_transid));
+    EXPECT_THROW(stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet), BadValue);
+    EXPECT_THROW(stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet), BadValue);
+    
+    stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
+    EXPECT_THROW(stats_mgr->passSentPacket(StatsMgr4::XCHG_RA, sent_packet), BadValue);
+    EXPECT_THROW(stats_mgr->passRcvdPacket(StatsMgr4::XCHG_RA, rcvd_packet), BadValue);
+
+    EXPECT_NO_THROW(stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet));
+    EXPECT_NO_THROW(stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet));
+}
+
+TEST_F(StatsMgrTest, SendReceiveSimple) {
+    boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
+    boost::shared_ptr<Pkt4> sent_packet(new Pkt4(DHCPDISCOVER, common_transid));
+    boost::shared_ptr<Pkt4> rcvd_packet(new Pkt4(DHCPOFFER, common_transid));
+    stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
+    ASSERT_NO_THROW(stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet));
+    EXPECT_NO_THROW(stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet));
+    EXPECT_NO_THROW(stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet));
+}
+
+}