Add MetricsBackend with prometheus counter support
authorLoic Blot <loic.blot@unix-experience.fr>
Mon, 27 Apr 2020 06:31:37 +0000 (08:31 +0200)
committerLoïc Blot <nerzhul@users.noreply.github.com>
Wed, 29 Apr 2020 05:48:08 +0000 (07:48 +0200)
16 files changed:
Dockerfile
README.md
builtin/settingtypes.txt
src/CMakeLists.txt
src/cmake_config.h.in
src/defaultsettings.cpp
src/map.cpp
src/map.h
src/server.cpp
src/server.h
src/server/mods.cpp
src/server/mods.h
src/util/CMakeLists.txt
src/util/metricsbackend.cpp [new file with mode: 0644]
src/util/metricsbackend.h [new file with mode: 0644]
util/ci/build_prometheus_cpp.sh [new file with mode: 0755]

index 7c11072881773eb4f0950ecf1dae4b6f9192db92..72343ab9c6d1dfd6f25e109d0e95d17c60ff8746 100644 (file)
@@ -21,15 +21,29 @@ WORKDIR /usr/src/minetest
 RUN apk add --no-cache git build-base irrlicht-dev cmake bzip2-dev libpng-dev \
                jpeg-dev libxxf86vm-dev mesa-dev sqlite-dev libogg-dev \
                libvorbis-dev openal-soft-dev curl-dev freetype-dev zlib-dev \
-               gmp-dev jsoncpp-dev postgresql-dev && \
+               gmp-dev jsoncpp-dev postgresql-dev ca-certificates && \
        git clone --depth=1 -b ${MINETEST_GAME_VERSION} https://github.com/minetest/minetest_game.git ./games/minetest_game && \
-       rm -fr ./games/minetest_game/.git && \
-       mkdir build && \
+       rm -fr ./games/minetest_game/.git
+
+WORKDIR /usr/src/
+RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ && \
+       mkdir prometheus-cpp/build && \
+       cd prometheus-cpp/build && \
+       cmake .. \
+               -DCMAKE_INSTALL_PREFIX=/usr/local \
+               -DCMAKE_BUILD_TYPE=Release \
+               -DENABLE_TESTING=0 && \
+       make -j2 && \
+       make install
+
+WORKDIR /usr/src/minetest
+RUN mkdir build && \
        cd build && \
        cmake .. \
                -DCMAKE_INSTALL_PREFIX=/usr/local \
                -DCMAKE_BUILD_TYPE=Release \
                -DBUILD_SERVER=TRUE \
+               -DENABLE_PROMETHEUS=TRUE \
                -DBUILD_UNITTESTS=FALSE \
                -DBUILD_CLIENT=FALSE && \
        make -j2 && \
@@ -49,6 +63,6 @@ COPY --from=0 /usr/local/share/doc/minetest/minetest.conf.example /etc/minetest/
 
 USER minetest:minetest
 
-EXPOSE 30000/udp
+EXPOSE 30000/udp 30000/tcp
 
 CMD ["/usr/local/bin/minetestserver", "--config", "/etc/minetest/minetest.conf"]
index b3b2b863eb65c2f6a46fa740f8f3568cba75c2a3..024e7b691fefdfbd2deb258f9accac0d1c50b849 100644 (file)
--- a/README.md
+++ b/README.md
@@ -236,6 +236,7 @@ General options and their default values:
     ENABLE_SPATIAL=ON          - Build with LibSpatial; Speeds up AreaStores
     ENABLE_SOUND=ON            - Build with OpenAL, libogg & libvorbis; in-game sounds
     ENABLE_LUAJIT=ON           - Build with LuaJIT (much faster than non-JIT Lua)
+    ENABLE_PROMETHEUS=OFF      - Build with Prometheus metrics exporter (listens on tcp/30000 by default)
     ENABLE_SYSTEM_GMP=ON       - Use GMP from system (much faster than bundled mini-gmp)
     ENABLE_SYSTEM_JSONCPP=OFF  - Use JsonCPP from system
     OPENGL_GL_PREFERENCE=LEGACY - Linux client build only; See CMake Policy CMP0072 for reference
