Add prometheus metrics for Central controllers (#1969)

* add header-only prometheus lib to ext

* rename folder

* Undo rename directory

* prometheus simpleapi included on mac & linux

* wip

* wire up some controller stats

* Get windows building with prometheus

* bsd build flags for prometheus

* Fix multiple network join from environment entrypoint.sh.release (#1961)

* _bond_m guards _bond, not _paths_m (#1965)

* Fix: warning: mutex '_aqm_m' is not held on every path through here [-Wthread-safety-analysis] (#1964)

* Serve prom metrics from /metrics endpoint

* Add prom metrics for Central controller specific things

* reorganize metric initialization

* testing out a labled gauge on Networks

* increment error counter on throw

* Consolidate metrics definitions

Put all metric definitions into node/Metrics.hpp.  Accessed as needed
from there.

* Revert "testing out a labled gauge on Networks"

This reverts commit 499ed6d95e.

* still blows up but adding to the record for completeness right now

* Fix runtime issues with metrics

* Add metrics files to visual studio project

* Missed an "extern"

* add copyright headers to new files

* Add metrics for sent/received bytes (total)

* put /metrics endpoint behind auth

* sendto returns int on Win32

---------

Co-authored-by: Leonardo Amaral <leleobhz@users.noreply.github.com>
Co-authored-by: Brenton Bostick <bostick@gmail.com>
This commit is contained in:
Grant Limberg 2023-04-21 12:12:43 -07:00 committed by GitHub
parent 0b03ad9a21
commit 8e6e4ede6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 4023 additions and 25 deletions

View File

@ -19,6 +19,8 @@
#define _DEBUG(x)
#endif
#include "../node/Metrics.hpp"
#include <deque>
#include <set>
#include <memory>
@ -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<Connection> 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<Connection> conn = m_factory->create();
m_borrowed.insert(conn);
Metrics::pool_in_use++;
return std::static_pointer_cast<T>(conn);
} catch (std::exception &e) {
Metrics::pool_errors++;
throw ConnectionUnavailable();
}
} else {
@ -116,11 +122,13 @@ public:
return std::static_pointer_cast<T>(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<Connection> 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<T>(conn);
};
@ -143,7 +153,9 @@ public:
// Lock
std::unique_lock<std::mutex> 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);
}
};

View File

@ -13,6 +13,7 @@
#include "DB.hpp"
#include "EmbeddedNetworkController.hpp"
#include "../node/Metrics.hpp"
#include <chrono>
#include <algorithm>
@ -211,16 +212,19 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no
{
std::lock_guard<std::mutex> l(_networks_l);
auto nw2 = _networks.find(networkId);
if (nw2 != _networks.end())
if (nw2 != _networks.end()) {
nw = nw2->second;
}
}
if (nw) {
std::lock_guard<std::mutex> 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;i<ips.size();++i) {
@ -255,11 +259,14 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no
nw->members[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<ips.size();++i) {
@ -303,6 +310,24 @@ void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool no
}
}
if (notifyListeners) {
if(networkId != 0 && memberId != 0 && old.is_object() && !memberConfig.is_object()) {
// member delete
Metrics::member_count--;
} else if (networkId != 0 && memberId != 0 && !old.is_object() && memberConfig.is_object()) {
// new member
Metrics::member_count++;
}
if (!wasAuth && isAuth) {
Metrics::member_auths++;
} else if (wasAuth && !isAuth) {
Metrics::member_deauths++;
} else {
Metrics::member_changes++;
}
}
if ((notifyListeners)&&((wasAuth)&&(!isAuth)&&(networkId)&&(memberId))) {
std::lock_guard<std::mutex> 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());

View File

@ -35,6 +35,8 @@
#include <nlohmann/json.hpp>
#include <prometheus/simpleapi.h>
#define ZT_MEMBER_AUTH_TIMEOUT_NOTIFY_BEFORE 25000
namespace ZeroTier

View File

@ -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<std::string> 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;

View File

@ -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

View File

