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