stats_mgr.h 44.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
    /// \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
242

        /// \brief Constructor
        ///
        /// \param xchg_type exchange type
        ExchangeStats(const ExchangeType xchg_type)
243
244
245
246
247
            : xchg_type_(xchg_type),
            min_delay_(std::numeric_limits<double>::max()),
            max_delay_(0.),
            sum_delay_(0.),
            orphans_(0),
248
            sum_delay_squared_(0.),
249
250
            ordered_lookups_(0),
            unordered_lookup_size_sum_(0),
251
252
253
            unordered_lookups_(0),
            sent_packets_num_(0),
            rcvd_packets_num_(0) {
254
255
256
257
258
259
260
            sent_packets_cache_ = sent_packets_.begin();
        }

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

271
272
273
274
275
        /// \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.
276
        /// \throw isc::BadValue if packet is null.
277
        void appendRcvd(const boost::shared_ptr<const T>& packet) {
278
279
280
            if (!packet) {
                isc_throw(BadValue, "Packet is null");
            }
281
            ++rcvd_packets_num_;
282
            rcvd_packets_.push_back(packet);
283
284
        }

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

303
304
305
306
307
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
            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;
335
            sum_delay_squared_ += delta * delta;
336
337
        }

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

358
            if (sent_packets_.size() == 0) {
359
360
361
362
                // 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.
363
                ++orphans_;
364
                return(boost::shared_ptr<const T>());
365
            } else if (sent_packets_cache_ == sent_packets_.end()) {
366
367
368
                // 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.
369
370
371
                sent_packets_cache_ = sent_packets_.begin();
            }

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

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

426
            boost::shared_ptr<const T> sent_packet(*sent_packets_cache_);
427
428
429
430
            // 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_);
431
            return(sent_packet);
432
433
        }

434
435
436
437
438
        /// \brief Return minumum delay between sent and received packet.
        ///
        /// Method returns minimum delay between sent and received packet.
        ///
        /// \return minimum delay between packets.
439
        double getMinDelay() const { return(min_delay_); }
440
441
442
443
444
445

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

448
        /// \brief Return avarage packet delay.
449
        ///
450
451
452
        /// 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.
453
        ///
454
455
456
457
458
459
460
        /// \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");
            }
461
            return(sum_delay_ / rcvd_packets_num_);
462
        }
463

464
        /// \brief Return standard deviation of packet delay.
465
        ///
466
467
468
469
        /// 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.
470
        ///
471
472
473
474
475
476
477
        /// \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");
            }
478
479
            return(sqrt(sum_delay_squared_ / rcvd_packets_num_ -
                        getAvgDelay() * getAvgDelay()));
480
        }
481
482
483
484
485
486
487
488

        /// \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.
489
        uint64_t getOrphans() const { return(orphans_); }
490
491
492
493

        /// \brief Return average unordered lookup set size.
        ///
        /// Method returns average unordered lookup set size.
494
495
        /// This value changes every time \ref ExchangeStats::findSent
        /// function performs unordered packet lookup.
496
        ///
497
498
        /// \throw isc::InvalidOperation if there have been no unordered
        /// lookups yet.
499
500
        /// \return average unordered lookup set size.
        double getAvgUnorderedLookupSetSize() const {
501
            if (unordered_lookups_ == 0) {
502
                isc_throw(InvalidOperation, "no unordered lookups");
503
            }
504
505
            return(static_cast<double>(unordered_lookup_size_sum_) /
                   static_cast<double>(unordered_lookups_));
506
507
508
509
510
511
512
513
514
515
        }

        /// \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.
516
        uint64_t getUnorderedLookups() const { return(unordered_lookups_); }
517
518
519
520
521
522
523
524
525
526

        /// \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.
527
        uint64_t getOrderedLookups() const { return(ordered_lookups_); }
528
529
530
531
532
533

        /// \brief Return total number of sent packets
        ///
        /// Method returns total number of sent packets.
        ///
        /// \return number of sent packets.
534
        uint64_t getSentPacketsNum() const { return(sent_packets_num_); }
535
536
537
538
539
540

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

543
544
545
546
547
548
549
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;
            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;
        }

574
575
576
577
578
579
580
        //// \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() {
581
582
583
            // We will be using boost::posix_time extensivelly here
            using namespace boost::posix_time;

