浏览代码

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 499ed6d95e11452019cdf48e32ed4cd878c2705b.

* 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 <[email protected]>
Co-authored-by: Brenton Bostick <[email protected]>
Grant Limberg 2 年之前
父节点
当前提交
8e6e4ede6d
共有 62 个文件被更改,包括 4023 次插入25 次删除
  1. 13 1
      controller/ConnectionPool.hpp
  2. 40 5
      controller/DB.cpp
  3. 2 0
      controller/DB.hpp
  4. 8 2
      controller/FileDB.cpp
  5. 8 1
      controller/PostgreSQL.cpp
  6. 3 0
      controller/PostgreSQL.hpp
  7. 3 0
      ext/prometheus-cpp-lite-1.0/.gitignore
  8. 36 0
      ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/CMakeLists.txt
  9. 22 0
      ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/LICENSE
  10. 30 0
      ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/README.md
  11. 5 0
      ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/examples/CMakeLists.txt
  12. 43 0
      ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/examples/simple_request.cpp
  13. 327 0
      ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/include/jdl/httpclientlite.h
  14. 34 0
      ext/prometheus-cpp-lite-1.0/CMakeLists.txt
  15. 21 0
      ext/prometheus-cpp-lite-1.0/LICENSE
  16. 201 0
      ext/prometheus-cpp-lite-1.0/README.md
  17. 20 0
      ext/prometheus-cpp-lite-1.0/core/CMakeLists.txt
  18. 40 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/atomic_floating.h
  19. 72 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/benchmark.h
  20. 35 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/builder.h
  21. 194 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/ckms_quantiles.h
  22. 94 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/client_metric.h
  23. 27 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/collectable.h
  24. 112 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/counter.h
  25. 355 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/family.h
  26. 205 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/gateway.h
  27. 128 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/gauge.h
  28. 50 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/hash.h
  29. 154 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/histogram.h
  30. 29 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/metric.h
  31. 18 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/metric_family.h
  32. 86 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/push_to_server.h
  33. 123 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/registry.h
  34. 83 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/save_to_file.h
  35. 154 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/summary.h
  36. 211 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/text_serializer.h
  37. 62 0
      ext/prometheus-cpp-lite-1.0/core/include/prometheus/time_window_quantiles.h
  38. 31 0
      ext/prometheus-cpp-lite-1.0/examples/CMakeLists.txt
  39. 64 0
      ext/prometheus-cpp-lite-1.0/examples/gateway_example.cpp
  40. 65 0
      ext/prometheus-cpp-lite-1.0/examples/modern_example.cpp
  41. 67 0
      ext/prometheus-cpp-lite-1.0/examples/original_example.cpp
  42. 44 0
      ext/prometheus-cpp-lite-1.0/examples/push_to_server_example.cpp
  43. 43 0
      ext/prometheus-cpp-lite-1.0/examples/save_to_file_example.cpp
  44. 26 0
      ext/prometheus-cpp-lite-1.0/examples/simpleapi_example.cpp
  45. 57 0
      ext/prometheus-cpp-lite-1.0/examples/simpleapi_use_in_class_example.cpp
  46. 59 0
      ext/prometheus-cpp-lite-1.0/examples/use_benchmark_in_class_example.cpp
  47. 79 0
      ext/prometheus-cpp-lite-1.0/examples/use_counters_in_class_example.cpp
  48. 79 0
      ext/prometheus-cpp-lite-1.0/examples/use_gauge_in_class_example.cpp
  49. 7 0
      ext/prometheus-cpp-lite-1.0/simpleapi/CMakeLists.txt
  50. 156 0
      ext/prometheus-cpp-lite-1.0/simpleapi/include/prometheus/simpleapi.h
  51. 13 0
      ext/prometheus-cpp-lite-1.0/simpleapi/src/simpleapi.cpp
  52. 1 1
      make-bsd.mk
  53. 1 1
      make-linux.mk
  54. 1 1
      make-mac.mk
  55. 75 0
      node/Metrics.cpp
  56. 57 0
      node/Metrics.hpp
  57. 1 0
      objects.mk
  58. 8 2
      osdep/Phy.hpp
  59. 26 4
      service/OneService.cpp
  60. 8 6
      windows/ZeroTierOne/ZeroTierOne.vcxproj
  61. 6 0
      windows/ZeroTierOne/ZeroTierOne.vcxproj.filters
  62. 1 1
      zeroidc/Cargo.toml

+ 13 - 1
controller/ConnectionPool.hpp

@@ -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);
         }
     };
@@ -158,4 +170,4 @@ protected:
 
 }
 
