stats_mgr.h 41.4 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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
    /// \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;
        }

        /// \brief Return counter value.
        ///
        /// Method returns counter value.
        ///
        /// \return counter value.
91
        unsigned long long getValue() const {
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
            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 constrcutor is private because we don't want client
        /// class to call it because we want client class to specify
        /// counter's name.
        CustomCounter() { };

111
112
        unsigned long long counter_;  ///< Counter's value.
        std::string name_;            ///< Counter's name.
113
114
115
116
    };

    typedef typename boost::shared_ptr<CustomCounter> CustomCounterPtr;

117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
    /// 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:

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

152
153
154
155
156
157
        /// \brief List of packets (sent or received).
        ///
        /// List of packets based on multi index container allows efficient
        /// search of packets based on their sequence (order in which they
        /// were inserted) as well as based on packet transaction id.
        typedef boost::multi_index_container<
158
            boost::shared_ptr<const T>,
159
160
            boost::multi_index::indexed_by<
                boost::multi_index::sequenced<>,
161
                boost::multi_index::hashed_non_unique<
162
                        boost::multi_index::global_fun<
163
                            const boost::shared_ptr<const T>&,
164
                            uint32_t,
165
                            &ExchangeStats::hashTransid
166
                        >
167
168
169
170
171
172
173
                >,
                boost::multi_index::hashed_non_unique<
                    boost::multi_index::const_mem_fun<
                        T,
                        uint32_t,
                        &T::getTransid
                    >
174
175
176
177
178
                >
            >
        > PktList;

        /// Packet list iterator for sequencial access to elements.
179
        typedef typename PktList::const_iterator PktListIterator;
180
        /// Packet list index to search packets using transaction id hash.
181
        typedef typename PktList::template nth_index<1>::type
182
183
184
185
186
187
            PktListTransidHashIndex;
        /// Packet list iterator to access packets using transaction id hash.
        typedef typename PktListTransidHashIndex::const_iterator
            PktListTransidHashIterator;
        /// Packet list index to search packets using transaction id.
        typedef typename PktList::template nth_index<2>::type
188
189
            PktListTransidIndex;
        /// Packet list iterator to access packets using transaction id.
190
191
        typedef typename PktListTransidIndex::const_iterator
            PktListTransidIterator;
192
193
194
195
196

        /// \brief Constructor
        ///
        /// \param xchg_type exchange type
        ExchangeStats(const ExchangeType xchg_type)
197
198
199
200
201
            : xchg_type_(xchg_type),
            min_delay_(std::numeric_limits<double>::max()),
            max_delay_(0.),
            sum_delay_(0.),
            orphans_(0),
202
203
204
            square_sum_delay_(0.),
            ordered_lookups_(0),
            unordered_lookup_size_sum_(0),
205
206
207
            unordered_lookups_(0),
            sent_packets_num_(0),
            rcvd_packets_num_(0) {
208
209
210
211
212
213
214
            sent_packets_cache_ = sent_packets_.begin();
        }

        /// \brief Add new packet to list of sent packets.
        ///
        /// Method adds new packet to list of sent packets.
        ///
215
        /// \param packet packet object to be added.
216
        /// \throw isc::BadValue if packet is null.
217
        void appendSent(const boost::shared_ptr<const T>& packet) {
218
219
220
            if (!packet) {
                isc_throw(BadValue, "Packet is null");
            }
221
            ++sent_packets_num_;
222
223
224
            sent_packets_.template get<0>().push_back(packet);
        }

225
226
227
228
229
        /// \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.
230
        /// \throw isc::BadValue if packet is null.
231
        void appendRcvd(const boost::shared_ptr<const T>& packet) {
232
233
234
            if (!packet) {
                isc_throw(BadValue, "Packet is null");
            }
235
            ++rcvd_packets_num_;
236
            rcvd_packets_.push_back(packet);
237
238
        }

239
240
241
242
243
244
245
        ///  \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
246
        /// \throw isc::BadValue if sent or received packet is null.
247
        /// \throw isc::Unexpected if failed to calculate timestamps