@ -26,6 +26,8 @@
#include <memory>
#include <redis++/redis++.h>
#include "../node/Metrics.hpp"
extern "C" {
typedef struct pg_conn PGconn;
}
@ -53,6 +55,7 @@ public:
}
virtual std::shared_ptr<Connection> create() {
Metrics::conn_counter++;
auto c = std::shared_ptr<PostgresConnection>(new PostgresConnection());
c->c = std::make_shared<pqxx::connection>(m_connString);
return std::static_pointer_cast<Connection>(c);

View File

@ -0,0 +1,3 @@
.vs
bin/
out/

View File

@ -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
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")

View File

@ -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.

View File

@ -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 <jdl/httpclientlite.hpp>
...
using namespace jdl;
...
HTTPResponse response = HTTPClient::request(HTTPClient::GET, URI("http://example.com"));
cout << response.body << endl;
...
```

View File

@ -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)

View File

@ -0,0 +1,43 @@
/*
* example for httpclientlite.hxx
*/
#include <iostream>
#include <map>
#include <string>
#include <jdl/httpclientlite.h>
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;
}

View File

@ -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 <iostream>
#include <string>
#include <map>
#include <vector>
#include <cstring>
#include <sstream>
#include <sys/types.h>
#if defined (PLATFORM_WINDOWS)
# include <WinSock2.h>
# include <WS2tcpip.h>
typedef SOCKET socktype_t;
typedef int socklen_t;
# pragma comment(lib, "ws2_32.lib")
#elif defined (PLATFORM_LINUX)
# include <unistd.h>
# include <sys/socket.h>
# include <netdb.h>
# 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<std::string, std::string> 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<socklen_t>(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<socklen_t>(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<socklen_t>(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:: */

View File

@ -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()

View File

@ -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.

View File

@ -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 <prometheus/simpleapi.h>
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 <prometheus/simpleapi.h>
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
```

View File

@ -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()

View File

@ -0,0 +1,40 @@
#pragma once
#include <type_traits>
#include <atomic>
namespace prometheus {
template <typename FloatingType>
inline std::atomic<FloatingType>& atomic_add_for_floating_types(std::atomic<FloatingType>& 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 <typename FloatingType, class = typename std::enable_if<std::is_floating_point<FloatingType>::value, int>::type>
inline std::atomic<FloatingType>& operator++(std::atomic<FloatingType>& value) {
return atomic_add_for_floating_types(value, 1.0);
}
template <typename FloatingType, class = typename std::enable_if<std::is_floating_point<FloatingType>::value, int>::type>
inline std::atomic<FloatingType>& operator+=(std::atomic<FloatingType>& value, const FloatingType& val) {
return atomic_add_for_floating_types(value, val);
}
template <typename FloatingType, class = typename std::enable_if<std::is_floating_point<FloatingType>::value, int>::type>
inline std::atomic<FloatingType>& operator--(std::atomic<FloatingType>& value) {
return atomic_add_for_floating_types(value, -1.0);
}
template <typename FloatingType, class = typename std::enable_if<std::is_floating_point<FloatingType>::value, int>::type>
inline std::atomic<FloatingType>& operator-=(std::atomic<FloatingType>& value, const FloatingType& val) {
return atomic_add_for_floating_types(value, -val);
}
}

View File

@ -0,0 +1,72 @@
#pragma once
#include "prometheus/metric.h"
#include "prometheus/family.h"
#include <chrono>
namespace prometheus {
class Benchmark : public Metric {
#ifndef NDEBUG
bool already_started = false;
#endif
std::chrono::time_point<std::chrono::high_resolution_clock> start_;
std::chrono::time_point<std::chrono::high_resolution_clock>::duration elapsed = std::chrono::time_point<std::chrono::high_resolution_clock>::duration::zero(); // elapsed time
public:
using Value = double;
using Family = CustomFamily<Benchmark>;
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<std::chrono::high_resolution_clock> 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<std::chrono::duration<double>>(elapsed).count();
}
virtual ClientMetric Collect() const {
ClientMetric metric;
metric.counter.value = Get();
return metric;
}
};
} // namespace prometheus

View File

@ -0,0 +1,35 @@
#pragma once
#include <string>
#include <map>
#include "registry.h"
namespace prometheus {
template <typename CustomMetric>
class Builder {
Family::Labels labels_;
std::string name_;
std::string help_;
public:
Builder& Labels(const std::map<const std::string, const std::string>& 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<CustomMetric>& Register(Registry& registry) {
return registry.Add<CustomFamily<CustomMetric>>(name_, help_, labels_);
}
};
}

View File

@ -0,0 +1,194 @@
#pragma once
#include <array>
#include <cstddef>
#include <functional>
#include <vector>
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<Quantile>& 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<double>::quiet_NaN();
}
int rankMin = 0;
const auto desired = static_cast<int>(q * static_cast<double>(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<double>(size + 1);
for (const auto& q : quantiles_.get()) {
double error;
if (static_cast<double>(rank) <= q.quantile * static_cast<double>(size)) {
error = q.u * static_cast<double>(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<int>(std::floor(allowableError(static_cast<int>(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<int>(idx - 1))) {
sample_[next].g += sample_[prev].g;
sample_.erase(sample_.begin() + prev);
}
}
}
private:
const std::reference_wrapper<const std::vector<Quantile>> quantiles_;
std::size_t count_;
std::vector<Item> sample_;
std::array<double, 500> buffer_;
std::size_t buffer_count_;
};
} // namespace detail
} // namespace prometheus

View File

@ -0,0 +1,94 @@
#pragma once
#include <cstdint>
#include <string>
#include <tuple>
#include <vector>
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<Label> label;
// Counter
struct Counter {
double value = 0.0;
};
Counter counter;
// Gauge
struct Gauge {
double value = 0.0;
};
Gauge gauge;
// Summary
struct Quantile {
double quantile = 0.0;
double value = 0.0;
};
struct Summary {
std::uint64_t sample_count = 0;
double sample_sum = 0.0;
std::vector<Quantile> quantile;
};
Summary summary;
// Histogram
struct Bucket {
std::uint64_t cumulative_count = 0;
double upper_bound = 0.0;
};
struct Histogram {
std::uint64_t sample_count = 0;
double sample_sum = 0.0;
std::vector<Bucket> bucket;
};
Histogram histogram;
// Untyped
struct Untyped {
double value = 0;
};
Untyped untyped;
// Timestamp
std::int64_t timestamp_ms = 0;
};
} // namespace prometheus

View File

@ -0,0 +1,27 @@
#pragma once
#include <vector>
#include "prometheus/metric_family.h"
namespace prometheus {
/// @brief Interface implemented by anything that can be used by Prometheus to
/// collect metrics.
///
/// A Collectable has to be registered for collection. See Registry.
class Collectable {
public:
//Collectable() = default;
virtual ~Collectable() = default;
using MetricFamilies = std::vector<MetricFamily>;
/// \brief Returns a list of metrics and their samples.
virtual MetricFamilies Collect() const = 0;
};
} // namespace prometheus

View File

@ -0,0 +1,112 @@
#pragma once
#include "prometheus/atomic_floating.h"
#include "prometheus/metric.h"
#include "prometheus/family.h"
#include "prometheus/builder.h"
#include <atomic>
namespace prometheus {
/// \brief A counter metric to represent a monotonically increasing value.
///
/// This class represents the metric type counter:
/// https://prometheus.io/docs/concepts/metric_types/#counter
///
/// The value of the counter can only increase. Example of counters are:
/// - the number of requests served
/// - tasks completed
/// - errors
///
/// Do not use a counter to expose a value that can decrease - instead use a
/// Gauge.
///
/// The class is thread-safe. No concurrent call to any API of this type causes
/// a data race.
template <typename Value_ = uint64_t>
class Counter : public Metric {
std::atomic<Value_> value{ 0 };
public:
using Value = Value_;
using Family = CustomFamily<Counter<Value>>;
static const Metric::Type static_type = Metric::Type::Counter;
Counter() : Metric (Metric::Type::Counter) {} ///< \brief Create a counter that starts at 0.
// original API
void Increment() { ///< \brief Increment the counter by 1.
++value;
}
void Increment(const Value& val) { ///< \brief Increment the counter by a given amount. The counter will not change if the given amount is negative.
if (val > 0)
value += val;
}
const Value Get() const { ///< \brief Get the current value of the counter.
return value;
}
virtual ClientMetric Collect() const { ///< /// \brief Get the current value of the counter. Collect is called by the Registry when collecting metrics.
ClientMetric metric;
metric.counter.value = static_cast<double>(value);
return metric;
}
// new API
Counter& operator ++() {
++value;
return *this;
}
Counter& operator++ (int) {
++value;
return *this;
}
Counter& operator += (const Value& val) {
value += val;
return *this;
}
};
/// \brief Return a builder to configure and register a Counter metric.
///
/// @copydetails Family<>::Family()
///
/// Example usage:
///
/// \code
/// auto registry = std::make_shared<Registry>();
/// auto& counter_family = prometheus::BuildCounter()
/// .Name("some_name")
/// .Help("Additional description.")
/// .Labels({{"key", "value"}})
/// .Register(*registry);
///
/// ...
/// \endcode
///
/// \return An object of unspecified type T, i.e., an implementation detail
/// except that it has the following members:
///
/// - Name(const std::string&) to set the metric name,
/// - Help(const std::string&) to set an additional description.
/// - Label(const std::map<std::string, std::string>&) to assign a set of
/// key-value pairs (= labels) to the metric.
///
/// To finish the configuration of the Counter metric, register it with
/// Register(Registry&).
using BuildCounter = Builder<Counter<double>>;
} // namespace prometheus

View File

@ -0,0 +1,355 @@
#pragma once
#include <cstddef>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
#include <cassert>
#include "prometheus/collectable.h"
#include "prometheus/metric.h"
#include "prometheus/hash.h"
namespace prometheus {
/// \brief A metric of type T with a set of labeled dimensions.
///
/// One of Prometheus main feature is a multi-dimensional data model with time
/// series data identified by metric name and key/value pairs, also known as
/// labels. A time series is a series of data points indexed (or listed or
/// graphed) in time order (https://en.wikipedia.org/wiki/Time_series).
///
/// An instance of this class is exposed as multiple time series during
/// scrape, i.e., one time series for each set of labels provided to Add().
///
/// For example it is possible to collect data for a metric
/// `http_requests_total`, with two time series:
///
/// - all HTTP requests that used the method POST
/// - all HTTP requests that used the method GET
///
/// The metric name specifies the general feature of a system that is
/// measured, e.g., `http_requests_total`. Labels enable Prometheus's
/// dimensional data model: any given combination of labels for the same
/// metric name identifies a particular dimensional instantiation of that
/// metric. For example a label for 'all HTTP requests that used the method
/// POST' can be assigned with `method= "POST"`.
///
/// Given a metric name and a set of labels, time series are frequently
/// identified using this notation:
///
/// <metric name> { < label name >= <label value>, ... }
///
/// It is required to follow the syntax of metric names and labels given by:
/// https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels
///
/// The following metric and label conventions are not required for using
/// Prometheus, but can serve as both a style-guide and a collection of best
/// practices: https://prometheus.io/docs/practices/naming/
///
/// tparam T One of the metric types Counter, Gauge, Histogram or Summary.
class Family : public Collectable {
public:
using Hash = std::size_t;
using Label = std::pair<const std::string, const std::string>;
using Labels = std::map <const std::string, const std::string>;
using MetricPtr = std::unique_ptr<Metric>;
const Metric::Type type;
const std::string name;
const std::string help;
const Labels constant_labels;
mutable std::mutex mutex;
std::unordered_map<Hash, MetricPtr> metrics;
std::unordered_map<Hash, Labels> labels;
std::unordered_map<Metric*, Hash> labels_reverse_lookup;
/// \brief Compute the hash value of a map of labels.
///
/// \param labels The map that will be computed the hash value.
///
/// \returns The hash value of the given labels.
static Hash hash_labels (const Labels& labels) {
size_t seed = 0;
for (const Label& label : labels)
detail::hash_combine (&seed, label.first, label.second);
return seed;
}
static bool isLocaleIndependentDigit (char c) { return '0' <= c && c <= '9'; }
static bool isLocaleIndependentAlphaNumeric (char c) { return isLocaleIndependentDigit(c) || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); }
bool nameStartsValid (const std::string& name) {
if (name.empty()) return false; // must not be empty
if (isLocaleIndependentDigit(name.front())) return false; // must not start with a digit
if (name.compare(0, 2, "__") == 0) return false; // must not start with "__"
return true;
}
/// \brief Check if the metric name is valid
///
/// The metric name regex is "[a-zA-Z_:][a-zA-Z0-9_:]*"
///
/// \see https://prometheus.io/docs/concepts/data_model/
///
/// \param name metric name
/// \return true is valid, false otherwise
bool CheckMetricName (const std::string& name) {
if (!nameStartsValid(name))
return false;
for (const char& c : name)
if ( !isLocaleIndependentAlphaNumeric(c) && c != '_' && c != ':' )
return false;
return true;
}
/// \brief Check if the label name is valid
///
/// The label name regex is "[a-zA-Z_][a-zA-Z0-9_]*"
///
/// \see https://prometheus.io/docs/concepts/data_model/
///
/// \param name label name
/// \return true is valid, false otherwise
bool CheckLabelName (const std::string& name) {
if (!nameStartsValid(name))
return false;
for (const char& c : name)
if (!isLocaleIndependentAlphaNumeric(c) && c != '_')
return false;
return true;
}
/// \brief Create a new metric.
///
/// Every metric is uniquely identified by its name and a set of key-value
/// pairs, also known as labels. Prometheus's query language allows filtering
/// and aggregation based on metric name and these labels.
///
/// This example selects all time series that have the `http_requests_total`
/// metric name:
///
/// http_requests_total
///
/// It is possible to assign labels to the metric name. These labels are
/// propagated to each dimensional data added with Add(). For example if a
/// label `job= "prometheus"` is provided to this constructor, it is possible
/// to filter this time series with Prometheus's query language by appending
/// a set of labels to match in curly braces ({})
///
/// http_requests_total{job= "prometheus"}
///
/// For further information see: [Quering Basics]
/// (https://prometheus.io/docs/prometheus/latest/querying/basics/)
///
/// \param name Set the metric name.
/// \param help Set an additional description.
/// \param constant_labels Assign a set of key-value pairs (= labels) to the
/// metric. All these labels are propagated to each time series within the
/// metric.
/// \throw std::runtime_exception on invalid metric or label names.
Family (Metric::Type type_, const std::string& name_, const std::string& help_, const Labels& constant_labels_)
: type(type_), name(name_), help(help_), constant_labels(constant_labels_) {
if (!CheckMetricName(name_))
throw std::invalid_argument("Invalid metric name");
for (const Label& label_pair : constant_labels) {
const std::string& label_name = label_pair.first;
if (!CheckLabelName(label_name))
throw std::invalid_argument("Invalid label name");
}
}
/// \brief Remove the given dimensional data.
///
/// \param metric Dimensional data to be removed. The function does nothing,
/// if the given metric was not returned by Add().
void Remove (Metric* metric) {
std::lock_guard<std::mutex> lock{ mutex };
if (labels_reverse_lookup.count(metric) == 0)
return;
const Hash hash = labels_reverse_lookup.at(metric);
metrics.erase(hash);
labels.erase(hash);
labels_reverse_lookup.erase(metric);
}
/// \brief Returns true if the dimensional data with the given labels exist
///
/// \param labels A set of key-value pairs (= labels) of the dimensional data.
bool Has (const Labels& labels) const {
const Hash hash = hash_labels (labels);
std::lock_guard<std::mutex> lock{ mutex };
return metrics.find(hash) != metrics.end();
}
/// \brief Returns the name for this family.
///
/// \return The family name.
const std::string& GetName() const {
return name;
}
/// \brief Returns the constant labels for this family.
///
/// \return All constant labels as key-value pairs.
const Labels& GetConstantLabels() const {
return constant_labels;
}
/// \brief Returns the current value of each dimensional data.
///
/// Collect is called by the Registry when collecting metrics.
///
/// \return Zero or more samples for each dimensional data.
MetricFamilies Collect() const override {
std::lock_guard<std::mutex> lock{ mutex };
if (metrics.empty())
return {};
MetricFamily family = MetricFamily{};
family.type = type;
family.name = name;
family.help = help;
family.metric.reserve(metrics.size());
for (const std::pair<const Hash, MetricPtr>& metric_pair : metrics) {
ClientMetric collected = metric_pair.second->Collect();
for (const Label& constant_label : constant_labels)
collected.label.emplace_back(ClientMetric::Label(constant_label.first, constant_label.second));
const Labels& metric_labels = labels.at(metric_pair.first);
for (const Label& metric_label : metric_labels)
collected.label.emplace_back(ClientMetric::Label(metric_label.first, metric_label.second));
family.metric.push_back(std::move(collected));
}
return { family };
}
};
template <typename CustomMetric>
class CustomFamily : public Family {
public:
static const Metric::Type static_type = CustomMetric::static_type;
CustomFamily(const std::string& name, const std::string& help, const Family::Labels& constant_labels)
: Family(static_type, name, help, constant_labels) {}
/// \brief Add a new dimensional data.
///
/// Each new set of labels adds a new dimensional data and is exposed in
/// Prometheus as a time series. It is possible to filter the time series
/// with Prometheus's query language by appending a set of labels to match in
/// curly braces ({})
///
/// http_requests_total{job= "prometheus",method= "POST"}
///
/// \param labels Assign a set of key-value pairs (= labels) to the
/// dimensional data. The function does nothing, if the same set of labels
/// already exists.
/// \param args Arguments are passed to the constructor of metric type T. See
/// Counter, Gauge, Histogram or Summary for required constructor arguments.
/// \return Return the newly created dimensional data or - if a same set of
/// labels already exists - the already existing dimensional data.
/// \throw std::runtime_exception on invalid label names.
template <typename... Args>
CustomMetric& Add (const Labels& new_labels, Args&&... args) {
const Hash hash = hash_labels (new_labels);
std::lock_guard<std::mutex> lock{ mutex };
// try to find existing one
auto metrics_iter = metrics.find(hash);
if (metrics_iter != metrics.end()) {
#ifndef NDEBUG
// check that we have stored labels for this existing metric
auto labels_iter = labels.find(hash);
assert(labels_iter != labels.end());
const Labels& stored_labels = labels_iter->second;
assert(new_labels == stored_labels);
#endif
return dynamic_cast<CustomMetric&>(*metrics_iter->second);
}
// check labels before create the new one
for (const Label& label_pair : new_labels) {
const std::string& label_name = label_pair.first;
if (!CheckLabelName(label_name))
throw std::invalid_argument("Invalid label name");
if (constant_labels.count(label_name))
throw std::invalid_argument("Label name already present in constant labels");
}
// create new one
std::unique_ptr<CustomMetric> metric_ptr (new CustomMetric(std::forward<Args>(args)...));
CustomMetric& metric = *metric_ptr;
const auto stored_metric = metrics.insert(std::make_pair(hash, std::move(metric_ptr)));
assert(stored_metric.second);
labels.insert({ hash, new_labels });
labels_reverse_lookup.insert({ stored_metric.first->second.get(), hash });
return metric;
}
/// \brief Return a builder to configure and register a Counter metric.
///
/// @copydetails family_base_t<>::family_base_t()
///
/// Example usage:
///
/// \code
/// auto registry = std::make_shared<Registry>();
/// auto& counter_family = prometheus::Counter_family::build("some_name", "Additional description.", {{"key", "value"}}, *registry);
///
/// ...
/// \endcode
///
/// \return An object of unspecified type T, i.e., an implementation detail
/// except that it has the following members:
///
/// - Name(const std::string&) to set the metric name,
/// - Help(const std::string&) to set an additional description.
/// - Label(const std::map<std::string, std::string>&) to assign a set of
/// key-value pairs (= labels) to the metric.
///
/// To finish the configuration of the Counter metric, register it with
/// Register(Registry&).
template <typename Registry>
static CustomFamily& Build(Registry& registry, const std::string& name, const std::string& help, const Family::Labels& labels = Family::Labels()) {
return registry.template Add<CustomFamily>(name, help, labels);
}
};
} // namespace prometheus

View File