-#endif
+#endif

+ 40 - 5
controller/DB.cpp

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

+ 2 - 0
controller/DB.hpp

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

+ 8 - 2
controller/FileDB.cpp

@@ -13,6 +13,8 @@
 
 #include "FileDB.hpp"
 
+#include "../node/Metrics.hpp"
+
 namespace ZeroTier
 {
 
@@ -39,6 +41,7 @@ FileDB::FileDB(const char *path) :
 				if (nwids.length() == 16) {
 					nlohmann::json nullJson;
 					_networkChanged(nullJson,network,false);
+					Metrics::network_count++;
 					std::string membersPath(_networksPath + ZT_PATH_SEPARATOR_S + nwids + ZT_PATH_SEPARATOR_S "member");
 					std::vector<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;

+ 8 - 1
controller/PostgreSQL.cpp

@@ -119,6 +119,7 @@ MemberNotificationReceiver::MemberNotificationReceiver(PostgreSQL *p, pqxx::conn
 
 void MemberNotificationReceiver::operator() (const std::string &payload, int packend_pid) {
 	fprintf(stderr, "Member Notification received: %s\n", payload.c_str());
+	Metrics::pgsql_mem_notification++;
 	json tmp(json::parse(payload));
 	json &ov = tmp["old_val"];
 	json &nv = tmp["new_val"];
@@ -141,6 +142,7 @@ NetworkNotificationReceiver::NetworkNotificationReceiver(PostgreSQL *p, pqxx::co
 
 void NetworkNotificationReceiver::operator() (const std::string &payload, int packend_pid) {
 	fprintf(stderr, "Network Notification received: %s\n", payload.c_str());
+	Metrics::pgsql_net_notification++;
 	json tmp(json::parse(payload));
 	json &ov = tmp["old_val"];
 	json &nv = tmp["new_val"];
@@ -705,6 +707,8 @@ void PostgreSQL::initializeNetworks()
 				}
 			}
 
+			Metrics::network_count++;
+
 		 	_networkChanged(empty, config, false);
 
 			auto end = std::chrono::high_resolution_clock::now();
@@ -925,6 +929,8 @@ void PostgreSQL::initializeMembers()
 				}
 			}
 
+			Metrics::member_count++;
+
 			_memberChanged(empty, config, false);
 
 			memberId = "";
@@ -1034,7 +1040,6 @@ void PostgreSQL::heartbeat()
 				w.commit();
 			} catch (std::exception &e) {
 				fprintf(stderr, "%s: Heartbeat update failed: %s\n", controllerId, e.what());
-				w.abort();
 				_pool->unborrow(c);
 				std::this_thread::sleep_for(std::chrono::milliseconds(1000));
 				continue;
@@ -1230,6 +1235,7 @@ void PostgreSQL::_networksWatcher_Redis() {
 						}
 						lastID = id;
 					}
+					Metrics::redis_net_notification++;
 				}
 			}
 		} catch (sw::redis::Error &e) {
@@ -1788,6 +1794,7 @@ uint64_t PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &con
 			.sadd("network-nodes-all:{"+controllerId+"}:"+networkId, memberId)
 			.hmset("member:{"+controllerId+"}:"+networkId+":"+memberId, record.begin(), record.end());
 		++count;
+		Metrics::redis_mem_notification++;
 	}
 
 	// expire records from all-nodes and network-nodes member list

+ 3 - 0
controller/PostgreSQL.hpp

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

+ 3 - 0
ext/prometheus-cpp-lite-1.0/.gitignore

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

+ 36 - 0
ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/CMakeLists.txt

@@ -0,0 +1,36 @@
+# Copyright 2021... by Maxim Gusev
+#
+# https://github.com/John-Jasper-Doe/http-client-lite
+#
+# Distributed under the MIT License.
+# (See accompanying file LICENSE or copy at https://mit-license.org/)
+
+cmake_minimum_required(VERSION 3.3 FATAL_ERROR)
+project(http-client-lite)
+
+## Set output binary
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)
+set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)
+set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)
+
+## Set property
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+add_compile_options(-Wall -Werror -Wextra -Wpedantic -g -O0)
+
+
+option(HTTP_CLIENT_LITE_OPT_BUILD_EXAMPLES "Build examples" OFF )
+
+if(HTTP_CLIENT_LITE_OPT_BUILD_EXAMPLES)
+  add_subdirectory(examples)
+endif()
+
+# Interface library:
+add_library(${PROJECT_NAME} INTERFACE)
+target_sources(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include/jdl/httpclientlite.h)
+add_custom_target(${PROJECT_NAME}.hdr SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/include/jdl/httpclientlite.h)
+target_include_directories(
+    ${PROJECT_NAME}
+    INTERFACE
+        "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
+        "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")