248
249
        void updateDelays(const boost::shared_ptr<const T>& sent_packet,
                          const boost::shared_ptr<const T>& rcvd_packet) {
250
251
252
253
254
255
256
            if (!sent_packet) {
                isc_throw(BadValue, "Sent packet is null");
            }
            if (!rcvd_packet) {
                isc_throw(BadValue, "Received packet is null");
            }

257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
            boost::posix_time::ptime sent_time = sent_packet->getTimestamp();
            boost::posix_time::ptime rcvd_time = rcvd_packet->getTimestamp();

            if (sent_time.is_not_a_date_time() ||
                rcvd_time.is_not_a_date_time()) {
                isc_throw(Unexpected,
                          "Timestamp must be set for sent and "
                          "received packet to measure RTT");
            }
            boost::posix_time::time_period period(sent_time, rcvd_time);
            // We don't bother calculating deltas in nanoseconds. It is much
            // more convenient to use seconds instead because we are going to
            // sum them up.
            double delta =
                static_cast<double>(period.length().total_nanoseconds()) / 1e9;

            if (delta < 0) {
                isc_throw(Unexpected, "Sent packet's timestamp must not be "
                          "greater than received packet's timestamp");
            }

            // Record the minimum delay between sent and received packets.
            if (delta < min_delay_) {
                min_delay_ = delta;
            }
            // Record the maximum delay between sent and received packets.
            if (delta > max_delay_) {
                max_delay_ = delta;
            }
            // Update delay sum and square sum. That will be used to calculate
            // mean delays.
            sum_delay_ += delta;
            square_sum_delay_ += delta * delta;
        }

292
293
294
295
296
297
298
299
300
301
302
        /// \brief Find packet on the list of sent packets.
        ///
        /// Method finds packet with specified transaction id on the list
        /// of sent packets. It is used to match received packet with
        /// corresponding sent packet.
        /// Since packets from the server most often come in the same order
        /// as they were sent by client, this method will first check if
        /// next sent packet matches. If it doesn't, function will search
        /// the packet using indexing by transaction id. This reduces
        /// packet search time significantly.
        ///
303
        /// \param rcvd_packet received packet to be matched with sent packet.
304
        /// \throw isc::BadValue if received packet is null.
305
306
        /// \return packet having specified transaction or NULL if packet
        /// not found
307
        boost::shared_ptr<const T> findSent(const boost::shared_ptr<const T>& rcvd_packet) {
308
309
310
311
            if (!rcvd_packet) {
                isc_throw(BadValue, "Received packet is null");
            }

312
            if (sent_packets_.size() == 0) {
313
314
315
316
                // 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.
317
                ++orphans_;
318
                return boost::shared_ptr<const T>();
319
            } else if (sent_packets_cache_ == sent_packets_.end()) {
320
321
322
                // Even if there are still many unmatched packets on the
                // list we might hit the end of it because of unordered
                // lookups. The next logical step is to reset cache.
323
324
325
                sent_packets_cache_ = sent_packets_.begin();
            }

326
327
            // With this variable we will be signalling success or failure
            // to find the packet.
328
            bool packet_found = false;
329
330
            // Most likely responses are sent from the server in the same
            // order as client's requests to the server. We are caching
331
            // next sent packet and first try to match it with the next
332
333
334
            // 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.
335
            if ((*sent_packets_cache_)->getTransid() == rcvd_packet->getTransid()) {
336
                ++ordered_lookups_;
337
338
                packet_found = true;
            } else {
339
340
341
342
                // 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).
343
                PktListTransidHashIndex& idx = sent_packets_.template get<1>();
344
                // Packets are grouped using trasaction id masked with value
345
346
347
                // 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
348
                // bucket of packets and we need to iterate through the bucket
349
                // to find the one that has desired transaction id.
350
                std::pair<PktListTransidHashIterator,PktListTransidHashIterator> p =
351
                    idx.equal_range(hashTransid(rcvd_packet));
352
                // We want to keep statistics of unordered lookups to make
353
                // sure that there is a right balance between number of
354
355
356
                // 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.
357
                ++unordered_lookups_;
358
359
360
                // 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.
361
                unordered_lookup_size_sum_ += std::distance(p.first, p.second);
362
                for (PktListTransidHashIterator it = p.first; it != p.second;
363
                     ++it) {
364
                    if ((*it)->getTransid() == rcvd_packet->getTransid()) {
365
366
367
368
369
                        packet_found = true;
                        sent_packets_cache_ =
                            sent_packets_.template project<0>(it);
                        break;
                    }
370
371
372
373
                }
            }

            if (!packet_found) {
374
375
                // If we are here, it means that both ordered lookup and
                // unordered lookup failed. Searched packet is not on the list.
376
                ++orphans_;
377
                return boost::shared_ptr<const T>();
378
379
            }

380
            boost::shared_ptr<const T> sent_packet(*sent_packets_cache_);
381
382
383
384
            // If packet was found, we assume it will be never searched
            // again. We want to delete this packet from the list to
            // improve performance of future searches.
            sent_packets_cache_ = eraseSent(sent_packets_cache_);
385
386
387
            return sent_packet;
        }

