stats_mgr.h 48.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// 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

18
#include <iostream>
19 20 21 22 23
#include <map>

#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/multi_index_container.hpp>
24
#include <boost/multi_index/hashed_index.hpp>
25
#include <boost/multi_index/sequenced_index.hpp>
26
#include <boost/multi_index/global_fun.hpp>
27
#include <boost/multi_index/mem_fun.hpp>
28
#include <boost/date_time/posix_time/posix_time.hpp>
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

#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:

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
    /// \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_;
76
            return(*this);
77 78 79 80 81 82
        }

        /// \brief Increment operator.
        const CustomCounter& operator++(int) {
            CustomCounter& this_counter(*this);
            operator++();
83
            return(this_counter);
84 85 86 87 88 89 90
        }

        /// \brief Return counter value.
        ///
        /// Method returns counter value.
        ///
        /// \return counter value.
91
        uint64_t getValue() const { return(counter_); }
92 93 94 95 96 97

        /// \brief Return counter name.
        ///
        /// Method returns counter name.
        ///
        /// \return counter name.
98
        const std::string& getName() const { return(name_); }
99 100 101 102 103 104 105 106
    private:
        /// \brief Default constructor.
        ///
        /// Default constrcutor is private because we don't want client
        /// class to call it because we want client class to specify
        /// counter's name.
        CustomCounter() { };

107
        uint64_t counter_;  ///< Counter's value.
108
        std::string name_;            ///< Counter's name.
109 110 111 112
    };

    typedef typename boost::shared_ptr<CustomCounter> CustomCounterPtr;

113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
    /// 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:

131 132 133 134 135 136 137 138 139 140
        /// \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.
141
        static uint32_t hashTransid(const boost::shared_ptr<const T>& packet) {
142 143 144
            if (!packet) {
                isc_throw(BadValue, "Packet is null");
            }
145
            return(packet->getTransid() & 1023);
146 147
        }

148 149 150 151
        /// \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
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
        /// 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 pacrticular 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
        /// packets_collection.push_back(pkt1);
        /// // Here is another way to add packet to the container. The result
        /// // is exactly the same as previously.
        /// packets_collection.template get<0>().push_back(pkt2);
        /// \endcode
        ///
        /// Example 2: Access elements through sequencial 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 hashed index
        /// \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
216
        typedef boost::multi_index_container<
217
            boost::shared_ptr<const T>,
218 219
            boost::multi_index::indexed_by<
                boost::multi_index::sequenced<>,
220
                boost::multi_index::hashed_non_unique<
221
                        boost::multi_index::global_fun<
222
                            const boost::shared_ptr<const T>&,
223
                            uint32_t,
224
                            &ExchangeStats::hashTransid
225
                        >
226 227 228 229 230
                >
            >
        > PktList;

        /// Packet list iterator for sequencial access to elements.
231
        typedef typename PktList::const_iterator PktListIterator;
232
        /// Packet list index to search packets using transaction id hash.
233
        typedef typename PktList::template nth_index<1>::type
234 235 236 237
            PktListTransidHashIndex;
        /// Packet list iterator to access packets using transaction id hash.
        typedef typename PktListTransidHashIndex::const_iterator
            PktListTransidHashIterator;
238 239 240 241

        /// \brief Constructor
        ///
        /// \param xchg_type exchange type
242 243
        /// \param archive_enabled if true packets archive mode is enabled.
        /// In this mode all packets are stored throughout the test execution.
244
        ExchangeStats(const ExchangeType xchg_type, const bool archive_enabled)
245 246 247 248 249
            : xchg_type_(xchg_type),
            min_delay_(std::numeric_limits<double>::max()),
            max_delay_(0.),
            sum_delay_(0.),
            orphans_(0),
250
            sum_delay_squared_(0.),
251 252
            ordered_lookups_(0),
            unordered_lookup_size_sum_(0),
253 254
            unordered_lookups_(0),
            sent_packets_num_(0),
255 256 257 258 259
            rcvd_packets_num_(0),
            sent_packets_(),
            rcvd_packets_(),
            archived_packets_(),
            archive_enabled_(archive_enabled) {
260
            next_sent_ = sent_packets_.begin();
261 262 263 264 265 266
        }

        /// \brief Add new packet to list of sent packets.
        ///
        /// Method adds new packet to list of sent packets.
        ///
