Skip to content

Commit

Permalink
Merge pull request #4 from UniStuttgart-VISUS/collector
Browse files Browse the repository at this point in the history
Improved collector
  • Loading branch information
crowbar27 authored Mar 9, 2023
2 parents 14a9bbf + 8412e64 commit 6efae95
Show file tree
Hide file tree
Showing 9 changed files with 417 additions and 8 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,12 @@ Second, in order to be eligible for the automated enumeration by the sensor util
* a template specialisation of `visus::power_overwhelming::detail::sensor_desc` must be provided in [sensor_desc.h](power_overwhelming/src/sensor_desc.h), which provides means to serialise and deserialise sensors,
* the class must be added to the `sensor_list` template at the bottom of [sensor_desc.h](power_overwhelming/src/sensor_desc.h).

The specialisation of `visus::power_overwhelming::detail::sensor_desc` must fulfil the following contract:
* It must have a member `static constexpr const char *type_name` specifing the unique name of the sensor, which can be declared using the `POWER_OVERWHELMING_DECLARE_SENSOR_NAME` macro.
* It must have a member `static constexpr bool intrinsic_async` specifying whether the sensor can run asynchronously without emulating it by starting a sampler thread that regularly polls the sensor.
* It must have a method `static inline nlohmann::json serialise(const value_type& value)` which serialises the given sensor into a JSON representation.
* It must have a method `static inline value_type deserialise(const nlohmann::json& value)` which restores a sensor from a given JSON representation.
* If the sensor can serialise all of its instances more efficiently than creating an instance of it and converting these instances to JSON, it can implement a method `static inline nlohmann::json serialise_all(void)` which serialises all sensors into a JSON array. The library will prefer this method if it is provided.

## Acknowledgments
This work was partially funded by Deutsche Forschungsgemeinschaft (DFG) as part of [SFB/Transregio 161](https://www.sfbtrr161.de) (project ID 251654672).
65 changes: 65 additions & 0 deletions power_overwhelming/include/power_overwhelming/collector.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@

#pragma once

#include <algorithm>
#include <array>
#include <initializer_list>
#include <memory>
#include <numeric>
#include <type_traits>
#include <vector>

#include "power_overwhelming/sensor.h"


Expand Down Expand Up @@ -51,6 +59,30 @@ namespace power_overwhelming {
/// specified file.</returns>
static collector from_json(const wchar_t *path);

/// <summary>
/// Initialise a new instance from the given lists of sensors.
/// </summary>
/// <typeparam name="TSensorLists">The types of the lists of sensors,
/// which must be STL collection types (with <c>begin</c> and
/// <c>end</c>) or sensors.</typeparam>
/// <param name="sensors">The lists of sensors.</param>
/// <returns></returns>
template<class... TSensorLists>
static collector from_sensor_lists(TSensorLists&&... sensors);

/// <summary>
/// Initialise a new instance from the given compile-time list of
/// (possibly different kinds of) sensors.
/// </summary>
/// <typeparam name="TSensors>The types of the sensors to be used by
/// the collector.</typeparam>
/// <param name="sensors">A compile-time list of sensors. The new
/// instance will take ownership of these sensors, ie they will be
/// disposed by moving them once the method returns.</param>
/// <returns>A new collector using the given sensors.</returns>
template<class... TSensors>
static collector from_sensors(TSensors&&... sensors);

/// <summary>
/// Creates a configuration file for all sensors currently attached to
/// the system.
Expand Down Expand Up @@ -140,15 +172,48 @@ namespace power_overwhelming {

private:

/// <summary>
/// Moves the given list of sensors to the heap.
/// </summary>
/// <typeparam name="TSensorList"></typeparam>
/// <param name="sensors"></param>
/// <returns></returns>
template<class TSensorList>
static std::vector<std::unique_ptr<sensor>> move_to_heap(
TSensorList&& sensors);

/// <summary>
/// Creates a new collector that has no sensors, reserved space for the
/// given number of sensors.
/// </summary>
/// <param name="capacity"></param>
/// <returns>A new collector without sensors.</returns>
static collector prepare(const std::size_t capacity);

/// <summary>
/// Initialise a new instance.
/// </summary>
/// <param name="impl"></param>
inline collector(detail::collector_impl *impl) : _impl(impl) { }

/// <summary>
/// Adds a new sensor to the collector.
/// </summary>
/// <remarks>
/// This method is not thread-safe and must only be called while the
/// collector is not running. It is therefore purposefully not part of
/// the public interface.
/// </remarks>
/// <param name="sensor">The sensor to be added. The object takes
/// ownership of the object designated by this pointer. The object
/// must have been allocated using C++ <c>new</c>.</param>
void add(sensor *sensor);

detail::collector_impl *_impl;

};

} /* namespace power_overwhelming */
} /* namespace visus */