388
389
390
391
392
        /// \brief Return minumum delay between sent and received packet.
        ///
        /// Method returns minimum delay between sent and received packet.
        ///
        /// \return minimum delay between packets.
393
        double getMinDelay() const { return min_delay_; }
394
395
396
397
398
399

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

402
        /// \brief Return avarage packet delay.
403
        ///
404
405
406
        /// 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.
407
        ///
408
409
410
411
412
413
414
415
416
        /// \throw isc::InvalidOperation if no packets for this exchange
        /// have been received yet.
        /// \return average packet delay.
        double getAvgDelay() const {
            if (sum_delay_ == 0) {
                isc_throw(InvalidOperation, "no packets received");
            }
            return sum_delay_ / rcvd_packets_num_;
        }
417

418
        /// \brief Return standard deviation of packet delay.
419
        ///
420
421
422
423
        /// 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.
424
        ///
425
426
427
428
429
430
431
432
433
434
        /// \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(square_sum_delay_ / rcvd_packets_num_ -
                        getAvgDelay() * getAvgDelay());
        }
435
436
437
438
439
440
441
442

        /// \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.
443
        unsigned long long getOrphans() const { return orphans_; }
444
445
446
447

        /// \brief Return average unordered lookup set size.
        ///
        /// Method returns average unordered lookup set size.
448
449
        /// This value changes every time \ref ExchangeStats::findSent
        /// function performs unordered packet lookup.
450
        ///
451
452
        /// \throw isc::InvalidOperation if there have been no unordered
        /// lookups yet.
453
454
        /// \return average unordered lookup set size.
        double getAvgUnorderedLookupSetSize() const {
455
            if (unordered_lookups_ == 0) {
456
                isc_throw(InvalidOperation, "no unordered lookups");
457
            }
458
459
460
461
462
463
464
465
466
467
468
469
            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.
470
        unsigned long long getUnorderedLookups() const { return unordered_lookups_; }
471
472
473
474
475
476
477
478
479
480

        /// \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.
481
        unsigned long long getOrderedLookups() const { return ordered_lookups_; }
482
483
484
485
486
487

        /// \brief Return total number of sent packets
        ///
        /// Method returns total number of sent packets.
        ///
        /// \return number of sent packets.
488
        unsigned long long getSentPacketsNum() const {
489
490
491
492
493
494
495
496
            return sent_packets_num_;
        }

        /// \brief Return total number of received packets
        ///
        /// Method returns total number of received packets.
        ///
        /// \return number of received packets.
497
        unsigned long long getRcvdPacketsNum() const {
498
499
500
            return rcvd_packets_num_;
        }

501
502
503
504
505
506
507
        //// \brief Print timestamps for sent and received packets.
        ///
        /// Method prints timestamps for all sent and received packets for
        /// packet exchange.
        ///
        /// \throw isc::InvalidOperation if found packet with no timestamp set.
        void printTimestamps() {
508
509
510
            // We will be using boost::posix_time extensivelly here
            using namespace boost::posix_time;

511
512
513
514
            // Iterate through all received packets.
            for (PktListIterator it = rcvd_packets_.begin();
                 it != rcvd_packets_.end();
                 ++it) {
515
                boost::shared_ptr<const T> rcvd_packet = *it;
516
517
518
519
520
521
522
523
                // Search for corresponding sent packet using transaction id
                // of received packet.
                PktListTransidIndex& idx = archived_packets_.template get<2>();
                PktListTransidIterator it_archived =
                    idx.find(rcvd_packet->getTransid());
                // This should not happen that there is no corresponding
                // sent packet. If it does however, we just drop the packet.
                if (it_archived != idx.end()) {
524
                    boost::shared_ptr<const T> sent_packet = *it_archived;
525
                    // Get sent and received packet times.
526
527
                    ptime sent_time = sent_packet->getTimestamp();
                    ptime rcvd_time = rcvd_packet->getTimestamp();
528
529
530
531
532
533
534
535
                    // 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.
536
537
538
                    ptime epoch_time(min_date_time);
                    time_period sent_period(epoch_time, sent_time);
                    time_period rcvd_period(epoch_time, rcvd_time);
539
                    // Print timestamps for sent and received packet.
540
541
542
543
                    std::cout << "sent / received: "
                              << to_iso_string(sent_period.length())
                              << " / " << to_iso_string(rcvd_period.length())
                              << std::endl;
544
545
546
547
                }
            }
        }

548
549
550
551
    private:

        /// \brief Private default constructor.
        ///
552
        /// Default constructor is private because we want the client
553
554
555
        /// class to specify exchange type explicitely.
        ExchangeStats();

556
557
558
559
560
561
562
563
        /// \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) {
564
565
566
567
568
569
570
571
             // 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);
             return sent_packets_.template get<0>().erase(it);
572
573
        }