index b9228f38418d2a0f1d38bf4ee8773aafdff3340a..165ed8c0687c096932d8e22e0c636f62790f9d74 100644 (file)
@@ -955,6 +955,12 @@ address (Server address) string
 #    Note that the port field in the main menu overrides this setting.
 remote_port (Remote port) int 30000 1 65535
 
+#    Prometheus listener address.
+#    If minetest is compiled with ENABLE_PROMETHEUS option enabled,
+#    enable metrics listener for Prometheus on that address.
+#    Metrics can be fetch on http://127.0.0.1:30000/metrics
+prometheus_listener_address (Prometheus listener address) string 127.0.0.1:30000
+
 #    Save the map received by the client on disk.
 enable_local_map_saving (Saving map received from server) bool false
 
index b416faaf304c166a90eeb5b1afa709ca55d1df7d..710d9e13e1c8c56c3b9062993bb65d45bba5a66d 100644 (file)
@@ -217,6 +217,26 @@ endif(ENABLE_REDIS)
 
 find_package(SQLite3 REQUIRED)
 
+OPTION(ENABLE_PROMETHEUS "Enable prometheus client support" FALSE)
+set(USE_PROMETHEUS FALSE)
+
+if(ENABLE_PROMETHEUS)
+       find_path(PROMETHEUS_CPP_INCLUDE_DIR NAMES prometheus/counter.h)
+       find_library(PROMETHEUS_PULL_LIBRARY NAMES prometheus-cpp-pull)
+       find_library(PROMETHEUS_CORE_LIBRARY NAMES prometheus-cpp-core)
+       if(PROMETHEUS_CPP_INCLUDE_DIR AND PROMETHEUS_PULL_LIBRARY AND PROMETHEUS_CORE_LIBRARY)
+               set(PROMETHEUS_LIBRARIES ${PROMETHEUS_PULL_LIBRARY} ${PROMETHEUS_CORE_LIBRARY})
+               set(USE_PROMETHEUS TRUE)
+               include_directories(${PROMETHEUS_CPP_INCLUDE_DIR})
+       endif(PROMETHEUS_CPP_INCLUDE_DIR AND PROMETHEUS_PULL_LIBRARY AND PROMETHEUS_CORE_LIBRARY)
+endif(ENABLE_PROMETHEUS)
+
+if(USE_PROMETHEUS)
+       message(STATUS "Prometheus client enabled.")
+else(USE_PROMETHEUS)
+       message(STATUS "Prometheus client disabled.")
+endif(USE_PROMETHEUS)
+
 OPTION(ENABLE_SPATIAL "Enable SpatialIndex AreaStore backend" TRUE)
 set(USE_SPATIAL FALSE)
 
@@ -597,6 +617,9 @@ if(BUILD_CLIENT)
        if (USE_REDIS)
                target_link_libraries(${PROJECT_NAME} ${REDIS_LIBRARY})
        endif()
+       if (USE_PROMETHEUS)
+               target_link_libraries(${PROJECT_NAME} ${PROMETHEUS_LIBRARIES})
+       endif()
        if (USE_SPATIAL)
                target_link_libraries(${PROJECT_NAME} ${SPATIAL_LIBRARY})
        endif()
@@ -632,6 +655,9 @@ if(BUILD_SERVER)
        if (USE_REDIS)
                target_link_libraries(${PROJECT_NAME}server ${REDIS_LIBRARY})
        endif()
+       if (USE_PROMETHEUS)
+               target_link_libraries(${PROJECT_NAME}server ${PROMETHEUS_LIBRARIES})
+       endif()
        if (USE_SPATIAL)
                target_link_libraries(${PROJECT_NAME}server ${SPATIAL_LIBRARY})
        endif()
index cac6335d40eb08dce8ce7fff930451a2cf75fa0c..cfcee4b5854cc6ccd278ab1b453f44681b326b2e 100644 (file)
@@ -23,6 +23,7 @@
 #cmakedefine01 USE_LEVELDB
 #cmakedefine01 USE_LUAJIT
 #cmakedefine01 USE_POSTGRESQL
+#cmakedefine01 USE_PROMETHEUS
 #cmakedefine01 USE_SPATIAL
 #cmakedefine01 USE_SYSTEM_GMP
 #cmakedefine01 USE_REDIS