267
        /// \param packet packet object to be added.
268
        /// \throw isc::BadValue if packet is null.
269
        void appendSent(const boost::shared_ptr<const T>& packet) {
270 271 272
            if (!packet) {
                isc_throw(BadValue, "Packet is null");
            }
273
            ++sent_packets_num_;
274 275 276
            sent_packets_.template get<0>().push_back(packet);
        }

277 278 279 280 281
        /// \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.
282
        /// \throw isc::BadValue if packet is null.
283
        void appendRcvd(const boost::shared_ptr<const T>& packet) {
284 285 286
            if (!packet) {
                isc_throw(BadValue, "Packet is null");
            }
287
            rcvd_packets_.push_back(packet);
288 289
        }

290 291 292 293 294 295 296
        ///  \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
297
        /// \throw isc::BadValue if sent or received packet is null.
298
        /// \throw isc::Unexpected if failed to calculate timestamps
299 300
        void updateDelays(const boost::shared_ptr<const T>& sent_packet,
                          const boost::shared_ptr<const T>& rcvd_packet) {
301 302 303 304 305 306 307
            if (!sent_packet) {
                isc_throw(BadValue, "Sent packet is null");
            }
            if (!rcvd_packet) {
                isc_throw(BadValue, "Received packet is null");
            }

308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
            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;
340
            sum_delay_squared_ += delta * delta;
341 342
        }

343
        /// \brief Match received packet with the corresponding sent packet.
344 345 346 347 348 349 350 351 352 353
        ///
        /// 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.
        ///
354
        /// \param rcvd_packet received packet to be matched with sent packet.
355
        /// \throw isc::BadValue if received packet is null.
356 357
        /// \return packet having specified transaction or NULL if packet
        /// not found
358
        boost::shared_ptr<const T> matchPackets(const boost::shared_ptr<const T>& rcvd_packet) {
359 360 361 362
            if (!rcvd_packet) {
                isc_throw(BadValue, "Received packet is null");
            }

363
            if (sent_packets_.size() == 0) {
364 365 366 367
                // 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.
368
                ++orphans_;
369
                return(boost::shared_ptr<const T>());
370
            } else if (next_sent_ == sent_packets_.end()) {
371 372
                // Even if there are still many unmatched packets on the
                // list we might hit the end of it because of unordered
373 374
                // lookups. The next logical step is to reset iterator.
                next_sent_ = sent_packets_.begin();
375 376
            }

377 378
            // With this variable we will be signalling success or failure
            // to find the packet.
379
            bool packet_found = false;
380 381
            // Most likely responses are sent from the server in the same
            // order as client's requests to the server. We are caching
382
            // next sent packet and first try to match it with the next
383 384 385
            // 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.
386
            if ((*next_sent_)->getTransid() == rcvd_packet->getTransid()) {
387
                ++ordered_lookups_;
388 389
                packet_found = true;
            } else {
390 391 392 393
                // 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).
394
                PktListTransidHashIndex& idx = sent_packets_.template get<1>();
395
                // Packets are grouped using trasaction id masked with value
396 397 398
                // 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
399
                // bucket of packets and we need to iterate through the bucket
400
                // to find the one that has desired transaction id.
401
                std::pair<PktListTransidHashIterator,PktListTransidHashIterator> p =
402
                    idx.equal_range(hashTransid(rcvd_packet));
403
                // We want to keep statistics of unordered lookups to make
404
                // sure that there is a right balance between number of
405 406 407
                // 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.
408
                ++unordered_lookups_;
409 410 411
                // 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.
412
                unordered_lookup_size_sum_ += std::distance(p.first, p.second);
413
                for (PktListTransidHashIterator it = p.first; it != p.second;
414
                     ++it) {
415
                    if ((*it)->getTransid() == rcvd_packet->getTransid()) {
416
                        packet_found = true;
417
                        next_sent_ =
418 419 420
                            sent_packets_.template project<0>(it);
                        break;
                    }
421 422 423 424
                }
            }

            if (!packet_found) {
425 426
                // If we are here, it means that both ordered lookup and
                // unordered lookup failed. Searched packet is not on the list.
427
                ++orphans_;
428
                return(boost::shared_ptr<const T>());
429 430
            }

431 432
            // Packet is matched so we count it. We don't count unmatched packets
            // as they are counted as orphans with a separate counter.
433
            ++rcvd_packets_num_;
434
            boost::shared_ptr<const T> sent_packet(*next_sent_);
435 436 437
            // 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.
438
            next_sent_ = eraseSent(next_sent_);
439
            return(sent_packet);
440 441
        }

