Files
scylladb/core/scollectd.hh
Avi Kivity 44c3e9fc04 collectd: wrap initializers with braces
Helps prevents ambiguity with constructors that accept multiple parameters.
2014-11-26 14:57:58 +02:00

448 lines
15 KiB
C++

/*
* Copyright (C) 2014 Cloudius Systems, Ltd.
*
* This work is open source software, licensed under the terms of the
* BSD license as described in the LICENSE file in the top-level directory.
*/
#ifndef SCOLLECTD_HH_
#define SCOLLECTD_HH_
#include <type_traits>
#include <utility>
#include <functional>
#include <array>
#include <iterator>
#include <stdint.h>
#include <memory>
#include <string>
#include <tuple>
#include "core/reactor.hh"
#include "net/byteorder.hh"
/**
* Implementation of rudimentary collectd data gathering.
*
* Usage is hopefully straight forward. Though, feel free to read
* https://collectd.org/wiki/index.php/Naming_schema
* for an explanation on the naming model.
*
* Typically, you'll add values something like:
*
* scollectd::type_instance_id typ("<pluginname>", "<instance_name>", "<type_name>", "<instance_name>");
* scollectd::add_polled_metric(typ, [<metric var> | scollectd::make_typed(<data_type>, <metric_var>) [, ...]);
*
* Where
* <pluginname> would be the overall 'module', e.g. "cpu"
* <instance_name> -> optional distinguisher between plugin instances. For cpu, the built-in
* scollectd::per_cpu_plugin_instance constant is a good choice, i.e. 0->N cpu.
* If there are no instances (e.g. only one), empty constant is appropriate (none)
* <type_name> is the 'type' of metric collected, for ex. "usage" (cpu/0/usage)
* <type_instance> is a distinguisher for metric parts of the type, e.g. "idle", "user", "kernel"
* -> cpu/0/usage/idle | cpu/0/usage/user | cpu/0/usage/kernel
*
* Each type instance can bind an arbitrary number of values, ech representing some aspect in turn of the instance.
* The structure and interpretation is up to the producer/consumer
*
* There is a single "scollectd" instance per cpu, and values should be bound locally
* to this cpu. Polling is done at a frequency set in the seastar config (def once per s),
* and all registered values will be sent via UDP packages to the destination host(s)
*
* Note that the tuple { plugin, plugin_instance, type, type_instance } is considered a
* unique ID for a value registration, so using the same tuple twice will remove the previously
* registered values.
*
* Values can be unregistered at any time, though they must be so on the same thread/cpu
* as they we're registered. The "registration" achor type provides RAII style value unregistration
* semantics.
*
*/
/* all-static. using a class instead of namespace to hide implementation templates */
class scollectd {
class impl;
public:
scollectd() = delete;
// The value binding data types
enum class data_type : uint8_t {
COUNTER, // unsigned int 64
GAUGE, // double
DERIVE, // signed int 64
ABSOLUTE, // unsigned int 64
};
// don't use directly. use make_typed.
template<typename T>
struct typed {
typed(data_type t, T && v)
: type(t), value(std::forward<T>(v)) {
}
data_type type;
T value;
};
template<typename T>
static inline typed<T> make_typed(data_type type, T&& t) {
return typed<T>(type, std::forward<T>(t));
}
typedef std::string plugin_id;
typedef std::string plugin_instance_id;
typedef std::string type_id;
class type_instance_id {
public:
type_instance_id() = default;
type_instance_id(const plugin_id & p, const plugin_instance_id & pi,
const type_id & t, const std::string & ti = std::string())
: _plugin(p), _plugin_instance(pi), _type(t), _type_instance(ti) {
}
type_instance_id(type_instance_id &&) = default;
type_instance_id(const type_instance_id &) = default;
type_instance_id & operator=(type_instance_id &&) = default;
type_instance_id & operator=(const type_instance_id &) = default;
const plugin_id & plugin() const {
return _plugin;
}
const plugin_instance_id & plugin_instance() const {
return _plugin_instance;
}
const type_id & type() const {
return _type;
}
const std::string & type_instance() const {
return _type_instance;
}
private:
plugin_id _plugin;
plugin_instance_id _plugin_instance;
type_id _type;
std::string _type_instance;
};
static const plugin_instance_id per_cpu_plugin_instance;
static void configure(const boost::program_options::variables_map&);
static boost::program_options::options_description get_options_description();
/**
* Anchor for polled registration.
* Iff the registered type is in some way none-persistent,
* use this as receiver of the reg and ensure it dies before the
* added value(s).
*
* Use:
* uint64_t v = 0;
* registration r = add_polled_metric(v);
* ++r;
* <scope end, above dies>
*/
struct registration {
registration() = default;
registration(const type_instance_id& id)
: _id(id) {
}
registration(type_instance_id&& id)
: _id(std::forward<type_instance_id>(id)) {
}
registration(const registration&) = default;
registration(registration&&) = default;
~registration() {
unregister();
}
registration & operator=(const registration&) = default;
registration & operator=(registration&&) = default;
void unregister() {
remove_polled_metric(_id);
_id = type_instance_id();
}
private:
type_instance_id _id;
};
typedef std::function<void()> notify_function;
template<typename ... _Args>
static type_instance_id add_polled_metric(const plugin_id & plugin,
const plugin_instance_id & plugin_instance, const type_id & type,
const std::string & type_instance, _Args&& ... args) {
return add_polled_metric(
type_instance_id(plugin, plugin_instance, type, type_instance),
std::forward<_Args>(args)...);
}
template<typename ... _Args>
static future<> send_explicit_metric(const plugin_id & plugin,
const plugin_instance_id & plugin_instance, const type_id & type,
const std::string & type_instance, _Args&& ... args) {
return send_explicit_metric(
type_instance_id(plugin, plugin_instance, type, type_instance),
std::forward<_Args>(args)...);
}
template<typename ... _Args>
static notify_function create_explicit_metric(const plugin_id & plugin,
const plugin_instance_id & plugin_instance, const type_id & type,
const std::string & type_instance, _Args&& ... args) {
return create_explicit_metric(
type_instance_id(plugin, plugin_instance, type, type_instance),
std::forward<_Args>(args)...);
}
template<typename ... _Args>
static type_instance_id add_polled_metric(const type_instance_id & id,
_Args&& ... args) {
typedef decltype(make_type_instance(std::forward<_Args>(args)...)) impl_type;
add_polled(id,
std::make_shared<impl_type>(
make_type_instance(std::forward<_Args>(args)...)));
return id;
}
// "Explicit" metric sends. Sends a single value list as a message.
// Obviously not super efficient either. But maybe someone needs it sometime.
template<typename ... _Args>
static future<> send_explicit_metric(const type_instance_id & id,
_Args&& ... args) {
return send_metric(id, make_type_instance(std::forward<_Args>(args)...));
}
template<typename ... _Args>
static notify_function create_explicit_metric(const type_instance_id & id,
_Args&& ... args) {
auto list = make_type_instance(std::forward<_Args>(args)...);
return [id, list=std::move(list)]() {
send_metric(id, list);
};
}
static void remove_polled_metric(const type_instance_id &);
// Send a message packet (string)
static future<> send_notification(const type_instance_id & id,
const std::string & msg);
private:
// lots of template junk to build typed value list tuples
// for registered values.
template<typename T, typename En = void>
struct data_type_for;
template<typename T, typename En = void>
struct is_callable;
template<typename T>
struct is_callable<T,
typename std::enable_if<
!std::is_void<typename std::result_of<T()>::type>::value,
void>::type> : public std::true_type {
};
template<typename T>
struct is_callable<T,
typename std::enable_if<std::is_fundamental<T>::value, void>::type> : public std::false_type {
};
template<typename T>
struct data_type_for<T,
typename std::enable_if<
std::is_integral<T>::value && std::is_unsigned<T>::value,
void>::type> : public std::integral_constant<data_type,
data_type::COUNTER> {
};
template<typename T>
struct data_type_for<T,
typename std::enable_if<
std::is_integral<T>::value && std::is_signed<T>::value, void>::type> : public std::integral_constant<
data_type, data_type::DERIVE> {
};
template<typename T>
struct data_type_for<T,
typename std::enable_if<std::is_floating_point<T>::value, void>::type> : public std::integral_constant<
data_type, data_type::GAUGE> {
};
template<typename T>
struct data_type_for<T,
typename std::enable_if<is_callable<T>::value, void>::type> : public data_type_for<
typename std::result_of<T()>::type> {
};
template<typename T>
struct data_type_for<typed<T>> : public data_type_for<T> {
};
template<typename T>
class value {
public:
template<typename W>
struct wrap {
wrap(const W & v)
: _v(v) {
}
const W & operator()() const {
return _v;
}
const W & _v;
};
typedef typename std::remove_reference<T>::type value_type;
typedef typename std::conditional<
is_callable<typename std::remove_reference<T>::type>::value,
value_type, wrap<value_type> >::type stored_type;
value(const value_type & t)
: value<T>(data_type_for<value_type>::value, t) {
}
value(data_type type, const value_type & t)
: _type(type), _t(t) {
}
uint64_t operator()() const {
auto v = _t();
if (_type == data_type::GAUGE) {
return convert(double(v));
} else {
uint64_t u = v;
return convert(u);
}
}
operator uint64_t() const {
return (*this)();
}
operator data_type() const {
return _type;
}
data_type type() const {
return _type;
}
private:
// not super quick value -> protocol endian 64-bit values.
template<typename _Iter>
void bpack(_Iter s, _Iter e, uint64_t v) const {
while (s != e) {
*s++ = (v & 0xff);
v >>= 8;
}
}
template<typename V>
typename std::enable_if<std::is_integral<V>::value, uint64_t>::type convert(
V v) const {
uint64_t i = v;
// network byte order
return ntohq(i);
}
template<typename V>
typename std::enable_if<std::is_floating_point<V>::value, uint64_t>::type convert(
V t) const {
union {
uint64_t i;
double v;
} v;
union {
uint64_t i;
uint8_t b[8];
} u;
v.v = t;
// intel byte order. could also obviously be faster.
// could be ignored if we just assume we're le (for now),
// but this is ok me thinks.
bpack(std::begin(u.b), std::end(u.b), v.i);
return u.i;
}
;
const data_type _type;
const stored_type _t;
};
template<typename T>
class value<typed<T>> : public value<T> {
public:
value(const typed<T> & args)
: value<T>(args.type, args.value) {
}
};
class value_list {
public:
virtual size_t size() const = 0;
virtual void types(data_type *) const = 0;
virtual void values(net::packed<uint64_t> *) const = 0;
bool empty() const {
return size() == 0;
}
};
template<typename ... Args>
class values_impl: public value_list {
public:
static const size_t num_values = sizeof...(Args);
values_impl(Args&& ...args)
: _values(std::forward<Args>(args)...)
{}
values_impl(values_impl<Args...>&& a) = default;
values_impl(const values_impl<Args...>& a) = default;
size_t size() const override {
return num_values;
}
void types(data_type * p) const override {
unpack(_values, [p](Args... args) {
const std::array<data_type, sizeof...(args)> tmp = { {args}...};
std::copy(tmp.begin(), tmp.end(), p);
});
}
void values(net::packed<uint64_t> * p) const override {
unpack(_values, [p](Args... args) {
std::array<uint64_t, num_values> tmp = { {args}...};
std::copy(tmp.begin(), tmp.end(), p);
});
}
private:
template<typename _Op>
void unpack(const std::tuple<Args...>& t, _Op&& op) const {
do_unpack(t, std::index_sequence_for<Args...> {}, std::forward<_Op>(op));
}
template<size_t ...S, typename _Op>
void do_unpack(const std::tuple<Args...>& t, const std::index_sequence<S...> &, _Op&& op) const {
op(std::get<S>(t)...);
}
std::tuple < Args... > _values;
};
template<typename... _Args>
static auto make_type_instance(_Args && ... args) -> values_impl < decltype(value<_Args>(std::forward<_Args>(args)))... >
{
return values_impl<decltype(value<_Args>(std::forward<_Args>(args)))... >
(value<_Args>(std::forward<_Args>(args))...);
}
static const ipv4_addr default_addr;
static const clock_type::duration default_period;
static void add_polled(const type_instance_id &, const std::shared_ptr<value_list> &);
static future<> send_metric(const type_instance_id &, const value_list &);
static impl & get_impl();
};
inline bool operator<(const scollectd::type_instance_id & id1,
const scollectd::type_instance_id & id2) {
return std::tie(id1.plugin(), id1.plugin_instance(), id1.type(),
id1.type_instance())
< std::tie(id2.plugin(), id2.plugin_instance(), id2.type(),
id2.type_instance());
}
inline bool operator==(const scollectd::type_instance_id & id1,
const scollectd::type_instance_id & id2) {
return std::tie(id1.plugin(), id1.plugin_instance(), id1.type(),
id1.type_instance())
== std::tie(id2.plugin(), id2.plugin_instance(), id2.type(),
id2.type_instance());
}
#endif /* SCOLLECTD_HH_ */