Kea 2.7.6
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
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
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) {
363 addExchangeStats(ExchangeType::DO, options.getDropTime()[0]);
364 if (options.getExchangeMode() == CommandOptions::DORA_SARR) {
365 addExchangeStats(ExchangeType::RA, options.getDropTime()[1]);
366 }
367 if (options.getRenewRate() != 0) {
368 addExchangeStats(ExchangeType::RNA);
369 }
370 if (options.getReleaseRate() != 0) {
371 addExchangeStats(ExchangeType::RLA);
372 }
373 } else if (options.getIpVersion() == 6) {
374 addExchangeStats(ExchangeType::SA, options.getDropTime()[0]);
375 if (options.getExchangeMode() == CommandOptions::DORA_SARR) {
376 addExchangeStats(ExchangeType::RR, options.getDropTime()[1]);
377 }
378 if (options.getRenewRate() != 0) {
379 addExchangeStats(ExchangeType::RN);
380 }
381 if (options.getReleaseRate() != 0) {
382 addExchangeStats(ExchangeType::RL);
383 }
384 }
385 if (options.testDiags('i')) {
386 addCustomCounter("shortwait", "Short waits for packets");
387 }
388}
389
390std::string
391ExchangeStats::receivedLeases() const {
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
453ExchangeStats::printLeases() const {
454 std::cout << receivedLeases() << std::endl;
455}
456
457void StatsMgr::printLeases() const {
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
466int ExchangeStats::malformed_pkts_{0};
467
468} // namespace perfdhcp
469} // namespace isc
if(!(yy_init))
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.
int getReleaseRate() const
Returns a rate at which DHCPv6 Release messages are sent.
static uint32_t hashTransid(const dhcp::PktPtr &packet)
Hash transaction id of the packet.
std::queue< PktListTransidHashIterator > PktListRemovalQueue
Packet list iterator queue for removal.
void printTimestamps()
Print timestamps for sent and received packets.
PktListTransidHashIndex::const_iterator PktListTransidHashIterator
Packet list iterator to access packets using transaction id hash.
PktList::template nth_index< 1 >::type PktListTransidHashIndex
Packet list index to search packets using transaction id hash.
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.
StatsMgr(CommandOptions &options)
Constructor.
@ 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:998
@ 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.