@ -0,0 +1,205 @@
#pragma once
#include "prometheus/collectable.h"
#include "prometheus/text_serializer.h"
#include "prometheus/metric_family.h"
#include <jdl/httpclientlite.h>
#include <memory>
#include <mutex>
#include <string>
#include <sstream>
#include <vector>
#include <map>
#include <future>
#include <algorithm>
#include <utility>
namespace prometheus {
class Gateway {
using CollectableEntry = std::pair<std::weak_ptr<Collectable>, std::string>;
std::string job_uri_;
std::string labels_;
std::mutex mutex_;
std::vector<CollectableEntry> collectables_;
enum class HttpMethod : uint8_t{
Post,
Put,
Delete,
};
public:
using Labels = std::map<std::string, std::string>;
Gateway(const std::string host, const std::string port,
const std::string jobname, const Labels& labels = {})
: job_uri_(host + ':' + port + std::string("/metrics/job/") + jobname)
, labels_{}
{
std::stringstream label_strm;
for (const auto& label : labels) {
label_strm << "/" << label.first << "/" << label.second;
}
labels_ = label_strm.str();
}
void RegisterCollectable(const std::weak_ptr<Collectable>& collectable,
const Labels* labels = nullptr) {
std::stringstream label_strm;
if (labels != nullptr) {
for (const auto& label : *labels) {
label_strm << "/" << label.first << "/" << label.second;
}
}
CleanupStalePointers(collectables_);
collectables_.emplace_back(std::make_pair(collectable, label_strm.str()));
}
static const Labels GetInstanceLabel(const std::string& hostname) {
if (hostname.empty()) {
return Gateway::Labels{};
}
return Gateway::Labels{{"instance", hostname}};
}
// Push metrics to the given pushgateway.
int Push() {
return push(HttpMethod::Post);
}
std::future<int> AsyncPush() {
return async_push(HttpMethod::Post);
}
// PushAdd metrics to the given pushgateway.
int PushAdd() {
return push(HttpMethod::Put);
}
std::future<int> AsyncPushAdd() {
return async_push(HttpMethod::Put);
}
// Delete metrics from the given pushgateway.
int Delete() {
return performHttpRequest(HttpMethod::Delete, job_uri_, {});
}
// Delete metrics from the given pushgateway.
std::future<int> AsyncDelete() {
return std::async(std::launch::async, [&] { return Delete(); });
}
private:
std::string getUri(const CollectableEntry& collectable) const {
return (job_uri_ + labels_ + collectable.second);
}
int performHttpRequest(HttpMethod /*method*/, const std::string& uri_str, const std::string& body) {
std::lock_guard<std::mutex> l(mutex_);
/* Stub function. The implementation will be later, after connecting the
* additional library of HTTP requests. */
jdl::URI uri(uri_str);
jdl::HTTPResponse response = jdl::HTTPClient::request(jdl::HTTPClient::m_post, uri, body);
return std::stoi(response.response);
}
int push(HttpMethod method) {
const auto serializer = TextSerializer{};
for (const auto& wcollectable : collectables_) {
auto collectable = wcollectable.first.lock();
if (!collectable) {
continue;
}
auto metrics = collectable->Collect();
auto uri = getUri(wcollectable);
std::stringstream body;
serializer.Serialize(body, metrics);
std::string body_str = body.str();
auto status_code = performHttpRequest(method, uri, body_str);
if (status_code < 100 || status_code >= 400) {
return status_code;
}
}
return 200;
}
std::future<int> async_push(HttpMethod method) {
const auto serializer = TextSerializer{};
std::vector<std::future<int>> futures;
for (const auto& wcollectable : collectables_) {
auto collectable = wcollectable.first.lock();
if (!collectable) {
continue;
}
auto metrics = collectable->Collect();
auto uri = getUri(wcollectable);
std::stringstream body;
serializer.Serialize(body, metrics);
auto body_ptr = std::make_shared<std::string>(body.str());
futures.emplace_back(std::async(std::launch::async, [method, &uri, &body_ptr, this] {
return performHttpRequest(method, uri, *body_ptr);
}));
}
const auto reduceFutures = [](std::vector<std::future<int>> lfutures) {
auto final_status_code = 200;
for (auto& future : lfutures) {
auto status_code = future.get();
if (status_code < 100 || status_code >= 400) {
final_status_code = status_code;
}
}
return final_status_code;
};
return std::async(std::launch::async, reduceFutures, std::move(futures));
}
static void CleanupStalePointers(std::vector<CollectableEntry>& collectables) {
collectables.erase(std::remove_if(std::begin(collectables), std::end(collectables),
[](const CollectableEntry& candidate) {
return candidate.first.expired();
}),
std::end(collectables));
}
};
} // namespace prometheus

View File

@ -0,0 +1,128 @@
#pragma once
#include "prometheus/atomic_floating.h"
#include "prometheus/metric.h"
#include "prometheus/family.h"
#include "prometheus/builder.h"
#include <atomic>
#include <ctime>
namespace prometheus {
/// \brief A gauge metric to represent a value that can arbitrarily go up and
/// down.
///
/// The class represents the metric type gauge:
/// https://prometheus.io/docs/concepts/metric_types/#gauge
///
/// Gauges are typically used for measured values like temperatures or current
/// memory usage, but also "counts" that can go up and down, like the number of
/// running processes.
///
/// The class is thread-safe. No concurrent call to any API of this type causes
/// a data race.
template <typename Value_ = uint64_t>
class Gauge : public Metric {
std::atomic<Value_> value { 0 };
public:
using Value = Value_;
using Family = CustomFamily<Gauge<Value>>;
static const Metric::Type static_type = Metric::Type::Gauge;
Gauge() : Metric (static_type) {} ///< \brief Create a gauge that starts at 0.
Gauge(const Value value_) : Metric(static_type), value{ value_ } {} ///< \brief Create a gauge that starts at the given amount.
// original API
void Increment() { ++value; } ///< \brief Increment the gauge by 1.
void Increment(const Value& val) { value += val; } ///< \brief Increment the gauge by the given amount.
void Decrement() { --value; } ///< \brief Decrement the gauge by 1.
void Decrement(const Value& val) { value -= val; } ///< \brief Decrement the gauge by the given amount.
void SetToCurrentTime() { ///< \brief Set the gauge to the current unixtime in seconds.
const time_t time = std::time(nullptr);
value = static_cast<Value>(time);
}
void Set(const Value& val) { value = val; } ///< \brief Set the gauge to the given value.
const Value Get() const { return value; } ///< \brief Get the current value of the gauge.
virtual ClientMetric Collect() const { ///< \brief Get the current value of the gauge. Collect is called by the Registry when collecting metrics.
ClientMetric metric;
metric.gauge.value = static_cast<double>(value);
return metric;
}
// new API
Gauge& operator ++() {
++value;
return *this;
}
Gauge& operator++ (int) {
++value;
return *this;
}
Gauge& operator --() {
--value;
return *this;
}
Gauge& operator-- (int) {
--value;
return *this;
}
Gauge& operator+=(const Value& val) {
value += val;
return *this;
}
Gauge& operator-=(const Value& val) {
value -= val;
return *this;
}
};
/// \brief Return a builder to configure and register a Gauge metric.
///
/// @copydetails Family<>::Family()
///
/// Example usage:
///
/// \code
/// auto registry = std::make_shared<Registry>();
/// auto& gauge_family = prometheus::BuildGauge()
/// .Name("some_name")
/// .Help("Additional description.")
/// .Labels({{"key", "value"}})
/// .Register(*registry);
///
/// ...
/// \endcode
///
/// \return An object of unspecified type T, i.e., an implementation detail
/// except that it has the following members:
///
/// - Name(const std::string&) to set the metric name,
/// - Help(const std::string&) to set an additional description.
/// - Label(const std::map<std::string, std::string>&) to assign a set of
/// key-value pairs (= labels) to the metric.
///
/// To finish the configuration of the Gauge metric register it with
/// Register(Registry&).
using BuildGauge = Builder<Gauge<double>>;
} // namespace prometheus

View File

@ -0,0 +1,50 @@
#pragma once
#include <cstddef>
#include <functional>
namespace prometheus {
namespace detail {
/// \brief Combine a hash value with nothing.
/// It's the boundary condition of this serial functions.
///
/// \param seed Not effect.
inline void hash_combine(std::size_t *) {}
/// \brief Combine the given hash value with another obeject.
///
/// \param seed The given hash value. It's a input/output parameter.
/// \param value The object that will be combined with the given hash value.
template <typename T>
inline void hash_combine(std::size_t *seed, const T &value) {
*seed ^= std::hash<T>{}(value) + 0x9e3779b9 + (*seed << 6) + (*seed >> 2);
}
/// \brief Combine the given hash value with another objects. It's a recursion。
///
/// \param seed The give hash value. It's a input/output parameter.
/// \param value The object that will be combined with the given hash value.
/// \param args The objects that will be combined with the given hash value.
template <typename T, typename... Types>
inline void hash_combine(std::size_t *seed, const T &value,
const Types &...args) {
hash_combine(seed, value);
hash_combine(seed, args...);
}
/// \brief Compute a hash value of the given args.
///
/// \param args The arguments that will be computed hash value.
/// \return The hash value of the given args.
template <typename... Types>
inline std::size_t hash_value(const Types &...args) {
std::size_t seed = 0;
hash_combine(&seed, args...);
return seed;
}
} // namespace detail
} // namespace prometheus

View File

@ -0,0 +1,154 @@
#pragma once
#include <vector>
#include <cassert>
#include <algorithm>
#include "prometheus/metric.h"
#include "prometheus/family.h"
#include "prometheus/counter.h"
namespace prometheus {
/// \brief A histogram metric to represent aggregatable distributions of events.
///
/// This class represents the metric type histogram:
/// https://prometheus.io/docs/concepts/metric_types/#histogram
///
/// A histogram tracks the number of observations and the sum of the observed
/// values, allowing to calculate the average of the observed values.
///
/// At its core a histogram has a counter per bucket. The sum of observations
/// also behaves like a counter as long as there are no negative observations.
///
/// See https://prometheus.io/docs/practices/histograms/ for detailed
/// explanations of histogram usage and differences to summaries.
///
/// The class is thread-safe. No concurrent call to any API of this type causes
/// a data race.
template <typename Value_ = uint64_t>
class Histogram : Metric {
using BucketBoundaries = std::vector<Value_>;
const BucketBoundaries bucket_boundaries_;
std::vector<Counter<Value_>> bucket_counts_;
Gauge<Value_> sum_;
public:
using Value = Value_;
using Family = CustomFamily<Histogram<Value>>;
static const Metric::Type static_type = Metric::Type::Histogram;
/// \brief Create a histogram with manually chosen buckets.
///
/// The BucketBoundaries are a list of monotonically increasing values
/// representing the bucket boundaries. Each consecutive pair of values is
/// interpreted as a half-open interval [b_n, b_n+1) which defines one bucket.
///
/// There is no limitation on how the buckets are divided, i.e, equal size,
/// exponential etc..
///
/// The bucket boundaries cannot be changed once the histogram is created.
Histogram (const BucketBoundaries& buckets)
: Metric(static_type), bucket_boundaries_{ buckets }, bucket_counts_{ buckets.size() + 1 }, sum_{} {
assert(std::is_sorted(std::begin(bucket_boundaries_),
std::end(bucket_boundaries_)));
}
/// \brief Observe the given amount.
///
/// The given amount selects the 'observed' bucket. The observed bucket is
/// chosen for which the given amount falls into the half-open interval [b_n,
/// b_n+1). The counter of the observed bucket is incremented. Also the total
/// sum of all observations is incremented.
void Observe(const Value value) {
// TODO: determine bucket list size at which binary search would be faster
const auto bucket_index = static_cast<std::size_t>(std::distance(
bucket_boundaries_.begin(),
std::find_if(
std::begin(bucket_boundaries_), std::end(bucket_boundaries_),
[value](const double boundary) { return boundary >= value; })));
sum_.Increment(value);
bucket_counts_[bucket_index].Increment();
}
/// \brief Observe multiple data points.
///
/// Increments counters given a count for each bucket. (i.e. the caller of
/// this function must have already sorted the values into buckets).
/// Also increments the total sum of all observations by the given value.
void ObserveMultiple(const std::vector<Value>& bucket_increments,
const Value sum_of_values) {
if (bucket_increments.size() != bucket_counts_.size()) {
throw std::length_error(
"The size of bucket_increments was not equal to"
"the number of buckets in the histogram.");
}
sum_.Increment(sum_of_values);
for (std::size_t i{ 0 }; i < bucket_counts_.size(); ++i) {
bucket_counts_[i].Increment(bucket_increments[i]);
}
}
/// \brief Get the current value of the counter.
///
/// Collect is called by the Registry when collecting metrics.
virtual ClientMetric Collect() const {
auto metric = ClientMetric{};
auto cumulative_count = 0ULL;
metric.histogram.bucket.reserve(bucket_counts_.size());
for (std::size_t i{0}; i < bucket_counts_.size(); ++i) {
cumulative_count += static_cast<std::size_t>(bucket_counts_[i].Value());
auto bucket = ClientMetric::Bucket{};
bucket.cumulative_count = cumulative_count;
bucket.upper_bound = (i == bucket_boundaries_.size()
? std::numeric_limits<double>::infinity()
: bucket_boundaries_[i]);
metric.histogram.bucket.push_back(std::move(bucket));
}
metric.histogram.sample_count = cumulative_count;
metric.histogram.sample_sum = sum_.Get();
return metric;
}
};
/// \brief Return a builder to configure and register a Histogram metric.
///
/// @copydetails Family<>::Family()
///
/// Example usage:
///
/// \code
/// auto registry = std::make_shared<Registry>();
/// auto& histogram_family = prometheus::BuildHistogram()
/// .Name("some_name")
/// .Help("Additional description.")
/// .Labels({{"key", "value"}})
/// .Register(*registry);
///
/// ...
/// \endcode
///
/// \return An object of unspecified type T, i.e., an implementation detail
/// except that it has the following members:
///
/// - Name(const std::string&) to set the metric name,
/// - Help(const std::string&) to set an additional description.
/// - Label(const std::map<std::string, std::string>&) to assign a set of
/// key-value pairs (= labels) to the metric.
///
/// To finish the configuration of the Histogram metric register it with
/// Register(Registry&).
using BuildHistogram = Builder<Histogram<double>>;
} // namespace prometheus

