From bb7c7af5b80530fae457aecefec1e6033a9a7e8a Mon Sep 17 00:00:00 2001 From: Adam Wegrzynek Date: Thu, 28 Mar 2019 13:39:50 +0100 Subject: [PATCH] Init commit --- .gitignore | 2 + CMakeLists.txt | 180 ++++++++++++++++++++++++++++++++++ README.md | 17 ++++ cmake/FindCURL.cmake | 67 +++++++++++++ cmake/InfluxDBConfig.cmake.in | 15 +++ include/InfluxDB.h | 72 ++++++++++++++ include/InfluxDBFactory.h | 37 +++++++ include/Point.h | 56 +++++++++++ include/Transport.h | 32 ++++++ src/HTTP.cxx | 62 ++++++++++++ src/HTTP.h | 45 +++++++++ src/InfluxDB.cxx | 61 ++++++++++++ src/InfluxDBFactory.cxx | 59 +++++++++++ src/Point.cxx | 69 +++++++++++++ src/UDP.cxx | 28 ++++++ src/UDP.h | 49 +++++++++ src/UnixSocket.cxx | 27 +++++ src/UnixSocket.h | 45 +++++++++ src/UriParser.h | 101 +++++++++++++++++++ test/testFactory.cxx | 20 ++++ 20 files changed, 1044 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/FindCURL.cmake create mode 100644 cmake/InfluxDBConfig.cmake.in create mode 100644 include/InfluxDB.h create mode 100644 include/InfluxDBFactory.h create mode 100644 include/Point.h create mode 100644 include/Transport.h create mode 100644 src/HTTP.cxx create mode 100644 src/HTTP.h create mode 100644 src/InfluxDB.cxx create mode 100644 src/InfluxDBFactory.cxx create mode 100644 src/Point.cxx create mode 100644 src/UDP.cxx create mode 100644 src/UDP.h create mode 100644 src/UnixSocket.cxx create mode 100644 src/UnixSocket.h create mode 100644 src/UriParser.h create mode 100644 test/testFactory.cxx diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7f1aabf6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/build/ +*~ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..938783db --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,180 @@ +#################################### +# General project definition +#################################### + +CMAKE_MINIMUM_REQUIRED(VERSION 3.5.2 FATAL_ERROR) + +# Set cmake policy by version: https://cmake.org/cmake/help/latest/manual/cmake-policies.7.html +if(${CMAKE_VERSION} VERSION_LESS 3.12) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.12) +endif() + +# Define project +project(InfluxDB-Client + VERSION 0.0.1 + DESCRIPTION "InfluxDB C++ client library" + LANGUAGES CXX +) + +# Documentation dir +#add_subdirectory(doc) + +# Add compiler flags for warnings and debug symbols +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wextra") + +# Set fPIC for all targets +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# Set CMAKE_INSTALL_LIBDIR explicitly to lib (to avoid lib64 on CC7) +set(CMAKE_INSTALL_LIBDIR lib) + +# Set the default build type to "RelWithDebInfo" +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "RelWithDebInfo" + CACHE + STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel Coverage." + FORCE + ) +endif() + +# Add coverage flags to Debug build +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 --coverage") +endif() + +#################################### +# Dependencies +#################################### + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") + +find_package(Boost REQUIRED COMPONENTS unit_test_framework system) +find_package(CURL REQUIRED MODULE) + + +#################################### +# Library +#################################### + +set(LIBRARY_OUTPUT_PATH "${CMAKE_BINARY_DIR}/lib") +set(EXECUTABLE_OUTPUT_PATH "${CMAKE_BINARY_DIR}/bin") +set(INCLUDE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/include") + +set(SRCS + src/InfluxDB.cxx + src/Point.cxx + src/InfluxDBFactory.cxx + src/UDP.cxx + src/HTTP.cxx + src/UnixSocket.cxx +) + +# Create library +add_library(InfluxDB SHARED ${SRCS}) +target_include_directories(InfluxDB + PUBLIC + $ + $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +# Link targets +target_link_libraries(InfluxDB + PUBLIC + Boost::boost + PRIVATE + Boost::system + CURL::CURL +) + +# Use C++17 +target_compile_features(InfluxDB PUBLIC cxx_std_17) + + +#################################### +# Tests +#################################### + +enable_testing() + +set(TEST_SRCS + test/testFactory.cxx +) + +foreach (test ${TEST_SRCS}) + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} + PRIVATE + InfluxDB Boost::unit_test_framework + ) + add_test(NAME ${test_name} COMMAND ${test_name}) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 60) +endforeach() + + +#################################### +# Install +#################################### + +include(GNUInstallDirs) + +# Build targets with install rpath on Mac to dramatically speed up installation +# https://gitlab.kitware.com/cmake/community/wikis/doc/cmake/RPATH-handling +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}" isSystemDir) +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + if("${isSystemDir}" STREQUAL "-1") + set(CMAKE_INSTALL_RPATH "@loader_path/../${CMAKE_INSTALL_LIBDIR}") + endif() + set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) +endif() +unset(isSystemDir) + +# Install library +install(TARGETS InfluxDB + EXPORT InfluxDBTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +# Create version file +include(CMakePackageConfigHelpers) +write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/cmake/InfluxDBConfigVersion.cmake" + VERSION ${PACKAGE_VERSION} + COMPATIBILITY AnyNewerVersion +) + +# Install headers +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/InfluxDB DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") + +# Export targets +install(EXPORT InfluxDBTargets + FILE + InfluxDBTargets.cmake + NAMESPACE + InfluxData:: + DESTINATION + ${CMAKE_INSTALL_LIBDIR}/cmake/InfluxDB +) + +# Configure and install Config files +configure_package_config_file( + cmake/InfluxDBConfig.cmake.in cmake/InfluxDBConfig.cmake + INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/InfluxDB" + PATH_VARS CMAKE_INSTALL_PREFIX +) + +install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/cmake/InfluxDBConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/InfluxDBConfigVersion.cmake" + DESTINATION + ${CMAKE_INSTALL_LIBDIR}/cmake/InfluxDB +) diff --git a/README.md b/README.md new file mode 100644 index 00000000..21c76111 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# influxdb-cxx +InfluxDB C++ client library + +## Installation + +1. Requirements: boost::asio and cURL +2. `mkdir build; cd build` +3. `cmake ..; make -j` + +## Sample usage +```cpp +auto influxdb = influxdb::InfluxDBFactory::Get("udp://127.0.0.1:1234"); +influxdb->write(Point{"test"} + .addField("value", 10) + .addTag("host", "localhost") +); +``` diff --git a/cmake/FindCURL.cmake b/cmake/FindCURL.cmake new file mode 100644 index 00000000..c102f36b --- /dev/null +++ b/cmake/FindCURL.cmake @@ -0,0 +1,67 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#.rst: +# FindCURL +# -------- +# +# Find curl +# +# IMPORTED Targets +# ^^^^^^^^^^^^^^^^ +# +# This module defines :prop_tgt:`IMPORTED` target ``CURL::CURL``, if +# curl has been found. +# +# Find the native CURL headers and libraries. +# +# :: +# +# CURL_INCLUDE_DIRS - where to find curl/curl.h, etc. +# CURL_LIBRARIES - List of libraries when using curl. +# CURL_FOUND - True if curl found. +# CURL_VERSION_STRING - the version of curl found (since CMake 2.8.8) + +# Look for the header file. +find_path(CURL_INCLUDE_DIR NAMES curl/curl.h) +mark_as_advanced(CURL_INCLUDE_DIR) + +# Look for the library (sorted from most current/relevant entry to least). +find_library(CURL_LIBRARY NAMES + curl + # Windows MSVC prebuilts: + curllib + libcurl_imp + curllib_static + # Windows older "Win32 - MSVC" prebuilts (libcurl.lib, e.g. libcurl-7.15.5-win32-msvc.zip): + libcurl +) +mark_as_advanced(CURL_LIBRARY) + +if(CURL_INCLUDE_DIR) + foreach(_curl_version_header curlver.h curl.h) + if(EXISTS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}") + file(STRINGS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}" curl_version_str REGEX "^#define[\t ]+LIBCURL_VERSION[\t ]+\".*\"") + + string(REGEX REPLACE "^#define[\t ]+LIBCURL_VERSION[\t ]+\"([^\"]*)\".*" "\\1" CURL_VERSION_STRING "${curl_version_str}") + unset(curl_version_str) + break() + endif() + endforeach() +endif() + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(CURL + REQUIRED_VARS CURL_LIBRARY CURL_INCLUDE_DIR + VERSION_VAR CURL_VERSION_STRING) + +if(CURL_FOUND) + set(CURL_LIBRARIES ${CURL_LIBRARY}) + set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR}) + + if(NOT TARGET CURL::CURL) + add_library(CURL::CURL UNKNOWN IMPORTED) + set_target_properties(CURL::CURL PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${CURL_INCLUDE_DIRS}") + set_property(TARGET CURL::CURL APPEND PROPERTY IMPORTED_LOCATION "${CURL_LIBRARY}") + endif() +endif() diff --git a/cmake/InfluxDBConfig.cmake.in b/cmake/InfluxDBConfig.cmake.in new file mode 100644 index 00000000..37b525bd --- /dev/null +++ b/cmake/InfluxDBConfig.cmake.in @@ -0,0 +1,15 @@ +@PACKAGE_INIT@ + +set(InfluxDB_VERSION @PROJECT_VERSION@) + +get_filename_component(InfluxDB_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) +include(CMakeFindDependencyMacro) + +find_dependency(Boost) +find_dependency(CURL) + +if(NOT TARGET InfluxData::InfluxDB) + include("${InfluxDB_CMAKE_DIR}/InfluxDBTargets.cmake") +endif() + +message(STATUS "InfluxDB ${InfluxDB_VERSION} found") diff --git a/include/InfluxDB.h b/include/InfluxDB.h new file mode 100644 index 00000000..86698005 --- /dev/null +++ b/include/InfluxDB.h @@ -0,0 +1,72 @@ +/// +/// \author Adam Wegrzynek +/// + +#ifndef INFLUXDATA_INFLUXDB_H +#define INFLUXDATA_INFLUXDB_H + +#include +#include +#include +#include + +#include "Transport.h" +#include "Point.h" + +namespace influxdb +{ + +class InfluxDB +{ + public: + /// Disable copy constructor + InfluxDB & operator=(const InfluxDB&) = delete; + + /// Disable copy constructor + InfluxDB(const InfluxDB&) = delete; + + /// Constructor required valid transport + InfluxDB(std::unique_ptr transport); + + /// Flushes buffer + ~InfluxDB(); + + /// Writes a metric + /// \param metric + void write(Point&& metric); + + /// Flushes metric buffer (this can also happens when buffer is full) + void flushBuffer(); + + /// Enables metric buffering + /// \param size + void enableBuffering(const std::size_t size = 32); + + /// Adds a global tag + /// \param name + /// \param value + void addGlobalTag(std::string_view name, std::string_view value); + + private: + /// Buffer for points + std::deque mBuffer; + + /// Flag stating whether point buffering is enabled + bool mBuffering; + + /// Buffer size + std::size_t mBufferSize; + + /// Underlying transport UDP/HTTP/Unix socket + std::unique_ptr mTransport; + + /// Transmits string over transport + void transmit(std::string&& point); + + /// List of global tags + std::string mGlobalTags; +}; + +} // namespace influxdb + +#endif // INFLUXDATA_INFLUXDB_H diff --git a/include/InfluxDBFactory.h b/include/InfluxDBFactory.h new file mode 100644 index 00000000..af0bb95d --- /dev/null +++ b/include/InfluxDBFactory.h @@ -0,0 +1,37 @@ +/// +/// \author Adam Wegrzynek +/// + +#ifndef INFLUXDATA_INFLUXDB_FACTORY_H +#define INFLUXDATA_INFLUXDB_FACTORY_H + +#include "InfluxDB.h" +#include "Transport.h" + +namespace influxdb +{ + +/// \brief InfluxDB factory +class InfluxDBFactory +{ + public: + /// Disables copy constructor + InfluxDBFactory & operator=(const InfluxDBFactory&) = delete; + + /// Disables copy constructor + InfluxDBFactory(const InfluxDBFactory&) = delete; + + /// InfluxDB factory + static std::unique_ptr Get(std::string url) noexcept(false); + + private: + ///\return backend based on provided URL + static std::unique_ptr GetTransport(std::string url); + + /// Private constructor disallows to create instance of Factory + InfluxDBFactory() = default; +}; + +} // namespace influxdb + +#endif // INFLUXDATA_INFLUXDB_FACTORY_H diff --git a/include/Point.h b/include/Point.h new file mode 100644 index 00000000..596694d5 --- /dev/null +++ b/include/Point.h @@ -0,0 +1,56 @@ +/// +/// \author Adam Wegrzynek +/// + +#ifndef INFLUXDATA_POINT_H +#define INFLUXDATA_POINT_H + +#include +#include +#include + +namespace influxdb +{ + +/// \brief Represents a point +class Point +{ + public: + /// Constructs point based on measurement name + Point(const std::string& measurement); + + /// Default destructor + ~Point() = default; + + /// Adds a tags + Point&& addTag(std::string_view key, std::string_view value); + + /// Adds filed + Point&& addField(std::string_view name, std::variant value); + + /// Generetes current timestamp + static auto getCurrentTimestamp() -> decltype(std::chrono::system_clock::now()); + + /// Converts point to Influx Line Protocol + std::string toLineProtocol(); + + protected: + /// A value + std::variant mValue; + + /// A name + std::string mMeasurement; + + /// A timestamp + std::chrono::time_point mTimestamp; + + /// Tags + std::string mTags; + + /// Fields + std::string mFields; +}; + +} // namespace influxdb + +#endif // INFLUXDATA_POINT_H diff --git a/include/Transport.h b/include/Transport.h new file mode 100644 index 00000000..48cbbae7 --- /dev/null +++ b/include/Transport.h @@ -0,0 +1,32 @@ +/// +/// \author Adam Wegrzynek +/// + +#ifndef INFLUXDATA_TRANSPORTINTERFACE_H +#define INFLUXDATA_TRANSPORTINTERFACE_H + +#include + +namespace influxdb +{ + +/// \brief Transport interface +class Transport +{ + public: + Transport() = default; + + virtual ~Transport() = default; + + /// Sends string blob + virtual void send(std::string&& message) = 0; + + /// Sends s request + virtual void query(const std::string& query) { + throw std::runtime_error("Queries are not supported in the selected transport"); + } +}; + +} // namespace influxdb + +#endif // INFLUXDATA_TRANSPORTINTERFACE_H diff --git a/src/HTTP.cxx b/src/HTTP.cxx new file mode 100644 index 00000000..54a67e80 --- /dev/null +++ b/src/HTTP.cxx @@ -0,0 +1,62 @@ +/// +/// \author Adam Wegrzynek +/// + +#include "HTTP.h" +#include +#include + +namespace influxdb +{ +namespace transports +{ + +HTTP::HTTP(const std::string& url) : + curlHandle(initCurl(url), &HTTP::deleteCurl) +{ +} + +CURL* HTTP::initCurl(const std::string& url) +{ + CURLcode globalInitResult = curl_global_init(CURL_GLOBAL_ALL); + if (globalInitResult != CURLE_OK) { + throw std::runtime_error(std::string("cURL init") + curl_easy_strerror(globalInitResult)); + } + + CURL *curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10); + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 120L); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 60L); + FILE *devnull = fopen("/dev/null", "w+"); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, devnull); + return curl; +} + +void HTTP::deleteCurl(CURL * curl) +{ + curl_easy_cleanup(curl); + curl_global_cleanup(); +} + +void HTTP::send(std::string&& post) +{ + CURLcode response; + long responseCode; + curl_easy_setopt(curlHandle.get(), CURLOPT_POSTFIELDS, post.c_str()); + curl_easy_setopt(curlHandle.get(), CURLOPT_POSTFIELDSIZE, (long) post.length()); + response = curl_easy_perform(curlHandle.get()); + curl_easy_getinfo(curlHandle.get(), CURLINFO_RESPONSE_CODE, &responseCode); + if (response != CURLE_OK) { + throw std::runtime_error(curl_easy_strerror(response)); + } + if (responseCode < 200 || responseCode > 206) { + throw std::runtime_error("Response code : " + std::to_string(responseCode)); + } +} + +} // namespace transports +} // namespace influxdb diff --git a/src/HTTP.h b/src/HTTP.h new file mode 100644 index 00000000..17460776 --- /dev/null +++ b/src/HTTP.h @@ -0,0 +1,45 @@ +/// +/// \author Adam Wegrzynek +/// + +#ifndef INFLUXDATA_TRANSPORTS_HTTP_H +#define INFLUXDATA_TRANSPORTS_HTTP_H + +#include "Transport.h" +#include +#include +#include + +namespace influxdb +{ +namespace transports +{ + +/// \brief HTTP transport +class HTTP : public Transport +{ + public: + /// Constructor + HTTP(const std::string& url); + + /// Default destructor + ~HTTP() = default; + + /// Sends point via HTTP POST + void send(std::string&& post); + + private: + /// Custom deleter of CURL object + static void deleteCurl(CURL * curl); + + /// Initilizes CURL and all common options + CURL* initCurl(const std::string& url); + + /// CURL smart pointer with custom deleter + std::unique_ptr curlHandle; +}; + +} // namespace transports +} // namespace influxdb + +#endif // INFLUXDATA_TRANSPORTS_HTTP_H diff --git a/src/InfluxDB.cxx b/src/InfluxDB.cxx new file mode 100644 index 00000000..8bec2f92 --- /dev/null +++ b/src/InfluxDB.cxx @@ -0,0 +1,61 @@ +/// +/// \author Adam Wegrzynek +/// + +#include "InfluxDB.h" + +#include +#include + +namespace influxdb +{ + +InfluxDB::InfluxDB(std::unique_ptr transport) : + mTransport(std::move(transport)) +{ +} + +void InfluxDB::enableBuffering(const std::size_t size) +{ + mBufferSize = size; + mBuffering = true; +} + +void InfluxDB::flushBuffer() { + if (!mBuffering) { + return; + } + for (auto&& point : mBuffer) { + transmit(std::move(point)); + } +} + +void InfluxDB::addGlobalTag(std::string_view key, std::string_view value) +{ + if (!mGlobalTags.empty()) mGlobalTags += ","; + mGlobalTags += key; + mGlobalTags += "="; + mGlobalTags += value; +} + +InfluxDB::~InfluxDB() +{ + if (mBuffering) { + flushBuffer(); + } +} + +void InfluxDB::transmit(std::string&& point) +{ + mTransport->send(std::move(point)); +} + +void InfluxDB::write(Point&& metric) +{ + if (mBuffering) { + mBuffer.emplace_back(metric.toLineProtocol()); + } else { + transmit(metric.toLineProtocol()); + } +} +} // namespace influxdb diff --git a/src/InfluxDBFactory.cxx b/src/InfluxDBFactory.cxx new file mode 100644 index 00000000..54f42e64 --- /dev/null +++ b/src/InfluxDBFactory.cxx @@ -0,0 +1,59 @@ +/// +/// \author Adam Wegrzynek +/// + +#include "InfluxDBFactory.h" +#include +#include +#include +#include "UriParser.h" +#include "HTTP.h" +#include "UDP.h" +#include "UnixSocket.h" + +namespace influxdb +{ + +std::unique_ptr withUdpTransport(const http::url& uri) { + return std::make_unique(uri.host, uri.port); +} + +std::unique_ptr withHttpTransport(const http::url& uri) { + return std::make_unique("http://" + uri.host + ":" + std::to_string(uri.port)); +} + +std::unique_ptr withHttpsTransport(const http::url& uri) { + return std::make_unique("https://" + uri.host + ":" + std::to_string(uri.port)); +} + +std::unique_ptr withUnixSocketTransport(const http::url& uri) { + return std::make_unique(uri.path); +} + +std::unique_ptr InfluxDBFactory::GetTransport(std::string url) { + static const std::map(const http::url&)>> map = { + {"udp", withUdpTransport}, + {"http", withHttpTransport}, + {"http", withHttpsTransport}, + {"unix", withUnixSocketTransport}, + }; + + http::url parsedUrl = http::ParseHttpUrl(url); + if (parsedUrl.protocol.empty()) { + throw std::runtime_error("Ill-formed URI"); + } + + auto iterator = map.find(parsedUrl.protocol); + if (iterator == map.end()) { + throw std::runtime_error("Unrecognized backend " + parsedUrl.protocol); + } + + return iterator->second(parsedUrl); +} + +std::unique_ptr InfluxDBFactory::Get(std::string url) +{ + return std::make_unique(InfluxDBFactory::GetTransport(url)); +} + +} // namespace influxdb diff --git a/src/Point.cxx b/src/Point.cxx new file mode 100644 index 00000000..b8ce0179 --- /dev/null +++ b/src/Point.cxx @@ -0,0 +1,69 @@ +/// +/// \author Adam Wegrzynek +/// + +#include "Point.h" + +#include +#include +#include +#include + +namespace influxdb +{ + +/* +int Point::getType() const +{ + if (std::holds_alternative(mValue)) return 0; + else if (std::holds_alternative(mValue)) return 1; + else if (std::holds_alternative(mValue)) return 2; + else return 3; +} +*/ + +template struct overloaded : Ts... { using Ts::operator()...; }; +template overloaded(Ts...) -> overloaded; + +Point::Point(const std::string& measurement) : + mMeasurement(measurement), mTimestamp(Point::getCurrentTimestamp()) +{ +} + +Point&& Point::addField(std::string_view name, std::variant value) +{ + std::stringstream convert; + if (!mFields.empty()) convert << ","; + + convert << name << "="; + std::visit(overloaded { + [&convert](int value) { convert << value << 'i'; }, + [&convert](double value) { convert << value; }, + [&convert](const std::string& value) { convert << '"' << value << '"'; }, + }, value); + mFields += convert.str(); + return std::move(*this); +} + +Point&& Point::addTag(std::string_view key, std::string_view value) +{ + mTags += ","; + mTags += key; + mTags += "="; + mTags += value; + return std::move(*this); +} + +auto Point::getCurrentTimestamp() -> decltype(std::chrono::system_clock::now()) +{ + return std::chrono::system_clock::now(); +} + +std::string Point::toLineProtocol() +{ + return mMeasurement + mTags + " " + mFields + " " + std::to_string( + std::chrono::duration_cast (mTimestamp.time_since_epoch()).count() + ); +} + +} // namespace influxdb diff --git a/src/UDP.cxx b/src/UDP.cxx new file mode 100644 index 00000000..0c69da7c --- /dev/null +++ b/src/UDP.cxx @@ -0,0 +1,28 @@ +/// +/// \author Adam Wegrzynek +/// + +#include "UDP.h" +#include + +namespace influxdb +{ +namespace transports +{ + +UDP::UDP(const std::string &hostname, int port) : + mSocket(mIoService, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 0)) +{ + boost::asio::ip::udp::resolver resolver(mIoService); + boost::asio::ip::udp::resolver::query query(boost::asio::ip::udp::v4(), hostname, std::to_string(port)); + boost::asio::ip::udp::resolver::iterator resolverInerator = resolver.resolve(query); + mEndpoint = *resolverInerator; +} + +void UDP::send(std::string&& message) +{ + mSocket.send_to(boost::asio::buffer(message, message.size()), mEndpoint); +} + +} // namespace transports +} // namespace influxdb diff --git a/src/UDP.h b/src/UDP.h new file mode 100644 index 00000000..01cb76c5 --- /dev/null +++ b/src/UDP.h @@ -0,0 +1,49 @@ +/// +/// \author Adam Wegrzynek +/// + +#ifndef INFLUXDATA_TRANSPORTS_UDP_H +#define INFLUXDATA_TRANSPORTS_UDP_H + +#include "Transport.h" + +#include +#include +#include +#include +#include + +namespace influxdb +{ +namespace transports +{ + +/// \brief UDP transport +class UDP : public Transport +{ + public: + /// Constructor + UDP(const std::string &hostname, int port); + + /// Default destructor + ~UDP() = default; + + /// Sends blob via UDP + void send(std::string&& message) override; + + private: + /// Boost Asio I/O functionality + boost::asio::io_service mIoService; + + /// UDP socket + boost::asio::ip::udp::socket mSocket; + + /// UDP endpoint + boost::asio::ip::udp::endpoint mEndpoint; + +}; + +} // namespace transports +} // namespace influxdb + +#endif // INFLUXDATA_TRANSPORTS_UDP_H diff --git a/src/UnixSocket.cxx b/src/UnixSocket.cxx new file mode 100644 index 00000000..7194498b --- /dev/null +++ b/src/UnixSocket.cxx @@ -0,0 +1,27 @@ +/// +/// \author Adam Wegrzynek +/// + +#include "UnixSocket.h" +#include + +namespace influxdb +{ + +namespace transports +{ +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) +UnixSocket::UnixSocket(const std::string &socketPath) : + mSocket(mIoService), mEndpoint(socketPath) +{ + mSocket.open(); +} + +void UnixSocket::send(std::string&& message) +{ + mSocket.send_to(boost::asio::buffer(message, message.size()), mEndpoint); +} +#endif // defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + +} // namespace transports +} // namespace influxdb diff --git a/src/UnixSocket.h b/src/UnixSocket.h new file mode 100644 index 00000000..acaf90c6 --- /dev/null +++ b/src/UnixSocket.h @@ -0,0 +1,45 @@ +/// +/// \author Adam Wegrzynek +/// + +#ifndef INFLUXDATA_TRANSPORTS_UNIX_H +#define INFLUXDATA_TRANSPORTS_UNIX_H + +#include "Transport.h" + +#include +#include + +namespace influxdb +{ +namespace transports +{ + +/// \brief Unix datagram socket transport +class UnixSocket : public Transport +{ + public: + UnixSocket(const std::string &socketPath); + + /// Default destructor + ~UnixSocket() = default; + + /// \param message r-value string formated + void send(std::string&& message) override; + + private: + /// Boost Asio I/O functionality + boost::asio::io_service mIoService; +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + /// Unix socket + boost::asio::local::datagram_protocol::socket mSocket; + + /// Unix endpoint + boost::asio::local::datagram_protocol::endpoint mEndpoint; +#endif // defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) +}; + +} // namespace transports +} // namespace influxdb + +#endif // INFLUXDATA_TRANSPORTS_UNIX_H diff --git a/src/UriParser.h b/src/UriParser.h new file mode 100644 index 00000000..8664f2a3 --- /dev/null +++ b/src/UriParser.h @@ -0,0 +1,101 @@ +/* +Copyright (c) 2013 Covenant Eyes + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be included in all copies +or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef INFLUXDATA_HTTPPARSER_H +#define INFLUXDATA_HTTPPARSER_H + +#include +#include +#include + + +namespace http { + struct url { + std::string protocol, user, password, host, path, search; + int port; + }; + + + //--- Helper Functions -------------------------------------------------------------~ + static inline std::string TailSlice(std::string &subject, std::string delimiter, bool keep_delim=false) { + // Chops off the delimiter and everything that follows (destructively) + // returns everything after the delimiter + auto delimiter_location = subject.find(delimiter); + auto delimiter_length = delimiter.length(); + std::string output = ""; + + if (delimiter_location < std::string::npos) { + auto start = keep_delim ? delimiter_location : delimiter_location + delimiter_length; + auto end = subject.length() - start; + output = subject.substr(start, end); + subject = subject.substr(0, delimiter_location); + } + return output; + } + + static inline std::string HeadSlice(std::string &subject, std::string delimiter) { + // Chops off the delimiter and everything that precedes (destructively) + // returns everthing before the delimeter + auto delimiter_location = subject.find(delimiter); + auto delimiter_length = delimiter.length(); + std::string output = ""; + if (delimiter_location < std::string::npos) { + output = subject.substr(0, delimiter_location); + subject = subject.substr(delimiter_location + delimiter_length, subject.length() - (delimiter_location + delimiter_length)); + } + return output; + } + + + //--- Extractors -------------------------------------------------------------------~ + static inline int ExtractPort(std::string &hostport) { + int port; + std::string portstring = TailSlice(hostport, ":"); + try { port = atoi(portstring.c_str()); } + catch (std::exception &e) { port = -1; } + return port; + } + + static inline std::string ExtractPath(std::string &in) { return TailSlice(in, "/", true); } + static inline std::string ExtractProtocol(std::string &in) { return HeadSlice(in, "://"); } + static inline std::string ExtractSearch(std::string &in) { return TailSlice(in, "?"); } + static inline std::string ExtractPassword(std::string &userpass) { return TailSlice(userpass, ":"); } + static inline std::string ExtractUserpass(std::string &in) { return HeadSlice(in, "@"); } + + + //--- Public Interface -------------------------------------------------------------~ + static inline url ParseHttpUrl(std::string &in) { + url ret; + ret.port = -1; + + ret.protocol = ExtractProtocol(in); + ret.search = ExtractSearch(in); + ret.path = ExtractPath(in); + std::string userpass = ExtractUserpass(in); + ret.password = ExtractPassword(userpass); + ret.user = userpass; + ret.port = ExtractPort(in); + ret.host = in; + + return ret; + } +} +#endif diff --git a/test/testFactory.cxx b/test/testFactory.cxx new file mode 100644 index 00000000..bed900c3 --- /dev/null +++ b/test/testFactory.cxx @@ -0,0 +1,20 @@ +#define BOOST_TEST_MODULE Test InfluxDB Factory +#define BOOST_TEST_DYN_LINK +#include + +#include "../include/InfluxDBFactory.h" + +namespace influxdb { +namespace test { + +BOOST_AUTO_TEST_CASE(test) +{ + auto influxdb = influxdb::InfluxDBFactory::Get("udp://127.0.0.1:1234"); + influxdb->write(Point{"test"} + .addField("value", 10) + .addTag("host", "pcaldadam") + ); +} + +} // namespace test +} // namespace influxdb