Parcourir la source

[master] Merge branch 'trac3793' (StatsMgr implementation)

Tomek Mrugalski il y a 10 ans
Parent
commit
68e9554eca

+ 2 - 0
configure.ac

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

+ 2 - 3
src/lib/Makefile.am

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

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

@@ -675,6 +675,13 @@ public:
     // a MapElement)
     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;
 };
 

+ 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)
 
 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 += locks.h lru_list.h
 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