View File

@ -0,0 +1,29 @@
#pragma once
#include <stdint.h>
#include "client_metric.h"
namespace prometheus {
class Metric {
public:
enum class Type {
Counter,
Gauge,
Summary,
Histogram,
Untyped,
};
Type type;
Metric (Type type_) : type(type_) {}
virtual ~Metric() = default;
virtual ClientMetric Collect() const = 0;
};
} // namespace prometheus

View File

@ -0,0 +1,18 @@
#pragma once
#include <string>
#include <vector>
#include "metric.h"
#include "prometheus/client_metric.h"
namespace prometheus {
struct MetricFamily {
Metric::Type type;
std::string name;
std::string help;
std::vector<ClientMetric> metric;
};
} // namespace prometheus

View File

@ -0,0 +1,86 @@
#pragma once
#include <thread>
#include <chrono>
#include <string>
#include "registry.h"
#include "text_serializer.h"
#include <jdl/httpclientlite.h>
namespace prometheus {
class PushToServer {
std::chrono::seconds period { 1 };
std::string uri { "" };
std::thread worker_thread { &PushToServer::worker_function, this };
std::shared_ptr<Registry> registry_ptr { nullptr };
bool must_die { false };
void push_data() {
if (registry_ptr) {
if (!uri.empty()) {
std::stringstream body_strm;
TextSerializer::Serialize(body_strm, registry_ptr->Collect());
std::string body = body_strm.str();
jdl::HTTPResponse response = jdl::HTTPClient::request(jdl::HTTPClient::m_post, jdl::URI(uri), body);
}
}
}
void worker_function() {
// it need for fast shutdown this thread when SaveToFile destructor is called
const uint64_t divider = 100;
uint64_t fraction = divider;
for (;;) {
std::chrono::milliseconds period_ms
= std::chrono::duration_cast<std::chrono::milliseconds>(period);
std::this_thread::sleep_for( period_ms / divider );
if (must_die) {
push_data();
return;
}
if (--fraction == 0) {
fraction = divider;
push_data();
}
}
}
public:
PushToServer() {
jdl::init_socket();
}
~PushToServer() {
must_die = true;
worker_thread.join();
jdl::deinit_socket();
}
PushToServer(std::shared_ptr<Registry>& registry_, const std::chrono::seconds& period_, const std::string& uri_) {
set_registry(registry_);
set_delay(period_);
set_uri(uri_);
}
void set_delay (const std::chrono::seconds& new_period) {
period = new_period;
}
void set_uri (const std::string& uri_) {
uri = std::move(uri_);
}
void set_registry (std::shared_ptr<Registry>& new_registry_ptr) {
registry_ptr = new_registry_ptr;
}
};
}

View File

@ -0,0 +1,123 @@
#pragma once
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "prometheus/collectable.h"
#include "prometheus/family.h"
namespace prometheus {
/// \brief Manages the collection of a number of metrics.
///
/// The Registry is responsible to expose data to a class/method/function
/// "bridge", which returns the metrics in a format Prometheus supports.
///
/// The key class is the Collectable. This has a method - called Collect() -
/// that returns zero or more metrics and their samples. The metrics are
/// represented by the class Family<>, which implements the Collectable
/// interface. A new metric is registered with BuildCounter(), BuildGauge(),
/// BuildHistogram() or BuildSummary().
///
/// The class is thread-safe. No concurrent call to any API of this type causes
/// a data race.
class Registry : public Collectable {
public:
/// \brief How to deal with repeatedly added family names for a type.
///
/// Adding a family with the same name but different types is always an error
/// and will lead to an exception.
enum class InsertBehavior {
/// \brief If a family with the same name and labels already exists return
/// the existing one. If no family with that name exists create it.
/// Otherwise throw.
Merge,
/// \brief Throws if a family with the same name already exists.
Throw,
/// \brief Never merge and always create a new family. This violates the
/// prometheus specification but was the default behavior in earlier
/// versions
NonStandardAppend,
};
using FamilyPtr = std::unique_ptr<Family>;
using Families = std::vector<FamilyPtr>;
const InsertBehavior insert_behavior;
mutable std::mutex mutex;
Families families;
/// \brief name Create a new registry.
///
/// \param insert_behavior How to handle families with the same name.
Registry (InsertBehavior insert_behavior_ = InsertBehavior::Merge)
: insert_behavior(insert_behavior_) {}
/// \brief Returns a list of metrics and their samples.
///
/// Every time the Registry is scraped it calls each of the metrics Collect
/// function.
///
/// \return Zero or more metrics and their samples.
virtual MetricFamilies Collect() const {
std::lock_guard<std::mutex> lock{ mutex };
MetricFamilies results;
for (const FamilyPtr& family_ptr : families) {
MetricFamilies metrics = family_ptr->Collect();
results.insert(results.end(), std::make_move_iterator(metrics.begin()), std::make_move_iterator(metrics.end()));
}
return results;
}
template <typename CustomFamily>
CustomFamily& Add (const std::string& name, const std::string& help, const Family::Labels& labels) {
std::lock_guard<std::mutex> lock{ mutex };
bool found_one_but_not_merge = false;
for (const FamilyPtr& family_ptr : families) {
if (family_ptr->GetName() == name) {
if (family_ptr->type != CustomFamily::static_type) // found family with this name and with different type
throw std::invalid_argument("Family name already exists with different type");
else { // found family with this name and the same type
switch (insert_behavior) {
case InsertBehavior::Throw:
throw std::invalid_argument("Family name already exists");
case InsertBehavior::Merge:
if (family_ptr->GetConstantLabels() == labels)
return dynamic_cast<CustomFamily&>(*family_ptr);
else // this strange rule was in previos version prometheus cpp
found_one_but_not_merge = true;
case InsertBehavior::NonStandardAppend:
continue;
}
}
}
}
if (found_one_but_not_merge) // this strange rule was in previos version prometheus cpp
throw std::invalid_argument("Family name already exists with different labels");
std::unique_ptr<CustomFamily> new_family_ptr (new CustomFamily(name, help, labels));
CustomFamily& new_family = *new_family_ptr;
families.push_back(std::move(new_family_ptr));
return new_family;
}
};
} // namespace prometheus

View File

@ -0,0 +1,83 @@
#pragma once
#include <thread>
#include <chrono>
#include <string>
#include <fstream>
#include <memory>
#include "registry.h"
#include "text_serializer.h"
namespace prometheus {
class SaveToFile {
std::chrono::seconds period { 1 };
std::string filename;
std::thread worker_thread { &SaveToFile::worker_function, this };
std::shared_ptr<Registry> registry_ptr { nullptr };
bool must_die { false };
void save_data() {
if (registry_ptr) {
std::fstream out_file_stream;
out_file_stream.open(filename, std::fstream::out | std::fstream::binary);
if (out_file_stream.is_open()) {
TextSerializer::Serialize(out_file_stream, registry_ptr->Collect());
out_file_stream.close();
}
}
}
void worker_function() {
// it need for fast shutdown this thread when SaveToFile destructor is called
const uint64_t divider = 100;
uint64_t fraction = divider;
for (;;) {
std::chrono::milliseconds period_ms
= std::chrono::duration_cast<std::chrono::milliseconds>(period);
std::this_thread::sleep_for( period_ms / divider );
if (must_die) {
save_data();
return;
}
if (--fraction == 0) {
fraction = divider;
save_data();
}
}
}
public:
SaveToFile() = default;
~SaveToFile() {
must_die = true;
worker_thread.join();
}
SaveToFile(std::shared_ptr<Registry>& registry_, const std::chrono::seconds& period_, const std::string& filename_) {
set_registry(registry_);
set_delay(period_);
set_out_file(filename_);
}
void set_delay (const std::chrono::seconds& new_period) {
period = new_period;
}
bool set_out_file (const std::string& filename_) {
filename = filename_;
std::fstream out_file_stream;
out_file_stream.open(filename, std::fstream::out | std::fstream::binary);
bool open_success = out_file_stream.is_open();
out_file_stream.close();
return open_success;
}
void set_registry (std::shared_ptr<Registry>& new_registry_ptr) {
registry_ptr = new_registry_ptr;
}
};
}

View File