+ 22 - 0
ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/LICENSE

@@ -0,0 +1,22 @@
+The MIT License
+
+Copyright (c) 2016 Christian C. Sachs
+Copyright (c) 2021 Maxim Gusev
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 30 - 0
ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/README.md

@@ -0,0 +1,30 @@
+# HTTP Client lite: C++ Cross-platform library only from single-file header-only
+
+This is a lite, C++ cross-platform header-only client library for http request based 
+on [csachs/picohttpclient](https://github.com/csachs/picohttpclient) project.
+
+A Lightweight HTTP 1.1 client where to quickly do very simple HTTP requests, 
+without adding larger dependencies to a project.
+
+
+## License
+
+http client lite  is distributed under the [MIT License](https://github.com/john-jasper-doe/http-client-lite/blob/master/LICENSE).
+
+
+## Example usage
+
+To see how this can be used see the examples folders.
+
+
+**Example:**
+```C++
+#include <jdl/httpclientlite.hpp>
+...
+using namespace jdl;
+...
+HTTPResponse response = HTTPClient::request(HTTPClient::GET, URI("http://example.com"));
+cout << response.body << endl;
+...
+```
+

+ 5 - 0
ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/examples/CMakeLists.txt

@@ -0,0 +1,5 @@
+cmake_minimum_required(VERSION 3.3 FATAL_ERROR)
+project(http-client-lite-examples)
+
+add_executable(${PROJECT_NAME}_simple_request simple_request.cpp)
+target_link_libraries(${PROJECT_NAME}_simple_request PRIVATE http_client_lite)

+ 43 - 0
ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/examples/simple_request.cpp

@@ -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;
+}

+ 327 - 0
ext/prometheus-cpp-lite-1.0/3rdpatry/http-client-lite/include/jdl/httpclientlite.h

@@ -0,0 +1,327 @@
+/*
+ *   httpclientlite.hpp
+ *   ===========================================================================================
+ *
+ *   The MIT License
+ *
+ *   Copyright (c) 2016 Christian C. Sachs
+ *   Copyright (c) 2021 Maxim G.
+ *
+ *   Permission is hereby granted, free of charge, to any person obtaining a copy
+ *   of this software and associated documentation files (the "Software"), to deal
+ *   in the Software without restriction, including without limitation the rights
+ *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ *   copies of the Software, and to permit persons to whom the Software is
+ *   furnished to do so, subject to the following conditions:
+ *
+ *   The above copyright notice and this permission notice shall be included in all
+ *   copies or substantial portions of the Software.
+ *
+ *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ *   SOFTWARE.
+ */
+
+
+#pragma once
+
+#if defined (__linux__)
+# define PLATFORM_LINUX
+#elif defined (_WIN32) || defined (_WIN64)
+# define PLATFORM_WINDOWS
+#else
+/* TODO:
+ *  - Added Apple OS */
+
+/* warning: Unknown OS */
+#endif
+
+
+#include <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:: */

+ 34 - 0
ext/prometheus-cpp-lite-1.0/CMakeLists.txt

@@ -0,0 +1,34 @@
+project(prometheus-cpp-lite)
+cmake_minimum_required(VERSION 3.2)
+
+option(PROMETHEUS_BUILD_EXAMPLES "Build with examples" OFF)
+
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
+
+if(WIN32) 
+
+  # it prevent create Debug/ and Release folders in Visual Studio
+  foreach( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} )
+    string( TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG )
+    set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${PROJECT_SOURCE_DIR}/bin )
+  endforeach()
+
+  set (INSTALL_PATH_BIN "${PROJECT_SOURCE_DIR}/installed/bin/")
+
+else() # not WIN32
+
+  set (INSTALL_PATH_BIN "bin/")
+
+endif()
+
+add_subdirectory("./core")
+
+add_subdirectory("./simpleapi")
+
+add_subdirectory("./3rdpatry/http-client-lite")
+
+if(PROMETHEUS_BUILD_EXAMPLES)
+  add_subdirectory("./examples")
+endif()
+
+

+ 21 - 0
ext/prometheus-cpp-lite-1.0/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 biaks ([email protected])
+
+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.

+ 201 - 0
ext/prometheus-cpp-lite-1.0/README.md