574
575
576
577
578
579
580
581
582
583
        ExchangeType xchg_type_;             ///< Packet exchange type.
        PktList sent_packets_;               ///< List of sent packets.

        /// Iterator pointing to the packet on sent list which will most
        /// likely match next received packet. This is based on the
        /// assumption that server responds in order to incoming packets.
        PktListIterator sent_packets_cache_;

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

584
585
586
587
588
        /// 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_;

589
590
591
592
593
594
595
596
        double min_delay_;             ///< Minimum delay between sent
                                       ///< and received packets.
        double max_delay_;             ///< Maximum delay between sent
                                       ///< and received packets.
        double sum_delay_;             ///< Sum of delays between sent
                                       ///< and received packets.
        double square_sum_delay_;      ///< Square sum of delays between
                                       ///< sent and recived packets.
597

598
        unsigned long long orphans_;   ///< Number of orphant received packets.
599
600
601
602
603
604

        /// 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.
605
        unsigned long long unordered_lookup_size_sum_;
606

607
        unsigned long long unordered_lookups_;   ///< Number of unordered sent packets
608
                                       ///< lookups.
609
        unsigned long long ordered_lookups_;     ///< Number of ordered sent packets
610
611
                                       ///< lookups.

612
613
        unsigned long long sent_packets_num_;    ///< Total number of sent packets.
        unsigned long long rcvd_packets_num_;    ///< Total number of received packets.
614
615
616
617
618
619
620
    };

    /// 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
621
    typedef typename ExchangesMap::const_iterator ExchangesMapIterator;
622
623
624
    /// Map containing custom counters.
    typedef typename std::map<std::string, CustomCounterPtr> CustomCountersMap;
    /// Iterator for \ref CustomCountersMap.
625
    typedef typename CustomCountersMap::const_iterator CustomCountersMapIterator;
626
627
628
629
630
631

    /// \brief Constructor.
    StatsMgr()
        : exchanges_(),
          custom_counters_() {
    }
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647

    /// \brief Specify new exchange type.
    ///
    /// This method creates new \ref ExchangeStats object that will
    /// collect statistics data from packets exchange of the specified
    /// type.
    ///
    /// \param xchg_type exchange type.
    /// \throw isc::BadValue if exchange of specified type exists.
    void addExchangeStats(const ExchangeType xchg_type) {
        if (exchanges_.find(xchg_type) != exchanges_.end()) {
            isc_throw(BadValue, "Exchange of specified type already added.");
        }
        exchanges_[xchg_type] = ExchangeStatsPtr(new ExchangeStats(xchg_type));
    }