@ -0,0 +1,154 @@
#pragma once
#include <chrono>
#include <cstdint>
#include <mutex>
#include <vector>
#include "prometheus/metric.h"
#include "prometheus/family.h"
#include "prometheus/detail/ckms_quantiles.h"
#include "prometheus/detail/time_window_quantiles.h"
#include "prometheus/builder.h"
namespace prometheus {
/// \brief A summary metric samples observations over a sliding window of time.
///
/// This class represents the metric type summary:
/// https://prometheus.io/docs/instrumenting/writing_clientlibs/#summary
///
/// A summary provides a total count of observations and a sum of all observed
/// values. In contrast to a histogram metric it also calculates configurable
/// Phi-quantiles over a sliding window of time.
///
/// The essential difference between summaries and histograms is that summaries
/// calculate streaming Phi-quantiles on the client side and expose them
/// directly, while histograms expose bucketed observation counts and the
/// calculation of quantiles from the buckets of a histogram happens on the
/// server side:
/// https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile.
///
/// Note that Phi designates the probability density function of the standard
/// Gaussian distribution.
///
/// See https://prometheus.io/docs/practices/histograms/ for detailed
/// explanations of Phi-quantiles, summary usage, and differences to histograms.
///
/// The class is thread-safe. No concurrent call to any API of this type causes
/// a data race.
class Summary : Metric {
public:
using Value = double;
using Family = CustomFamily<Summary>;
static const Metric::Type static_type = Metric::Type::Summary;
using Quantiles = std::vector<detail::CKMSQuantiles::Quantile>;
const Quantiles quantiles_;
mutable std::mutex mutex_;
std::uint64_t count_;
double sum_;
detail::TimeWindowQuantiles quantile_values_;
public:
/// \brief Create a summary metric.
///
/// \param quantiles A list of 'targeted' Phi-quantiles. A targeted
/// Phi-quantile is specified in the form of a Phi-quantile and tolerated
/// error. For example a Quantile{0.5, 0.1} means that the median (= 50th
/// percentile) should be returned with 10 percent error or a Quantile{0.2,
/// 0.05} means the 20th percentile with 5 percent tolerated error. Note that
/// percentiles and quantiles are the same concept, except percentiles are
/// expressed as percentages. The Phi-quantile must be in the interval [0, 1].
/// Note that a lower tolerated error for a Phi-quantile results in higher
/// usage of resources (memory and cpu) to calculate the summary.
///
/// The Phi-quantiles are calculated over a sliding window of time. The
/// sliding window of time is configured by max_age and age_buckets.
///
/// \param max_age Set the duration of the time window, i.e., how long
/// observations are kept before they are discarded. The default value is 60
/// seconds.
///
/// \param age_buckets Set the number of buckets of the time window. It
/// determines the number of buckets used to exclude observations that are
/// older than max_age from the summary, e.g., if max_age is 60 seconds and
/// age_buckets is 5, buckets will be switched every 12 seconds. The value is
/// a trade-off between resources (memory and cpu for maintaining the bucket)
/// and how smooth the time window is moved. With only one age bucket it
/// effectively results in a complete reset of the summary each time max_age
/// has passed. The default value is 5.
Summary(const Quantiles& quantiles, std::chrono::milliseconds max_age = std::chrono::seconds{ 60 }, int age_buckets = 5)
: Metric(static_type), quantiles_{ quantiles }, count_{ 0 }, sum_{ 0 }, quantile_values_(quantiles_, max_age, age_buckets) {}
/// \brief Observe the given amount.
void Observe(const double value) {
std::lock_guard<std::mutex> lock(mutex_);
count_ += 1;
sum_ += value;
quantile_values_.insert(value);
}
/// \brief Get the current value of the summary.
///
/// Collect is called by the Registry when collecting metrics.
virtual ClientMetric Collect() const {
auto metric = ClientMetric{};
std::lock_guard<std::mutex> lock(mutex_);
metric.summary.quantile.reserve(quantiles_.size());
for (const auto& quantile : quantiles_) {
auto metricQuantile = ClientMetric::Quantile{};
metricQuantile.quantile = quantile.quantile;
metricQuantile.value = quantile_values_.get(quantile.quantile);
metric.summary.quantile.push_back(std::move(metricQuantile));
}
metric.summary.sample_count = count_;
metric.summary.sample_sum = sum_;
return metric;
}
};
/// \brief Return a builder to configure and register a Summary metric.
///
/// @copydetails Family<>::Family()
///
/// Example usage:
///
/// \code
/// auto registry = std::make_shared<Registry>();
/// auto& summary_family = prometheus::BuildSummary()
/// .Name("some_name")
/// .Help("Additional description.")
/// .Labels({{"key", "value"}})
/// .Register(*registry);
///
/// ...
/// \endcode
///
/// \return An object of unspecified type T, i.e., an implementation detail
/// except that it has the following members:
///
/// - Name(const std::string&) to set the metric name,
/// - Help(const std::string&) to set an additional description.
/// - Label(const std::map<std::string, std::string>&) to assign a set of
/// key-value pairs (= labels) to the metric.
///
/// To finish the configuration of the Summary metric register it with
/// Register(Registry&).
using BuildSummary = Builder<Summary>;
} // namespace prometheus

View File

@ -0,0 +1,211 @@
#pragma once
#include <iosfwd>
#include <vector>
#include <array>
#include <math.h>
#include <ostream>
#include "prometheus/metric_family.h"
#if __cpp_lib_to_chars >= 201611L
#include <charconv>
#endif
namespace prometheus {
class TextSerializer {
// Write a double as a string, with proper formatting for infinity and NaN
static void WriteValue (std::ostream& out, double value) {
if (std::isnan(value))
out << "Nan";
else if (std::isinf(value))
out << (value < 0 ? "-Inf" : "+Inf");
else {
std::array<char, 128> buffer;
#if __cpp_lib_to_chars >= 201611L
auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), value);
if (ec != std::errc()) {
throw std::runtime_error("Could not convert double to string: " +
std::make_error_code(ec).message());
}
out.write(buffer.data(), ptr - buffer.data());
#else
int wouldHaveWritten = std::snprintf(buffer.data(), buffer.size(), "%.*g", std::numeric_limits<double>::max_digits10 - 1, value);
if (wouldHaveWritten <= 0 || static_cast<std::size_t>(wouldHaveWritten) >= buffer.size()) {
throw std::runtime_error("Could not convert double to string");
}
out.write(buffer.data(), wouldHaveWritten);
#endif
}
}
static void WriteValue(std::ostream& out, const std::string& value) {
for (auto c : value) {
switch (c) {
case '\n': out << '\\' << 'n'; break;
case '\\': out << '\\' << c; break;
case '"': out << '\\' << c; break;
default: out << c; break;
}
}
}
// Write a line header: metric name and labels
template <typename T = std::string>
static void WriteHead(
std::ostream& out,
const MetricFamily& family,
const ClientMetric& metric,
const std::string& suffix = "",
const std::string& extraLabelName = "",
const T& extraLabelValue = T()) {
out << family.name << suffix;
if (!metric.label.empty() || !extraLabelName.empty()) {
out << "{";
const char* prefix = "";
for (auto& lp : metric.label) {
out << prefix << lp.name << "=\"";
WriteValue(out, lp.value);
out << "\"";
prefix = ",";
}
if (!extraLabelName.empty()) {
out << prefix << extraLabelName << "=\"";
WriteValue(out, extraLabelValue);
out << "\"";
}
out << "}";
}
out << " ";
}
// Write a line trailer: timestamp
static void WriteTail(std::ostream& out, const ClientMetric& metric) {
if (metric.timestamp_ms != 0) {
out << " " << metric.timestamp_ms;
}
out << "\n";
}
static void SerializeCounter(std::ostream& out, const MetricFamily& family, const ClientMetric& metric) {
WriteHead(out, family, metric);
WriteValue(out, metric.counter.value);
WriteTail(out, metric);
}
static void SerializeGauge(std::ostream& out, const MetricFamily& family, const ClientMetric& metric) {
WriteHead(out, family, metric);
WriteValue(out, metric.gauge.value);
WriteTail(out, metric);
}
static void SerializeSummary(std::ostream& out, const MetricFamily& family, const ClientMetric& metric) {
auto& sum = metric.summary;
WriteHead(out, family, metric, "_count");
out << sum.sample_count;
WriteTail(out, metric);
WriteHead(out, family, metric, "_sum");
WriteValue(out, sum.sample_sum);
WriteTail(out, metric);
for (auto& q : sum.quantile) {
WriteHead(out, family, metric, "", "quantile", q.quantile);
WriteValue(out, q.value);
WriteTail(out, metric);
}
}
static void SerializeUntyped(std::ostream& out, const MetricFamily& family, const ClientMetric& metric) {
WriteHead(out, family, metric);
WriteValue(out, metric.untyped.value);
WriteTail(out, metric);
}
static void SerializeHistogram(std::ostream& out, const MetricFamily& family, const ClientMetric& metric) {
auto& hist = metric.histogram;
WriteHead(out, family, metric, "_count");
out << hist.sample_count;
WriteTail(out, metric);
WriteHead(out, family, metric, "_sum");
WriteValue(out, hist.sample_sum);
WriteTail(out, metric);
double last = -std::numeric_limits<double>::infinity();
for (auto& b : hist.bucket) {
WriteHead(out, family, metric, "_bucket", "le", b.upper_bound);
last = b.upper_bound;
out << b.cumulative_count;
WriteTail(out, metric);
}
if (last != std::numeric_limits<double>::infinity()) {
WriteHead(out, family, metric, "_bucket", "le", "+Inf");
out << hist.sample_count;
WriteTail(out, metric);
}
}
static void SerializeFamily(std::ostream& out, const MetricFamily& family) {
if (!family.help.empty()) {
out << "# HELP " << family.name << " " << family.help << "\n";
}
switch (family.type) {
case Metric::Type::Counter:
out << "# TYPE " << family.name << " counter\n";
for (auto& metric : family.metric) {
SerializeCounter(out, family, metric);
}
break;
case Metric::Type::Gauge:
out << "# TYPE " << family.name << " gauge\n";
for (auto& metric : family.metric) {
SerializeGauge(out, family, metric);
}
break;
case Metric::Type::Summary:
out << "# TYPE " << family.name << " summary\n";
for (auto& metric : family.metric) {
SerializeSummary(out, family, metric);
}
break;
case Metric::Type::Untyped:
out << "# TYPE " << family.name << " untyped\n";
for (auto& metric : family.metric) {
SerializeUntyped(out, family, metric);
}
break;
case Metric::Type::Histogram:
out << "# TYPE " << family.name << " histogram\n";
for (auto& metric : family.metric) {
SerializeHistogram(out, family, metric);
}
break;
}
}
public:
static void Serialize (std::ostream& out, const std::vector<MetricFamily>& metrics) {
std::locale saved_locale = out.getloc();
out.imbue(std::locale::classic());
for (auto& family : metrics) {
SerializeFamily(out, family);
}
out.imbue(saved_locale);
}
};
} // namespace prometheus

View File

@ -0,0 +1,62 @@
#pragma once
#include <chrono>
#include <cstddef>
#include <vector>
#include "prometheus/detail/ckms_quantiles.h"
namespace prometheus {
namespace detail {
class TimeWindowQuantiles {
using Clock = std::chrono::steady_clock;
public:
TimeWindowQuantiles(const std::vector<CKMSQuantiles::Quantile>& quantiles,
const Clock::duration max_age, const int age_buckets)
: quantiles_(quantiles),
ckms_quantiles_(age_buckets, CKMSQuantiles(quantiles_)),
current_bucket_(0),
last_rotation_(Clock::now()),
rotation_interval_(max_age / age_buckets) {}
double get(double q) const {
CKMSQuantiles& current_bucket = rotate();
return current_bucket.get(q);
}
void insert(double value) {
rotate();
for (auto& bucket : ckms_quantiles_) {
bucket.insert(value);
}
}
private:
CKMSQuantiles& rotate() const {
auto delta = Clock::now() - last_rotation_;
while (delta > rotation_interval_) {
ckms_quantiles_[current_bucket_].reset();
if (++current_bucket_ >= ckms_quantiles_.size()) {
current_bucket_ = 0;
}
delta -= rotation_interval_;
last_rotation_ += rotation_interval_;
}
return ckms_quantiles_[current_bucket_];
}
const std::vector<CKMSQuantiles::Quantile>& quantiles_;
mutable std::vector<CKMSQuantiles> ckms_quantiles_;
mutable std::size_t current_bucket_;
mutable Clock::time_point last_rotation_;
const Clock::duration rotation_interval_;
};
} // namespace detail
} // namespace prometheus

View File

@ -0,0 +1,31 @@
add_executable (original_example "original_example.cpp" )
target_link_libraries(original_example prometheus-cpp-lite-core)
add_executable (modern_example "modern_example.cpp" )
target_link_libraries(modern_example prometheus-cpp-lite-core)
add_executable (use_counters_in_class_example "use_counters_in_class_example.cpp" )
target_link_libraries(use_counters_in_class_example prometheus-cpp-lite-core)
add_executable (use_gauge_in_class_example "use_gauge_in_class_example.cpp" )
target_link_libraries(use_gauge_in_class_example prometheus-cpp-lite-core)
add_executable (use_benchmark_in_class_example "use_benchmark_in_class_example.cpp" )
target_link_libraries(use_benchmark_in_class_example prometheus-cpp-lite-core)
add_executable (save_to_file_example "save_to_file_example.cpp" )
target_link_libraries(save_to_file_example prometheus-cpp-lite-core)
add_executable (push_to_server_example "push_to_server_example.cpp" )
target_link_libraries(push_to_server_example prometheus-cpp-lite-core)
add_executable (gateway_example "gateway_example.cpp" )
target_link_libraries(gateway_example prometheus-cpp-lite-core)
add_executable (simpleapi_example "simpleapi_example.cpp")
target_link_libraries(simpleapi_example prometheus-cpp-simpleapi)
add_executable (simpleapi_use_in_class_example "simpleapi_use_in_class_example.cpp")
target_link_libraries(simpleapi_use_in_class_example prometheus-cpp-simpleapi)

View File