@@ -0,0 +1,201 @@
+# C++ Header-only Prometheus client library
+
+It is a tool for quickly adding metrics (and profiling) functionality to C++ projects.
+
+## Advantages:
+
+1. Written in pure C++,
+2. Header-only,
+2. Cross-platform,
+3. Compiles with C ++ 11, C ++ 14, C ++ 17 standards,
+4. Has no third-party dependencies,
+5. Several APIs for use in your projects,
+6. Saving metrics to a file (and then works with node_exporter) or sending via http (uses built-in header-only http-client-lite library),
+7. Possiblity to use different types for storing metrics data (default is uint32_t, but you can use double or uint64_t types if you want),
+8. Five types of metrics are supported: counter, gauge, summary, histogram and benchmark,
+10. Has detailed examples of use (see examples folder)
+
+## How it differs from the [jupp0r/prometheus-cpp](https://github.com/jupp0r/prometheus-cpp) project:
+1. I need a simple header only wariant library without dependencies to write metrics to a .prom file,
+2. I need the fastest possible work using integer values of counters (original project use only floating pointer values),
+3. The origianl project have problems on compilers that do not know how to do LTO optimization,
+4. I did not like the python style of the original project and the large amount of extra code in it and I wanted to make it lighter and more c++ classic.
+
+## How to use it:
+The library has two API:
+1. Complex API for those who want to control everything,
+2. Simple API for those who want to quickly add metrics to their C ++ (and it is actually just a wrapper around the complex API).
+
+
+### Let's start with a simple API because it's simple:
+
+To add it to your C++ project add these lines to your CMakeLists.txt file:
+```
+add_subdirectory("prometheus-cpp-lite/core")
+add_subdirectory("prometheus-cpp-lite/3rdpatry/http-client-lite")
+add_subdirectory("prometheus-cpp-lite/simpleapi")
+target_link_libraries(your_target prometheus-cpp-simpleapi)
+```
+
+The simplest way to create a metric would be like this:
+``` c++
+prometheus::simpleapi::METRIC_metric_t metric1 { "metric1", "first simple metric without any tag" };
+prometheus::simpleapi::METRIC_metric_t metric2 { "metric2", "second simple metric without any tag" };
+```
+where ```METRIC``` can be ```counter```, ```gauge```, ```summary```, ```histogram``` or ```benchmark```.
+
+If you want to access an existing metric again elsewhere in the code, you can do this:
+``` c++
+prometheus::simpleapi::METRIC_metric_t metric2_yet_another_link { "metric2", "" };
+```
+this works because when adding a metric, it checks whether there is already a metric with the same name and, if there is one, a link to it is returned.
+
+You can create a family of metrics (metrics with tags) as follows:
+``` c++
+prometheus::simpleapi::METRIC_family_t family  { "metric_family", "metric family" };
+prometheus::simpleapi::METRIC_metric_t metric1 { family.Add({{"name", "metric1"}}) };
+prometheus::simpleapi::METRIC_metric_t metric2 { family.Add({{"name", "metric2"}}) };
+```
+where METRIC can be ```counter```, ```gauge```, ```summary```, ```histogram``` or ```benchmark```.
+
+Next, you can do the following things with metrics:
+``` c++
+metric++; // for increment it (only for counter and gauge metrics)
+metric += value; // for add value to metric (only for gauge metric)
+metric -= value; // for sub value from metric (only for gauge metric) 
+metric = value;  // save current value (only gauge metrics)
+metric.start();  // start calculate time (only for benchmark metric)
+metric.stop();   // stop calculate time (only for benchmark metric)
+```
+
+You can change the settings of save (or send) metrics data as follows:
+``` c++
+prometheus::simpleapi::saver.set_delay(period_in_seconds); // change the period of saving (or sending) metrics data in seconds (5 seconds by default)
+prometheus::simpleapi::saver.set_out_file(filename);       // change the name of the output file (metrics.prom by default)
+prometheus::simpleapi::saver.set_server_url(url);          // change the name of prometheus server (unset by default)
+```
+
+### Simple API complex example 1 (examples/simpleapi_example.cpp):
+
+``` c++
+#include <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
+```
+

+ 20 - 0
ext/prometheus-cpp-lite-1.0/core/CMakeLists.txt