#include "power_overwhelming/collector.inl"
77 changes: 77 additions & 0 deletions power_overwhelming/include/power_overwhelming/collector.inl
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// <copyright file="collector.inl" company="Visualisierungsinstitut der Universität Stuttgart">
// Copyright © 2023 Visualisierungsinstitut der Universität Stuttgart. Alle Rechte vorbehalten.
// </copyright>
// <author>Christoph Müller</author>


/*
* visus::power_overwhelming::collector::from_sensor_lists
*/
template<class... TSensorLists>
visus::power_overwhelming::collector
visus::power_overwhelming::collector::from_sensor_lists(
TSensorLists&&... sensors) {
std::array<std::vector<std::unique_ptr<sensor>>,
sizeof...(sensors)> instances = { move_to_heap(sensors)... };

const auto cnt = std::accumulate(instances.begin(),
instances.end(), static_cast<std::size_t>(0),
[](const std::size_t s, const decltype(instances)::value_type& v) {
return std::size(v) + s;
});

auto retval = collector::prepare(cnt);

for (auto& l : instances) {
for (auto& i : l) {
// See 'from_sensors' for the rationale of this.
retval.add(i.release());
}
}

return retval;
}

/*
* visus::power_overwhelming::collector::from_sensors
*/
template<class... TSensors>
visus::power_overwhelming::collector
visus::power_overwhelming::collector::from_sensors(TSensors&&... sensors) {
std::array<std::unique_ptr<sensor>, sizeof...(sensors)> instances = {
std::unique_ptr<sensor>(new typename std::decay<TSensors>::type(
std::move(sensors)))...
};

auto retval = collector::prepare(instances.size());

for (auto& i : instances) {
// Note: We release this on purpose as the library and the calling code
// might have been compiled with different versions of the STL. The
// unique_ptr here is only for releasing the memory in case of
// an exeception.
retval.add(i.release());
}

return retval;
}


/*
* visus::power_overwhelming::collector::move_to_heap
*/
template<class TSensorList>
std::vector<std::unique_ptr<visus::power_overwhelming::sensor>>
visus::power_overwhelming::collector::move_to_heap(TSensorList&& sensors) {
typedef typename std::decay<decltype(sensors.front())>::type sensor_type;

decltype(move_to_heap(sensors)) retval;
retval.reserve(std::size(sensors));

std::transform(sensors.begin(), sensors.end(), std::back_inserter(retval),
[](typename sensor_type& s) {
return std::unique_ptr<sensor>(new typename sensor_type(std::move(s)));
});

return retval;
}
44 changes: 44 additions & 0 deletions power_overwhelming/include/power_overwhelming/regex_escape.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// <copyright file="regex_escape.h" company="Visualisierungsinstitut der Universität Stuttgart">
// Copyright © 2023 Visualisierungsinstitut der Universität Stuttgart. Alle Rechte vorbehalten.
// </copyright>
// <author>Christoph Müller</author>

#pragma once

#include <algorithm>
#include <regex>

#include "power_overwhelming/literal.h"