648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
    /// \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.
    /// \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.
    ///
    /// 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);
        return ++(*counter);
    }

691
692
693
694
695
696
697
698
    /// \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
699
700
    /// \throw isc::BadValue if invalid exchange type specified or
    /// packet is null.
701
    void passSentPacket(const ExchangeType xchg_type,
702
                        const boost::shared_ptr<const T>& packet) {
703
704
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
        xchg_stats->appendSent(packet);
705
706
707
708
709
710
711
712
713
714
715
    }

    /// \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
716
717
    /// \throw isc::BadValue if invalid exchange type specified
    /// or packet is null.
718
719
720
    /// \throw isc::Unexpected if corresponding packet was not
    /// found on the list of sent packets.
    void passRcvdPacket(const ExchangeType xchg_type,
721
                        const boost::shared_ptr<const T>& packet) {
722
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
723
        boost::shared_ptr<const T> sent_packet
724
            = xchg_stats->findSent(packet);
725
726
727

        if (sent_packet) {
            xchg_stats->updateDelays(sent_packet, packet);
728
            xchg_stats->appendRcvd(packet);
729
730
731
        }
    }

732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
    /// \brief Return minumum delay between sent and received packet.
    ///
    /// Method returns minimum delay between sent and received packet
    /// for specified exchange type.
    ///
    /// \param xchg_type exchange type.
    /// \throw isc::BadValue if invalid exchange type specified.
    /// \return minimum delay between packets.
    double getMinDelay(const ExchangeType xchg_type) const {
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
        return xchg_stats->getMinDelay();
    }

    /// \brief Return maxmimum delay between sent and received packet.
    ///
    /// Method returns maximum delay between sent and received packet
    /// for specified exchange type.
    ///
    /// \param xchg_type exchange type.
    /// \throw isc::BadValue if invalid exchange type specified.
    /// \return maximum delay between packets.
    double getMaxDelay(const ExchangeType xchg_type) const {
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
        return xchg_stats->getMaxDelay();
    }

758
    /// \brief Return avarage packet delay.
759
    ///
760
761
    /// Method returns average packet delay for specified
    /// exchange type.
762
    ///
763
764
    /// \return average packet delay.
    double getAvgDelay(const ExchangeType xchg_type) const {
765
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
766
        return xchg_stats->getAvgDelay();
767
768
    }

769
    /// \brief Return standard deviation of packet delay.
770
    ///
771
772
    /// Method returns standard deviation of packet delay
    /// for specified exchange type.
773
    ///
774
775
    /// \return standard deviation of packet delay.
    double getStdDevDelay(const ExchangeType xchg_type) const {
776
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
777
        return xchg_stats->getStdDevDelay();
778
779
    }

780
781
782
783
784
785
786
787
    /// \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.
788
    unsigned long long getOrphans(const ExchangeType xchg_type) const {
789
790
791
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
        return xchg_stats->getOrphans();
    }
792
793
794
795

    /// \brief Return average unordered lookup set size.
    ///
    /// Method returns average unordered lookup set size.
796
797
    /// This value changes every time \ref ExchangeStats::findSent
    /// function performs unordered packet lookup.
798
799
    ///
    /// \param xchg_type exchange type.
800
    /// \throw isc::BadValue if invalid exchange type specified.
801
802
803
804
805
806
807
808
809
810
811
812
813
    /// \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.
    ///
814
815
    /// \param xchg_type exchange type.
    /// \throw isc::BadValue if invalid exchange type specified.
816
    /// \return number of unordered lookups.
817
    unsigned long long getUnorderedLookups(const ExchangeType xchg_type) const {
818
819
820
821
822
823
824
825
826
827
828
829
        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).
    ///
830
831
    /// \param xchg_type exchange type.
    /// \throw isc::BadValue if invalid exchange type specified.
832
    /// \return number of ordered lookups.
833
    unsigned long long getOrderedLookups(const ExchangeType xchg_type) const {
834
835
836
837
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
        return xchg_stats->getOrderedLookups();
    }

838
839
840
841
842
843
844
845
    /// \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.
846
    unsigned long long getSentPacketsNum(const ExchangeType xchg_type) const {
847
848
849
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
        return xchg_stats->getSentPacketsNum();
    }