@@ -0,0 +1,20 @@
+project(prometheus-cpp-lite-core)
+cmake_minimum_required(VERSION 3.2)
+
+file(GLOB_RECURSE PROMETHEUS_CPP_LITE_HEADERS *.h)
+
+# it is header only target
+
+add_library               (${PROJECT_NAME}     INTERFACE)
+target_sources            (${PROJECT_NAME}     INTERFACE ${PROMETHEUS_CPP_LITE_HEADERS})
+target_include_directories(${PROJECT_NAME}     INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
+add_custom_target         (${PROJECT_NAME}-ide SOURCES   ${PROMETHEUS_CPP_LITE_HEADERS})
+target_link_libraries     (${PROJECT_NAME}     INTERFACE http-client-lite)
+
+set                       (${PROJECT_NAME}_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/include PARENT_SCOPE)
+
+# it need for save_to_file_t
+if(NOT WIN32)
+  find_package(Threads)
+  target_link_libraries(${PROJECT_NAME} INTERFACE ${CMAKE_THREAD_LIBS_INIT})
+endif()

+ 40 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/atomic_floating.h

@@ -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);
+  }
+
+}

+ 72 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/benchmark.h

@@ -0,0 +1,72 @@
+#pragma once
+
+#include "prometheus/metric.h"
+#include "prometheus/family.h"
+
+#include <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

+ 35 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/builder.h

@@ -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_);
+    }
+
+  };
+
+}

+ 194 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/ckms_quantiles.h

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

+ 94 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/client_metric.h

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

+ 27 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/collectable.h

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

+ 112 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/counter.h

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

+ 355 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/family.h

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

+ 205 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/gateway.h

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

+ 128 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/gauge.h

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

+ 50 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/hash.h

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

+ 154 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/histogram.h

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

+ 29 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/metric.h

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

+ 18 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/metric_family.h

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

+ 86 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/push_to_server.h

@@ -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;
+    }
+
+  };
+}

+ 123 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/registry.h

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

+ 83 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/save_to_file.h

@@ -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;
+    }
+
+  };
+}

+ 154 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/summary.h

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

+ 211 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/text_serializer.h

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

+ 62 - 0
ext/prometheus-cpp-lite-1.0/core/include/prometheus/time_window_quantiles.h

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

+ 31 - 0
ext/prometheus-cpp-lite-1.0/examples/CMakeLists.txt

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

+ 64 - 0
ext/prometheus-cpp-lite-1.0/examples/gateway_example.cpp

@@ -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;
+  }
+}

+ 65 - 0
ext/prometheus-cpp-lite-1.0/examples/modern_example.cpp

@@ -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());
+
+  }
+}

+ 67 - 0
ext/prometheus-cpp-lite-1.0/examples/original_example.cpp

@@ -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());
+
+  }
+}

+ 44 - 0
ext/prometheus-cpp-lite-1.0/examples/push_to_server_example.cpp

@@ -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;
+  }
+}

+ 43 - 0
ext/prometheus-cpp-lite-1.0/examples/save_to_file_example.cpp

@@ -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;
+  }
+}

+ 26 - 0
ext/prometheus-cpp-lite-1.0/examples/simpleapi_example.cpp

@@ -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;
+
+}

+ 57 - 0
ext/prometheus-cpp-lite-1.0/examples/simpleapi_use_in_class_example.cpp

@@ -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();
+
+  }
+
+}
+

+ 59 - 0
ext/prometheus-cpp-lite-1.0/examples/use_benchmark_in_class_example.cpp

@@ -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());
+
+  }
+}
+

+ 79 - 0
ext/prometheus-cpp-lite-1.0/examples/use_counters_in_class_example.cpp

@@ -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());
+
+  }
+}
+

+ 79 - 0
ext/prometheus-cpp-lite-1.0/examples/use_gauge_in_class_example.cpp

@@ -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());
+
+  }
+}
+

+ 7 - 0
ext/prometheus-cpp-lite-1.0/simpleapi/CMakeLists.txt

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

+ 156 - 0
ext/prometheus-cpp-lite-1.0/simpleapi/include/prometheus/simpleapi.h

@@ -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>;
+
+  }
+}

+ 13 - 0
ext/prometheus-cpp-lite-1.0/simpleapi/src/simpleapi.cpp

@@ -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"));
+
+  }
+}

+ 1 - 1
make-bsd.mk

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

+ 1 - 1
make-linux.mk

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

+ 1 - 1
make-mac.mk

@@ -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 - 0
node/Metrics.cpp

@@ -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 - 0
node/Metrics.hpp

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

+ 1 - 0
objects.mk

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

+ 8 - 2
osdep/Phy.hpp

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

+ 26 - 4
service/OneService.cpp

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

+ 8 - 6
windows/ZeroTierOne/ZeroTierOne.vcxproj

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

+ 6 - 0
windows/ZeroTierOne/ZeroTierOne.vcxproj.filters

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

+ 1 - 1
zeroidc/Cargo.toml

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