namespace visus {
namespace power_overwhelming {

/// <summary>
/// Escape all special characters in the given string such that they match
/// as literals in a regular expression.
/// </summary>
/// <typeparam name="TChar">The character type.</typeparam>
/// <param name="str">The string to match literally in a regular expression.
/// </param>
/// <returns>The escaped string.</returns>
template<class TChar>
std::basic_string<TChar> regex_escape(const std::basic_string<TChar>& str) {
static const std::basic_string<TChar> special
= POWER_OVERWHELMING_TPL_LITERAL(TChar, "-[]{}()*+?.,\\^$|#");
std::basic_string<TChar> retval;
retval.reserve(str.size() + str.size() / 3); // Just a heuristic ...

for (auto c : str) {
if (special.find(c) != decltype(special)::npos) {
retval += POWER_OVERWHELMING_TPL_LITERAL(TChar, "\\");
}

retval += c;
}

return retval;
}

} /* namespace power_overwhelming */
} /* namespace visus */
31 changes: 31 additions & 0 deletions power_overwhelming/src/collector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,34 @@ visus::power_overwhelming::collector::operator =(collector&& rhs) noexcept {
visus::power_overwhelming::collector::operator bool(void) const noexcept {
return (this->_impl != nullptr);
}


/*
* visus::power_overwhelming::collector::prepare
*/
visus::power_overwhelming::collector
visus::power_overwhelming::collector::prepare(const std::size_t capacity) {
auto retval = collector(new detail::collector_impl());
retval._impl->sensors.reserve(capacity);
return retval;
}


/*
* visus::power_overwhelming::collector::add
*/
void visus::power_overwhelming::collector::add(sensor *sensor) {
assert(*this);

if (sensor == nullptr) {
throw std::invalid_argument("None of the sensors added to a collector "
"must be null.");
}
if (!*sensor) {
throw std::invalid_argument("None of the sensors added to a collector "
"must have been disposed.");
}

this->_impl->sensors.emplace_back(sensor);
}

53 changes: 45 additions & 8 deletions power_overwhelming/src/sensor_desc.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,30 @@
#include "power_overwhelming/emi_sensor.h"
#include "power_overwhelming/hmc8015_sensor.h"
#include "power_overwhelming/nvml_sensor.h"
#include "power_overwhelming/regex_escape.h"
#include "power_overwhelming/rtb_sensor.h"
#include "power_overwhelming/tinkerforge_sensor.h"

#include "described_sensor_type.h"
#include "tinkerforge_sensor_impl.h"


/// <summary>
/// Declares the static constant <c>type_name</c> for the sensor with the
/// given name.
/// </summary>
#define POWER_OVERWHELMING_DECLARE_SENSOR_NAME(name)\
static constexpr const char *type_name = #name

/// <summary>
/// Declares the static constant <c>intrinsic_async</c> specifying whether a
/// sensor is intrinsically asynchronous and should not be sampled by polling if
/// used in a <see cref="visus::power_overwhelming::collector" />.
/// </summary>
#define POWER_OVERWHELMING_DECLARE_INTRINSIC_ASYNC(value)\
static constexpr bool intrinsic_async = (value)


namespace visus {
namespace power_overwhelming {
namespace detail {
Expand Down Expand Up @@ -101,7 +118,8 @@ namespace detail {
/// </summary>
template<> struct sensor_desc<adl_sensor> final
: detail::sensor_desc_base<sensor_desc<adl_sensor>> {
static constexpr const char *type_name = "adl_sensor";
POWER_OVERWHELMING_DECLARE_SENSOR_NAME(adl_sensor);
POWER_OVERWHELMING_DECLARE_INTRINSIC_ASYNC(false);

static inline value_type deserialise(const nlohmann::json& value) {
auto source0 = value[json_field_source].get<std::string>();
Expand Down Expand Up @@ -130,7 +148,8 @@ namespace detail {
/// </summary>
template<> struct sensor_desc<emi_sensor> final
: detail::sensor_desc_base<sensor_desc<emi_sensor>> {
static constexpr const char *type_name = "emi_sensor";
POWER_OVERWHELMING_DECLARE_SENSOR_NAME(emi_sensor);
POWER_OVERWHELMING_DECLARE_INTRINSIC_ASYNC(false);

static inline value_type deserialise(const nlohmann::json& value) {
#if defined(_WIN32)
Expand All @@ -139,9 +158,20 @@ namespace detail {
auto path0 = value[json_field_path].get<std::string>();
auto path = power_overwhelming::convert_string<wchar_t>(path0);

std::size_t cnt = 0;
value_type retval;
auto cnt = value_type::for_device_and_channel(&retval, 1,
path.c_str(), channel);

try {
cnt = value_type::for_device_and_channel(&retval, 1,
path.c_str(), channel);
} catch (std::regex_error) {
// Second chance: User might have specified a literal name,
// which is usually not a valid regular expression, so we
// escape it now and retry.
path = power_overwhelming::regex_escape(path);
cnt = value_type::for_device_and_channel(&retval, 1,
path.c_str(), channel);
}

if (cnt == 0) {
throw std::invalid_argument("The specified EMI device and "
Expand Down Expand Up @@ -177,7 +207,8 @@ namespace detail {
/// </summary>
template<> struct sensor_desc<hmc8015_sensor> final
: detail::sensor_desc_base<sensor_desc<hmc8015_sensor>> {
static constexpr const char *type_name = "hmc8015_sensor";
POWER_OVERWHELMING_DECLARE_SENSOR_NAME(hmc8015_sensor);
POWER_OVERWHELMING_DECLARE_INTRINSIC_ASYNC(false);

static inline value_type deserialise(const nlohmann::json& value) {
auto path = value[json_field_path].get<std::string>();
Expand All @@ -203,7 +234,8 @@ namespace detail {
/// </summary>
template<> struct sensor_desc<nvml_sensor> final
: detail::sensor_desc_base<sensor_desc<nvml_sensor>> {
static constexpr const char *type_name = "nvml_sensor";
POWER_OVERWHELMING_DECLARE_SENSOR_NAME(nvml_sensor);
POWER_OVERWHELMING_DECLARE_INTRINSIC_ASYNC(false);

static inline value_type deserialise(const nlohmann::json& value) {
auto guid = value[json_field_dev_guid].get<std::string>();
Expand All @@ -227,7 +259,8 @@ namespace detail {
/// </summary>
template<> struct sensor_desc<rtb_sensor> final
: detail::sensor_desc_base<sensor_desc<rtb_sensor>> {
static constexpr const char *type_name = "rtb_sensor";
POWER_OVERWHELMING_DECLARE_SENSOR_NAME(rtb_sensor);
POWER_OVERWHELMING_DECLARE_INTRINSIC_ASYNC(false);

static inline value_type deserialise(const nlohmann::json& value) {
auto path = value[json_field_path].get<std::string>();
Expand All @@ -253,7 +286,8 @@ namespace detail {
/// </summary>
template<> struct sensor_desc<tinkerforge_sensor> final
: detail::sensor_desc_base<sensor_desc<tinkerforge_sensor>> {
static constexpr const char *type_name = "tinkerforge_sensor";
POWER_OVERWHELMING_DECLARE_SENSOR_NAME(tinkerforge_sensor);
POWER_OVERWHELMING_DECLARE_INTRINSIC_ASYNC(true);

static inline value_type deserialise(const nlohmann::json& value) {
const auto dit = value.find(json_field_description);
Expand Down Expand Up @@ -336,6 +370,9 @@ namespace detail {
};


#undef POWER_OVERWHELMING_DECLARE_SENSOR_NAME
#undef POWER_OVERWHELMING_DECLARE_INTRINSIC_ASYNC

/// <summary>
/// A type list of all known sensors, which allows for compile-time
/// enumeration of known sensor types.
Expand Down
Loading

0 comments on commit 6efae95

Please sign in to comment.