850

851
852
853
854
855
856
857
858
    /// \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.
859
    unsigned long long getRcvdPacketsNum(const ExchangeType xchg_type) const {
860
861
862
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
        return xchg_stats->getRcvdPacketsNum();
    }
863

864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
    /// \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:
            return "DISCOVER-OFFER";
        case XCHG_RA:
            return "REQUEST-ACK";
        case XCHG_SA:
            return "SOLICIT-ADVERTISE";
        case XCHG_RR:
            return "REQUEST-REPLY";
        default:
            return "Unknown exchange type";
        }
    }

    /// \brief Print main statistics for all exchange types.
    ///
    /// Method prints main statistics for all exchange types.
    /// Statistics includes: number of sent and received packets,
    /// number of dropped packets and number of orphans.
    void printMainStats() const {
        for (ExchangesMapIterator it = exchanges_.begin();
             it != exchanges_.end();
             ++it) {
            ExchangeStatsPtr xchg_stats = it->second;
896
897
898
899
900
901
902
903
904
905
            unsigned long long drops = xchg_stats->getRcvdPacketsNum() -
                xchg_stats->getSentPacketsNum();
            std::cout << "***Statistics for packet exchange "
                      << exchangeToString(it->first) << "***" << std::endl
                      << "sent: " << xchg_stats->getSentPacketsNum() << ", "
                      << "received: " << xchg_stats->getRcvdPacketsNum()
                      << std::endl
                      << "drops: " << drops << ", orphans: "
                      << xchg_stats->getOrphans()
                      << std::endl << std::endl;
906
907
908
909
910
911
912
913
914
915
916
        }
    }

    /// \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 {
917
        using namespace std;
918
919
920
921
        for (ExchangesMapIterator it = exchanges_.begin();
             it != exchanges_.end();
             ++it) {
            ExchangeStatsPtr xchg_stats = it->second;
922
923
924
925
926
927
928
929
930
931
932
            cout << "***Round trip time Statistics for packet exchange "
                 << exchangeToString(it->first) << "***" << endl
                 << fixed << setprecision(3)
                 << "min delay: " << xchg_stats->getMinDelay() * 1e3
                 << " ms" << endl
                 << "avg delay: " <<  xchg_stats->getAvgDelay() * 1e3
                 << " ms" << endl
                 << "max delay: " << xchg_stats->getMaxDelay() * 1e3
                 << " ms" << endl
                 << "std deviation: " << xchg_stats->getStdDevDelay() * 1e3
                 << " ms" << endl << endl;
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
        }
    }

    /// \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.
    void printTimestamps() const {
        for (ExchangesMapIterator it = exchanges_.begin();
             it != exchanges_.end();
             ++it) {
            ExchangeStatsPtr xchg_stats = it->second;
948
949
            std::cout << "***Timestamps for packets in exchange "
                      << exchangeToString(it->first) << "***" << std::endl;
950
            xchg_stats->printTimestamps();
951
            std::cout << std::endl;
952
953
954
955
956
957
958
959
960
        }
    }

    /// \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.
    void printCustomCounters() const {
        if (custom_counters_.size() > 0) {
961
            std::cout << "***Various statistics counters***" << std::endl;
962
963
964
965
966
        }
        for (CustomCountersMapIterator it = custom_counters_.begin();
             it != custom_counters_.end();
             ++it) {
            CustomCounterPtr counter = it->second;
967
            std::cout << counter->getName() << ": " << counter->getValue() << std::endl;
968
969
970
        }
    }

971
private:
972
973
974
975
976
977
978
979
    /// \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 {
980
981
982
983
        ExchangesMapIterator it = exchanges_.find(xchg_type);
        if (it == exchanges_.end()) {
            isc_throw(BadValue, "Packets exchange not specified");
        }
984
        ExchangeStatsPtr xchg_stats = it->second;
985
        return xchg_stats;
986
987
    }

988
989
    ExchangesMap exchanges_;            ///< Map of exchange types.
    CustomCountersMap custom_counters_; ///< Map with custom counters.
990
991
992
993
994
995
};

} // namespace perfdhcp
} // namespace isc

#endif // __STATS_MGR_H