From: Tomek Mrugalski Date: Wed, 6 May 2015 14:49:00 +0000 (+0200) Subject: [3793] Observation class + unittests, skeleton impl of StatsMgr X-Git-Tag: trac3513_base~5^2~1^2~12 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=46f3bc40270e0acbabac9a05ff27f4e3495decd9;p=thirdparty%2Fkea.git [3793] Observation class + unittests, skeleton impl of StatsMgr --- diff --git a/configure.ac b/configure.ac index 7e49cfd37b..b23a5a26c2 100644 --- a/configure.ac +++ b/configure.ac @@ -1494,6 +1494,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 diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 5afc412b51..364e34ccb7 100644 --- a/src/lib/Makefile.am +++ b/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 diff --git a/src/lib/stats/Makefile.am b/src/lib/stats/Makefile.am new file mode 100644 index 0000000000..99c66cc000 --- /dev/null +++ b/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 diff --git a/src/lib/stats/context.cc b/src/lib/stats/context.cc new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/lib/stats/context.h b/src/lib/stats/context.h new file mode 100644 index 0000000000..33e20c6d70 --- /dev/null +++ b/src/lib/stats/context.h @@ -0,0 +1,22 @@ + + +#ifndef CONTEXT_H +#define CONTEXT_H + +#include +#include + +namespace isc { +namespace stats { + +class StatContext { + public: + std::map stats; +}; + + typedef boost::shared_ptr StatContextPtr; + +}; +}; + +#endif // CONTEXT_H diff --git a/src/lib/stats/observation.cc b/src/lib/stats/observation.cc new file mode 100644 index 0000000000..72bd1559ce --- /dev/null +++ b/src/lib/stats/observation.cc @@ -0,0 +1,224 @@ + +#include +#include +#include +#include + +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 +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(integer_samples_, STAT_INTEGER)); +} + +FloatSample Observation::getFloat() { + return (getValueInternal(float_samples_, STAT_FLOAT)); +} + +DurationSample Observation::getDuration() { + return (getValueInternal(duration_samples_, STAT_DURATION)); +} + +StringSample Observation::getString() { + return (getValueInternal(string_samples_, STAT_STRING)); +} + +template +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(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(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_)); + }; +} + +}; +}; diff --git a/src/lib/stats/observation.h b/src/lib/stats/observation.h new file mode 100644 index 0000000000..a3a3bf733c --- /dev/null +++ b/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 +#include +#include +#include +#include +#include + +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 IntegerSample; + +/// @brief Float (implemented as double precision) +typedef std::pair FloatSample; + +/// @brief Time Duration +typedef std::pair DurationSample; + +/// @brief String +typedef std::pair 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& getIntegerList() { + return (integer_samples_); + } + + const std::list& getFloatList() { + return (float_samples_); + } + + const std::list& getDurationList() { + return (duration_samples_); + } + + const std::list& 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 + void setValueInternal(SampleType value, StorageType& storage, + Type exp_type); + + template + SampleType getValueInternal(Storage& storage, Type exp_type); + + std::string name_; + Type type_; + + size_t max_samples_; + + std::list integer_samples_; + std::list float_samples_; + std::list duration_samples_; + std::list string_samples_; +}; + + typedef boost::shared_ptr ObservationPtr; + +}; +}; + +#endif // OBSERVATION_H diff --git a/src/lib/stats/stats_mgr.cc b/src/lib/stats/stats_mgr.cc new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/lib/stats/stats_mgr.h b/src/lib/stats/stats_mgr.h new file mode 100644 index 0000000000..b0155a415f --- /dev/null +++ b/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 +#include +#include + +#include +#include +#include + +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 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 contexts_; +}; + +}; +}; + +#endif // STATS_MGR diff --git a/src/lib/stats/tests/Makefile.am b/src/lib/stats/tests/Makefile.am new file mode 100644 index 0000000000..03bd5497b9 --- /dev/null +++ b/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) diff --git a/src/lib/stats/tests/context_unittest.cc b/src/lib/stats/tests/context_unittest.cc new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/lib/stats/tests/observation_unittest.cc b/src/lib/stats/tests/observation_unittest.cc new file mode 100644 index 0000000000..32c3a86c64 --- /dev/null +++ b/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 + +#include +#include + +#include +#include +#include + +#include +#include + +#include + +using namespace isc; +using namespace isc::stats; +using namespace boost::posix_time; + +namespace { + +class ObservationTest : public ::testing::Test { +public: + ObservationTest() + :a(static_cast(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(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(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(5678)), InvalidStatType); + EXPECT_THROW(c.setValue(56e+78), InvalidStatType); + EXPECT_THROW(c.setValue("fiveSixSevenEight"), InvalidStatType); + + EXPECT_THROW(d.setValue(static_cast(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(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(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 int_list = a.getIntegerList(); + std::list float_list = b.getFloatList(); + std::list dur_list = c.getDurationList(); + std::list 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(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); +} + +}; diff --git a/src/lib/stats/tests/run_unittests.cc b/src/lib/stats/tests/run_unittests.cc new file mode 100644 index 0000000000..9999820298 --- /dev/null +++ b/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 + +int +main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + + int result = RUN_ALL_TESTS(); + + return (result); +} diff --git a/src/lib/stats/tests/stats_mgr_unittest.cc b/src/lib/stats/tests/stats_mgr_unittest.cc new file mode 100644 index 0000000000..68ec157303 --- /dev/null +++ b/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 + +#include +#include + +#include +#include + +#include +#include + +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()); +} + +};