diff --git a/.travis.yml b/.travis.yml index 9af120b..3dddd23 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,23 +1,57 @@ language: cpp -compiler: - - gcc - -before_script: - - mkdir build - - cd build - - cmake .. - -install: - - if [ "$CXX" = "g++" ]; then export CXX="g++-4.8" CC="gcc-4.8"; fi - -script: make +sudo : false addons: apt: - sources: - - ubuntu-toolchain-r-test packages: - - gcc-4.8 - - g++-4.8 - cmake + +matrix: + include: + - os: linux + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - gcc-4.9 + - g++-4.9 + env: + - CC_EVAL="CC=gcc-4.9 && CXX=g++-4.9" + before_script: + - pip install --user cpp-coveralls + after_success: + - coveralls -E ".*gtest.*" -E ".*CMakeFiles.*" --exclude lib --exclude test + --exclude examples --gcov-options '\-lp' + + - os: linux + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - gcc-7 + - g++-7 + env: + - CC_EVAL="CC=gcc-7 && CXX=g++-7" + + - os: linux + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-precise-3.6 + packages: + - clang-3.6 + env: + - CC_EVAL="CC=clang-3.6 && CXX=clang++-3.6" + +before_install: + - eval "${CC_EVAL}" + - cmake -DBUILD_TESTS=On . + +script: + - make + - make test + diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c9fe77..8e3edd2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,9 @@ endif() set (CMAKE_CXX_STANDARD 11) -if(CMAKE_BUILD_TYPE MATCHES Debug) +if(BUILD_TESTS) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -fprofile-arcs -ftest-coverage") +elseif(CMAKE_BUILD_TYPE MATCHES Debug) if(MSVC) if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") string(REGEX REPLACE "/W[0-4]" "/W4" @@ -48,7 +50,6 @@ if(NOT CURL_FOUND) set(CURL_FOUND TRUE) set(CURL_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/lib/curl/include) set(CURL_LIBRARIES libcurl) - endif() add_subdirectory(src) @@ -56,6 +57,11 @@ add_subdirectory(src) if(BUILD_TESTS) find_package(GTest) + if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + endif() + + include_directories(src) + if(NOT GTEST_FOUND) message(STATUS "Not using system gtest, using built-in googletest project instead.") @@ -67,7 +73,6 @@ if(BUILD_TESTS) set(GTEST_LIBRARIES gtest) set(GTEST_MAIN_LIBRARIES gtest_main) set(GTEST_BOTH_LIBRARIES gtest gtest_main) - set(GTEST_INCLUDE_DIRS ${gtest_SOURCE_DIR}/include) # Group under the "tests/gtest" project folder in IDEs such as Visual Studio. set_property(TARGET gtest PROPERTY FOLDER "tests/gtest") diff --git a/examples/ex_influx.cc b/examples/ex_influx.cc index 08ff19f..cf5ae76 100644 --- a/examples/ex_influx.cc +++ b/examples/ex_influx.cc @@ -23,12 +23,15 @@ * SOFTWARE. */ +#include #include int main(int argc, char** argv) { - auto ms = metricspp::create_connection("http://127.0.0.1:8086/write?db=test", - metricspp::Tag{"server", "server01"}, - metricspp::Tag{"region", "eu-east"}); + auto ms = + metricspp::create_connection(std::make_shared( + "http://127.0.0.1:8086/write?db=test"), + metricspp::Tag{"server", "server01"}, + metricspp::Tag{"region", "eu-east"}); // Simple usage with different measurements ms << "eth0" << 53; diff --git a/include/metricspp/_internal/d_stream.h b/include/metricspp/_internal/d_stream.h index 2ed2ca8..d393366 100644 --- a/include/metricspp/_internal/d_stream.h +++ b/include/metricspp/_internal/d_stream.h @@ -2,17 +2,18 @@ #define L__INTERNAL_DATA_STREAM_H #include -#include #include -#include #include +#include +#include namespace metricspp { -class NetworkConnector; -namespace _internal { -class DataStreamPrivate; +namespace base { +class IConnector; +} +namespace _internal { /** DataStream class * * Stream class object returns as the temporary object from @@ -20,6 +21,8 @@ class DataStreamPrivate; */ class DataStream { public: + using value_storage = std::list>; + DataStream() = delete; /** DataStream Constructor @@ -30,7 +33,7 @@ class DataStream { * @param connector Pointer on a valid \a NetworkConnector object * @see NetworkConnector */ - DataStream(const std::shared_ptr &connector); + DataStream(const std::shared_ptr &connector); ~DataStream(); /** Set Measure name method @@ -56,7 +59,7 @@ class DataStream { void set_tags(const std::map &tags); /** Insertion operator - * + * Inserts name as a \a str for the next value which comes after the call. * * @param str Name of variable @@ -65,6 +68,7 @@ class DataStream { */ DataStream &operator<<(const std::string &str); + DataStream &operator<<(const char *data); /** Insertion operators(different types) * * Inserts numeric value for previous set variable name @@ -75,7 +79,6 @@ class DataStream { */ DataStream &operator<<(bool value); DataStream &operator<<(short value); - DataStream &operator<<(float value); DataStream &operator<<(double value); DataStream &operator<<(long double value); DataStream &operator<<(int value); @@ -83,11 +86,39 @@ class DataStream { DataStream &operator<<(unsigned value); DataStream &operator<<(long unsigned value); - DataStream(const DataStream &stream); - DataStream &operator=(const DataStream &in); - private: - const std::unique_ptr m_data; + /** Set net variable + * + * Sets next variable field with serialized \a value + * + * @param value data as a string + */ + void set_next(const std::string &value); + + /** Set record + * + * Set new record \a value by \a name or update it + * @param name Record name + * @param value Record value + */ + void set_record(const std::string &name, const std::string &value); + + /** Format data method + * + * Forms a string for send from set of variables,tags and the measure name. + * + * @return Formatted string + */ + std::string form_data() const; + + std::string m_previous; ///< Previusly set variable name + std::string m_measure; ///< Measure name + std::string m_tags; ///< Measure tags + + value_storage m_values; ///< Measure values + value_storage::iterator m_pos; ///< Current position of values fill procedure + + std::shared_ptr m_connector; ///< Network connector pointer }; } } diff --git a/include/metricspp/base/baseconnector.hpp b/include/metricspp/base/baseconnector.hpp new file mode 100644 index 0000000..0599a93 --- /dev/null +++ b/include/metricspp/base/baseconnector.hpp @@ -0,0 +1,28 @@ +#ifndef _BASE_BASECONNECTOR_ +#define _BASE_BASECONNECTOR_ + +#include + +namespace metricspp { +namespace base { + +/** IConnector interface class + * + * Represents a data interface for database. All custom connectors + * must be derived from this class. + */ +class IConnector { + public: + /** Post data method + * + * Sends \a data using post method + * + * @param data String of data for send + * + * @return \a true on sucess operation + */ + virtual bool post(const std::string &data) = 0; +}; +} +} +#endif // ifndef _BASE_BASECONNECTOR_ diff --git a/include/metricspp/httpconnector.hpp b/include/metricspp/httpconnector.hpp new file mode 100644 index 0000000..3d2e341 --- /dev/null +++ b/include/metricspp/httpconnector.hpp @@ -0,0 +1,64 @@ +#ifndef L_NETWORKCONNECTOR_H +#define L_NETWORKCONNECTOR_H + +#include + +#include "base/baseconnector.hpp" + +namespace metricspp { +/** HttpConnector class + * + * Network connector class objects communicate with a database + * and handle data streams + */ +class HttpConnector : public base::IConnector { + public: + HttpConnector(); + + /** HttpConnector constructor + * + * Constructor creates a new object with an \a addr to communicate + * with + * + * @param addr Database address + */ + HttpConnector(const std::string &addr); + + virtual ~HttpConnector(); + + /** Post data method + * + * Reimplemented method. + * + * @param data + * @see base::IConnector::post + * + * @return \a true on sucess operation + */ + bool post(const std::string &data) override; + + /** Set adress + * + * Set new database address + * + * @param addr Domain name or ip address as a string + */ + void set_address(const std::string &addr); + + /** Get address + * + * Returns current usable address. + * + * @return Domain or ip address + */ + std::string address() const; + + private: + std::string m_addr; ///< Usable address + std::string m_error; ///< Curl error buffer + + void *m_handle; ///< Curl handler +}; +} + +#endif // L_NETWORKCONNECTOR_H diff --git a/include/metricspp/connector.hpp b/include/metricspp/metric.hpp similarity index 69% rename from include/metricspp/connector.hpp rename to include/metricspp/metric.hpp index fba4b31..072ef9d 100644 --- a/include/metricspp/connector.hpp +++ b/include/metricspp/metric.hpp @@ -1,10 +1,12 @@ #ifndef L_METRICSPP_CONNECTOR_HPP #define L_METRICSPP_CONNECTOR_HPP +#include #include #include #include +#include "base/baseconnector.hpp" #include "modifier.hpp" #include "_internal/d_stream.h" @@ -14,15 +16,14 @@ namespace metricspp { using Tag = std::pair; -class MetricsModifier; -class MetricsConnectorPrivate; - /** MetricsConnector class * * General working class. Object of this class primary uses for * sending metrics in series databases. Can be extended with different * tags if database has it support. They will be attached to every * database commit. + * + * Connector must be based on IConnector */ class MetricsConnector { public: @@ -30,18 +31,28 @@ class MetricsConnector { /** MetricsConnector constructor * - * Specified class constructor for the class' object with \a addr - * as address of specified API and list of \a tags for future - * usage + * Specified class constructor for the class' object with \a connector + * as access point, through all data will flow, and list of \a tags for + * future usage. Tags must only contain letters and/or numbers without * - * @param addr Full API address + * @param connector Shared pointer to connector. If pointer is invalid, + * invalid_argument will be throw as exception * @param tags List of usable tags + * @see base::IConnector * */ - MetricsConnector(const std::string &addr, - const std::initializer_list &tags = {}); + MetricsConnector(const std::shared_ptr &connector, + std::initializer_list tags = {}); - ~MetricsConnector(); + virtual ~MetricsConnector(); + + /** Get Metric Tags + * + * Returns tags and values for this metric. + * + * @return map of tags + */ + std::map tags() const; /** Insertion Operator * @@ -78,11 +89,9 @@ class MetricsConnector { */ _internal::DataStream operator<<(const std::string &str); - MetricsConnector(const MetricsConnector &cr); - MetricsConnector &operator=(const MetricsConnector &cr); - private: - const std::unique_ptr m_data; + std::shared_ptr m_connector; // Network Connector + std::map m_tags; // Measurement tags }; } diff --git a/include/metricspp/metricspp.hpp b/include/metricspp/metricspp.hpp index 89621c0..7e67892 100644 --- a/include/metricspp/metricspp.hpp +++ b/include/metricspp/metricspp.hpp @@ -3,7 +3,8 @@ #include -#include "connector.hpp" +#include "base/baseconnector.hpp" +#include "metric.hpp" #include "modifier.hpp" // \brief Library namespace @@ -34,13 +35,10 @@ namespace metricspp { * @param tags Tags for additional information in db * @return MetrcsConnector object */ -MetricsConnector create_connection(const std::string &addr, - const std::initializer_list &tags = {}); - -template -MetricsConnector create_connection(const std::string &addr, +template +MetricsConnector create_connection(const std::shared_ptr &connector, const Tags... tags) { - return create_connection(addr, {tags...}); + return metricspp::MetricsConnector(connector, {tags...}); } /** Create Measurement Modificator Function @@ -64,14 +62,10 @@ MetricsConnector create_connection(const std::string &addr, * @return MetricsModifier object * */ -MetricsModifier create_measurement( - const std::string &name, - const std::initializer_list &vnames = {}); - template MetricsModifier create_measurement(const std::string &name, const Fields &... vnames) { - return create_measurement(name, {vnames...}); + return MetricsModifier(name, {vnames...}); } } diff --git a/include/metricspp/modifier.hpp b/include/metricspp/modifier.hpp index e9309b7..56743f6 100644 --- a/include/metricspp/modifier.hpp +++ b/include/metricspp/modifier.hpp @@ -31,8 +31,8 @@ class MetricsModifier { * as it comes in. */ MetricsModifier(const std::string &measure, - const std::initializer_list &vqueue); - ~MetricsModifier(); + const std::initializer_list &vqueue = {}); + virtual ~MetricsModifier(); /** Measure method * @@ -50,11 +50,9 @@ class MetricsModifier { */ std::list queue() const; - MetricsModifier(const MetricsModifier &cr); - MetricsModifier &operator=(const MetricsModifier &cr); - private: - const std::unique_ptr m_data; + std::string m_measure; ///< Measure name + std::list m_queue; ///< Measure's variables queue }; } diff --git a/src/_internal/d_stream.cpp b/src/_internal/d_stream.cpp index 1079bfa..98ce34d 100644 --- a/src/_internal/d_stream.cpp +++ b/src/_internal/d_stream.cpp @@ -1,106 +1,167 @@ +#include +#include +#include +#include + #include +#include -#include "../networkconnector.h" -#include "d_stream_p.h" +#include "../funcollection.h" using namespace metricspp::_internal; -DataStream::DataStream(const std::shared_ptr &connector) - : m_data(new DataStreamPrivate()) { - m_data->m_connector = connector; +DataStream::DataStream(const std::shared_ptr &connector) + : m_connector(connector), m_pos(m_values.end()) { + if (!connector) { + throw std::invalid_argument("Input connector pointer is invalid"); + } } DataStream::~DataStream() { - if (m_data->m_connector) { - m_data->m_connector->post(m_data->form_data()); + if (m_connector) { + auto data = form_data(); + if (!data.empty()) { + m_connector->post(data); + } } } void DataStream::set_measure(const std::string &measure) { - if (!measure.empty()) { - m_data->m_measure = measure; + if (is_var_valid(measure)) { + m_measure = measure; } } void DataStream::set_values_queue(const std::list &queue) { + m_values.clear(); + for (auto &name : queue) { - m_data->m_values.emplace(std::make_pair(name, "")); + if (is_var_valid(name)) { + set_record(name, ""); + } } - m_data->m_pos = m_data->m_values.begin(); + m_pos = m_values.begin(); } void DataStream::set_tags(const std::map &tags) { - m_data->m_tags.clear(); + m_tags.clear(); for (auto tag = tags.cbegin(); tag != tags.cend(); ++tag) { - m_data->m_tags += tag->first + "=" + tag->second + - (std::next(tag) == tags.cend() ? "" : ","); + if (is_var_valid(tag->first) && is_var_valid(tag->second)) { + m_tags += tag->first + "=" + tag->second + + (std::next(tag) == tags.cend() ? "" : ","); + } } } DataStream &DataStream::operator<<(const std::string &str) { - if (m_data->m_previous.empty()) { - m_data->m_previous = str; - - if (m_data->m_values.find(str) == m_data->m_values.end()) { - m_data->m_values.emplace(std::make_pair(str, "")); - } + if (is_var_valid(str)) { + m_previous = str; } return *this; } -DataStream &DataStream::operator<<(bool value) { - m_data->set_next(std::to_string(value)); - return *this; +DataStream &DataStream::operator<<(const char *data) { + return this->operator<<(std::string(data)); } -DataStream &DataStream::operator<<(short value) { - m_data->set_next(std::to_string(value)); +DataStream &DataStream::operator<<(bool value) { + set_next(to_string(value)); return *this; } -DataStream &DataStream::operator<<(float value) { - m_data->set_next(std::to_string(value)); +DataStream &DataStream::operator<<(short value) { + set_next(to_string(value)); return *this; } DataStream &DataStream::operator<<(double value) { - m_data->set_next(std::to_string(value)); + set_next(to_string(value)); return *this; } DataStream &DataStream::operator<<(long double value) { - m_data->set_next(std::to_string(value)); + set_next(to_string(value)); return *this; } DataStream &DataStream::operator<<(int value) { - m_data->set_next(std::to_string(value)); + set_next(to_string(value)); return *this; } DataStream &DataStream::operator<<(long int value) { - m_data->set_next(std::to_string(value)); + set_next(to_string(value)); return *this; } DataStream &DataStream::operator<<(unsigned value) { - m_data->set_next(std::to_string(value)); + set_next(to_string(value)); return *this; } DataStream &DataStream::operator<<(long unsigned value) { - m_data->set_next(std::to_string(value)); + set_next(to_string(value)); return *this; } -DataStream::DataStream(const DataStream &stream) : DataStream(nullptr) { - this->operator=(stream); +void DataStream::set_next(const std::string &value) { + if (!m_previous.empty()) { + set_record(m_previous, value); + m_previous.clear(); + return; + } + + if (m_pos != m_values.end()) { + if (m_pos->second.empty()) { + m_pos->second = value; + } + ++m_pos; + } +} + +void DataStream::set_record(const std::string &name, const std::string &value) { + auto pos = std::find_if( + m_values.begin(), m_values.end(), + [name](const value_storage::value_type &it) { return it.first == name; }); + + if (pos == m_values.end()) { + m_values.emplace_back(std::make_pair(name, value)); + } else { + (pos->second = value); + } } -DataStream &DataStream::operator=(const DataStream &in) { - if (&in != this) { - *m_data = *in.m_data; +std::string DataStream::form_data() const { + std::stringstream out; + + // No need to send somthing if name is empty or it values + if (m_measure.empty() || m_values.empty()) { + return ""; } - return *this; + + out << m_measure << (m_tags.empty() ? "" : ",") << m_tags << " "; + + bool first = true; + for (auto value = m_values.cbegin(); value != m_values.cend(); ++value) { + if (!value->second.empty()) { + out << (first ? "" : ",") << value->first << "=" << value->second; + + if (first) { + first = false; + } + } + } + + // No need to send somthing if we put nothing as a value + if (first) { + return ""; + } + + out << " " + << std::chrono::nanoseconds( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + return out.str(); } diff --git a/src/_internal/d_stream_p.cpp b/src/_internal/d_stream_p.cpp deleted file mode 100644 index 53d42f2..0000000 --- a/src/_internal/d_stream_p.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include - -#include "d_stream_p.h" - -using namespace metricspp::_internal; - -void DataStreamPrivate::set_next(const std::string &value) { - if (!m_previous.empty()) { - m_values[m_previous] = value; - m_previous.clear(); - return; - } - - if (m_pos != m_values.end()) { - if (m_pos->second.empty()) { - m_pos->second = value; - } - ++m_pos; - } -} - -std::string DataStreamPrivate::form_data() const { - std::stringstream out; - out << m_measure << (m_tags.empty() ? "" : ",") << m_tags << " "; - - bool first = true; - for (auto value = m_values.cbegin(); value != m_values.cend(); ++value) { - if (!value->second.empty()) { - out << (first ? "" : ",") << value->first << "=" << value->second; - - if (first) { - first = false; - } - } - } - - out << " " - << std::chrono::nanoseconds( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - - return out.str(); -} diff --git a/src/_internal/d_stream_p.h b/src/_internal/d_stream_p.h deleted file mode 100644 index 158cef0..0000000 --- a/src/_internal/d_stream_p.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef L__INTERNAL_DATA_STREAM_P_H -#define L__INTERNAL_DATA_STREAM_P_H - -#include -#include -#include -#include - -namespace metricspp { -class NetworkConnector; - -namespace _internal { -class DataStreamPrivate { - using value_storage = std::unordered_map; - - /** Set net variable - * - * Sets next variable field with serialized \a value - * - * @param value data as a string - */ - void set_next(const std::string &value); - - /** Format data method - * - * Forms a string for send from set of variables,tags and the measure name. - * - * @return Formatted string - */ - std::string form_data() const; - - std::string m_previous; ///< Previusly set variable name - std::string m_measure; ///< Measure name - std::string m_tags; ///< Measure tags - - value_storage m_values; ///< Measure values - value_storage::iterator m_pos; ///< Current position of values fill procedure - - std::shared_ptr m_connector; ///< Network connector pointer - - friend class DataStream; -}; -} -} -#endif // L__INTERNAL_DATA_STREAM_P_H diff --git a/src/connector.cpp b/src/connector.cpp deleted file mode 100644 index 6998ef3..0000000 --- a/src/connector.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include - -#include "connector_p.h" -#include "networkconnector.h" - -using namespace metricspp; - -MetricsConnector::MetricsConnector(const std::string &addr, - const std::initializer_list &tags) - : m_data(new MetricsConnectorPrivate()) { - m_data->m_connector = std::make_shared(addr); - - for (auto &value : tags) { - m_data->m_tags.emplace(value); - } -} - -MetricsConnector::~MetricsConnector() {} - -_internal::DataStream MetricsConnector::operator<<(const MetricsModifier &mod) { - auto data = _internal::DataStream(m_data->m_connector); - - data.set_measure(mod.measure()); - data.set_tags(m_data->m_tags); - data.set_values_queue(mod.queue()); - - return data; -} - -_internal::DataStream MetricsConnector::operator<<(const std::string &str) { - return operator<<(MetricsModifier(str, {})); -} - -MetricsConnector::MetricsConnector(const MetricsConnector &cr) - : m_data(new MetricsConnectorPrivate()) { - this->operator=(cr); -} - -MetricsConnector &MetricsConnector::operator=(const MetricsConnector &cr) { - m_data->m_connector = - std::make_shared(*cr.m_data->m_connector); - m_data->m_tags = cr.m_data->m_tags; - - return *this; -} diff --git a/src/connector_p.h b/src/connector_p.h deleted file mode 100644 index 0172c06..0000000 --- a/src/connector_p.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef L_METRICSPP_CONNECTOR_P_H -#define L_METRICSPP_CONNECTOR_P_H - -#include -#include -#include - -#include - -namespace metricspp { - -class NetworkConnector; -class MetricsConnectorPrivate { - public: - MetricsConnectorPrivate() {} - virtual ~MetricsConnectorPrivate() {} - - private: - std::shared_ptr m_connector; ///< Network object pointer - std::map m_tags; ///< Connector tags - - friend class MetricsConnector; -}; -} -#endif // L_METRICSPP_CONNECTOR_P_H diff --git a/src/funcollection.cpp b/src/funcollection.cpp new file mode 100644 index 0000000..52212f6 --- /dev/null +++ b/src/funcollection.cpp @@ -0,0 +1,8 @@ +#include + +#include "funcollection.h" + +bool metricspp::is_var_valid(const std::string &variable) { + static const std::regex tag_regex("^[\\w\\.]+$"); + return std::regex_match(variable, tag_regex); +} diff --git a/src/funcollection.h b/src/funcollection.h new file mode 100644 index 0000000..5c96dfa --- /dev/null +++ b/src/funcollection.h @@ -0,0 +1,32 @@ +#ifndef _METRICSPP_FUNCOLLECTION_H +#define _METRICSPP_FUNCOLLECTION_H + +#include +#include +#include + +namespace metricspp { +/** Check variable function + * + * Verifies if input \a varuable is valid to use + * as a name or a value of measurement. + * + * A value must contains only letters, numbers, dot(.) signes and/or + * underline(_) signes + * + * @param variable input text variable + * @return true if conditions meet + */ +bool is_var_valid(const std::string &variable); + +template +std::string to_string(const T &value) { + std::stringstream out; + out.unsetf(std::ios::floatfield); + out << std::setprecision(10) << value; + + return out.str(); +} +} + +#endif // ifndef _METRICSPP_FUNCOLLECTION_H diff --git a/src/networkconnector.cpp b/src/httpconnector.cpp similarity index 52% rename from src/networkconnector.cpp rename to src/httpconnector.cpp index a393802..f6a71f0 100644 --- a/src/networkconnector.cpp +++ b/src/httpconnector.cpp @@ -1,9 +1,13 @@ -#include "networkconnector.h" +#include + +#include + +#include using namespace metricspp; -NetworkConnector::NetworkConnector(const std::string &addr) - : m_addr(addr), m_handle(NULL) { +HttpConnector::HttpConnector() + : m_error(CURL_ERROR_SIZE, '\0'), m_handle(NULL) { m_handle = curl_easy_init(); if (m_handle) { @@ -14,7 +18,7 @@ NetworkConnector::NetworkConnector(const std::string &addr) curl_easy_setopt(m_handle, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(m_handle, CURLOPT_NOPROGRESS, 1L); curl_easy_setopt(m_handle, CURLOPT_MAXREDIRS, 50L); - curl_easy_setopt(m_handle, CURLOPT_ERRORBUFFER, m_error); + curl_easy_setopt(m_handle, CURLOPT_ERRORBUFFER, m_error.data()); curl_easy_setopt(m_handle, CURLOPT_COOKIEFILE, ""); curl_easy_setopt(m_handle, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt(m_handle, CURLOPT_TIMEOUT_MS, 1000); @@ -26,17 +30,19 @@ NetworkConnector::NetworkConnector(const std::string &addr) #endif #endif #endif - - curl_easy_setopt(m_handle, CURLOPT_URL, m_addr.c_str()); } } -NetworkConnector::~NetworkConnector() { +HttpConnector::HttpConnector(const std::string &addr) : HttpConnector() { + set_address(addr); +} + +HttpConnector::~HttpConnector() { curl_easy_cleanup(m_handle); } -bool NetworkConnector::post(const std::string &data) { - if (m_handle == NULL) { +bool HttpConnector::post(const std::string &data) { + if (m_handle == NULL || m_addr.empty()) { return false; } @@ -48,3 +54,19 @@ bool NetworkConnector::post(const std::string &data) { return false; } + +void HttpConnector::set_address(const std::string &addr) { + const static std::regex rx_url( + "^(?:http(s)?:\\/\\/)?[\\w.-]+(?:\\.[\\w\\.-]+)*[\\w\\-\\._~:/" + "?#[\\]@!\\$&'\\(\\)\\*\\+,;=.]+$"); + if (!std::regex_match(addr, rx_url)) { + throw std::invalid_argument("Input address is not valid"); + } + + m_addr = addr; + curl_easy_setopt(m_handle, CURLOPT_URL, addr.c_str()); +} + +std::string HttpConnector::address() const { + return m_addr; +} diff --git a/src/metric.cpp b/src/metric.cpp new file mode 100644 index 0000000..1a576ce --- /dev/null +++ b/src/metric.cpp @@ -0,0 +1,48 @@ +#include +#include +#include + +#include + +#include "funcollection.h" + +using namespace metricspp; + +MetricsConnector::MetricsConnector( + const std::shared_ptr &connector, + std::initializer_list tags) + : m_connector(connector) { + if (!connector) { + throw std::invalid_argument("Connector pointer is not valid!"); + } + + std::for_each(tags.begin(), tags.end(), [&](const Tag &tag) { + if (is_var_valid(tag.first) && is_var_valid(tag.second) && + m_tags.find(tag.first) == m_tags.end()) { + m_tags.emplace(tag); + } + }); +} + +MetricsConnector::~MetricsConnector() {} + +std::map MetricsConnector::tags() const { + return m_tags; +} + +_internal::DataStream MetricsConnector::operator<<(const MetricsModifier &mod) { + auto data = _internal::DataStream(m_connector); + + data.set_measure(mod.measure()); + data.set_tags(m_tags); + data.set_values_queue(mod.queue()); + + return data; +} + +_internal::DataStream MetricsConnector::operator<<(const std::string &str) { + if (!is_var_valid(str)) { + throw std::invalid_argument("Input measure name is not valid!"); + } + return operator<<(MetricsModifier(str, {})); +} diff --git a/src/metricspp.cpp b/src/metricspp.cpp deleted file mode 100644 index 81cb704..0000000 --- a/src/metricspp.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include - -using namespace metricspp; - -MetricsConnector metricspp::create_connection( - const std::string &addr, const std::initializer_list &tags) { - return MetricsConnector(addr, tags); -} - -MetricsModifier metricspp::create_measurement( - const std::string &name, const std::initializer_list &vnames) { - return MetricsModifier(name, vnames); -} diff --git a/src/modifier.cpp b/src/modifier.cpp index f7b1bd6..44980ce 100644 --- a/src/modifier.cpp +++ b/src/modifier.cpp @@ -1,33 +1,37 @@ +#include + #include -#include "modifier_p.h" +#include "funcollection.h" using namespace metricspp; MetricsModifier::MetricsModifier( const std::string &measure, const std::initializer_list &vqueue) - : m_data(new MetricsModifierPrivate()) { - m_data->m_measure = measure; - m_data->m_queue = vqueue.size() == 0 ? std::list{"value"} - : std::list(vqueue); + : m_measure(measure) { + if (!is_var_valid(measure)) { + throw std::invalid_argument("Invalid measure name"); + } + + for (auto &value : vqueue) { + if (!is_var_valid(value)) { + throw std::invalid_argument("Invalid variable name"); + } + m_queue.emplace_back(value); + } + + if (m_queue.empty()) { + m_queue.emplace_back("value"); + } } MetricsModifier::~MetricsModifier() {} std::string MetricsModifier::measure() const { - return m_data->m_measure; + return m_measure; } std::list MetricsModifier::queue() const { - return m_data->m_queue; -} - -MetricsModifier::MetricsModifier(const MetricsModifier &cr) - : m_data(new MetricsModifierPrivate()) { - operator=(cr); -} - -MetricsModifier &MetricsModifier::operator=(const MetricsModifier &cr) { - *m_data = *cr.m_data; + return m_queue; } diff --git a/src/modifier_p.h b/src/modifier_p.h deleted file mode 100644 index 20d700b..0000000 --- a/src/modifier_p.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef L_METRICSPP_MODIFIER_P_H -#define L_METRICSPP_MODIFIER_P_H - -#include -#include - -namespace metricspp { -class MetricsModifierPrivate { - public: - MetricsModifierPrivate() {} - virtual ~MetricsModifierPrivate() {} - - private: - std::string m_measure; ///< Measure name - std::list m_queue; ///< Measure's variables queue - - friend class MetricsModifier; -}; -} -#endif // L_METRICSPP_MODIFIER_P_H diff --git a/src/networkconnector.h b/src/networkconnector.h deleted file mode 100644 index d2f4491..0000000 --- a/src/networkconnector.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef L_NETWORKCONNECTOR_H -#define L_NETWORKCONNECTOR_H - -#include - -#include - -namespace metricspp { -/** NetworkConnector class - * - * Network connector class objects communicate with a database - * and handle data streams - */ -class NetworkConnector { - public: - /** NetworkConnector constructor - * - * Constructor creates a new object with an \a addr to communicate - * with - * - * @param addr Database address - */ - NetworkConnector(const std::string &addr = "http:127.0.0.1:8080/"); - virtual ~NetworkConnector(); - - /** Post data methid - * - * Sends \a data using post method - * - * @param data String of data for send - * - * @return \a true on sucess operation - */ - bool post(const std::string &data); - - private: - std::string m_addr; ///< Full address for publish data - - CURL *m_handle; ///< Curl handler - char m_error[CURL_ERROR_SIZE]; ///< Curl error buffer -}; -} - -#endif // L_NETWORKCONNECTOR_H diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 89351a3..5bddf65 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -5,12 +5,13 @@ if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} GREATER_EQUAL 3.9) include(GoogleTest) endif() -include_directories(${GTEST_INCLUDE_DIRS}) +include_directories(${CURL_INCLUDE_DIRS}) aux_source_directory(. SOURCE) add_executable(${PROJECT_NAME}.test ${SOURCE}) target_link_libraries(${PROJECT_NAME}.test ${PROJECT_NAME} - ${GTEST_BOTH_LIBRARIES}) + ${GTEST_BOTH_LIBRARIES} + gmock) gtest_add_tests(${PROJECT_NAME}.test "" ${SOURCE}) diff --git a/test/connector_mock.h b/test/connector_mock.h new file mode 100644 index 0000000..2a3360a --- /dev/null +++ b/test/connector_mock.h @@ -0,0 +1,16 @@ +#ifndef _TEST_CONNECTOR_MOCK_H +#define _TEST_CONNECTOR_MOCK_H value + +#include + +#include + +#include + +class ConnectorMock : public metricspp::base::IConnector { + public: + MOCK_METHOD1(set_address, void(const std::string&)); + MOCK_METHOD1(post, bool(const std::string&)); +}; + +#endif // _TEST_CONNECTOR_MOCK_H diff --git a/test/d_stream_test.cpp b/test/d_stream_test.cpp new file mode 100644 index 0000000..997a6cb --- /dev/null +++ b/test/d_stream_test.cpp @@ -0,0 +1,170 @@ +#include +#include + +#include + +#include + +#include "connector_mock.h" + +using ::testing::StartsWith; +using ::testing::_; + +using metricspp::_internal::DataStream; + +class DataStreamTest : public ::testing::Test { + protected: + std::shared_ptr ctr; + std::string measure = "test.0"; + + virtual void SetUp() { + ctr = std::make_shared(); + } + + virtual void TearDown() { + ctr.reset(); + } + + void test_ouput( + const std::function &, + const std::shared_ptr &)> &fn) { + auto connector = std::make_shared(); + auto stream = std::make_shared(connector); + + fn(stream, connector); + } +}; + +TEST_F(DataStreamTest, Constructor) { + EXPECT_NO_THROW(new DataStream(std::make_shared())); + EXPECT_THROW(new DataStream(nullptr), std::invalid_argument); +} + +TEST_F(DataStreamTest, SetTags) { + test_ouput([&](const std::shared_ptr &st, + const std::shared_ptr &cn) { + EXPECT_CALL(*cn, post(StartsWith(measure + " "))); + + st->set_measure(measure); + st->set_values_queue({"value"}); + *st << 1; + }); + + test_ouput([&](const std::shared_ptr &st, + const std::shared_ptr &cn) { + EXPECT_CALL(*cn, post(StartsWith(measure + ",tag0=value0,tag1=value1"))); + st->set_tags({{"tag0", "value0"}, {"tag1", "value1"}}); + + st->set_measure(measure); + st->set_values_queue({"value"}); + *st << 1; + }); + + test_ouput([&](const std::shared_ptr &st, + const std::shared_ptr &cn) { + EXPECT_CALL(*cn, post(StartsWith(measure + ",valid=value0 "))); + st->set_tags({{"valid", "value0"}, + {"ta%##$ag0", "value0"}, + {"tag7", "N*&#vb1"}, + {"tag1", ""}, + {"", "value1"}, + {"valid", "value2"}}); + + st->set_measure(measure); + st->set_values_queue({"value"}); + *st << 1; + }); +} + +TEST_F(DataStreamTest, SetMeasure) { + test_ouput([&](const std::shared_ptr &st, + const std::shared_ptr &cn) { + EXPECT_CALL(*cn, post(StartsWith(measure + " "))); + st->set_measure(measure); + st->set_values_queue({"value"}); + *st << 1; + }); + + test_ouput([&](const std::shared_ptr &st, + const std::shared_ptr &cn) { + EXPECT_CALL(*cn, post(StartsWith(measure + " "))); + st->set_measure("v1"); + st->set_measure("v2"); + st->set_measure(measure); + st->set_values_queue({"value"}); + *st << 1; + }); + + test_ouput([&](const std::shared_ptr &st, + const std::shared_ptr &cn) { + EXPECT_CALL(*cn, post(_)).Times(0); + st->set_values_queue({"value"}); + *st << 1; + }); + + test_ouput([&](const std::shared_ptr &st, + const std::shared_ptr &cn) { + EXPECT_CALL(*cn, post(_)).Times(0); + st->set_measure("N(&98273)(*!@"); + st->set_values_queue({"value"}); + *st << 1; + }); +} + +TEST_F(DataStreamTest, SetQueue) { + test_ouput([&](const std::shared_ptr &st, + const std::shared_ptr &cn) { + EXPECT_CALL(*cn, post(StartsWith(measure + " value=1 "))); + st->set_measure(measure); + st->set_values_queue({"value"}); + *st << 1; + }); + test_ouput([&](const std::shared_ptr &st, + const std::shared_ptr &cn) { + EXPECT_CALL(*cn, post(_)).Times(0); + st->set_measure(measure); + st->set_values_queue({"value"}); + }); + + test_ouput([&](const std::shared_ptr &st, + const std::shared_ptr &cn) { + EXPECT_CALL(*cn, post(StartsWith(measure + " value0=1,value2=5 "))); + st->set_measure(measure); + st->set_values_queue({"value0"}); + st->set_values_queue({"value0", "value2", "value0"}); + *st << 1 << 5 << 4; + }); + + test_ouput([&](const std::shared_ptr &st, + const std::shared_ptr &cn) { + EXPECT_CALL(*cn, post(_)).Times(0); + st->set_measure(measure); + st->set_values_queue({"", "*&^V@C"}); + *st << 1 << 5; + }); + + test_ouput([&](const std::shared_ptr &st, + const std::shared_ptr &cn) { + EXPECT_CALL(*cn, post(_)).Times(0); + st->set_measure(measure); + *st << 1; + }); +} + +TEST_F(DataStreamTest, Insertion) { + test_ouput([&](const std::shared_ptr &st, + const std::shared_ptr &cn) { + EXPECT_CALL( + *cn, + post(StartsWith(measure + " value0=1,value2=5.55,value5=1,value10=-0." + "4441,value12=0,value13=2,value14=3.3," + "value15=-15,value16=20,value17=12345678 "))); + st->set_measure(measure); + st->set_values_queue({"value0", "value2", "value5"}); + *st << 1 << 5.55 << true << "value10" << -0.4441 << "value11" + << "value12" << 0 << "qN" << 5 << "value13" << short(2) + << "value14" << (long double)(3.3) << "value15" << (long int)(-15) + << "value16" << (unsigned)(20) << "value17" + << (long unsigned)(12345678); + }); +} diff --git a/test/httpconnector_test.cpp b/test/httpconnector_test.cpp new file mode 100644 index 0000000..956ed56 --- /dev/null +++ b/test/httpconnector_test.cpp @@ -0,0 +1,25 @@ +#include + +#include + +using metricspp::HttpConnector; + +class HttpConnectorTest : public ::testing::Test {}; + +TEST_F(HttpConnectorTest, Constructor) { + EXPECT_NO_THROW(new HttpConnector()); + EXPECT_NO_THROW(new HttpConnector("http://127.0.0.1/")); + EXPECT_THROW(new HttpConnector(""), std::invalid_argument); + EXPECT_THROW(new HttpConnector("n92n09N&%128"), std::invalid_argument); +} + +TEST_F(HttpConnectorTest, AddressSet) { + HttpConnector ctr; + std::string addr = "http://example.com:544/api"; + + EXPECT_NO_THROW(ctr.set_address(addr)); + EXPECT_EQ(ctr.address(), addr); + + EXPECT_NO_THROW(ctr.set_address("localhost")); + EXPECT_THROW(ctr.set_address(""), std::invalid_argument); +} diff --git a/test/metric_test.cpp b/test/metric_test.cpp new file mode 100644 index 0000000..27e090d --- /dev/null +++ b/test/metric_test.cpp @@ -0,0 +1,67 @@ +#include + +#include +#include + +#include "connector_mock.h" + +class MetricTest : public ::testing::Test { + protected: + std::shared_ptr ctr; + + virtual void SetUp() { + ctr = std::make_shared(); + } + + virtual void TearDown() { + ctr.reset(); + } +}; + +TEST_F(MetricTest, Constructor) { + EXPECT_NO_THROW(std::make_shared(ctr)); + EXPECT_NO_THROW(std::make_shared( + ctr, + std::initializer_list{metricspp::Tag{"name0", "value"}})); + EXPECT_NO_THROW(std::make_shared( + ctr, + std::initializer_list{ + metricspp::Tag{"name0", "value"}, metricspp::Tag{"name1", "value"}, + metricspp::Tag{"name2", "value"}, metricspp::Tag{"name3", "value"}})); + EXPECT_THROW(std::make_shared(nullptr), + std::invalid_argument); +} + +TEST_F(MetricTest, Tags) { + auto mc = std::make_shared(ctr); + auto tags = mc->tags(); + EXPECT_TRUE(tags.empty()); + + mc = std::make_shared( + ctr, std::initializer_list{ + metricspp::Tag{"name.0", "value"}, + metricspp::Tag{"N*&@N&$$", "1n-0N*1\\value"}}); + tags = mc->tags(); + EXPECT_EQ(tags.size(), 1); + EXPECT_EQ(tags["name.0"], "value"); + + mc = std::make_shared( + ctr, + std::initializer_list{metricspp::Tag{"name0", "value"}, + metricspp::Tag{"name0", "value2"}}); + tags = mc->tags(); + + EXPECT_EQ(tags.size(), 1); + EXPECT_EQ(tags["name0"], "value"); +} + +TEST_F(MetricTest, DataInsertion) { + auto mc = std::make_shared(ctr); + + EXPECT_CALL(*ctr, post(::testing::_)).Times(2); + + EXPECT_NO_THROW(mc->operator<<("measure.0") << 1); + EXPECT_NO_THROW(mc->operator<<(metricspp::MetricsModifier("measure.1")) << 1); + EXPECT_THROW(mc->operator<<(""), std::invalid_argument); + EXPECT_THROW(mc->operator<<("qwe=*&^B"), std::invalid_argument); +} diff --git a/test/modifier_test.cpp b/test/modifier_test.cpp new file mode 100644 index 0000000..cb7509c --- /dev/null +++ b/test/modifier_test.cpp @@ -0,0 +1,31 @@ +#include + +#include + +using metricspp::MetricsModifier; + +class ModifierTest : public ::testing::Test {}; + +TEST_F(ModifierTest, Constructor) { + EXPECT_NO_THROW(new MetricsModifier("data0", {"d0", "d1"})); + EXPECT_NO_THROW(new MetricsModifier("data0")); + EXPECT_THROW(new MetricsModifier(""), std::invalid_argument); + EXPECT_THROW(new MetricsModifier("N(*&@#NN"), std::invalid_argument); + EXPECT_THROW(new MetricsModifier("data0", {"s(*&NV"}), std::invalid_argument); +} + +TEST_F(ModifierTest, Measure) { + auto mod = MetricsModifier("data0"); + + EXPECT_EQ("data0", mod.measure()); +} + +TEST_F(ModifierTest, Queue) { + std::initializer_list queue = {"d0", "d1", "d3", "d0"}; + auto mod = MetricsModifier("data0", queue); + + EXPECT_EQ(mod.queue(), std::list(queue)); + + mod = MetricsModifier("data0"); + EXPECT_EQ(mod.queue(), std::list{"value"}); +}