@ -0,0 +1,64 @@
#include <chrono>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include "prometheus/client_metric.h"
#include "prometheus/counter.h"
#include "prometheus/family.h"
#include "prometheus/gateway.h"
#include "prometheus/registry.h"
#ifdef _WIN32
#include <Winsock2.h>
#else
#include <unistd.h>
#endif
static std::string GetHostName() {
char hostname[1024];
if (::gethostname(hostname, sizeof(hostname))) {
return {};
}
return hostname;
}
int main() {
using namespace prometheus;
// create a push gateway
const auto labels = Gateway::GetInstanceLabel(GetHostName());
Gateway gateway{"127.0.0.1", "9091", "sample_client", labels};
// create a metrics registry with component=main labels applied to all its
// metrics
auto registry = std::make_shared<Registry>();
// add a new counter family to the registry (families combine values with the
// same name, but distinct label dimensions)
auto& counter_family = BuildCounter()
.Name("time_running_seconds_total")
.Help("How many seconds is this server running?")
.Labels({{"label", "value"}})
.Register(*registry);
// add a counter to the metric family
auto& second_counter = counter_family.Add(
{{"another_label", "value"}, {"yet_another_label", "value"}});
// ask the pusher to push the metrics to the pushgateway
gateway.RegisterCollectable(registry);
for (;;) {
std::this_thread::sleep_for(std::chrono::seconds(1));
// increment the counter by one (second)
second_counter.Increment();
// push metrics
auto returnCode = gateway.Push();
std::cout << "returnCode is " << returnCode << std::endl;
}
}

View File

@ -0,0 +1,65 @@
#include <prometheus/registry.h>
#include <prometheus/counter.h>
#include <prometheus/text_serializer.h>
#include <array>
#include <chrono>
#include <cstdlib>
#include <memory>
#include <string>
#include <thread>
#include <iostream>
int main() {
using namespace prometheus;
// for clarity, we deduce the required types
using IntegerCounter = Counter<uint64_t>;
using FloatingCounter = Counter<double>;
using IntegerCounterFamily = CustomFamily<IntegerCounter>;
using FloatingCounterFamily = CustomFamily<FloatingCounter>;
// create a metrics registry
// @note it's the users responsibility to keep the object alive
Registry registry;
// add a new counter family to the registry (families combine values with the
// same name, but distinct label dimensions)
//
// @note please follow the metric-naming best-practices:
// https://prometheus.io/docs/practices/naming/
FloatingCounterFamily& packet_counter{ FloatingCounter::Family::Build(registry, "observed_packets_total", "Number of observed packets") };
// add and remember dimensional data, incrementing those is very cheap
FloatingCounter& tcp_rx_counter{ packet_counter.Add({ {"protocol", "tcp"}, {"direction", "rx"} }) };
FloatingCounter& tcp_tx_counter{ packet_counter.Add({ {"protocol", "tcp"}, {"direction", "tx"} }) };
FloatingCounter& udp_rx_counter{ packet_counter.Add({ {"protocol", "udp"}, {"direction", "rx"} }) };
FloatingCounter& udp_tx_counter{ packet_counter.Add({ {"protocol", "udp"}, {"direction", "tx"} }) };
// add a counter whose dimensional data is not known at compile time
// nevertheless dimensional values should only occur in low cardinality:
// https://prometheus.io/docs/practices/naming/#labels
IntegerCounterFamily& http_requests_counter = IntegerCounter::Family::Build(registry, "http_requests_total", "Number of HTTP requests");
for (;; ) {
std::this_thread::sleep_for(std::chrono::seconds(1));
const int random_value = std::rand();
if (random_value & 1) tcp_rx_counter++;
if (random_value & 2) ++tcp_tx_counter;
if (random_value & 4) udp_rx_counter += 0.5;
if (random_value & 8) udp_tx_counter += 0.7;
const std::array<std::string, 4> methods = { "GET", "PUT", "POST", "HEAD" };
const std::string& method = methods.at(static_cast<std::size_t>(random_value) % methods.size());
// dynamically calling Family<T>.Add() works but is slow and should be avoided
http_requests_counter.Add({ {"method", method} }) += 10;
TextSerializer text_serializer;
text_serializer.Serialize(std::cout, registry.Collect());
}
}

View File

@ -0,0 +1,67 @@
#include <prometheus/registry.h>
#include <prometheus/counter.h>
#include <prometheus/text_serializer.h>
#include <array>
#include <chrono>
#include <cstdlib>
#include <memory>
#include <string>
#include <thread>
#include <iostream>
int main() {
using namespace prometheus;
// create a metrics registry
// @note it's the users responsibility to keep the object alive
auto registry = std::make_shared<Registry>();
// add a new counter family to the registry (families combine values with the
// same name, but distinct label dimensions)
//
// @note please follow the metric-naming best-practices:
// https://prometheus.io/docs/practices/naming/
auto& packet_counter = BuildCounter()
.Name("observed_packets_total")
.Help("Number of observed packets")
.Register(*registry);
// add and remember dimensional data, incrementing those is very cheap
auto& tcp_rx_counter = packet_counter.Add({ {"protocol", "tcp"}, {"direction", "rx"} });
auto& tcp_tx_counter = packet_counter.Add({ {"protocol", "tcp"}, {"direction", "tx"} });
auto& udp_rx_counter = packet_counter.Add({ {"protocol", "udp"}, {"direction", "rx"} });
auto& udp_tx_counter = packet_counter.Add({ {"protocol", "udp"}, {"direction", "tx"} });
// add a counter whose dimensional data is not known at compile time
// nevertheless dimensional values should only occur in low cardinality:
// https://prometheus.io/docs/practices/naming/#labels
auto& http_requests_counter = BuildCounter()
.Name("http_requests_total")
.Help("Number of HTTP requests")
.Register(*registry);
// ask the exposer to scrape the registry on incoming HTTP requests
//exposer.RegisterCollectable(registry);
for ( ;; ) {
std::this_thread::sleep_for(std::chrono::seconds(1));
const auto random_value = std::rand();
if (random_value & 1) tcp_rx_counter.Increment();
if (random_value & 2) tcp_tx_counter.Increment();
if (random_value & 4) udp_rx_counter.Increment(10);
if (random_value & 8) udp_tx_counter.Increment(10);
const std::array<std::string, 4> methods = { "GET", "PUT", "POST", "HEAD" };
auto method = methods.at(static_cast<std::size_t>(random_value) % methods.size());
// dynamically calling Family<T>.Add() works but is slow and should be avoided
http_requests_counter.Add({ {"method", method} }).Increment();
TextSerializer text_serializer;
text_serializer.Serialize(std::cout, registry->Collect());
}
}

View File

@ -0,0 +1,44 @@
#include <prometheus/registry.h>
#include <prometheus/counter.h>
#include <prometheus/push_to_server.h>
#include <array>
#include <chrono>
#include <cstdlib>
#include <memory>
#include <string>
#include <thread>
#include <iostream>
int main() {
using namespace prometheus;
// for clarity, we deduce the required types
using Metric = Counter<uint64_t>;
using Family = Metric::Family;
// create a metrics registry
// @note it's the users responsibility to keep the object alive
std::shared_ptr<Registry> registry_ptr = std::make_shared<Registry>();
PushToServer pusher(registry_ptr, std::chrono::seconds(5),
std::string("http://127.0.0.1:9091/metrics/job/samples/instance/test") );
// add a new counter family to the registry (families combine values with the
// same name, but distinct label dimensions)
//
// @note please follow the metric-naming best-practices:
// https://prometheus.io/docs/practices/naming/
Family& family { Family::Build(*registry_ptr, "our_metric", "some metric") };
// add and remember dimensional data, incrementing those is very cheap
Metric& metric { family.Add({}) };
for (;; ) {
std::this_thread::sleep_for(std::chrono::seconds(1));
const int random_value = std::rand();
metric += random_value % 10;
}
}

View File

@ -0,0 +1,43 @@
#include <prometheus/registry.h>
#include <prometheus/counter.h>
#include <prometheus/save_to_file.h>
#include <array>
#include <chrono>
#include <cstdlib>
#include <memory>
#include <string>
#include <thread>
#include <iostream>
int main() {
using namespace prometheus;
// for clarity, we deduce the required types
using Metric = Counter<uint64_t>;
using Family = Metric::Family;
// create a metrics registry
// @note it's the users responsibility to keep the object alive
std::shared_ptr<Registry> registry_ptr = std::make_shared<Registry>();
SaveToFile saver( registry_ptr, std::chrono::seconds(5), "./metrics.prom" );
// add a new counter family to the registry (families combine values with the
// same name, but distinct label dimensions)
//
// @note please follow the metric-naming best-practices:
// https://prometheus.io/docs/practices/naming/
Family& family { Family::Build(*registry_ptr, "our_metric", "some metric") };
// add and remember dimensional data, incrementing those is very cheap
Metric& metric { family.Add({}) };
for (;; ) {
std::this_thread::sleep_for(std::chrono::seconds(1));
const int random_value = std::rand();
metric += random_value % 10;
}
}

View File

@ -0,0 +1,26 @@
#include <prometheus/simpleapi.h>
int 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++;
}
//return 0;
}

View File

@ -0,0 +1,57 @@
#include <prometheus/simpleapi.h>
// use prometheus namespace
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();
}
};
int 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();
}
}

View File

@ -0,0 +1,59 @@
#include <prometheus/registry.h>
#include <prometheus/benchmark.h>
#include <prometheus/text_serializer.h>
#include <array>
#include <chrono>
#include <cstdlib>
#include <memory>
#include <string>
#include <thread>
#include <iostream>
// use prometheus namespace
using namespace prometheus;
// create global registry for use it from our classes
static Registry globalRegistry;
class MyClass {
Benchmark::Family& benchmarkFamily { Benchmark::Family::Build(globalRegistry,
"benchmark_family", "family for check benchmark functionality") };
Benchmark& benchmark1 { benchmarkFamily.Add({{"number", "1"}}) };
Benchmark& benchmark2 { benchmarkFamily.Add({{"number", "2"}}) };
public:
MyClass() = default;
void member_to_do_something() {
benchmark1.start();
benchmark2.start();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
benchmark1.stop();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
benchmark2.stop();
}
};
int main() {
MyClass myClass;
for (;; ) {
std::this_thread::sleep_for(std::chrono::seconds(1));
myClass.member_to_do_something();
TextSerializer text_serializer;
text_serializer.Serialize(std::cout, globalRegistry.Collect());
}
}

View File

@ -0,0 +1,79 @@
#include <prometheus/registry.h>
#include <prometheus/counter.h>
#include <prometheus/text_serializer.h>
#include <array>
#include <chrono>
#include <cstdlib>
#include <memory>
#include <string>
#include <thread>
#include <iostream>
// use prometheus namespace
using namespace prometheus;
// for clarity, we deduce the required types
using IntegerCounter = Counter<uint64_t>;
using FloatingCounter = Counter<double>;
using IntegerCounterFamily = CustomFamily<IntegerCounter>;
using FloatingCounterFamily = CustomFamily<FloatingCounter>;
// create global registry for use it from our classes
static Registry globalRegistry;
class MyClass {
IntegerCounterFamily& counterFamily1 { IntegerCounter::Family::Build(globalRegistry,
"counter_family_1", "counter for check integer functionality",
{{"type","integer"}} ) };
IntegerCounter& counter11{ counterFamily1.Add({{"number", "1"}}) };
IntegerCounter& counter12{ counterFamily1.Add({{"number", "2"}}) };
IntegerCounter& counter13{ counterFamily1.Add({{"number", "3"}}) };
FloatingCounterFamily& counterFamily2 { FloatingCounter::Family::Build(globalRegistry,
"counter_family_2", "counter for check floating functionality",
{{"type","float"}} ) };
FloatingCounter& counter21{ counterFamily2.Add({{"number", "1"}}) };
FloatingCounter& counter22{ counterFamily2.Add({{"number", "2"}}) };
FloatingCounter& counter23{ counterFamily2.Add({{"number", "3"}}) };
public:
MyClass() = default;
void member_to_do_something() {
const int random_value = std::rand();
if (random_value & 1) counter11++;
if (random_value & 2) counter12++;
if (random_value & 4) counter13++;
if (random_value & 8) counter21++;
if (random_value & 16) counter22++;
if (random_value & 32) counter23++;
}
};
int main() {
MyClass myClass;
for (;; ) {
std::this_thread::sleep_for(std::chrono::seconds(1));
myClass.member_to_do_something();
TextSerializer text_serializer;
text_serializer.Serialize(std::cout, globalRegistry.Collect());
}
}

View File