index b6b1ce1f2a9a5b1789a80d0c6a248946b96409ae..06daa3b947697018dd831b8b2c66f54e65f8ce02 100644 (file)
@@ -334,6 +334,9 @@ void set_default_settings(Settings *settings)
        // Server
        settings->setDefault("disable_escape_sequences", "false");
        settings->setDefault("strip_color_codes", "false");
+#if USE_PROMETHEUS
+       settings->setDefault("prometheus_listener_address", "127.0.0.1:30000");
+#endif
 
        // Network
        settings->setDefault("enable_ipv6", "true");
index 12e1221249820f0f63b87f9b207dea69824de1e0..5f1b984a43952a12d821dee733d616435ecddb2c 100644 (file)
@@ -144,7 +144,7 @@ bool Map::isNodeUnderground(v3s16 p)
 {
        v3s16 blockpos = getNodeBlockPos(p);
        MapBlock *block = getBlockNoCreateNoEx(blockpos);
-       return block && block->getIsUnderground(); 
+       return block && block->getIsUnderground();
 }
 
 bool Map::isValidPosition(v3s16 p)
@@ -1187,7 +1187,7 @@ bool Map::isBlockOccluded(MapBlock *block, v3s16 cam_pos_nodes)
        ServerMap
 */
 ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef,
-               EmergeManager *emerge):
+               EmergeManager *emerge, MetricsBackend *mb):
        Map(dout_server, gamedef),
        settings_mgr(g_settings, savedir + DIR_DELIM + "map_meta.txt"),
        m_emerge(emerge)
@@ -1221,6 +1221,8 @@ ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef,
        m_savedir = savedir;
        m_map_saving_enabled = false;
 
+       m_save_time_counter = mb->addCounter("minetest_core_map_save_time", "Map save time (in nanoseconds)");
+
        try {
                // If directory exists, check contents and load if possible
                if (fs::PathExists(m_savedir)) {
@@ -1777,6 +1779,8 @@ void ServerMap::save(ModifiedState save_level)
                return;
        }
 
+       u64 start_time = porting::getTimeNs();
+
        if(save_level == MOD_STATE_CLEAN)
                infostream<<"ServerMap: Saving whole map, this can take time."
                                <<std::endl;
@@ -1835,6 +1839,9 @@ void ServerMap::save(ModifiedState save_level)
                infostream<<"Blocks modified by: "<<std::endl;
                modprofiler.print(infostream);
        }
+
+       auto end_time = porting::getTimeNs();
+       m_save_time_counter->increment(end_time - start_time);
 }
 
 void ServerMap::listAllLoadableBlocks(std::vector<v3s16> &dst)
index ff6b20c4fa87a69f06f2ffc3543e434c03089dd2..77ee4da9e575221ccd914eebd878355c244467f0 100644 (file)
--- a/src/map.h
+++ b/src/map.h
@@ -31,6 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "voxel.h"
 #include "modifiedstate.h"
 #include "util/container.h"
+#include "util/metricsbackend.h"
 #include "nodetimer.h"
 #include "map_settings_manager.h"
 #include "debug.h"
@@ -45,6 +46,7 @@ class NodeMetadata;
 class IGameDef;
 class IRollbackManager;
 class EmergeManager;
+class MetricsBackend;
 class ServerEnvironment;
 struct BlockMakeData;
 
@@ -324,7 +326,7 @@ public:
        /*
                savedir: directory to which map data should be saved
        */
-       ServerMap(const std::string &savedir, IGameDef *gamedef, EmergeManager *emerge);
+       ServerMap(const std::string &savedir, IGameDef *gamedef, EmergeManager *emerge, MetricsBackend *mb);
        ~ServerMap();
 
        s32 mapType() const
@@ -449,6 +451,8 @@ private:
        bool m_map_metadata_changed = true;
        MapDatabase *dbase = nullptr;
        MapDatabase *dbase_ro = nullptr;
+
+       MetricCounterPtr m_save_time_counter;
 };
 
 
