Kea  1.9.9-git
bin/perfdhcp/stats_mgr.cc
Go to the documentation of this file.
1 // Copyright (C) 2012-2021 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 
9 #include <dhcp/dhcp4.h>
10 #include <dhcp/dhcp6.h>
11 #include <dhcp/duid.h>
12 #include <dhcp/option6_iaaddr.h>
13 #include <dhcp/option6_iaprefix.h>
14 #include <dhcp/pkt4.h>
15 #include <perfdhcp/stats_mgr.h>
16 #include <perfdhcp/test_control.h>
17 
19 using isc::dhcp::DUID;
25 using isc::dhcp::Pkt4;
26 using isc::dhcp::Pkt4Ptr;
27 using isc::dhcp::PktPtr;
28 
29 namespace isc {
30 namespace perfdhcp {
31 
32 int dhcpVersion(ExchangeType const exchange_type) {
33  switch (exchange_type) {
34  case ExchangeType::DO:
35  case ExchangeType::RA:
36  case ExchangeType::RNA:
37  return 4;
38  case ExchangeType::SA:
39  case ExchangeType::RR:
40  case ExchangeType::RN:
41  case ExchangeType::RL:
42  return 6;
43  default:
45  "unrecognized exchange type '" << exchange_type << "'");
46  }
47 }
48 
49 std::ostream& operator<<(std::ostream& os, ExchangeType xchg_type)
50 {
51  switch(xchg_type) {
52  case ExchangeType::DO:
53  return(os << "DISCOVER-OFFER");
54  case ExchangeType::RA:
55  return(os << "REQUEST-ACK");
56  case ExchangeType::RNA:
57  return(os << "REQUEST-ACK (renewal)");
58  case ExchangeType::SA:
59  return(os << "SOLICIT-ADVERTISE");
60  case ExchangeType::RR:
61  return(os << "REQUEST-REPLY");
62  case ExchangeType::RN:
63  return(os << "RENEW-REPLY");
64  case ExchangeType::RL:
65  return(os << "RELEASE-REPLY");
66  default:
67  return(os << "Unknown exchange type");
68  }
69 }
70 
71 
72 ExchangeStats::ExchangeStats(const ExchangeType xchg_type,
73  const double drop_time,
74  const bool archive_enabled,
75  const boost::posix_time::ptime boot_time)
76  : xchg_type_(xchg_type),
77  sent_packets_(),
78  rcvd_packets_(),
79  archived_packets_(),
80  archive_enabled_(archive_enabled),
81  drop_time_(drop_time),
82  min_delay_(std::numeric_limits<double>::max()),
83  max_delay_(0.),
84  sum_delay_(0.),
85  sum_delay_squared_(0.),
86  orphans_(0),
87  collected_(0),
88  unordered_lookup_size_sum_(0),
89  unordered_lookups_(0),
90  ordered_lookups_(0),
91  sent_packets_num_(0),
92  rcvd_packets_num_(0),
93  non_unique_addr_num_(0),
94  rejected_leases_num_(0),
95  boot_time_(boot_time)
96 {
97  next_sent_ = sent_packets_.begin();
98 }
99 
100 void
102  const PktPtr& rcvd_packet) {
103  if (!sent_packet) {
104  isc_throw(BadValue, "Sent packet is null");
105  }
106  if (!rcvd_packet) {
107  isc_throw(BadValue, "Received packet is null");
108  }
109 
110  boost::posix_time::ptime sent_time = sent_packet->getTimestamp();
111  boost::posix_time::ptime rcvd_time = rcvd_packet->getTimestamp();
112 
113  if (sent_time.is_not_a_date_time() ||
114  rcvd_time.is_not_a_date_time()) {
116  "Timestamp must be set for sent and "
117  "received packet to measure RTT,"
118  << " sent: " << sent_time
119  << " recv: " << rcvd_time);
120  }
121  boost::posix_time::time_period period(sent_time, rcvd_time);
122  // We don't bother calculating deltas in nanoseconds. It is much
123  // more convenient to use seconds instead because we are going to
124  // sum them up.
125  double delta =
126  static_cast<double>(period.length().total_nanoseconds()) / 1e9;
127 
128  if (delta < 0) {
129  isc_throw(Unexpected, "Sent packet's timestamp must not be "
130  "greater than received packet's timestamp in "
131  << xchg_type_ << ".\nTime difference: "
132  << delta << ", sent: " << sent_time << ", rcvd: "
133  << rcvd_time << ".\nTrans ID: " << sent_packet->getTransid()
134  << ".");
135  }
136 
137  // Record the minimum delay between sent and received packets.
138  if (delta < min_delay_) {
139  min_delay_ = delta;
140  }
141  // Record the maximum delay between sent and received packets.
142  if (delta > max_delay_) {
143  max_delay_ = delta;
144  }
145  // Update delay sum and square sum. That will be used to calculate
146  // mean delays.
147  sum_delay_ += delta;
148  sum_delay_squared_ += delta * delta;
149 }
150 
151 PktPtr
152 ExchangeStats::matchPackets(const PktPtr& rcvd_packet) {
153  using namespace boost::posix_time;
154 
155  if (!rcvd_packet) {
156  isc_throw(BadValue, "Received packet is null");
157  }
158 
159  if (sent_packets_.size() == 0) {
160  // List of sent packets is empty so there is no sense
161  // to continue looking fo the packet. It also means
162  // that the received packet we got has no corresponding
163  // sent packet so orphans counter has to be updated.
164  ++orphans_;
165  return(PktPtr());
166  } else if (next_sent_ == sent_packets_.end()) {
167  // Even if there are still many unmatched packets on the
168  // list we might hit the end of it because of unordered
169  // lookups. The next logical step is to reset iterator.
170  next_sent_ = sent_packets_.begin();
171  }
172 
173  // With this variable we will be signalling success or failure
174  // to find the packet.
175  bool packet_found = false;
176  // Most likely responses are sent from the server in the same
177  // order as client's requests to the server. We are caching
178  // next sent packet and first try to match it with the next
179  // incoming packet. We are successful if there is no
180  // packet drop or out of order packets sent. This is actually
181  // the fastest way to look for packets.
182  if ((*next_sent_)->getTransid() == rcvd_packet->getTransid()) {
183  ++ordered_lookups_;
184  packet_found = true;
185  } else {
186  // If we are here, it means that we were unable to match the
187  // next incoming packet with next sent packet so we need to
188  // take a little more expensive approach to look packets using
189  // alternative index (transaction id & 1023).
190  PktListTransidHashIndex& idx = sent_packets_.template get<1>();
191  // Packets are grouped using transaction id masked with value
192  // of 1023. For instance, packets with transaction id equal to
193  // 1, 1024 ... will belong to the same group (a.k.a. bucket).
194  // When using alternative index we don't find the packet but
195  // bucket of packets and we need to iterate through the bucket
196  // to find the one that has desired transaction id.
197  std::pair<PktListTransidHashIterator,PktListTransidHashIterator> p =
198  idx.equal_range(hashTransid(rcvd_packet));
199  // We want to keep statistics of unordered lookups to make
200  // sure that there is a right balance between number of
201  // unordered lookups and ordered lookups. If number of unordered
202  // lookups is high it may mean that many packets are lost or
203  // sent out of order.
204  ++unordered_lookups_;
205  // We also want to keep the mean value of the bucket. The lower
206  // bucket size the better. If bucket sizes appear to big we
207  // might want to increase number of buckets.
208  unordered_lookup_size_sum_ += std::distance(p.first, p.second);
209  bool non_expired_found = false;
210  // Removal can be done only after the loop
211  PktListRemovalQueue to_remove;
212  for (PktListTransidHashIterator it = p.first; it != p.second; ++it) {
213  // If transaction id is matching, we found the original
214  // packet sent to the server. Therefore, we reset the
215  // 'next sent' pointer to point to this location. We
216  // also indicate that the matching packet is found.
217  // Even though the packet has been found, we continue
218  // iterating over the bucket to remove all those packets
219  // that are timed out.
220  if (!packet_found && ((*it)->getTransid() == rcvd_packet->getTransid())) {
221  packet_found = true;
222  next_sent_ = sent_packets_.template project<0>(it);
223  }
224 
225  if (!non_expired_found) {
226  // Check if the packet should be removed due to timeout.
227  // This includes the packet matching the received one.
228  ptime now = microsec_clock::universal_time();
229  ptime packet_time = (*it)->getTimestamp();
230  time_period packet_period(packet_time, now);
231  if (!packet_period.is_null()) {
232  double period_fractional =
233  packet_period.length().total_seconds() +
234  (static_cast<double>(packet_period.length().fractional_seconds())
235  / packet_period.length().ticks_per_second());
236  if (drop_time_ > 0 && (period_fractional > drop_time_)) {
237  // Push the iterator on the removal queue.
238  to_remove.push(it);
239 
240  } else {
241  // We found first non-expired transaction. All other
242  // transactions within this bucket are considered
243  // non-expired because packets are held in the
244  // order of addition within the bucket.
245  non_expired_found = true;
246  }
247  }
248  }
249 
250  // If we found the packet and all expired transactions,
251  // there is nothing more to do.
252  if (non_expired_found && packet_found) {
253  break;
254  }
255  }
256 
257  // Deal with the removal queue.
258  while (!to_remove.empty()) {
259  PktListTransidHashIterator it = to_remove.front();
260  to_remove.pop();
261  // If timed out packet is not the one matching server response,
262  // we simply remove it and keep the pointer to the 'next sent'
263  // packet as it was. If the timed out packet appears to be the
264  // one that is matching the server response, we still want to
265  // remove it, but we need to update the 'next sent' pointer to
266  // point to a valid location.
267  if (sent_packets_.template project<0>(it) != next_sent_) {
268  eraseSent(sent_packets_.template project<0>(it));
269  } else {
270  next_sent_ = eraseSent(sent_packets_.template project<0>(it));
271  // We removed the matching packet because of the timeout. It
272  // means that there is no match anymore.
273  packet_found = false;
274  }
275  ++collected_;
276  }
277  }
278 
279  if (!packet_found) {
280  // If we are here, it means that both ordered lookup and
281  // unordered lookup failed. Searched packet is not on the list.
282  ++orphans_;
283  return(PktPtr());
284  }
285 
286  // Packet is matched so we count it. We don't count unmatched packets
287  // as they are counted as orphans with a separate counter.
288  ++rcvd_packets_num_;
289  PktPtr sent_packet(*next_sent_);
290  // If packet was found, we assume it will be never searched
291  // again. We want to delete this packet from the list to
292  // improve performance of future searches.
293  next_sent_ = eraseSent(next_sent_);
294  return(sent_packet);
295 }
296 
297 
298 void
300  // If archive mode is disabled there is no sense to proceed
301  // because we don't have packets and their timestamps.
302  if (!archive_enabled_) {
304  "packets archive mode is disabled");
305  }
306  if (rcvd_packets_num_ == 0) {
307  std::cout << "Unavailable! No packets received." << std::endl;
308  }
309  // We will be using boost::posix_time extensively here
310  using namespace boost::posix_time;
311 
312  // Iterate through all received packets.
313  for (PktListIterator it = rcvd_packets_.begin();
314  it != rcvd_packets_.end();
315  ++it) {
316  PktPtr rcvd_packet = *it;
318  archived_packets_.template get<1>();
319  std::pair<PktListTransidHashIterator,
320  PktListTransidHashIterator> p =
321  idx.equal_range(hashTransid(rcvd_packet));
322  for (PktListTransidHashIterator it_archived = p.first;
323  it_archived != p.second;
324  ++it_archived) {
325  if ((*it_archived)->getTransid() ==
326  rcvd_packet->getTransid()) {
327  PktPtr sent_packet = *it_archived;
328  // Get sent and received packet times.
329  ptime sent_time = sent_packet->getTimestamp();
330  ptime rcvd_time = rcvd_packet->getTimestamp();
331  // All sent and received packets should have timestamps
332  // set but if there is a bug somewhere and packet does
333  // not have timestamp we want to catch this here.
334  if (sent_time.is_not_a_date_time() ||
335  rcvd_time.is_not_a_date_time()) {
337  "packet time is not set");
338  }
339  // Calculate durations of packets from beginning of epoch.
340  time_period sent_period(boot_time_, sent_time);
341  time_period rcvd_period(boot_time_, rcvd_time);
342  // Print timestamps for sent and received packet.
343  std::cout << "sent / received: "
344  << to_iso_string(sent_period.length())
345  << " / "
346  << to_iso_string(rcvd_period.length())
347  << std::endl;
348  break;
349  }
350  }
351  }
352 }
353 
355  exchanges_(),
356  boot_time_(boost::posix_time::microsec_clock::universal_time())
357 {
358  // Check if packet archive mode is required. If user
359  // requested diagnostics option -x l or -x t we have to enable
360  // it so as StatsMgr preserves all packets.
361  archive_enabled_ = options.testDiags('l') || options.testDiags('t');
362 
363  if (options.getIpVersion() == 4) {
365  if (options.getExchangeMode() == CommandOptions::DORA_SARR) {
367  }
368  if (options.getRenewRate() != 0) {
370  }
371 
372  } else if (options.getIpVersion() == 6) {
374  if (options.getExchangeMode() == CommandOptions::DORA_SARR) {
376  }
377  if (options.getRenewRate() != 0) {
379  }
380  if (options.getReleaseRate() != 0) {
382  }
383  }
384  if (options.testDiags('i')) {
385  addCustomCounter("shortwait", "Short waits for packets");
386  }
387 }
388 
389 std::string
391  // Get DHCP version.
392  int const v(dhcpVersion(xchg_type_));
393 
394  std::stringstream result;
395  // Iterate through all received packets.
396  for (PktPtr const& packet : rcvd_packets_) {
397 
398  // Get client identifier.
399  if (v == 4) {
400  OptionPtr const& client_id_option(
401  packet->getOption(DHO_DHCP_CLIENT_IDENTIFIER));
402  if (client_id_option) {
403  result << TestControl::vector2Hex(client_id_option->getData());
404  }
405  } else if (v == 6) {
406  OptionPtr const& client_id_option(packet->getOption(D6O_CLIENTID));
407  if (client_id_option) {
408  result << DUID(client_id_option->getData()).toText();
409  }
410  } else {
411  isc_throw(BadValue, "unrecognized DHCP version '" << v << "'");
412  }
413  result << ',';
414 
415  // Get address.
416  if (v == 4) {
417  Pkt4Ptr const& packet4(boost::dynamic_pointer_cast<Pkt4>(packet));
418  if (packet4) {
419  result << packet4->getYiaddr().toText();
420  }
421  } else if (v == 6) {
422  OptionPtr const& option(packet->getOption(D6O_IA_NA));
423  if (option) {
424  Option6IAAddrPtr const& iaaddr(
425  boost::dynamic_pointer_cast<Option6IAAddr>(
426  option->getOption(D6O_IAADDR)));
427  if (iaaddr) {
428  result << iaaddr->getAddress().toText();
429  }
430  }
431  }
432  result << ',';
433 
434  // Get prefix.
435  OptionPtr const& option(packet->getOption(D6O_IA_PD));
436  if (option) {
437  Option6IAPrefixPtr const& iaprefix(
438  boost::dynamic_pointer_cast<Option6IAPrefix>(
439  option->getOption(D6O_IAPREFIX)));
440  if (iaprefix) {
441  result << iaprefix->getAddress().toText();
442  }
443  }
444 
445  result << std::endl;
446  }
447 
448  return result.str();
449 }
450 
451 void
453  std::cout << receivedLeases() << std::endl;
454 }
455 
456 void StatsMgr::printLeases() const {
457  for (auto const& exchange : exchanges_) {
458  std::cout << "***Leases for " << exchange.first << "***" << std::endl;
459  std::cout << "client_id,adrress,prefix" << std::endl;
460  exchange.second->printLeases();
461  std::cout << std::endl;
462  }
463 }
464 
466 
467 } // namespace perfdhcp
468 } // namespace isc
DHCPv6 SOLICIT-ADVERTISE.
ExchangeType
DHCP packet exchange types.
static std::string vector2Hex(const std::vector< uint8_t > &vec, const std::string &separator="")
Convert vector in hexadecimal string.
static uint32_t hashTransid(const dhcp::PktPtr &packet)
Hash transaction id of the packet.
std::queue< PktListTransidHashIterator > PktListRemovalQueue
Packet list iterator queue for removal.
boost::shared_ptr< Option > OptionPtr
Definition: option.h:36
STL namespace.
void updateDelays(const dhcp::PktPtr &sent_packet, const dhcp::PktPtr &rcvd_packet)
Update delay counters.
Holds DUID (DHCPv6 Unique Identifier)
Definition: duid.h:27
std::vector< double > getDropTime() const
Returns drop time.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
StatsMgr(CommandOptions &options)
Constructor.
boost::shared_ptr< Option6IAPrefix > Option6IAPrefixPtr
Pointer to the Option6IAPrefix object.
DHCPv4 REQUEST-ACK (renewal)
A generic exception that is thrown when an unexpected error condition occurs.
DHCPv6 RELEASE-REPLY.
void printLeases() const
Print the list of received leases.
int dhcpVersion(ExchangeType const exchange_type)
Get the DHCP version that fits the exchange type.
std::ostream & operator<<(std::ostream &os, ExchangeType xchg_type)
Return name of the exchange.
void printLeases() const
Delegate to all exchanges to print their leases.
boost::shared_ptr< Pkt4 > Pkt4Ptr
A pointer to Pkt4 object.
Definition: pkt4.h:544
uint8_t getIpVersion() const
Returns IP version.
boost::shared_ptr< isc::dhcp::Pkt > PktPtr
A pointer to either Pkt4 or Pkt6 packet.
Definition: pkt.h:797
dhcp::PktPtr matchPackets(const dhcp::PktPtr &rcvd_packet)
Match received packet with the corresponding sent packet.
Defines the logger used by the top-level component of kea-dhcp-ddns.
PktList::iterator PktListIterator
Packet list iterator for sequential access to elements.
PktList::template nth_index< 1 >::type PktListTransidHashIndex
Packet list index to search packets using transaction id hash.
Represents DHCPv4 packet.
Definition: pkt4.h:37
void addExchangeStats(const ExchangeType xchg_type, const double drop_time=-1)
Specify new exchange type.
DHCPv4 DISCOVER-OFFER.
PktListTransidHashIndex::const_iterator PktListTransidHashIterator
Packet list iterator to access packets using transaction id hash.
DHCPv6 REQUEST-REPLY.
ExchangeMode getExchangeMode() const
Returns packet exchange mode.
int getRenewRate() const
Returns a rate at which DHCPv6 Renew messages are sent.
A generic exception that is thrown if a function is called in a prohibited way.
bool testDiags(const char diag)
Find if diagnostic flag has been set.
int getReleaseRate() const
Returns a rate at which DHCPv6 Release messages are sent.
void addCustomCounter(const std::string &short_name, const std::string &long_name)
Add named custom uint64 counter.
Class that represents IAPREFIX option in DHCPv6.
void printTimestamps()
Print timestamps for sent and received packets.
boost::shared_ptr< Option6IAAddr > Option6IAAddrPtr
A pointer to the isc::dhcp::Option6IAAddr object.
std::string receivedLeases() const
Return the list of received leases in CSV format as string.