Commit 46f3bc40 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

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

parent e5c443b8
......@@ -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
......
# 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
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
#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
#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_));
};
}
};
};
// 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
// 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_;
};
};
};