index af6d3e40d5ddf795a4acf9e55e404132f671047d..a7eb528376d62b337f87f2e2da0ef5deb74ba629 100644 (file)
@@ -229,18 +229,46 @@ Server::Server(
        m_nodedef(createNodeDefManager()),
        m_craftdef(createCraftDefManager()),
        m_thread(new ServerThread(this)),
-       m_uptime(0),
        m_clients(m_con),
        m_admin_chat(iface),
        m_modchannel_mgr(new ModChannelMgr())
 {
-       m_lag = g_settings->getFloat("dedicated_server_step");
-
        if (m_path_world.empty())
                throw ServerError("Supplied empty world path");
 
        if (!gamespec.isValid())
                throw ServerError("Supplied invalid gamespec");
+
+#if USE_PROMETHEUS
+       m_metrics_backend = std::unique_ptr<MetricsBackend>(createPrometheusMetricsBackend());
+#else
+       m_metrics_backend = std::unique_ptr<MetricsBackend>(new MetricsBackend());
+#endif
+
+       m_uptime_counter = m_metrics_backend->addCounter("minetest_core_server_uptime", "Server uptime (in seconds)");
+       m_player_gauge = m_metrics_backend->addGauge("minetest_core_player_number", "Number of connected players");
+
+       m_timeofday_gauge = m_metrics_backend->addGauge(
+                       "minetest_core_timeofday",
+                       "Time of day value");
+
+       m_lag_gauge = m_metrics_backend->addGauge(
+                       "minetest_core_latency",
+                       "Latency value (in seconds)");
+
+       m_aom_buffer_counter = m_metrics_backend->addCounter(
+                       "minetest_core_aom_generated_count",
+                       "Number of active object messages generated");
+
+       m_packet_recv_counter = m_metrics_backend->addCounter(
+                       "minetest_core_server_packet_recv",
+                       "Processable packets received");
+
+       m_packet_recv_processed_counter = m_metrics_backend->addCounter(
+                       "minetest_core_server_packet_recv_processed",
+                       "Valid received packets processed");
+
+       m_lag_gauge->set(g_settings->getFloat("dedicated_server_step"));
 }
 
 Server::~Server()
@@ -353,7 +381,7 @@ void Server::init()
        MutexAutoLock envlock(m_env_mutex);
 
        // Create the Map (loads map_meta.txt, overriding configured mapgen params)
-       ServerMap *servermap = new ServerMap(m_path_world, this, m_emerge);
+       ServerMap *servermap = new ServerMap(m_path_world, this, m_emerge, m_metrics_backend.get());
 
        // Initialize scripting
        infostream << "Server: Initializing Lua" << std::endl;
@@ -511,9 +539,7 @@ void Server::AsyncRunStep(bool initial_step)
        /*
                Update uptime
        */
-       {
-               m_uptime.set(m_uptime.get() + dtime);
-       }
+       m_uptime_counter->increment(dtime);
 
        handlePeerChanges();
 
@@ -527,11 +553,13 @@ void Server::AsyncRunStep(bool initial_step)
        */
 
        m_time_of_day_send_timer -= dtime;
-       if(m_time_of_day_send_timer < 0.0) {
+       if (m_time_of_day_send_timer < 0.0) {
                m_time_of_day_send_timer = g_settings->getFloat("time_send_interval");
                u16 time = m_env->getTimeOfDay();
                float time_speed = g_settings->getFloat("time_speed");
                SendTimeOfDay(PEER_ID_INEXISTENT, time, time_speed);
+
+               m_timeofday_gauge->set(time);
        }
 
        {
@@ -603,7 +631,7 @@ void Server::AsyncRunStep(bool initial_step)
        }
        m_clients.step(dtime);
 
-       m_lag += (m_lag > dtime ? -1 : 1) * dtime/100;
+       m_lag_gauge->increment((m_lag_gauge->get() > dtime ? -1 : 1) * dtime/100);
 #if USE_CURL
        // send masterserver announce
        {
@@ -614,9 +642,9 @@ void Server::AsyncRunStep(bool initial_step)
                                                ServerList::AA_START,
                                        m_bind_addr.getPort(),
                                        m_clients.getPlayerNames(),
-                                       m_uptime.get(),
+                                       m_uptime_counter->get(),
                                        m_env->getGameTime(),
-                                       m_lag,
+                                       m_lag_gauge->get(),
                                        m_gamespec.id,
                                        Mapgen::getMapgenName(m_emerge->mgparams->mgtype),
                                        m_modmgr->getMods(),
@@ -638,6 +666,7 @@ void Server::AsyncRunStep(bool initial_step)
                const RemoteClientMap &clients = m_clients.getClientList();
                ScopeProfiler sp(g_profiler, "Server: update objects within range");
 
+               m_player_gauge->set(clients.size());
                for (const auto &client_it : clients) {
                        RemoteClient *client = client_it.second;
 
@@ -703,6 +732,8 @@ void Server::AsyncRunStep(bool initial_step)
                        message_list->push_back(aom);
                }
 
+               m_aom_buffer_counter->increment(buffered_messages.size());
+
                m_clients.lock();
                const RemoteClientMap &clients = m_clients.getClientList();
                // Route data to every client
@@ -943,7 +974,9 @@ void Server::Receive()
                        }
 
                        peer_id = pkt.getPeerId();
+                       m_packet_recv_counter->increment();
                        ProcessData(&pkt);
+                       m_packet_recv_processed_counter->increment();
                } catch (const con::InvalidIncomingDataException &e) {
                        infostream << "Server::Receive(): InvalidIncomingDataException: what()="
                                        << e.what() << std::endl;
@@ -3127,7 +3160,7 @@ std::wstring Server::getStatusString()
        // Version
        os << L"version=" << narrow_to_wide(g_version_string);
        // Uptime
-       os << L", uptime=" << m_uptime.get();
+       os << L", uptime=" << m_uptime_counter->get();
        // Max lag estimate
        os << L", max_lag=" << (m_env ? m_env->getMaxLagEstimate() : 0);
 
index b995aba287a91c80f0d8d8168795ad2dcee185e7..71059dd307c6b89cc1a967aefcff9022093f5d55 100644 (file)
@@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "util/numeric.h"
 #include "util/thread.h"
 #include "util/basic_macros.h"
+#include "util/metricsbackend.h"
 #include "serverenvironment.h"
 #include "clientiface.h"
 #include "chatmessage.h"
@@ -203,7 +204,7 @@ public:
 
        // Connection must be locked when called
        std::wstring getStatusString();
-       inline double getUptime() const { return m_uptime.m_value; }
+       inline double getUptime() const { return m_uptime_counter->get(); }
 
        // read shutdown state
        inline bool isShutdownRequested() const { return m_shutdown_state.is_requested; }
@@ -591,9 +592,6 @@ private:
        float m_step_dtime = 0.0f;
        std::mutex m_step_dtime_mutex;
 
-       // current server step lag counter
-       float m_lag;
-
        // The server mainly operates in this thread
        ServerThread *m_thread = nullptr;
 
@@ -602,8 +600,6 @@ private:
        */
        // Timer for sending time of day over network
        float m_time_of_day_send_timer = 0.0f;
-       // Uptime of server in seconds
-       MutexedVariable<double> m_uptime;
 
        /*
                Client interface
@@ -677,6 +673,19 @@ private:
 
        // ModChannel manager
        std::unique_ptr<ModChannelMgr> m_modchannel_mgr;
+
+       // Global server metrics backend
+       std::unique_ptr<MetricsBackend> m_metrics_backend;
+
+       // Server metrics
+       MetricCounterPtr m_uptime_counter;
+       MetricGaugePtr m_player_gauge;
+       MetricGaugePtr m_timeofday_gauge;
+       // current server step lag
+       MetricGaugePtr m_lag_gauge;
+       MetricCounterPtr m_aom_buffer_counter;
+       MetricCounterPtr m_packet_recv_counter;
+       MetricCounterPtr m_packet_recv_processed_counter;
 };
 
 /*
index c8d8a28e290b76192f97ace87a27d9aaa0a1cc82..6ac530739bde5f109b6b8f58c16ba3ba3c3ccd45 100644 (file)
@@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "scripting_server.h"
 #include "content/subgames.h"
 #include "porting.h"
+#include "util/metricsbackend.h"
 
 /**
  * Manage server mods
index 2bc1aa22ff7f21eeab63ebd1fd87851a9603bf96..54774bd8637124097065e748ce382e214d513bc2 100644 (file)
@@ -20,7 +20,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #pragma once
 
 #include "content/mods.h"
+#include <memory>
 
+class MetricsBackend;
+class MetricCounter;
 class ServerScripting;
 
 /**
index 199d3aeaa10ae30e6150b2d34313c9da53c19996..cd2e468d197f73a7e2791d926a6ed24576bea605 100644 (file)
@@ -5,6 +5,7 @@ set(UTIL_SRCS
        ${CMAKE_CURRENT_SOURCE_DIR}/directiontables.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/enriched_string.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/ieee_float.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/metricsbackend.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/numeric.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/pointedthing.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/quicktune.cpp
diff --git a/src/util/metricsbackend.cpp b/src/util/metricsbackend.cpp
new file mode 100644 (file)
index 0000000..4454557
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+Minetest
+Copyright (C) 2013-2020 Minetest core developers team
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "metricsbackend.h"
+#if USE_PROMETHEUS
+#include <prometheus/exposer.h>
+#include <prometheus/registry.h>
+#include <prometheus/counter.h>
+#include <prometheus/gauge.h>
+#include "log.h"
+#include "settings.h"
+#endif
+
+MetricCounterPtr MetricsBackend::addCounter(
+               const std::string &name, const std::string &help_str)
+{
+       return std::make_shared<SimpleMetricCounter>(name, help_str);
+}
+
+MetricGaugePtr MetricsBackend::addGauge(
+               const std::string &name, const std::string &help_str)
+{
+       return std::make_shared<SimpleMetricGauge>(name, help_str);
+}
+
+#if USE_PROMETHEUS
+
+class PrometheusMetricCounter : public MetricCounter
+{
+public:
+       PrometheusMetricCounter() = delete;
+
+       PrometheusMetricCounter(const std::string &name, const std::string &help_str,
+                       std::shared_ptr<prometheus::Registry> registry) :
+                       MetricCounter(),
+                       m_family(prometheus::BuildCounter()
+                                                       .Name(name)
+                                                       .Help(help_str)
+                                                       .Register(*registry)),
+                       m_counter(m_family.Add({}))
+       {
+       }
+
+       virtual ~PrometheusMetricCounter() {}
+
+       virtual void increment(double number) { m_counter.Increment(number); }
+       virtual double get() const { return m_counter.Value(); }
+
+private:
+       prometheus::Family<prometheus::Counter> &m_family;
+       prometheus::Counter &m_counter;
+};
+
+class PrometheusMetricGauge : public MetricGauge
+{
+public:
+       PrometheusMetricGauge() = delete;
+
+       PrometheusMetricGauge(const std::string &name, const std::string &help_str,
+                       std::shared_ptr<prometheus::Registry> registry) :
+                       MetricGauge(),
+                       m_family(prometheus::BuildGauge()
+                                                       .Name(name)
+                                                       .Help(help_str)
+                                                       .Register(*registry)),
+                       m_gauge(m_family.Add({}))
+       {
+       }
+
+       virtual ~PrometheusMetricGauge() {}
+
+       virtual void increment(double number) { m_gauge.Increment(number); }
+       virtual void decrement(double number) { m_gauge.Decrement(number); }
+       virtual void set(double number) { m_gauge.Set(number); }
+       virtual double get() const { return m_gauge.Value(); }
+
+private:
+       prometheus::Family<prometheus::Gauge> &m_family;
+       prometheus::Gauge &m_gauge;
+};
+
+class PrometheusMetricsBackend : public MetricsBackend
+{
+public:
+       PrometheusMetricsBackend(const std::string &addr) :
+                       MetricsBackend(), m_exposer(std::unique_ptr<prometheus::Exposer>(
+                                                         new prometheus::Exposer(addr))),
+                       m_registry(std::make_shared<prometheus::Registry>())
+       {
+               m_exposer->RegisterCollectable(m_registry);
+       }
+
+       virtual ~PrometheusMetricsBackend() {}
+
+       virtual MetricCounterPtr addCounter(
+                       const std::string &name, const std::string &help_str);
+       virtual MetricGaugePtr addGauge(
+                       const std::string &name, const std::string &help_str);
+
+private:
+       std::unique_ptr<prometheus::Exposer> m_exposer;
+       std::shared_ptr<prometheus::Registry> m_registry;
+};
+
+MetricCounterPtr PrometheusMetricsBackend::addCounter(
+               const std::string &name, const std::string &help_str)
+{
+       return std::make_shared<PrometheusMetricCounter>(name, help_str, m_registry);
+}
+
+MetricGaugePtr PrometheusMetricsBackend::addGauge(
+               const std::string &name, const std::string &help_str)
+{
+       return std::make_shared<PrometheusMetricGauge>(name, help_str, m_registry);
+}
+
+MetricsBackend *createPrometheusMetricsBackend()
+{
+       std::string addr;
+       g_settings->getNoEx("prometheus_listener_address", addr);
+       return new PrometheusMetricsBackend(addr);
+}
+
+#endif
diff --git a/src/util/metricsbackend.h b/src/util/metricsbackend.h
new file mode 100644 (file)
index 0000000..c373063
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+Minetest
+Copyright (C) 2013-2020 Minetest core developers team
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+#include <memory>
+#include "config.h"
+#include "util/thread.h"
+
+class MetricCounter
+{
+public:
+       MetricCounter() = default;
+
+       virtual ~MetricCounter() {}
+
+       virtual void increment(double number = 1.0) = 0;
+       virtual double get() const = 0;
+};
+
+typedef std::shared_ptr<MetricCounter> MetricCounterPtr;
+
+class SimpleMetricCounter : public MetricCounter
+{
+public:
+       SimpleMetricCounter() = delete;
+
+       virtual ~SimpleMetricCounter() {}
+
+       SimpleMetricCounter(const std::string &name, const std::string &help_str) :
+                       MetricCounter(), m_name(name), m_help_str(help_str),
+                       m_counter(0.0)
+       {
+       }
+
+       virtual void increment(double number)
+       {
+               MutexAutoLock lock(m_mutex);
+               m_counter += number;
+       }
+       virtual double get() const
+       {
+               MutexAutoLock lock(m_mutex);
+               return m_counter;
+       }
+
+private:
+       std::string m_name;
+       std::string m_help_str;
+
+       mutable std::mutex m_mutex;
+       double m_counter;
+};
+
+class MetricGauge
+{
+public:
+       MetricGauge() = default;
+       virtual ~MetricGauge() {}
+
+       virtual void increment(double number = 1.0) = 0;
+       virtual void decrement(double number = 1.0) = 0;
+       virtual void set(double number) = 0;
+       virtual double get() const = 0;
+};
+
+typedef std::shared_ptr<MetricGauge> MetricGaugePtr;
+
+class SimpleMetricGauge : public MetricGauge
+{
+public:
+       SimpleMetricGauge() = delete;
+
+       SimpleMetricGauge(const std::string &name, const std::string &help_str) :
+                       MetricGauge(), m_name(name), m_help_str(help_str), m_gauge(0.0)
+       {
+       }
+
+       virtual ~SimpleMetricGauge() {}
+
+       virtual void increment(double number)
+       {
+               MutexAutoLock lock(m_mutex);
+               m_gauge += number;
+       }
+       virtual void decrement(double number)
+       {
+               MutexAutoLock lock(m_mutex);
+               m_gauge -= number;
+       }
+       virtual void set(double number)
+       {
+               MutexAutoLock lock(m_mutex);
+               m_gauge = number;
+       }
+       virtual double get() const
+       {
+               MutexAutoLock lock(m_mutex);
+               return m_gauge;
+       }
+
+private:
+       std::string m_name;
+       std::string m_help_str;
+
+       mutable std::mutex m_mutex;
+       double m_gauge;
+};
+
+class MetricsBackend
+{
+public:
+       MetricsBackend() = default;
+
+       virtual ~MetricsBackend() {}
+
+       virtual MetricCounterPtr addCounter(
+                       const std::string &name, const std::string &help_str);
+       virtual MetricGaugePtr addGauge(
+                       const std::string &name, const std::string &help_str);
+};
+
+#if USE_PROMETHEUS
+MetricsBackend *createPrometheusMetricsBackend();
+#endif
diff --git a/util/ci/build_prometheus_cpp.sh b/util/ci/build_prometheus_cpp.sh
new file mode 100755 (executable)
index 0000000..edfd574
--- /dev/null
@@ -0,0 +1,13 @@
+#! /bin/bash -eu
+
+cd /tmp
+git clone --recursive https://github.com/jupp0r/prometheus-cpp
+mkdir prometheus-cpp/build
+cd prometheus-cpp/build
+cmake .. \
+       -DCMAKE_INSTALL_PREFIX=/usr/local \
+       -DCMAKE_BUILD_TYPE=Release \
+       -DENABLE_TESTING=0
+make -j2
+sudo make install
+