442 443 444 445 446
        /// \brief Return minumum delay between sent and received packet.
        ///
        /// Method returns minimum delay between sent and received packet.
        ///
        /// \return minimum delay between packets.
447
        double getMinDelay() const { return(min_delay_); }
448 449 450 451 452 453

        /// \brief Return maxmimum delay between sent and received packet.
        ///
        /// Method returns maximum delay between sent and received packet.
        ///
        /// \return maximum delay between packets.
454
        double getMaxDelay() const { return(max_delay_); }
455

456
        /// \brief Return avarage packet delay.
457
        ///
458 459 460
        /// 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.
461
        ///
462 463 464 465
        /// \throw isc::InvalidOperation if no packets for this exchange
        /// have been received yet.
        /// \return average packet delay.
        double getAvgDelay() const {
466
            if (rcvd_packets_num_  == 0) {
467 468
                isc_throw(InvalidOperation, "no packets received");
            }
469
            return(sum_delay_ / rcvd_packets_num_);
470
        }
471

472
        /// \brief Return standard deviation of packet delay.
473
        ///
474 475 476 477
        /// 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.
478
        ///
479 480 481 482 483 484 485
        /// \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");
            }
486 487
            return(sqrt(sum_delay_squared_ / rcvd_packets_num_ -
                        getAvgDelay() * getAvgDelay()));
488
        }
489 490 491 492 493 494 495 496

        /// \brief Return number of orphant 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 orphant received packets.
497
        uint64_t getOrphans() const { return(orphans_); }
498 499 500 501

        /// \brief Return average unordered lookup set size.
        ///
        /// Method returns average unordered lookup set size.
502
        /// This value changes every time \ref ExchangeStats::matchPackets
503
        /// function performs unordered packet lookup.
504
        ///
505 506
        /// \throw isc::InvalidOperation if there have been no unordered
        /// lookups yet.
507 508
        /// \return average unordered lookup set size.
        double getAvgUnorderedLookupSetSize() const {
509
            if (unordered_lookups_ == 0) {
510
                isc_throw(InvalidOperation, "no unordered lookups");
511
            }
512 513
            return(static_cast<double>(unordered_lookup_size_sum_) /
                   static_cast<double>(unordered_lookups_));
514 515 516 517 518 519 520 521 522 523
        }

        /// \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.
524
        uint64_t getUnorderedLookups() const { return(unordered_lookups_); }
525 526 527 528 529 530 531 532 533 534

        /// \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.
535
        uint64_t getOrderedLookups() const { return(ordered_lookups_); }
536 537 538 539 540 541

        /// \brief Return total number of sent packets
        ///
        /// Method returns total number of sent packets.
        ///
        /// \return number of sent packets.
542
        uint64_t getSentPacketsNum() const { return(sent_packets_num_); }
543 544 545 546 547 548

        /// \brief Return total number of received packets
        ///
        /// Method returns total number of received packets.
        ///
        /// \return number of received packets.
549
        uint64_t getRcvdPacketsNum() const { return(rcvd_packets_num_); }
550

551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
        /// \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.
        void printMainStats() const {
            using namespace std;
            uint64_t drops = getRcvdPacketsNum() - getSentPacketsNum();
            cout << "sent packets: " << getSentPacketsNum() << endl
                 << "received packets: " << getRcvdPacketsNum() << endl
                 << "drops: " << drops << endl
                 << "orphans: " << getOrphans() << 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;
574 575 576 577 578 579 580 581 582 583
            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;
            } catch (const Exception& e) {
                cout << "Unavailable! No packets received." << endl;
            }
584 585
        }

586 587 588
        //// \brief Print timestamps for sent and received packets.
        ///
        /// Method prints timestamps for all sent and received packets for
589 590 591 592
        /// 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.
593
        ///
594 595
        /// \throw isc::InvalidOperation if found packet with no timestamp or
        /// if packets archive mode is disabled.