@ -0,0 +1,79 @@
#include <prometheus/registry.h>
#include <prometheus/gauge.h>
#include <prometheus/text_serializer.h>
#include <array>
#include <chrono>
#include <cstdlib>
#include <memory>
#include <string>
#include <thread>
#include <iostream>
// use prometheus namespace
using namespace prometheus;
// for clarity, we deduce the required types
using IntegerGauge = Gauge<int64_t>;
using FloatingGauge = Gauge<double>;
using IntegerGaugeFamily = CustomFamily<IntegerGauge>;
using FloatingGaugeFamily = CustomFamily<FloatingGauge>;
// create global registry for use it from our classes
static Registry globalRegistry;
class MyClass {
IntegerGaugeFamily& gaugeFamily1 { IntegerGauge::Family::Build(globalRegistry,
"gauge_family_1", "gauge for check integer functionality",
{{"type","integer"}} ) };
IntegerGauge& gauge11{ gaugeFamily1.Add({{"number", "1"}}) };
IntegerGauge& gauge12{ gaugeFamily1.Add({{"number", "2"}}) };
IntegerGauge& gauge13{ gaugeFamily1.Add({{"number", "3"}}) };
FloatingGaugeFamily& gaugeFamily2 { FloatingGauge::Family::Build(globalRegistry,
"gauge_family_2", "gauge for check floating functionality",
{{"type","float"}} ) };
FloatingGauge& gauge21{ gaugeFamily2.Add({{"number", "1"}}) };
FloatingGauge& gauge22{ gaugeFamily2.Add({{"number", "2"}}) };
FloatingGauge& gauge23{ gaugeFamily2.Add({{"number", "3"}}) };
public:
MyClass() = default;
void member_to_do_something() {
const int random_value = std::rand();
if (random_value & 1 ) gauge11++; else gauge11--;
if (random_value & (1 << 1)) gauge12++; else gauge12--;
if (random_value & (1 << 2)) gauge13++; else gauge13--;
if (random_value & (1 << 3)) gauge21++; else gauge21--;
if (random_value & (1 << 4)) gauge22++; else gauge22--;
if (random_value & (1 << 5)) gauge23++; else gauge23--;
}
};
int main() {
MyClass myClass;
for (;; ) {
std::this_thread::sleep_for(std::chrono::seconds(1));
myClass.member_to_do_something();
TextSerializer text_serializer;
text_serializer.Serialize(std::cout, globalRegistry.Collect());
}
}

View File

