diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
commit | f5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch) | |
tree | 49e44c6f87febed37efb953ab5485aa49f6481a7 /src/bin/perfdhcp/stats_mgr.h | |
parent | Initial commit. (diff) | |
download | isc-kea-upstream.tar.xz isc-kea-upstream.zip |
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/bin/perfdhcp/stats_mgr.h')
-rw-r--r-- | src/bin/perfdhcp/stats_mgr.h | 1258 |
1 files changed, 1258 insertions, 0 deletions
diff --git a/src/bin/perfdhcp/stats_mgr.h b/src/bin/perfdhcp/stats_mgr.h new file mode 100644 index 0000000..39718e9 --- /dev/null +++ b/src/bin/perfdhcp/stats_mgr.h @@ -0,0 +1,1258 @@ +// Copyright (C) 2012-2021 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 STATS_MGR_H +#define STATS_MGR_H + +#include <dhcp/pkt.h> +#include <exceptions/exceptions.h> +#include <perfdhcp/command_options.h> + +#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/global_fun.hpp> +#include <boost/multi_index/mem_fun.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> + +#include <iostream> +#include <map> +#include <queue> + + +namespace isc { +namespace perfdhcp { + +/// DHCP packet exchange types. +enum class ExchangeType { + DO, ///< DHCPv4 DISCOVER-OFFER + RA, ///< DHCPv4 REQUEST-ACK + RNA, ///< DHCPv4 REQUEST-ACK (renewal) + RLA, ///< DHCPv4 RELEASE + SA, ///< DHCPv6 SOLICIT-ADVERTISE + RR, ///< DHCPv6 REQUEST-REPLY + RN, ///< DHCPv6 RENEW-REPLY + RL ///< DHCPv6 RELEASE-REPLY +}; + +/// \brief Get the DHCP version that fits the exchange type. +/// +/// \param exchange_type exchange type that will determine the version +/// \throw isc::BadValue exchange type is unrecognized +/// \return DHCP version: 4 or 6 +int dhcpVersion(ExchangeType const exchange_type); + +/// \brief Return name of the exchange. +/// +/// Function returns name of the specified exchange type. +/// This function is mainly for logging purposes. +/// +/// \param os output stream to use. +/// \param xchg_type exchange type. +/// \return string representing name of the exchange. +std::ostream& operator<<(std::ostream& os, ExchangeType xchg_type); + +/// \brief Custom Counter +/// +/// This class represents custom statistics counters. Client class +/// may create unlimited number of counters. Such counters are +/// being stored in map in Statistics Manager and access using +/// unique string key. +class CustomCounter { +public: + /// \brief Constructor. + /// + /// This constructor sets counter name. This name is used in + /// log file to report value of each counter. + /// + /// \param name name of the counter used in log file. + CustomCounter(const std::string& name) : + counter_(0), + name_(name) { + } + + /// \brief Increment operator. + const CustomCounter& operator++() { + ++counter_; + return (*this); + } + + /// \brief Increment operator. + const CustomCounter& operator++(int) { + CustomCounter& this_counter(*this); + operator++(); + return (this_counter); + } + + const CustomCounter& operator+=(int val) { + counter_ += val; + return (*this); + } + + /// \brief Return counter value. + /// + /// Method returns counter value. + /// + /// \return counter value. + uint64_t getValue() const { + return (counter_); + } + + /// \brief Return counter name. + /// + /// Method returns counter name. + /// + /// \return counter name. + const std::string& getName() const { + return (name_); + } + +private: + /// \brief Default constructor. + /// + /// Default constructor is private because we don't want client + /// class to call it because we want client class to specify + /// counter's name. + CustomCounter() : counter_(0) { + } + + uint64_t counter_; ///< Counter's value. + std::string name_; ///< Counter's name. +}; + +typedef typename boost::shared_ptr<CustomCounter> CustomCounterPtr; + +/// Map containing custom counters. +typedef typename std::map<std::string, CustomCounterPtr> CustomCountersMap; + +/// Iterator for \ref CustomCountersMap. +typedef typename CustomCountersMap::const_iterator CustomCountersMapIterator; + + +/// \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 Hash transaction id of the packet. + /// + /// Function hashes transaction id of the packet. Hashing is + /// non-unique. Many packets may have the same hash value and thus + /// they belong to the same packet buckets. Packet buckets are + /// used for unordered packets search with multi index container. + /// + /// \param packet packet which transaction id is to be hashed. + /// \throw isc::BadValue if packet is null. + /// \return transaction id hash. + static uint32_t hashTransid(const dhcp::PktPtr& packet) { + if (!packet) { + isc_throw(BadValue, "Packet is null"); + } + return(packet->getTransid() & 1023); + } + + /// \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 their hashed transaction id. + /// The first index (sequenced) provides the way to use container + /// as a regular list (including iterators, removal of elements from + /// the middle of the collection etc.). This index is meant to be used + /// more frequently than the latter one and it is based on the + /// assumption that responses from the DHCP server are received in + /// order. In this case, when next packet is received it can be + /// matched with next packet on the list of sent packets. This + /// prevents intensive searches on the list of sent packets every + /// time new packet arrives. In many cases however packets can be + /// dropped by the server or may be sent out of order and we still + /// want to have ability to search packets using transaction id. + /// The second index can be used for this purpose. This index is + /// hashing transaction ids using custom function \ref hashTransid. + /// Note that other possibility would be to simply specify index + /// that uses transaction id directly (instead of hashing with + /// \ref hashTransid). In this case however we have chosen to use + /// hashing function because it shortens the index size to just + /// 1023 values maximum. Search operation on this index generally + /// returns the range of packets that have the same transaction id + /// hash assigned but most often these ranges will be short so further + /// search within a range to find a packet with particular transaction + /// id will not be intensive. + /// + /// Example 1: Add elements to the list + /// \code + /// PktList packets_collection(); + /// boost::shared_ptr<Pkt4> pkt1(new Pkt4(...)); + /// boost::shared_ptr<Pkt4> pkt2(new Pkt4(...)); + /// // Add new packet to the container, it will be available through + /// // both indexes + /// static_cast<void>(packets_collection.push_back(pkt1)); + /// // Here is another way to add packet to the container. The result + /// // is exactly the same as previously. + /// static_cast<void>(packets_collection.template get<0>().push_back(pkt2)); + /// \endcode + /// + /// @note The multi index has no unique index so insertion should never + /// fail and there is no need to check the return of push_back(). + /// + /// Example 2: Access elements through sequential index + /// \code + /// PktList packets_collection(); + /// ... # Add elements to the container + /// for (PktListIterator it = packets_collection.begin(); + /// it != packets_collection.end(); + /// ++it) { + /// boost::shared_ptr<Pkt4> pkt = *it; + /// # Do something with packet; + /// } + /// \endcode + /// + /// Example 3: Access elements through ordered index by hash + /// \code + /// // Get the instance of the second search index. + /// PktListTransidHashIndex& idx = sent_packets_.template get<1>(); + /// // Get the range (bucket) of packets sharing the same transaction + /// // id hash. + /// std::pair<PktListTransidHashIterator,PktListTransidHashIterator> p = + /// idx.equal_range(hashTransid(rcvd_packet)); + /// // Iterate through the returned bucket. + /// for (PktListTransidHashIterator it = p.first; it != p.second; + /// ++it) { + /// boost::shared_ptr pkt = *it; + /// ... # Do something with the packet (e.g. check transaction id) + /// } + /// \endcode + typedef boost::multi_index_container< + // Container holds PktPtr objects. + dhcp::PktPtr, + // List container indexes. + boost::multi_index::indexed_by< + // Sequenced index provides the way to use this container + // in the same way as std::list. + boost::multi_index::sequenced<>, + // The other index keeps products of transaction id. + // Elements with the same hash value are grouped together + // into buckets and transactions are ordered from the + // oldest to latest within a bucket. + boost::multi_index::ordered_non_unique< + // Specify hash function to get the product of + // transaction id. This product is obtained by calling + // hashTransid() function. + boost::multi_index::global_fun< + // Hashing function takes PktPtr as argument. + const dhcp::PktPtr&, + // ... and returns uint32 value. + uint32_t, + // ... and here is a reference to it. + &ExchangeStats::hashTransid + > + > + > + > PktList; + + /// Packet list iterator for sequential access to elements. + typedef typename PktList::iterator PktListIterator; + /// Packet list index to search packets using transaction id hash. + typedef typename PktList::template nth_index<1>::type + PktListTransidHashIndex; + /// Packet list iterator to access packets using transaction id hash. + typedef typename PktListTransidHashIndex::const_iterator + PktListTransidHashIterator; + /// Packet list iterator queue for removal. + typedef typename std::queue<PktListTransidHashIterator> + PktListRemovalQueue; + + /// \brief Constructor + /// + /// \param xchg_type exchange type + /// \param drop_time maximum time elapsed before packet is + /// assumed dropped. Negative value disables it. + /// \param archive_enabled if true packets archive mode is enabled. + /// In this mode all packets are stored throughout the test execution. + /// \param boot_time Holds the timestamp when perfdhcp has been started. + ExchangeStats(const ExchangeType xchg_type, + const double drop_time, + const bool archive_enabled, + const boost::posix_time::ptime boot_time); + + /// \brief Add new packet to list of sent packets. + /// + /// Method adds new packet to list of sent packets. + /// + /// \param packet packet object to be added. + /// \throw isc::BadValue if packet is null. + void appendSent(const dhcp::PktPtr& packet) { + if (!packet) { + isc_throw(BadValue, "Packet is null"); + } + static_cast<void>(sent_packets_.template get<0>().push_back(packet)); + ++sent_packets_num_; + } + + /// \brief Add new packet to list of received packets. + /// + /// Method adds new packet to list of received packets. + /// + /// \param packet packet object to be added. + /// \throw isc::BadValue if packet is null. + void appendRcvd(const dhcp::PktPtr& packet) { + if (!packet) { + isc_throw(BadValue, "Packet is null"); + } + static_cast<void>(rcvd_packets_.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::BadValue if sent or received packet is null. + /// \throw isc::Unexpected if failed to calculate timestamps + void updateDelays(const dhcp::PktPtr& sent_packet, + const dhcp::PktPtr& rcvd_packet); + + /// \brief Match received packet with the corresponding sent packet. + /// + /// 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 rcvd_packet received packet to be matched with sent packet. + /// \throw isc::BadValue if received packet is null. + /// \return packet having specified transaction or NULL if packet + /// not found + dhcp::PktPtr matchPackets(const dhcp::PktPtr& rcvd_packet); + + /// \brief Return minimum delay between sent and received packet. + /// + /// Method returns minimum delay between sent and received packet. + /// + /// \return minimum delay between packets. + double getMinDelay() const { return(min_delay_); } + + /// \brief Return maximum delay between sent and received packet. + /// + /// Method returns maximum delay between sent and received packet. + /// + /// \return maximum delay between packets. + double getMaxDelay() const { return(max_delay_); } + + /// \brief Return average packet delay. + /// + /// Method returns average packet delay. If no packets have been + /// received for this exchange avg delay can't be calculated and + /// thus method throws exception. + /// + /// \throw isc::InvalidOperation if no packets for this exchange + /// have been received yet. + /// \return average packet delay. + double getAvgDelay() const { + if (rcvd_packets_num_ == 0) { + isc_throw(InvalidOperation, "no packets received"); + } + return(sum_delay_ / rcvd_packets_num_); + } + + /// \brief Return standard deviation of packet delay. + /// + /// Method returns standard deviation of packet delay. If no + /// packets have been received for this exchange, the standard + /// deviation can't be calculated and thus method throws + /// exception. + /// + /// \throw isc::InvalidOperation if number of received packets + /// for the exchange is equal to zero. + /// \return standard deviation of packet delay. + double getStdDevDelay() const { + if (rcvd_packets_num_ == 0) { + isc_throw(InvalidOperation, "no packets received"); + } + return(sqrt(sum_delay_squared_ / rcvd_packets_num_ - + getAvgDelay() * getAvgDelay())); + } + + /// \brief Return number of orphan packets. + /// + /// Method returns number of received packets that had no matching + /// sent packet. It is possible that such packet was late or not + /// for us. + /// + /// \return number of orphan received packets. + uint64_t getOrphans() const { return(orphans_); } + + /// \brief Return number of garbage collected packets. + /// + /// Method returns number of garbage collected timed out + /// packets. Packet is assumed timed out when duration + /// between sending it to server and receiving server's + /// response is greater than value specified with -d<value> + /// command line argument. + /// + /// \return number of garbage collected packets. + uint64_t getCollectedNum() const { return(collected_); } + + /// \brief Return average unordered lookup set size. + /// + /// Method returns average unordered lookup set size. + /// This value changes every time \ref ExchangeStats::matchPackets + /// function performs unordered packet lookup. + /// + /// \throw isc::InvalidOperation if there have been no unordered + /// lookups yet. + /// \return average unordered lookup set size. + double getAvgUnorderedLookupSetSize() const { + if (unordered_lookups_ == 0) { + isc_throw(InvalidOperation, "no unordered lookups"); + } + return(static_cast<double>(unordered_lookup_size_sum_) / + static_cast<double>(unordered_lookups_)); + } + + /// \brief Return number of unordered sent packets lookups. + /// + /// Method returns number of unordered sent packet lookups. + /// Unordered lookup is used when received packet was sent + /// out of order by server - transaction id of received + /// packet does not match transaction id of next sent packet. + /// + /// \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. + /// Ordered lookup is used when packets are received in the + /// same order as they were sent to the server. + /// If packets are skipped or received out of order, lookup + /// function will use unordered lookup (with hash table). + /// + /// \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_); } + + /// \brief Return number of dropped packets. + /// + /// Method returns number of dropped packets. + /// + /// \return number of dropped packets. + uint64_t getDroppedPacketsNum() const { + uint64_t drops = 0; + if (getSentPacketsNum() > getRcvdPacketsNum()) { + drops = getSentPacketsNum() - getRcvdPacketsNum(); + } + return(drops); + } + + /// \brief Return total number of rejected leases. + /// + /// Method returns total number of rejected leases. + /// + /// \return number of rejected leases. + uint64_t getRejLeasesNum() const { return(rejected_leases_num_); } + + /// \brief Return total number of non unique addresses. + /// + /// Method returns total number of non unique addresses. + /// + /// \return number of non unique addresses. + uint64_t getNonUniqueAddrNum() const { return(non_unique_addr_num_); } + + /// \brief Increase number of rejected leases. + /// + /// Method increases total number of rejected leases by one. + void updateRejLeases() { ++rejected_leases_num_; } + + /// \brief Increase number of non unique addresses. + /// + /// Method increases total number of non unique addresses by one. + void updateNonUniqueAddr() { ++non_unique_addr_num_; } + + /// \brief Print main statistics for packet exchange. + /// + /// Method prints main statistics for particular exchange. + /// Statistics includes: number of sent and received packets, + /// 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 response to any of the messages sent by + /// perfdhcp. + void printMainStats() const { + using namespace std; + auto sent = getSentPacketsNum(); + auto drops = getDroppedPacketsNum(); + double drops_ratio = 100.0 * static_cast<double>(drops) / static_cast<double>(sent); + + cout << "sent packets: " << sent << endl + << "received packets: " << getRcvdPacketsNum() << endl + << "drops: " << drops << endl + << "drops ratio: " << drops_ratio << " %" << endl + << "orphans: " << getOrphans() << endl + << "rejected leases: " << getRejLeasesNum() << endl + << "non unique addresses: " << getNonUniqueAddrNum() << endl; + } + + /// \brief Print round trip time packets statistics. + /// + /// Method prints round trip time packets statistics. Statistics + /// includes minimum packet delay, maximum packet delay, average + /// packet delay and standard deviation of delays. Packet delay + /// is a duration between sending a packet to server and receiving + /// response from server. + void printRTTStats() const { + using namespace std; + try { + cout << fixed << setprecision(3) + << "min delay: " << getMinDelay() * 1e3 << " ms" << endl + << "avg delay: " << getAvgDelay() * 1e3 << " ms" << endl + << "max delay: " << getMaxDelay() * 1e3 << " ms" << endl + << "std deviation: " << getStdDevDelay() * 1e3 << " ms" + << endl + << "collected packets: " << getCollectedNum() << endl; + } catch (const Exception&) { + // repeated output for easier automated parsing + cout << "min delay: n/a" << endl + << "avg delay: n/a" << endl + << "max delay: n/a" << endl + << "std deviation: n/a" << endl + << "collected packets: 0" << endl; + } + } + + //// \brief Print timestamps for sent and received packets. + /// + /// Method prints timestamps for all sent and received packets for + /// packet exchange. In order to run this method the packets + /// archiving mode has to be enabled during object constructions. + /// Otherwise sent packets are not stored during tests execution + /// and this method has no ability to get and print their timestamps. + /// + /// \throw isc::InvalidOperation if found packet with no timestamp or + /// if packets archive mode is disabled. + void printTimestamps(); + + std::tuple<PktListIterator, PktListIterator> getSentPackets() { + return(std::make_tuple(sent_packets_.begin(), sent_packets_.end())); + } + + /// \brief Return the list of received leases in CSV format as string. + /// + /// Depending exchange type, it can apply to + /// potential leases received in offers and advertisements, + /// committed leases received in acknowledgements and replies, + /// renewed or released leases. + /// + /// \return multiline string of received leases in CSV format + std::string receivedLeases() const; + + /// \brief Print the list of received leases. + void printLeases() const; + + static int malformed_pkts_; + +// Private stuff of ExchangeStats class +private: + + /// \brief Private default constructor. + /// + /// Default constructor is private because we want the client + /// class to specify exchange type explicitly. + 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) { + if (archive_enabled_) { + // We don't want to keep list of all sent packets + // because it will affect packet lookup performance. + // If packet is matched with received packet we + // move it to list of archived packets. List of + // archived packets may be used for diagnostics + // when test is completed. + static_cast<void>(archived_packets_.push_back(*it)); + } + // get<0>() template returns sequential index to + // container. + return(sent_packets_.template get<0>().erase(it)); + } + + 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 next_sent_; + + PktList rcvd_packets_; ///< List of received packets. + + /// List of archived packets. All sent packets that have + /// been matched with received packet are moved to this + /// list for diagnostics purposes. + PktList archived_packets_; + + /// Indicates all packets have to be preserved after matching. + /// By default this is disabled which means that when received + /// packet is matched with sent packet both are deleted. This + /// is important when test is executed for extended period of + /// time and high memory usage might be the issue. + /// When timestamps listing is specified from the command line + /// (using diagnostics selector), all packets have to be preserved + /// so as the printing method may read their timestamps and + /// print it to user. In such usage model it will be rare to + /// run test for extended period of time so it should be fine + /// to keep all packets archived throughout the test. + bool archive_enabled_; + + /// Maximum time elapsed between sending and receiving packet + /// before packet is assumed dropped. + double drop_time_; + + 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 sum_delay_squared_; ///< Squared sum of delays between + ///< sent and received packets. + + uint64_t orphans_; ///< Number of orphan received packets. + + uint64_t collected_; ///< Number of garbage collected packets. + + /// Sum of unordered lookup sets. Needed to calculate mean size of + /// lookup set. It is desired that number of unordered lookups is + /// minimal for performance reasons. Tracking number of lookups and + /// mean size of the lookup set should give idea of packets search + /// complexity. + uint64_t unordered_lookup_size_sum_; + + uint64_t unordered_lookups_; ///< Number of unordered sent packets + ///< lookups. + 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. + + uint64_t non_unique_addr_num_; ///< Total number of non unique addresses + ///< offered/advertised. + uint64_t rejected_leases_num_; ///< Total number of rejected leases + /// (e.g. NoAddrAvail) + boost::posix_time::ptime boot_time_; ///< Time when test is started. +}; + +/// Pointer to ExchangeStats. +typedef boost::shared_ptr<ExchangeStats> ExchangeStatsPtr; + +/// Map containing all specified exchange types. +typedef typename std::map<ExchangeType, ExchangeStatsPtr> ExchangesMap; + +/// Iterator pointing to \ref ExchangesMap. +typedef typename ExchangesMap::const_iterator ExchangesMapIterator; + + +/// \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. +/// +class StatsMgr : public boost::noncopyable { +public: + /// \brief Constructor. + /// + /// This constructor by default disables packets archiving mode. + /// In this mode all packets from the list of sent packets are + /// moved to list of archived packets once they have been matched + /// with received packets. This is required if it has been selected + /// from the command line to print timestamps for all packets after + /// the test. If this is not selected archiving should be disabled + /// for performance reasons and to avoid waste of memory for storing + /// large list of archived packets. + StatsMgr(CommandOptions& options); + + /// \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. + /// \param drop_time maximum time elapsed before packet is + /// assumed dropped. Negative value disables it. + /// \throw isc::BadValue if exchange of specified type exists. + void addExchangeStats(const ExchangeType xchg_type, + const double drop_time = -1) { + if (exchanges_.find(xchg_type) != exchanges_.end()) { + isc_throw(BadValue, "Exchange of specified type already added."); + } + exchanges_[xchg_type] = + ExchangeStatsPtr(new ExchangeStats(xchg_type, + drop_time, + archive_enabled_, + 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 represented 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. + /// + /// Method creates new named counter and stores in counter's map under + /// key specified here as short_name. + /// + /// \param short_name key to use to access counter in the map. + /// \param long_name name of the counter presented in the log file. + void addCustomCounter(const std::string& short_name, + const std::string& long_name) { + if (custom_counters_.find(short_name) != custom_counters_.end()) { + isc_throw(BadValue, + "Custom counter " << short_name << " already added."); + } + custom_counters_[short_name] = + CustomCounterPtr(new CustomCounter(long_name)); + } + + /// \brief Check if any packet drops occurred. + /// + // \return true, if packet drops occurred. + bool droppedPackets() const { + for (ExchangesMapIterator it = exchanges_.begin(); + it != exchanges_.end(); + ++it) { + if (it->second->getDroppedPacketsNum() > 0) { + return (true); + } + } + return (false); + } + + /// \brief Return specified counter. + /// + /// Method returns specified counter. + /// + /// \param counter_key key pointing to the counter in the counters map. + /// The short counter name has to be used to access counter. + /// \return pointer to specified counter object. + CustomCounterPtr getCounter(const std::string& counter_key) { + CustomCountersMapIterator it = custom_counters_.find(counter_key); + if (it == custom_counters_.end()) { + isc_throw(BadValue, + "Custom counter " << counter_key << "does not exist"); + } + return(it->second); + } + + /// \brief Increment specified counter. + /// + /// Increment counter value by one. + /// + /// \param counter_key key pointing to the counter in the counters map. + /// \param value value to increment counter by. + /// \return pointer to specified counter after incrementation. + const CustomCounter& incrementCounter(const std::string& counter_key, + const uint64_t value = 1) { + CustomCounterPtr counter = getCounter(counter_key); + *counter += value; + return (*counter); + } + + /// \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 or + /// packet is null. + void passSentPacket(const ExchangeType xchg_type, + const dhcp::PktPtr& packet) { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + xchg_stats->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 + /// or packet is null. + /// \throw isc::Unexpected if corresponding packet was not + /// found on the list of sent packets. + dhcp::PktPtr + passRcvdPacket(const ExchangeType xchg_type, + const dhcp::PktPtr& packet) { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + dhcp::PktPtr sent_packet = xchg_stats->matchPackets(packet); + + if (sent_packet) { + xchg_stats->updateDelays(sent_packet, packet); + if (archive_enabled_) { + xchg_stats->appendRcvd(packet); + } + } + return(sent_packet); + } + + /// \brief Return minimum 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 maximum 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 average packet delay. + /// + /// Method returns average packet delay for specified + /// exchange type. + /// + /// \return average packet delay. + double getAvgDelay(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getAvgDelay()); + } + + /// \brief Return standard deviation of packet delay. + /// + /// Method returns standard deviation of packet delay + /// for specified exchange type. + /// + /// \return standard deviation of packet delay. + double getStdDevDelay(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getStdDevDelay()); + } + + /// \brief Return number of orphan packets. + /// + /// Method returns number of orphan packets for specified + /// exchange type. + /// + /// \param xchg_type exchange type. + /// \throw isc::BadValue if invalid exchange type specified. + /// \return number of orphan packets so far. + uint64_t getOrphans(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getOrphans()); + } + + /// \brief Return average unordered lookup set size. + /// + /// Method returns average unordered lookup set size. + /// This value changes every time \ref ExchangeStats::matchPackets + /// function performs unordered packet lookup. + /// + /// \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); + return(xchg_stats->getAvgUnorderedLookupSetSize()); + } + + /// \brief Return number of unordered sent packets lookups. + /// + /// Method returns number of unordered sent packet lookups. + /// Unordered lookup is used when received packet was sent + /// 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); + return(xchg_stats->getUnorderedLookups()); + } + + /// \brief Return number of ordered sent packets lookups. + /// + /// Method returns number of ordered sent packet lookups. + /// Ordered lookup is used when packets are received in the + /// same order as they were sent to the server. + /// 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()); + } + + /// \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()); + } + + /// \brief Return total number of dropped packets. + /// + /// Method returns total number of dropped packets for specified + /// exchange type. + /// + /// \param xchg_type exchange type. + /// \throw isc::BadValue if invalid exchange type specified. + /// \return number of dropped packets. + uint64_t getDroppedPacketsNum(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getDroppedPacketsNum()); + } + + /// \brief Return number of garbage collected packets. + /// + /// Method returns number of garbage collected timed out + /// packets. Packet is assumed timed out when duration + /// between sending it to server and receiving server's + /// response is greater than value specified with -d<value> + /// command line argument. + /// + /// \throw isc::BadValue if invalid exchange type specified. + /// \return number of garbage collected packets. + uint64_t getCollectedNum(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getCollectedNum()); + } + + /// \brief Return total number of rejected leases. + /// + /// Method returns total number of rejected leases for specified + /// exchange type. + /// + /// \param xchg_type exchange type. + /// \throw isc::BadValue if invalid exchange type specified. + /// \return number of rejected leases. + uint64_t getRejLeasesNum(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getRejLeasesNum()); + } + + /// \brief Increase total number of rejected leases. + /// + /// Method increases total number of rejected leases by one + /// for specified exchange type. + void updateRejLeases(const ExchangeType xchg_type) { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + xchg_stats->updateRejLeases(); + } + + /// \brief Increase total number of non unique addresses. + /// + /// Method increases total number of non unique addresses or + /// prefixes by one for specified exchange type. + void updateNonUniqueAddrNum(const ExchangeType xchg_type) { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + xchg_stats->updateNonUniqueAddr(); + } + + /// \brief Return total number of non unique addresses. + /// + /// Method returns total number of non unique addresses and/or + /// prefixes for specified exchange type. + /// + /// \param xchg_type exchange type. + /// \throw isc::BadValue if invalid exchange type specified. + /// \return number of non unique addresses. + uint64_t getNonUniqueAddrNum(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + return(xchg_stats->getNonUniqueAddrNum()); + } + + /// \brief Get time period since the start of test. + /// + /// Calculate dna return period since the test start. This + /// can be specifically helpful when calculating packet + /// exchange rates. + /// + /// \return test period so far. + boost::posix_time::time_period getTestPeriod() const { + using namespace boost::posix_time; + time_period test_period(boot_time_, + microsec_clock::universal_time()); + return test_period; + } + + /// \brief Print statistics counters for all exchange types. + /// + /// Method prints statistics for all exchange types. + /// Statistics includes: + /// - number of sent and received packets + /// - number of dropped packets and number of orphans + /// - minimum packets delay, + /// - average packets delay, + /// - maximum packets delay, + /// - standard deviation of packets delay. + /// + /// \throw isc::InvalidOperation if no exchange type added to + /// track statistics. + void printStats() const { + if (exchanges_.empty()) { + isc_throw(isc::InvalidOperation, + "no exchange type added for tracking"); + } + for (ExchangesMapIterator it = exchanges_.begin(); + it != exchanges_.end(); + ++it) { + ExchangeStatsPtr xchg_stats = it->second; + std::cout << "***Statistics for: " << it->first + << "***" << std::endl; + xchg_stats->printMainStats(); + std::cout << std::endl; + xchg_stats->printRTTStats(); + std::cout << std::endl; + } + } + + /// \brief Print intermediate statistics. + /// + /// Method prints intermediate statistics for all exchanges. + /// Statistics includes sent, received and dropped packets + /// counters. + /// + /// \param clean_report value to generate easy to parse report. + /// \param clean_sep string used as separator if clean_report enabled.. + void + printIntermediateStats(bool clean_report, std::string clean_sep) const { + std::ostringstream stream_sent; + std::ostringstream stream_rcvd; + std::ostringstream stream_drops; + std::ostringstream stream_reject; + std::string sep(""); + for (ExchangesMapIterator it = exchanges_.begin(); + it != exchanges_.end(); ++it) { + + if (it != exchanges_.begin()) { + if (clean_report) { + sep = clean_sep; + } else { + sep = "/"; + } + } + stream_sent << sep << it->second->getSentPacketsNum(); + stream_rcvd << sep << it->second->getRcvdPacketsNum(); + stream_drops << sep << it->second->getDroppedPacketsNum(); + stream_reject << sep << it->second->getRejLeasesNum(); + } + + if (clean_report) { + std::cout << stream_sent.str() + << clean_sep << stream_rcvd.str() + << clean_sep << stream_drops.str() + << clean_sep << stream_reject.str() + << std::endl; + + } else { + std::cout << "sent: " << stream_sent.str() + << "; received: " << stream_rcvd.str() + << "; drops: " << stream_drops.str() + << "; rejected: " << stream_reject.str() + << std::endl; + } + } + + /// \brief Print timestamps of all packets. + /// + /// Method prints timestamps of all sent and received + /// packets for all defined exchange types. + /// + /// \throw isc::InvalidOperation if one of the packets has + /// no timestamp value set or if packets archive mode is + /// disabled. + /// + /// \throw isc::InvalidOperation if no exchange type added to + /// track statistics or packets archive mode is disabled. + void printTimestamps() const { + if (exchanges_.empty()) { + isc_throw(isc::InvalidOperation, + "no exchange type added for tracking"); + } + for (ExchangesMapIterator it = exchanges_.begin(); + it != exchanges_.end(); + ++it) { + ExchangeStatsPtr xchg_stats = it->second; + std::cout << "***Timestamps for packets: " + << it->first + << "***" << std::endl; + xchg_stats->printTimestamps(); + std::cout << std::endl; + } + } + + /// \brief Delegate to all exchanges to print their leases. + void printLeases() const; + + /// \brief Print names and values of custom counters. + /// + /// Method prints names and values of custom counters. Custom counters + /// are defined by client class for tracking different statistics. + /// + /// \throw isc::InvalidOperation if no custom counters added for tracking. + void printCustomCounters() const { + if (custom_counters_.empty()) { + isc_throw(isc::InvalidOperation, "no custom counters specified"); + } + for (CustomCountersMapIterator it = custom_counters_.begin(); + it != custom_counters_.end(); + ++it) { + CustomCounterPtr counter = it->second; + std::cout << counter->getName() << ": " << counter->getValue() + << std::endl; + } + } + + std::tuple<typename ExchangeStats::PktListIterator, typename ExchangeStats::PktListIterator> getSentPackets(const ExchangeType xchg_type) const { + ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); + std::tuple<typename ExchangeStats::PktListIterator, typename ExchangeStats::PktListIterator> sent_packets_its = xchg_stats->getSentPackets(); + return(sent_packets_its); + } + +private: + + /// \brief Return exchange stats object for given exchange type. + /// + /// Method returns exchange stats object for given exchange type. + /// + /// \param xchg_type exchange type. + /// \throw isc::BadValue if invalid exchange type specified. + /// \return exchange stats object. + ExchangeStatsPtr getExchangeStats(const ExchangeType xchg_type) const { + ExchangesMapIterator it = exchanges_.find(xchg_type); + if (it == exchanges_.end()) { + isc_throw(BadValue, "Packets exchange not specified"); + } + ExchangeStatsPtr xchg_stats = it->second; + return(xchg_stats); + } + + ExchangesMap exchanges_; ///< Map of exchange types. + CustomCountersMap custom_counters_; ///< Map with custom counters. + + /// Indicates that packets from list of sent packets should be + /// archived (moved to list of archived packets) once they are + /// matched with received packets. This is required when it has + /// been selected from the command line to print packets' + /// timestamps after test. This may affect performance and + /// consume large amount of memory when the test is running + /// for extended period of time and many packets have to be + /// archived. + bool archive_enabled_; + + boost::posix_time::ptime boot_time_; ///< Time when test is started. +}; + +/// Pointer to Statistics Manager; +typedef boost::shared_ptr<StatsMgr> StatsMgrPtr; + + +} // namespace perfdhcp +} // namespace isc + +#endif // STATS_MGR_H |