596
        void printTimestamps() {
597 598 599 600 601 602 603 604 605
            // 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;
            }
606 607 608
            // We will be using boost::posix_time extensivelly here
            using namespace boost::posix_time;

609 610 611 612
            // Iterate through all received packets.
            for (PktListIterator it = rcvd_packets_.begin();
                 it != rcvd_packets_.end();
                 ++it) {
613
                boost::shared_ptr<const T> rcvd_packet = *it;
614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
                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) {
                    if ((*it_archived)->getTransid() ==
                        rcvd_packet->getTransid()) {
                        boost::shared_ptr<const T> 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()) {
633 634
                            isc_throw(InvalidOperation,
                                      "packet time is not set");
635 636 637 638 639 640 641 642 643 644 645 646
                        }
                        // Calculate durations of packets from beginning of epoch.
                        ptime epoch_time(min_date_time);
                        time_period sent_period(epoch_time, sent_time);
                        time_period rcvd_period(epoch_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;
647 648 649 650 651
                    }
                }
            }
        }

652 653 654 655
    private:

        /// \brief Private default constructor.
        ///
656
        /// Default constructor is private because we want the client
657 658 659
        /// class to specify exchange type explicitely.
        ExchangeStats();

660 661 662 663 664 665 666 667
        /// \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) {
668 669 670 671 672 673 674 675 676
             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.
                 archived_packets_.push_back(*it);
             }
677 678
             // get<0>() template returns sequencial index to
             // container.
679
             return(sent_packets_.template get<0>().erase(it));
680 681
        }

682 683 684 685 686 687
        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.
688
        PktListIterator next_sent_;
689 690 691

        PktList rcvd_packets_;         ///< List of received packets.

692 693 694 695 696
        /// 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_;

697 698 699 700 701 702 703 704 705 706 707 708 709
        /// 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_;

710 711 712 713 714 715
        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.
716
        double sum_delay_squared_;     ///< Squared sum of delays between
717
                                       ///< sent and recived packets.
718

719
        uint64_t orphans_;   ///< Number of orphant received packets.
720 721 722 723 724 725

        /// 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 serach
        /// complexity.
726
        uint64_t unordered_lookup_size_sum_;
727

728
        uint64_t unordered_lookups_;   ///< Number of unordered sent packets
729
                                       ///< lookups.
730
        uint64_t ordered_lookups_;     ///< Number of ordered sent packets
731 732
                                       ///< lookups.

733 734
        uint64_t sent_packets_num_;    ///< Total number of sent packets.
        uint64_t rcvd_packets_num_;    ///< Total number of received packets.
735 736 737 738 739 740 741
    };

    /// 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
742
    typedef typename ExchangesMap::const_iterator ExchangesMapIterator;
743 744 745
    /// Map containing custom counters.
    typedef typename std::map<std::string, CustomCounterPtr> CustomCountersMap;
    /// Iterator for \ref CustomCountersMap.
746
    typedef typename CustomCountersMap::const_iterator CustomCountersMapIterator;
747

748
    /// \brief Constructor.
749 750 751 752 753 754 755 756 757 758 759 760
    ///
    /// 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.
    ///
    /// \param archive_enabled true indicates that packets
    /// archive mode is enabled.
761
    StatsMgr(const bool archive_enabled = false) :
762 763 764
        exchanges_(),
        custom_counters_(),
        archive_enabled_(archive_enabled) {
765
    }
766 767 768 769 770 771 772 773 774 775 776 777 778

    /// \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.");
        }
779 780
        exchanges_[xchg_type] =
            ExchangeStatsPtr(new ExchangeStats(xchg_type, archive_enabled_));
781 782
    }

783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804
    /// \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 Return specified counter.
    ///
    /// Method returns specified counter.
    ///
    /// \param counter_key key poiting to the counter in the counters map.
805
    /// The short counter name has to be used to access counter.
806 807 808 809 810 811 812
    /// \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");
        }
813
        return(it->second);
814 815 816 817 818 819 820 821 822 823
    }

    /// \brief Increment specified counter.
    ///
    /// Increement counter value by one.
    ///
    /// \param counter_key key poitinh to the counter in the counters map.
    /// \return pointer to specified counter after incrementation.
    const CustomCounter& IncrementCounter(const std::string& counter_key) {
        CustomCounterPtr counter = getCounter(counter_key);
824
        return(++(*counter));
825 826
    }