@ -0,0 +1,7 @@
project(prometheus-cpp-simpleapi)
cmake_minimum_required(VERSION 3.2)
add_library (${PROJECT_NAME} STATIC "./src/simpleapi.cpp" )
target_sources (${PROJECT_NAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include/prometheus/simpleapi.h")
target_include_directories(${PROJECT_NAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries (${PROJECT_NAME} PUBLIC prometheus-cpp-lite-core)

View File

@ -0,0 +1,156 @@
#pragma once
#include <prometheus/family.h>
#include <prometheus/registry.h>
#include <prometheus/counter.h>
#include <prometheus/gauge.h>
#include <prometheus/benchmark.h>
#include <prometheus/registry.h>
#include <prometheus/save_to_file.h>
#include <thread>
#include <iostream>
#include <memory>
#include <functional>
namespace prometheus {
namespace simpleapi {
extern Registry& registry;
extern SaveToFile saver;
template <typename CustomWrapper>
class family_wrapper_t {
typename CustomWrapper::Family* family_{ nullptr };
public:
// make new family: family_t family {"family_name", "Family description"}
family_wrapper_t(const std::string& name, const std::string& description)
: family_(&CustomWrapper::Family::Build(registry, name, description)) {}
// make new metric into existing family: metric_t metric {family.Add({{"tag_name", "tag_value"}})}
CustomWrapper Add(const typename CustomWrapper::Family::Labels& labels) {
return CustomWrapper(family_, family_->Add(labels));
}
};
class counter_metric_t {
public:
using Metric = Counter<uint64_t>;
using Family = Metric::Family;
private:
Family* family_ { nullptr };
Metric* metric_ { nullptr };
friend family_wrapper_t<counter_metric_t>;
counter_metric_t(typename Metric::Family* family, Metric& metric)
: family_(family), metric_(&metric) {}
public:
// fake empty metric
counter_metric_t() = default;
// make new counter as simple metric without tags and with hidden family included: metric_t metric {"counter_name", "Counter description"}
counter_metric_t(const std::string& name, const std::string& description)
: family_(&Metric::Family::Build(registry, name, description)), metric_(&family_->Add({})) {}
void operator++ () { metric_->Increment(); }
void operator++ (int) { metric_->Increment(); }
void operator+= (typename Metric::Value val) { metric_->Increment(val); }
uint64_t value() const { return metric_->Get(); }
};
using counter_family_t = family_wrapper_t<counter_metric_t>;
class gauge_metric_t {
public:
using Metric = Gauge<int64_t>;
using Family = Metric::Family;
private:
Family* family_ { nullptr };
Metric* metric_ { nullptr };
friend family_wrapper_t<gauge_metric_t>;
gauge_metric_t(typename Metric::Family* family, Metric& metric)
: family_(family), metric_(&metric) {}
public:
// fake empty metric
gauge_metric_t() = default;
// make new gauge as simple metric without tags and with hidden family included: metric {"counter_name", "Counter description"}
gauge_metric_t(const std::string& name, const std::string& description)
: family_(&Metric::Family::Build(registry, name, description)), metric_(&family_->Add({})) {}
void operator++ () { metric_->Increment(); }
void operator++ (int) { metric_->Increment(); }
void operator+= (typename Metric::Value val) { metric_->Increment(val); }
void operator-- () { metric_->Decrement(); }
void operator-- (int) { metric_->Decrement(); }
void operator-= (typename Metric::Value val) { metric_->Decrement(val); }
void operator= (typename Metric::Value val) { metric_->Set(val); }
int64_t value() const { return metric_->Get(); }
};
using gauge_family_t = family_wrapper_t<gauge_metric_t>;
class benchmark_metric_t {
public:
using Metric = Benchmark;
using Family = Metric::Family;
private:
Family* family_ { nullptr };
Metric* metric_ { nullptr };
friend family_wrapper_t<benchmark_metric_t>;
benchmark_metric_t(typename Metric::Family* family, Metric& metric)
: family_(family), metric_(&metric) {}
public:
// fake empty metric
benchmark_metric_t() = default;
// make new benchmark as simple metric without tags and with hidden family included: metric {"counter_name", "Counter description"}
benchmark_metric_t(const std::string& name, const std::string& description)
: family_(&Metric::Family::Build(registry, name, description)), metric_(&family_->Add({})) {}
void start() { metric_->start(); }
void stop() { metric_->stop(); }
double value() const { return metric_->Get(); }
};
using benchmark_family_t = family_wrapper_t<benchmark_metric_t>;
}
}

View File

@ -0,0 +1,13 @@
#include "prometheus/simpleapi.h"
#include <memory>
namespace prometheus {
namespace simpleapi {
std::shared_ptr<Registry> registry_ptr = std::make_shared<Registry>();
Registry& registry = *registry_ptr;
SaveToFile saver(registry_ptr, std::chrono::seconds(5), std::string("./metrics.prom"));
}
}

View File

@ -1,6 +1,6 @@
# This requires GNU make, which is typically "gmake" on BSD systems
INCLUDES=-isystem ext
INCLUDES=-isystem ext -Iext/prometheus-cpp-lite-1.0/core/include -Iext/prometheus-cpp-lite-1.0/simpleapi/include
DEFS=
LIBS=

View File

@ -9,7 +9,7 @@ ifeq ($(origin CXX),default)
CXX:=$(shell if [ -e /opt/rh/devtoolset-8/root/usr/bin/g++ ]; then echo /opt/rh/devtoolset-8/root/usr/bin/g++; else echo $(CXX); fi)
endif
INCLUDES?=-Izeroidc/target -isystem ext
INCLUDES?=-Izeroidc/target -isystem ext -Iext/prometheus-cpp-lite-1.0/core/include -Iext-prometheus-cpp-lite-1.0/3rdparty/http-client-lite/include -Iext/prometheus-cpp-lite-1.0/simpleapi/include
DEFS?=
LDLIBS?=
DESTDIR?=

View File

@ -2,7 +2,7 @@ CC=clang
CXX=clang++
TOPDIR=$(shell PWD)
INCLUDES=-I$(shell PWD)/zeroidc/target -isystem $(TOPDIR)/ext
INCLUDES=-I$(shell PWD)/zeroidc/target -isystem $(TOPDIR)/ext -I$(TOPDIR)/ext/prometheus-cpp-lite-1.0/core/include -I$(TOPDIR)/ext-prometheus-cpp-lite-1.0/3rdparty/http-client-lite/include -I$(TOPDIR)/ext/prometheus-cpp-lite-1.0/simpleapi/include
DEFS=
LIBS=
ARCH_FLAGS=-arch x86_64 -arch arm64

75
node/Metrics.cpp Normal file
View File

@ -0,0 +1,75 @@
/*
* Copyright (c)2013-2023 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2025-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
#include <prometheus/simpleapi.h>
namespace prometheus {
namespace simpleapi {
std::shared_ptr<Registry> registry_ptr = std::make_shared<Registry>();
Registry& registry = *registry_ptr;
SaveToFile saver;
}
}
namespace ZeroTier {
namespace Metrics {
// Data Sent/Received Metrics
prometheus::simpleapi::counter_metric_t udp_send
{ "zt_udp_data_sent", "number of bytes ZeroTier has sent via UDP" };
prometheus::simpleapi::counter_metric_t udp_recv
{ "zt_udp_data_recv", "number of bytes ZeroTier has received via UDP" };
prometheus::simpleapi::counter_metric_t tcp_send
{ "zt_tcp_data_sent", "number of bytes ZeroTier has sent via TCP" };
prometheus::simpleapi::counter_metric_t tcp_recv
{ "zt_tcp_data_recv", "number of bytes ZeroTier has received via TCP" };
// General Controller Metrics
prometheus::simpleapi::gauge_metric_t network_count
{"controller_network_count", "number of networks the controller is serving"};
prometheus::simpleapi::gauge_metric_t member_count
{"controller_member_count", "number of network members the controller is serving"};
prometheus::simpleapi::counter_metric_t network_changes
{"controller_network_change_count", "number of times a network configuration is changed"};
prometheus::simpleapi::counter_metric_t member_changes
{"controller_member_change_count", "number of times a network member configuration is changed"};
prometheus::simpleapi::counter_metric_t member_auths
{"controller_member_auth_count", "number of network member auths"};
prometheus::simpleapi::counter_metric_t member_deauths
{"controller_member_deauth_count", "number of network member deauths"};
#ifdef ZT_CONTROLLER_USE_LIBPQ
// Central Controller Metrics
prometheus::simpleapi::counter_metric_t pgsql_mem_notification
{ "controller_pgsql_member_notifications_received", "number of member change notifications received via pgsql NOTIFY" };
prometheus::simpleapi::counter_metric_t pgsql_net_notification
{ "controller_pgsql_network_notifications_received", "number of network change notifications received via pgsql NOTIFY" };
prometheus::simpleapi::counter_metric_t redis_mem_notification
{ "controller_redis_member_notifications_received", "number of member change notifications received via redis" };
prometheus::simpleapi::counter_metric_t redis_net_notification
{ "controller_redis_network_notifications_received", "number of network change notifications received via redis" };
// Central DB Pool Metrics
prometheus::simpleapi::counter_metric_t conn_counter
{ "controller_pgsql_connections_created", "number of pgsql connections created"};
prometheus::simpleapi::counter_metric_t max_pool_size
{ "controller_pgsql_max_conn_pool_size", "max connection pool size for postgres"};
prometheus::simpleapi::counter_metric_t min_pool_size
{ "controller_pgsql_min_conn_pool_size", "minimum connection pool size for postgres" };
prometheus::simpleapi::gauge_metric_t pool_avail
{ "controller_pgsql_available_conns", "number of available postgres connections" };
prometheus::simpleapi::gauge_metric_t pool_in_use
{ "controller_pgsql_in_use_conns", "number of postgres database connections in use" };
prometheus::simpleapi::counter_metric_t pool_errors
{ "controller_pgsql_connection_errors", "number of connection errors the connection pool has seen" };
#endif
}
}

57
node/Metrics.hpp Normal file
View File

@ -0,0 +1,57 @@
/*
* Copyright (c)2013-2023 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2025-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
#ifndef METRICS_H_
#define METRICS_H_
#include <prometheus/simpleapi.h>
namespace prometheus {
namespace simpleapi {
extern std::shared_ptr<Registry> registry_ptr;
}
}
namespace ZeroTier {
namespace Metrics {
// Data Sent/Received Metrics
extern prometheus::simpleapi::counter_metric_t udp_send;
extern prometheus::simpleapi::counter_metric_t udp_recv;
extern prometheus::simpleapi::counter_metric_t tcp_send;
extern prometheus::simpleapi::counter_metric_t tcp_recv;
// General Controller Metrics
extern prometheus::simpleapi::gauge_metric_t network_count;
extern prometheus::simpleapi::gauge_metric_t member_count;
extern prometheus::simpleapi::counter_metric_t network_changes;
extern prometheus::simpleapi::counter_metric_t member_changes;
extern prometheus::simpleapi::counter_metric_t member_auths;
extern prometheus::simpleapi::counter_metric_t member_deauths;
#ifdef ZT_CONTROLLER_USE_LIBPQ
// Central Controller Metrics
extern prometheus::simpleapi::counter_metric_t pgsql_mem_notification;
extern prometheus::simpleapi::counter_metric_t pgsql_net_notification;
extern prometheus::simpleapi::counter_metric_t redis_mem_notification;
extern prometheus::simpleapi::counter_metric_t redis_net_notification;
// Central DB Pool Metrics
extern prometheus::simpleapi::counter_metric_t conn_counter;
extern prometheus::simpleapi::counter_metric_t max_pool_size;
extern prometheus::simpleapi::counter_metric_t min_pool_size;
extern prometheus::simpleapi::gauge_metric_t pool_avail;
extern prometheus::simpleapi::gauge_metric_t pool_in_use;
extern prometheus::simpleapi::counter_metric_t pool_errors;
#endif
} // namespace Metrics
}// namespace ZeroTier
#endif // METRICS_H_

View File

@ -10,6 +10,7 @@ CORE_OBJS=\
node/IncomingPacket.o \
node/InetAddress.o \
node/Membership.o \
node/Metrics.o \
node/Multicaster.o \
node/Network.o \
node/NetworkConfig.o \

View File

@ -50,6 +50,8 @@
#include <netinet/in.h>
#include <netinet/tcp.h>
#include "../node/Metrics.hpp"
#if defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux)
#ifndef IPV6_DONTFRAG
#define IPV6_DONTFRAG 62
@ -452,9 +454,13 @@ public:
{
PhySocketImpl &sws = *(reinterpret_cast<PhySocketImpl *>(sock));
#if defined(_WIN32) || defined(_WIN64)
return ((long)::sendto(sws.sock,reinterpret_cast<const char *>(data),len,0,remoteAddress,(remoteAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) == (long)len);
int sent = ((long)::sendto(sws.sock,reinterpret_cast<const char *>(data),len,0,remoteAddress,(remoteAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) == (long)len);
Metrics::udp_send += sent;
return sent;
#else
return ((long)::sendto(sws.sock,data,len,0,remoteAddress,(remoteAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) == (long)len);
ssize_t sent = ((long)::sendto(sws.sock,data,len,0,remoteAddress,(remoteAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) == (long)len);
Metrics::udp_send += sent;
return sent;
#endif
}

View File

@ -87,6 +87,8 @@
#include "../ext/http-parser/http_parser.h"
#endif
#include "../node/Metrics.hpp"
#if ZT_VAULT_SUPPORT
extern "C" {
#include <curl/curl.h>
@ -846,6 +848,10 @@ public:
_ports[1] = 0;
_ports[2] = 0;
prometheus::simpleapi::saver.set_registry(prometheus::simpleapi::registry_ptr);
prometheus::simpleapi::saver.set_delay(std::chrono::seconds(5));
prometheus::simpleapi::saver.set_out_file(_homePath + ZT_PATH_SEPARATOR + "metrics.prom");
#if ZT_VAULT_SUPPORT
curl_global_init(CURL_GLOBAL_DEFAULT);
#endif
@ -1667,7 +1673,15 @@ public:
} else scode = 404;
_node->freeQueryResult((void *)pl);
} else scode = 500;
} else scode = 500;\
} else if (ps[0] == "metrics") {
std::string statspath = _homePath + ZT_PATH_SEPARATOR + "metrics.prom";
if (!OSUtils::readFile(statspath.c_str(), responseBody)) {
scode = 500;
} else {
scode = 200;
responseContentType = "text/plain";
}
} else {
if (_controller) {
scode = _controller->handleControlPlaneHttpGET(std::vector<std::string>(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType);
@ -2456,9 +2470,11 @@ public:
if (_forceTcpRelay) {
return;
}
Metrics::udp_recv += len;
const uint64_t now = OSUtils::now();
if ((len >= 16)&&(reinterpret_cast<const InetAddress *>(from)->ipScope() == InetAddress::IP_SCOPE_GLOBAL))
if ((len >= 16)&&(reinterpret_cast<const InetAddress *>(from)->ipScope() == InetAddress::IP_SCOPE_GLOBAL)) {
_lastDirectReceiveFromGlobal = now;
}
const ZT_ResultCode rc = _node->processWirePacket(nullptr,now,reinterpret_cast<int64_t>(sock),reinterpret_cast<const struct sockaddr_storage *>(from),data,len,&_nextBackgroundTaskDeadline);
if (ZT_ResultCode_isFatal(rc)) {
char tmp[256];
@ -2545,6 +2561,7 @@ public:
{
try {
if (!len) return; // sanity check, should never happen
Metrics::tcp_recv += len;
TcpConnection *tc = reinterpret_cast<TcpConnection *>(*uptr);
tc->lastReceive = OSUtils::now();
switch(tc->type) {
@ -2683,6 +2700,7 @@ public:
Mutex::Lock _l(tc->writeq_m);
if (tc->writeq.length() > 0) {
long sent = (long)_phy.streamSend(sock,tc->writeq.data(),(unsigned long)tc->writeq.length(),true);
Metrics::tcp_send += sent;
if (sent > 0) {
if ((unsigned long)sent >= (unsigned long)tc->writeq.length()) {
tc->writeq.clear();
@ -3221,9 +3239,13 @@ public:
// working we can instantly "fail forward" to it and stop using TCP
// proxy fallback, which is slow.
if ((localSocket != -1)&&(localSocket != 0)&&(_binder.isUdpSocketValid((PhySocket *)((uintptr_t)localSocket)))) {
if ((ttl)&&(addr->ss_family == AF_INET)) _phy.setIp4UdpTtl((PhySocket *)((uintptr_t)localSocket),ttl);
if ((ttl)&&(addr->ss_family == AF_INET)) {
_phy.setIp4UdpTtl((PhySocket *)((uintptr_t)localSocket),ttl);
}
const bool r = _phy.udpSend((PhySocket *)((uintptr_t)localSocket),(const struct sockaddr *)addr,data,len);
if ((ttl)&&(addr->ss_family == AF_INET)) _phy.setIp4UdpTtl((PhySocket *)((uintptr_t)localSocket),255);
if ((ttl)&&(addr->ss_family == AF_INET)) {
_phy.setIp4UdpTtl((PhySocket *)((uintptr_t)localSocket),255);
}
return ((r) ? 0 : -1);
} else {
return ((_binder.udpSendAll(_phy,addr,data,len,ttl)) ? 0 : -1);

View File

@ -81,6 +81,7 @@
<ClCompile Include="..\..\node\IncomingPacket.cpp" />
<ClCompile Include="..\..\node\InetAddress.cpp" />
<ClCompile Include="..\..\node\Membership.cpp" />
<ClCompile Include="..\..\node\Metrics.cpp" />
<ClCompile Include="..\..\node\Multicaster.cpp" />
<ClCompile Include="..\..\node\Network.cpp" />
<ClCompile Include="..\..\node\NetworkConfig.cpp" />
@ -210,6 +211,7 @@
<ClInclude Include="..\..\node\InetAddress.hpp" />
<ClInclude Include="..\..\node\MAC.hpp" />
<ClInclude Include="..\..\node\Membership.hpp" />
<ClInclude Include="..\..\node\Metrics.hpp" />
<ClInclude Include="..\..\node\Multicaster.hpp" />
<ClInclude Include="..\..\node\MulticastGroup.hpp" />
<ClInclude Include="..\..\node\Mutex.hpp" />
@ -412,7 +414,7 @@
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>$(SolutionDir)\..\ext;$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)\..\ext;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\core\include;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\simpleapi\include;$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>ZT_SSO_ENABLED=1;ZT_EXPORT;FD_SETSIZE=1024;NOMINMAX;STATICLIB;WIN32;ZT_TRACE;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;ZT_SOFTWARE_UPDATE_DEFAULT="disable";%(PreprocessorDefinitions)</PreprocessorDefinitions>
<DisableSpecificWarnings>4996</DisableSpecificWarnings>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
@ -434,7 +436,7 @@
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>$(SolutionDir)\..\ext;$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)\..\ext;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\core\include;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\simpleapi\include;$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>ZT_SSO_ENABLED=1;ZT_EXPORT;FD_SETSIZE=1024;NOMINMAX;STATICLIB;WIN32;ZT_TRACE;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;ZT_SOFTWARE_UPDATE_DEFAULT="disable";%(PreprocessorDefinitions)</PreprocessorDefinitions>
<DisableSpecificWarnings>4996</DisableSpecificWarnings>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
@ -455,7 +457,7 @@
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>$(SolutionDir)\..\ext;$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)\..\ext;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\core\include;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\simpleapi\include;$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>ZT_SSO_ENABLED=1;ZT_EXPORT;FD_SETSIZE=1024;NOMINMAX;STATICLIB;WIN32;ZT_TRACE;ZT_RULES_ENGINE_DEBUGGING;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;ZT_SOFTWARE_UPDATE_DEFAULT="disable";%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<DisableSpecificWarnings>4996</DisableSpecificWarnings>
@ -501,7 +503,7 @@
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>$(SolutionDir)\..\ext;$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)\..\ext;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\core\include;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\simpleapi\include;$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>ZT_SSO_ENABLED=1;ZT_EXPORT;FD_SETSIZE=1024;NOMINMAX;STATICLIB;WIN32;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;ZT_SOFTWARE_UPDATE_DEFAULT="disable";%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<DisableSpecificWarnings>4996</DisableSpecificWarnings>
@ -547,7 +549,7 @@
<FunctionLevelLinking>false</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>$(SolutionDir)\..\ext;$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)\..\ext;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\core\include;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\simpleapi\include;$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>ZT_SSO_ENABLED=1;ZT_EXPORT;FD_SETSIZE=1024;STATICLIB;ZT_SALSA20_SSE;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;WIN32;NOMINMAX;ZT_SOFTWARE_UPDATE_DEFAULT="apply";ZT_BUILD_PLATFORM=2;ZT_BUILD_ARCHITECTURE=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<StringPooling>true</StringPooling>
@ -583,7 +585,7 @@
<FunctionLevelLinking>false</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>$(SolutionDir)\..\ext;$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)\..\ext;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\core\include;$(SolutionDir)\..\ext\prometheus-cpp-lite-1.0\simpleapi\include;$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>ZT_SSO_ENABLED=1;ZT_EXPORT;FD_SETSIZE=1024;STATICLIB;ZT_SOFTWARE_UPDATE_DEFAULT="apply";ZT_SALSA20_SSE;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;WIN32;NOMINMAX;ZT_BUILD_PLATFORM=2;ZT_BUILD_ARCHITECTURE=2;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<EnableEnhancedInstructionSet>NotSet</EnableEnhancedInstructionSet>

View File

@ -291,6 +291,9 @@
<ClCompile Include="..\..\osdep\WinFWHelper.cpp">
<Filter>Source Files\osdep</Filter>
</ClCompile>
<ClCompile Include="..\..\node\Metrics.cpp">
<Filter>Source Files\node</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="resource.h">
@ -557,6 +560,9 @@
<ClInclude Include="..\..\osdep\WinFWHelper.hpp">
<Filter>Header Files\osdep</Filter>
</ClInclude>
<ClInclude Include="..\..\node\Metrics.hpp">
<Filter>Header Files\node</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="ZeroTierOne.rc">

View File

@ -15,7 +15,7 @@ url = "2.3"
reqwest = "0.11"
jwt = {version = "0.16", git = "https://github.com/glimberg/rust-jwt"}
serde = "1.0"
time = { version = "0.3", features = ["formatting"] }
time = { version = "~0.3", features = ["formatting"] }
bytes = "1.3"
thiserror = "1"
tokio = { version = ">=1.24" }