584
585
586
587
            // Iterate through all received packets.
            for (PktListIterator it = rcvd_packets_.begin();
                 it != rcvd_packets_.end();
                 ++it) {
588
                boost::shared_ptr<const T> rcvd_packet = *it;
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
                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()) {
                            isc_throw(InvalidOperation, "packet time is not set");
                        }
                        // 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;
621
622
623
624
625
                    }
                }
            }
        }

626
627
628
629
    private:

        /// \brief Private default constructor.
        ///
630
        /// Default constructor is private because we want the client
631
632
633
        /// class to specify exchange type explicitely.
        ExchangeStats();

634
635
636
637
638
639
640
641
        /// \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) {
642
643
644
645
646
647
648
             // 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);
649
650
             // get<0>() template returns sequencial index to
             // container.
651
             return(sent_packets_.template get<0>().erase(it));
652
653
        }

654
655
656
657
658
659
660
661
662
663
        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.

664
665
666
667
668
        /// 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_;

669
670
671
672
673
674
        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.
675
        double sum_delay_squared_;     ///< Squared sum of delays between
676
                                       ///< sent and recived packets.
677

678
        uint64_t orphans_;   ///< Number of orphant received packets.
679
680
681
682
683
684

        /// 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.
685
        uint64_t unordered_lookup_size_sum_;
686

687
        uint64_t unordered_lookups_;   ///< Number of unordered sent packets
688
                                       ///< lookups.
689
        uint64_t ordered_lookups_;     ///< Number of ordered sent packets
690
691
                                       ///< lookups.

692
693
        uint64_t sent_packets_num_;    ///< Total number of sent packets.
        uint64_t rcvd_packets_num_;    ///< Total number of received packets.
694
695
696
697
698
699
700
    };

    /// 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
701
    typedef typename ExchangesMap::const_iterator ExchangesMapIterator;
702
703
704
    /// Map containing custom counters.
    typedef typename std::map<std::string, CustomCounterPtr> CustomCountersMap;
    /// Iterator for \ref CustomCountersMap.
705
    typedef typename CustomCountersMap::const_iterator CustomCountersMapIterator;
706
707
708
709
710
711

    /// \brief Constructor.
    StatsMgr()
        : exchanges_(),
          custom_counters_() {
    }
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727

    /// \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));
    }

728
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
    /// \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");
        }
757
        return(it->second);
758
759
760
761
762
763
764
765
766
767
    }

    /// \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);
768
        return(++(*counter));
769
770
    }

771
772
773
774
775
776
777
778
    /// \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
779
780
    /// \throw isc::BadValue if invalid exchange type specified or
    /// packet is null.
781
    void passSentPacket(const ExchangeType xchg_type,
782
                        const boost::shared_ptr<const T>& packet) {
783
784
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
        xchg_stats->appendSent(packet);
785
786
787
788
789
790
791
792
793
794
795
    }

    /// \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
796
797
    /// \throw isc::BadValue if invalid exchange type specified
    /// or packet is null.
798
799
800
    /// \throw isc::Unexpected if corresponding packet was not
    /// found on the list of sent packets.
    void passRcvdPacket(const ExchangeType xchg_type,
801
                        const boost::shared_ptr<const T>& packet) {
802
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
803
        boost::shared_ptr<const T> sent_packet
804
            = xchg_stats->findSent(packet);
805
806
807

        if (sent_packet) {
            xchg_stats->updateDelays(sent_packet, packet);
808
            xchg_stats->appendRcvd(packet);
809
810
811
        }
    }

812
813
814
815
816
817
818
819
820
821
    /// \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);
822
        return(xchg_stats->getMinDelay());
823
824
825
826
827
828
829
830
831
832
833
834
    }

    /// \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);
835
        return(xchg_stats->getMaxDelay());
836
837
    }

838
    /// \brief Return avarage packet delay.
839
    ///
840
841
    /// Method returns average packet delay for specified
    /// exchange type.
842
    ///
843
844
    /// \return average packet delay.
    double getAvgDelay(const ExchangeType xchg_type) const {
845
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
846
        return(xchg_stats->getAvgDelay());
847
848
    }

849
    /// \brief Return standard deviation of packet delay.
850
    ///
851
852
    /// Method returns standard deviation of packet delay
    /// for specified exchange type.
853
    ///
854
855
    /// \return standard deviation of packet delay.
    double getStdDevDelay(const ExchangeType xchg_type) const {
856
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
857
        return(xchg_stats->getStdDevDelay());
858
859
    }

860
861
862
863
864
865
866
867
    /// \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.