827 828 829 830 831 832 833 834
    /// \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
835 836
    /// \throw isc::BadValue if invalid exchange type specified or
    /// packet is null.
837
    void passSentPacket(const ExchangeType xchg_type,
838
                        const boost::shared_ptr<const T>& packet) {
839 840
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
        xchg_stats->appendSent(packet);
841 842 843 844 845 846 847 848 849 850 851
    }

    /// \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
852 853
    /// \throw isc::BadValue if invalid exchange type specified
    /// or packet is null.
854 855 856
    /// \throw isc::Unexpected if corresponding packet was not
    /// found on the list of sent packets.
    void passRcvdPacket(const ExchangeType xchg_type,
857
                        const boost::shared_ptr<const T>& packet) {
858
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
859
        boost::shared_ptr<const T> sent_packet
860
            = xchg_stats->matchPackets(packet);
861 862 863

        if (sent_packet) {
            xchg_stats->updateDelays(sent_packet, packet);
864 865 866
            if (archive_enabled_) {
                xchg_stats->appendRcvd(packet);
            }
867 868 869
        }
    }

870 871 872 873 874 875 876 877 878 879
    /// \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);
880
        return(xchg_stats->getMinDelay());
881 882 883 884 885 886 887 888 889 890 891 892
    }

    /// \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);
893
        return(xchg_stats->getMaxDelay());
894 895
    }

896
    /// \brief Return avarage packet delay.
897
    ///
898 899
    /// Method returns average packet delay for specified
    /// exchange type.
900
    ///
901 902
    /// \return average packet delay.
    double getAvgDelay(const ExchangeType xchg_type) const {
903
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
904
        return(xchg_stats->getAvgDelay());
905 906
    }

907
    /// \brief Return standard deviation of packet delay.
908
    ///
909 910
    /// Method returns standard deviation of packet delay
    /// for specified exchange type.
911
    ///
912 913
    /// \return standard deviation of packet delay.
    double getStdDevDelay(const ExchangeType xchg_type) const {
914
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
915
        return(xchg_stats->getStdDevDelay());
916 917
    }

918 919 920 921 922 923 924 925
    /// \brief Return number of orphant packets.
    ///
    /// Method returns number of orphant packets for specified
    /// exchange type.
    ///
    /// \param xchg_type exchange type.
    /// \throw isc::BadValue if invalid exchange type specified.
    /// \return number of orphant packets so far.
926
    uint64_t getOrphans(const ExchangeType xchg_type) const {
927
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
928
        return(xchg_stats->getOrphans());
929
    }
930 931 932 933

    /// \brief Return average unordered lookup set size.
    ///
    /// Method returns average unordered lookup set size.
934
    /// This value changes every time \ref ExchangeStats::matchPackets
935
    /// function performs unordered packet lookup.
936 937
    ///
    /// \param xchg_type exchange type.
938
    /// \throw isc::BadValue if invalid exchange type specified.
939 940 941
    /// \return average unordered lookup set size.
    double getAvgUnorderedLookupSetSize(const ExchangeType xchg_type) const {
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
942
        return(xchg_stats->getAvgUnorderedLookupSetSize());
943 944 945 946 947 948 949 950 951
    }

    /// \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.
    ///
952 953
    /// \param xchg_type exchange type.
    /// \throw isc::BadValue if invalid exchange type specified.
954
    /// \return number of unordered lookups.
955
    uint64_t getUnorderedLookups(const ExchangeType xchg_type) const {
956
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
957
        return(xchg_stats->getUnorderedLookups());
958 959 960 961 962 963 964 965 966 967
    }

    /// \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).
    ///
968 969
    /// \param xchg_type exchange type.
    /// \throw isc::BadValue if invalid exchange type specified.
970
    /// \return number of ordered lookups.
971
    uint64_t getOrderedLookups(const ExchangeType xchg_type) const {
972
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
973
        return(xchg_stats->getOrderedLookups());
974 975
    }

976 977 978 979 980 981 982 983
    /// \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.
984
    uint64_t getSentPacketsNum(const ExchangeType xchg_type) const {
985
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
986
        return(xchg_stats->getSentPacketsNum());
987
    }
988

