diff options
Diffstat (limited to '')
-rw-r--r-- | src/bin/perfdhcp/stats_mgr.cc | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/src/bin/perfdhcp/stats_mgr.cc b/src/bin/perfdhcp/stats_mgr.cc new file mode 100644 index 0000000..1563588 --- /dev/null +++ b/src/bin/perfdhcp/stats_mgr.cc @@ -0,0 +1,473 @@ +// 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/. + +#include <config.h> + +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/duid.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option6_iaprefix.h> +#include <dhcp/pkt4.h> +#include <perfdhcp/stats_mgr.h> +#include <perfdhcp/test_control.h> + +using isc::dhcp::DHO_DHCP_CLIENT_IDENTIFIER; +using isc::dhcp::DUID; +using isc::dhcp::Option6IAAddr; +using isc::dhcp::Option6IAAddrPtr; +using isc::dhcp::Option6IAPrefix; +using isc::dhcp::Option6IAPrefixPtr; +using isc::dhcp::OptionPtr; +using isc::dhcp::Pkt4; +using isc::dhcp::Pkt4Ptr; +using isc::dhcp::PktPtr; + +namespace isc { +namespace perfdhcp { + +int dhcpVersion(ExchangeType const exchange_type) { + switch (exchange_type) { + case ExchangeType::DO: + case ExchangeType::RA: + case ExchangeType::RNA: + case ExchangeType::RLA: + return 4; + case ExchangeType::SA: + case ExchangeType::RR: + case ExchangeType::RN: + case ExchangeType::RL: + return 6; + default: + isc_throw(BadValue, + "unrecognized exchange type '" << exchange_type << "'"); + } +} + +std::ostream& operator<<(std::ostream& os, ExchangeType xchg_type) +{ + switch(xchg_type) { + case ExchangeType::DO: + return(os << "DISCOVER-OFFER"); + case ExchangeType::RA: + return(os << "REQUEST-ACK"); + case ExchangeType::RNA: + return(os << "REQUEST-ACK (renewal)"); + case ExchangeType::RLA: + return(os << "RELEASE"); + case ExchangeType::SA: + return(os << "SOLICIT-ADVERTISE"); + case ExchangeType::RR: + return(os << "REQUEST-REPLY"); + case ExchangeType::RN: + return(os << "RENEW-REPLY"); + case ExchangeType::RL: + return(os << "RELEASE-REPLY"); + default: + return(os << "Unknown exchange type"); + } +} + + +ExchangeStats::ExchangeStats(const ExchangeType xchg_type, + const double drop_time, + const bool archive_enabled, + const boost::posix_time::ptime boot_time) + : xchg_type_(xchg_type), + sent_packets_(), + rcvd_packets_(), + archived_packets_(), + archive_enabled_(archive_enabled), + drop_time_(drop_time), + min_delay_(std::numeric_limits<double>::max()), + max_delay_(0.), + sum_delay_(0.), + sum_delay_squared_(0.), + orphans_(0), + collected_(0), + unordered_lookup_size_sum_(0), + unordered_lookups_(0), + ordered_lookups_(0), + sent_packets_num_(0), + rcvd_packets_num_(0), + non_unique_addr_num_(0), + rejected_leases_num_(0), + boot_time_(boot_time) +{ + next_sent_ = sent_packets_.begin(); +} + +void +ExchangeStats::updateDelays(const PktPtr& sent_packet, + const PktPtr& rcvd_packet) { + if (!sent_packet) { + isc_throw(BadValue, "Sent packet is null"); + } + if (!rcvd_packet) { + isc_throw(BadValue, "Received packet is null"); + } + + 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," + << " sent: " << sent_time + << " recv: " << rcvd_time); + } + 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 in " + << xchg_type_ << ".\nTime difference: " + << delta << ", sent: " << sent_time << ", rcvd: " + << rcvd_time << ".\nTrans ID: " << sent_packet->getTransid() + << "."); + } + + // 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; + sum_delay_squared_ += delta * delta; +} + +PktPtr +ExchangeStats::matchPackets(const PktPtr& rcvd_packet) { + using namespace boost::posix_time; + + if (!rcvd_packet) { + isc_throw(BadValue, "Received packet is null"); + } + + 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(PktPtr()); + } else if (next_sent_ == 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 iterator. + next_sent_ = 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 it with 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 ((*next_sent_)->getTransid() == rcvd_packet->getTransid()) { + ++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). + PktListTransidHashIndex& idx = sent_packets_.template get<1>(); + // Packets are grouped using transaction id masked 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 we need to iterate through the bucket + // to find the one that has desired transaction id. + std::pair<PktListTransidHashIterator,PktListTransidHashIterator> p = + idx.equal_range(hashTransid(rcvd_packet)); + // We want to keep statistics of unordered lookups to make + // sure that there is a right balance between 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); + bool non_expired_found = false; + // Removal can be done only after the loop + PktListRemovalQueue to_remove; + for (PktListTransidHashIterator it = p.first; it != p.second; ++it) { + // If transaction id is matching, we found the original + // packet sent to the server. Therefore, we reset the + // 'next sent' pointer to point to this location. We + // also indicate that the matching packet is found. + // Even though the packet has been found, we continue + // iterating over the bucket to remove all those packets + // that are timed out. + if (!packet_found && ((*it)->getTransid() == rcvd_packet->getTransid())) { + packet_found = true; + next_sent_ = sent_packets_.template project<0>(it); + } + + if (!non_expired_found) { + // Check if the packet should be removed due to timeout. + // This includes the packet matching the received one. + ptime now = microsec_clock::universal_time(); + ptime packet_time = (*it)->getTimestamp(); + time_period packet_period(packet_time, now); + if (!packet_period.is_null()) { + double period_fractional = + packet_period.length().total_seconds() + + (static_cast<double>(packet_period.length().fractional_seconds()) + / packet_period.length().ticks_per_second()); + if (drop_time_ > 0 && (period_fractional > drop_time_)) { + // Push the iterator on the removal queue. + to_remove.push(it); + + } else { + // We found first non-expired transaction. All other + // transactions within this bucket are considered + // non-expired because packets are held in the + // order of addition within the bucket. + non_expired_found = true; + } + } + } + + // If we found the packet and all expired transactions, + // there is nothing more to do. + if (non_expired_found && packet_found) { + break; + } + } + + // Deal with the removal queue. + while (!to_remove.empty()) { + PktListTransidHashIterator it = to_remove.front(); + to_remove.pop(); + // If timed out packet is not the one matching server response, + // we simply remove it and keep the pointer to the 'next sent' + // packet as it was. If the timed out packet appears to be the + // one that is matching the server response, we still want to + // remove it, but we need to update the 'next sent' pointer to + // point to a valid location. + if (sent_packets_.template project<0>(it) != next_sent_) { + eraseSent(sent_packets_.template project<0>(it)); + } else { + next_sent_ = eraseSent(sent_packets_.template project<0>(it)); + // We removed the matching packet because of the timeout. It + // means that there is no match anymore. + packet_found = false; + } + ++collected_; + } + } + + 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(PktPtr()); + } + + // Packet is matched so we count it. We don't count unmatched packets + // as they are counted as orphans with a separate counter. + ++rcvd_packets_num_; + PktPtr sent_packet(*next_sent_); + // 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. + next_sent_ = eraseSent(next_sent_); + return(sent_packet); +} + + +void +ExchangeStats::printTimestamps() { + // If archive mode is disabled there is no sense to proceed + // because we don't have packets and their timestamps. + if (!archive_enabled_) { + isc_throw(isc::InvalidOperation, + "packets archive mode is disabled"); + } + if (rcvd_packets_num_ == 0) { + std::cout << "Unavailable! No packets received." << std::endl; + } + // We will be using boost::posix_time extensively here + using namespace boost::posix_time; + + // Iterate through all received packets. + for (PktListIterator it = rcvd_packets_.begin(); + it != rcvd_packets_.end(); + ++it) { + PktPtr rcvd_packet = *it; + PktListTransidHashIndex& idx = + archived_packets_.template get<1>(); + std::pair<PktListTransidHashIterator, + PktListTransidHashIterator> p = + idx.equal_range(hashTransid(rcvd_packet)); + for (PktListTransidHashIterator it_archived = p.first; + it_archived != p.second; + ++it_archived) { + if ((*it_archived)->getTransid() == + rcvd_packet->getTransid()) { + PktPtr sent_packet = *it_archived; + // Get sent and received packet times. + ptime sent_time = sent_packet->getTimestamp(); + ptime rcvd_time = rcvd_packet->getTimestamp(); + // All sent and received packets should have timestamps + // set but if there is a bug somewhere and packet does + // not have timestamp we want to catch this here. + if (sent_time.is_not_a_date_time() || + rcvd_time.is_not_a_date_time()) { + isc_throw(InvalidOperation, + "packet time is not set"); + } + // Calculate durations of packets from beginning of epoch. + time_period sent_period(boot_time_, sent_time); + time_period rcvd_period(boot_time_, rcvd_time); + // Print timestamps for sent and received packet. + std::cout << "sent / received: " + << to_iso_string(sent_period.length()) + << " / " + << to_iso_string(rcvd_period.length()) + << std::endl; + break; + } + } + } +} + +StatsMgr::StatsMgr(CommandOptions& options) : + exchanges_(), + boot_time_(boost::posix_time::microsec_clock::universal_time()) +{ + // Check if packet archive mode is required. If user + // requested diagnostics option -x l or -x t we have to enable + // it so as StatsMgr preserves all packets. + archive_enabled_ = options.testDiags('l') || options.testDiags('t'); + + if (options.getIpVersion() == 4) { + addExchangeStats(ExchangeType::DO, options.getDropTime()[0]); + if (options.getExchangeMode() == CommandOptions::DORA_SARR) { + addExchangeStats(ExchangeType::RA, options.getDropTime()[1]); + } + if (options.getRenewRate() != 0) { + addExchangeStats(ExchangeType::RNA); + } + if (options.getReleaseRate() != 0) { + addExchangeStats(ExchangeType::RLA); + } + } else if (options.getIpVersion() == 6) { + addExchangeStats(ExchangeType::SA, options.getDropTime()[0]); + if (options.getExchangeMode() == CommandOptions::DORA_SARR) { + addExchangeStats(ExchangeType::RR, options.getDropTime()[1]); + } + if (options.getRenewRate() != 0) { + addExchangeStats(ExchangeType::RN); + } + if (options.getReleaseRate() != 0) { + addExchangeStats(ExchangeType::RL); + } + } + if (options.testDiags('i')) { + addCustomCounter("shortwait", "Short waits for packets"); + } +} + +std::string +ExchangeStats::receivedLeases() const { + // Get DHCP version. + int const v(dhcpVersion(xchg_type_)); + + std::stringstream result; + // Iterate through all received packets. + for (PktPtr const& packet : rcvd_packets_) { + + // Get client identifier. + if (v == 4) { + OptionPtr const& client_id_option( + packet->getOption(DHO_DHCP_CLIENT_IDENTIFIER)); + if (client_id_option) { + result << TestControl::vector2Hex(client_id_option->getData()); + } + } else if (v == 6) { + OptionPtr const& client_id_option(packet->getOption(D6O_CLIENTID)); + if (client_id_option) { + result << DUID(client_id_option->getData()).toText(); + } + } else { + isc_throw(BadValue, "unrecognized DHCP version '" << v << "'"); + } + result << ','; + + // Get address. + if (v == 4) { + Pkt4Ptr const& packet4(boost::dynamic_pointer_cast<Pkt4>(packet)); + if (packet4) { + result << packet4->getYiaddr().toText(); + } + } else if (v == 6) { + OptionPtr const& option(packet->getOption(D6O_IA_NA)); + if (option) { + Option6IAAddrPtr const& iaaddr( + boost::dynamic_pointer_cast<Option6IAAddr>( + option->getOption(D6O_IAADDR))); + if (iaaddr) { + result << iaaddr->getAddress().toText(); + } + } + } + result << ','; + + // Get prefix. + OptionPtr const& option(packet->getOption(D6O_IA_PD)); + if (option) { + Option6IAPrefixPtr const& iaprefix( + boost::dynamic_pointer_cast<Option6IAPrefix>( + option->getOption(D6O_IAPREFIX))); + if (iaprefix) { + result << iaprefix->getAddress().toText(); + } + } + + result << std::endl; + } + + return result.str(); +} + +void +ExchangeStats::printLeases() const { + std::cout << receivedLeases() << std::endl; +} + +void StatsMgr::printLeases() const { + for (auto const& exchange : exchanges_) { + std::cout << "***Leases for " << exchange.first << "***" << std::endl; + std::cout << "client_id,adrress,prefix" << std::endl; + exchange.second->printLeases(); + std::cout << std::endl; + } +} + +int ExchangeStats::malformed_pkts_{0}; + +} // namespace perfdhcp +} // namespace isc |