Parcourir la source

[3793] Observation class + unittests, skeleton impl of StatsMgr

Tomek Mrugalski il y a 10 ans
Parent
commit
46f3bc4027

+ 2 - 0
configure.ac

@@ -1494,6 +1494,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 \
-          asiolink asiodns testutils dhcp dhcp_ddns \
-          dhcpsrv
+SUBDIRS = exceptions util log hooks cryptolink dns cc stats config \
+          asiolink asiodns testutils dhcp dhcp_ddns dhcpsrv

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

@@ -0,0 +1,16 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+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_LIBADD  = $(top_builddir)/src/lib/cc/libkea-cc.la
+
+libkea_stats_la_LIBADD += -lboost_date_time
+
+libkea_stats_includedir = $(includedir)/$(PACKAGE_NAME)/stats
+libkea_stats_include_HEADERS = stats_mgr.h

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


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

@@ -0,0 +1,22 @@
+
+
+#ifndef CONTEXT_H
+#define CONTEXT_H
+
+#include <stats/observation.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace stats {
+
+class StatContext {
+ public:
+    std::map<std::string, ObservationPtr> stats;
+};
+
+ typedef boost::shared_ptr<StatContext> StatContextPtr;
+
+};
+};
+
+#endif // CONTEXT_H

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