989 990 991 992 993 994 995 996
    /// \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.
997
    uint64_t getRcvdPacketsNum(const ExchangeType xchg_type) const {
998
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
999
        return(xchg_stats->getRcvdPacketsNum());
1000
    }
1001

1002 1003 1004 1005 1006 1007 1008 1009 1010 1011
    /// \brief Return name of the exchange.
    ///
    /// Method returns name of the specified exchange type.
    /// This function is mainly for logging purposes.
    ///
    /// \param xchg_type exchange type.
    /// \return string representing name of the exchange.
    std::string exchangeToString(ExchangeType xchg_type) const {
        switch(xchg_type) {
        case XCHG_DO:
1012
            return("DISCOVER-OFFER");
1013
        case XCHG_RA:
1014
            return("REQUEST-ACK");
1015
        case XCHG_SA:
1016
            return("SOLICIT-ADVERTISE");
1017
        case XCHG_RR:
1018
            return("REQUEST-REPLY");
1019
        default:
1020
            return("Unknown exchange type");
1021 1022 1023
        }
    }

1024 1025 1026 1027 1028 1029 1030 1031 1032 1033
   /// \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.
1034 1035 1036
    ///
    /// \throw isc::InvalidOperation if no exchange type added to
    /// track statistics.
1037
     void printStats() const {
1038 1039 1040 1041
        if (exchanges_.size() == 0) {
            isc_throw(isc::InvalidOperation,
                      "no exchange type added for tracking");
        }
1042 1043 1044 1045
        for (ExchangesMapIterator it = exchanges_.begin();
             it != exchanges_.end();
             ++it) {
            ExchangeStatsPtr xchg_stats = it->second;
1046 1047 1048 1049 1050 1051
            std::cout << "***Statistics for: " << exchangeToString(it->first)
                      << "***" << std::endl;
            xchg_stats->printMainStats();
            std::cout << std::endl;
            xchg_stats->printRTTStats();
            std::cout << std::endl;
1052 1053 1054 1055 1056 1057 1058 1059 1060
        }
    }

    /// \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
1061 1062 1063 1064 1065
    /// 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.
1066
    void printTimestamps() const {
1067 1068 1069 1070
        if (exchanges_.size() == 0) {
            isc_throw(isc::InvalidOperation,
                      "no exchange type added for tracking");
        }
1071 1072 1073 1074
        for (ExchangesMapIterator it = exchanges_.begin();
             it != exchanges_.end();
             ++it) {
            ExchangeStatsPtr xchg_stats = it->second;
1075 1076 1077
            std::cout << "***Timestamps for packets: "
                      << exchangeToString(it->first)
                      << "***" << std::endl;
1078
            xchg_stats->printTimestamps();
1079
            std::cout << std::endl;
1080 1081 1082 1083 1084 1085 1086
        }
    }

    /// \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.
1087 1088
    ///
    /// \throw isc::InvalidOperation if no custom counters added for tracking.
1089
    void printCustomCounters() const {
1090 1091
        if (custom_counters_.size() == 0) {
            isc_throw(isc::InvalidOperation, "no custom counters specified");
1092 1093 1094 1095 1096
        }
        for (CustomCountersMapIterator it = custom_counters_.begin();
             it != custom_counters_.end();
             ++it) {
            CustomCounterPtr counter = it->second;
1097 1098
            std::cout << counter->getName() << ": " << counter->getValue()
                      << std::endl;
1099 1100 1101
        }
    }

1102
private:
1103

1104 1105 1106 1107 1108 1109 1110 1111
    /// \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 {
1112 1113 1114 1115
        ExchangesMapIterator it = exchanges_.find(xchg_type);
        if (it == exchanges_.end()) {
            isc_throw(BadValue, "Packets exchange not specified");
        }
1116
        ExchangeStatsPtr xchg_stats = it->second;
1117
        return(xchg_stats);
1118 1119
    }

1120 1121
    ExchangesMap exchanges_;            ///< Map of exchange types.
    CustomCountersMap custom_counters_; ///< Map with custom counters.
1122 1123 1124 1125 1126 1127 1128 1129 1130 1131

    /// 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_;
1132 1133 1134 1135 1136 1137
};

} // namespace perfdhcp
} // namespace isc

#endif // __STATS_MGR_H