diff --git a/controller/ConnectionPool.hpp b/controller/ConnectionPool.hpp index 74672eb43..97a20639b 100644 --- a/controller/ConnectionPool.hpp +++ b/controller/ConnectionPool.hpp @@ -19,6 +19,8 @@ #define _DEBUG(x) #endif +#include "../node/Metrics.hpp" + #include #include #include @@ -61,6 +63,7 @@ public: { while(m_pool.size() < m_minPoolSize){ m_pool.push_back(m_factory->create()); + Metrics::pool_avail++; } }; @@ -91,6 +94,7 @@ public: while((m_pool.size() + m_borrowed.size()) < m_minPoolSize) { std::shared_ptr conn = m_factory->create(); m_pool.push_back(conn); + Metrics::pool_avail++; } if(m_pool.size()==0){ @@ -99,8 +103,10 @@ public: try { std::shared_ptr conn = m_factory->create(); m_borrowed.insert(conn); + Metrics::pool_in_use++; return std::static_pointer_cast(conn); } catch (std::exception &e) { + Metrics::pool_errors++; throw ConnectionUnavailable(); } } else { @@ -116,11 +122,13 @@ public: return std::static_pointer_cast(conn); } catch(std::exception& e) { // Error creating a replacement connection + Metrics::pool_errors++; throw ConnectionUnavailable(); } } } // Nothing available + Metrics::pool_errors++; throw ConnectionUnavailable(); } } @@ -128,8 +136,10 @@ public: // Take one off the front std::shared_ptr conn = m_pool.front(); m_pool.pop_front(); + Metrics::pool_avail--; // Add it to the borrowed list m_borrowed.insert(conn); + Metrics::pool_in_use++; return std::static_pointer_cast(conn); }; @@ -143,7 +153,9 @@ public: // Lock std::unique_lock lock(m_poolMutex); m_borrowed.erase(conn); + Metrics::pool_in_use--; if ((m_pool.size() + m_borrowed.size()) < m_maxPoolSize) { + Metrics::pool_avail++; m_pool.push_back(conn); } }; @@ -158,4 +170,4 @@ protected: } -#endif \ No newline at end of file +#endif diff --git a/controller/DB.cpp b/controller/DB.cpp index 2edcadbbe..1de2fbe8b 100644 --- a/controller/DB.cpp +++ b/controller/DB.cpp @@ -13,6 +13,7 @@ #include "DB.hpp" #include "EmbeddedNetworkController.hpp" +#include "../node/Metrics.hpp" #include #include @@ -211,16 +212,19 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no { std::lock_guard l(_networks_l); auto nw2 = _networks.find(networkId); - if (nw2 != _networks.end()) + if (nw2 != _networks.end()) { nw = nw2->second; + } } if (nw) { std::lock_guard l(nw->lock); - if (OSUtils::jsonBool(old["activeBridge"],false)) + if (OSUtils::jsonBool(old["activeBridge"],false)) { nw->activeBridgeMembers.erase(memberId); + } wasAuth = OSUtils::jsonBool(old["authorized"],false); - if (wasAuth) + if (wasAuth) { nw->authorizedMembers.erase(memberId); + } json &ips = old["ipAssignments"]; if (ips.is_array()) { for(unsigned long i=0;imembers[memberId] = memberConfig; - if (OSUtils::jsonBool(memberConfig["activeBridge"],false)) + if (OSUtils::jsonBool(memberConfig["activeBridge"],false)) { nw->activeBridgeMembers.insert(memberId); + } isAuth = OSUtils::jsonBool(memberConfig["authorized"],false); - if (isAuth) + if (isAuth) { + Metrics::member_auths++; nw->authorizedMembers.insert(memberId); + } json &ips = memberConfig["ipAssignments"]; if (ips.is_array()) { for(unsigned long i=0;i ll(_changeListeners_l); for(auto i=_changeListeners.begin();i!=_changeListeners.end();++i) { @@ -313,6 +338,16 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool notifyListeners) { + if (notifyListeners) { + if (old.is_object() && old.contains("id") && networkConfig.is_object() && networkConfig.contains("id")) { + Metrics::network_changes++; + } else if (!old.is_object() && networkConfig.is_object() && networkConfig.contains("id")) { + Metrics::network_count++; + } else if (old.is_object() && old.contains("id") && !networkConfig.is_object()) { + Metrics::network_count--; + } + } + if (networkConfig.is_object()) { const std::string ids = networkConfig["id"]; const uint64_t networkId = Utils::hexStrToU64(ids.c_str()); diff --git a/controller/DB.hpp b/controller/DB.hpp index 64bd83af0..f70d66e03 100644 --- a/controller/DB.hpp +++ b/controller/DB.hpp @@ -35,6 +35,8 @@ #include +#include + #define ZT_MEMBER_AUTH_TIMEOUT_NOTIFY_BEFORE 25000 namespace ZeroTier diff --git a/controller/FileDB.cpp b/controller/FileDB.cpp index d454e93e1..0ced1226a 100644 --- a/controller/FileDB.cpp +++ b/controller/FileDB.cpp @@ -13,6 +13,8 @@ #include "FileDB.hpp" +#include "../node/Metrics.hpp" + namespace ZeroTier { @@ -39,6 +41,7 @@ FileDB::FileDB(const char *path) : if (nwids.length() == 16) { nlohmann::json nullJson; _networkChanged(nullJson,network,false); + Metrics::network_count++; std::string membersPath(_networksPath + ZT_PATH_SEPARATOR_S + nwids + ZT_PATH_SEPARATOR_S "member"); std::vector members(OSUtils::listDirectory(membersPath.c_str(),false)); for(auto m=members.begin();m!=members.end();++m) { @@ -50,6 +53,7 @@ FileDB::FileDB(const char *path) : if (addrs.length() == 10) { nlohmann::json nullJson2; _memberChanged(nullJson2,member,false); + Metrics::member_count++; } } catch ( ... ) {} } @@ -88,8 +92,9 @@ bool FileDB::save(nlohmann::json &record,bool notifyListeners) if ((!old.is_object())||(!_compareRecords(old,record))) { record["revision"] = OSUtils::jsonInt(record["revision"],0ULL) + 1ULL; OSUtils::ztsnprintf(p1,sizeof(p1),"%s" ZT_PATH_SEPARATOR_S "%.16llx.json",_networksPath.c_str(),nwid); - if (!OSUtils::writeFile(p1,OSUtils::jsonDump(record,-1))) + if (!OSUtils::writeFile(p1,OSUtils::jsonDump(record,-1))) { fprintf(stderr,"WARNING: controller unable to write to path: %s" ZT_EOL_S,p1); + } _networkChanged(old,record,notifyListeners); modified = true; } @@ -110,8 +115,9 @@ bool FileDB::save(nlohmann::json &record,bool notifyListeners) OSUtils::ztsnprintf(p2,sizeof(p2),"%s" ZT_PATH_SEPARATOR_S "%.16llx",_networksPath.c_str(),(unsigned long long)nwid); OSUtils::mkdir(p2); OSUtils::mkdir(pb); - if (!OSUtils::writeFile(p1,OSUtils::jsonDump(record,-1))) + if (!OSUtils::writeFile(p1,OSUtils::jsonDump(record,-1))) { fprintf(stderr,"WARNING: controller unable to write to path: %s" ZT_EOL_S,p1); + } } _memberChanged(old,record,notifyListeners); modified = true; diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 626fa4c26..ae705bb33 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -119,6 +119,7 @@ MemberNotificationReceiver::MemberNotificationReceiver(PostgreSQL *p, pqxx::conn void MemberNotificationReceiver::operator() (const std::string &payload, int packend_pid) { fprintf(stderr, "Member Notification received: %s\n", payload.c_str()); + Metrics::pgsql_mem_notification++; json tmp(json::parse(payload)); json &ov = tmp["old_val"]; json &nv = tmp["new_val"]; @@ -141,6 +142,7 @@ NetworkNotificationReceiver::NetworkNotificationReceiver(PostgreSQL *p, pqxx::co void NetworkNotificationReceiver::operator() (const std::string &payload, int packend_pid) { fprintf(stderr, "Network Notification received: %s\n", payload.c_str()); + Metrics::pgsql_net_notification++; json tmp(json::parse(payload)); json &ov = tmp["old_val"]; json &nv = tmp["new_val"]; @@ -705,6 +707,8 @@ void PostgreSQL::initializeNetworks() } } + Metrics::network_count++; + _networkChanged(empty, config, false); auto end = std::chrono::high_resolution_clock::now(); @@ -925,6 +929,8 @@ void PostgreSQL::initializeMembers() } } + Metrics::member_count++; + _memberChanged(empty, config, false); memberId = ""; @@ -1034,7 +1040,6 @@ void PostgreSQL::heartbeat() w.commit(); } catch (std::exception &e) { fprintf(stderr, "%s: Heartbeat update failed: %s\n", controllerId, e.what()); - w.abort(); _pool->unborrow(c); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); continue; @@ -1230,6 +1235,7 @@ void PostgreSQL::_networksWatcher_Redis() { } lastID = id; } + Metrics::redis_net_notification++; } } } catch (sw::redis::Error &e) { @@ -1788,6 +1794,7 @@ uint64_t PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &con .sadd("network-nodes-all:{"+controllerId+"}:"+networkId, memberId) .hmset("member:{"+controllerId+"}:"+networkId+":"+memberId, record.begin(), record.end()); ++count; + Metrics::redis_mem_notification++; } // expire records from all-nodes and network-nodes member list diff --git a/controller/PostgreSQL.hpp b/controller/PostgreSQL.hpp index c37c4e1a1..8eea3608a 100644 --- a/controller/PostgreSQL.hpp +++ b/controller/PostgreSQL.hpp @@ -26,6 +26,8 @@ #include #include +#include "../node/Metrics.hpp" + extern "C" { typedef struct pg_conn PGconn; } @@ -53,6 +55,7 @@ public: } virtual std::shared_ptr create() { + Metrics::conn_counter++; auto c = std::shared_ptr(new PostgresConnection()); c->c = std::make_shared(m_connString); return std::static_pointer_cast(c); diff --git a/ext/prometheus-cpp-lite-1.0/.gitignore b/ext/prometheus-cpp-lite-1.0/.gitignore new file mode 100644 index 000000000..c18a70b25 --- /dev/null +++ b/ext/prometheus-cpp-lite-1.0/.gitignore @@ -0,0 +1,3 @@ +.vs +bin/ +out/ \ No newline at end of file diff --git a/ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/CMakeLists.txt b/ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/CMakeLists.txt new file mode 100644 index 000000000..177cdb974 --- /dev/null +++ b/ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/CMakeLists.txt @@ -0,0 +1,36 @@ +# Copyright 2021... by Maxim Gusev +# +# https://github.com/John-Jasper-Doe/http-client-lite +# +# Distributed under the MIT License. +# (See accompanying file LICENSE or copy at https://mit-license.org/) + +cmake_minimum_required(VERSION 3.3 FATAL_ERROR) +project(http-client-lite) + +## Set output binary +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) + +## Set property +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +add_compile_options(-Wall -Werror -Wextra -Wpedantic -g -O0) + + +option(HTTP_CLIENT_LITE_OPT_BUILD_EXAMPLES "Build examples" OFF ) + +if(HTTP_CLIENT_LITE_OPT_BUILD_EXAMPLES) + add_subdirectory(examples) +endif() + +# Interface library: +add_library(${PROJECT_NAME} INTERFACE) +target_sources(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include/jdl/httpclientlite.h) +add_custom_target(${PROJECT_NAME}.hdr SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/include/jdl/httpclientlite.h) +target_include_directories( + ${PROJECT_NAME} + INTERFACE + "$" + "$") diff --git a/ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/LICENSE b/ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/LICENSE new file mode 100644 index 000000000..c2dd18535 --- /dev/null +++ b/ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2016 Christian C. Sachs +Copyright (c) 2021 Maxim Gusev + +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. diff --git a/ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/README.md b/ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/README.md new file mode 100644 index 000000000..96b1d1504 --- /dev/null +++ b/ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/README.md @@ -0,0 +1,30 @@ +# HTTP Client lite: C++ Cross-platform library only from single-file header-only + +This is a lite, C++ cross-platform header-only client library for http request based +on [csachs/picohttpclient](https://github.com/csachs/picohttpclient) project. + +A Lightweight HTTP 1.1 client where to quickly do very simple HTTP requests, +without adding larger dependencies to a project. + + +## License + +http client lite is distributed under the [MIT License](https://github.com/john-jasper-doe/http-client-lite/blob/master/LICENSE). + + +## Example usage + +To see how this can be used see the examples folders. + + +**Example:** +```C++ +#include +... +using namespace jdl; +... +HTTPResponse response = HTTPClient::request(HTTPClient::GET, URI("http://example.com")); +cout << response.body << endl; +... +``` + diff --git a/ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/examples/CMakeLists.txt b/ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/examples/CMakeLists.txt new file mode 100644 index 000000000..2f983f892 --- /dev/null +++ b/ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/examples/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.3 FATAL_ERROR) +project(http-client-lite-examples) + +add_executable(${PROJECT_NAME}_simple_request simple_request.cpp) +target_link_libraries(${PROJECT_NAME}_simple_request PRIVATE http_client_lite) diff --git a/ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/examples/simple_request.cpp b/ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/examples/simple_request.cpp new file mode 100644 index 000000000..c0a5e6739 --- /dev/null +++ b/ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/examples/simple_request.cpp @@ -0,0 +1,43 @@ +/* + * example for httpclientlite.hxx + */ + +#include +#include +#include + +#include + + +using namespace jdl; + + +int main(int argc, char *argv[]) { + if (argc == 1) { + std::cout << "Use " << argv[0] << " http://example.org" << std::endl; + return EXIT_SUCCESS; + } + + HTTPResponse response = HTTPClient::request(HTTPClient::POST, URI(argv[1])); + + if (!response.success) { + std::cout << "Request failed!" << std::endl; + return EXIT_FAILURE; + } + + std::cout << "Request success" << endl; + + std::cout << "Server protocol: " << response.protocol << std::endl; + std::cout << "Response code: " << response.response << std::endl; + std::cout << "Response string: " << response.responseString << std::endl; + + std::cout << "Headers:" << std::endl; + + for (stringMap::iterator it = response.header.begin(); it != response.header.end(); ++it) { + std::cout << "\t" << it->first << "=" << it->second << std::endl; + } + + std::cout << response.body << std::endl; + + return EXIT_SUCCESS; +} diff --git a/ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/include/jdl/httpclientlite.h b/ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/include/jdl/httpclientlite.h new file mode 100644 index 000000000..4432308a4 --- /dev/null +++ b/ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/include/jdl/httpclientlite.h @@ -0,0 +1,327 @@ +/* + * httpclientlite.hpp + * =========================================================================================== + * + * The MIT License + * + * Copyright (c) 2016 Christian C. Sachs + * Copyright (c) 2021 Maxim G. + * + * 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. + */ + + +#pragma once + +#if defined (__linux__) +# define PLATFORM_LINUX +#elif defined (_WIN32) || defined (_WIN64) +# define PLATFORM_WINDOWS +#else +/* TODO: + * - Added Apple OS */ + +/* warning: Unknown OS */ +#endif + + +#include +#include +#include +#include +#include +#include + +#include + +#if defined (PLATFORM_WINDOWS) +# include +# include + + typedef SOCKET socktype_t; + typedef int socklen_t; + +# pragma comment(lib, "ws2_32.lib") + +#elif defined (PLATFORM_LINUX) +# include +# include +# include + +# define INVALID_SOCKET -1 +# define closesocket(__sock) close(__sock) + +typedef int socktype_t; + +#endif /* PLATFORM_WINDOWS or PLATFORM_LINUX */ + + + +const std::string content_type = "Content-Type: text/plain; version=0.0.4; charset=utf-8"; + + + + +namespace jdl { + + void init_socket() { +#if defined (PLATFORM_WINDOWS) + WSADATA wsa_data; + WSAStartup(MAKEWORD(2, 2), &wsa_data); +#endif /* PLATFORM_WINDOWS */ + } + + void deinit_socket() { +#if defined (PLATFORM_WINDOWS) + WSACleanup(); +#endif /* PLATFORM_WINDOWS */ + } + + + class tokenizer { + public: + inline tokenizer(std::string &str) : str(str), position(0){} + + inline std::string next(std::string search, bool returnTail = false) { + size_t hit = str.find(search, position); + if (hit == std::string::npos) { + if (returnTail) { + return tail(); + } else { + return ""; + } + } + + size_t oldPosition = position; + position = hit + search.length(); + + return str.substr(oldPosition, hit - oldPosition); + } + + inline std::string tail() { + size_t oldPosition = position; + position = str.length(); + return str.substr(oldPosition); + } + + private: + std::string str; + std::size_t position; + }; + + typedef std::map stringMap; + + struct URI { + inline void parseParameters() { + tokenizer qt(querystring); + do { + std::string key = qt.next("="); + if (key == "") + break; + parameters[key] = qt.next("&", true); + } while (true); + } + + inline URI(std::string input, bool shouldParseParameters = false) { + tokenizer t = tokenizer(input); + protocol = t.next("://"); + std::string hostPortString = t.next("/"); + + tokenizer hostPort(hostPortString); + + host = hostPort.next(hostPortString[0] == '[' ? "]:" : ":", true); + + if (host[0] == '[') + host = host.substr(1, host.size() - 1); + + port = hostPort.tail(); + if (port.empty()) + port = "80"; + + address = t.next("?", true); + querystring = t.next("#", true); + + hash = t.tail(); + + if (shouldParseParameters) { + parseParameters(); + } + } + + std::string protocol, host, port, address, querystring, hash; + stringMap parameters; + }; + + struct HTTPResponse { + bool success; + std::string protocol; + std::string response; + std::string responseString; + + stringMap header; + + std::string body; + + inline HTTPResponse() : success(true){} + inline static HTTPResponse fail() { + HTTPResponse result; + result.success = false; + return result; + } + }; + + struct HTTPClient { + typedef enum { + m_options = 0, + m_get, + m_head, + m_post, + m_put, + m_delete, + m_trace, + m_connect + } HTTPMethod; + + inline static const char *method2string(HTTPMethod method) { + const char *methods[] = {"OPTIONS", "GET", "HEAD", "POST", "PUT", + "DELETE", "TRACE", "CONNECT", nullptr}; + return methods[method]; + } + + inline static socktype_t connectToURI(const URI& uri) { + struct addrinfo hints, *result, *rp; + + memset(&hints, 0, sizeof(addrinfo)); + + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + int getaddrinfo_result = + getaddrinfo(uri.host.c_str(), uri.port.c_str(), &hints, &result); + + if (getaddrinfo_result != 0) + return -1; + + socktype_t fd = INVALID_SOCKET; + + for (rp = result; rp != nullptr; rp = rp->ai_next) { + + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + + if (fd == INVALID_SOCKET) { + continue; + } + + int connect_result = connect(fd, rp->ai_addr, static_cast(rp->ai_addrlen)); + + if (connect_result == -1) { + // successfully created a socket, but connection failed. close it! + closesocket(fd); + fd = INVALID_SOCKET; + continue; + } + + break; + } + + freeaddrinfo(result); + + return fd; + } + + inline static std::string bufferedRead(socktype_t fd) { + size_t initial_factor = 4, buffer_increment_size = 8192, buffer_size = 0, + bytes_read = 0; + std::string buffer; + + buffer.resize(initial_factor * buffer_increment_size); + + // do { + bytes_read = recv(fd, ((char*)buffer.c_str()) + buffer_size, + static_cast(buffer.size() - buffer_size), 0); + + buffer_size += bytes_read; + + // if (bytes_read > 0 && + // (buffer.size() - buffer_size) < buffer_increment_size) { + // buffer.resize(buffer.size() + buffer_increment_size); + // } + // } while (bytes_read > 0); + + buffer.resize(buffer_size); + return buffer; + } + + #define HTTP_NEWLINE "\r\n" + #define HTTP_SPACE " " + #define HTTP_HEADER_SEPARATOR ": " + + inline static HTTPResponse request(HTTPMethod method, const URI& uri, const std::string& body = "") { + + socktype_t fd = connectToURI(uri); + if (fd < 0) + return HTTPResponse::fail(); + + // string request = string(method2string(method)) + string(" /") + + // uri.address + ((uri.querystring == "") ? "" : "?") + + // uri.querystring + " HTTP/1.1" HTTP_NEWLINE "Host: " + + // uri.host + HTTP_NEWLINE + // "Accept: */*" HTTP_NEWLINE + // "Connection: close" HTTP_NEWLINE HTTP_NEWLINE; + + std::string request = std::string(method2string(method)) + std::string(" /") + + uri.address + ((uri.querystring == "") ? "" : "?") + uri.querystring + " HTTP/1.1" + HTTP_NEWLINE + + "Host: " + uri.host + ":" + uri.port + HTTP_NEWLINE + + "Accept: */*" + HTTP_NEWLINE + + content_type + HTTP_NEWLINE + + "Content-Length: " + std::to_string(body.size()) + HTTP_NEWLINE + HTTP_NEWLINE + + body; + + /*int bytes_written = */send(fd, request.c_str(), static_cast(request.size()), 0); + + std::string buffer = bufferedRead(fd); + + closesocket(fd); + + HTTPResponse result; + + tokenizer bt(buffer); + + result.protocol = bt.next(HTTP_SPACE); + result.response = bt.next(HTTP_SPACE); + result.responseString = bt.next(HTTP_NEWLINE); + + std::string header = bt.next(HTTP_NEWLINE HTTP_NEWLINE); + + result.body = bt.tail(); + + tokenizer ht(header); + + do { + std::string key = ht.next(HTTP_HEADER_SEPARATOR); + if (key == "") + break; + result.header[key] = ht.next(HTTP_NEWLINE, true); + } while (true); + + return result; + } + }; + +} /* jdl:: */ diff --git a/ext/prometheus-cpp-lite-1.0/CMakeLists.txt b/ext/prometheus-cpp-lite-1.0/CMakeLists.txt new file mode 100644 index 000000000..390e9091a --- /dev/null +++ b/ext/prometheus-cpp-lite-1.0/CMakeLists.txt @@ -0,0 +1,34 @@ +project(prometheus-cpp-lite) +cmake_minimum_required(VERSION 3.2) + +option(PROMETHEUS_BUILD_EXAMPLES "Build with examples" OFF) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin) + +if(WIN32) + + # it prevent create Debug/ and Release folders in Visual Studio + foreach( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} ) + string( TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG ) + set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${PROJECT_SOURCE_DIR}/bin ) + endforeach() + + set (INSTALL_PATH_BIN "${PROJECT_SOURCE_DIR}/installed/bin/") + +else() # not WIN32 + + set (INSTALL_PATH_BIN "bin/") + +endif() + +add_subdirectory("./core") + +add_subdirectory("./simpleapi") + +add_subdirectory("./3rdpatry/http-client-lite") + +if(PROMETHEUS_BUILD_EXAMPLES) + add_subdirectory("./examples") +endif() + + diff --git a/ext/prometheus-cpp-lite-1.0/LICENSE b/ext/prometheus-cpp-lite-1.0/LICENSE new file mode 100644 index 000000000..266f827c8 --- /dev/null +++ b/ext/prometheus-cpp-lite-1.0/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 biaks (ianiskr@gmail.com) + +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. diff --git a/ext/prometheus-cpp-lite-1.0/README.md b/ext/prometheus-cpp-lite-1.0/README.md new file mode 100644 index 000000000..5aeae60d4 --- /dev/null +++ b/ext/prometheus-cpp-lite-1.0/README.md @@ -0,0 +1,201 @@ +# C++ Header-only Prometheus client library + +It is a tool for quickly adding metrics (and profiling) functionality to C++ projects. + +## Advantages: + +1. Written in pure C++, +2. Header-only, +2. Cross-platform, +3. Compiles with C ++ 11, C ++ 14, C ++ 17 standards, +4. Has no third-party dependencies, +5. Several APIs for use in your projects, +6. Saving metrics to a file (and then works with node_exporter) or sending via http (uses built-in header-only http-client-lite library), +7. Possiblity to use different types for storing metrics data (default is uint32_t, but you can use double or uint64_t types if you want), +8. Five types of metrics are supported: counter, gauge, summary, histogram and benchmark, +10. Has detailed examples of use (see examples folder) + +## How it differs from the [jupp0r/prometheus-cpp](https://github.com/jupp0r/prometheus-cpp) project: +1. I need a simple header only wariant library without dependencies to write metrics to a .prom file, +2. I need the fastest possible work using integer values of counters (original project use only floating pointer values), +3. The origianl project have problems on compilers that do not know how to do LTO optimization, +4. I did not like the python style of the original project and the large amount of extra code in it and I wanted to make it lighter and more c++ classic. + +## How to use it: +The library has two API: +1. Complex API for those who want to control everything, +2. Simple API for those who want to quickly add metrics to their C ++ (and it is actually just a wrapper around the complex API). + + +### Let's start with a simple API because it's simple: + +To add it to your C++ project add these lines to your CMakeLists.txt file: +``` +add_subdirectory("prometheus-cpp-lite/core") +add_subdirectory("prometheus-cpp-lite/3rdpatry/http-client-lite") +add_subdirectory("prometheus-cpp-lite/simpleapi") +target_link_libraries(your_target prometheus-cpp-simpleapi) +``` + +The simplest way to create a metric would be like this: +``` c++ +prometheus::simpleapi::METRIC_metric_t metric1 { "metric1", "first simple metric without any tag" }; +prometheus::simpleapi::METRIC_metric_t metric2 { "metric2", "second simple metric without any tag" }; +``` +where ```METRIC``` can be ```counter```, ```gauge```, ```summary```, ```histogram``` or ```benchmark```. + +If you want to access an existing metric again elsewhere in the code, you can do this: +``` c++ +prometheus::simpleapi::METRIC_metric_t metric2_yet_another_link { "metric2", "" }; +``` +this works because when adding a metric, it checks whether there is already a metric with the same name and, if there is one, a link to it is returned. + +You can create a family of metrics (metrics with tags) as follows: +``` c++ +prometheus::simpleapi::METRIC_family_t family { "metric_family", "metric family" }; +prometheus::simpleapi::METRIC_metric_t metric1 { family.Add({{"name", "metric1"}}) }; +prometheus::simpleapi::METRIC_metric_t metric2 { family.Add({{"name", "metric2"}}) }; +``` +where METRIC can be ```counter```, ```gauge```, ```summary```, ```histogram``` or ```benchmark```. + +Next, you can do the following things with metrics: +``` c++ +metric++; // for increment it (only for counter and gauge metrics) +metric += value; // for add value to metric (only for gauge metric) +metric -= value; // for sub value from metric (only for gauge metric) +metric = value; // save current value (only gauge metrics) +metric.start(); // start calculate time (only for benchmark metric) +metric.stop(); // stop calculate time (only for benchmark metric) +``` + +You can change the settings of save (or send) metrics data as follows: +``` c++ +prometheus::simpleapi::saver.set_delay(period_in_seconds); // change the period of saving (or sending) metrics data in seconds (5 seconds by default) +prometheus::simpleapi::saver.set_out_file(filename); // change the name of the output file (metrics.prom by default) +prometheus::simpleapi::saver.set_server_url(url); // change the name of prometheus server (unset by default) +``` + +### Simple API complex example 1 (examples/simpleapi_example.cpp): + +``` c++ +#include + +void main() { + + using namespace prometheus::simpleapi; + + counter_family_t family { "simple_family", "simple family example" }; + counter_metric_t metric1 { family.Add({{"name", "counter1"}}) }; + counter_metric_t metric2 { family.Add({{"name", "counter2"}}) }; + + counter_metric_t metric3 { "simple_counter_1", "simple counter 1 without labels example" }; + counter_metric_t metric4 { "simple_counter_2", "simple counter 2 without labels example" }; + + for (;; ) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + const int random_value = std::rand(); + if (random_value & 1) metric1++; + if (random_value & 2) metric2++; + if (random_value & 4) metric3++; + if (random_value & 8) metric4++; + } + +} +``` + +Output in "metrics.prom" file (by default) will be: + +``` +# HELP simple_family simple family example +# TYPE simple_family counter +simple_family{name="counter1"} 10 +simple_family{name="counter2"} 9 +# HELP simple_counter_1 simple counter 1 without labels example +# TYPE simple_counter_1 counter +simple_counter_1 6 +# HELP simple_counter_2 simple counter 2 without labels example +# TYPE simple_counter_2 counter +simple_counter_2 8 +``` + +### Simple API complex example 2 (examples/simpleapi_use_in_class_example.cpp): + +``` c++ +#include + +using namespace prometheus::simpleapi; + +class MyClass { + + counter_family_t metric_family { "simple_family", "simple family example" }; + counter_metric_t metric1 { metric_family.Add({{"name", "counter1"}}) }; + counter_metric_t metric2 { metric_family.Add({{"name", "counter2"}}) }; + + counter_metric_t metric3 { "simple_counter_1", "simple counter 1 without labels example" }; + counter_metric_t metric4 { "simple_counter_2", "simple counter 2 without labels example" }; + + benchmark_family_t benchmark_family { "simple_benchmark_family", "simple benchmark family example" }; + benchmark_metric_t benchmark1 { benchmark_family.Add({{"benchmark", "1"}}) }; + benchmark_metric_t benchmark2 { benchmark_family.Add({{"benchmark", "2"}}) }; + +public: + + MyClass() = default; + + void member_to_do_something() { + + benchmark1.start(); + const int random_value = std::rand(); + benchmark1.stop(); + + benchmark2.start(); + if (random_value & 1) metric1++; + if (random_value & 2) metric2++; + if (random_value & 4) metric3++; + if (random_value & 8) metric4++; + benchmark2.stop(); + + } + +}; + +void main() { + + MyClass myClass; + benchmark_metric_t benchmark { "simple_benchmark", "simple benchmark example" }; + + for (;; ) { + + benchmark.start(); + std::this_thread::sleep_for(std::chrono::seconds(1)); + benchmark.stop(); + + myClass.member_to_do_something(); + + } + +} +``` + +Output in "metrics.prom" file (by default) will be: + +``` +# HELP simple_family simple family example +# TYPE simple_family counter +simple_family{name="counter1"} 3 +simple_family{name="counter2"} 2 +# HELP simple_counter_1 simple counter 1 without labels example +# TYPE simple_counter_1 counter +simple_counter_1 3 +# HELP simple_counter_2 simple counter 2 without labels example +# TYPE simple_counter_2 counter +simple_counter_2 3 +# HELP simple_benchmark_family simple benchmark family example +# TYPE simple_benchmark_family counter +simple_benchmark_family{benchmark="1"} 0.0001088 +simple_benchmark_family{benchmark="2"} 1.48e-05 +# HELP simple_benchmark simple benchmark example +# TYPE simple_benchmark counter +simple_benchmark 6.0503248 +``` + diff --git a/ext/prometheus-cpp-lite-1.0/core/CMakeLists.txt b/ext/prometheus-cpp-lite-1.0/core/CMakeLists.txt new file mode 100644 index 000000000..70c932218 --- /dev/null +++ b/ext/prometheus-cpp-lite-1.0/core/CMakeLists.txt @@ -0,0 +1,20 @@ +project(prometheus-cpp-lite-core) +cmake_minimum_required(VERSION 3.2) + +file(GLOB_RECURSE PROMETHEUS_CPP_LITE_HEADERS *.h) + +# it is header only target + +add_library (${PROJECT_NAME} INTERFACE) +target_sources (${PROJECT_NAME} INTERFACE ${PROMETHEUS_CPP_LITE_HEADERS}) +target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) +add_custom_target (${PROJECT_NAME}-ide SOURCES ${PROMETHEUS_CPP_LITE_HEADERS}) +target_link_libraries (${PROJECT_NAME} INTERFACE http-client-lite) + +set (${PROJECT_NAME}_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/include PARENT_SCOPE) + +# it need for save_to_file_t +if(NOT WIN32) + find_package(Threads) + target_link_libraries(${PROJECT_NAME} INTERFACE ${CMAKE_THREAD_LIBS_INIT}) +endif() diff --git a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/atomic_floating.h b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/atomic_floating.h new file mode 100644 index 000000000..9cc7ff0fe --- /dev/null +++ b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/atomic_floating.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +namespace prometheus { + + template + inline std::atomic& atomic_add_for_floating_types(std::atomic& value, + const FloatingType& add) { + FloatingType desired; + FloatingType expected = value.load(std::memory_order_relaxed); + do { + desired = expected + add; + } while (!value.compare_exchange_weak(expected, desired)); + return value; + } + + + template ::value, int>::type> + inline std::atomic& operator++(std::atomic& value) { + return atomic_add_for_floating_types(value, 1.0); + } + + template ::value, int>::type> + inline std::atomic& operator+=(std::atomic& value, const FloatingType& val) { + return atomic_add_for_floating_types(value, val); + } + + template ::value, int>::type> + inline std::atomic& operator--(std::atomic& value) { + return atomic_add_for_floating_types(value, -1.0); + } + + template ::value, int>::type> + inline std::atomic& operator-=(std::atomic& value, const FloatingType& val) { + return atomic_add_for_floating_types(value, -val); + } + +} diff --git a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/benchmark.h b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/benchmark.h new file mode 100644 index 000000000..166801ba8 --- /dev/null +++ b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/benchmark.h @@ -0,0 +1,72 @@ +#pragma once + +#include "prometheus/metric.h" +#include "prometheus/family.h" + +#include + +namespace prometheus { + + class Benchmark : public Metric { + + #ifndef NDEBUG + bool already_started = false; + #endif + + std::chrono::time_point start_; + std::chrono::time_point::duration elapsed = std::chrono::time_point::duration::zero(); // elapsed time + + public: + + using Value = double; + using Family = CustomFamily; + + static const Metric::Type static_type = Metric::Type::Counter; + + Benchmark() : Metric(Metric::Type::Counter) {} + + void start() { + + #ifndef NDEBUG + if (already_started) + throw std::runtime_error("try to start already started counter"); + else + already_started = true; + #endif + + start_ = std::chrono::high_resolution_clock::now(); + + } + + void stop() { + + #ifndef NDEBUG + if (already_started == false) + throw std::runtime_error("try to stop already stoped counter"); + #endif + + std::chrono::time_point stop; + stop = std::chrono::high_resolution_clock::now(); + elapsed += stop - start_; + + #ifndef NDEBUG + already_started = false; + #endif + + } + + double Get() const { + return std::chrono::duration_cast>(elapsed).count(); + } + + virtual ClientMetric Collect() const { + ClientMetric metric; + metric.counter.value = Get(); + return metric; + } + + }; + + + +} // namespace prometheus diff --git a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/builder.h b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/builder.h new file mode 100644 index 000000000..74f22b97d --- /dev/null +++ b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/builder.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include "registry.h" + +namespace prometheus { + + template + class Builder { + + Family::Labels labels_; + std::string name_; + std::string help_; + + public: + Builder& Labels(const std::map& labels) { + labels_ = labels; + return *this; + } + Builder& Name(const std::string& name) { + name_ = name; + return *this; + } + Builder& Help(const std::string& help) { + help_ = help; + return *this; + } + CustomFamily& Register(Registry& registry) { + return registry.Add>(name_, help_, labels_); + } + + }; + +} \ No newline at end of file diff --git a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/ckms_quantiles.h b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/ckms_quantiles.h new file mode 100644 index 000000000..267d442ae --- /dev/null +++ b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/ckms_quantiles.h @@ -0,0 +1,194 @@ +#pragma once + +#include +#include +#include +#include + +namespace prometheus { + + namespace detail { + + class CKMSQuantiles { + + public: + + struct Quantile { + + double quantile; + double error; + double u; + double v; + + Quantile(double quantile, double error) + : quantile(quantile), + error(error), + u(2.0 * error / (1.0 - quantile)), + v(2.0 * error / quantile) {} + + }; + + private: + + struct Item { + + double value; + int g; + int delta; + + Item(double value, int lower_delta, int delta) + : value(value), g(lower_delta), delta(delta) {} + + }; + + public: + + explicit CKMSQuantiles(const std::vector& quantiles) + : quantiles_(quantiles), count_(0), buffer_{}, buffer_count_(0) {} + + void insert(double value) { + buffer_[buffer_count_] = value; + ++buffer_count_; + + if (buffer_count_ == buffer_.size()) { + insertBatch(); + compress(); + } + } + + double get(double q) { + insertBatch(); + compress(); + + if (sample_.empty()) { + return std::numeric_limits::quiet_NaN(); + } + + int rankMin = 0; + const auto desired = static_cast(q * static_cast(count_)); + const auto bound = desired + (allowableError(desired) / 2); + + auto it = sample_.begin(); + decltype(it) prev; + auto cur = it++; + + while (it != sample_.end()) { + prev = cur; + cur = it++; + + rankMin += prev->g; + + if (rankMin + cur->g + cur->delta > bound) { + return prev->value; + } + } + + return sample_.back().value; + } + + void reset() { + count_ = 0; + sample_.clear(); + buffer_count_ = 0; + } + + private: + + double allowableError(int rank) { + auto size = sample_.size(); + double minError = static_cast(size + 1); + + for (const auto& q : quantiles_.get()) { + double error; + if (static_cast(rank) <= q.quantile * static_cast(size)) { + error = q.u * static_cast(size - rank); + } + else { + error = q.v * rank; + } + if (error < minError) { + minError = error; + } + } + + return minError; + } + + bool insertBatch() { + if (buffer_count_ == 0) { + return false; + } + + std::sort(buffer_.begin(), buffer_.begin() + buffer_count_); + + std::size_t start = 0; + if (sample_.empty()) { + sample_.emplace_back(buffer_[0], 1, 0); + ++start; + ++count_; + } + + std::size_t idx = 0; + std::size_t item = idx++; + + for (std::size_t i = start; i < buffer_count_; ++i) { + double v = buffer_[i]; + while (idx < sample_.size() && sample_[item].value < v) { + item = idx++; + } + + if (sample_[item].value > v) { + --idx; + } + + int delta; + if (idx - 1 == 0 || idx + 1 == sample_.size()) { + delta = 0; + } + else { + delta = static_cast(std::floor(allowableError(static_cast(idx + 1)))) + 1; + } + + sample_.emplace(sample_.begin() + idx, v, 1, delta); + count_++; + item = idx++; + } + + buffer_count_ = 0; + return true; + } + + void compress() { + if (sample_.size() < 2) { + return; + } + + std::size_t idx = 0; + std::size_t prev; + std::size_t next = idx++; + + while (idx < sample_.size()) { + prev = next; + next = idx++; + + if (sample_[prev].g + sample_[next].g + sample_[next].delta <= + allowableError(static_cast(idx - 1))) { + sample_[next].g += sample_[prev].g; + sample_.erase(sample_.begin() + prev); + } + } + } + + private: + + const std::reference_wrapper> quantiles_; + + std::size_t count_; + std::vector sample_; + std::array buffer_; + std::size_t buffer_count_; + }; + + } // namespace detail + +} // namespace prometheus diff --git a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/client_metric.h b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/client_metric.h new file mode 100644 index 000000000..39acff782 --- /dev/null +++ b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/client_metric.h @@ -0,0 +1,94 @@ +#pragma once + +#include +#include +#include +#include + +namespace prometheus { + + // структура, в которую копируются значения метрик перед их сериализацией + struct ClientMetric { + + // Label + + struct Label { + + std::string name; + std::string value; + + Label(const std::string name_, const std::string value_) : name(name_), value(value_) {} + + bool operator<(const Label& rhs) const { + return std::tie(name, value) < std::tie(rhs.name, rhs.value); + } + + bool operator==(const Label& rhs) const { + return std::tie(name, value) == std::tie(rhs.name, rhs.value); + } + + }; + + std::vector