@@ -0,0 +1,224 @@
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <stats/observation.h>
+#include <cc/data.h>
+#include <utility>
+
+using namespace std;
+using namespace isc::data;
+using namespace boost::posix_time;
+
+namespace isc {
+namespace stats {
+
+Observation::Observation(uint64_t value)
+    :type_(STAT_INTEGER), max_samples_(1) {
+    setValue(value);
+}
+
+Observation::Observation(double value)
+    :type_(STAT_FLOAT), max_samples_(1) {
+    setValue(value);
+}
+
+Observation::Observation(StatsDuration value)
+    :type_(STAT_DURATION), max_samples_(1) {
+    setValue(value);
+}
+
+Observation::Observation(const std::string& value)
+    :type_(STAT_STRING), max_samples_(1) {
+    setValue(value);
+}
+
+void Observation::addValue(uint64_t value) {
+    IntegerSample current = getInteger();
+    setValue(current.first + value);
+}
+
+void Observation::addValue(double value) {
+    FloatSample current = getFloat();
+    setValue(current.first + value);
+}
+
+void Observation::addValue(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(uint64_t value) {
+    setValueInternal(value, integer_samples_, STAT_INTEGER);
+}
+
+void Observation::setValue(double value) {
+    setValueInternal(value, float_samples_, STAT_FLOAT);
+}
+
+void Observation::setValue(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() {
+    return (getValueInternal<IntegerSample>(integer_samples_, STAT_INTEGER));
+}
+
+FloatSample Observation::getFloat() {
+    return (getValueInternal<FloatSample>(float_samples_, STAT_FLOAT));
+}
+
+DurationSample Observation::getDuration() {
+    return (getValueInternal<DurationSample>(duration_samples_, STAT_DURATION));
+}
+
+StringSample Observation::getString() {
+    return (getValueInternal<StringSample>(string_samples_, STAT_STRING));
+}
+
+template<typename SampleType, typename Storage>
+SampleType Observation::getValueInternal(Storage& 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()) {
+        // 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());
+}
+
+std::string
+Observation::ptimeToText(ptime t) {
+
+    // The alternative would be to call to_simple_string(ptime), but unfortunately
+    // that requires linking with boost libraries.
+
+    return (to_simple_string(t));
+}
+
+std::string
+Observation::durationToText(StatsDuration dur) {
+    return (to_simple_string(dur));
+}
+
+isc::data::ConstElementPtr
+Observation::getJSON() {
+
+    ElementPtr list = isc::data::Element::createList();
+    ElementPtr value;
+    ElementPtr timestamp;
+
+    switch (type_) {
+    case STAT_INTEGER: {
+        IntegerSample s = getInteger();
+        value = isc::data::Element::create(static_cast<int64_t>(s.first));
+        timestamp = isc::data::Element::create(ptimeToText(s.second));
+        break;
+    }
+    case STAT_FLOAT: {
+        FloatSample s = getFloat();
+        value = isc::data::Element::create(s.first);
+        timestamp = isc::data::Element::create(ptimeToText(s.second));
+        break;
+    }
+    case STAT_DURATION: {
+        DurationSample s = getDuration();
+        value = isc::data::Element::create(durationToText(s.first));
+        timestamp = isc::data::Element::create(ptimeToText(s.second));
+        break;
+    }
+    case STAT_STRING: {
+        StringSample s = getString();
+        value = isc::data::Element::create(s.first);
+        timestamp = isc::data::Element::create(ptimeToText(s.second));
+        break;
+    }
+    default:
+        isc_throw(InvalidStatType, "Unknown stat type: " << typeToText(type_));
+    };
+
+    list->add(value);
+    list->add(timestamp);
+
+    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 stat type: " << typeToText(type_));
+    };
+}
+
+};
+};

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

@@ -0,0 +1,244 @@
+// 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 <boost/shared_ptr.hpp>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <boost/date_time/time_duration.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+#include <list>
+
+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
+///
+/// Boost offers a base boost::posix_time::time_duration class, that has specific
+/// implementations: boost::posix_time::{hours,minutes,seconds,millisec,nanosec}.
+/// For statistics purposes, the most appropriate choice seems to be milliseconds
+/// precision, so we'll stick with that.
+typedef boost::posix_time::millisec::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 value integer value observed.
+    Observation(uint64_t value);
+
+    /// @brief Constructor for floating point observations
+    ///
+    /// @param value floating point value observed.
+    Observation(double value);
+
+    /// @brief Constructor for duration observations
+    ///
+    /// @param value duration observed.
+    Observation(StatsDuration value);
+
+    /// @brief Constructor for string observations
+    ///
+    /// @param value string observed.
+    Observation(const std::string& value);
+
+    /// @brief Records absolute integer observation
+    ///
+    /// @param value integer value observed
+    /// @throw InvalidStatType if statistic is not integer
+    void setValue(uint64_t value);
+
+    /// @brief Records absolute floating point observation
+    ///
+    /// @param value floating point value observed
+    /// @throw InvalidStatType if statistic is not fp
+    void setValue(double value);
+
+    /// @brief Records absolute duration observation
+    ///
+    /// @param value duration value observed
+    /// @throw InvalidStatType if statistic is not time duration
+    void setValue(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(uint64_t value = 1);
+
+    /// @brief Records inremental floating point observation
+    ///
+    /// @param value floating point value observed
+    /// @throw InvalidStatType if statistic is not fp
+    void addValue(double value = 1.0f);
+
+    /// @brief Records incremental duration observation
+    ///
+    /// @param value duration value observed
+    /// @throw InvalidStatType if statistic is not time duration
+    void addValue(StatsDuration value = StatsDuration(0,0,0,0));
+
+    /// @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)
+    IntegerSample getInteger();
+
+    /// @brief Returns observed float sample
+    /// @return observed sample (value + timestamp)
+    FloatSample getFloat();
+
+    /// @brief Returns observed duration sample
+    /// @return observed sample (value + timestamp)
+    DurationSample getDuration();
+
+    /// @brief Returns observed string sample
+    /// @return observed sample (value + timestamp)
+    StringSample getString();
+
+    const std::list<IntegerSample>& getIntegerList() {
+        return (integer_samples_);
+    }
+
+    const std::list<FloatSample>& getFloatList() {
+        return (float_samples_);
+    }
+
+    const std::list<DurationSample>& getDurationList() {
+        return (duration_samples_);
+    }
+
+    const std::list<StringSample>& getStringList() {
+        return (string_samples_);
+    }
+
+    /// Returns as a JSON structure
+    isc::data::ConstElementPtr getJSON();
+
+    static std::string typeToText(Type type);
+
+    static std::string ptimeToText(boost::posix_time::ptime time);
+
+    static std::string durationToText(StatsDuration dur);
+
+ protected:
+    template<typename SampleType, typename StorageType>
+        void setValueInternal(SampleType value, StorageType& storage,
+            Type exp_type);
+
+    template<typename SampleType, typename Storage>
+    SampleType getValueInternal(Storage& storage, Type exp_type);
+
+    std::string name_;
+    Type type_;
+
+    size_t max_samples_;
+
+    std::list<IntegerSample> integer_samples_;
+    std::list<FloatSample> float_samples_;
+    std::list<DurationSample> duration_samples_;
+    std::list<StringSample> string_samples_;
+};
+
+ typedef boost::shared_ptr<Observation> ObservationPtr;
+
+};
+};
+
+#endif // OBSERVATION_H

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


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