868
    uint64_t getOrphans(const ExchangeType xchg_type) const {
869
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
870
        return(xchg_stats->getOrphans());
871
    }
872
873
874
875

    /// \brief Return average unordered lookup set size.
    ///
    /// Method returns average unordered lookup set size.
876
877
    /// This value changes every time \ref ExchangeStats::findSent
    /// function performs unordered packet lookup.
878
879
    ///
    /// \param xchg_type exchange type.
880
    /// \throw isc::BadValue if invalid exchange type specified.
881
882
883
    /// \return average unordered lookup set size.
    double getAvgUnorderedLookupSetSize(const ExchangeType xchg_type) const {
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
884
        return(xchg_stats->getAvgUnorderedLookupSetSize());
885
886
887
888
889
890
891
892
893
    }

    /// \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.
    ///
894
895
    /// \param xchg_type exchange type.
    /// \throw isc::BadValue if invalid exchange type specified.
896
    /// \return number of unordered lookups.
897
    uint64_t getUnorderedLookups(const ExchangeType xchg_type) const {
898
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
899
        return(xchg_stats->getUnorderedLookups());
900
901
902
903
904
905
906
907
908
909
    }

    /// \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).
    ///
910
911
    /// \param xchg_type exchange type.
    /// \throw isc::BadValue if invalid exchange type specified.
912
    /// \return number of ordered lookups.
913
    uint64_t getOrderedLookups(const ExchangeType xchg_type) const {
914
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
915
        return(xchg_stats->getOrderedLookups());
916
917
    }

918
919
920
921
922
923
924
925
    /// \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.
926
    uint64_t getSentPacketsNum(const ExchangeType xchg_type) const {
927
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
928
        return(xchg_stats->getSentPacketsNum());
929
    }
930

931
932
933
934
935
936
937
938
    /// \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.
939
    uint64_t getRcvdPacketsNum(const ExchangeType xchg_type) const {
940
        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
941
        return(xchg_stats->getRcvdPacketsNum());
942
    }
943

944
945
946
947
948
949
950
951
952
953
    /// \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:
954
            return("DISCOVER-OFFER");
955
        case XCHG_RA:
956
            return("REQUEST-ACK");
957
        case XCHG_SA:
958
            return("SOLICIT-ADVERTISE");
959
        case XCHG_RR:
960
            return("REQUEST-REPLY");
961
        default:
962
            return("Unknown exchange type");
963
964
965
        }
    }

966
967
968
969
970
971
972
973
974
975
976
   /// \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.
     void printStats() const {
977
978
979
980
        for (ExchangesMapIterator it = exchanges_.begin();
             it != exchanges_.end();
             ++it) {
            ExchangeStatsPtr xchg_stats = it->second;
981
982
983
984
985
986
            std::cout << "***Statistics for: " << exchangeToString(it->first)
                      << "***" << std::endl;
            xchg_stats->printMainStats();
            std::cout << std::endl;
            xchg_stats->printRTTStats();
            std::cout << std::endl;
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
        }
    }

    /// \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;
1002
1003
1004
            std::cout << "***Timestamps for packets: "
                      << exchangeToString(it->first)
                      << "***" << std::endl;
1005
            xchg_stats->printTimestamps();
1006
            std::cout << std::endl;
1007
1008
1009
1010
1011
1012
1013
1014
1015
        }
    }

    /// \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) {
1016
            std::cout << "***Various statistics counters***" << std::endl;
1017
1018
1019
1020
1021
        }
        for (CustomCountersMapIterator it = custom_counters_.begin();
             it != custom_counters_.end();
             ++it) {
            CustomCounterPtr counter = it->second;
1022
            std::cout << counter->getName() << ": " << counter->getValue() << std::endl;
1023
1024
1025
        }
    }

1026
private:
1027
1028
1029
1030
1031
1032
1033
1034
    /// \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 {
1035
1036
1037
1038
        ExchangesMapIterator it = exchanges_.find(xchg_type);
        if (it == exchanges_.end()) {
            isc_throw(BadValue, "Packets exchange not specified");
        }
1039
        ExchangeStatsPtr xchg_stats = it->second;
1040
        return(xchg_stats);
1041
1042
    }

1043
1044
    ExchangesMap exchanges_;            ///< Map of exchange types.
    CustomCountersMap custom_counters_; ///< Map with custom counters.
1045
1046
1047
1048
1049
1050
};

} // namespace perfdhcp
} // namespace isc

#endif // __STATS_MGR_H