Parcourir la source

Address some review comments [comment:ticket:347:5]

* Timer call back scheme of submitting statistics changed.
* Avoid dynamic allocations.
* Some files and classes were renamed.
  - stats.{cc,h} -> statistics.{cc,h}
  - class Counter -> class QueryCounters
* Remove unnecessary includes.
* Some test cases were appended.
  - class IntervalTimer
  - Submitted values from QueryCounters::submitStatistics()
  - Counter increment with handling query
* Remove namespace 'statistics'.
* Argument to constructor of IntervalTimer was changed
  to asio_link::IOService&.
* Error handling improved
  - Parameter check
  - Exception handling
* Documentation and comments were appended.



git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac347@3782 e5f2f494-b856-4b98-b285-d166d9295462
Yoshitaka Aharen il y a 14 ans
Parent
commit
468ed141b7

+ 4 - 1
ChangeLog

@@ -1,8 +1,11 @@
   TBD.  [func]		y-aharen
 	src/bin/auth: Added a feature to count query and send counter
-	values to b10-stats periodically. To support it, added wrapping
+	values to statistics periodically. To support it, added wrapping
 	class of asio::deadline_timer to use as interval timer.
 	Added a command 'sendstats' to send counter values at once.
+	The counter value can be seen with sending 'sendstats' command to
+	statistics module via bindctl like:
+	  ... "auth.queries.tcp": 1, "auth.queries.udp": 1 ...
 	(Trac #347, svn rxxxx)
 
   122.  [func]		stephen

+ 1 - 1
src/bin/auth/Makefile.am

@@ -58,7 +58,7 @@ pkglibexec_PROGRAMS = b10-auth
 b10_auth_SOURCES = auth_srv.cc auth_srv.h
 b10_auth_SOURCES += change_user.cc change_user.h
 b10_auth_SOURCES += common.h
-b10_auth_SOURCES += stats.cc stats.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

+ 31 - 21
src/bin/auth/asio_link.cc

@@ -669,64 +669,74 @@ IOService::setCallBack(const IOCallBack callback) {
 
 class IntervalTimerImpl {
 private:
-    // prohibit copy to avoid the function be called twice
+    // prohibit copy
     IntervalTimerImpl(const IntervalTimerImpl& source);
     IntervalTimerImpl& operator=(const IntervalTimerImpl& source);
 public:
-    IntervalTimerImpl(asio::io_service& io_service);
+    IntervalTimerImpl(IOService& io_service);
     ~IntervalTimerImpl();
-    bool setupTimer(const IntervalTimer::Callback& cbfunc,
+    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(asio::io_service& io_service) :
-    timer_(io_service)
+IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
+    timer_(io_service.get_io_service())
 {}
 
-IntervalTimerImpl::~IntervalTimerImpl() {
-    timer_.cancel();
-}
+IntervalTimerImpl::~IntervalTimerImpl()
+{}
 
-bool
+void
 IntervalTimerImpl::setupTimer(const IntervalTimer::Callback& cbfunc,
-                              const uint32_t interval)
+                                    const uint32_t interval)
 {
-    // interval value must be positive
-    assert(interval > 0);
+    // 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;
-    // start timer
+    // 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 (true);
+    return;
 }
 
 void
 IntervalTimerImpl::updateTimer() {
-    // update expire time (current time + interval_)
+    // Update expire time to (current time + interval_).
     timer_.expires_from_now(boost::posix_time::seconds(interval_));
-    // restart timer
+    // Reset timer.
     timer_.async_wait(boost::bind(&IntervalTimerImpl::callback, this, _1));
 }
 
 void
 IntervalTimerImpl::callback(const asio::error_code& cancelled) {
-    assert(!cbfunc_.empty());
-    // skip function call in case the timer was 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_();
-        // restart timer
+        // Set next expire time.
         updateTimer();
     }
 }
 
-IntervalTimer::IntervalTimer(asio::io_service& io_service) {
+IntervalTimer::IntervalTimer(IOService& io_service) {
     impl_ = new IntervalTimerImpl(io_service);
 }
 
@@ -734,7 +744,7 @@ IntervalTimer::~IntervalTimer() {
     delete impl_;
 }
 
-bool
+void
 IntervalTimer::setupTimer(const Callback& cbfunc, const uint32_t interval) {
     return (impl_->setupTimer(cbfunc, interval));
 }

+ 64 - 13
src/bin/auth/asio_link.h

@@ -446,15 +446,45 @@ private:
     IOServiceImpl* impl_;
 };
 
-/// \brief The \c IntervalTimer class is a wrapper for the ASIO \c deadline_timer
-/// class.
+/// \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 update the timer to expire on (now + interval).
+/// The type of call back function is \c void(void).
+///
+/// This class is mainly designed to use for calling
+/// \c QueryCounters::submitStatistics() periodically.
+///
+/// Note: Destruction of the instance of this class while call back
+/// is pending causes throwing an exception from IOService.
+///
+/// Sample code:
+/// \code
+///  void function_to_call_back() {
+///      // this function will 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
 ///
-/// This class is implemented to use boost::deadline_timer as interval timer.
-/// Copy of this class is prohibited not to call the callback function twice.
 class IntervalTimer {
 public:
     /// \name The type of timer callback function
-    typedef boost::function<void(void)> Callback;
+    typedef boost::function<void()> Callback;
 
     ///
     /// \name Constructors and Destructor
@@ -466,22 +496,43 @@ private:
     IntervalTimer(const IntervalTimer& source);
     IntervalTimer& operator=(const IntervalTimer& source);
 public:
-    /// \brief The constructor with asio::io_service.
+    /// \brief The constructor with \c IOService.
     ///
-    /// \param io_service A reference to an instance of asio::io_service
-    IntervalTimer(asio::io_service& io_service);
+    /// 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 cancelled
+    /// inside \c asio::deadline_timer.
+    ///
     ~IntervalTimer();
     //@}
 
-    /// \brief Register timer callback function
+    /// \brief Register timer callback function and interval
+    ///
+    /// This function sets call back function and interval in seconds.
+    /// Timer will actually start after calling \c IOService::run().
     ///
-    /// \param cbfunc A reference to a function to call back
+    /// \param cbfunc A reference to a function \c void(void) to call back
     /// when the timer is expired
-    /// \param interval Interval in seconds
+    /// \param interval Interval in seconds (greater than 0)
+    ///
+    /// Note: IntervalTimer will not pass 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 asio::system_error ASIO library error
     ///
-    /// \return \c true on success
-    bool setupTimer(const Callback& cbfunc, const uint32_t interval);
+    void setupTimer(const Callback& cbfunc, const uint32_t interval);
 private:
     IntervalTimerImpl* impl_;
 };

+ 1 - 1
src/bin/auth/auth.spec.pre.in

@@ -17,7 +17,7 @@
       },
       {
         "command_name": "sendstats",
-        "command_description": "Send statistics data to b10-stats at once",
+        "command_description": "Send data to a statistics module at once",
         "command_args": []
       }
     ]

