[svn] commit: r4026 - in /trunk: ./ src/bin/auth/ src/bin/auth/benchmarks/ src/bin/auth/tests/ src/bin/auth/tests/testdata/ src/bin/recurse/tests/ src/lib/asiolink/ src/lib/asiolink/tests/ src/lib/log/ src/lib/python/isc/utils/ src/lib/testutils/
BIND 10 source code commits
bind10-changes at lists.isc.org
Tue Dec 28 03:50:44 UTC 2010
Author: y-aharen
Date: Tue Dec 28 03:50:44 2010
New Revision: 4026
Log:
Merged trac #347: Implement query counters in auth module (branches/trac347 r3685:r4016)
Added:
trunk/src/bin/auth/statistics.cc
- copied unchanged from r4016, branches/trac347/src/bin/auth/statistics.cc
trunk/src/bin/auth/statistics.h
- copied unchanged from r4016, branches/trac347/src/bin/auth/statistics.h
trunk/src/bin/auth/tests/statistics_unittest.cc
- copied unchanged from r4016, branches/trac347/src/bin/auth/tests/statistics_unittest.cc
Modified:
trunk/ (props changed)
trunk/ChangeLog
trunk/configure.ac
trunk/src/bin/auth/Makefile.am
trunk/src/bin/auth/auth.spec.pre.in
trunk/src/bin/auth/auth_srv.cc
trunk/src/bin/auth/auth_srv.h
trunk/src/bin/auth/benchmarks/Makefile.am
trunk/src/bin/auth/main.cc
trunk/src/bin/auth/tests/Makefile.am
trunk/src/bin/auth/tests/auth_srv_unittest.cc
trunk/src/bin/auth/tests/testdata/ (props changed)
trunk/src/bin/recurse/tests/ (props changed)
trunk/src/lib/asiolink/ (props changed)
trunk/src/lib/asiolink/asiolink.cc
trunk/src/lib/asiolink/asiolink.h
trunk/src/lib/asiolink/tests/asiolink_unittest.cc
trunk/src/lib/log/ (props changed)
trunk/src/lib/python/isc/utils/ (props changed)
trunk/src/lib/testutils/ (props changed)
Modified: trunk/ChangeLog
==============================================================================
--- trunk/ChangeLog (original)
+++ trunk/ChangeLog Tue Dec 28 03:50:44 2010
@@ -1,3 +1,14 @@
+ 140. [func] y-aharen
+ src/bin/auth: Added a feature to count queries and send counter
+ values to statistics periodically. To support it, added wrapping
+ class of asio::deadline_timer to use as interval timer.
+ The counters can be seen using the "Stats show" command from
+ bindctl. The result would look like:
+ ... "auth.queries.tcp": 1, "auth.queries.udp": 1 ...
+ Using the "Auth sendstats" command you can make b10-auth send the
+ counters to b10-stats immediately.
+ (Trac #347, svn r4026)
+
139. [build] jreed
Introduced configure option and make targets for generating
Python code coverage report. This adds new make targets:
Modified: trunk/configure.ac
==============================================================================
--- trunk/configure.ac (original)
+++ trunk/configure.ac Tue Dec 28 03:50:44 2010
@@ -371,7 +371,7 @@
BOOST_INCLUDES="-I${boost_include_path}"
CPPFLAGS="$CPPFLAGS $BOOST_INCLUDES"
fi
-AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp],,
+AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp boost/date_time/posix_time/posix_time_types.hpp boost/bind.hpp boost/function.hpp],,
AC_MSG_ERROR([Missing required header files.]))
CPPFLAGS="$CPPFLAGS_SAVES"
AC_SUBST(BOOST_INCLUDES)
Modified: trunk/src/bin/auth/Makefile.am
==============================================================================
--- trunk/src/bin/auth/Makefile.am (original)
+++ trunk/src/bin/auth/Makefile.am Tue Dec 28 03:50:44 2010
@@ -41,6 +41,7 @@
b10_auth_SOURCES += change_user.cc change_user.h
b10_auth_SOURCES += config.cc config.h
b10_auth_SOURCES += common.h
+b10_auth_SOURCES += statistics.cc statistics.h
b10_auth_SOURCES += main.cc
b10_auth_LDADD = $(top_builddir)/src/lib/datasrc/libdatasrc.la
b10_auth_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
Modified: trunk/src/bin/auth/auth.spec.pre.in
==============================================================================
--- trunk/src/bin/auth/auth.spec.pre.in (original)
+++ trunk/src/bin/auth/auth.spec.pre.in Tue Dec 28 03:50:44 2010
@@ -59,6 +59,11 @@
"command_name": "shutdown",
"command_description": "Shut down authoritative DNS server",
"command_args": []
+ },
+ {
+ "command_name": "sendstats",
+ "command_description": "Send data to a statistics module at once",
+ "command_args": []
}
]
}
Modified: trunk/src/bin/auth/auth_srv.cc
==============================================================================
--- trunk/src/bin/auth/auth_srv.cc (original)
+++ trunk/src/bin/auth/auth_srv.cc Tue Dec 28 03:50:44 2010
@@ -55,6 +55,7 @@
#include <auth/config.h>
#include <auth/auth_srv.h>
#include <auth/query.h>
+#include <auth/statistics.h>
using namespace std;
@@ -100,6 +101,9 @@
/// Hot spot cache
isc::datasrc::HotCache cache_;
+
+ /// Query counters for statistics
+ AuthCounters counters_;
private:
std::string db_file_;
@@ -111,6 +115,9 @@
bool xfrout_connected_;
AbstractXfroutClient& xfrout_client_;
+
+ /// Increment query counter
+ void incCounter(const int protocol);
};
AuthSrvImpl::AuthSrvImpl(const bool use_cache,
@@ -118,6 +125,7 @@
config_session_(NULL), verbose_mode_(false),
xfrin_session_(NULL),
memory_datasrc_class_(RRClass::IN()),
+ counters_(verbose_mode_),
xfrout_connected_(false),
xfrout_client_(xfrout_client)
{
@@ -294,6 +302,11 @@
impl_->config_session_ = config_session;
}
+void
+AuthSrv::setStatisticsSession(AbstractSession* statistics_session) {
+ impl_->counters_.setStatisticsSession(statistics_session);
+}
+
ModuleCCSession*
AuthSrv::getConfigSession() const {
return (impl_->config_session_);
@@ -429,6 +442,9 @@
message->setHeaderFlag(Message::HEADERFLAG_AA);
message->setRcode(Rcode::NOERROR());
+ // Increment query counter.
+ incCounter(io_message.getSocket().getProtocol());
+
if (remote_edns) {
EDNSPtr local_edns = EDNSPtr(new EDNS());
local_edns->setDNSSECAwareness(dnssec_ok);
@@ -476,6 +492,9 @@
AuthSrvImpl::processAxfrQuery(const IOMessage& io_message, MessagePtr message,
OutputBufferPtr buffer)
{
+ // Increment query counter.
+ incCounter(io_message.getSocket().getProtocol());
+
if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
if (verbose_mode_) {
cerr << "[b10-auth] AXFR query over UDP isn't allowed" << endl;
@@ -601,6 +620,19 @@
return (true);
}
+void
+AuthSrvImpl::incCounter(const int protocol) {
+ // Increment query counter.
+ if (protocol == IPPROTO_UDP) {
+ counters_.inc(AuthCounters::COUNTER_UDP_QUERY);
+ } else if (protocol == IPPROTO_TCP) {
+ counters_.inc(AuthCounters::COUNTER_TCP_QUERY);
+ } else {
+ // unknown protocol
+ isc_throw(Unexpected, "Unknown protocol: " << protocol);
+ }
+}
+
ConstElementPtr
AuthSrvImpl::setDbFile(ConstElementPtr config) {
ConstElementPtr answer = isc::config::createAnswer();
@@ -670,3 +702,12 @@
return (isc::config::createAnswer(1, error.what()));
}
}
+
+bool AuthSrv::submitStatistics() const {
+ return (impl_->counters_.submitStatistics());
+}
+
+uint64_t
+AuthSrv::getCounter(const AuthCounters::CounterType type) const {
+ return (impl_->counters_.getCounter(type));
+}
Modified: trunk/src/bin/auth/auth_srv.h
==============================================================================
--- trunk/src/bin/auth/auth_srv.h (original)
+++ trunk/src/bin/auth/auth_srv.h Tue Dec 28 03:50:44 2010
@@ -27,6 +27,7 @@
#include <config/ccsession.h>
#include <asiolink/asiolink.h>
+#include <auth/statistics.h>
namespace isc {
namespace datasrc {
@@ -62,6 +63,7 @@
///
/// The design of this class is still in flux. It's quite likely to change
/// in future versions.
+///
class AuthSrv {
///
/// \name Constructors, Assignment Operator and Destructor.
@@ -96,6 +98,8 @@
/// \param message Pointer to the \c Message object
/// \param buffer Pointer to an \c OutputBuffer for the resposne
/// \param server Pointer to the \c DNSServer
+ ///
+ /// \throw isc::Unexpected Protocol type of \a message is unexpected
void processMessage(const asiolink::IOMessage& io_message,
isc::dns::MessagePtr message,
isc::dns::OutputBufferPtr buffer,
@@ -281,6 +285,49 @@
void setMemoryDataSrc(const isc::dns::RRClass& rrclass,
MemoryDataSrcPtr memory_datasrc);
+ /// \brief Set the communication session with Statistics.
+ ///
+ /// This function never throws an exception as far as
+ /// AuthCounters::setStatisticsSession() doesn't throw.
+ ///
+ /// Note: this interface is tentative. We'll revisit the ASIO and
+ /// session frameworks, at which point the session will probably
+ /// be passed on construction of the server.
+ ///
+ /// \param statistics_session A Session object over which statistics
+ /// information is exchanged with statistics module.
+ /// The session must be established before setting in the server
+ /// object.
+ /// Ownership isn't transferred: the caller is responsible for keeping
+ /// this object to be valid while the server object is working and for
+ /// disconnecting the session and destroying the object when the server
+ /// is shutdown.
+ void setStatisticsSession(isc::cc::AbstractSession* statistics_session);
+
+ /// \brief Submit statistics counters to statistics module.
+ ///
+ /// This function can throw an exception from
+ /// AuthCounters::submitStatistics().
+ ///
+ /// \return true on success, false on failure (e.g. session timeout,
+ /// session error).
+ bool submitStatistics() const;
+
+ /// \brief Get the value of counter in the AuthCounters.
+ ///
+ /// This function calls AuthCounters::getCounter() and
+ /// returns its return value.
+ ///
+ /// This function never throws an exception as far as
+ /// AuthCounters::getCounter() doesn't throw.
+ ///
+ /// Note: Currently this function is for testing purpose only.
+ ///
+ /// \param type Type of a counter to get the value of
+ ///
+ /// \return the value of the counter.
+ uint64_t getCounter(const AuthCounters::CounterType type) const;
+
private:
AuthSrvImpl* impl_;
asiolink::IOService* io_service_;
Modified: trunk/src/bin/auth/benchmarks/Makefile.am
==============================================================================
--- trunk/src/bin/auth/benchmarks/Makefile.am (original)
+++ trunk/src/bin/auth/benchmarks/Makefile.am Tue Dec 28 03:50:44 2010
@@ -11,6 +11,7 @@
query_bench_SOURCES += ../query.h ../query.cc
query_bench_SOURCES += ../auth_srv.h ../auth_srv.cc
query_bench_SOURCES += ../config.h ../config.cc
+query_bench_SOURCES += ../statistics.h ../statistics.cc
query_bench_LDADD = $(top_builddir)/src/lib/dns/libdns++.la
query_bench_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
Modified: trunk/src/bin/auth/main.cc
==============================================================================
--- trunk/src/bin/auth/main.cc (original)
+++ trunk/src/bin/auth/main.cc Tue Dec 28 03:50:44 2010
@@ -25,7 +25,7 @@
#include <cassert>
#include <iostream>
-#include <boost/foreach.hpp>
+#include <boost/bind.hpp>
#include <exceptions/exceptions.h>
@@ -62,6 +62,10 @@
static const string PROGRAM = "Auth";
static const char* DNSPORT = "5300";
+// Note: this value must be greater than 0.
+// TODO: make it configurable via command channel.
+const uint32_t STATISTICS_SEND_INTERVAL_SEC = 60;
+
/* need global var for config/command handlers.
* todo: turn this around, and put handlers in the authserver
* class itself? */
@@ -84,6 +88,12 @@
answer = createAnswer(0, args);
} else if (command == "shutdown") {
io_service.stop();
+ } else if (command == "sendstats") {
+ if (verbose_mode) {
+ cerr << "[b10-auth] command 'sendstats' received" << endl;
+ }
+ assert(auth_server != NULL);
+ auth_server->submitStatistics();
}
return (answer);
@@ -103,6 +113,12 @@
cerr << "\t-v: verbose output" << endl;
exit(1);
}
+
+void
+statisticsTimerCallback(AuthSrv* auth_server) {
+ assert(auth_server != NULL);
+ auth_server->submitStatistics();
+}
} // end of anonymous namespace
int
@@ -168,7 +184,10 @@
// XXX: we should eventually pass io_service here.
Session* cc_session = NULL;
Session* xfrin_session = NULL;
+ Session* statistics_session = NULL;
+ IntervalTimer* itimer = NULL;
bool xfrin_session_established = false; // XXX (see Trac #287)
+ bool statistics_session_established = false; // XXX (see Trac #287)
ModuleCCSession* config_session = NULL;
string xfrout_socket_path;
if (getenv("B10_FROM_BUILD") != NULL) {
@@ -230,12 +249,19 @@
xfrin_session_established = true;
cout << "[b10-auth] Xfrin session channel established." << endl;
+ statistics_session = new Session(io_service.get_io_service());
+ cout << "[b10-auth] Statistics session channel created." << endl;
+ statistics_session->establish(NULL);
+ statistics_session_established = true;
+ cout << "[b10-auth] Statistics session channel established." << endl;
+
// XXX: with the current interface to asiolink we have to create
// auth_server before io_service while Session needs io_service.
// In a next step of refactoring we should make asiolink independent
// from auth_server, and create io_service, auth_server, and
// sessions in that order.
auth_server->setXfrinSession(xfrin_session);
+ auth_server->setStatisticsSession(statistics_session);
// Configure the server. configureAuthServer() is expected to install
// all initial configurations, but as a short term workaround we
@@ -245,6 +271,14 @@
configureAuthServer(*auth_server, config_session->getFullConfig());
auth_server->updateConfig(ElementPtr());
+ // create interval timer instance
+ itimer = new IntervalTimer(io_service);
+ // set up interval timer
+ // register function to send statistics with interval
+ itimer->setupTimer(boost::bind(statisticsTimerCallback, auth_server),
+ STATISTICS_SEND_INTERVAL_SEC);
+ cout << "[b10-auth] Interval timer to send statistics set." << endl;
+
cout << "[b10-auth] Server started." << endl;
io_service.run();
@@ -254,10 +288,16 @@
ret = 1;
}
+ if (statistics_session_established) {
+ statistics_session->disconnect();
+ }
+
if (xfrin_session_established) {
xfrin_session->disconnect();
}
+ delete itimer;
+ delete statistics_session;
delete xfrin_session;
delete config_session;
delete cc_session;
Modified: trunk/src/bin/auth/tests/Makefile.am
==============================================================================
--- trunk/src/bin/auth/tests/Makefile.am (original)
+++ trunk/src/bin/auth/tests/Makefile.am Tue Dec 28 03:50:44 2010
@@ -22,10 +22,12 @@
run_unittests_SOURCES += ../query.h ../query.cc
run_unittests_SOURCES += ../change_user.h ../change_user.cc
run_unittests_SOURCES += ../config.h ../config.cc
+run_unittests_SOURCES += ../statistics.h ../statistics.cc
run_unittests_SOURCES += auth_srv_unittest.cc
run_unittests_SOURCES += config_unittest.cc
run_unittests_SOURCES += query_unittest.cc
run_unittests_SOURCES += change_user_unittest.cc
+run_unittests_SOURCES += statistics_unittest.cc
run_unittests_SOURCES += run_unittests.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
Modified: trunk/src/bin/auth/tests/auth_srv_unittest.cc
==============================================================================
--- trunk/src/bin/auth/tests/auth_srv_unittest.cc (original)
+++ trunk/src/bin/auth/tests/auth_srv_unittest.cc Tue Dec 28 03:50:44 2010
@@ -18,6 +18,7 @@
#include <datasrc/memory_datasrc.h>
#include <auth/auth_srv.h>
#include <testutils/srv_unittest.h>
+#include <auth/statistics.h>
using namespace isc::cc;
using namespace isc::dns;
@@ -38,7 +39,9 @@
protected:
AuthSrvTest() : server(true, xfrout), rrclass(RRClass::IN()) {
server.setXfrinSession(¬ify_session);
+ server.setStatisticsSession(&statistics_session);
}
+ MockSession statistics_session;
MockXfroutClient xfrout;
AuthSrv server;
const RRClass rrclass;
@@ -429,4 +432,90 @@
server.setCacheSlots(0);
EXPECT_EQ(00, server.getCacheSlots());
}
-}
+
+// Submit UDP normal query and check query counter
+TEST_F(AuthSrvTest, queryCounterUDPNormal) {
+ // The counter should be initialized to 0.
+ EXPECT_EQ(0, server.getCounter(AuthCounters::COUNTER_UDP_QUERY));
+ // Create UDP message and process.
+ UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+ default_qid, Name("example.com"),
+ RRClass::IN(), RRType::NS());
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, parse_message, response_obuffer,
+ &dnsserv);
+ // After processing UDP query, the counter should be 1.
+ EXPECT_EQ(1, server.getCounter(AuthCounters::COUNTER_UDP_QUERY));
+}
+
+// Submit TCP normal query and check query counter
+TEST_F(AuthSrvTest, queryCounterTCPNormal) {
+ // The counter should be initialized to 0.
+ EXPECT_EQ(0, server.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+ // Create TCP message and process.
+ UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+ default_qid, Name("example.com"),
+ RRClass::IN(), RRType::NS());
+ createRequestPacket(request_message, IPPROTO_TCP);
+ server.processMessage(*io_message, parse_message, response_obuffer,
+ &dnsserv);
+ // After processing TCP query, the counter should be 1.
+ EXPECT_EQ(1, server.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+}
+
+// Submit TCP AXFR query and check query counter
+TEST_F(AuthSrvTest, queryCounterTCPAXFR) {
+ // The counter should be initialized to 0.
+ EXPECT_EQ(0, server.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+ UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+ Name("example.com"), RRClass::IN(), RRType::AXFR());
+ createRequestPacket(request_message, IPPROTO_TCP);
+ // On success, the AXFR query has been passed to a separate process,
+ // so we shouldn't have to respond.
+ server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+ // After processing TCP AXFR query, the counter should be 1.
+ EXPECT_EQ(1, server.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+}
+
+// class for queryCounterUnexpected test
+// getProtocol() returns IPPROTO_IP
+class DummyUnknownSocket : public IOSocket {
+public:
+ DummyUnknownSocket() {}
+ virtual int getNative() const { return (0); }
+ virtual int getProtocol() const { return (IPPROTO_IP); }
+};
+
+// function for queryCounterUnexpected test
+// returns a reference to a static object of DummyUnknownSocket
+IOSocket&
+getDummyUnknownSocket() {
+ static DummyUnknownSocket socket;
+ return (socket);
+}
+
+// Submit unexpected type of query and check it throws isc::Unexpected
+TEST_F(AuthSrvTest, queryCounterUnexpected) {
+ // This code isn't exception safe, but we'd rather keep the code
+ // simpler and more readable as this is only for tests and if it throws
+ // the program would immediately terminate anyway.
+
+ // Create UDP query packet.
+ UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+ default_qid, Name("example.com"),
+ RRClass::IN(), RRType::NS());
+ createRequestPacket(request_message, IPPROTO_UDP);
+
+ // Modify the message.
+ delete io_message;
+ endpoint = IOEndpoint::create(IPPROTO_UDP,
+ IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
+ io_message = new IOMessage(request_renderer.getData(),
+ request_renderer.getLength(),
+ getDummyUnknownSocket(), *endpoint);
+
+ EXPECT_THROW(server.processMessage(*io_message, parse_message,
+ response_obuffer, &dnsserv),
+ isc::Unexpected);
+}
+}
Modified: trunk/src/lib/asiolink/asiolink.cc
==============================================================================
--- trunk/src/lib/asiolink/asiolink.cc (original)
+++ trunk/src/lib/asiolink/asiolink.cc Tue Dec 28 03:50:44 2010
@@ -26,6 +26,7 @@
#include <asio.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/bind.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/shared_ptr.hpp>
@@ -374,4 +375,90 @@
timeout_, retries_);
}
-}
+class IntervalTimerImpl {
+private:
+ // prohibit copy
+ IntervalTimerImpl(const IntervalTimerImpl& source);
+ IntervalTimerImpl& operator=(const IntervalTimerImpl& source);
+public:
+ IntervalTimerImpl(IOService& io_service);
+ ~IntervalTimerImpl();
+ void setupTimer(const IntervalTimer::Callback& cbfunc,
+ const uint32_t interval);
+ void callback(const asio::error_code& error);
+private:
+ // a function to update timer_ when it expires
+ void updateTimer();
+ // a function to call back when timer_ expires
+ IntervalTimer::Callback cbfunc_;
+ // interval in seconds
+ uint32_t interval_;
+ // asio timer
+ asio::deadline_timer timer_;
+};
+
+IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
+ timer_(io_service.get_io_service())
+{}
+
+IntervalTimerImpl::~IntervalTimerImpl()
+{}
+
+void
+IntervalTimerImpl::setupTimer(const IntervalTimer::Callback& cbfunc,
+ const uint32_t interval)
+{
+ // Interval should not be 0.
+ if (interval == 0) {
+ isc_throw(isc::BadValue, "Interval should not be 0");
+ }
+ // Call back function should not be empty.
+ if (cbfunc.empty()) {
+ isc_throw(isc::InvalidParameter, "Callback function is empty");
+ }
+ cbfunc_ = cbfunc;
+ interval_ = interval;
+ // Set initial expire time.
+ // At this point the timer is not running yet and will not expire.
+ // After calling IOService::run(), the timer will expire.
+ updateTimer();
+ return;
+}
+
+void
+IntervalTimerImpl::updateTimer() {
+ try {
+ // Update expire time to (current time + interval_).
+ timer_.expires_from_now(boost::posix_time::seconds(interval_));
+ } catch (const asio::system_error& e) {
+ isc_throw(isc::Unexpected, "Failed to update timer");
+ }
+ // Reset timer.
+ timer_.async_wait(boost::bind(&IntervalTimerImpl::callback, this, _1));
+}
+
+void
+IntervalTimerImpl::callback(const asio::error_code& cancelled) {
+ // Do not call cbfunc_ in case the timer was cancelled.
+ // The timer will be canelled in the destructor of asio::deadline_timer.
+ if (!cancelled) {
+ cbfunc_();
+ // Set next expire time.
+ updateTimer();
+ }
+}
+
+IntervalTimer::IntervalTimer(IOService& io_service) {
+ impl_ = new IntervalTimerImpl(io_service);
+}
+
+IntervalTimer::~IntervalTimer() {
+ delete impl_;
+}
+
+void
+IntervalTimer::setupTimer(const Callback& cbfunc, const uint32_t interval) {
+ return (impl_->setupTimer(cbfunc, interval));
+}
+
+}
Modified: trunk/src/lib/asiolink/asiolink.h
==============================================================================
--- trunk/src/lib/asiolink/asiolink.h (original)
+++ trunk/src/lib/asiolink/asiolink.h Tue Dec 28 03:50:44 2010
@@ -23,6 +23,7 @@
#include <unistd.h> // for some network system calls
#include <asio/ip/address.hpp>
#include <boost/shared_ptr.hpp>
+#include <boost/function.hpp>
#include <functional>
#include <string>
@@ -98,6 +99,7 @@
namespace asiolink {
class DNSServiceImpl;
struct IOServiceImpl;
+struct IntervalTimerImpl;
/// \brief An exception that is thrown if an error occurs within the IO
/// module. This is mainly intended to be a wrapper exception class for
@@ -567,6 +569,98 @@
unsigned retries_;
};
+/// \brief The \c IntervalTimer class is a wrapper for the ASIO
+/// \c asio::deadline_timer class.
+///
+/// This class is implemented to use \c asio::deadline_timer as
+/// interval timer.
+///
+/// \c setupTimer() sets a timer to expire on (now + interval) and
+/// a call back function.
+///
+/// \c IntervalTimerImpl::callback() is called by the timer when
+/// it expires.
+///
+/// The function calls the call back function set by \c setupTimer()
+/// and updates the timer to expire in (now + interval) seconds.
+/// The type of call back function is \c void(void).
+///
+/// The call back function will not be called if the instance of this
+/// class is destructed before the timer is expired.
+///
+/// Note: Destruction of an instance of this class while call back
+/// is pending causes throwing an exception from \c IOService.
+///
+/// Sample code:
+/// \code
+/// void function_to_call_back() {
+/// // this function will be called periodically
+/// }
+/// int interval_in_seconds = 1;
+/// IOService io_service;
+///
+/// IntervalTimer intervalTimer(io_service);
+/// intervalTimer.setupTimer(function_to_call_back, interval_in_seconds);
+/// io_service.run();
+/// \endcode
+///
+class IntervalTimer {
+public:
+ /// \name The type of timer callback function
+ typedef boost::function<void()> Callback;
+
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IntervalTimer(const IntervalTimer& source);
+ IntervalTimer& operator=(const IntervalTimer& source);
+public:
+ /// \brief The constructor with \c IOService.
+ ///
+ /// This constructor may throw a standard exception if
+ /// memory allocation fails inside the method.
+ /// This constructor may also throw \c asio::system_error.
+ ///
+ /// \param io_service A reference to an instance of IOService
+ ///
+ IntervalTimer(IOService& io_service);
+
+ /// \brief The destructor.
+ ///
+ /// This destructor never throws an exception.
+ ///
+ /// On the destruction of this class the timer will be canceled
+ /// inside \c asio::deadline_timer.
+ ///
+ ~IntervalTimer();
+ //@}
+
+ /// \brief Register timer callback function and interval.
+ ///
+ /// This function sets callback function and interval in seconds.
+ /// Timer will actually start after calling \c IOService::run().
+ ///
+ /// \param cbfunc A reference to a function \c void(void) to call back
+ /// when the timer is expired (should not be an empty functor)
+ /// \param interval Interval in seconds (greater than 0)
+ ///
+ /// Note: IntervalTimer will not pass \c asio::error_code to
+ /// call back function. In case the timer is cancelled, the function
+ /// will not be called.
+ ///
+ /// \throw isc::InvalidParameter cbfunc is empty
+ /// \throw isc::BadValue interval is 0
+ /// \throw isc::Unexpected ASIO library error
+ ///
+ void setupTimer(const Callback& cbfunc, const uint32_t interval);
+private:
+ IntervalTimerImpl* impl_;
+};
+
} // asiolink
#endif // __ASIOLINK_H
Modified: trunk/src/lib/asiolink/tests/asiolink_unittest.cc
==============================================================================
--- trunk/src/lib/asiolink/tests/asiolink_unittest.cc (original)
+++ trunk/src/lib/asiolink/tests/asiolink_unittest.cc Tue Dec 28 03:50:44 2010
@@ -21,6 +21,7 @@
#include <boost/lexical_cast.hpp>
#include <boost/bind.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <gtest/gtest.h>
@@ -54,6 +55,9 @@
// two octets encode the length of the rest of the data. This is crucial
// for the tests below.
const uint8_t test_data[] = {0, 4, 1, 2, 3, 4};
+// TODO: Consider this margin
+const boost::posix_time::time_duration TIMER_MARGIN_MSEC =
+ boost::posix_time::milliseconds(50);
TEST(IOAddressTest, fromText) {
IOAddress io_address_v4("192.0.2.1");
@@ -710,4 +714,247 @@
EXPECT_EQ(3, num);
}
-}
+// This fixture is for testing IntervalTimer. Some callback functors are
+// registered as callback function of the timer to test if they are called
+// or not.
+class IntervalTimerTest : public ::testing::Test {
+protected:
+ IntervalTimerTest() : io_service_() {};
+ ~IntervalTimerTest() {}
+ class TimerCallBack : public std::unary_function<void, void> {
+ public:
+ TimerCallBack(IntervalTimerTest* test_obj) : test_obj_(test_obj) {}
+ void operator()() const {
+ test_obj_->timer_called_ = true;
+ test_obj_->io_service_.stop();
+ return;
+ }
+ private:
+ IntervalTimerTest* test_obj_;
+ };
+ class TimerCallBackCounter : public std::unary_function<void, void> {
+ public:
+ TimerCallBackCounter(IntervalTimerTest* test_obj) : test_obj_(test_obj) {
+ counter_ = 0;
+ }
+ void operator()() {
+ ++counter_;
+ return;
+ }
+ int counter_;
+ private:
+ IntervalTimerTest* test_obj_;
+ };
+ class TimerCallBackCancelDeleter : public std::unary_function<void, void> {
+ public:
+ TimerCallBackCancelDeleter(IntervalTimerTest* test_obj,
+ IntervalTimer* timer,
+ TimerCallBackCounter& counter)
+ : test_obj_(test_obj), timer_(timer), counter_(counter), count_(0)
+ {}
+ void operator()() {
+ ++count_;
+ if (count_ == 1) {
+ // First time of call back.
+ // Store the value of counter_.counter_.
+ prev_counter_ = counter_.counter_;
+ delete timer_;
+ } else if (count_ == 2) {
+ // Second time of call back.
+ // Stop io_service to stop all timers.
+ test_obj_->io_service_.stop();
+ // Compare the value of counter_.counter_ with stored one.
+ // If TimerCallBackCounter was not called (expected behavior),
+ // they are same.
+ if (counter_.counter_ == prev_counter_) {
+ test_obj_->timer_cancel_success_ = true;
+ }
+ }
+ return;
+ }
+ private:
+ IntervalTimerTest* test_obj_;
+ IntervalTimer* timer_;
+ TimerCallBackCounter& counter_;
+ int count_;
+ int prev_counter_;
+ };
+ class TimerCallBackOverwriter : public std::unary_function<void, void> {
+ public:
+ TimerCallBackOverwriter(IntervalTimerTest* test_obj,
+ IntervalTimer& timer)
+ : test_obj_(test_obj), timer_(timer), count_(0)
+ {}
+ void operator()() {
+ ++count_;
+ if (count_ == 1) {
+ // First time of call back.
+ // Call setupTimer() to update callback function
+ // to TimerCallBack.
+ test_obj_->timer_called_ = false;
+ timer_.setupTimer(TimerCallBack(test_obj_), 1);
+ } else if (count_ == 2) {
+ // Second time of call back.
+ // If it reaches here, re-setupTimer() is failed (unexpected).
+ // We should stop here.
+ test_obj_->io_service_.stop();
+ }
+ return;
+ }
+ private:
+ IntervalTimerTest* test_obj_;
+ IntervalTimer& timer_;
+ int count_;
+ };
+protected:
+ IOService io_service_;
+ bool timer_called_;
+ bool timer_cancel_success_;
+};
+
+TEST_F(IntervalTimerTest, invalidArgumentToIntervalTimer) {
+ // Create asio_link::IntervalTimer and setup.
+ IntervalTimer itimer(io_service_);
+ // expect throw if call back function is empty
+ EXPECT_THROW(itimer.setupTimer(IntervalTimer::Callback(), 1),
+ isc::InvalidParameter);
+ // expect throw if interval is 0
+ EXPECT_THROW(itimer.setupTimer(TimerCallBack(this), 0), isc::BadValue);
+}
+
+TEST_F(IntervalTimerTest, startIntervalTimer) {
+ // Create asio_link::IntervalTimer and setup.
+ // Then run IOService and test if the callback function is called.
+ IntervalTimer itimer(io_service_);
+ timer_called_ = false;
+ // store start time
+ boost::posix_time::ptime start;
+ start = boost::posix_time::microsec_clock::universal_time();
+ // setup timer
+ itimer.setupTimer(TimerCallBack(this), 1);
+ io_service_.run();
+ // reaches here after timer expired
+ // delta: difference between elapsed time and 1 second
+ boost::posix_time::time_duration delta =
+ (boost::posix_time::microsec_clock::universal_time() - start)
+ - boost::posix_time::seconds(1);
+ if (delta.is_negative()) {
+ delta.invert_sign();
+ }
+ // expect TimerCallBack is called; timer_called_ is true
+ EXPECT_TRUE(timer_called_);
+ // expect interval is 1 second +/- TIMER_MARGIN_MSEC.
+ EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+}
+
+TEST_F(IntervalTimerTest, destructIntervalTimer) {
+ // Note: This test currently takes 6 seconds. The timer should have
+ // finer granularity and timer periods in this test should be shorter
+ // in the future.
+ // This code isn't exception safe, but we'd rather keep the code
+ // simpler and more readable as this is only for tests and if it throws
+ // the program would immediately terminate anyway.
+
+ // The call back function will not be called after the timer is
+ // destructed.
+ //
+ // There are two timers:
+ // itimer_counter (A)
+ // (Calls TimerCallBackCounter)
+ // - increments internal counter in callback function
+ // itimer_canceller (B)
+ // (Calls TimerCallBackCancelDeleter)
+ // - first time of callback, it stores the counter value of
+ // callback_canceller and destructs itimer_counter
+ // - second time of callback, it compares the counter value of
+ // callback_canceller with stored value
+ // if they are same the timer was not called; expected result
+ // if they are different the timer was called after destructed
+ //
+ // 0 1 2 3 4 5 6 (s)
+ // (A) i-----+--x
+ // ^
+ // |destruct itimer_counter
+ // (B) i--------+--------s
+ // ^stop io_service
+ // and test itimer_counter have been stopped
+ //
+
+ // itimer_counter will be deleted in TimerCallBackCancelDeleter
+ IntervalTimer* itimer_counter = new IntervalTimer(io_service_);
+ IntervalTimer itimer_canceller(io_service_);
+ timer_cancel_success_ = false;
+ TimerCallBackCounter callback_canceller(this);
+ itimer_counter->setupTimer(callback_canceller, 2);
+ itimer_canceller.setupTimer(
+ TimerCallBackCancelDeleter(this, itimer_counter,
+ callback_canceller),
+ 3);
+ io_service_.run();
+ EXPECT_TRUE(timer_cancel_success_);
+}
+
+TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
+ // Note: This test currently takes 4 seconds. The timer should have
+ // finer granularity and timer periods in this test should be shorter
+ // in the future.
+
+ // Calling setupTimer() multiple times updates call back function
+ // and interval.
+ //
+ // There are two timers:
+ // itimer (A)
+ // (Calls TimerCallBackCounter / TimerCallBack)
+ // - increments internal counter in callback function
+ // (TimerCallBackCounter)
+ // interval: 2 seconds
+ // - io_service_.stop() (TimerCallBack)
+ // interval: 1 second
+ // itimer_overwriter (B)
+ // (Calls TimerCallBackOverwriter)
+ // - first time of callback, it calls setupTimer() to change
+ // call back function and interval of itimer to
+ // TimerCallBack / 1 second
+ // after 3 + 1 seconds from the beginning of this test,
+ // TimerCallBack() will be called and io_service_ stops.
+ // - second time of callback, it means the test fails.
+ //
+ // 0 1 2 3 4 5 6 (s)
+ // (A) i-----+--C--s
+ // ^ ^stop io_service
+ // |change call back function
+ // (B) i--------+--------S
+ // ^(stop io_service on fail)
+ //
+
+ IntervalTimer itimer(io_service_);
+ IntervalTimer itimer_overwriter(io_service_);
+ // store start time
+ boost::posix_time::ptime start;
+ start = boost::posix_time::microsec_clock::universal_time();
+ itimer.setupTimer(TimerCallBackCounter(this), 2);
+ itimer_overwriter.setupTimer(TimerCallBackOverwriter(this, itimer), 3);
+ io_service_.run();
+ // reaches here after timer expired
+ // if interval is updated, it takes
+ // 3 seconds for TimerCallBackOverwriter
+ // + 1 second for TimerCallBack (stop)
+ // = 4 seconds.
+ // otherwise (test fails), it takes
+ // 3 seconds for TimerCallBackOverwriter
+ // + 3 seconds for TimerCallBackOverwriter (stop)
+ // = 6 seconds.
+ // delta: difference between elapsed time and 3 + 1 seconds
+ boost::posix_time::time_duration delta =
+ (boost::posix_time::microsec_clock::universal_time() - start)
+ - boost::posix_time::seconds(3 + 1);
+ if (delta.is_negative()) {
+ delta.invert_sign();
+ }
+ // expect callback function is updated: TimerCallBack is called
+ EXPECT_TRUE(timer_called_);
+ // expect interval is updated
+ EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+}
+
+}
More information about the bind10-changes
mailing list