DurationDataInterval::DurationDataInterval(const Timestamp& start_time /* = PktEvent::now()*/)
: start_time_(start_time), occurrences_(0),
min_duration_(pos_infin), max_duration_(neg_infin),
- total_duration_(microseconds(0)) {
-}
-
-void
+ total_duration_(microseconds(0)) { } void
DurationDataInterval::addDuration(const Duration& duration) {
++occurrences_;
if (duration < min_duration_) {
}
}
+Timestamp
+MonitoredDuration::getCurrentIntervalStart() const {
+ return (current_interval_ ? current_interval_->getStartTime()
+ : dhcp::PktEvent::MIN_TIME());
+}
+
bool
MonitoredDuration::addSample(const Duration& sample) {
auto now = PktEvent::now();
return (do_report);
}
+void
+MonitoredDuration::expireCurrentInterval() {
+ if (!current_interval_) {
+ isc_throw(InvalidOperation, "MonitoredDuration::expireInterval"
+ " - no current interval for: " << getLabel());
+ }
+
+ previous_interval_ = current_interval_;
+ current_interval_.reset();
+}
+
void
MonitoredDuration::clear() {
current_interval_.reset();
return (current_interval_);
}
- /// @brief Get the current interval.
+ /// @brief Get the current interval start time.
///
- /// @return Pointer to the current interval if it exists or an empty pointer.
- Timestamp getCurrentIntervalStart() const {
- return (current_interval_ ? current_interval_->getStartTime()
- : dhcp::PktEvent::EMPTY_TIME());
- }
+ /// @return Current interval's start time or MIN_TIME if there's no
+ /// current interval.
+ Timestamp getCurrentIntervalStart() const;
/// @brief Add a sample to the duration's current interval.
///
/// @return True if there is a newly completed (i.e. previous) interval to report.
bool addSample(const Duration& sample);
+ /// @brief Concludes the current interval
+ ///
+ /// Rotates current interval to previous and resets curent interval to empty.
+ ///
+ /// @throw InvalidOperation if there is no current interval.
+ void expireCurrentInterval();
+
/// @brief Deletes the current and previous intervals.
void clear();
#include <util/multi_threading_mgr.h>
using namespace isc;
+using namespace isc::dhcp;
using namespace isc::util;
+using namespace boost::posix_time;
namespace isc {
namespace perfmon {
durations_.clear();
}
+MonitoredDurationPtr
+MonitoredDurationStore::getReportsNext() {
+ MultiThreadingLock lock(*mutex_);
+ const auto& index = durations_.get<IntervalStartTag>();
+ // We want to find the oldest interval that is less than interval_duration in the past.
+ auto duration_iter = index.lower_bound(dhcp::PktEvent::now() - interval_duration_);
+ return (duration_iter == index.end() ? MonitoredDurationPtr()
+ : MonitoredDurationPtr(new MonitoredDuration(**duration_iter)));
+}
+
+MonitoredDurationCollectionPtr
+MonitoredDurationStore::getOverdueReports(const Timestamp& since /* = PktEvent::now() */) {
+ MultiThreadingLock lock(*mutex_);
+ // We use a lower bound of MIN + 1us to avoid dormant durations
+ static Timestamp lower_limit_time(PktEvent::MIN_TIME() + microseconds(1));
+
+ // We want to find anything since the start of time that who's start time
+ // is more than interval_duration_ in the past.
+ const auto& index = durations_.get<IntervalStartTag>();
+ auto lower_limit = index.lower_bound(lower_limit_time);
+ auto upper_limit = index.upper_bound(since - interval_duration_);
+
+ MonitoredDurationCollectionPtr collection(new MonitoredDurationCollection());
+ for (auto duration_iter = lower_limit; duration_iter != upper_limit; ++duration_iter) {
+ collection->push_back(MonitoredDurationPtr(new MonitoredDuration(**duration_iter)));
+ }
+
+ return (collection);
+}
+
} // end of namespace perfmon
} // end of namespace isc
/// @return a collection of all durations in the store.
MonitoredDurationCollectionPtr getAll();
+ /// @brief Fetches the duration which is due to report next.
+ ///
+ /// @return pointer to the matching duration or an empty pointer if
+ /// not found.
+ MonitoredDurationPtr getReportsNext();
+
+ /// @brief Fetches all durations that are overdue to report.
+ ///
+ /// @param since timestamp to search by. Defaults to current time.
+ ///
+ /// @return a collection of the matching durations, ordered by current interval
+ /// start time.
+ MonitoredDurationCollectionPtr getOverdueReports(const Timestamp& since
+ = dhcp::PktEvent::now());
+
/// @brief Removes all durations from the store.
void clear();
EXPECT_EQ(previous_interval->getTotalDuration(), (five_ms) * 2);
}
+ /// @brief Veriries getReportsNext and getOverdueReports
+ ///
+ /// @param family protocol family to test, AF_INET or AF_INET6
+ void reportDueTest(uint16_t family) {
+ // Create a store.
+ Duration interval_duration(milliseconds(100));
+ MonitoredDurationStore store(family, interval_duration);
+
+ // Create durations in the store, none of them will have intervals.
+ size_t num_subnets = 4;
+ std::vector<DurationKeyPtr> keys;
+ for (int s = 0; s < num_subnets; ++s) {
+ auto key = makeKey(family, s);
+ store.addDuration(key);
+ keys.push_back(key);
+ }
+
+ // No duration should be due to report.
+ MonitoredDurationPtr mond;
+ ASSERT_NO_THROW_LOG(mond = store.getReportsNext());
+ ASSERT_FALSE(mond);
+
+ // No durations should be over due to report.
+ MonitoredDurationCollectionPtr durations;
+ ASSERT_NO_THROW_LOG(durations = store.getOverdueReports());
+ ASSERT_TRUE(durations);
+ EXPECT_TRUE(durations->empty());
+
+ // Now add a 5ms sample to all four durations in reverse order
+ // This should give us a list of durations (by key) as follows:
+ //
+ // key[0] - interval start = now + 54ms
+ // key[1] - interval start = now + 52ms
+ // key[2] - interval start = now + 2ms
+ // key[3] - interval start = now
+ auto five_ms(milliseconds(5));
+ for (auto k = keys.rbegin(); k != keys.rend(); ++k ) {
+ ASSERT_NO_THROW_LOG(store.addDurationSample(*k, five_ms));
+ if ((*k)->getSubnetId() != 2) {
+ usleep(2 * 1000); // put 2ms gap between them
+ } else {
+ usleep(50 * 1000); // put 50ms gap between them.
+ }
+ }
+
+ // Key[3] should be returned by getReportsNext().
+ ASSERT_NO_THROW_LOG(mond = store.getReportsNext());
+ ASSERT_TRUE(mond);
+ EXPECT_EQ(*mond, *keys[3]) << "mond: " << mond->getLabel();
+
+ // None should be returned by getOverdueReports().
+ ASSERT_NO_THROW_LOG(durations = store.getOverdueReports());
+ ASSERT_TRUE(durations);
+ EXPECT_TRUE(durations->empty());
+
+ // Sleep for 50 ms.
+ usleep(50 * 1000);
+
+ // Key[1] should be returned by getReportsNext().
+ ASSERT_NO_THROW_LOG(mond = store.getReportsNext());
+ ASSERT_TRUE(mond);
+ EXPECT_EQ(*mond, *keys[1]) << "mond: " << mond->getLabel();
+
+ // Key[3] and key[2] should be returned by getOverdueReports().
+ ASSERT_NO_THROW_LOG(durations = store.getOverdueReports());
+ ASSERT_TRUE(durations);
+ EXPECT_EQ(durations->size(), 2);
+ EXPECT_EQ(*(*durations)[0], *keys[3]);
+ EXPECT_EQ(*(*durations)[1], *keys[2]);
+
+ // Sleep for 50 ms.
+ usleep(50 * 1000);
+
+ // None should report as reports next.
+ ASSERT_NO_THROW_LOG(mond = store.getReportsNext());
+ ASSERT_FALSE(mond);
+
+ // All should be found overdue.
+ ASSERT_NO_THROW_LOG(durations = store.getOverdueReports());
+ ASSERT_TRUE(durations);
+ EXPECT_EQ(durations->size(), keys.size());
+ EXPECT_EQ(*(*durations)[0], *keys[3]);
+ EXPECT_EQ(*(*durations)[1], *keys[2]);
+ EXPECT_EQ(*(*durations)[2], *keys[1]);
+ EXPECT_EQ(*(*durations)[3], *keys[0]);
+ }
+
/// @brief Test tool for gauging speed.
///
/// This test is really just a development tool for gauging performance.
addDurationSampleTest(AF_INET6);
}
+TEST_F(MonitoredDurationStoreTest, reportDue) {
+ reportDueTest(AF_INET);
+}
+
+TEST_F(MonitoredDurationStoreTest, reportDueMultiThreading) {
+ MultiThreadingTest mt;
+ reportDueTest(AF_INET);
+}
+
+TEST_F(MonitoredDurationStoreTest, reportDue6) {
+ reportDueTest(AF_INET);
+}
+
+TEST_F(MonitoredDurationStoreTest, reportDue6MultiThreading) {
+ MultiThreadingTest mt;
+ reportDueTest(AF_INET6);
+}
+
} // end of anonymous namespace
EXPECT_FALSE(mond->getPreviousInterval());
}
+TEST(MonitoredDuration, expireInterval) {
+ MonitoredDurationPtr mond;
+ Duration interval_duration(milliseconds(50));
+ auto start_time = PktEvent::now();
+ auto ten_ms = milliseconds(10);
+
+ // Create valid v4 duration with interval duration of 50ms.
+ ASSERT_NO_THROW_LOG(mond.reset(new MonitoredDuration(AF_INET, DHCPDISCOVER, DHCPOFFER,
+ "process_started", "process_completed",
+ SUBNET_ID_GLOBAL, interval_duration)));
+ ASSERT_TRUE(mond);
+
+ // Initially there are no intervals.
+ EXPECT_FALSE(mond->getCurrentInterval());
+ EXPECT_FALSE(mond->getPreviousInterval());
+ EXPECT_EQ(mond->getCurrentIntervalStart(), PktEvent::MIN_TIME());
+
+ ASSERT_THROW_MSG(mond->expireCurrentInterval(), InvalidOperation,
+ "MonitoredDuration::expireInterval - no current interval for:"
+ " DHCPDISCOVER-DHCPOFFER.process_started-process_completed.0");
+
+ ASSERT_NO_THROW(mond->addSample(ten_ms));
+ auto current_interval = mond->getCurrentInterval();
+ ASSERT_TRUE(current_interval);
+ EXPECT_EQ(current_interval->getOccurrences(), 1);
+ EXPECT_EQ(current_interval->getTotalDuration(), ten_ms);
+ EXPECT_FALSE(mond->getPreviousInterval());
+ EXPECT_GE(mond->getCurrentIntervalStart(), start_time);
+ EXPECT_EQ(mond->getCurrentIntervalStart(), current_interval->getStartTime());
+
+ ASSERT_NO_THROW_LOG(mond->expireCurrentInterval());
+ EXPECT_FALSE(mond->getCurrentInterval());
+
+ auto previous_interval = mond->getPreviousInterval();
+ EXPECT_EQ(previous_interval, current_interval);
+ EXPECT_EQ(mond->getCurrentIntervalStart(), PktEvent::MIN_TIME());
+}
+
} // end of anonymous namespace
/// @brief Fetch an empty timestamp, used for logic comparisons
///
/// @return an unset ptime.
- static boost::posix_time::ptime EMPTY_TIME() {
+ static boost::posix_time::ptime& EMPTY_TIME() {
static boost::posix_time::ptime empty_time;
return (empty_time);
}
+ /// @brief Fetches the minimum timestamp
+ ///
+ /// @return the minimum timestamp
+ static boost::posix_time::ptime& MIN_TIME() {
+ static auto min_time = boost::posix_time::ptime(boost::posix_time::min_date_time);
+ return (min_time);
+ }
+
+ /// @brief Fetches the maximum timestamp
+ ///
+ /// @return the maximum timestamp
+ static boost::posix_time::ptime& MAX_TIME() {
+ static auto max_time = boost::posix_time::ptime(boost::posix_time::max_date_time);
+ return (max_time);
+ }
+
/// @brief Label identifying this event.
std::string label_;