+ 19 - 17
src/bin/auth/auth_srv.cc

@@ -50,6 +50,7 @@
 #include <auth/common.h>
 #include <auth/auth_srv.h>
 #include <auth/asio_link.h>
+#include <auth/statistics.h>
 
 using namespace std;
 
@@ -99,6 +100,9 @@ public:
 
     /// Hot spot cache
     isc::datasrc::HotCache cache_;
+
+    /// Query counters for statistics
+    QueryCounters counters_;
 };
 
 AuthSrvImpl::AuthSrvImpl(const bool use_cache,
@@ -106,7 +110,8 @@ AuthSrvImpl::AuthSrvImpl(const bool use_cache,
     config_session_(NULL), verbose_mode_(false),
     xfrin_session_(NULL),
     xfrout_connected_(false),
-    xfrout_client_(xfrout_client)
+    xfrout_client_(xfrout_client),
+    counters_(verbose_mode_)
 {
     // cur_datasrc_ is automatically initialized by the default constructor,
     // effectively being an empty (sqlite) data source.  once ccsession is up
@@ -128,13 +133,10 @@ AuthSrvImpl::~AuthSrvImpl() {
 
 AuthSrv::AuthSrv(const bool use_cache, AbstractXfroutClient& xfrout_client) :
     impl_(new AuthSrvImpl(use_cache, xfrout_client))
-{
-    counter = new statistics::Counter(impl_->verbose_mode_);
-}
+{}
 
 AuthSrv::~AuthSrv() {
     delete impl_;
-    delete counter;
 }
 
 namespace {
@@ -219,7 +221,7 @@ AuthSrv::setConfigSession(ModuleCCSession* config_session) {
 
 void
 AuthSrv::setStatsSession(AbstractSession* stats_session) {
-    counter->setStatsSession(stats_session);
+    impl_->counters_.setStatsSession(stats_session);
 }
 
 ModuleCCSession*
@@ -276,15 +278,13 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
 
     // increment Query Counter
     if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
-        counter->inc(statistics::Counter::COUNTER_UDP);
+        impl_->counters_.inc(QueryCounters::COUNTER_UDP);
     } else if (io_message.getSocket().getProtocol() == IPPROTO_TCP) {
-        counter->inc(statistics::Counter::COUNTER_TCP);
+        impl_->counters_.inc(QueryCounters::COUNTER_TCP);
     } else {
         // unknown protocol
-        if (impl_->verbose_mode_) {
-            cerr << "[b10-auth] Unknown protocol: " <<
-                     io_message.getSocket().getProtocol() << endl;
-        }
+        isc_throw(Unexpected, "Unknown protocol: " <<
+                  io_message.getSocket().getProtocol());
     }
 
     // Perform further protocol-level validation.
@@ -565,9 +565,11 @@ AuthSrv::updateConfig(ConstElementPtr new_config) {
     }
 }
 
-asio_link::IntervalTimer::Callback
-AuthSrv::getStatsCallback() {
-    // just invoke statistics::Counter::getStatsCallback()
-    // and return its return value
-    return (counter->getCallback());
+bool AuthSrv::submitStatistics() {
+    return (impl_->counters_.submitStatistics());
+}
+
+const std::vector<uint64_t>&
+AuthSrv::getCounters() const {
+    return (impl_->counters_.getCounters());
 }

+ 38 - 19
src/bin/auth/auth_srv.h

@@ -22,12 +22,6 @@
 #include <cc/data.h>
 #include <config/ccsession.h>
 
-/// for the typedef of asio_link::IntervalTimer::Callback
-#include <auth/asio_link.h>
-
-// for class statistics::Counter
-#include <auth/stats.h>
-
 namespace isc {
 namespace dns {
 class InputBuffer;
@@ -208,25 +202,50 @@ public:
 
     /// \brief Set the communication session with Statistics.
     ///
+    /// This function never throws an exception as far as
+    /// QueryCounters::setStatsSession() 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 stats_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 setStatsSession(isc::cc::AbstractSession* stats_session);
 
-    /// \brief Return the function that sends statistics information
-    /// to Statistics module.
+    /// \brief Submit statistics counters to statistics module.
+    ///
+    /// This function can throw an exception from
+    /// QueryCounters::submitStatistics().
+    ///
+    /// \return true on success, false on failure (e.g. session timeout,
+    /// session error).
+    ///
+    bool submitStatistics();
+
+    /// \brief Get counters in the QueryCounters.
     /// 
-    /// This function returns the return value of
-    /// statistics::Counter::getCallback().
+    /// This function calls QueryCounters::getCounters() and
+    /// returns its return velue, a reference to the counters.
     ///
-    /// \return \c boost::function which contains the procedure
-    /// to send statistics.
-    asio_link::IntervalTimer::Callback getStatsCallback();
+    /// This function never throws an exception as far as
+    /// QueryCounters::getCounters() doesn't throw.
+    /// 
+    /// Note: Currently this function is for testing purpose only.
+    /// This function should not be called except from tests.
+    ///
+    /// \return a reference to the counters.
+    ///
+    const std::vector<uint64_t>& getCounters() const;
 private:
     AuthSrvImpl* impl_;
-
-    // TODO: consider where to put the counter.
-    // Currently, count-up is in AuthSrv::processMessage.
-    // In the future, count-up will be in AuthSrvImpl::process*Query
-    // and this declaration will be moved into AuthSrvImpl.
-    statistics::Counter *counter;
 };
 
 #endif // __AUTH_SRV_H

+ 1 - 1
src/bin/auth/benchmarks/Makefile.am

@@ -9,7 +9,7 @@ CLEANFILES = *.gcno *.gcda
 noinst_PROGRAMS = query_bench
 query_bench_SOURCES = query_bench.cc
 query_bench_SOURCES += ../auth_srv.h ../auth_srv.cc
-query_bench_SOURCES += ../stats.h ../stats.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

+ 12 - 5
src/bin/auth/main.cc

@@ -26,6 +26,7 @@
 #include <iostream>
 
 #include <boost/foreach.hpp>
+#include <boost/bind.hpp>
 
 #include <exceptions/exceptions.h>
 
@@ -89,9 +90,8 @@ my_command_handler(const string& command, ConstElementPtr args) {
         if (verbose_mode) {
             cerr << "[b10-auth] command 'sendstats' received" << endl;
         }
-        if (auth_server != NULL) {
-            auth_server->getStatsCallback()();
-        }
+        assert(auth_server != NULL);
+        auth_server->submitStatistics();
     }
     
     return (answer);
@@ -104,6 +104,12 @@ usage() {
 }
 } // end of anonymous namespace
 
+void
+statisticsTimerCallback(AuthSrv* auth_server) {
+    assert(auth_server != NULL);
+    auth_server->submitStatistics();
+}
+
 int
 main(int argc, char* argv[]) {
     int ch;
@@ -240,10 +246,11 @@ main(int argc, char* argv[]) {
         auth_server->updateConfig(ElementPtr());
 
         // create interval timer instance
-        itimer = new asio_link::IntervalTimer(io_service->get_io_service());
+        itimer = new asio_link::IntervalTimer(*io_service);
         // set up interval timer
         // register function to send statistics with interval
-        itimer->setupTimer(auth_server->getStatsCallback(),
+        itimer->setupTimer(boost::bind(statisticsTimerCallback,
+                                       auth_server),
                            STATS_SEND_INTERVAL_SEC);
         cout << "[b10-auth] Interval timer set to send stats." << endl;
 

+ 162 - 0
src/bin/auth/statistics.cc

@@ -0,0 +1,162 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <auth/statistics.h>
+
+#include <cc/data.h>
+#include <cc/session.h>
+
+#include <sstream>
+#include <iostream>
+
+// TODO: We need a namespace ("auth_server"?) to hold
+// AuthSrv and QueryCounters.
+
+class QueryCountersImpl {
+private:
+    // prohibit copy
+    QueryCountersImpl(const QueryCountersImpl& source);
+    QueryCountersImpl& operator=(const QueryCountersImpl& source);
+public:
+    // References verbose_mode flag in AuthSrvImpl
+    // TODO: Fix this short term workaround for logging
+    // after we have logging framework
+    QueryCountersImpl(const bool& verbose_mode);
+    ~QueryCountersImpl();
+    void inc(const QueryCounters::QueryCounterType type);
+    bool submitStatistics() const;
+    void setStatsSession(isc::cc::AbstractSession* stats_session);
+    // Currently for testing purpose only
+    const std::vector<uint64_t>& getCounters() const;
+private:
+    std::vector<uint64_t> counters_;
+    isc::cc::AbstractSession* stats_session_;
+    const bool& verbose_mode_;
+};
+
+QueryCountersImpl::QueryCountersImpl(const bool& verbose_mode) :
+    // initialize counter
+    // size: QueryCounters::COUNTER_TYPES, initial value: 0
+    counters_(QueryCounters::COUNTER_TYPES, 0),
+    stats_session_(NULL),
+    verbose_mode_(verbose_mode)
+{}
+
+QueryCountersImpl::~QueryCountersImpl()
+{}
+
+void
+QueryCountersImpl::inc(const QueryCounters::QueryCounterType type)
+{
+    try {
+        ++counters_.at(type);
+    } catch (std::out_of_range) {
+        isc_throw(isc::InvalidParameter, "Unknown counter type: " << type);
+    }
+}
+
+bool
+QueryCountersImpl::submitStatistics() const {
+    if (stats_session_ == NULL) {
+        if (verbose_mode_) {
+            std::cerr << "[b10-auth] "
+                      << "session interface for statistics"
+                      << " is not available" << std::endl;
+        }
+        return (false);
+    }
+    std::stringstream strstats;
+    strstats << "{\"command\": [\"set\","
+             <<   "{ \"stats_data\": "
+             <<     "{ \"auth.queries.udp\": "
+             <<     counters_.at(QueryCounters::COUNTER_UDP)
+             <<     ", \"auth.queries.tcp\": "
+             <<     counters_.at(QueryCounters::COUNTER_TCP)
+             <<   " }"
+             <<   "}"
+             << "]}";
+    isc::data::ConstElementPtr set_stats =
+        isc::data::Element::fromJSON(strstats);
+    try {
+        // group_{send,recv}msg() can throw an exception when encountering
+        // an error, and group_recvmsg() will throw an exception on timeout.
+        // We don't want to kill the main server just due to this, so we
+        // handle them here.
+        const int seq = stats_session_->group_sendmsg(set_stats, "Stats");
+        isc::data::ConstElementPtr env, answer;
+        if (verbose_mode_) {
+            std::cerr << "[b10-auth] "
+                      << "send statistics data" << std::endl;
+        }
+        // TODO: parse and check response from statistics module
+        // currently it just returns empty message
+        stats_session_->group_recvmsg(env, answer, false, seq);
+    } catch (const isc::cc::SessionError& ex) {
+        if (verbose_mode_) {
+            std::cerr << "[b10-auth] "
+                      << "communication error in sending statistics data: "
+                      << ex.what() << std::endl;
+        }
+        return (false);
+    } catch (const isc::cc::SessionTimeout& ex) {
+        if (verbose_mode_) {
+            std::cerr << "[b10-auth] "
+                      << "timeout happened while sending statistics data: "
+                      << ex.what() << std::endl;
+        }
+        return (false);
+    }
+    return (true);
+}
+
+void
+QueryCountersImpl::setStatsSession(isc::cc::AbstractSession* stats_session) {
+    stats_session_ = stats_session;
+}
+
+// Currently for testing purpose only
+const std::vector<uint64_t>&
+QueryCountersImpl::getCounters() const {
+    return (counters_);
+}
+
+QueryCounters::QueryCounters(const bool& verbose_mode) :
+    impl_(new QueryCountersImpl(verbose_mode))
+{}
+
+QueryCounters::~QueryCounters() {
+    delete impl_;
+}
+
+void
+QueryCounters::inc(const QueryCounters::QueryCounterType type) {
+    impl_->inc(type);
+}
+
+bool
+QueryCounters::submitStatistics() const {
+    return (impl_->submitStatistics());
+}
+
+void
+QueryCounters::setStatsSession(isc::cc::AbstractSession* stats_session) {
+    impl_->setStatsSession(stats_session);
+}
+
+const std::vector<uint64_t>&
+QueryCounters::getCounters() const {
+    return (impl_->getCounters());
+}

+ 142 - 0
src/bin/auth/statistics.h

@@ -0,0 +1,142 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#ifndef __STATS_H
+#define __STATS_H 1
+
+#include <boost/function.hpp>
+
+#include <cc/session.h>
+
+class QueryCountersImpl;
+
+/// \brief Set of query counters.
+///
+/// \c QueryCounters is set of query counters class. It holds query counters
+/// and provides an interface to increment the counter of specified
+/// type (e.g. UDP query, TCP query).
+///
+/// This class also provides a function to send statistics information to
+/// statistics module.
+///
+/// This class is designed to be a part of \c AuthSrv.
+/// Call \c setStatsSession() to set a session to communicate with statistics
+/// module like Xfrin session.
+/// Call \c inc() to increment a counter for specific type of query in
+/// the query processing function. use \c enum \c QueryCounterType to specify
+/// the type of query.
+/// Call \c submitStatistics() to submit statistics information to statistics
+/// module with stats_session, periodically or at a time the command
+/// \c sendstats is received.
+///
+/// This class uses pimpl idiom and hides detailed implementation.
+/// This class is constructed on startup of the server, so
+/// construction overhead of this approach should be acceptable.
+///
+/// \todo Hold counters for each query types (Notify, Axfr, Ixfr, Normal)
+/// \todo Consider overhead of \c QueryCounters::inc()
+class QueryCounters {
+private:
+    QueryCountersImpl* impl_;
+public:
+    // Enum for the type of counter
+    enum QueryCounterType {
+        COUNTER_UDP = 0,  ///< COUNTER_UDP: counter for UDP queries
+        COUNTER_TCP = 1,  ///< COUNTER_TCP: counter for TCP queries
+        COUNTER_TYPES = 2 ///< The number of defined counters
+    };
+    /// The constructor.
+    ///
+    /// \param verbose_mode reference to verbose_mode_ of AuthSrvImpl
+    ///
+    /// This constructor is mostly exception free. But it may still throw
+    /// a standard exception if memory allocation fails inside the method.
+    ///
+    /// \todo Fix this short term workaround for logging
+    /// after we have logging framework.
+    ///
+    QueryCounters(const bool& verbose_mode);
+    /// The destructor.
+    ///
+    /// This method never throws an exception.
+    ///
+    ~QueryCounters();
+
+    /// \brief Increment the counter specified by the parameter.
+    ///
+    /// \param type Type of a counter to increment.
+    ///
+    /// \throw isc::InvalidParameter the type is unknown.
+    ///
+    /// usage: counter.inc(QueryCounterType::COUNTER_UDP);
+    /// 
+    void inc(const QueryCounterType type);
+
+    /// \brief Submit statistics counters to statistics module.
+    ///
+    /// This method is desinged to be called periodically
+    /// with \c asio_link::StatisticsSendTimer, or arbitrary
+    /// by the command 'sendstats'.
+    ///
+    /// Note: Set the session to communicate with statistics module
+    /// by \c setStatsSession() before calling \c submitStatistics().
+    ///
+    /// This method is mostly exception free (error conditions are
+    /// represented via the return value). But it may still throw
+    /// a standard exception if memory allocation fails inside the method.
+    ///
+    /// \return true on success, false on error.
+    ///
+    /// \todo Do not block query handling while submitting statistics data.
+    ///
+    bool submitStatistics() const;
+
+    /// \brief Set the session to communicate with Statistics
+    /// module.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// 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.
+    ///
+    /// 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.
+    ///
+    /// \param stats_session A pointer to the session
+    ///
+    void setStatsSession(isc::cc::AbstractSession* stats_session);
+
+    /// \brief Get a reference to the counters in the QueryCounters.
+    ///
+    /// This function returns a refetence to the counters.
+    /// This method never throws an exception.
+    ///
+    /// Note: Currently this function is for testing purpose only.
+    /// This function should not be called except from tests.
+    ///
+    /// \return a reference to the counters.
+    ///
+    const std::vector<uint64_t>& getCounters() const;
+};
+
+#endif // __STATS_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 0 - 144
src/bin/auth/stats.cc

@@ -1,144 +0,0 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-// $Id$
-
-#include <boost/bind.hpp>
-
-#include <auth/stats.h>
-
-#include <cc/data.h>
-
-#include <sstream>
-#include <iostream>
-
-namespace statistics {
-
-class CounterImpl {
-private:
-    // prohibit copy
-    CounterImpl(const CounterImpl& source);
-    CounterImpl& operator=(const CounterImpl& source);
-public:
-    CounterImpl(const bool& verbose_mode);
-    ~CounterImpl();
-    void inc(const Counter::CounterType type);
-    asio_link::IntervalTimer::Callback getCallback();
-    void sendStats();
-    void setStatsSession(isc::cc::AbstractSession* stats_session);
-private:
-    std::vector<uint64_t>* counters_;
-    isc::cc::AbstractSession* stats_session_;
-    const bool& verbose_mode_;
-};
-
-CounterImpl::CounterImpl(const bool& verbose_mode) :
-    // initialize counter
-    // size: Counter::COUNTER_TYPES, initial value: 0
-    stats_session_(NULL),
-    verbose_mode_(verbose_mode)
-{
-    counters_ = new std::vector<uint64_t>(Counter::COUNTER_TYPES, 0);
-}
-
-CounterImpl::~CounterImpl() {
-    delete counters_;
-}
-
-void
-CounterImpl::inc(const Counter::CounterType type) {
-    ++counters_->at(type);
-}
-
-asio_link::IntervalTimer::Callback
-CounterImpl::getCallback() {
-    return (boost::bind(&CounterImpl::sendStats, this));
-}
-
-void
-CounterImpl::sendStats() {
-    if (stats_session_ == NULL) {
-        if (verbose_mode_) {
-            std::cerr << "[b10-auth] "
-                      << "session interface for statistics"
-                      << " is not available" << std::endl;
-        }
-        return;
-    }
-    std::stringstream strstats;
-    strstats << "{\"command\": [\"set\","
-             <<   "{ \"stats_data\": "
-             <<     "{ \"auth.queries.udp\": "
-             <<     counters_->at(Counter::COUNTER_UDP)
-             <<     ", \"auth.queries.tcp\": "
-             <<     counters_->at(Counter::COUNTER_TCP)
-             <<   " }"
-             <<   "}"
-             << "]}";
-    isc::data::ConstElementPtr set_stats =
-        isc::data::Element::fromJSON(strstats);
-    try {
-        // group_recvmsg() will time out in MSGQ_DEFAULT_TIMEOUT.
-        // On timeout or error, an exception will be thrown
-        const int seq =
-            stats_session_->group_sendmsg(set_stats, "Stats");
-        isc::data::ConstElementPtr env, answer;
-        if (verbose_mode_) {
-            std::cerr << "[b10-auth] "
-                      << "send statistics data to b10-stats"
-                      << std::endl;
-        }
-        // TODO: parse and check response from b10-stats
-        // currently it just returns empty message
-        stats_session_->group_recvmsg(env, answer, false, seq);
-    } catch (const isc::Exception& ex) {
-        // catch the exception and do nothing.
-        if (verbose_mode_) {
-            std::cerr << "[b10-auth] "
-                      << "failed to send statistics data to b10-stats: "
-                      << ex.what() << std::endl;
-        }
-    }
-    return;
-}
-
-void
-CounterImpl::setStatsSession(isc::cc::AbstractSession* stats_session) {
-    stats_session_ = stats_session;
-}
-
-Counter::Counter(const bool& verbose_mode) :
-    impl_(new CounterImpl(verbose_mode))
-{}
-
-Counter::~Counter() {
-    delete impl_;
-}
-
-asio_link::IntervalTimer::Callback
-Counter::getCallback() {
-    return (impl_->getCallback());
-}
-
-void
-Counter::inc(const Counter::CounterType type) {
-    impl_->inc(type);
-}
-
-void
-Counter::setStatsSession(isc::cc::AbstractSession* stats_session) {
-    impl_->setStatsSession(stats_session);
-}
-
-} // statistics

+ 0 - 90
src/bin/auth/stats.h

@@ -1,90 +0,0 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-// $Id$
-
-#ifndef __STATS_H
-#define __STATS_H 1
-
-#include <boost/function.hpp>
-
-#include <cc/session.h>
-
-/// for asio_link::IntervalTimer::Callback
-#include <auth/asio_link.h>
-
-namespace statistics {
-
-class CounterImpl;
-
-/// \brief A query counter.
-///
-/// \c Counter is a query counter class. It holds query counter
-/// and provides an interface to increment the counter.
-/// This class also provides a function to send information to
-/// statistics (most commonly the b10-stats program).
-///
-/// This class uses pimpl idiom and hides detailed implementation.
-/// This class is constructed on startup of the server, so
-/// construction overhead of this approach should be acceptable.
-class Counter {
-private:
-    CounterImpl* impl_;
-public:
-    /// Enum for the type of counter
-    enum CounterType {
-        COUNTER_UDP = 0, ///< counter for UDP queries
-        COUNTER_TCP = 1, ///< counter for TCP queries
-        COUNTER_TYPES = 2 ///< The number of defined counters
-    };
-
-    /// The constructor.
-    ///
-    /// \param verbose_mode references verbose_mode_ of AuthSrvImpl
-    Counter(const bool& verbose_mode);
-
-    /// The destructor.
-    ~Counter();
-
-    /// \brief Increment the counter specified by the parameter
-    ///
-    /// \param type Type of a counter to increment
-    /// usage: counter->inc(CounterType::COUNTER_DUP);
-    /// 
-    void inc(const CounterType type);
-
-    /// \brief Get the function to send statistics information
-    /// to a Statistics module
-    ///
-    /// The returned function is inteneded to be called
-    /// periodically.
-    ///
-    /// \return \c asio_link::IntervalTimer::Callback points
-    /// to a function to send statistics
-    asio_link::IntervalTimer::Callback getCallback();
-
-    /// \brief Set the session to communicate with Statistics
-    /// module
-    ///
-    /// \param stats_session A pointer to the session
-    void setStatsSession(isc::cc::AbstractSession* stats_session);
-};
-
-} // statistics
-
-#endif // __STATS_H
-
-// Local Variables: 
-// mode: c++
-// End: 

+ 2 - 1
src/bin/auth/tests/Makefile.am

@@ -22,10 +22,11 @@ run_unittests_SOURCES = $(top_srcdir)/src/lib/dns/tests/unittest_util.h
 run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
 run_unittests_SOURCES += ../auth_srv.h ../auth_srv.cc
 run_unittests_SOURCES += ../change_user.h ../change_user.cc
-run_unittests_SOURCES += ../stats.h ../stats.cc
+run_unittests_SOURCES += ../statistics.h ../statistics.cc
 run_unittests_SOURCES += auth_srv_unittest.cc
 run_unittests_SOURCES += change_user_unittest.cc
 run_unittests_SOURCES += asio_link_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)

+ 229 - 27
src/bin/auth/tests/asio_link_unittest.cc

@@ -32,6 +32,8 @@
 
 #include <auth/asio_link.h>
 
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
 using isc::UnitTestUtil;
 using namespace std;
 using namespace asio_link;
@@ -44,6 +46,9 @@ const char* const TEST_IPV4_ADDR = "127.0.0.1";
 // 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 mergin
+const boost::posix_time::time_duration TIMER_MERGIN_MSEC =
+    boost::posix_time::milliseconds(50);
 
 TEST(IOAddressTest, fromText) {
     IOAddress io_address_v4("192.0.2.1");
@@ -252,15 +257,92 @@ protected:
                             callback_data_.size(),
                             expected_data, expected_datasize);
     }
-    void doTimerTest(asio_link::IntervalTimer* itimer) {
-        // interval timer test
-        // set test_obj_->timerCallBack() as callback function
-        // and check that the function was called
-        timer_called_ = false;
-        EXPECT_TRUE(itimer->setupTimer(TimerCallBack(this), 1));
-        io_service_->run();
-        EXPECT_TRUE(timer_called_);
-    }
+    class TimerCallBack : public std::unary_function<void, void> {
+    public:
+        TimerCallBack(ASIOLinkTest* test_obj) : test_obj_(test_obj) {}
+        void operator()() const {
+            test_obj_->timer_called_ = true;
+            test_obj_->io_service_->stop();
+            return;
+        }
+    private:
+        ASIOLinkTest* test_obj_;
+    };
+    class TimerCallBackCounter : public std::unary_function<void, void> {
+    public:
+        TimerCallBackCounter(ASIOLinkTest* test_obj) : test_obj_(test_obj) {
+            counter_ = 0;
+        }
+        void operator()() {
+            ++counter_;
+            return;
+        }
+        int counter_;
+    private:
+        ASIOLinkTest* test_obj_;
+    };
+    class TimerCallBackCancelDeleter : public std::unary_function<void, void> {
+    public:
+        TimerCallBackCancelDeleter(ASIOLinkTest* 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:
+        ASIOLinkTest* test_obj_;
+        IntervalTimer* timer_;
+        TimerCallBackCounter& counter_;
+        int count_;
+        int prev_counter_;
+    };
+    class TimerCallBackOverwriter : public std::unary_function<void, void> {
+    public:
+        TimerCallBackOverwriter(ASIOLinkTest* 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(IntervalTimer::Callback(
+                                       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:
+        ASIOLinkTest* test_obj_;
+        IntervalTimer& timer_;
+        int count_;
+    };
 private:
     class ASIOCallBack : public std::unary_function<IOMessage, void> {
     public:
@@ -282,20 +364,6 @@ private:
             io_message.getDataSize());
         io_service_->stop();
     }
-private:
-    class TimerCallBack : public std::unary_function<void, void> {
-    public:
-        TimerCallBack(ASIOLinkTest* test_obj) : test_obj_(test_obj) {}
-        void operator()(void) const {
-            test_obj_->timerCallBack();
-        }
-    private:
-        ASIOLinkTest* test_obj_;
-    };
-    void timerCallBack() {
-        timer_called_ = true;
-        io_service_->stop();
-    }
 protected:
     IOService* io_service_;
     int callback_protocol_;
@@ -303,6 +371,7 @@ protected:
     string callback_address_;
     vector<uint8_t> callback_data_;
     bool timer_called_;
+    bool timer_cancel_success_;
     int sock_;
 private:
     struct addrinfo* res_;
@@ -378,13 +447,146 @@ TEST_F(ASIOLinkTest, v4TCPOnly) {
     EXPECT_THROW(sendTCP(AF_INET6), IOError);
 }
 
+TEST_F(ASIOLinkTest, invalidArgumentToIntervalTimer) {
+    // Create asio_link::IntervalTimer and setup.
+    setIOService(false, false);
+    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(
+                     IntervalTimer::Callback(TimerCallBack(this)), 0),
+                     isc::BadValue);
+}
+
 TEST_F(ASIOLinkTest, startIntervalTimer) {
     // Create asio_link::IntervalTimer and setup.
     // Then run IOService and test if the callback function is called.
     setIOService(false, false);
-    asio_link::IntervalTimer* itimer =
-        new asio_link::IntervalTimer(io_service_->get_io_service());
-    doTimerTest(itimer);
-    delete itimer;
+    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
+    EXPECT_NO_THROW(itimer.setupTimer(
+                        IntervalTimer::Callback(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 call back is updated: TimerCallBack is called
+    EXPECT_TRUE(timer_called_);
+    // expect interval is 1 second +/- TIMER_MERGIN_MSEC.
+    EXPECT_TRUE(delta < TIMER_MERGIN_MSEC);
+}
+
+TEST_F(ASIOLinkTest, destructIntervalTimer) {
+    // The call back function will not be called
+    // after the timer destucted.
+    setIOService(false, false);
+    // 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;
+    ASSERT_NO_THROW(itimer_counter = new IntervalTimer(*io_service_));
+    IntervalTimer itimer_canceller(*io_service_);
+    timer_cancel_success_ = false;
+    TimerCallBackCounter callback_canceller(this);
+    itimer_counter->setupTimer(IntervalTimer::Callback(callback_canceller), 2);
+    itimer_canceller.setupTimer(
+        IntervalTimer::Callback(
+            TimerCallBackCancelDeleter(this, itimer_counter,
+                                       callback_canceller)),
+        3);
+    io_service_->run();
+    EXPECT_TRUE(timer_cancel_success_);
+}
+
+TEST_F(ASIOLinkTest, overwriteIntervalTimer) {
+    // Calling setupTimer() multiple times updates call back
+    // function and interval.
+    setIOService(false, false);
+    // 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(IntervalTimer::Callback(TimerCallBackCounter(this)), 2);
+    itimer_overwriter.setupTimer(
+        IntervalTimer::Callback(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 call back is updated: TimerCallBack is called
+    EXPECT_TRUE(timer_called_);
+    // expect interval is updated
+    EXPECT_TRUE(delta < TIMER_MERGIN_MSEC);
 }
 }

+ 16 - 53
src/bin/auth/tests/auth_srv_unittest.cc

@@ -34,6 +34,7 @@
 
 #include <auth/auth_srv.h>
 #include <auth/asio_link.h>
+#include <auth/statistics.h>
 
 #include <dns/tests/unittest_util.h>
 
@@ -44,7 +45,6 @@ using namespace isc::dns;
 using namespace isc::data;
 using namespace isc::xfr;
 using namespace asio_link;
-using namespace statistics;
 
 namespace {
 const char* const CONFIG_TESTDB =
@@ -121,8 +121,7 @@ protected:
                     qclass(RRClass::IN()), qtype(RRType::A()),
                     io_message(NULL), endpoint(NULL), request_obuffer(0),
                     request_renderer(request_obuffer),
-                    response_obuffer(0), response_renderer(response_obuffer),
-                    counter(counter_verbose)
+                    response_obuffer(0), response_renderer(response_obuffer)
     {
         server.setXfrinSession(&notify_session);
         server.setStatsSession(&stats_session);
@@ -149,12 +148,6 @@ protected:
     OutputBuffer response_obuffer;
     MessageRenderer response_renderer;
     vector<uint8_t> data;
-    // for Counter unittest
-    // TODO: consider where to put Counter
-    // AuthSrvTest is now includes a test for Counter
-    // In future make a test class CounterTest
-    bool counter_verbose;
-    Counter counter;
 
     void createDataFromFile(const char* const datafile, int protocol);
     void createRequestMessage(const Opcode& opcode, const Name& request_name,
@@ -778,51 +771,21 @@ TEST_F(AuthSrvTest, cacheSlots) {
     EXPECT_EQ(00, server.getCacheSlots());
 }
 
-TEST_F(AuthSrvTest, statsCallback) {
-    // getStatsCallback() test
-    // expect returning a valid function
-    asio_link::IntervalTimer::Callback cbFunc;
-    cbFunc = server.getStatsCallback();
-    EXPECT_FALSE(cbFunc.empty());
-}
-
-TEST_F(AuthSrvTest, sendStatsWithoutSession) {
-    // to cover the code path in case the stats session is not set
-    // expect to put an error message
-    server.setStatsSession(NULL);
-    bool verbose = server.getVerbose();
-    server.setVerbose(true);
-    server.getStatsCallback()();
-    server.setVerbose(verbose);
-}
-
-//
-// statistics::Counter unittest
-
-TEST_F(AuthSrvTest, counter_incUDP) {
-    counter.inc(Counter::COUNTER_UDP);
-}
-
-TEST_F(AuthSrvTest, counter_incTCP) {
-    counter.inc(Counter::COUNTER_TCP);
-}
-
-TEST_F(AuthSrvTest, counter_incUnknown) {
-    EXPECT_THROW(counter.inc(Counter::COUNTER_TYPES), std::out_of_range);
-}
-
-TEST_F(AuthSrvTest, counter_getCallback) {
-    // getCallback() test
-    // expect returning a valid function
-    asio_link::IntervalTimer::Callback cbFunc;
-    cbFunc = counter.getCallback();
-    EXPECT_FALSE(cbFunc.empty());
+TEST_F(AuthSrvTest, queryCounterUDP) {
+    // submit UDP query and check query counter
+    createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
+                        RRType::NS(), IPPROTO_UDP);
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
+    EXPECT_EQ(1, server.getCounters().at(QueryCounters::COUNTER_UDP));
 }
 
-TEST_F(AuthSrvTest, counter_sendStatsWithSession) {
-    // Test the function to send statistics information to b10-stats
-    // expect to run without throwing any exception
-    counter.setStatsSession(&stats_session);
-    counter.getCallback()();
+TEST_F(AuthSrvTest, queryCounterTCP) {
+    // submit TCP query and check query counter
+    createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
+                        RRType::NS(), IPPROTO_TCP);
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
+    EXPECT_EQ(1, server.getCounters().at(QueryCounters::COUNTER_TCP));
 }
 }

+ 200 - 0
src/bin/auth/tests/statistics_unittest.cc

@@ -0,0 +1,200 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+#include <cc/data.h>
+#include <cc/session.h>
+
+#include <auth/statistics.h>
+
+#include <dns/tests/unittest_util.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace isc::cc;
+using namespace isc::dns;
+using namespace isc::data;
+
+namespace {
+
+class QueryCountersTest : public ::testing::Test {
+private:
+    class MockSession : public AbstractSession {
+    public:
+        MockSession() :
+            // by default we return a simple "success" message.
+            msg_(Element::fromJSON("{\"result\": [0, \"SUCCESS\"]}")),
+            throw_session_error_(false), throw_session_timeout_(false)
+        {}
+        virtual void establish(const char* socket_file);
+        virtual void disconnect();
+        virtual int group_sendmsg(ConstElementPtr msg, string group,
+                                  string instance, string to);
+        virtual bool group_recvmsg(ConstElementPtr& envelope,
+                                   ConstElementPtr& msg,
+                                   bool nonblock, int seq);
+        virtual void subscribe(string group, string instance);
+        virtual void unsubscribe(string group, string instance);
+        virtual void startRead(boost::function<void()> read_callback);
+        virtual int reply(ConstElementPtr envelope, ConstElementPtr newmsg);
+        virtual bool hasQueuedMsgs() const;
+        virtual void setTimeout(size_t) {}
+        virtual size_t getTimeout() const { return (0); };
+
+        void setThrowSessionError(bool flag);
+        void setThrowSessionTimeout(bool flag);
+
+        void setMessage(ConstElementPtr msg) { msg_ = msg; }
+
+        ConstElementPtr sent_msg;
+        string msg_destination;
+    private:
+        ConstElementPtr msg_;
+        bool throw_session_error_;
+        bool throw_session_timeout_;
+    };
+
+protected:
+    QueryCountersTest() : counters(verbose_mode_) {
+        counters.setStatsSession(&stats_session_);
+    }
+    ~QueryCountersTest() {
+    }
+    MockSession stats_session_;
+    QueryCounters counters;
+    bool verbose_mode_;
+};
+
+void
+QueryCountersTest::MockSession::establish(const char*) {}
+
+void
+QueryCountersTest::MockSession::disconnect() {}
+
+void
+QueryCountersTest::MockSession::subscribe(string, string)
+{}
+
+void
+QueryCountersTest::MockSession::unsubscribe(string, string)
+{}
+
+void
+QueryCountersTest::MockSession::startRead(boost::function<void()>)
+{}
+
+int
+QueryCountersTest::MockSession::reply(ConstElementPtr, ConstElementPtr) {
+    return (-1);
+}
+
+bool
+QueryCountersTest::MockSession::hasQueuedMsgs() const {
+    return (false);
+}
+
+int
+QueryCountersTest::MockSession::group_sendmsg(ConstElementPtr msg,
+                                              string group, string, string)
+{
+    if (throw_session_error_) {
+        isc_throw(SessionError, "Session Error");
+    }
+    sent_msg = msg;
+    msg_destination = group;
+    return (0);
+}
+
+bool
+QueryCountersTest::MockSession::group_recvmsg(ConstElementPtr&,
+                                              ConstElementPtr& msg, bool, int)
+{
+    if (throw_session_timeout_) {
+        isc_throw(SessionTimeout, "Session Timeout");
+    }
+    msg = msg_;
+    return (true);
+}
+
+void
+QueryCountersTest::MockSession::setThrowSessionError(bool flag) {
+    throw_session_error_ = flag;
+}
+
+void
+QueryCountersTest::MockSession::setThrowSessionTimeout(bool flag) {
+    throw_session_timeout_ = flag;
+}
+
+TEST_F(QueryCountersTest, incrementUDPCounter) {
+    EXPECT_NO_THROW(counters.inc(QueryCounters::COUNTER_UDP));
+}
+
+TEST_F(QueryCountersTest, incrementTCPCounter) {
+    EXPECT_NO_THROW(counters.inc(QueryCounters::COUNTER_TCP));
+}
+
+TEST_F(QueryCountersTest, incrementInvalidCounter) {
+    EXPECT_THROW(counters.inc(QueryCounters::COUNTER_TYPES),
+                 isc::InvalidParameter);
+}
+
+TEST_F(QueryCountersTest, submitStatisticsWithoutSession) {
+    // Set stats_session to NULL and call submitStatistics().
+    // Expect to return false.
+    counters.setStatsSession(NULL);
+    EXPECT_FALSE(counters.submitStatistics());
+}
+
+TEST_F(QueryCountersTest, submitStatisticsWithException) {
+    // Exception SessionError and SessionTimeout will be thrown
+    // while sending statistics data.
+    // Both expect to return false.
+    stats_session_.setThrowSessionError(true);
+    EXPECT_FALSE(counters.submitStatistics());
+    stats_session_.setThrowSessionError(false);
+    stats_session_.setThrowSessionTimeout(true);
+    EXPECT_FALSE(counters.submitStatistics());
+    stats_session_.setThrowSessionTimeout(false);
+}
+
+TEST_F(QueryCountersTest, submitStatistics) {
+    // Submit statistics data.
+    // Validate if it submits correct data.
+
+    // UDP query counter is set to 2.
+    counters.inc(QueryCounters::COUNTER_UDP);
+    counters.inc(QueryCounters::COUNTER_UDP);
+    // TCP query counter is set to 1.
+    counters.inc(QueryCounters::COUNTER_TCP);
+    counters.submitStatistics();
+
+    // Destination is "Stats".
+    EXPECT_EQ("Stats", stats_session_.msg_destination);
+    // Command is "set".
+    EXPECT_EQ("set", stats_session_.sent_msg->get("command")
+                         ->get(0)->stringValue());
+    ConstElementPtr stats_data = stats_session_.sent_msg ->get("command")
+                                     ->get(1)->get("stats_data");
+    // UDP query counter is 2 and TCP query counter is 1.
+    EXPECT_EQ(2, stats_data->get("auth.queries.udp")->intValue());
+    EXPECT_EQ(1, stats_data->get("auth.queries.tcp")->intValue());
+}
+
+}