@@ -0,0 +1,92 @@
+// 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 {
+
+class StatsMgr : public boost::noncopyable {
+ public:
+
+    static StatsMgr& instance();
+
+    // methods used data producers
+    void addValue(const std::string& name, uint64_t value = 1);
+    void addValue(const std::string& name, double value = 1.0f);
+    void addValue(const std::string& name, StatsDuration time);
+    void setValue(const std::string& name, uint64_t value = 1);
+    void setValue(const std::string& name, double value = 1.0f);
+    void setValue(const std::string& name, StatsDuration time);
+
+    // resets statistic
+    // this is a convenience function and is equivalent to
+    // setValue(0) or setValue(0.0f)
+    void reset(const std::string& name);
+
+    /// @brief determines whether a given statistic is kept as a single value
+    ///        or as a number of values
+    /// 
+    /// 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 setStorage() below.
+    ///
+    /// Example: to set a statistic to keep observations for the last 5 minutes,
+    /// call setStorage("incoming-packets", time_duration(0,5,0,0));
+    /// to revert statistic to a single value, call:
+    /// setStorage("incoming-packets" time_duration(0,0,0,0))
+    void setStorage(const std::string& name,
+                    boost::posix_time::time_duration 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.
+    ///
+    /// Example:
+    /// To set a statistic to keep the last 100 observations, call:
+    /// setStorage("incoming-packets", 100);
+    void setStorage(const std::string& name, uint32_t max_samples);
+
+    // methods used by data consumers
+    const ObservationPtr& getValue(const std::string& name);
+
+    // returns all statistics
+    const std::map<std::string, ObservationPtr> getValues();
+
+ private:
+    /// @brief returns a context for specified name
+    StatContextPtr getContext(const std::string& name);
+
+    // This is a global context. All stats will initially be stored here.
+    StatContextPtr global_;
+
+    std::map<std::string, StatContextPtr> contexts_;
+};
+
+};
+};
+
+#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/exceptions/libkea-exceptions.la
+libstats_unittests_LDADD  += -lboost_date_time
+libstats_unittests_LDADD += $(GTEST_LDADD)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)

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


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

@@ -0,0 +1,263 @@
+// 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 <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 {
+
+class ObservationTest : public ::testing::Test {
+public:
+    ObservationTest()
+        :a(static_cast<uint64_t>(1234)), // integer
+         b(12.34), // float
+         c(millisec::time_duration(1,2,3,4)), // duration
+         d("1234") { // string
+    }
+
+    Observation a;
+    Observation b;
+    Observation c;
+    Observation d;
+};
+
+// Basic tests for V4 functionality. This test checks whether parameters
+// passed to 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);
+}
+
+// Observation will be extended to cover multiple samples of the same
+// property. That is not implemented for now, so regardless of the
+// number of recorded observation, always the last one is kept.
+TEST_F(ObservationTest, getLists) {
+
+    // Let's record some data!
+    for (int i = 0; i <= 42; ++i) {
+
+        a.setValue(static_cast<uint64_t>(i));
+        b.setValue(0.25*i);
+        c.setValue(millisec::time_duration(0,0,i,0));
+
+        std::stringstream tmp;
+        tmp << i;
+        d.setValue(tmp.str());
+    }
+
+    // Get the lists.
+    std::list<IntegerSample> int_list = a.getIntegerList();
+    std::list<FloatSample> float_list = b.getFloatList();
+    std::list<DurationSample> dur_list = c.getDurationList();
+    std::list<StringSample> str_list = d.getStringList();
+
+    // Check that they have only one observation.
+    ASSERT_EQ(1, int_list.size());
+    ASSERT_EQ(1, float_list.size());
+    ASSERT_EQ(1, dur_list.size());
+    ASSERT_EQ(1, str_list.size());
+
+    // Now check that that the recorded value is correct.
+    EXPECT_EQ(42, int_list.begin()->first);
+    EXPECT_EQ(10.5, float_list.begin()->first);
+    EXPECT_EQ(millisec::time_duration(0,0,42,0), dur_list.begin()->first);
+    EXPECT_EQ("42", str_list.begin()->first);
+}
+
+// Test checks whether timing is reported properly.
+TEST_F(ObservationTest, timers) {
+    ptime min = microsec_clock::local_time();
+    b.setValue(123.0); // set it to a random value
+
+    // Allow a bit of inprecision. This test allows 5ms. That's ok, when running
+    // on virtual machines.
+    ptime max = min + millisec::time_duration(0,0,0,5);
+
+    // 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 (min,max) range.
+    EXPECT_TRUE(min <= sample.second);
+    EXPECT_TRUE(sample.second <= max);
+}
+
+// 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, \""
+        + Observation::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, \""
+        + Observation::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\", \""
+        + Observation::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\", \""
+        + Observation::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);
+}
+
+};

+ 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);
+}

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

@@ -0,0 +1,43 @@
+// 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 <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::stats;
+
+namespace {
+
+class StatsMgrTest : public ::testing::Test {
+public:
+    StatsMgrTest() {
+    }
+};
+
+// Basic tests for V4 functionality
+TEST_F(StatsMgrTest, basic) {
+
+    //    EXPECT_NO_THROW(StatsMgr::instance());
+}
+
+};