Browse Source

[master] Merge branch 'master' of ssh://git.kea.isc.org/git/kea

Marcin Siodelski 10 years ago
parent
commit
f3e7ab056d

+ 8 - 2
ChangeLog

@@ -1,4 +1,10 @@
-929	[build]		fdupont
+930.	[func]		tomek
+	Statistics Manager is now implemented. There is a new library
+	libkea-stats that governs statistics collection. Its usage will
+	be added in the upcoming tickets.
+	(Trac #3793, git 68e9554ecabfc2a79731eeec1c706522e4d39332)
+
+929.	[build]		fdupont
 	Corrected problem in build system whereby specifying an
 	Corrected problem in build system whereby specifying an
 	installation directory on the "configure" command line that
 	installation directory on the "configure" command line that
 	included a "+" in the name caused the build to fail.
 	included a "+" in the name caused the build to fail.
@@ -10,7 +16,7 @@
 	(Trac #3812, git cbb135d5f217b0692dcdbc9cfcc04f6a0dbc3922)
 	(Trac #3812, git cbb135d5f217b0692dcdbc9cfcc04f6a0dbc3922)
 
 
 927.	[bug]		tmark
 927.	[bug]		tmark
-	DHCPv4 no longer attempts to update the lease database with the 
+	DHCPv4 no longer attempts to update the lease database with the
 	generated FQDN when processing DHCPDISCOVERs.
 	generated FQDN when processing DHCPDISCOVERs.
 	(Trac #3779, git 0b413ee8aba1afa1643b216a1e8c35103c6c975b)
 	(Trac #3779, git 0b413ee8aba1afa1643b216a1e8c35103c6c975b)
 
 

+ 2 - 0
configure.ac

@@ -1510,6 +1510,8 @@ AC_CONFIG_FILES([compatcheck/Makefile
                  src/lib/testutils/Makefile
                  src/lib/testutils/Makefile
                  src/lib/testutils/dhcp_test_lib.sh
                  src/lib/testutils/dhcp_test_lib.sh
                  src/lib/testutils/testdata/Makefile
                  src/lib/testutils/testdata/Makefile
+                 src/lib/stats/Makefile
+                 src/lib/stats/tests/Makefile
                  src/lib/util/Makefile
                  src/lib/util/Makefile
                  src/lib/util/io/Makefile
                  src/lib/util/io/Makefile
                  src/lib/util/python/Makefile
                  src/lib/util/python/Makefile

+ 2 - 3
src/lib/Makefile.am

@@ -1,4 +1,3 @@
 # The following build order must be maintained.
 # The following build order must be maintained.
-SUBDIRS = exceptions util log hooks cryptolink dns cc config \
+SUBDIRS = exceptions util log hooks cryptolink dns cc stats config \
-          asiolink asiodns testutils dhcp dhcp_ddns \
+          asiolink asiodns testutils dhcp dhcp_ddns dhcpsrv
-          dhcpsrv

+ 7 - 0
src/lib/cc/data.h

@@ -675,6 +675,13 @@ public:
     // a MapElement)
     // a MapElement)
     bool find(const std::string& id, ConstElementPtr& t) const;
     bool find(const std::string& id, ConstElementPtr& t) const;
 
 
+    /// @brief Returns number of stored elements
+    ///
+    /// @return number of elements.
+    size_t size() const {
+        return (m.size());
+    }
+
     bool equals(const Element& other) const;
     bool equals(const Element& other) const;
 };
 };
 
 

+ 17 - 0
src/lib/stats/Makefile.am

@@ -0,0 +1,17 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS=$(KEA_CXXFLAGS)
+
+lib_LTLIBRARIES = libkea-stats.la
+libkea_stats_la_SOURCES = observation.h observation.cc
+libkea_stats_la_SOURCES += context.h context.cc
+libkea_stats_la_SOURCES += stats_mgr.h stats_mgr.cc
+
+libkea_stats_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_stats_la_LIBADD  = $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_stats_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+
+libkea_stats_includedir = $(includedir)/$(PACKAGE_NAME)/stats
+libkea_stats_include_HEADERS = stats_mgr.h

+ 52 - 0
src/lib/stats/context.cc

@@ -0,0 +1,52 @@
+// Copyright (C) 2015 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.
+
+#include <stats/context.h>
+#include <map>
+
+namespace isc {
+namespace stats {
+
+ObservationPtr StatContext::get(const std::string& name) const {
+    std::map<std::string, ObservationPtr>::const_iterator obs = stats_.find(name);
+    if (obs == stats_.end()) {
+        return (ObservationPtr());
+    } else {
+        return (obs->second);
+    }
+}
+
+void StatContext::add(const ObservationPtr& obs) {
+    std::map<std::string, ObservationPtr>::iterator existing = stats_.find(obs->getName());
+    if (existing == stats_.end()) {
+        stats_.insert(make_pair(obs->getName() ,obs));
+    } else {
+        isc_throw(DuplicateStat, "Statistic named " << obs->getName()
+                  << " already exists.");
+    }
+
+}
+
+bool StatContext::del(const std::string& name) {
+    std::map<std::string, ObservationPtr>::iterator obs = stats_.find(name);
+    if (obs == stats_.end()) {
+        return (false);
+    } else {
+        stats_.erase(obs);
+        return (true);
+    }
+}
+
+};
+};

+ 72 - 0
src/lib/stats/context.h

@@ -0,0 +1,72 @@
+// Copyright (C) 2015 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.
+
+#ifndef CONTEXT_H
+#define CONTEXT_H
+
+#include <stats/observation.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace stats {
+
+/// @brief Exception indicating that a given statistic is duplicated.
+class DuplicateStat : public Exception {
+public:
+    DuplicateStat(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// @brief Statistics context
+///
+/// Statistics context is essentially a container used to group statistics
+/// related to a given context together. Two examples of such contexts are
+/// all statistics related to a given subnet or all statistics related to a
+/// given network interface.
+struct StatContext {
+ public:
+
+    /// @brief attempts to get an observation
+    /// @param name name of the statistic
+    /// @return appropriate Observation object (or NULL)
+    ObservationPtr get(const std::string& name) const;
+
+    /// @brief Adds a new observation
+    /// @param obs observation to be added
+    /// @throw DuplicateStat if an observation with the same name exists already
+    void add(const ObservationPtr& obs);
+
+    /// @brief Attempts to delete an observation
+    /// @param name name of the observation to be deleted
+    /// @return true if successful, false if no such statistic was found
+    bool del(const std::string& name);
+
+    /// @brief Statistics container
+    ///
+    /// It is public to allow various operations that require iterating over
+    /// all elements. In particular, two operations (setting all stats to 0;
+    /// reporting all stats) will take advantage of this. Alternatively, we
+    /// could make it protected and then return a pointer to it, but that
+    /// would defeat the purpose of the hermetization in the first place.
+    std::map<std::string, ObservationPtr> stats_;
+};
+
+/// @brief Pointer to the statistics context
+typedef boost::shared_ptr<StatContext> StatContextPtr;
+
+};
+};
+
+#endif // CONTEXT_H

+ 233 - 0
src/lib/stats/observation.cc

@@ -0,0 +1,233 @@
+// Copyright (C) 2015 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.
+
+#include <stats/observation.h>
+#include <util/boost_time_utils.h>
+#include <cc/data.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/date_time/gregorian/gregorian.hpp>
+#include <utility>
+
+using namespace std;
+using namespace isc::data;
+using namespace boost::posix_time;
+
+namespace isc {
+namespace stats {
+
+Observation::Observation(const std::string& name, const uint64_t value)
+    :name_(name), type_(STAT_INTEGER) {
+    setValue(value);
+}
+
+Observation::Observation(const std::string& name, const double value)
+    :name_(name), type_(STAT_FLOAT) {
+    setValue(value);
+}
+
+Observation::Observation(const std::string& name, const StatsDuration& value)
+    :name_(name), type_(STAT_DURATION) {
+    setValue(value);
+}
+
+Observation::Observation(const std::string& name, const std::string& value)
+    :name_(name), type_(STAT_STRING) {
+    setValue(value);
+}
+
+void Observation::addValue(const uint64_t value) {
+    IntegerSample current = getInteger();
+    setValue(current.first + value);
+}
+
+void Observation::addValue(const double value) {
+    FloatSample current = getFloat();
+    setValue(current.first + value);
+}
+
+void Observation::addValue(const StatsDuration& value) {
+    DurationSample current = getDuration();
+    setValue(current.first + value);
+}
+
+void Observation::addValue(const std::string& value) {
+    StringSample current = getString();
+    setValue(current.first + value);
+}
+
+void Observation::setValue(const uint64_t value) {
+    setValueInternal(value, integer_samples_, STAT_INTEGER);
+}
+
+void Observation::setValue(const double value) {
+    setValueInternal(value, float_samples_, STAT_FLOAT);
+}
+
+void Observation::setValue(const StatsDuration& value) {
+    setValueInternal(value, duration_samples_, STAT_DURATION);
+}
+
+void Observation::setValue(const std::string& value) {
+    setValueInternal(value, string_samples_, STAT_STRING);
+}
+
+template<typename SampleType, typename StorageType>
+void Observation::setValueInternal(SampleType value, StorageType& storage,
+    Type exp_type) {
+    if (type_ != exp_type) {
+        isc_throw(InvalidStatType, "Invalid statistic type requested: "
+                  << typeToText(exp_type) << ", but the actual type is "
+                  << typeToText(type_) );
+    }
+
+    if (storage.empty()) {
+        storage.push_back(make_pair(value, microsec_clock::local_time()));
+    } else {
+
+        /// @todo: Update once more than one sample is supported
+        *storage.begin() = make_pair(value, microsec_clock::local_time());
+    }
+}
+
+IntegerSample Observation::getInteger() const {
+    return (getValueInternal<IntegerSample>(integer_samples_, STAT_INTEGER));
+}
+
+FloatSample Observation::getFloat() const {
+    return (getValueInternal<FloatSample>(float_samples_, STAT_FLOAT));
+}
+
+DurationSample Observation::getDuration() const {
+    return (getValueInternal<DurationSample>(duration_samples_, STAT_DURATION));
+}
+
+StringSample Observation::getString() const {
+    return (getValueInternal<StringSample>(string_samples_, STAT_STRING));
+}
+
+template<typename SampleType, typename Storage>
+SampleType Observation::getValueInternal(Storage& storage, Type exp_type) const {
+    if (type_ != exp_type) {
+        isc_throw(InvalidStatType, "Invalid statistic type requested: "
+                  << typeToText(exp_type) << ", but the actual type is "
+                  << typeToText(type_) );
+    }
+
+    if (storage.empty()) {
+        // That should never happen. The first element is always initialized in
+        // the constructor. reset() sets its value to zero, but the element should
+        // still be there.
+        isc_throw(Unexpected, "Observation storage container empty");
+    }
+    return (*storage.begin());
+}
+
+std::string Observation::typeToText(Type type) {
+    std::stringstream tmp;
+    switch (type) {
+    case STAT_INTEGER:
+        tmp << "integer";
+        break;
+    case STAT_FLOAT:
+        tmp << "float";
+        break;
+    case STAT_DURATION:
+        tmp << "duration";
+        break;
+    case STAT_STRING:
+        tmp << "string";
+        break;
+    default:
+        tmp << "unknown";
+        break;
+    }
+    tmp << "(" << type << ")";
+    return (tmp.str());
+}
+
+isc::data::ConstElementPtr
+Observation::getJSON() const {
+
+    ElementPtr entry = isc::data::Element::createList(); // a single observation
+    ElementPtr value;
+    ElementPtr timestamp;
+
+    /// @todo: Add support for retrieving more than one sample for a given
+    /// observation
+
+    switch (type_) {
+    case STAT_INTEGER: {
+        IntegerSample s = getInteger();
+        value = isc::data::Element::create(static_cast<int64_t>(s.first));
+        timestamp = isc::data::Element::create(isc::util::ptimeToText(s.second));
+        break;
+    }
+    case STAT_FLOAT: {
+        FloatSample s = getFloat();
+        value = isc::data::Element::create(s.first);
+        timestamp = isc::data::Element::create(isc::util::ptimeToText(s.second));
+        break;
+    }
+    case STAT_DURATION: {
+        DurationSample s = getDuration();
+        value = isc::data::Element::create(isc::util::durationToText(s.first));
+        timestamp = isc::data::Element::create(isc::util::ptimeToText(s.second));
+        break;
+    }
+    case STAT_STRING: {
+        StringSample s = getString();
+        value = isc::data::Element::create(s.first);
+        timestamp = isc::data::Element::create(isc::util::ptimeToText(s.second));
+        break;
+    }
+    default:
+        isc_throw(InvalidStatType, "Unknown statistic type: "
+                  << typeToText(type_));
+    };
+
+    entry->add(value);
+    entry->add(timestamp);
+
+    ElementPtr list = isc::data::Element::createList(); // a single observation
+    list->add(entry);
+
+    return (list);
+}
+
+void Observation::reset() {
+    switch(type_) {
+    case STAT_INTEGER: {
+        setValue(static_cast<uint64_t>(0));
+        return;
+    }
+    case STAT_FLOAT: {
+        setValue(0.0);
+        return;
+    }
+    case STAT_DURATION: {
+        setValue(time_duration(0,0,0,0));
+        return;
+    }
+    case STAT_STRING: {
+        setValue(string(""));
+        return;
+    }
+    default:
+        isc_throw(InvalidStatType, "Unknown statistic type: "
+                  << typeToText(type_));
+    };
+}
+
+};
+};

+ 274 - 0
src/lib/stats/observation.h

@@ -0,0 +1,274 @@
+// Copyright (C) 2015 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.
+
+#ifndef OBSERVATION_H
+#define OBSERVATION_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/date_time/time_duration.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+#include <list>
+#include <stdint.h>
+
+namespace isc {
+namespace stats {
+
+/// @brief Exception thrown if invalid statistic type is used
+///
+/// For example statistic is of type duration, but methods using
+/// it as integer are called.
+class InvalidStatType : public Exception {
+public:
+    InvalidStatType(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// @brief Defines duration resolution
+///
+typedef boost::posix_time::time_duration StatsDuration;
+
+/// @defgroup stat_samples Specifies supported observation types.
+///
+/// @brief The list covers all supported types of observations.
+///
+/// @{
+
+/// @brief Integer (implemented as unsigned 64-bit integer)
+typedef std::pair<uint64_t, boost::posix_time::ptime> IntegerSample;
+
+/// @brief Float (implemented as double precision)
+typedef std::pair<double, boost::posix_time::ptime> FloatSample;
+
+/// @brief Time Duration
+typedef std::pair<StatsDuration, boost::posix_time::ptime> DurationSample;
+
+/// @brief String
+typedef std::pair<std::string, boost::posix_time::ptime> StringSample;
+
+/// @}
+
+/// @brief Represents a single observable characteristic (a 'statistic')
+///
+/// Currently it supports one of four types: integer (implemented as unsigned 64
+/// bit integer), float (implemented as double), time duration (implemented with
+/// millisecond precision) and string. Absolute (setValue) and
+/// incremental (addValue) modes are supported. Statistic type is determined
+/// during its first use. Once type is set, any additional observations recorded
+/// must be of the same type. Attempting to set or extract information about
+/// other types will result in InvalidStateType exception.
+///
+/// Observation can be retrieved in one of @ref getInteger, @ref getFloat,
+/// @ref getDuration, @ref getString (appropriate type must be used) or
+/// @ref getJSON, which is generic and can be used for all types.
+///
+/// @todo: Eventually it will be possible to retain multiple samples for the same
+/// observation, but that is outside of scope for 0.9.2.
+class Observation {
+ public:
+
+    /// @brief Type of available statistics
+    ///
+    /// Note that those will later be exposed using control socket. Therefore
+    /// an easy to understand names were chosen (integer instead of uint64).
+    /// To avoid confusion, we will support only one type of integer and only
+    /// one type of floating points. Initially, these are represented by
+    /// uint64_t and double. If convincing use cases appear to change them
+    /// to something else, we may change the underlying type.
+    enum Type {
+        STAT_INTEGER, ///< this statistic is unsinged 64-bit integer value
+        STAT_FLOAT,   ///< this statistic is a floating point value
+        STAT_DURATION,///< this statistic represents time duration
+        STAT_STRING   ///< this statistic represents a string
+    };
+
+    /// @brief Constructor for integer observations
+    ///
+    /// @param name observation name
+    /// @param value integer value observed.
+    Observation(const std::string& name, const uint64_t value);
+
+    /// @brief Constructor for floating point observations
+    ///
+    /// @param name observation name
+    /// @param value floating point value observed.
+    Observation(const std::string& name, const double value);
+
+    /// @brief Constructor for duration observations
+    ///
+    /// @param name observation name
+    /// @param value duration observed.
+    Observation(const std::string& name, const StatsDuration& value);
+
+    /// @brief Constructor for string observations
+    ///
+    /// @param name observation name
+    /// @param value string observed.
+    Observation(const std::string& name, const std::string& value);
+
+    /// @brief Records absolute integer observation
+    ///
+    /// @param value integer value observed
+    /// @throw InvalidStatType if statistic is not integer
+    void setValue(const uint64_t value);
+
+    /// @brief Records absolute floating point observation
+    ///
+    /// @param value floating point value observed
+    /// @throw InvalidStatType if statistic is not fp
+    void setValue(const double value);
+
+    /// @brief Records absolute duration observation
+    ///
+    /// @param value duration value observed
+    /// @throw InvalidStatType if statistic is not time duration
+    void setValue(const StatsDuration& duration);
+
+    /// @brief Records absolute string observation
+    ///
+    /// @param value string value observed
+    /// @throw InvalidStatType if statistic is not a string
+    void setValue(const std::string& value);
+
+    /// @brief Records incremental integer observation
+    ///
+    /// @param value integer value observed
+    /// @throw InvalidStatType if statistic is not integer
+    void addValue(const uint64_t value);
+
+    /// @brief Records incremental floating point observation
+    ///
+    /// @param value floating point value observed
+    /// @throw InvalidStatType if statistic is not fp
+    void addValue(const double value);
+
+    /// @brief Records incremental duration observation
+    ///
+    /// @param value duration value observed
+    /// @throw InvalidStatType if statistic is not time duration
+    void addValue(const StatsDuration& value);
+
+    /// @brief Records incremental string observation.
+    ///
+    /// @param value string value observed
+    /// @throw InvalidStatType if statistic is not a string
+    void addValue(const std::string& value);
+
+    /// @brief Resets statistic.
+    ///
+    /// Sets statistic to a neutral (0, 0.0 or "") value.
+    void reset();
+
+    /// @brief Returns statistic type
+    /// @return statistic type
+    Type getType() const {
+        return (type_);
+    }
+
+    /// @brief Returns observed integer sample
+    /// @return observed sample (value + timestamp)
+    /// @throw InvalidStatType if statistic is not integer
+    IntegerSample getInteger() const;
+
+    /// @brief Returns observed float sample
+    /// @return observed sample (value + timestamp)
+    /// @throw InvalidStatType if statistic is not fp
+    FloatSample getFloat() const;
+
+    /// @brief Returns observed duration sample
+    /// @return observed sample (value + timestamp)
+    /// @throw InvalidStatType if statistic is not time duration
+    DurationSample getDuration() const;
+
+    /// @brief Returns observed string sample
+    /// @return observed sample (value + timestamp)
+    /// @throw InvalidStatType if statistic is not a string
+    StringSample getString() const;
+
+    /// @brief Returns as a JSON structure
+    /// @return JSON structures representing all observations
+    isc::data::ConstElementPtr getJSON() const;
+
+    /// @brief Converts statistic type to string
+    /// @return textual name of statistic type
+    static std::string typeToText(Type type);
+
+    /// @brief Returns observation name
+    std::string getName() const {
+        return (name_);
+    }
+
+private:
+    /// @brief Records absolute sample (internal version)
+    ///
+    /// This method records an absolute value of an observation.
+    /// It is used by public methods to add sample to one of
+    /// available storages.
+    ///
+    /// @tparam SampleType type of sample (e.g. IntegerSample)
+    /// @tparam StorageType type of storage (e.g. list<IntegerSample>)
+    /// @param value observation to be recorded
+    /// @param storage observation will be stored here
+    /// @param exp_type expected observation type (used for sanity checking)
+    /// @throw InvalidStatType if observation type mismatches
+    template<typename SampleType, typename StorageType>
+    void setValueInternal(SampleType value, StorageType& storage,
+                          Type exp_type);
+
+    /// @brief Returns a sample (internal version)
+    ///
+    /// @tparam SampleType type of sample (e.g. IntegerSample)
+    /// @tparam StorageType type of storage (e.g. list<IntegerSample>)
+    /// @param observation storage
+    /// @param exp_type expected observation type (used for sanity checking)
+    /// @throw InvalidStatType if observation type mismatches
+    /// @return Observed sample
+    template<typename SampleType, typename Storage>
+    SampleType getValueInternal(Storage& storage, Type exp_type) const;
+
+    /// @brief Observation (statistic) name
+    std::string name_;
+
+    /// @brief Observation (statistic) type)
+    Type type_;
+
+    /// @defgroup samples_storage Storage for supported observations
+    ///
+    /// @brief The following containers serve as a storage for all supported
+    /// observation types.
+    ///
+    /// @{
+
+    /// @brief Storage for integer samples
+    std::list<IntegerSample> integer_samples_;
+
+    /// @brief Storage for floating point samples
+    std::list<FloatSample> float_samples_;
+
+    /// @brief Storage for time duration samples
+    std::list<DurationSample> duration_samples_;
+
+    /// @brief Storage for string samples
+    std::list<StringSample> string_samples_;
+    /// @}
+};
+
+/// @brief Observation pointer
+typedef boost::shared_ptr<Observation> ObservationPtr;
+
+};
+};
+
+#endif // OBSERVATION_H

+ 148 - 0
src/lib/stats/stats_mgr.cc

@@ -0,0 +1,148 @@
+// Copyright (C) 2015 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.
+
+#include <exceptions/exceptions.h>
+#include <stats/stats_mgr.h>
+
+using namespace std;
+
+namespace isc {
+namespace stats {
+
+StatsMgr& StatsMgr::instance() {
+    static StatsMgr stats_mgr;
+    return (stats_mgr);
+}
+
+StatsMgr::StatsMgr()
+    :global_(new StatContext()) {
+
+}
+
+void StatsMgr::setValue(const std::string& name, const uint64_t value) {
+    setValueInternal(name, value);
+}
+
+void StatsMgr::setValue(const std::string& name, const double value) {
+    setValueInternal(name, value);
+}
+
+void StatsMgr::setValue(const std::string& name, const StatsDuration& value) {
+    setValueInternal(name, value);
+}
+void StatsMgr::setValue(const std::string& name, const std::string& value) {
+    setValueInternal(name, value);
+}
+
+void StatsMgr::addValue(const std::string& name, const uint64_t value) {
+    addValueInternal(name, value);
+}
+
+void StatsMgr::addValue(const std::string& name, const double value) {
+    addValueInternal(name, value);
+}
+
+void StatsMgr::addValue(const std::string& name, const StatsDuration& value) {
+    addValueInternal(name, value);
+}
+
+void StatsMgr::addValue(const std::string& name, const std::string& value) {
+    addValueInternal(name, value);
+}
+
+ObservationPtr StatsMgr::getObservation(const std::string& name) const {
+    /// @todo: Implement contexts.
+    // Currently we keep everyting in a global context.
+    return (global_->get(name));
+}
+
+void StatsMgr::addObservation(const ObservationPtr& stat) {
+    /// @todo: Implement contexts.
+    // Currently we keep everyting in a global context.
+    return (global_->add(stat));
+}
+
+bool StatsMgr::deleteObservation(const std::string& name) {
+    /// @todo: Implement contexts.
+    // Currently we keep everyting in a global context.
+    return (global_->del(name));
+}
+
+void StatsMgr::setMaxSampleAge(const std::string& ,
+                               const StatsDuration&) {
+    isc_throw(NotImplemented, "setMaxSampleAge not implemented");
+}
+
+void StatsMgr::setMaxSampleCount(const std::string& , uint32_t){
+    isc_throw(NotImplemented, "setMaxSampleCount not implemented");
+}
+
+bool StatsMgr::reset(const std::string& name) {
+    ObservationPtr obs = getObservation(name);
+    if (obs) {
+        obs->reset();
+        return (true);
+    } else {
+        return (false);
+    }
+}
+
+bool StatsMgr::del(const std::string& name) {
+    return (global_->del(name));
+}
+
+void StatsMgr::removeAll() {
+    global_->stats_.clear();
+}
+
+isc::data::ConstElementPtr StatsMgr::get(const std::string& name) const {
+    isc::data::ElementPtr response = isc::data::Element::createMap(); // a map
+    ObservationPtr obs = getObservation(name);
+    if (obs) {
+        response->set(name, obs->getJSON()); // that contains the observation
+    }
+    return (response);
+}
+
+isc::data::ConstElementPtr StatsMgr::getAll() const {
+    isc::data::ElementPtr map = isc::data::Element::createMap(); // a map
+
+    // Let's iterate over all stored statistics...
+    for (std::map<std::string, ObservationPtr>::iterator s = global_->stats_.begin();
+         s != global_->stats_.end(); ++s) {
+
+        // ... and add each of them to the map.
+        map->set(s->first, s->second->getJSON());
+    }
+    return (map);
+}
+
+void StatsMgr::resetAll() {
+    // Let's iterate over all stored statistics...
+    for (std::map<std::string, ObservationPtr>::iterator s = global_->stats_.begin();
+         s != global_->stats_.end(); ++s) {
+
+        // ... and reset each statistic.
+        s->second->reset();
+    }
+}
+
+size_t StatsMgr::count() const {
+    return (global_->stats_.size());
+}
+
+
+
+};
+};

+ 274 - 0
src/lib/stats/stats_mgr.h

@@ -0,0 +1,274 @@
+// Copyright (C) 2015 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.
+
+#ifndef STATSMGR_H
+#define STATSMGR_H
+
+#include <stats/observation.h>
+#include <stats/context.h>
+#include <boost/noncopyable.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace stats {
+
+/// @brief Statistics Manager class
+///
+/// StatsMgr is a singleton class that represents a subsystem that manages
+/// collection, storage and reporting of various types of statistics.
+/// It is also the intended API for both core code and hooks.
+///
+/// As of May 2015, Tomek ran performance benchmarks (see unit-tests in
+/// stats_mgr_unittest.cc with performance in their names) and it seems
+/// the code is able to register ~2.5-3 million observations per second, even
+/// with 1000 different statistics recored. That seems sufficient for now,
+/// so there is no immediate need to develop any multi-threading solutions
+/// for now. However, should this decision be revised in the future, the
+/// best place for it would to be modify @ref addObservation method here.
+/// It's the common code point that all new observations must pass through.
+/// One possible way to enable multi-threading would be to run a separate
+/// thread handling collection. The main thread would call @ref addValue and
+/// @ref setValue methods that would end up calling @ref addObservation.
+/// That method would pass the data to separate thread to be collected and
+/// would immediately return. Further processing would be mostly as it
+/// is today, except happening in a separate thread. One unsolved issue in
+/// this approach is how to extract data, but that will remain unsolvable
+/// until we get the control socket implementation.
+class StatsMgr : public boost::noncopyable {
+ public:
+
+    /// @brief Statistics Manager accessor method.
+    static StatsMgr& instance();
+
+    /// @defgroup producer_methods Methods are used by data producers.
+    ///
+    /// @brief The following methods are used by data producers:
+    ///
+    /// @{
+
+    /// @brief Records absolute integer observation.
+    ///
+    /// @param name name of the observation
+    /// @param value integer value observed
+    /// @throw InvalidStatType if statistic is not integer
+    void setValue(const std::string& name, const uint64_t value);
+
+    /// @brief Records absolute floating point observation.
+    ///
+    /// @param name name of the observation
+    /// @param value floating point value observed
+    /// @throw InvalidStatType if statistic is not fp
+    void setValue(const std::string& name, const double value);
+
+    /// @brief Records absolute duration observation.
+    ///
+    /// @param name name of the observation
+    /// @param value duration value observed
+    /// @throw InvalidStatType if statistic is not time duration
+    void setValue(const std::string& name, const StatsDuration& value);
+
+    /// @brief Records absolute string observation.
+    ///
+    /// @param name name of the observation
+    /// @param value string value observed
+    /// @throw InvalidStatType if statistic is not a string
+    void setValue(const std::string& name, const std::string& value);
+
+    /// @brief Records incremental integer observation.
+    ///
+    /// @param name name of the observation
+    /// @param value integer value observed
+    /// @throw InvalidStatType if statistic is not integer
+    void addValue(const std::string& name, const uint64_t value);
+
+    /// @brief Records incremental floating point observation.
+    ///
+    /// @param name name of the observation
+    /// @param value floating point value observed
+    /// @throw InvalidStatType if statistic is not fp
+    void addValue(const std::string& name, const double value);
+
+    /// @brief Records incremental duration observation.
+    ///
+    /// @param name name of the observation
+    /// @param value duration value observed
+    /// @throw InvalidStatType if statistic is not time duration
+    void addValue(const std::string& name, const StatsDuration& time);
+
+    /// @brief Records incremental string observation.
+    ///
+    /// @param name name of the observation
+    /// @param value string value observed
+    /// @throw InvalidStatType if statistic is not a string
+    void addValue(const std::string& name, const std::string& value);
+
+    /// @brief Determines maximum age of samples.
+    ///
+    /// Specifies that statistic name should be stored not as a single value,
+    /// but rather as a set of values. duration determines the timespan.
+    /// Samples older than duration will be discarded. This is time-constrained
+    /// approach. For sample count constrained approach, see @ref
+    /// setMaxSampleCount() below.
+    ///
+    /// @todo: Not implemented.
+    ///
+    /// Example: to set a statistic to keep observations for the last 5 minutes,
+    /// call setMaxSampleAge("incoming-packets", time_duration(0,5,0,0));
+    /// to revert statistic to a single value, call:
+    /// setMaxSampleAge("incoming-packets" time_duration(0,0,0,0))
+    void setMaxSampleAge(const std::string& name, const StatsDuration& duration);
+
+    /// @brief Determines how many samples of a given statistic should be kept.
+    ///
+    /// Specifies that statistic name should be stored not as single value, but
+    /// rather as a set of values. In this form, at most max_samples will be kept.
+    /// When adding max_samples+1 sample, the oldest sample will be discarded.
+    ///
+    /// @todo: Not implemented.
+    ///
+    /// Example:
+    /// To set a statistic to keep the last 100 observations, call:
+    /// setMaxSampleCount("incoming-packets", 100);
+    void setMaxSampleCount(const std::string& name, uint32_t max_samples);
+
+    /// @}
+
+    /// @defgroup consumer_methods Methods are used by data consumers.
+    ///
+    /// @brief The following methods are used by data consumers:
+    ///
+    /// @{
+
+    /// @brief Resets specified statistic.
+    ///
+    /// This is a convenience function and is equivalent to setValue(name,
+    /// neutral_value), where neutral_value is 0, 0.0 or "".
+    /// @param name name of the statistic to be reset.
+    /// @return true if successful, false if there's no such statistic
+    bool reset(const std::string& name);
+
+    /// @brief Removes specified statistic.
+    ///
+    /// @param name name of the statistic to be removed.
+    /// @return true if successful, false if there's no such statistic
+    bool del(const std::string& name);
+
+    /// @brief Resets all collected statistics back to zero.
+    void resetAll();
+
+    /// @brief Removes all collected statistics.
+    void removeAll();
+
+    /// @brief Returns number of available statistics.
+    ///
+    /// @return number of recorded statistics.
+    size_t count() const;
+
+    /// @brief Returns a single statistic as a JSON structure.
+    ///
+    /// @return JSON structures representing a single statistic
+    isc::data::ConstElementPtr get(const std::string& name) const;
+
+    /// @brief Returns all statistics as a JSON structure.
+    ///
+    /// @return JSON structures representing all statistics
+    isc::data::ConstElementPtr getAll() const;
+
+    /// @}
+
+    /// @brief Returns an observation.
+    ///
+    /// Used in testing only. Production code should use @ref get() method.
+    /// @param name name of the statistic
+    /// @return Pointer to the Observation object
+    ObservationPtr getObservation(const std::string& name) const;
+
+ private:
+
+    /// @brief Sets a given statistic to specified value (internal version).
+    ///
+    /// This template method sets statistic identified by name to a value
+    /// specified by value. This internal method is used by public @ref setValue
+    /// methods.
+    ///
+    /// @tparam DataType one of uint64_t, double, StatsDuration or string
+    /// @param name name of the statistic
+    /// @param value specified statistic will be set to this value
+    /// @throw InvalidStatType is statistic exists and has a different type.
+    template<typename DataType>
+    void setValueInternal(const std::string& name, DataType value) {
+        ObservationPtr stat = getObservation(name);
+        if (stat) {
+            stat->setValue(value);
+        } else {
+            stat.reset(new Observation(name, value));
+            addObservation(stat);
+        }
+    }
+
+    /// @brief Adds specified value to a given statistic (internal version).
+    ///
+    /// This template method adds specified value to a given statistic (identified
+    /// by name to a value). This internal method is used by public @ref setValue
+    /// methods.
+    ///
+    /// @tparam DataType one of uint64_t, double, StatsDuration or string
+    /// @param name name of the statistic
+    /// @param value specified statistic will be set to this value
+    /// @throw InvalidStatType is statistic exists and has a different type.
+    template<typename DataType>
+    void addValueInternal(const std::string& name, DataType value) {
+        ObservationPtr existing = getObservation(name);
+        if (!existing) {
+            // We tried to add to a non-existing statistic. We can recover from
+            // that. Simply add the new incremental value as a new statistic and
+            // we're done.
+            setValue(name, value);
+            return;
+        } else {
+            // Let's hope it is of correct type. If not, the underlying
+            // addValue() method will throw.
+            existing->addValue(value);
+        }
+    }
+
+    /// @brief Private constructor.
+    /// StatsMgr is a singleton. It should be accessed using @ref instance
+    /// method.
+    StatsMgr();
+
+    /// @brief Adds a new observation.
+    ///
+    /// That's an utility method used by public @ref setValue() and
+    /// @ref addValue() methods.
+    /// @param obs observation
+    void addObservation(const ObservationPtr& o);
+
+    /// @brief Tries to delete an observation.
+    ///
+    /// @param name of the statistic to be deleted
+    /// @return true if deleted, false if not found
+    bool deleteObservation(const std::string& name);
+
+    // This is a global context. All statistics will initially be stored here.
+    StatContextPtr global_;
+};
+
+};
+};
+
+#endif // STATS_MGR

+ 34 - 0
src/lib/stats/tests/Makefile.am

@@ -0,0 +1,34 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+if HAVE_GTEST
+
+TESTS = libstats_unittests
+
+libstats_unittests_SOURCES  = run_unittests.cc
+libstats_unittests_SOURCES += observation_unittest.cc
+libstats_unittests_SOURCES += context_unittest.cc
+libstats_unittests_SOURCES += stats_mgr_unittest.cc
+
+libstats_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libstats_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)
+libstats_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+
+libstats_unittests_LDADD  = $(top_builddir)/src/lib/stats/libkea-stats.la
+libstats_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libstats_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libstats_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libstats_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libstats_unittests_LDADD += $(GTEST_LDADD)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)

+ 79 - 0
src/lib/stats/tests/context_unittest.cc

@@ -0,0 +1,79 @@
+// Copyright (C) 2015 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.
+
+#include <config.h>
+
+#include <stats/context.h>
+#include <gtest/gtest.h>
+
+using namespace isc::stats;
+
+// Basic test that checks get, add, del methods
+TEST(ContextTest, basic) {
+
+    // Let's create a couple observations. Using floating point,
+    // as they're easiest to initialize.
+    ObservationPtr a(new Observation("alpha", 1.11));
+    ObservationPtr b(new Observation("beta", 2.22));
+    ObservationPtr c(new Observation("gamma", 3.33));
+
+    // Context where we will store the observations.
+    StatContext ctx;
+
+    // By default the context does not hold any statistics.
+    EXPECT_EQ(0, ctx.stats_.size());
+    EXPECT_TRUE(ctx.stats_.empty());
+
+    // It should be possible to add 'a' statistic
+    EXPECT_NO_THROW(ctx.add(a));
+
+    // We can't add a duplicate.
+    EXPECT_THROW(ctx.add(a), DuplicateStat);
+
+    // It should be ok to add other statistics
+    EXPECT_NO_THROW(ctx.add(b));
+    EXPECT_NO_THROW(ctx.add(c));
+
+    // By now we should have 3 statistics recorded
+    EXPECT_EQ(3, ctx.stats_.size());
+    EXPECT_FALSE(ctx.stats_.empty());
+
+    // Let's try to retrieve them
+    ObservationPtr from_ctx;
+    EXPECT_NO_THROW(from_ctx = ctx.get("alpha"));
+    ASSERT_TRUE(from_ctx);
+    EXPECT_EQ(a->getJSON()->str(), from_ctx->getJSON()->str());
+
+    EXPECT_NO_THROW(from_ctx = ctx.get("beta"));
+    ASSERT_TRUE(from_ctx);
+    EXPECT_EQ(b->getJSON()->str(), from_ctx->getJSON()->str());
+
+    EXPECT_NO_THROW(from_ctx = ctx.get("gamma"));
+    ASSERT_TRUE(from_ctx);
+    EXPECT_EQ(c->getJSON()->str(), from_ctx->getJSON()->str());
+
+    // Let's try to retrieve non-existing stat
+    EXPECT_NO_THROW(from_ctx = ctx.get("delta"));
+    EXPECT_FALSE(from_ctx);
+
+    // Now delete one of the stats...
+    EXPECT_TRUE(ctx.del("beta"));
+
+    // ... and check that it's really gone.
+    EXPECT_FALSE(ctx.get("beta"));
+
+    // Attempt to delete non-existing stat should fail.
+    EXPECT_FALSE(ctx.del("beta"));
+}
+

+ 243 - 0
src/lib/stats/tests/observation_unittest.cc

@@ -0,0 +1,243 @@
+// Copyright (C) 2015 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.
+
+#include <config.h>
+
+#include <stats/observation.h>
+#include <exceptions/exceptions.h>
+#include <util/boost_time_utils.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+#include <unistd.h>
+
+using namespace isc;
+using namespace isc::stats;
+using namespace boost::posix_time;
+
+namespace {
+
+/// @brief Test class for Observation
+///
+/// This simple fixture class initializes four observations:
+/// a (integer), b (float), c(time duration) and d (string).
+class ObservationTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor
+    /// Initializes four observations.
+    ObservationTest()
+        :a("alpha", static_cast<uint64_t>(1234)), // integer
+         b("beta", 12.34), // float
+         c("gamma", millisec::time_duration(1,2,3,4)), // duration
+         d("delta", "1234") { // string
+    }
+
+    Observation a;
+    Observation b;
+    Observation c;
+    Observation d;
+};
+
+// Basic tests for the Obseration constructors. This test checks whether
+// parameters passed to the constructor initialize the object properly.
+TEST_F(ObservationTest, constructor) {
+
+    EXPECT_EQ(Observation::STAT_INTEGER, a.getType());
+    EXPECT_EQ(Observation::STAT_FLOAT, b.getType());
+    EXPECT_EQ(Observation::STAT_DURATION, c.getType());
+    EXPECT_EQ(Observation::STAT_STRING, d.getType());
+
+    EXPECT_EQ(1234, a.getInteger().first);
+    EXPECT_EQ(12.34, b.getFloat().first);
+    EXPECT_EQ(millisec::time_duration(1,2,3,4),
+              c.getDuration().first);
+    EXPECT_EQ("1234", d.getString().first);
+
+    // Let's check that attempting to get a different type
+    // than used will cause an exception.
+    EXPECT_THROW(a.getFloat(), InvalidStatType);
+    EXPECT_THROW(a.getDuration(), InvalidStatType);
+    EXPECT_THROW(a.getString(), InvalidStatType);
+
+    EXPECT_THROW(b.getInteger(), InvalidStatType);
+    EXPECT_THROW(b.getDuration(), InvalidStatType);
+    EXPECT_THROW(b.getString(), InvalidStatType);
+
+    EXPECT_THROW(c.getInteger(), InvalidStatType);
+    EXPECT_THROW(c.getFloat(), InvalidStatType);
+    EXPECT_THROW(c.getString(), InvalidStatType);
+
+    EXPECT_THROW(d.getInteger(), InvalidStatType);
+    EXPECT_THROW(d.getFloat(), InvalidStatType);
+    EXPECT_THROW(d.getDuration(), InvalidStatType);
+}
+
+// This test checks whether it is possible to set to an absolute value for all
+// given types.
+TEST_F(ObservationTest, setValue) {
+
+    EXPECT_NO_THROW(a.setValue(static_cast<uint64_t>(5678)));
+    EXPECT_NO_THROW(b.setValue(56e+78));
+    EXPECT_NO_THROW(c.setValue(millisec::time_duration(5,6,7,8)));
+    EXPECT_NO_THROW(d.setValue("fiveSixSevenEight"));
+
+
+    EXPECT_EQ(5678, a.getInteger().first);
+    EXPECT_EQ(56e+78, b.getFloat().first);
+    EXPECT_EQ(millisec::time_duration(5,6,7,8),
+              c.getDuration().first);
+    EXPECT_EQ("fiveSixSevenEight", d.getString().first);
+
+    // Now check whether setting value to a different type does
+    // throw an exception
+    EXPECT_THROW(a.setValue(56e+78), InvalidStatType);
+    EXPECT_THROW(a.setValue(millisec::time_duration(5,6,7,8)), InvalidStatType);
+    EXPECT_THROW(a.setValue("fiveSixSevenEight"), InvalidStatType);
+
+    EXPECT_THROW(b.setValue(static_cast<uint64_t>(5678)), InvalidStatType);
+    EXPECT_THROW(b.setValue(millisec::time_duration(5,6,7,8)), InvalidStatType);
+    EXPECT_THROW(b.setValue("fiveSixSevenEight"), InvalidStatType);
+
+    EXPECT_THROW(c.setValue(static_cast<uint64_t>(5678)), InvalidStatType);
+    EXPECT_THROW(c.setValue(56e+78), InvalidStatType);
+    EXPECT_THROW(c.setValue("fiveSixSevenEight"), InvalidStatType);
+
+    EXPECT_THROW(d.setValue(static_cast<uint64_t>(5678)), InvalidStatType);
+    EXPECT_THROW(d.setValue(56e+78), InvalidStatType);
+    EXPECT_THROW(d.setValue(millisec::time_duration(5,6,7,8)), InvalidStatType);
+}
+
+// This test checks whether it is possible to add value to existing
+// counter.
+TEST_F(ObservationTest, addValue) {
+
+    // Note: all Observations were set to 1234,12.34 or similar in
+    // ObservationTest constructor.
+
+    EXPECT_NO_THROW(a.addValue(static_cast<uint64_t>(5678)));
+    EXPECT_NO_THROW(b.addValue(56.78));
+    EXPECT_NO_THROW(c.addValue(millisec::time_duration(5,6,7,8)));
+    EXPECT_NO_THROW(d.addValue("fiveSixSevenEight"));
+
+    EXPECT_EQ(6912, a.getInteger().first);
+    EXPECT_EQ(69.12, b.getFloat().first);
+
+    EXPECT_EQ(millisec::time_duration(6,8,10,12), c.getDuration().first);
+    EXPECT_EQ("1234fiveSixSevenEight", d.getString().first);
+}
+
+// Test checks whether timing is reported properly.
+TEST_F(ObservationTest, timers) {
+    ptime before = microsec_clock::local_time();
+    b.setValue(123.0); // Set it to a random value and record the time.
+
+    // Allow a bit of inprecision. This test allows 50ms. That should be ok,
+    // when running on virtual machines.
+    ptime after = before + millisec::time_duration(0,0,0,50);
+
+    // Now wait some time. We want to confirm that the timestamp recorded is the
+    // time the observation took place, not current time.
+    sleep(1);
+
+    FloatSample sample = b.getFloat();
+
+    // Let's check that the timestamp is within (before,after) range:
+    // before < sample-time < after
+    EXPECT_TRUE(before <= sample.second);
+    EXPECT_TRUE(sample.second <= after);
+}
+
+// Checks whether an integer statistic can generate proper JSON structures.
+// See http://kea.isc.org/wiki/StatsDesign for details.
+TEST_F(ObservationTest, integerToJSON) {
+
+    a.setValue(static_cast<uint64_t>(1234));
+
+    std::string exp = "[ [ 1234, \""
+        + isc::util::ptimeToText(a.getInteger().second) + "\" ] ]";
+
+    std::cout << a.getJSON()->str() << std::endl;
+    EXPECT_EQ(exp, a.getJSON()->str());
+}
+
+// Checks whether a floating point statistic can generate proper JSON
+// structures. See http://kea.isc.org/wiki/StatsDesign for details.
+TEST_F(ObservationTest, floatToJSON) {
+
+    // Let's use a value that converts easily to floating point.
+    // No need to deal with infinite fractions in binary systems.
+    b.setValue(1234.5);
+
+    std::string exp = "[ [ 1234.5, \""
+        + isc::util::ptimeToText(b.getFloat().second) + "\" ] ]";
+
+    std::cout << b.getJSON()->str() << std::endl;
+    EXPECT_EQ(exp, b.getJSON()->str());
+}
+
+// Checks whether a time duration statistic can generate proper JSON structures.
+// See http://kea.isc.org/wiki/StatsDesign for details.
+TEST_F(ObservationTest, durationToJSON) {
+
+    // 1 hour 2 minutes 3 seconds and 4 milliseconds
+    c.setValue(time_duration(1,2,3,4));
+
+    std::string exp = "[ [ \"01:02:03.000004\", \""
+        + isc::util::ptimeToText(c.getDuration().second) + "\" ] ]";
+
+    std::cout << c.getJSON()->str() << std::endl;
+    EXPECT_EQ(exp, c.getJSON()->str());
+}
+
+// Checks whether a string statistic can generate proper JSON structures.
+// See http://kea.isc.org/wiki/StatsDesign for details.
+TEST_F(ObservationTest, stringToJSON) {
+
+    //
+    d.setValue("Lorem ipsum dolor sit amet");
+
+    std::string exp = "[ [ \"Lorem ipsum dolor sit amet\", \""
+        + isc::util::ptimeToText(d.getString().second) + "\" ] ]";
+
+    std::cout << d.getJSON()->str() << std::endl;
+    EXPECT_EQ(exp, d.getJSON()->str());
+}
+
+// Checks whether reset() resets the statistics properly.
+TEST_F(ObservationTest, reset) {
+    a.reset(); // integer
+    b.reset(); // float
+    c.reset(); // duration
+    d.reset(); // string
+
+    EXPECT_EQ(0, a.getInteger().first);
+    EXPECT_EQ(0.0, b.getFloat().first);
+    EXPECT_EQ(time_duration(0,0,0,0), c.getDuration().first);
+    EXPECT_EQ("", d.getString().first);
+}
+
+// Checks whether an observation can keep its name.
+TEST_F(ObservationTest, names) {
+    EXPECT_EQ("alpha", a.getName());
+    EXPECT_EQ("beta", b.getName());
+    EXPECT_EQ("gamma", c.getName());
+    EXPECT_EQ("delta", d.getName());
+}
+
+};

+ 24 - 0
src/lib/stats/tests/run_unittests.cc

@@ -0,0 +1,24 @@
+// Copyright (C) 2015  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.
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+
+    int result = RUN_ALL_TESTS();
+
+    return (result);
+}

+ 400 - 0
src/lib/stats/tests/stats_mgr_unittest.cc

@@ -0,0 +1,400 @@
+// Copyright (C) 2015 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.
+
+#include <config.h>
+
+#include <stats/stats_mgr.h>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <util/boost_time_utils.h>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::stats;
+using namespace boost::posix_time;
+
+namespace {
+
+/// @brief Fixture class for StatsMgr testing
+///
+/// Very simple class that makes sure that StatsMgr is indeed instantiated
+/// before the test and any statistics are wiped out after it.
+class StatsMgrTest : public ::testing::Test {
+public:
+    /// @brief Constructor
+    /// Makes sure that the Statistics Manager is instantiated.
+    StatsMgrTest() {
+        StatsMgr::instance();
+    }
+
+    /// @brief Destructor
+    /// Removes all statistics.
+    ~StatsMgrTest() {
+        StatsMgr::instance().removeAll();
+    }
+};
+
+// Basic test for statistics manager interface.
+TEST_F(StatsMgrTest, basic) {
+
+    // Getting an instance
+    EXPECT_NO_THROW(StatsMgr::instance());
+
+    // Check that there are no statistics recorded by default.
+    EXPECT_EQ(0, StatsMgr::instance().count());
+}
+
+// Test checks whether it's possible to record and later report
+// an integer statistic.
+TEST_F(StatsMgrTest, integerStat) {
+    EXPECT_NO_THROW(StatsMgr::instance().setValue("alpha",
+                                                  static_cast<uint64_t>(1234)));
+
+    ObservationPtr alpha;
+    EXPECT_NO_THROW(alpha = StatsMgr::instance().getObservation("alpha"));
+    ASSERT_TRUE(alpha);
+
+    std::string exp = "{ \"alpha\": [ [ 1234, \""
+        + isc::util::ptimeToText(alpha->getInteger().second) + "\" ] ] }";
+
+    EXPECT_EQ(exp, StatsMgr::instance().get("alpha")->str());
+}
+
+// Test checks whether it's possible to record and later report
+// a floating point statistic.
+TEST_F(StatsMgrTest, floatStat) {
+    EXPECT_NO_THROW(StatsMgr::instance().setValue("beta", 12.34));
+
+    ObservationPtr beta;
+    EXPECT_NO_THROW(beta = StatsMgr::instance().getObservation("beta"));
+    ASSERT_TRUE(beta);
+
+    std::string exp = "{ \"beta\": [ [ 12.34, \""
+        + isc::util::ptimeToText(beta->getFloat().second) + "\" ] ] }";
+
+    EXPECT_EQ(exp, StatsMgr::instance().get("beta")->str());
+}
+
+// Test checks whether it's possible to record and later report
+// a duration statistic.
+TEST_F(StatsMgrTest, durationStat) {
+    EXPECT_NO_THROW(StatsMgr::instance().setValue("gamma",
+                                                  microsec::time_duration(1,2,3,4)));
+
+    ObservationPtr gamma;
+    EXPECT_NO_THROW(gamma = StatsMgr::instance().getObservation("gamma"));
+    ASSERT_TRUE(gamma);
+
+    std::string exp = "{ \"gamma\": [ [ \"01:02:03.000004\", \""
+        + isc::util::ptimeToText(gamma->getDuration().second) + "\" ] ] }";
+
+    EXPECT_EQ(exp, StatsMgr::instance().get("gamma")->str());
+}
+
+// Test checks whether it's possible to record and later report
+// a string statistic.
+TEST_F(StatsMgrTest, stringStat) {
+    EXPECT_NO_THROW(StatsMgr::instance().setValue("delta",
+                                                  "Lorem ipsum"));
+
+    ObservationPtr delta;
+    EXPECT_NO_THROW(delta = StatsMgr::instance().getObservation("delta"));
+    ASSERT_TRUE(delta);
+
+    std::string exp = "{ \"delta\": [ [ \"Lorem ipsum\", \""
+        + isc::util::ptimeToText(delta->getString().second) + "\" ] ] }";
+
+    EXPECT_EQ(exp, StatsMgr::instance().get("delta")->str());
+}
+
+// Setting limits is currently not implemented, so those methods should
+// throw.
+TEST_F(StatsMgrTest, setLimits) {
+    EXPECT_THROW(StatsMgr::instance().setMaxSampleAge("foo",
+                                                      time_duration(1,0,0,0)),
+                 NotImplemented);
+
+    EXPECT_THROW(StatsMgr::instance().setMaxSampleCount("foo", 100),
+                 NotImplemented);
+}
+
+// This test checks whether a single (get("foo")) and all (getAll())
+// statistics are reported properly.
+TEST_F(StatsMgrTest, getGetAll) {
+
+    // Set a couple of statistics
+    StatsMgr::instance().setValue("alpha", static_cast<uint64_t>(1234));
+    StatsMgr::instance().setValue("beta", 12.34);
+    StatsMgr::instance().setValue("gamma", time_duration(1,2,3,4));
+    StatsMgr::instance().setValue("delta", "Lorem");
+
+    // Now add some values to them
+    StatsMgr::instance().addValue("alpha", static_cast<uint64_t>(5678));
+    StatsMgr::instance().addValue("beta", 56.78);
+    StatsMgr::instance().addValue("gamma", time_duration(5,6,7,8));
+    StatsMgr::instance().addValue("delta", " ipsum");
+
+    // There should be 4 statistics reported
+    EXPECT_EQ(4, StatsMgr::instance().count());
+
+    // Now check whether they can be reported back
+    ConstElementPtr rep_alpha = StatsMgr::instance().get("alpha");
+    ConstElementPtr rep_beta = StatsMgr::instance().get("beta");
+    ConstElementPtr rep_gamma = StatsMgr::instance().get("gamma");
+    ConstElementPtr rep_delta = StatsMgr::instance().get("delta");
+
+    ASSERT_TRUE(rep_alpha);
+    ASSERT_TRUE(rep_beta);
+    ASSERT_TRUE(rep_gamma);
+    ASSERT_TRUE(rep_delta);
+
+    std::string exp_str_alpha = "[ [ 6912, \""
+        + isc::util::ptimeToText(StatsMgr::instance().getObservation("alpha")
+                                   ->getInteger().second) + "\" ] ]";
+    std::string exp_str_beta = "[ [ 69.12, \""
+        + isc::util::ptimeToText(StatsMgr::instance().getObservation("beta")
+                                   ->getFloat().second) + "\" ] ]";
+    std::string exp_str_gamma = "[ [ \"06:08:10.000012\", \""
+        + isc::util::ptimeToText(StatsMgr::instance().getObservation("gamma")
+                                   ->getDuration().second) + "\" ] ]";
+    std::string exp_str_delta = "[ [ \"Lorem ipsum\", \""
+        + isc::util::ptimeToText(StatsMgr::instance().getObservation("delta")
+                                   ->getString().second) + "\" ] ]";
+
+    // Check that individual stats are reported properly
+    EXPECT_EQ("{ \"alpha\": " + exp_str_alpha + " }", rep_alpha->str());
+    EXPECT_EQ("{ \"beta\": " + exp_str_beta + " }", rep_beta->str());
+    EXPECT_EQ("{ \"gamma\": " + exp_str_gamma + " }", rep_gamma->str());
+    EXPECT_EQ("{ \"delta\": " + exp_str_delta + " }", rep_delta->str());
+
+    // Check that non-existent metric is not reported.
+    EXPECT_EQ("{  }", StatsMgr::instance().get("epsilon")->str());
+
+    // Check that all of them can be reported at once
+    ConstElementPtr rep_all = StatsMgr::instance().getAll();
+    ASSERT_TRUE(rep_all);
+
+    // Verifying this is a bit more involved, as we don't know whether the
+    // order would be preserved or not.
+    EXPECT_EQ(4, rep_all->size());
+    ASSERT_TRUE(rep_all->get("alpha"));
+    ASSERT_TRUE(rep_all->get("beta"));
+    ASSERT_TRUE(rep_all->get("delta"));
+    ASSERT_TRUE(rep_all->get("gamma"));
+    EXPECT_FALSE(rep_all->get("epsilon"));
+
+    EXPECT_EQ(exp_str_alpha, rep_all->get("alpha")->str());
+    EXPECT_EQ(exp_str_beta, rep_all->get("beta")->str());
+    EXPECT_EQ(exp_str_gamma, rep_all->get("gamma")->str());
+    EXPECT_EQ(exp_str_delta, rep_all->get("delta")->str());
+}
+
+// This test checks whether existing statistics can be reset.
+TEST_F(StatsMgrTest, reset) {
+
+    // Set a couple of statistics
+    StatsMgr::instance().setValue("alpha", static_cast<uint64_t>(1234));
+    StatsMgr::instance().setValue("beta", 12.34);
+    StatsMgr::instance().setValue("gamma", time_duration(1,2,3,4));
+    StatsMgr::instance().setValue("delta", "Lorem ipsum");
+
+    // This should reset alpha to 0
+    EXPECT_NO_THROW(StatsMgr::instance().reset("alpha"));
+    EXPECT_EQ(0, StatsMgr::instance().getObservation("alpha")->getInteger().first);
+
+    // The other stats should remain untouched
+    EXPECT_EQ(12.34,
+              StatsMgr::instance().getObservation("beta")->getFloat().first);
+    EXPECT_EQ(time_duration(1,2,3,4),
+              StatsMgr::instance().getObservation("gamma")->getDuration().first);
+    EXPECT_EQ("Lorem ipsum",
+              StatsMgr::instance().getObservation("delta")->getString().first);
+
+    // Now let's wipe them, too.
+    EXPECT_NO_THROW(StatsMgr::instance().reset("beta"));
+    EXPECT_NO_THROW(StatsMgr::instance().reset("gamma"));
+    EXPECT_NO_THROW(StatsMgr::instance().reset("delta"));
+    EXPECT_EQ(0.0,
+              StatsMgr::instance().getObservation("beta")->getFloat().first);
+    EXPECT_EQ(time_duration(0,0,0,0),
+              StatsMgr::instance().getObservation("gamma")->getDuration().first);
+    EXPECT_EQ("",
+              StatsMgr::instance().getObservation("delta")->getString().first);
+
+    // Resetting statistics should not remove them
+    EXPECT_EQ(4, StatsMgr::instance().count());
+}
+
+// This test checks whether existing statistics can be reset.
+TEST_F(StatsMgrTest, resetAll) {
+
+    // Set a couple of statistics
+    StatsMgr::instance().setValue("alpha", static_cast<uint64_t>(1234));
+    StatsMgr::instance().setValue("beta", 12.34);
+    StatsMgr::instance().setValue("gamma", time_duration(1,2,3,4));
+    StatsMgr::instance().setValue("delta", "Lorem ipsum");
+
+    // This should reset alpha to 0
+    EXPECT_NO_THROW(StatsMgr::instance().resetAll());
+    EXPECT_EQ(0, StatsMgr::instance().getObservation("alpha")->getInteger().first);
+    EXPECT_EQ(0.0,
+              StatsMgr::instance().getObservation("beta")->getFloat().first);
+    EXPECT_EQ(time_duration(0,0,0,0),
+              StatsMgr::instance().getObservation("gamma")->getDuration().first);
+    EXPECT_EQ("",
+              StatsMgr::instance().getObservation("delta")->getString().first);
+
+    // Resetting all statistics should not remove them
+    EXPECT_EQ(4, StatsMgr::instance().count());
+}
+
+// This test checks whether statistics can be removed.
+TEST_F(StatsMgrTest, removeAll) {
+
+    // Set a couple of statistics
+    StatsMgr::instance().setValue("alpha", static_cast<uint64_t>(1234));
+    StatsMgr::instance().setValue("beta", 12.34);
+    StatsMgr::instance().setValue("gamma", time_duration(1,2,3,4));
+    StatsMgr::instance().setValue("delta", "Lorem ipsum");
+
+    // This should reset alpha to 0
+    EXPECT_NO_THROW(StatsMgr::instance().removeAll());
+
+    // Resetting all statistics should not remove them
+    EXPECT_EQ(0, StatsMgr::instance().count());
+
+    // There should be no such statistics anymore
+    EXPECT_EQ("{  }", StatsMgr::instance().get("alpha")->str());
+    EXPECT_EQ("{  }", StatsMgr::instance().get("beta")->str());
+    EXPECT_EQ("{  }", StatsMgr::instance().get("gamma")->str());
+    EXPECT_EQ("{  }", StatsMgr::instance().get("delta")->str());
+
+    // There should be no such statistics anymore
+    EXPECT_FALSE(StatsMgr::instance().getObservation("alpha"));
+    EXPECT_FALSE(StatsMgr::instance().getObservation("beta"));
+    EXPECT_FALSE(StatsMgr::instance().getObservation("gamma"));
+    EXPECT_FALSE(StatsMgr::instance().getObservation("delta"));
+}
+
+// This is a performance benchmark that checks how long does it take
+// to increment a single statistic million times.
+//
+// Data points:
+// It took 00:00:00.363709 (363ms) on late 2013 Mac with Mac OS X 10.9.5.
+TEST_F(StatsMgrTest, DISABLED_performanceSingleAdd) {
+    StatsMgr::instance().removeAll();
+
+    uint32_t cycles = 1000000;
+
+    ptime before = microsec_clock::local_time();
+    for (uint32_t i = 0; i < cycles; ++i) {
+        StatsMgr::instance().addValue("metric1", 0.1*i);
+    }
+    ptime after = microsec_clock::local_time();
+
+    time_duration dur = after - before;
+
+    std::cout << "Incrementing a single statistic " << cycles << " times took: "
+              << isc::util::durationToText(dur) << std::endl;
+}
+
+// This is a performance benchmark that checks how long does it take
+// to set absolute value of a single statistic million times.
+//
+// Data points:
+// It took 00:00:00.361003 (361ms) on late 2013 Mac with Mac OS X 10.9.5.
+TEST_F(StatsMgrTest, DISABLED_performanceSingleSet) {
+    StatsMgr::instance().removeAll();
+
+    uint32_t cycles = 1000000;
+
+    ptime before = microsec_clock::local_time();
+    for (uint32_t i = 0; i < cycles; ++i) {
+        StatsMgr::instance().setValue("metric1", 0.1*i);
+    }
+    ptime after = microsec_clock::local_time();
+
+    time_duration dur = after - before;
+
+    std::cout << "Setting a single statistic " << cycles << " times took: "
+              << isc::util::durationToText(dur) << std::endl;
+}
+
+// This is a performance benchmark that checks how long does it take to
+// increment one statistic a million times, when there is 1000 other statistics
+// present.
+//
+// Data points:
+// 00:00:00.436943 (436ms) on late 2013 Mac with Mac OS X 10.9.5
+TEST_F(StatsMgrTest, DISABLED_performanceMultipleAdd) {
+    StatsMgr::instance().removeAll();
+
+    uint32_t cycles = 1000000;
+    uint32_t stats = 1000;
+
+    for (uint32_t i = 0; i < stats; ++i) {
+        std::stringstream tmp;
+        tmp << "statistic" << i;
+        StatsMgr::instance().setValue(tmp.str(), static_cast<uint64_t>(i));
+    }
+
+    ptime before = microsec_clock::local_time();
+    for (uint32_t i = 0; i < cycles; ++i) {
+        StatsMgr::instance().addValue("metric1", static_cast<uint64_t>(i));
+    }
+    ptime after = microsec_clock::local_time();
+
+    time_duration dur = after - before;
+
+    std::cout << "Incrementing one of " << stats << " statistics " << cycles
+              << " times took: " << isc::util::durationToText(dur) << std::endl;
+}
+
+// This is a performance benchmark that checks how long does it take to
+// set one statistic to a given value a million times, when there is 1000 other
+// statistics present.
+//
+// Data points:
+// 00:00:00.424518 (424ms) on late 2013 Mac with Mac OS X 10.9.5
+TEST_F(StatsMgrTest, DISABLED_performanceMultipleSet) {
+    StatsMgr::instance().removeAll();
+
+    uint32_t cycles = 1000000;
+    uint32_t stats = 1000;
+
+    for (uint32_t i = 0; i < stats; ++i) {
+        std::stringstream tmp;
+        tmp << "statistic" << i;
+        StatsMgr::instance().setValue(tmp.str(), static_cast<uint64_t>(i));
+    }
+
+    ptime before = microsec_clock::local_time();
+    for (uint32_t i = 0; i < cycles; ++i) {
+        StatsMgr::instance().setValue("metric1", static_cast<uint64_t>(i));
+    }
+    ptime after = microsec_clock::local_time();
+
+    time_duration dur = after - before;
+
+    std::cout << "Setting one of " << stats << " statistics " << cycles
+              << " times took: " << isc::util::durationToText(dur) << std::endl;
+}
+
+};

+ 2 - 1
src/lib/util/Makefile.am

@@ -9,7 +9,8 @@ AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CXXFLAGS = $(KEA_CXXFLAGS)
 AM_CXXFLAGS = $(KEA_CXXFLAGS)
 
 
 lib_LTLIBRARIES = libkea-util.la
 lib_LTLIBRARIES = libkea-util.la
-libkea_util_la_SOURCES  = csv_file.h csv_file.cc
+libkea_util_la_SOURCES  = boost_time_utils.h boost_time_utils.cc
+libkea_util_la_SOURCES += csv_file.h csv_file.cc
 libkea_util_la_SOURCES += filename.h filename.cc
 libkea_util_la_SOURCES += filename.h filename.cc
 libkea_util_la_SOURCES += locks.h lru_list.h
 libkea_util_la_SOURCES += locks.h lru_list.h
 libkea_util_la_SOURCES += strutil.h strutil.cc
 libkea_util_la_SOURCES += strutil.h strutil.cc

+ 39 - 0
src/lib/util/boost_time_utils.cc

@@ -0,0 +1,39 @@
+// Copyright (C) 2015 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.
+
+#include <util/boost_time_utils.h>
+#include <sstream>
+#include <iomanip>
+
+std::string
+isc::util::ptimeToText(boost::posix_time::ptime t) {
+    boost::gregorian::date d = t.date();
+    std::stringstream s;
+    s << d.year() << "-" << d.month() << "-" << d.day();
+    s << " " << durationToText(t.time_of_day());
+    return (s.str());
+}
+
+std::string
+isc::util::durationToText(boost::posix_time::time_duration dur) {
+    std::stringstream s;
+    s << std::setw(2) << std::setfill('0') << dur.hours()
+      << ":" << std::setw(2) << std::setfill('0') << dur.minutes()
+      << ":" << std::setw(2) << std::setfill('0') << dur.seconds()
+      << "." << std::setw(boost::posix_time::time_duration::num_fractional_digits())
+      << std::setfill('0')
+      << dur.fractional_seconds();
+
+    return (s.str());
+}

+ 50 - 0
src/lib/util/boost_time_utils.h

@@ -0,0 +1,50 @@
+// Copyright (C) 2015 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.
+
+#ifndef KEA_BOOST_TIME_UTILS_H
+#define KEA_BOOST_TIME_UTILS_H
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/// @brief Converts ptime structure to text
+///
+/// This is Kea implementation for converting ptime to strings.
+/// It's a functional equivalent of boost::posix_time::to_simple_string. We do
+/// not use it, though, because it would introduce unclear dependency on
+/// boost_time_date library. First, we try to avoid depending on boost libraries
+/// (we tend to use only the headers). Second, this dependency is system
+/// specific, i.e. it is required on Ubuntu and FreeBSD, but doesn't seem to
+/// be needed on OS X. Since the functionality needed is minor, we decided to
+/// reimplement it on our own, rather than introduce extra dependencies.
+/// This explanation also applies to @ref durationToText.
+///
+/// @return a string representing time
+std::string ptimeToText(boost::posix_time::ptime t);
+
+/// @brief Converts StatsDuration to text
+///
+/// This is Kea equivalent of boost::posix_time::to_simple_string(time_duration).
+/// See @ref ptimeToText for explanation why we chose our own implementation.
+///
+/// @return a string representing time
+std::string durationToText(boost::posix_time::time_duration dur);
+
+}; // end of isc::util namespace
+}; // end of isc namespace
+
+#endif