├── .github └── workflows │ ├── luacheck.yml │ └── test.yml ├── .luacheckrc ├── README.md ├── builtin ├── abm_calls.lua ├── after.lua ├── api.lua ├── auth_fail.lua ├── chat_count.lua ├── cheat_count.lua ├── craft_count.lua ├── dig_count.lua ├── eat_count.lua ├── forceload_blocks.lua ├── generated.lua ├── globalstep.lua ├── jit.lua ├── join_count.lua ├── lag.lua ├── lbm_calls.lua ├── leave_count.lua ├── liquid_transformed.lua ├── log.lua ├── luamem.lua ├── nodetimer_calls.lua ├── on_joinplayer.lua ├── on_prejoinplayer.lua ├── on_step.lua ├── place_count.lua ├── playercount.lua ├── protection.lua ├── protection_violation_count.lua ├── punchplayer.lua ├── received_fields.lua ├── registered_count.lua ├── settings.lua ├── ticks.lua ├── time.lua ├── uptime.lua └── version.lua ├── chatcommands.lua ├── dev ├── docker-compose.yml └── minetest.conf ├── doc ├── Makefile ├── chatcommands.md ├── custom.md ├── dashboard-overview.json ├── docker.md ├── exporters.md ├── gnu.plot ├── hosted.md ├── install.md ├── standalone-stack.dot ├── standalone-stack.png └── standalone.md ├── docker ├── docker-compose.yml ├── grafana.ini └── prometheus.yml ├── export ├── csv.lua ├── json.lua ├── prometheus_push.lua └── prometheus_push.spec.lua ├── init.lua ├── init.spec.lua ├── license.txt ├── metrictypes ├── counter.lua ├── counter.spec.lua ├── gauge.lua └── histogram.lua ├── mod.conf ├── mods ├── advtrains │ ├── chatcommands.lua │ └── metrics.lua ├── basic_machines │ └── init.lua ├── digilines │ ├── init.lua │ └── metric_controller.lua ├── mesecons │ ├── action_on.lua │ ├── functions.lua │ ├── globals.lua │ ├── luac.lua │ └── queue.lua ├── nodecore │ └── nc_optics.lua ├── pipeworks │ ├── can_go.lua │ ├── chatcommands.lua │ ├── entity_count.lua │ ├── expiration.lua │ ├── filter_action_on.lua │ ├── flush.lua │ ├── globalsteps.lua │ ├── init.lua │ ├── inject_limiter.lua │ ├── metrics.lua │ ├── teleport_tubes.lua │ └── tube_inject_item.lua └── technic │ ├── abm.lua │ ├── quarry.lua │ ├── switch.lua │ └── technic_run.lua ├── pics ├── craft.png └── lag.png ├── prom_push_example.sh ├── register.lua ├── sampling.lua ├── settingtypes.txt └── textures └── monitoring_controller_top.png /.github/workflows/luacheck.yml: -------------------------------------------------------------------------------- 1 | name: luacheck 2 | on: [push, pull_request] 3 | jobs: 4 | luacheck: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout 8 | uses: actions/checkout@master 9 | - name: Luacheck 10 | uses: lunarmodules/luacheck@master 11 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: buckaroobanzay/mtt@main 12 | with: 13 | modname: monitoring 14 | enable_coverage: "true" 15 | - uses: coverallsapp/github-action@v1 -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | globals = { 2 | "monitoring", 3 | "minetest", 4 | "technic", 5 | "mesecon", 6 | "pipeworks" 7 | } 8 | 9 | read_globals = { 10 | -- Stdlib 11 | string = {fields = {"split"}}, 12 | table = {fields = {"copy", "getn"}}, 13 | 14 | -- Minetest 15 | "vector", "ItemStack", 16 | "dump", 17 | 18 | -- optional dependencies 19 | "QoS", "mtt", "digiline", "advtrains" 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Monitoring framework for minetest 2 | Provides a [prometheus](https://prometheus.io) monitoring endpoint (via push-gateway). 3 | 4 | ![](https://github.com/minetest-monitoring/monitoring/workflows/luacheck/badge.svg) 5 | ![](https://github.com/minetest-monitoring/monitoring/workflows/test/badge.svg) 6 | [![License](https://img.shields.io/badge/License-MIT-green.svg)](license.txt) 7 | [![Coverage Status](https://coveralls.io/repos/github/minetest-monitoring/monitoring/badge.svg?branch=master)](https://coveralls.io/github/minetest-monitoring/monitoring?branch=master) 8 | 9 | ## Demo 10 | 11 | * [monitoring.minetest.ch](https://monitoring.minetest.ch/d/YUpouLmWk/lua-server-monitoring-mod?tab=visualization&orgId=1&refresh=5s&var-instance=pandorabox.io) 12 | 13 | ## Documentation 14 | 15 | * [Custom metrics](doc/custom.md) 16 | * [Exporters](doc/exporters.md) 17 | * [Chat commands](doc/chatcommands.md) 18 | * [Installation of the mod](doc/install.md) 19 | * [Hosted](doc/hosted.md) 20 | * [Docker](doc/docker.md) 21 | * [Installation of the tools (without docker)](doc/standalone.md) 22 | 23 | ## Features 24 | 25 | * Builtin metrics (lag, mapgen, time, uptime, auth, etc.). 26 | * Supports the **gauge**, **counter** and **histogram** metrics. 27 | 28 | ## Mod integrations 29 | 30 | * advtrains 31 | * technic 32 | * mesecons 33 | * digtron 34 | * protector 35 | 36 | ## Screenshots 37 | 38 | ![](./pics/lag.png?raw=true) 39 | 40 | ![](./pics/craft.png?raw=true) 41 | 42 | # Pipeworks Features 43 | 44 | * Tubed item management (flushing) 45 | * Enable and disable pipeworks at runtime 46 | * Item expiration (tubed items only stay 10 minutes in the pipes) 47 | * Item injection limit per mapchunk 48 | 49 | # Chatcommands 50 | 51 | * **/pipeworks_flush** flushes (removes) all items in the tubes 52 | * **/pipeworks_stats** shows the item count 53 | * **/pipeworks_enable** enables the pipeworks mod at runtime 54 | * **/pipeworks_disable** disables the pieworks mod at runtime 55 | * **/pipeworks_check_limit** check the injection limits on the current mapchunk 56 | * **/pipeworks_limit_stats** shows the chunk with the highest injection rate 57 | 58 | 59 | ## Metric controller 60 | 61 | Usage: 62 | 63 | Reading: 64 | ```lua 65 | if event.type == "program" then 66 | -- query metric by its name 67 | digiline_send("ctrl_channel", "pipeworks_entity_count") 68 | end 69 | 70 | if event.type == "digiline" and event.channel == "ctrl_channel" then 71 | print("Pipeworks entities: " .. event.msg) 72 | end 73 | ``` 74 | 75 | Writing: 76 | ```lua 77 | digiline_send("channel", { 78 | metric = "ingame_lua_tube_mese", 79 | help = "my mese lua tube count" 80 | counter = true, 81 | increment = true, 82 | value = 20 83 | }) 84 | ``` 85 | 86 | ## License 87 | 88 | * Code: MIT 89 | * textures/monitoring_controller_top.png 90 | * CC BY-SA 3.0 https://cheapiesystems.com/git/digistuff 91 | -------------------------------------------------------------------------------- /builtin/abm_calls.lua: -------------------------------------------------------------------------------- 1 | local get_us_time = minetest.get_us_time 2 | local metric = monitoring.counter("abm_count", "number of abm calls") 3 | local metric_time = monitoring.counter("abm_time", "time usage in microseconds for abm calls") 4 | 5 | local metric_time_max = monitoring.gauge( 6 | "abm_time_max", 7 | "max time usage in microseconds for abm calls", 8 | { autoflush=true } 9 | ) 10 | 11 | 12 | local global_abms_enabled = true 13 | 14 | minetest.register_on_mods_loaded(function() 15 | for _, abm in ipairs(minetest.registered_abms) do 16 | local old_action = abm.action 17 | abm.action = function(pos, node, active_object_count, active_object_count_wider) 18 | 19 | if not global_abms_enabled then 20 | return 21 | end 22 | 23 | metric.inc() 24 | local t0 = get_us_time() 25 | old_action(pos, node, active_object_count, active_object_count_wider) 26 | local t1 = get_us_time() 27 | local diff = t1 - t0 28 | metric_time.inc(diff) 29 | metric_time_max.setmax(diff) 30 | end 31 | end 32 | end) 33 | 34 | 35 | minetest.register_chatcommand("abm_disable", { 36 | description = "disables all abm's", 37 | privs = {server=true}, 38 | func = function(name) 39 | minetest.log("warning", "Player " .. name .. " disables all abm's") 40 | global_abms_enabled = false 41 | end 42 | }) 43 | 44 | minetest.register_chatcommand("abm_enable", { 45 | description = "enables all abm's", 46 | privs = {server=true}, 47 | func = function(name) 48 | minetest.log("warning", "Player " .. name .. " enables all abm's") 49 | global_abms_enabled = true 50 | end 51 | }) 52 | -------------------------------------------------------------------------------- /builtin/after.lua: -------------------------------------------------------------------------------- 1 | local metric = monitoring.counter("minetest_after_count", "number of after() calls") 2 | local metric_time = monitoring.counter("minetest_after_time", "time usage in microseconds for after() calls") 3 | 4 | local old_minetest_after = minetest.after 5 | 6 | minetest.after = function(delay, callback, ...) 7 | callback = metric_time.wraptime(callback) 8 | metric.inc() 9 | return old_minetest_after(delay, callback, ...) 10 | end -------------------------------------------------------------------------------- /builtin/api.lua: -------------------------------------------------------------------------------- 1 | 2 | monitoring.wrap_global({"minetest", "find_nodes_in_area"}, "find_nodes_in_area") 3 | monitoring.wrap_global({"minetest", "find_node_near"}, "find_node_near") 4 | monitoring.wrap_global({"minetest", "get_objects_inside_radius"}, "get_objects_inside_radius") 5 | monitoring.wrap_global({"minetest", "check_player_privs"}, "check_player_privs") 6 | monitoring.wrap_global({"minetest", "get_craft_result"}, "get_craft_result") 7 | monitoring.wrap_global({"minetest", "get_craft_recipe"}, "get_craft_recipe") 8 | monitoring.wrap_global({"minetest", "get_all_craft_recipes"}, "get_all_craft_recipes") 9 | 10 | -- call intensive functions, only enable if explicitly set 11 | if minetest.settings:get_bool("monitoring.verbose") then 12 | monitoring.wrap_global({"minetest", "add_entity"}, "add_entity") 13 | monitoring.wrap_global({"minetest", "add_item"}, "add_item") 14 | monitoring.wrap_global({"minetest", "add_node"}, "add_node") 15 | 16 | monitoring.wrap_global({"minetest", "add_particle"}, "add_particle") 17 | monitoring.wrap_global({"minetest", "add_particlespawner"}, "add_particlespawner") 18 | 19 | monitoring.wrap_global({"minetest", "get_node"}, "get_node") 20 | monitoring.wrap_global({"minetest", "get_node_or_nil"}, "get_node_or_nil") 21 | monitoring.wrap_global({"minetest", "get_meta"}, "get_meta") 22 | 23 | monitoring.wrap_global({"minetest", "swap_node"}, "swap_node") 24 | monitoring.wrap_global({"minetest", "load_area"}, "load_area") 25 | monitoring.wrap_global({"minetest", "get_voxel_manip"}, "get_voxel_manip") 26 | 27 | monitoring.wrap_global({"minetest", "set_node"}, "set_node") 28 | monitoring.wrap_global({"minetest", "find_path"}, "find_path") 29 | 30 | monitoring.wrap_global({"minetest", "check_for_falling"}, "check_for_falling") 31 | end 32 | -------------------------------------------------------------------------------- /builtin/auth_fail.lua: -------------------------------------------------------------------------------- 1 | if minetest.register_on_auth_fail then 2 | local metric = monitoring.counter("auth_fail_count", "number of auth fails") 3 | minetest.register_on_authplayer(function(_, _, is_success) 4 | if is_success == false then 5 | metric.inc() 6 | end 7 | end) 8 | end 9 | -------------------------------------------------------------------------------- /builtin/chat_count.lua: -------------------------------------------------------------------------------- 1 | local metric = monitoring.counter("chat_count", "number of chat messages") 2 | minetest.register_on_chat_message(function() 3 | metric.inc() 4 | end) 5 | -------------------------------------------------------------------------------- /builtin/cheat_count.lua: -------------------------------------------------------------------------------- 1 | local metric = monitoring.counter("cheat_count", "number of on_cheat") 2 | 3 | -- TODO: labels for cheat.type: 4 | -- `moved_too_fast` `interacted_too_far` `interacted_while_dead` `finished_unknown_dig` `dug_unbreakable` `dug_too_fast` 5 | minetest.register_on_cheat(function() 6 | metric.inc() 7 | end) 8 | -------------------------------------------------------------------------------- /builtin/craft_count.lua: -------------------------------------------------------------------------------- 1 | local metric = monitoring.counter("craft_count", "crafted items counter") 2 | 3 | minetest.register_on_craft(function(itemstack, player) 4 | if player and player:is_player() then 5 | metric.inc(itemstack:get_count()) 6 | end 7 | end) 8 | -------------------------------------------------------------------------------- /builtin/dig_count.lua: -------------------------------------------------------------------------------- 1 | local metric = monitoring.counter("dig_count", "digged nodes counter") 2 | 3 | minetest.register_on_dignode(function() 4 | metric.inc() 5 | end) 6 | -------------------------------------------------------------------------------- /builtin/eat_count.lua: -------------------------------------------------------------------------------- 1 | local metric = monitoring.counter("eat_count", "number of eaten items") 2 | 3 | minetest.register_on_item_eat(function() 4 | metric.inc() 5 | end) 6 | -------------------------------------------------------------------------------- /builtin/forceload_blocks.lua: -------------------------------------------------------------------------------- 1 | local metric = monitoring.gauge("forceload_blocks_count", "number of forceload blocks") 2 | 3 | local blocks_forceloaded = {} 4 | 5 | local function update_metric() 6 | local counter = 0 7 | for _, _ in pairs(blocks_forceloaded) do 8 | counter = counter + 1 9 | end 10 | metric.set(counter) 11 | end 12 | 13 | local function get_blockpos(pos) 14 | return { 15 | x = math.floor(pos.x/16), 16 | y = math.floor(pos.y/16), 17 | z = math.floor(pos.z/16)} 18 | end 19 | 20 | local old_forceload_block = minetest.forceload_block 21 | 22 | function minetest.forceload_block(pos, transient, limit) 23 | local retval = old_forceload_block(pos, transient, limit) 24 | 25 | if retval == true then 26 | local blockpos = get_blockpos(pos) 27 | local hash = minetest.hash_node_position(blockpos) 28 | blocks_forceloaded[hash] = true 29 | update_metric() 30 | end 31 | 32 | return retval 33 | end 34 | 35 | local old_forceload_free_block = minetest.forceload_free_block 36 | 37 | function minetest.forceload_free_block(pos, transient) 38 | local blockpos = get_blockpos(pos) 39 | local hash = minetest.hash_node_position(blockpos) 40 | blocks_forceloaded[hash] = nil -- remove from cache 41 | update_metric() 42 | 43 | return old_forceload_free_block(pos, transient) 44 | end 45 | 46 | -- returns true if the block at the node-position is forceloaded 47 | function monitoring.is_forceloaded(pos) 48 | local blockpos = get_blockpos(pos) 49 | local hash = minetest.hash_node_position(blockpos) 50 | return blocks_forceloaded[hash] == true 51 | end 52 | 53 | minetest.register_on_mods_loaded(function() 54 | update_metric() 55 | end) 56 | 57 | -- minetest doesn't call the global api on startup 58 | 59 | local wpath = minetest.get_worldpath() 60 | 61 | -- stolen from https://github.com/minetest/minetest/blob/master/builtin/game/forceloading.lua 62 | local function read_file(filename) 63 | local f = io.open(filename, "r") 64 | if f == nil then return {} end 65 | local t = f:read("*all") 66 | f:close() 67 | if t == "" or t == nil then return {} end 68 | 69 | return minetest.deserialize(t) or {} 70 | end 71 | 72 | local forceloadedtxt = read_file(wpath.."/force_loaded.txt") 73 | 74 | minetest.after(5, function() 75 | for hash, _ in pairs(forceloadedtxt) do 76 | blocks_forceloaded[hash] = true 77 | end 78 | update_metric() 79 | end) 80 | -------------------------------------------------------------------------------- /builtin/generated.lua: -------------------------------------------------------------------------------- 1 | -- generated blocks (80x80) 2 | local get_us_time = minetest.get_us_time 3 | 4 | local metric = monitoring.counter("mapgen_generated_count", "Generated mapgen count") 5 | 6 | local metric_time = monitoring.counter("on_generated_time", "time usage in microseconds for on_generated calls") 7 | local metric_time_max = monitoring.gauge( 8 | "on_generated_time_max", 9 | "max time usage in microseconds for on_generated calls", 10 | { autoflush=true } 11 | ) 12 | 13 | minetest.register_on_generated(function() 14 | -- increment chunk count metric 15 | metric.inc() 16 | end) 17 | 18 | minetest.register_on_mods_loaded(function() 19 | for i, on_generated in ipairs(minetest.registered_on_generateds) do 20 | minetest.registered_on_generateds[i] = function(...) 21 | local t0 = get_us_time() 22 | on_generated(...) 23 | local t1 = get_us_time() 24 | 25 | local diff = t1 - t0 26 | metric_time.inc(diff) 27 | metric_time_max.setmax(diff) 28 | end 29 | end 30 | end) 31 | -------------------------------------------------------------------------------- /builtin/globalstep.lua: -------------------------------------------------------------------------------- 1 | local get_us_time = minetest.get_us_time 2 | local metric_callbacks = monitoring.gauge("globalstep_callback_count", "number of globalstep callbacks") 3 | local metric = monitoring.counter("globalstep_count", "number of globalstep calls") 4 | local metric_time = monitoring.counter("globalstep_time", "time usage in microseconds for globalstep calls") 5 | local metric_time_max = monitoring.gauge( 6 | "globalstep_time_max", 7 | "max time usage in microseconds for globalstep calls", 8 | { autoflush=true } 9 | ) 10 | 11 | -- -> { strategy = "" } 12 | -- expose config for other mods 13 | monitoring.globalsteps_config = {} 14 | 15 | -- globalstep run strategies 16 | local STRATEGY_DISABLED = "DISABLED" 17 | local STRATEGY_SKIP = "SKIP" 18 | local strategies = { 19 | -- entirely disabled 20 | [STRATEGY_DISABLED] = function() return false end, 21 | -- skip calls 22 | [STRATEGY_SKIP] = function(cfg) 23 | cfg.count = cfg.count and cfg.count + 1 or 1 24 | cfg.skip = cfg.skip or 2 -- every 2nd call as default 25 | if cfg.count >= cfg.skip then 26 | cfg.count = 0 27 | return true 28 | end 29 | return false 30 | end 31 | } 32 | 33 | local function wrap_globalsteps() 34 | metric_callbacks.set(#minetest.registered_globalsteps) 35 | 36 | -- info.name => 37 | local step_name_count = {} 38 | 39 | for i, globalstep in ipairs(minetest.registered_globalsteps) do 40 | local info = minetest.callback_origins[globalstep] 41 | 42 | -- get unique name for globalstep entry 43 | local modname = info.mod 44 | if not modname or modname == "" then 45 | modname = "unknown" 46 | end 47 | local name_count = step_name_count[modname] 48 | if not name_count then 49 | name_count = 1 50 | end 51 | local globalstep_key = modname .. "_" .. name_count 52 | name_count = name_count + 1 53 | step_name_count[modname] = name_count 54 | 55 | local new_callback = function(dtime) 56 | local cfg = monitoring.globalsteps_config[globalstep_key] 57 | if not cfg then 58 | -- set up empty config 59 | cfg = {} 60 | monitoring.globalsteps_config[globalstep_key] = cfg 61 | end 62 | local strategy_fn = strategies[cfg.strategy] 63 | if strategy_fn then 64 | -- call strategy 65 | local enable = strategy_fn(cfg) 66 | if not enable then 67 | return 68 | end 69 | end 70 | 71 | metric.inc() 72 | local t0 = get_us_time() 73 | globalstep(dtime) 74 | local t1 = get_us_time() 75 | local diff = t1 - t0 76 | 77 | metric_time.inc(diff) 78 | metric_time_max.setmax(diff) 79 | 80 | -- time table 81 | local tt_entry = cfg.time_us or 0 82 | tt_entry = tt_entry + diff 83 | cfg.time_us = tt_entry 84 | end 85 | 86 | minetest.registered_globalsteps[i] = new_callback 87 | 88 | -- for the profiler 89 | if minetest.callback_origins then 90 | minetest.callback_origins[new_callback] = info 91 | end 92 | end 93 | end 94 | 95 | -- delay globalstep "wrapping" until the mesecons mod did their hack (set to 4 seconds) 96 | -- see: https://github.com/minetest-mods/mesecons/blob/master/mesecons/actionqueue.lua#L118 97 | minetest.register_on_mods_loaded(function() 98 | minetest.after(5, function() 99 | print("[monitoring] wrapping globalstep callbacks") 100 | wrap_globalsteps() 101 | end) 102 | end) 103 | 104 | minetest.register_chatcommand("globalstep_disable", { 105 | description = "disables a globalstep", 106 | privs = {server=true}, 107 | func = function(name, param) 108 | if not param then 109 | minetest.chat_send_player(name, "Usage: globalstep_disable ") 110 | return false 111 | end 112 | 113 | minetest.log("warning", "Player " .. name .. " disables globalstep " .. param) 114 | monitoring.globalsteps_config[param] = { strategy = STRATEGY_DISABLED } 115 | end 116 | }) 117 | 118 | minetest.register_chatcommand("globalstep_enable", { 119 | description = "enables a globalstep", 120 | privs = {server=true}, 121 | func = function(name, param) 122 | if not param then 123 | minetest.chat_send_player(name, "Usage: globalstep_enable ") 124 | return false 125 | end 126 | 127 | minetest.log("warning", "Player " .. name .. " enables globalstep " .. param) 128 | monitoring.globalsteps_config[param] = nil 129 | end 130 | }) 131 | 132 | minetest.register_chatcommand("globalsteps_enable", { 133 | description = "enables all globalsteps", 134 | privs = {server=true}, 135 | func = function(name) 136 | minetest.log("warning", "Player " .. name .. " enables all globalsteps") 137 | monitoring.globalsteps_config = {} 138 | end 139 | }) 140 | 141 | minetest.register_chatcommand("globalstep_status", { 142 | description = "shows the globalstep status", 143 | func = function() 144 | local list = "Globalstep status:\n" 145 | 146 | for name, cfg in pairs(monitoring.globalsteps_config) do 147 | list = list .. "* " .. name .. ", " .. 148 | "strategy: " .. (cfg.strategy or "DEFAULT") .. ", " .. 149 | "time: " .. (cfg.time_us or "UNKNOWN") .. " us" .. 150 | "\n" 151 | end 152 | 153 | return true, list 154 | end 155 | }) 156 | -------------------------------------------------------------------------------- /builtin/jit.lua: -------------------------------------------------------------------------------- 1 | local jit_version = monitoring.gauge("jit_version", "luajit version") 2 | local jit_enabled = monitoring.gauge("jit_enabled", "luajit enabled") 3 | 4 | if jit then 5 | if jit.version_num then 6 | jit_version.set(jit.version_num) 7 | else 8 | jit_version.set(0) 9 | end 10 | 11 | local enabled = jit.status() 12 | 13 | if enabled then 14 | jit_enabled.set(1) 15 | else 16 | jit_enabled.set(0) 17 | end 18 | else 19 | jit_version.set(0) 20 | jit_enabled.set(0) 21 | end 22 | -------------------------------------------------------------------------------- /builtin/join_count.lua: -------------------------------------------------------------------------------- 1 | local metric_join = monitoring.counter("player_join_count", "number of players joined") 2 | local metric_join_new = monitoring.counter("player_join_new_count", "number of new players joined") 3 | local metric_prejoin = monitoring.counter("player_prejoin_count", "number of players prejoined") 4 | 5 | minetest.register_on_prejoinplayer(function(name) 6 | metric_prejoin.inc() 7 | 8 | if not minetest.player_exists(name) then 9 | metric_join_new.inc() 10 | end 11 | end) 12 | 13 | minetest.register_on_joinplayer(function() 14 | metric_join.inc() 15 | end) 16 | -------------------------------------------------------------------------------- /builtin/lag.lua: -------------------------------------------------------------------------------- 1 | local get_us_time = minetest.get_us_time 2 | local max_metric = monitoring.gauge("max_lag", "max lag in seconds", { autoflush=true }) 3 | local min_metric = monitoring.gauge("min_lag", "min lag in seconds", { autoflush=true }) 4 | local avg_metric = monitoring.gauge("avg_lag", "avg lag in seconds") 5 | 6 | local lag_histogram = monitoring.histogram("lag", "lag histogram", 7 | {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}) 8 | 9 | 10 | local function explode(sep, input) 11 | local t={} 12 | local i=0 13 | for k in string.gmatch(input,"([^"..sep.."]+)") do 14 | t[i]=k 15 | i=i+1 16 | end 17 | return t 18 | end 19 | local function get_max_lag() 20 | local status = minetest.get_server_status() 21 | if not status then 22 | return 0 23 | end 24 | local arrayoutput = explode(", ",status) 25 | local arrayoutput2 = explode("=",arrayoutput[4]) 26 | return arrayoutput2[1] 27 | end 28 | 29 | local timer = 0 30 | local last_call_t = get_us_time() 31 | 32 | minetest.register_globalstep(function() 33 | -- calculate own delta-time 34 | local now = get_us_time() 35 | local deltat = (now - last_call_t) / 1000000 36 | 37 | -- swap timestamps 38 | last_call_t = now 39 | 40 | lag_histogram.observe(deltat) 41 | 42 | -- executed on every step 43 | max_metric.setmax( deltat ) 44 | min_metric.setmin( deltat ) 45 | 46 | timer = timer + deltat 47 | if timer < 5 then return end 48 | timer=0 49 | 50 | -- executed every few seconds 51 | avg_metric.set( tonumber(get_max_lag()) ) 52 | end) 53 | -------------------------------------------------------------------------------- /builtin/lbm_calls.lua: -------------------------------------------------------------------------------- 1 | local get_us_time = minetest.get_us_time 2 | local metric = monitoring.counter("lbm_count", "number of lbm calls") 3 | local metric_time = monitoring.counter("lbm_time", "time usage in microseconds for lbm calls") 4 | 5 | local metric_time_max = monitoring.gauge( 6 | "lbm_time_max", 7 | "max time usage in microseconds for lbm calls", 8 | { autoflush=true } 9 | ) 10 | 11 | 12 | local global_lbms_enabled = true 13 | 14 | minetest.register_on_mods_loaded(function() 15 | for _, lbm in ipairs(minetest.registered_lbms) do 16 | local old_action = lbm.action 17 | lbm.action = function(pos, node) 18 | 19 | if not global_lbms_enabled then 20 | return 21 | end 22 | 23 | metric.inc() 24 | local t0 = get_us_time() 25 | old_action(pos, node) 26 | local t1 = get_us_time() 27 | local diff = t1 - t0 28 | metric_time.inc(diff) 29 | metric_time_max.setmax(diff) 30 | end 31 | end 32 | end) 33 | 34 | minetest.register_chatcommand("lbm_disable", { 35 | description = "disables all lbm's", 36 | privs = {server=true}, 37 | func = function(name) 38 | minetest.log("warning", "Player " .. name .. " disables all lbm's") 39 | global_lbms_enabled = false 40 | end 41 | }) 42 | 43 | minetest.register_chatcommand("lbm_enable", { 44 | description = "enables all lbm's", 45 | privs = {server=true}, 46 | func = function(name) 47 | minetest.log("warning", "Player " .. name .. " enables all lbm's") 48 | global_lbms_enabled = true 49 | end 50 | }) 51 | -------------------------------------------------------------------------------- /builtin/leave_count.lua: -------------------------------------------------------------------------------- 1 | local metric_leave = monitoring.counter("player_leave_count", "number of players that left") 2 | local metric_leave_timeout = monitoring.counter("player_leave_timeout_count", "number of players left due to timeout") 3 | 4 | minetest.register_on_leaveplayer(function(_, timed_out) 5 | metric_leave.inc() 6 | if timed_out then 7 | metric_leave_timeout.inc() 8 | end 9 | end) 10 | -------------------------------------------------------------------------------- /builtin/liquid_transformed.lua: -------------------------------------------------------------------------------- 1 | 2 | if not minetest.registered_on_liquid_transformed then 3 | -- not available 4 | return 5 | end 6 | 7 | local metric_calls = monitoring.counter("liquid_transformed_calls", "count of liquid_transformed calls") 8 | local metric_time = monitoring.counter("liquid_transformed_time", "time of liquid_transformed calls") 9 | 10 | minetest.register_on_mods_loaded(function() 11 | for i, fn in ipairs(minetest.registered_on_liquid_transformed) do 12 | fn = metric_time.wraptime(fn) 13 | fn = metric_calls.wrap(fn) 14 | minetest.registered_on_liquid_transformed[i] = fn 15 | end 16 | end) -------------------------------------------------------------------------------- /builtin/log.lua: -------------------------------------------------------------------------------- 1 | local metric = monitoring.counter("log_count", "number of log messages") 2 | 3 | local old_log = minetest.log 4 | 5 | function minetest.log(...) 6 | metric.inc() 7 | return old_log(...) 8 | end -------------------------------------------------------------------------------- /builtin/luamem.lua: -------------------------------------------------------------------------------- 1 | local metric = monitoring.gauge("lua_mem_kb", "lua used memory in kilobytes", { autoflush=true }) 2 | local metric_min = monitoring.gauge("lua_mem_min_kb", "lua min used memory in kilobytes", { autoflush=true }) 3 | local metric_max = monitoring.gauge("lua_mem_max_kb", "lua max used memory in kilobytes", { autoflush=true }) 4 | 5 | minetest.register_globalstep(function() 6 | -- https://www.lua.org/manual/5.1/manual.html 7 | local mem = collectgarbage("count") 8 | metric_min.setmin(mem) 9 | metric_max.setmax(mem) 10 | metric.set(mem) 11 | end) 12 | -------------------------------------------------------------------------------- /builtin/nodetimer_calls.lua: -------------------------------------------------------------------------------- 1 | local get_us_time = minetest.get_us_time 2 | local metric = monitoring.counter("nodetimer_count", "number of nodetime calls") 3 | local metric_time = monitoring.counter("nodetimer_time", "time usage in microseconds for nodetimer calls") 4 | 5 | local metric_time_max = monitoring.gauge( 6 | "nodetimer_time_max", 7 | "max time usage in microseconds for nodetimer calls", 8 | { autoflush=true } 9 | ) 10 | 11 | 12 | local global_nodetimer_enabled = true 13 | 14 | minetest.register_on_mods_loaded(function() 15 | for _, node in pairs(minetest.registered_nodes) do 16 | if node.on_timer then 17 | local old_action = node.on_timer 18 | node.on_timer = function(pos, elapsed) 19 | 20 | if not global_nodetimer_enabled then 21 | -- rerun timer again 22 | return true 23 | end 24 | 25 | metric.inc() 26 | local t0 = get_us_time() 27 | local result = old_action(pos, elapsed) 28 | local t1 = get_us_time() 29 | local diff = t1 - t0 30 | 31 | metric_time.inc(diff) 32 | metric_time_max.setmax(diff) 33 | 34 | return result 35 | end 36 | end 37 | end 38 | end) 39 | 40 | 41 | 42 | 43 | minetest.register_chatcommand("nodetimer_disable", { 44 | description = "disables all nodetimers", 45 | privs = {server=true}, 46 | func = function(name) 47 | minetest.log("warning", "Player " .. name .. " disables all nodetimers") 48 | global_nodetimer_enabled = false 49 | end 50 | }) 51 | 52 | minetest.register_chatcommand("nodetimer_enable", { 53 | description = "enables all nodetimers", 54 | privs = {server=true}, 55 | func = function(name) 56 | minetest.log("warning", "Player " .. name .. " enables all nodetimers") 57 | global_nodetimer_enabled = true 58 | end 59 | }) 60 | -------------------------------------------------------------------------------- /builtin/on_joinplayer.lua: -------------------------------------------------------------------------------- 1 | local metric_time = monitoring.counter("registered_on_joinplayers_time", "time usage in microseconds" .. 2 | "for registered_on_joinplayers calls") 3 | local metric_time_max = monitoring.gauge( 4 | "registered_on_joinplayers_time_max", 5 | "max time usage in microseconds for registered_on_joinplayers calls", 6 | { autoflush=true } 7 | ) 8 | 9 | 10 | minetest.register_on_mods_loaded(function() 11 | for i, fn in ipairs(minetest.registered_on_joinplayers) do 12 | minetest.registered_on_joinplayers[i] = function(...) 13 | local t0 = minetest.get_us_time() 14 | fn(...) 15 | local t1 = minetest.get_us_time() 16 | 17 | local diff = t1 - t0 18 | metric_time.inc(diff) 19 | metric_time_max.setmax(diff) 20 | end 21 | end 22 | end) 23 | -------------------------------------------------------------------------------- /builtin/on_prejoinplayer.lua: -------------------------------------------------------------------------------- 1 | local metric_time = monitoring.counter("registered_on_prejoinplayers_time", "time usage in microseconds" .. 2 | "for registered_on_prejoinplayers calls") 3 | local metric_time_max = monitoring.gauge( 4 | "registered_on_prejoinplayers_time_max", 5 | "max time usage in microseconds for registered_on_prejoinplayers calls", 6 | { autoflush=true } 7 | ) 8 | 9 | 10 | minetest.register_on_mods_loaded(function() 11 | for i, fn in ipairs(minetest.registered_on_prejoinplayers) do 12 | 13 | minetest.registered_on_prejoinplayers[i] = function(...) 14 | local t0 = minetest.get_us_time() 15 | local result = fn(...) 16 | local t1 = minetest.get_us_time() 17 | 18 | local diff = t1 - t0 19 | metric_time.inc(diff) 20 | metric_time_max.setmax(diff) 21 | return result 22 | end 23 | 24 | end 25 | end) 26 | -------------------------------------------------------------------------------- /builtin/on_step.lua: -------------------------------------------------------------------------------- 1 | local get_us_time = minetest.get_us_time 2 | local metric = monitoring.counter("entity_on_step_count", "Entity on_step call count") 3 | 4 | local metric_time = monitoring.counter("entity_on_step_time", "time usage in microseconds for on_step calls") 5 | local metric_time_max = monitoring.gauge( 6 | "entity_on_step_time_max", 7 | "max time usage in microseconds for on_step calls", 8 | { autoflush=true } 9 | ) 10 | 11 | 12 | minetest.register_on_mods_loaded(function() 13 | for _, entity in pairs(minetest.registered_entities) do 14 | if type(entity.on_step) == "function" then 15 | local old_on_step = entity.on_step 16 | entity.on_step = function(...) 17 | local t0 = get_us_time() 18 | local result = old_on_step(...) 19 | local t1 = get_us_time() 20 | 21 | local diff = t1 - t0 22 | metric_time.inc(diff) 23 | metric_time_max.setmax(diff) 24 | metric.inc() 25 | 26 | return result 27 | end 28 | end 29 | end 30 | end) 31 | -------------------------------------------------------------------------------- /builtin/place_count.lua: -------------------------------------------------------------------------------- 1 | local metric = monitoring.counter("place_count", "placed nodes counter") 2 | 3 | minetest.register_on_placenode(function() 4 | metric.inc() 5 | end) 6 | -------------------------------------------------------------------------------- /builtin/playercount.lua: -------------------------------------------------------------------------------- 1 | local metric = monitoring.gauge("player_count", "number of players") 2 | 3 | 4 | local timer = 0 5 | minetest.register_globalstep(function(dtime) 6 | timer = timer + dtime 7 | if timer < 5 then return end 8 | timer=0 9 | 10 | metric.set( #minetest.get_connected_players() ) 11 | end) 12 | -------------------------------------------------------------------------------- /builtin/protection.lua: -------------------------------------------------------------------------------- 1 | 2 | monitoring.wrap_global({"minetest", "is_protected"}, "is_protected") 3 | -------------------------------------------------------------------------------- /builtin/protection_violation_count.lua: -------------------------------------------------------------------------------- 1 | local metric = monitoring.counter("protection_violation_count", "number of protection violations") 2 | 3 | minetest.register_on_protection_violation(function() 4 | metric.inc() 5 | end) 6 | -------------------------------------------------------------------------------- /builtin/punchplayer.lua: -------------------------------------------------------------------------------- 1 | local metric = monitoring.counter("player_punch_count", "number of players punched others") 2 | 3 | minetest.register_on_punchplayer(function() 4 | metric.inc() 5 | end) 6 | -------------------------------------------------------------------------------- /builtin/received_fields.lua: -------------------------------------------------------------------------------- 1 | local get_us_time = minetest.get_us_time 2 | local metric = monitoring.counter("received_fields", "received client fields count") 3 | 4 | minetest.register_on_player_receive_fields(function() 5 | metric.inc() 6 | end) 7 | 8 | 9 | local metric_time = monitoring.counter("on_player_receive_fields_time", "time usage in microseconds" .. 10 | "for on_player_receive_fields calls") 11 | local metric_time_max = monitoring.gauge( 12 | "on_player_receive_fields_time_max", 13 | "max time usage in microseconds for on_player_receive_fields calls", 14 | { autoflush=true } 15 | ) 16 | 17 | 18 | minetest.register_on_mods_loaded(function() 19 | for i, fn in ipairs(minetest.registered_on_player_receive_fields) do 20 | minetest.registered_on_player_receive_fields[i] = function(...) 21 | local t0 = get_us_time() 22 | local result = fn(...) 23 | local t1 = get_us_time() 24 | 25 | local diff = t1 - t0 26 | metric_time.inc(diff) 27 | metric_time_max.setmax(diff) 28 | return result 29 | end 30 | end 31 | end) 32 | -------------------------------------------------------------------------------- /builtin/registered_count.lua: -------------------------------------------------------------------------------- 1 | local metric_nodes = monitoring.gauge("registered_node_count", "number of registered nodes") 2 | local metric_items = monitoring.gauge("registered_items_count", "number of registered items") 3 | local metric_entities = monitoring.gauge("registered_entities_count", "number of registered entities") 4 | local metric_abm = monitoring.gauge("registered_abm_count", "number of registered abm's") 5 | 6 | -- https://stackoverflow.com/questions/2705793/how-to-get-number-of-entries-in-a-lua-table 7 | local function tablelength(T) 8 | local count = 0 9 | for _ in pairs(T) do count = count + 1 end 10 | return count 11 | end 12 | 13 | 14 | minetest.after(5, function() 15 | metric_nodes.set( tablelength(minetest.registered_nodes) ) 16 | metric_items.set( tablelength(minetest.registered_items) ) 17 | metric_entities.set( tablelength(minetest.registered_entities) ) 18 | metric_abm.set( tablelength(minetest.registered_abms) ) 19 | end) 20 | -------------------------------------------------------------------------------- /builtin/settings.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | local settings = { 4 | "max_packets_per_iteration", 5 | 6 | -- possibly live-setting when client relogs 7 | "max_simultaneous_block_sends_per_client", 8 | 9 | "max_users", 10 | "active_block_range", 11 | "active_object_send_range_blocks", 12 | "max_block_send_distance", 13 | "full_block_send_enable_min_time_from_building" 14 | } 15 | 16 | for _, setting in ipairs(settings) do 17 | local name = setting:gsub("[.]", "_") 18 | 19 | local metric = monitoring.gauge("setting_" .. name, "setting: " .. setting) 20 | 21 | local value = minetest.settings:get(setting) 22 | 23 | if value then 24 | metric.set(tonumber(value)) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /builtin/ticks.lua: -------------------------------------------------------------------------------- 1 | local metric = monitoring.counter("tick_count", "number of ticks") 2 | 3 | minetest.register_globalstep(function() 4 | metric.inc() 5 | end) 6 | -------------------------------------------------------------------------------- /builtin/time.lua: -------------------------------------------------------------------------------- 1 | local metric = monitoring.gauge("ingame_time", "ingame time") 2 | 3 | local timer = 0 4 | minetest.register_globalstep(function(dtime) 5 | timer = timer + dtime 6 | if timer < 5 then return end 7 | timer=0 8 | 9 | metric.set( minetest.get_timeofday() * 24000 ) 10 | end) 11 | -------------------------------------------------------------------------------- /builtin/uptime.lua: -------------------------------------------------------------------------------- 1 | local metric = monitoring.gauge("uptime", "server uptime") 2 | 3 | local timer = 0 4 | minetest.register_globalstep(function(dtime) 5 | timer = timer + dtime 6 | if timer < 5 then return end 7 | timer=0 8 | 9 | metric.set( minetest.get_server_uptime() ) 10 | end) 11 | -------------------------------------------------------------------------------- /builtin/version.lua: -------------------------------------------------------------------------------- 1 | local metric_major = monitoring.gauge("version_major", "monitoring major version") 2 | local metric_minor = monitoring.gauge("version_minor", "monitoring minor version") 3 | 4 | metric_major.set( monitoring.version_major ) 5 | metric_minor.set( monitoring.version_minor ) 6 | 7 | local version = minetest.get_version() 8 | local metric_version = monitoring.gauge("minetest_version", "minetest version", { 9 | labels = { 10 | version = version.string, 11 | project = version.project, 12 | hash = version.hash 13 | } 14 | }) 15 | metric_version.set(0) 16 | -------------------------------------------------------------------------------- /chatcommands.lua: -------------------------------------------------------------------------------- 1 | 2 | minetest.register_chatcommand("metric", { 3 | params = "", 4 | description = "shows the current metric value of a gauge or counter", 5 | func = function(_, param) 6 | if param == "" or not param then 7 | return false, "Please specify the metric!" 8 | end 9 | 10 | local metric = monitoring.metrics_mapped[param] 11 | if not metric then 12 | return false, "No such metric: '" .. param .. "'" 13 | end 14 | 15 | return true, "" .. metric.value or "" 16 | end 17 | }) 18 | 19 | -- lag simulation 20 | 21 | local lag = 0 22 | 23 | minetest.register_chatcommand("lag", { 24 | privs = { server = true }, 25 | description = "simulate server lag", 26 | params = "", 27 | func = function(_, param) 28 | lag = tonumber(param or "0") or 0 29 | return true, "lag = " .. lag .. " s" 30 | end 31 | }) 32 | 33 | minetest.register_globalstep(function() 34 | if lag < 0.01 then 35 | -- ignore value 36 | return 37 | end 38 | 39 | local start = minetest.get_us_time() 40 | local stop = start + (lag * 1000 * 1000) 41 | 42 | while minetest.get_us_time() < stop do 43 | -- no-op 44 | end 45 | end) 46 | 47 | minetest.register_chatcommand("forceload", { 48 | privs = { server = true }, 49 | description = "checks or sets the forceload property of the current mapblock", 50 | params = "[]", 51 | func = function(name, param) 52 | local player = minetest.get_player_by_name(name) 53 | if not player then 54 | return false, "Player '" .. name .. "' not found" 55 | end 56 | 57 | local ppos = player:get_pos() 58 | if param == "on" then 59 | -- enable 60 | minetest.forceload_block(ppos, false) 61 | return true, "Enabled forceloading in current mapblock" 62 | elseif param == "off" then 63 | -- disable 64 | minetest.forceload_free_block(ppos, false) 65 | return true, "Disabled forceloading in current mapblock" 66 | else 67 | -- check 68 | local fl = monitoring.is_forceloaded(ppos) 69 | return true, "Current mapblock is " .. (fl and "" or "not ") .. "forceloaded" 70 | end 71 | end 72 | }) -------------------------------------------------------------------------------- /dev/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | services: 4 | minetest: 5 | image: registry.gitlab.com/minetest/minetest/server:5.7.0 6 | ports: 7 | - "30000:30000/udp" 8 | user: root 9 | volumes: 10 | - "./minetest.conf:/etc/minetest/minetest.conf" 11 | - "../:/root/.minetest/worlds/world/worldmods/monitoring" 12 | -------------------------------------------------------------------------------- /dev/minetest.conf: -------------------------------------------------------------------------------- 1 | secure.http_mods = monitoring 2 | monitoring.prometheus_push_url = https://monitoring.minetest.ch/push/metrics/job/minetest/instance/dev-test 3 | default_privs = interact, shout, privs, fast, noclip, fly, server 4 | enable_fire = true -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %.png: %.dot 5 | cat $< | sudo docker run --rm -i vladgolubev/dot2png > $@ 6 | 7 | 8 | 9 | all_dot: standalone-stack.png 10 | -------------------------------------------------------------------------------- /doc/chatcommands.md: -------------------------------------------------------------------------------- 1 | 2 | # Provided chat commands 3 | 4 | Use with care! 5 | 6 | ## /abm_disable 7 | 8 | * Disables all abm's 9 | * **privs:** server 10 | 11 | ## /abm_enable 12 | 13 | * Enables all abm's 14 | * **privs:** server 15 | 16 | ## /nodetimer_disable 17 | 18 | * Disables all nodetimers 19 | * **privs:** server 20 | 21 | ## /nodetimer_enable 22 | 23 | * Enables all nodetimers 24 | * **privs:** server 25 | 26 | ## /lbm_disable 27 | 28 | * Disables all lbm's 29 | * **privs:** server 30 | 31 | ## /lbm_enable 32 | 33 | * Enables all lbm's 34 | * **privs:** server 35 | 36 | ## /globalstep_disable 37 | 38 | * Disables all globalstep in the mod 39 | * **privs:** server 40 | 41 | ## /globalstep_enable 42 | 43 | * Enables all globalsteps in the mod 44 | * **privs:** server 45 | 46 | ## /globalsteps_enable 47 | 48 | * Enables all globalsteps 49 | * **privs:** server 50 | 51 | ## /globalstep_status 52 | 53 | * shows all disabled globalsteps 54 | 55 | ## /globalstep_table_reset 56 | 57 | * Resets the globalstep time tracking table 58 | 59 | ## /globalstep_table_show 60 | 61 | * Shows the globalstep time tracking table 62 | -------------------------------------------------------------------------------- /doc/custom.md: -------------------------------------------------------------------------------- 1 | 2 | ## Usage in mods 3 | 4 | ### Counter 5 | See: https://prometheus.io/docs/concepts/metric_types/#counter 6 | 7 | ```lua 8 | local metric = monitoring.counter("cheat_count", "number of on_cheat") 9 | 10 | function do_stuff_periodically() 11 | metric.inc() 12 | end) 13 | ``` 14 | 15 | ### Gauge 16 | See: https://prometheus.io/docs/concepts/metric_types/#gauge 17 | 18 | ```lua 19 | local metric = monitoring.gauge("player_count", "number of players") 20 | 21 | local timer = 0 22 | minetest.register_globalstep(function(dtime) 23 | timer = timer + dtime 24 | if timer < 5 then return end 25 | timer=0 26 | 27 | metric.set( #minetest.get_connected_players() ) 28 | end) 29 | 30 | ``` 31 | 32 | `monitoring.gauge` optionally accepts a third "options" parameter: 33 | 34 | ``` 35 | local metric = monitoring.gauge("player_count", "number of players", { 36 | autoflush = true -- reset value on every export 37 | }) 38 | ``` 39 | 40 | ### Histogram 41 | See: https://prometheus.io/docs/concepts/metric_types/#histogram 42 | 43 | ```lua 44 | -- top level variable 45 | local export_metric = monitoring.histogram("prom_export_latency", "latency of the export", 46 | {0.001, 0.005, 0.01, 0.02, 0.1}) 47 | 48 | function do_stuff() 49 | local timer = export_metric.timer() 50 | -- do stuff 51 | timer.observe() 52 | end 53 | ``` 54 | 55 | ### Wrapping functions 56 | 57 | Histogram and counter metrics can also wrap existing functions: 58 | ```lua 59 | local mymetric = monitoring.counter("my_count", "my counter") 60 | 61 | function do_global_stuff() 62 | -- more stuff 63 | end 64 | 65 | -- overwrite with wrapped and counted function 66 | do_global_stuff = mymetric.wrap(do_global_stuff) 67 | 68 | ``` 69 | 70 | * the `counter` metric will increment the value on every call 71 | * the `histogram` metric adds the timing on every call 72 | 73 | ### As optional dependency 74 | It is best to depend optionally on the `monitoring` dependency. 75 | For that to work you have to check for its presence when creating metric on top level: 76 | 77 | Optional dependency in `depends.txt`: 78 | ``` 79 | monitoring? 80 | ``` 81 | 82 | Optional metric and setter: 83 | ```lua 84 | local has_monitoring = minetest.get_modpath("monitoring") 85 | local metric 86 | 87 | if has_monitoring then 88 | metric = monitoring.counter("cheat_count", "number of on_cheat") 89 | end 90 | 91 | -- called periodically 92 | function do_stuff_periodically() 93 | if metric ~= nil then 94 | -- only increment if the variable is non-nil 95 | metric.inc() 96 | end 97 | end) 98 | ``` 99 | -------------------------------------------------------------------------------- /doc/dashboard-overview.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "name": "Annotations & Alerts", 11 | "type": "dashboard" 12 | } 13 | ] 14 | }, 15 | "editable": true, 16 | "gnetId": null, 17 | "graphTooltip": 0, 18 | "id": 1, 19 | "iteration": 1583846140269, 20 | "links": [], 21 | "panels": [ 22 | { 23 | "cacheTimeout": null, 24 | "colorBackground": false, 25 | "colorValue": false, 26 | "colors": [ 27 | "#299c46", 28 | "rgba(237, 129, 40, 0.89)", 29 | "#d44a3a" 30 | ], 31 | "datasource": null, 32 | "decimals": 1, 33 | "format": "s", 34 | "gauge": { 35 | "maxValue": 100, 36 | "minValue": 0, 37 | "show": false, 38 | "thresholdLabels": false, 39 | "thresholdMarkers": true 40 | }, 41 | "gridPos": { 42 | "h": 3, 43 | "w": 3, 44 | "x": 0, 45 | "y": 0 46 | }, 47 | "id": 14, 48 | "interval": null, 49 | "links": [], 50 | "mappingType": 1, 51 | "mappingTypes": [ 52 | { 53 | "name": "value to text", 54 | "value": 1 55 | }, 56 | { 57 | "name": "range to text", 58 | "value": 2 59 | } 60 | ], 61 | "maxDataPoints": 100, 62 | "nullPointMode": "connected", 63 | "nullText": null, 64 | "options": {}, 65 | "postfix": "", 66 | "postfixFontSize": "50%", 67 | "prefix": "", 68 | "prefixFontSize": "50%", 69 | "rangeMaps": [ 70 | { 71 | "from": "null", 72 | "text": "N/A", 73 | "to": "null" 74 | } 75 | ], 76 | "sparkline": { 77 | "fillColor": "rgba(31, 118, 189, 0.18)", 78 | "full": false, 79 | "lineColor": "rgb(31, 120, 193)", 80 | "show": false 81 | }, 82 | "tableColumn": "", 83 | "targets": [ 84 | { 85 | "expr": "uptime{instance=\"$instance\"}", 86 | "format": "time_series", 87 | "instant": true, 88 | "intervalFactor": 1, 89 | "refId": "A" 90 | } 91 | ], 92 | "thresholds": "", 93 | "timeFrom": null, 94 | "timeShift": null, 95 | "title": "Uptime", 96 | "type": "singlestat", 97 | "valueFontSize": "80%", 98 | "valueMaps": [ 99 | { 100 | "op": "=", 101 | "text": "N/A", 102 | "value": "null" 103 | } 104 | ], 105 | "valueName": "avg" 106 | }, 107 | { 108 | "cacheTimeout": null, 109 | "colorBackground": false, 110 | "colorValue": true, 111 | "colors": [ 112 | "#299c46", 113 | "rgba(237, 129, 40, 0.89)", 114 | "#d44a3a" 115 | ], 116 | "datasource": "Prometheus", 117 | "format": "s", 118 | "gauge": { 119 | "maxValue": 5, 120 | "minValue": 0, 121 | "show": true, 122 | "thresholdLabels": false, 123 | "thresholdMarkers": true 124 | }, 125 | "gridPos": { 126 | "h": 6, 127 | "w": 3, 128 | "x": 3, 129 | "y": 0 130 | }, 131 | "id": 12, 132 | "interval": null, 133 | "links": [], 134 | "mappingType": 1, 135 | "mappingTypes": [ 136 | { 137 | "name": "value to text", 138 | "value": 1 139 | }, 140 | { 141 | "name": "range to text", 142 | "value": 2 143 | } 144 | ], 145 | "maxDataPoints": 100, 146 | "nullPointMode": "connected", 147 | "nullText": null, 148 | "options": {}, 149 | "postfix": "", 150 | "postfixFontSize": "50%", 151 | "prefix": "", 152 | "prefixFontSize": "50%", 153 | "rangeMaps": [ 154 | { 155 | "from": "null", 156 | "text": "N/A", 157 | "to": "null" 158 | } 159 | ], 160 | "sparkline": { 161 | "fillColor": "rgba(31, 118, 189, 0.18)", 162 | "full": false, 163 | "lineColor": "rgb(31, 120, 193)", 164 | "show": false 165 | }, 166 | "tableColumn": "", 167 | "targets": [ 168 | { 169 | "expr": "max_lag{instance=\"$instance\"}", 170 | "format": "time_series", 171 | "instant": true, 172 | "intervalFactor": 1, 173 | "legendFormat": "Lag", 174 | "refId": "A" 175 | } 176 | ], 177 | "thresholds": "1,2", 178 | "timeFrom": null, 179 | "timeShift": null, 180 | "title": "Current Lag", 181 | "type": "singlestat", 182 | "valueFontSize": "80%", 183 | "valueMaps": [ 184 | { 185 | "op": "=", 186 | "text": "N/A", 187 | "value": "null" 188 | } 189 | ], 190 | "valueName": "avg" 191 | }, 192 | { 193 | "cacheTimeout": null, 194 | "colorBackground": false, 195 | "colorValue": true, 196 | "colors": [ 197 | "#299c46", 198 | "rgba(237, 129, 40, 0.89)", 199 | "#d44a3a" 200 | ], 201 | "datasource": "Prometheus", 202 | "decimals": 0, 203 | "format": "s", 204 | "gauge": { 205 | "maxValue": 5, 206 | "minValue": 0, 207 | "show": true, 208 | "thresholdLabels": false, 209 | "thresholdMarkers": true 210 | }, 211 | "gridPos": { 212 | "h": 6, 213 | "w": 3, 214 | "x": 6, 215 | "y": 0 216 | }, 217 | "id": 25, 218 | "interval": null, 219 | "links": [], 220 | "mappingType": 1, 221 | "mappingTypes": [ 222 | { 223 | "name": "value to text", 224 | "value": 1 225 | }, 226 | { 227 | "name": "range to text", 228 | "value": 2 229 | } 230 | ], 231 | "maxDataPoints": 100, 232 | "nullPointMode": "connected", 233 | "nullText": null, 234 | "options": {}, 235 | "postfix": "", 236 | "postfixFontSize": "50%", 237 | "prefix": "", 238 | "prefixFontSize": "50%", 239 | "rangeMaps": [ 240 | { 241 | "from": "null", 242 | "text": "N/A", 243 | "to": "null" 244 | } 245 | ], 246 | "sparkline": { 247 | "fillColor": "rgba(31, 118, 189, 0.18)", 248 | "full": false, 249 | "lineColor": "rgb(31, 120, 193)", 250 | "show": false 251 | }, 252 | "tableColumn": "", 253 | "targets": [ 254 | { 255 | "expr": "avg_lag{instance=\"$instance\"}", 256 | "format": "time_series", 257 | "instant": true, 258 | "intervalFactor": 1, 259 | "legendFormat": "Lag", 260 | "refId": "A" 261 | } 262 | ], 263 | "thresholds": "1,2", 264 | "timeFrom": null, 265 | "timeShift": null, 266 | "title": "Average Lag", 267 | "type": "singlestat", 268 | "valueFontSize": "80%", 269 | "valueMaps": [ 270 | { 271 | "op": "=", 272 | "text": "N/A", 273 | "value": "null" 274 | } 275 | ], 276 | "valueName": "avg" 277 | }, 278 | { 279 | "cacheTimeout": null, 280 | "colorBackground": false, 281 | "colorValue": false, 282 | "colors": [ 283 | "#299c46", 284 | "rgba(237, 129, 40, 0.89)", 285 | "#d44a3a" 286 | ], 287 | "datasource": "Prometheus", 288 | "format": "none", 289 | "gauge": { 290 | "maxValue": 40, 291 | "minValue": 0, 292 | "show": true, 293 | "thresholdLabels": false, 294 | "thresholdMarkers": true 295 | }, 296 | "gridPos": { 297 | "h": 6, 298 | "w": 3, 299 | "x": 9, 300 | "y": 0 301 | }, 302 | "id": 4, 303 | "interval": null, 304 | "links": [], 305 | "mappingType": 1, 306 | "mappingTypes": [ 307 | { 308 | "name": "value to text", 309 | "value": 1 310 | }, 311 | { 312 | "name": "range to text", 313 | "value": 2 314 | } 315 | ], 316 | "maxDataPoints": 100, 317 | "nullPointMode": "connected", 318 | "nullText": null, 319 | "options": {}, 320 | "postfix": "", 321 | "postfixFontSize": "50%", 322 | "prefix": "", 323 | "prefixFontSize": "50%", 324 | "rangeMaps": [ 325 | { 326 | "from": "null", 327 | "text": "N/A", 328 | "to": "null" 329 | } 330 | ], 331 | "sparkline": { 332 | "fillColor": "rgba(31, 118, 189, 0.18)", 333 | "full": false, 334 | "lineColor": "rgb(31, 120, 193)", 335 | "show": false 336 | }, 337 | "tableColumn": "", 338 | "targets": [ 339 | { 340 | "expr": "player_count{instance=\"$instance\"}", 341 | "format": "time_series", 342 | "instant": true, 343 | "intervalFactor": 1, 344 | "refId": "A" 345 | } 346 | ], 347 | "thresholds": "30,35", 348 | "timeFrom": null, 349 | "timeShift": null, 350 | "title": "Players", 351 | "type": "singlestat", 352 | "valueFontSize": "80%", 353 | "valueMaps": [ 354 | { 355 | "op": "=", 356 | "text": "N/A", 357 | "value": "null" 358 | } 359 | ], 360 | "valueName": "avg" 361 | }, 362 | { 363 | "content": "\n# Public minetest monitoring server\n\n* [Forum topic](https://forum.minetest.net/viewtopic.php?f=14&t=22473)\n* [Github](https://github.com/thomasrudin-mt/monitoring)\n\n\n\n\n", 364 | "datasource": null, 365 | "gridPos": { 366 | "h": 6, 367 | "w": 4, 368 | "x": 12, 369 | "y": 0 370 | }, 371 | "id": 24, 372 | "links": [], 373 | "mode": "markdown", 374 | "options": {}, 375 | "timeFrom": null, 376 | "timeShift": null, 377 | "title": "About", 378 | "type": "text" 379 | }, 380 | { 381 | "cacheTimeout": null, 382 | "colorBackground": false, 383 | "colorValue": false, 384 | "colors": [ 385 | "#299c46", 386 | "rgba(237, 129, 40, 0.89)", 387 | "#d44a3a" 388 | ], 389 | "datasource": "Prometheus", 390 | "decimals": 2, 391 | "format": "none", 392 | "gauge": { 393 | "maxValue": 100, 394 | "minValue": 0, 395 | "show": false, 396 | "thresholdLabels": false, 397 | "thresholdMarkers": true 398 | }, 399 | "gridPos": { 400 | "h": 6, 401 | "w": 4, 402 | "x": 16, 403 | "y": 0 404 | }, 405 | "id": 18, 406 | "interval": null, 407 | "links": [], 408 | "mappingType": 1, 409 | "mappingTypes": [ 410 | { 411 | "name": "value to text", 412 | "value": 1 413 | }, 414 | { 415 | "name": "range to text", 416 | "value": 2 417 | } 418 | ], 419 | "maxDataPoints": 100, 420 | "nullPointMode": "connected", 421 | "nullText": null, 422 | "options": {}, 423 | "postfix": "", 424 | "postfixFontSize": "50%", 425 | "prefix": "Version", 426 | "prefixFontSize": "50%", 427 | "rangeMaps": [ 428 | { 429 | "from": "null", 430 | "text": "N/A", 431 | "to": "null" 432 | } 433 | ], 434 | "sparkline": { 435 | "fillColor": "rgba(31, 118, 189, 0.18)", 436 | "full": false, 437 | "lineColor": "rgb(31, 120, 193)", 438 | "show": false 439 | }, 440 | "tableColumn": "", 441 | "targets": [ 442 | { 443 | "expr": "version_major{instance=\"$instance\"} + (version_minor{instance=\"$instance\"} / 100)", 444 | "format": "time_series", 445 | "instant": true, 446 | "intervalFactor": 1, 447 | "legendFormat": "Version", 448 | "refId": "A" 449 | } 450 | ], 451 | "thresholds": "", 452 | "timeFrom": null, 453 | "timeShift": null, 454 | "title": "Monitoring Mod", 455 | "type": "singlestat", 456 | "valueFontSize": "80%", 457 | "valueMaps": [ 458 | { 459 | "op": "=", 460 | "text": "N/A", 461 | "value": "null" 462 | } 463 | ], 464 | "valueName": "avg" 465 | }, 466 | { 467 | "datasource": null, 468 | "folderId": null, 469 | "gridPos": { 470 | "h": 6, 471 | "w": 4, 472 | "x": 20, 473 | "y": 0 474 | }, 475 | "headings": false, 476 | "id": 28, 477 | "limit": 10, 478 | "links": [], 479 | "options": {}, 480 | "query": "", 481 | "recent": false, 482 | "search": true, 483 | "starred": false, 484 | "tags": [ 485 | "minetest" 486 | ], 487 | "timeFrom": null, 488 | "timeShift": null, 489 | "title": "Dashboards", 490 | "type": "dashlist" 491 | }, 492 | { 493 | "cacheTimeout": null, 494 | "colorBackground": false, 495 | "colorValue": false, 496 | "colors": [ 497 | "#299c46", 498 | "rgba(237, 129, 40, 0.89)", 499 | "#d44a3a" 500 | ], 501 | "datasource": null, 502 | "decimals": 2, 503 | "format": "none", 504 | "gauge": { 505 | "maxValue": 100, 506 | "minValue": 0, 507 | "show": false, 508 | "thresholdLabels": false, 509 | "thresholdMarkers": true 510 | }, 511 | "gridPos": { 512 | "h": 3, 513 | "w": 3, 514 | "x": 0, 515 | "y": 3 516 | }, 517 | "id": 16, 518 | "interval": null, 519 | "links": [], 520 | "mappingType": 1, 521 | "mappingTypes": [ 522 | { 523 | "name": "value to text", 524 | "value": 1 525 | }, 526 | { 527 | "name": "range to text", 528 | "value": 2 529 | } 530 | ], 531 | "maxDataPoints": 100, 532 | "nullPointMode": "connected", 533 | "nullText": null, 534 | "options": {}, 535 | "postfix": "", 536 | "postfixFontSize": "50%", 537 | "prefix": "", 538 | "prefixFontSize": "50%", 539 | "rangeMaps": [ 540 | { 541 | "from": "null", 542 | "text": "N/A", 543 | "to": "null" 544 | } 545 | ], 546 | "sparkline": { 547 | "fillColor": "rgba(31, 118, 189, 0.18)", 548 | "full": false, 549 | "lineColor": "rgb(31, 120, 193)", 550 | "show": false 551 | }, 552 | "tableColumn": "", 553 | "targets": [ 554 | { 555 | "expr": "floor(ingame_time{instance=\"$instance\"}/1000.0) + ((ingame_time{instance=\"$instance\"}/1000.0 % 1) / 100 * 60)", 556 | "format": "time_series", 557 | "instant": true, 558 | "intervalFactor": 1, 559 | "refId": "A" 560 | } 561 | ], 562 | "thresholds": "", 563 | "timeFrom": null, 564 | "timeShift": null, 565 | "title": "Ingame Time", 566 | "type": "singlestat", 567 | "valueFontSize": "80%", 568 | "valueMaps": [ 569 | { 570 | "op": "=", 571 | "text": "N/A", 572 | "value": "null" 573 | } 574 | ], 575 | "valueName": "avg" 576 | }, 577 | { 578 | "aliasColors": {}, 579 | "bars": false, 580 | "dashLength": 10, 581 | "dashes": false, 582 | "datasource": null, 583 | "fill": 1, 584 | "fillGradient": 0, 585 | "gridPos": { 586 | "h": 8, 587 | "w": 12, 588 | "x": 0, 589 | "y": 6 590 | }, 591 | "hiddenSeries": false, 592 | "id": 8, 593 | "legend": { 594 | "avg": false, 595 | "current": false, 596 | "max": false, 597 | "min": false, 598 | "show": true, 599 | "total": false, 600 | "values": false 601 | }, 602 | "lines": true, 603 | "linewidth": 1, 604 | "links": [], 605 | "nullPointMode": "null", 606 | "options": { 607 | "dataLinks": [] 608 | }, 609 | "paceLength": 10, 610 | "percentage": false, 611 | "pointradius": 2, 612 | "points": false, 613 | "renderer": "flot", 614 | "seriesOverrides": [], 615 | "spaceLength": 10, 616 | "stack": false, 617 | "steppedLine": false, 618 | "targets": [ 619 | { 620 | "expr": "player_count{instance=\"$instance\"}", 621 | "format": "time_series", 622 | "intervalFactor": 1, 623 | "legendFormat": "Players", 624 | "refId": "A" 625 | } 626 | ], 627 | "thresholds": [], 628 | "timeFrom": null, 629 | "timeRegions": [], 630 | "timeShift": null, 631 | "title": "Players", 632 | "tooltip": { 633 | "shared": true, 634 | "sort": 0, 635 | "value_type": "individual" 636 | }, 637 | "type": "graph", 638 | "xaxis": { 639 | "buckets": null, 640 | "mode": "time", 641 | "name": null, 642 | "show": true, 643 | "values": [] 644 | }, 645 | "yaxes": [ 646 | { 647 | "format": "short", 648 | "label": null, 649 | "logBase": 1, 650 | "max": null, 651 | "min": "0", 652 | "show": true 653 | }, 654 | { 655 | "format": "short", 656 | "label": null, 657 | "logBase": 1, 658 | "max": null, 659 | "min": null, 660 | "show": true 661 | } 662 | ], 663 | "yaxis": { 664 | "align": false, 665 | "alignLevel": null 666 | } 667 | }, 668 | { 669 | "aliasColors": {}, 670 | "bars": false, 671 | "dashLength": 10, 672 | "dashes": false, 673 | "datasource": null, 674 | "fill": 1, 675 | "fillGradient": 0, 676 | "gridPos": { 677 | "h": 8, 678 | "w": 12, 679 | "x": 12, 680 | "y": 6 681 | }, 682 | "hiddenSeries": false, 683 | "id": 6, 684 | "legend": { 685 | "avg": false, 686 | "current": false, 687 | "max": false, 688 | "min": false, 689 | "show": true, 690 | "total": false, 691 | "values": false 692 | }, 693 | "lines": true, 694 | "linewidth": 1, 695 | "links": [], 696 | "nullPointMode": "null", 697 | "options": { 698 | "dataLinks": [] 699 | }, 700 | "paceLength": 10, 701 | "percentage": false, 702 | "pointradius": 2, 703 | "points": false, 704 | "renderer": "flot", 705 | "seriesOverrides": [ 706 | { 707 | "alias": "DIgged", 708 | "transform": "negative-Y" 709 | } 710 | ], 711 | "spaceLength": 10, 712 | "stack": false, 713 | "steppedLine": false, 714 | "targets": [ 715 | { 716 | "expr": "irate(dig_count{instance=\"$instance\"}[10s])", 717 | "format": "time_series", 718 | "intervalFactor": 1, 719 | "legendFormat": "Digged", 720 | "refId": "A" 721 | }, 722 | { 723 | "expr": "irate(craft_count{instance=\"$instance\"}[10s])", 724 | "format": "time_series", 725 | "intervalFactor": 1, 726 | "legendFormat": "Crafted", 727 | "refId": "B" 728 | }, 729 | { 730 | "expr": "irate(eat_count{instance=\"$instance\"}[10s])", 731 | "format": "time_series", 732 | "intervalFactor": 1, 733 | "legendFormat": "Eaten", 734 | "refId": "C" 735 | }, 736 | { 737 | "expr": "irate(place_count{instance=\"$instance\"}[10s])", 738 | "format": "time_series", 739 | "intervalFactor": 1, 740 | "legendFormat": "Placed", 741 | "refId": "D" 742 | }, 743 | { 744 | "expr": "irate(received_fields{instance=\"$instance\"}[10s])", 745 | "format": "time_series", 746 | "intervalFactor": 1, 747 | "legendFormat": "Fields", 748 | "refId": "E" 749 | } 750 | ], 751 | "thresholds": [], 752 | "timeFrom": null, 753 | "timeRegions": [], 754 | "timeShift": null, 755 | "title": "Craft/Place/Dig/Eat count", 756 | "tooltip": { 757 | "shared": true, 758 | "sort": 0, 759 | "value_type": "individual" 760 | }, 761 | "type": "graph", 762 | "xaxis": { 763 | "buckets": null, 764 | "mode": "time", 765 | "name": null, 766 | "show": true, 767 | "values": [] 768 | }, 769 | "yaxes": [ 770 | { 771 | "format": "short", 772 | "label": null, 773 | "logBase": 1, 774 | "max": null, 775 | "min": null, 776 | "show": true 777 | }, 778 | { 779 | "format": "short", 780 | "label": null, 781 | "logBase": 1, 782 | "max": null, 783 | "min": null, 784 | "show": true 785 | } 786 | ], 787 | "yaxis": { 788 | "align": false, 789 | "alignLevel": null 790 | } 791 | }, 792 | { 793 | "aliasColors": {}, 794 | "bars": false, 795 | "dashLength": 10, 796 | "dashes": false, 797 | "datasource": null, 798 | "fill": 1, 799 | "fillGradient": 0, 800 | "gridPos": { 801 | "h": 9, 802 | "w": 12, 803 | "x": 0, 804 | "y": 14 805 | }, 806 | "hiddenSeries": false, 807 | "id": 2, 808 | "legend": { 809 | "avg": false, 810 | "current": false, 811 | "max": false, 812 | "min": false, 813 | "show": true, 814 | "total": false, 815 | "values": false 816 | }, 817 | "lines": true, 818 | "linewidth": 1, 819 | "links": [], 820 | "nullPointMode": "null", 821 | "options": { 822 | "dataLinks": [] 823 | }, 824 | "paceLength": 10, 825 | "percentage": false, 826 | "pointradius": 2, 827 | "points": false, 828 | "renderer": "flot", 829 | "seriesOverrides": [ 830 | { 831 | "alias": "Mapgen", 832 | "yaxis": 2 833 | } 834 | ], 835 | "spaceLength": 10, 836 | "stack": false, 837 | "steppedLine": true, 838 | "targets": [ 839 | { 840 | "expr": "avg_lag{instance=\"$instance\"}", 841 | "format": "time_series", 842 | "intervalFactor": 1, 843 | "legendFormat": "Average", 844 | "refId": "A" 845 | }, 846 | { 847 | "expr": "min_lag{instance=\"$instance\"}", 848 | "format": "time_series", 849 | "intervalFactor": 1, 850 | "legendFormat": "Min", 851 | "refId": "B" 852 | }, 853 | { 854 | "expr": "max_over_time( max_lag{instance=\"$instance\"} [$__interval] )", 855 | "format": "time_series", 856 | "intervalFactor": 1, 857 | "legendFormat": "Max", 858 | "refId": "C" 859 | }, 860 | { 861 | "expr": "irate(mapgen_generated_count{instance=\"$instance\"}[10s])", 862 | "format": "time_series", 863 | "intervalFactor": 1, 864 | "legendFormat": "Mapgen", 865 | "refId": "D" 866 | } 867 | ], 868 | "thresholds": [ 869 | { 870 | "colorMode": "warning", 871 | "fill": true, 872 | "line": true, 873 | "op": "gt", 874 | "value": 1, 875 | "yaxis": "left" 876 | }, 877 | { 878 | "colorMode": "critical", 879 | "fill": true, 880 | "line": true, 881 | "op": "gt", 882 | "value": 2, 883 | "yaxis": "left" 884 | } 885 | ], 886 | "timeFrom": null, 887 | "timeRegions": [], 888 | "timeShift": null, 889 | "title": "Lag", 890 | "tooltip": { 891 | "shared": true, 892 | "sort": 0, 893 | "value_type": "individual" 894 | }, 895 | "type": "graph", 896 | "xaxis": { 897 | "buckets": null, 898 | "mode": "time", 899 | "name": null, 900 | "show": true, 901 | "values": [] 902 | }, 903 | "yaxes": [ 904 | { 905 | "format": "s", 906 | "label": null, 907 | "logBase": 1, 908 | "max": "3", 909 | "min": "0", 910 | "show": true 911 | }, 912 | { 913 | "decimals": 0, 914 | "format": "short", 915 | "label": null, 916 | "logBase": 1, 917 | "max": null, 918 | "min": "0", 919 | "show": true 920 | } 921 | ], 922 | "yaxis": { 923 | "align": false, 924 | "alignLevel": null 925 | } 926 | }, 927 | { 928 | "aliasColors": {}, 929 | "bars": false, 930 | "dashLength": 10, 931 | "dashes": false, 932 | "datasource": null, 933 | "fill": 1, 934 | "fillGradient": 0, 935 | "gridPos": { 936 | "h": 9, 937 | "w": 12, 938 | "x": 12, 939 | "y": 14 940 | }, 941 | "hiddenSeries": false, 942 | "id": 10, 943 | "legend": { 944 | "avg": false, 945 | "current": false, 946 | "max": false, 947 | "min": false, 948 | "show": true, 949 | "total": false, 950 | "values": false 951 | }, 952 | "lines": true, 953 | "linewidth": 1, 954 | "links": [], 955 | "nullPointMode": "null", 956 | "options": { 957 | "dataLinks": [] 958 | }, 959 | "paceLength": 10, 960 | "percentage": false, 961 | "pointradius": 2, 962 | "points": false, 963 | "renderer": "flot", 964 | "seriesOverrides": [], 965 | "spaceLength": 10, 966 | "stack": true, 967 | "steppedLine": false, 968 | "targets": [ 969 | { 970 | "expr": "max_over_time( ( irate( abm_time{instance=\"$instance\"}[15s] ) )[$__interval:3s] )", 971 | "format": "time_series", 972 | "intervalFactor": 1, 973 | "legendFormat": "ABM", 974 | "refId": "A" 975 | }, 976 | { 977 | "expr": "max_over_time( ( irate( lbm_time{instance=\"$instance\"}[15s] ) )[$__interval:3s] )", 978 | "format": "time_series", 979 | "intervalFactor": 1, 980 | "legendFormat": "LBM", 981 | "refId": "B" 982 | }, 983 | { 984 | "expr": "max_over_time( ( irate( nodetimer_time{instance=\"$instance\"}[15s] ) )[$__interval:3s] )", 985 | "format": "time_series", 986 | "intervalFactor": 1, 987 | "legendFormat": "Nodetimer", 988 | "refId": "C" 989 | }, 990 | { 991 | "expr": "max_over_time( ( irate( globalstep_time{instance=\"$instance\"}[15s] ) )[$__interval:3s] )", 992 | "format": "time_series", 993 | "intervalFactor": 1, 994 | "legendFormat": "Globalstep", 995 | "refId": "D" 996 | } 997 | ], 998 | "thresholds": [], 999 | "timeFrom": null, 1000 | "timeRegions": [], 1001 | "timeShift": null, 1002 | "title": "ABM/LBM/Nodetimer Stats", 1003 | "tooltip": { 1004 | "shared": true, 1005 | "sort": 0, 1006 | "value_type": "individual" 1007 | }, 1008 | "type": "graph", 1009 | "xaxis": { 1010 | "buckets": null, 1011 | "mode": "time", 1012 | "name": null, 1013 | "show": true, 1014 | "values": [] 1015 | }, 1016 | "yaxes": [ 1017 | { 1018 | "format": "µs", 1019 | "label": null, 1020 | "logBase": 1, 1021 | "max": null, 1022 | "min": "0", 1023 | "show": true 1024 | }, 1025 | { 1026 | "format": "short", 1027 | "label": null, 1028 | "logBase": 1, 1029 | "max": null, 1030 | "min": null, 1031 | "show": true 1032 | } 1033 | ], 1034 | "yaxis": { 1035 | "align": false, 1036 | "alignLevel": null 1037 | } 1038 | }, 1039 | { 1040 | "aliasColors": {}, 1041 | "bars": false, 1042 | "dashLength": 10, 1043 | "dashes": false, 1044 | "datasource": null, 1045 | "fill": 1, 1046 | "fillGradient": 0, 1047 | "gridPos": { 1048 | "h": 8, 1049 | "w": 12, 1050 | "x": 0, 1051 | "y": 23 1052 | }, 1053 | "hiddenSeries": false, 1054 | "id": 26, 1055 | "legend": { 1056 | "avg": false, 1057 | "current": false, 1058 | "max": false, 1059 | "min": false, 1060 | "show": true, 1061 | "total": false, 1062 | "values": false 1063 | }, 1064 | "lines": true, 1065 | "linewidth": 1, 1066 | "links": [], 1067 | "nullPointMode": "null", 1068 | "options": { 1069 | "dataLinks": [] 1070 | }, 1071 | "paceLength": 10, 1072 | "percentage": false, 1073 | "pointradius": 2, 1074 | "points": false, 1075 | "renderer": "flot", 1076 | "seriesOverrides": [], 1077 | "spaceLength": 10, 1078 | "stack": false, 1079 | "steppedLine": false, 1080 | "targets": [ 1081 | { 1082 | "expr": "forceload_blocks_count{instance=\"$instance\"}", 1083 | "format": "time_series", 1084 | "intervalFactor": 1, 1085 | "legendFormat": "Blocks", 1086 | "refId": "A" 1087 | } 1088 | ], 1089 | "thresholds": [], 1090 | "timeFrom": null, 1091 | "timeRegions": [], 1092 | "timeShift": null, 1093 | "title": "Forceloaded blocks", 1094 | "tooltip": { 1095 | "shared": true, 1096 | "sort": 0, 1097 | "value_type": "individual" 1098 | }, 1099 | "type": "graph", 1100 | "xaxis": { 1101 | "buckets": null, 1102 | "mode": "time", 1103 | "name": null, 1104 | "show": true, 1105 | "values": [] 1106 | }, 1107 | "yaxes": [ 1108 | { 1109 | "format": "short", 1110 | "label": null, 1111 | "logBase": 1, 1112 | "max": null, 1113 | "min": "0", 1114 | "show": true 1115 | }, 1116 | { 1117 | "format": "short", 1118 | "label": null, 1119 | "logBase": 1, 1120 | "max": null, 1121 | "min": null, 1122 | "show": true 1123 | } 1124 | ], 1125 | "yaxis": { 1126 | "align": false, 1127 | "alignLevel": null 1128 | } 1129 | }, 1130 | { 1131 | "aliasColors": {}, 1132 | "bars": false, 1133 | "dashLength": 10, 1134 | "dashes": false, 1135 | "datasource": null, 1136 | "fill": 1, 1137 | "fillGradient": 0, 1138 | "gridPos": { 1139 | "h": 8, 1140 | "w": 12, 1141 | "x": 12, 1142 | "y": 23 1143 | }, 1144 | "hiddenSeries": false, 1145 | "id": 22, 1146 | "legend": { 1147 | "avg": false, 1148 | "current": false, 1149 | "max": false, 1150 | "min": false, 1151 | "show": true, 1152 | "total": false, 1153 | "values": false 1154 | }, 1155 | "lines": true, 1156 | "linewidth": 1, 1157 | "links": [], 1158 | "nullPointMode": "null", 1159 | "options": { 1160 | "dataLinks": [] 1161 | }, 1162 | "paceLength": 10, 1163 | "percentage": false, 1164 | "pointradius": 2, 1165 | "points": false, 1166 | "renderer": "flot", 1167 | "seriesOverrides": [], 1168 | "spaceLength": 10, 1169 | "stack": false, 1170 | "steppedLine": false, 1171 | "targets": [ 1172 | { 1173 | "expr": "lua_mem_kb{instance=\"$instance\"}", 1174 | "format": "time_series", 1175 | "intervalFactor": 1, 1176 | "legendFormat": "Used Memory", 1177 | "refId": "A" 1178 | }, 1179 | { 1180 | "expr": "lua_mem_max_kb{instance=\"$instance\"}", 1181 | "format": "time_series", 1182 | "intervalFactor": 1, 1183 | "legendFormat": "Max Memory", 1184 | "refId": "B" 1185 | }, 1186 | { 1187 | "expr": "lua_mem_min_kb{instance=\"$instance\"}", 1188 | "format": "time_series", 1189 | "intervalFactor": 1, 1190 | "legendFormat": "Min Memory", 1191 | "refId": "C" 1192 | } 1193 | ], 1194 | "thresholds": [], 1195 | "timeFrom": null, 1196 | "timeRegions": [], 1197 | "timeShift": null, 1198 | "title": "Lua VM", 1199 | "tooltip": { 1200 | "shared": true, 1201 | "sort": 0, 1202 | "value_type": "individual" 1203 | }, 1204 | "type": "graph", 1205 | "xaxis": { 1206 | "buckets": null, 1207 | "mode": "time", 1208 | "name": null, 1209 | "show": true, 1210 | "values": [] 1211 | }, 1212 | "yaxes": [ 1213 | { 1214 | "format": "deckbytes", 1215 | "label": null, 1216 | "logBase": 1, 1217 | "max": null, 1218 | "min": null, 1219 | "show": true 1220 | }, 1221 | { 1222 | "format": "short", 1223 | "label": null, 1224 | "logBase": 1, 1225 | "max": null, 1226 | "min": null, 1227 | "show": true 1228 | } 1229 | ], 1230 | "yaxis": { 1231 | "align": false, 1232 | "alignLevel": null 1233 | } 1234 | } 1235 | ], 1236 | "refresh": "5s", 1237 | "schemaVersion": 22, 1238 | "style": "dark", 1239 | "tags": [ 1240 | "base", 1241 | "minetest" 1242 | ], 1243 | "templating": { 1244 | "list": [ 1245 | { 1246 | "allValue": null, 1247 | "current": { 1248 | "text": "pandorabox.io", 1249 | "value": "pandorabox.io" 1250 | }, 1251 | "datasource": "Prometheus", 1252 | "definition": "label_values(avg_lag, instance)", 1253 | "hide": 0, 1254 | "includeAll": false, 1255 | "label": "Instance:", 1256 | "multi": false, 1257 | "name": "instance", 1258 | "options": [], 1259 | "query": "label_values(avg_lag, instance)", 1260 | "refresh": 1, 1261 | "regex": "", 1262 | "skipUrlSync": false, 1263 | "sort": 0, 1264 | "tagValuesQuery": "", 1265 | "tags": [], 1266 | "tagsQuery": "", 1267 | "type": "query", 1268 | "useTags": false 1269 | } 1270 | ] 1271 | }, 1272 | "time": { 1273 | "from": "now-1h", 1274 | "to": "now" 1275 | }, 1276 | "timepicker": { 1277 | "refresh_intervals": [ 1278 | "5s", 1279 | "10s", 1280 | "30s", 1281 | "1m", 1282 | "5m", 1283 | "15m", 1284 | "30m", 1285 | "1h", 1286 | "2h", 1287 | "1d" 1288 | ], 1289 | "time_options": [ 1290 | "5m", 1291 | "15m", 1292 | "1h", 1293 | "6h", 1294 | "12h", 1295 | "24h", 1296 | "2d", 1297 | "7d", 1298 | "30d" 1299 | ] 1300 | }, 1301 | "timezone": "", 1302 | "title": "Overview", 1303 | "uid": "YUpouLmWk", 1304 | "version": 62 1305 | } 1306 | -------------------------------------------------------------------------------- /doc/docker.md: -------------------------------------------------------------------------------- 1 | 2 | ## Docker-compose setup for prometheus/grafana 3 | 4 | ### docker-compose.yml 5 | 6 | ```yml 7 | version: "2" 8 | 9 | services: 10 | pushgateway: 11 | image: prom/pushgateway:v1.2.0 12 | restart: always 13 | 14 | prometheus: 15 | image: prom/prometheus:v2.18.0 16 | restart: always 17 | depends_on: 18 | - "pushgateway" 19 | volumes: 20 | - "./data/prometheus-data:/prometheus" 21 | - "./prometheus.yml:/etc/prometheus/prometheus.yml" 22 | command: 23 | - '--config.file=/etc/prometheus/prometheus.yml' 24 | - '--storage.tsdb.path=/prometheus' 25 | - '--storage.tsdb.retention.time=72h' 26 | 27 | grafana: 28 | image: grafana/grafana:7.0.0 29 | restart: always 30 | environment: 31 | GF_SECURITY_ADMIN_PASSWORD: enter 32 | depends_on: 33 | - prometheus 34 | volumes: 35 | - "./data/grafana-data:/var/lib/grafana" 36 | - "./grafana.ini:/etc/grafana/grafana.ini" 37 | 38 | nginx: 39 | image: nginx:1.18.0 40 | restart: always 41 | networks: 42 | - terminator 43 | - default 44 | restart: always 45 | volumes: 46 | - "./data/nginx/nginx.conf:/etc/nginx/nginx.conf:ro" 47 | - "./data/nginx/routes:/routes" 48 | environment: 49 | VIRTUAL_PORT: 80 50 | VIRTUAL_HOST: monitoring.minetest.ch 51 | LETSENCRYPT_EMAIL: thomas@rudin.io 52 | LETSENCRYPT_HOST: monitoring.minetest.ch 53 | depends_on: 54 | - grafana 55 | - pushgateway 56 | logging: 57 | options: 58 | max-size: 50m 59 | 60 | networks: 61 | terminator: 62 | external: true 63 | 64 | 65 | ``` 66 | 67 | ### prometheus.yml 68 | 69 | ```yml 70 | global: 71 | scrape_interval: 5s 72 | external_labels: 73 | monitor: 'my-monitor' 74 | scrape_configs: 75 | - job_name: 'minetest' 76 | honor_labels: true 77 | static_configs: 78 | - targets: ['pushgateway:9091'] 79 | ``` 80 | -------------------------------------------------------------------------------- /doc/exporters.md: -------------------------------------------------------------------------------- 1 | ## Exporters 2 | 3 | Currently only the exporter for the prometheus push gateway is implemented 4 | 5 | 6 | ## References 7 | * Export format: https://github.com/prometheus/docs/blob/master/content/docs/instrumenting/exposition_formats.md 8 | -------------------------------------------------------------------------------- /doc/gnu.plot: -------------------------------------------------------------------------------- 1 | set grid 2 | set datafile separator "," 3 | set term png 4 | set output 'data.png' 5 | set timefmt '%Y-%m-%d_%H:%M:%S' 6 | set xdata time 7 | plot 'data.csv' using 0:1 w lines 8 | -------------------------------------------------------------------------------- /doc/hosted.md: -------------------------------------------------------------------------------- 1 | ## Hosted on monitoring.minetest.ch 2 | 3 | The central minetest monitoring server can be used to display the metrics. 4 | Install the mod and add the following settings to your `minetest.conf`: 5 | 6 | ``` 7 | secure.http_mods = monitoring 8 | monitoring.prometheus_push_url = https://monitoring.minetest.ch/push/metrics/job/minetest/instance/my-server-name 9 | ``` 10 | 11 | The last part of the push_url is the name of the instance. 12 | If the server name is `my-awesome-server` then the url should look like this: 13 | 14 | ``` 15 | monitoring.prometheus_push_url = https://monitoring.minetest.ch/push/metrics/job/minetest/instance/my-awesome-server 16 | ``` 17 | 18 | The stats can now be seen at [monitoring.minetest.ch](https://monitoring.minetest.ch/d/YUpouLmWk/overview?tab=visualization&orgId=1&refresh=5s&var-instance=creative1) 19 | -------------------------------------------------------------------------------- /doc/install.md: -------------------------------------------------------------------------------- 1 | ## Install 2 | 3 | Either install the grafana/prometheus stack yourself (native or via [docker](./docker.md)) 4 | or use the [hosted](./hosted.md) central server for monitoring 5 | 6 | 7 | An example `minetest.conf` with the pushgateway at port 9091: 8 | 9 | ``` 10 | secure.http_mods = monitoring 11 | monitoring.prometheus_push_url = http://127.0.0.1:9091/metrics/job/minetest/instance/my_server 12 | ``` 13 | 14 | ## Verbose monitoring 15 | 16 | Enables more metric at a slightly increased cpu-usage: 17 | ``` 18 | monitoring.verbose = true 19 | ``` 20 | -------------------------------------------------------------------------------- /doc/standalone-stack.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | monitoring_mod -> pushgateway [label=POST]; 3 | prometheus -> pushgateway [label=GET]; 4 | grafana -> prometheus [label=GET]; 5 | Browser -> grafana [labe=GET] 6 | } 7 | -------------------------------------------------------------------------------- /doc/standalone-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-monitoring/monitoring/a1b1e41593ce01a49ce390683894e4e7fe1a294b/doc/standalone-stack.png -------------------------------------------------------------------------------- /doc/standalone.md: -------------------------------------------------------------------------------- 1 | 2 | # Standalone monitoring server setup 3 | 4 | Quick instructions to get the monitoring stack running locally (without docker). 5 | For a (proper) docker setup see the `docker-compose.yml` file at https://github.com/minetest-monitoring/monitoring.minetest.ch 6 | 7 | The monitoring stack: 8 | 9 | 10 | Responsibilities: 11 | * **monitoring_mod** gathers the data from within minetest and POST's them to the pushgateway 12 | * **Pushgateway** is the "switchboard" application where the minetest-mod pushes and prometheus pulls the data 13 | * **Prometheus** scrapes (pulls) the data from the pushgateway and persists them on-disk for later querying 14 | * **Grafana** Retrieves the data from prometheus and visualizes it 15 | 16 | ## Prometheus 17 | 18 | * Download **prometheus** from: https://prometheus.io/download/ 19 | * Extract the archive 20 | 21 | Replace the `prometheus.yml` file with the following contents: 22 | ```yml 23 | global: 24 | scrape_interval: 5s 25 | evaluation_interval: 15s 26 | 27 | scrape_configs: 28 | - job_name: 'minetest' 29 | static_configs: 30 | - targets: ['localhost:9091'] 31 | ``` 32 | 33 | Start prometheus: 34 | ```bash 35 | ./prometheus 36 | ``` 37 | 38 | 39 | ## Pushgateway 40 | 41 | * Download **pushgateway** from https://prometheus.io/download/ 42 | * Extract the archive 43 | 44 | Start the pushgateway: 45 | ```bash 46 | ./pushgateway 47 | ``` 48 | 49 | ## Grafana 50 | 51 | * Download **grafana** from https://grafana.com/grafana/download 52 | * Extract the archive 53 | 54 | Start grafana: 55 | ```bash 56 | ./bin/grafana-server 57 | ``` 58 | 59 | * Point your browser to http://127.0.0.1:3000/login 60 | * Use `admin` as username and `admin` as initial password to login 61 | * Create a new datasource with URL: http://127.0.0.1:9090 62 | 63 | ### Import dashboards 64 | 65 | Export a dashboard from https://monitoring.minetest.ch or download the [overview](./dashboard-overview.json) dashboard. 66 | 67 | * Click the "+" Sign on the left 68 | * Choose "Import" 69 | * Paste the above json file and import the dashboard 70 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | services: 4 | pushgateway: 5 | image: prom/pushgateway 6 | ports: 7 | - "9091:9091" 8 | 9 | prometheus: 10 | image: prom/prometheus 11 | depends_on: 12 | - "pushgateway" 13 | volumes: 14 | - "prometheus-data:/prometheus" 15 | - "./prometheus.yml:/etc/prometheus/prometheus.yml" 16 | 17 | grafana: 18 | image: grafana/grafana 19 | ports: 20 | - "3000:3000" 21 | environment: 22 | - GF_SECURITY_ADMIN_PASSWORD=enter 23 | depends_on: 24 | - prometheus 25 | volumes: 26 | - "grafana-data:/var/lib/grafana" 27 | - "./grafana.ini:/etc/grafana/grafana.ini" 28 | 29 | volumes: 30 | grafana-data: 31 | prometheus-data: 32 | -------------------------------------------------------------------------------- /docker/grafana.ini: -------------------------------------------------------------------------------- 1 | ##################### Grafana Configuration Example ##################### 2 | # 3 | # Everything has defaults so you only need to uncomment things you want to 4 | # change 5 | 6 | # possible values : production, development 7 | ; app_mode = production 8 | 9 | #################################### Paths #################################### 10 | [paths] 11 | # Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used) 12 | # 13 | ;data = /var/lib/grafana 14 | # 15 | # Directory where grafana can store logs 16 | # 17 | ;logs = /var/log/grafana 18 | # 19 | # Directory where grafana will automatically scan and look for plugins 20 | # 21 | ;plugins = /var/lib/grafana/plugins 22 | 23 | # 24 | #################################### Server #################################### 25 | [server] 26 | # Protocol (http or https) 27 | ;protocol = http 28 | 29 | # The ip address to bind to, empty will bind to all interfaces 30 | ;http_addr = 31 | 32 | # The http port to use 33 | ;http_port = 3000 34 | 35 | # The public facing domain name used to access grafana from a browser 36 | domain = pandorabox.io 37 | 38 | # Redirect to correct domain if host header does not match domain 39 | # Prevents DNS rebinding attacks 40 | ;enforce_domain = false 41 | 42 | # The full public facing url 43 | ;root_url = %(protocol)s://%(domain)s:%(http_port)s/ 44 | root_url = %(protocol)s://%(domain)s/ 45 | 46 | # Log web requests 47 | ;router_logging = false 48 | 49 | # the path relative working path 50 | ;static_root_path = public 51 | 52 | # enable gzip 53 | ;enable_gzip = false 54 | 55 | # https certs & key file 56 | ;cert_file = 57 | ;cert_key = 58 | 59 | #################################### Database #################################### 60 | [database] 61 | # Either "mysql", "postgres" or "sqlite3", it's your choice 62 | ;type = sqlite3 63 | ;host = 127.0.0.1:3306 64 | ;name = grafana 65 | ;user = root 66 | ;password = 67 | 68 | # For "postgres" only, either "disable", "require" or "verify-full" 69 | ;ssl_mode = disable 70 | 71 | # For "sqlite3" only, path relative to data_path setting 72 | ;path = grafana.db 73 | 74 | #################################### Session #################################### 75 | [session] 76 | # Either "memory", "file", "redis", "mysql", "postgres", default is "file" 77 | ;provider = file 78 | 79 | # Provider config options 80 | # memory: not have any config yet 81 | # file: session dir path, is relative to grafana data_path 82 | # redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=grafana` 83 | # mysql: go-sql-driver/mysql dsn config string, e.g. `user:password@tcp(127.0.0.1:3306)/database_name` 84 | # postgres: user=a password=b host=localhost port=5432 dbname=c sslmode=disable 85 | ;provider_config = sessions 86 | 87 | # Session cookie name 88 | ;cookie_name = grafana_sess 89 | 90 | # If you use session in https only, default is false 91 | ;cookie_secure = false 92 | 93 | # Session life time, default is 86400 94 | ;session_life_time = 86400 95 | 96 | #################################### Analytics #################################### 97 | [analytics] 98 | # Server reporting, sends usage counters to stats.grafana.org every 24 hours. 99 | # No ip addresses are being tracked, only simple counters to track 100 | # running instances, dashboard and error counts. It is very helpful to us. 101 | # Change this option to false to disable reporting. 102 | ;reporting_enabled = true 103 | 104 | # Set to false to disable all checks to https://grafana.net 105 | # for new vesions (grafana itself and plugins), check is used 106 | # in some UI views to notify that grafana or plugin update exists 107 | # This option does not cause any auto updates, nor send any information 108 | # only a GET request to http://grafana.net to get latest versions 109 | check_for_updates = true 110 | 111 | # Google Analytics universal tracking code, only enabled if you specify an id here 112 | ;google_analytics_ua_id = 113 | 114 | #################################### Security #################################### 115 | [security] 116 | # default admin user, created on startup 117 | ;admin_user = admin 118 | 119 | # default admin password, can be changed before first start of grafana, or in profile settings 120 | ;admin_password = admin 121 | 122 | # used for signing 123 | ;secret_key = SW2YcwTIb9zpOOhoPsMm 124 | 125 | # Auto-login remember days 126 | ;login_remember_days = 7 127 | ;cookie_username = grafana_user 128 | ;cookie_remember_name = grafana_remember 129 | 130 | # disable gravatar profile images 131 | ;disable_gravatar = false 132 | 133 | # data source proxy whitelist (ip_or_domain:port seperated by spaces) 134 | ;data_source_proxy_whitelist = 135 | 136 | [snapshots] 137 | # snapshot sharing options 138 | ;external_enabled = true 139 | ;external_snapshot_url = https://snapshots-origin.raintank.io 140 | ;external_snapshot_name = Publish to snapshot.raintank.io 141 | 142 | #################################### Users #################################### 143 | [users] 144 | # disable user signup / registration 145 | ;allow_sign_up = true 146 | 147 | # Allow non admin users to create organizations 148 | ;allow_org_create = true 149 | 150 | # Set to true to automatically assign new users to the default organization (id 1) 151 | ;auto_assign_org = true 152 | 153 | # Default role new users will be automatically assigned (if disabled above is set to true) 154 | ;auto_assign_org_role = Viewer 155 | 156 | # Background text for the user field on the login page 157 | ;login_hint = email or username 158 | 159 | #################################### Anonymous Auth ########################## 160 | [auth.anonymous] 161 | # enable anonymous access 162 | enabled = true 163 | 164 | # specify organization name that should be used for unauthenticated users 165 | org_name = Public 166 | 167 | # specify role for unauthenticated users 168 | ;org_role = Viewer 169 | 170 | #################################### Github Auth ########################## 171 | [auth.github] 172 | ;enabled = false 173 | ;allow_sign_up = false 174 | ;client_id = some_id 175 | ;client_secret = some_secret 176 | ;scopes = user:email,read:org 177 | ;auth_url = https://github.com/login/oauth/authorize 178 | ;token_url = https://github.com/login/oauth/access_token 179 | ;api_url = https://api.github.com/user 180 | ;team_ids = 181 | ;allowed_organizations = 182 | 183 | #################################### Google Auth ########################## 184 | [auth.google] 185 | ;enabled = false 186 | ;allow_sign_up = false 187 | ;client_id = some_client_id 188 | ;client_secret = some_client_secret 189 | ;scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email 190 | ;auth_url = https://accounts.google.com/o/oauth2/auth 191 | ;token_url = https://accounts.google.com/o/oauth2/token 192 | ;api_url = https://www.googleapis.com/oauth2/v1/userinfo 193 | ;allowed_domains = 194 | 195 | #################################### Auth Proxy ########################## 196 | [auth.proxy] 197 | ;enabled = false 198 | ;header_name = X-WEBAUTH-USER 199 | ;header_property = username 200 | ;auto_sign_up = true 201 | 202 | #################################### Basic Auth ########################## 203 | [auth.basic] 204 | ;enabled = true 205 | 206 | #################################### Auth LDAP ########################## 207 | [auth.ldap] 208 | ;enabled = false 209 | ;config_file = /etc/grafana/ldap.toml 210 | 211 | #################################### SMTP / Emailing ########################## 212 | [smtp] 213 | ;enabled = false 214 | ;host = localhost:25 215 | ;user = 216 | ;password = 217 | ;cert_file = 218 | ;key_file = 219 | ;skip_verify = false 220 | ;from_address = admin@grafana.localhost 221 | 222 | [emails] 223 | ;welcome_email_on_sign_up = false 224 | 225 | #################################### Logging ########################## 226 | [log] 227 | # Either "console", "file", default is "console" 228 | # Use comma to separate multiple modes, e.g. "console, file" 229 | ;mode = console, file 230 | 231 | # Buffer length of channel, keep it as it is if you don't know what it is. 232 | ;buffer_len = 10000 233 | 234 | # Either "Trace", "Debug", "Info", "Warn", "Error", "Critical", default is "Trace" 235 | ;level = Info 236 | 237 | # For "console" mode only 238 | [log.console] 239 | ;level = 240 | 241 | # For "file" mode only 242 | [log.file] 243 | ;level = 244 | # This enables automated log rotate(switch of following options), default is true 245 | ;log_rotate = true 246 | 247 | # Max line number of single file, default is 1000000 248 | ;max_lines = 1000000 249 | 250 | # Max size shift of single file, default is 28 means 1 << 28, 256MB 251 | ;max_lines_shift = 28 252 | 253 | # Segment log daily, default is true 254 | ;daily_rotate = true 255 | 256 | # Expired days of log file(delete after max days), default is 7 257 | ;max_days = 7 258 | 259 | #################################### AMPQ Event Publisher ########################## 260 | [event_publisher] 261 | ;enabled = false 262 | ;rabbitmq_url = amqp://localhost/ 263 | ;exchange = grafana_events 264 | 265 | ;#################################### Dashboard JSON files ########################## 266 | [dashboards.json] 267 | ;enabled = false 268 | ;path = /var/lib/grafana/dashboards 269 | 270 | 271 | 272 | -------------------------------------------------------------------------------- /docker/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 5s 3 | external_labels: 4 | monitor: 'my-monitor' 5 | scrape_configs: 6 | - job_name: 'minetest' 7 | honor_labels: true 8 | static_configs: 9 | - targets: ['pushgateway:9091'] 10 | -------------------------------------------------------------------------------- /export/csv.lua: -------------------------------------------------------------------------------- 1 | 2 | local export_csv = function() 3 | -- local data = "" 4 | 5 | for _ in ipairs(monitoring.metrics) do 6 | -- TODO 7 | end 8 | end 9 | 10 | 11 | monitoring.csv_init = function() 12 | local timer = 0 13 | minetest.register_globalstep(function(dtime) 14 | timer = timer + dtime 15 | if timer < 5 then return end 16 | timer=0 17 | 18 | export_csv() 19 | end) 20 | end 21 | -------------------------------------------------------------------------------- /export/json.lua: -------------------------------------------------------------------------------- 1 | 2 | local export_json = function() 3 | local json_data = minetest.write_json(monitoring.metrics, true) 4 | local path = minetest.get_worldpath().."/monitoring.json"; 5 | 6 | local file = io.open( path, "w" ); 7 | if(file) then 8 | file:write(json_data); 9 | file:close(); 10 | else 11 | print("[monitoring] Error: Savefile could not be written"); 12 | end 13 | 14 | end 15 | 16 | 17 | monitoring.json_init = function() 18 | local timer = 0 19 | minetest.register_globalstep(function(dtime) 20 | timer = timer + dtime 21 | if timer < 5 then return end 22 | timer=0 23 | 24 | export_json() 25 | end) 26 | end 27 | -------------------------------------------------------------------------------- /export/prometheus_push.lua: -------------------------------------------------------------------------------- 1 | local http = ... 2 | 3 | local get_us_time = minetest.get_us_time 4 | local export_metric_collect_time = monitoring.gauge( 5 | "prom_export_collect_time", 6 | "export collect time" 7 | ) 8 | 9 | local export_metric_post_time = monitoring.gauge( 10 | "prom_export_post_time", 11 | "export post time" 12 | ) 13 | 14 | local export_size = monitoring.counter( 15 | "prom_export_size_count", 16 | "byte count of the prometheus export" 17 | ) 18 | 19 | function monitoring.serialize_prometheus_labels(labels) 20 | local str = "" 21 | local i = 0 22 | for k,v in pairs(labels or {}) do 23 | if v ~= nil then 24 | str = str .. k .. "=\"" .. v .. "\"," 25 | i = i + 1 26 | end 27 | end 28 | 29 | if i > 0 then 30 | return "{" .. string.sub(str, 1, #str-1) .. "}" 31 | else 32 | return "" 33 | end 34 | end 35 | 36 | function monitoring.serialize_prometheus_metric(metric) 37 | local data = "" 38 | if metric.value or metric.value == 0 then 39 | data = data .. "# TYPE " .. metric.name .. " " .. metric.type .. "\n" 40 | data = data .. "# HELP " .. metric.name .. " " .. metric.help .. "\n" 41 | 42 | if metric.value ~= nil then 43 | if metric.type == "gauge" or metric.type == "counter" then 44 | data = data .. metric.name 45 | if metric.options and metric.options.labels then 46 | -- append labels 47 | data = data .. monitoring.serialize_prometheus_labels(metric.options.labels) 48 | end 49 | data = data .. " " .. metric.value .. "\n" 50 | 51 | if metric.options and metric.options.autoflush then 52 | -- reset metric value on export 53 | metric.value = nil 54 | end 55 | end 56 | end 57 | 58 | if metric.type == "histogram" then 59 | for k, bucket in ipairs(metric.buckets) do 60 | data = data .. metric.name .. "_bucket{le=\"" .. bucket .. "\"} " .. metric.bucketvalues[k] .. "\n" 61 | end 62 | data = data .. metric.name .. "_bucket{le=\"+Inf\"} " .. metric.infcount .. "\n" 63 | data = data .. metric.name .. "_sum " .. metric.sum .. "\n" 64 | data = data .. metric.name .. "_count " .. metric.count .. "\n" 65 | end 66 | end 67 | return data 68 | end 69 | 70 | local function push_metrics() 71 | local t0 = minetest.get_us_time() 72 | 73 | local data = "" 74 | for _, metric in ipairs(monitoring.metrics) do 75 | data = data .. monitoring.serialize_prometheus_metric(metric) 76 | end 77 | 78 | local t_collect_us = get_us_time() - t0 79 | export_metric_collect_time.set(t_collect_us / 1000000) 80 | t0 = get_us_time() 81 | 82 | export_size.inc(string.len(data)) 83 | 84 | -- https://www.nginx.com/blog/deploying-nginx-plus-as-an-api-gateway-part-1/ 85 | http.fetch({ 86 | url = monitoring.settings.prom_push_url, 87 | extra_headers = { "Content-Type: text/plain" }, 88 | post_data = data, 89 | timeout = 5 90 | }, function(res) 91 | local t_post_us = get_us_time() - t0 92 | export_metric_post_time.set(t_post_us / 1000000) 93 | if res.code >= 400 then 94 | minetest.log("error", "[monitoring] prom-push returned code " .. res.code .. " data: " .. res.data) 95 | end 96 | end) 97 | end 98 | 99 | 100 | function monitoring.prometheus_push_init() 101 | local timer = 0 102 | minetest.register_globalstep(function(dtime) 103 | timer = timer + dtime 104 | if timer < 5 then return end 105 | timer=0 106 | 107 | push_metrics() 108 | end) 109 | end 110 | -------------------------------------------------------------------------------- /export/prometheus_push.spec.lua: -------------------------------------------------------------------------------- 1 | 2 | mtt.register("serialize_prometheus_metric", function(callback) 3 | local m = monitoring.gauge("my_metric", "help stuff") 4 | assert(monitoring.metrics_mapped["my_metric"] == m) 5 | 6 | local str = monitoring.serialize_prometheus_metric(m) 7 | assert(str == "") 8 | 9 | m.set(1) 10 | str = monitoring.serialize_prometheus_metric(m) 11 | 12 | local lines = {} 13 | for s in str:gmatch("[^\n]+") do 14 | table.insert(lines, s) 15 | end 16 | 17 | assert(#lines == 3) 18 | assert(lines[1] == "# TYPE my_metric gauge") 19 | assert(lines[2] == "# HELP my_metric help stuff") 20 | assert(lines[3] == "my_metric 1") 21 | 22 | m = monitoring.gauge("my_metric", "help stuff", { 23 | labels = { x = "123" } 24 | }) 25 | assert(monitoring.metrics_mapped["my_metric"] == m) 26 | m.set(1) 27 | 28 | str = monitoring.serialize_prometheus_metric(m) 29 | 30 | lines = {} 31 | for s in str:gmatch("[^\n]+") do 32 | table.insert(lines, s) 33 | end 34 | 35 | assert(#lines == 3) 36 | assert(lines[1] == "# TYPE my_metric gauge") 37 | assert(lines[2] == "# HELP my_metric help stuff") 38 | assert(lines[3] == "my_metric{x=\"123\"} 1") 39 | 40 | callback() 41 | end) 42 | 43 | mtt.register("serialize_prometheus_labels", function(callback) 44 | local str = monitoring.serialize_prometheus_labels(nil) 45 | assert(str == "") 46 | 47 | str = monitoring.serialize_prometheus_labels({}) 48 | assert(str == "") 49 | 50 | str = monitoring.serialize_prometheus_labels({x="abc"}) 51 | assert(str == "{x=\"abc\"}") 52 | 53 | str = monitoring.serialize_prometheus_labels({x="abc", y="123"}) 54 | assert(str == "{x=\"abc\",y=\"123\"}" or str == "{y=\"123\",x=\"abc\"}") 55 | 56 | callback() 57 | end) -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | 2 | monitoring = { 3 | version_major = 1, 4 | version_minor = 6, 5 | metrics = {}, -- {name="", help="", type="", ...} 6 | metrics_mapped = {}, -- metrics mapped by name as key 7 | storage = minetest.get_mod_storage(), 8 | settings = { 9 | prom_push_url = minetest.settings:get("monitoring.prometheus_push_url"), 10 | builtin_disable = minetest.settings:get_bool("monitoring.builtin_disable"), 11 | csv_enable = minetest.settings:get_bool("monitoring.csv_enable"), 12 | json_enable = minetest.settings:get_bool("monitoring.json_enable") 13 | } 14 | } 15 | 16 | local http = minetest.get_modpath("qos") and QoS and QoS(minetest.request_http_api(), 2) or minetest.request_http_api() 17 | local MP = minetest.get_modpath("monitoring") 18 | 19 | dofile(MP.."/metrictypes/gauge.lua") 20 | dofile(MP.."/metrictypes/counter.lua") 21 | dofile(MP.."/metrictypes/histogram.lua") 22 | 23 | dofile(MP.."/chatcommands.lua") 24 | dofile(MP.."/register.lua") 25 | dofile(MP.."/sampling.lua") 26 | 27 | loadfile(MP.."/export/prometheus_push.lua")(http) 28 | dofile(MP.."/export/csv.lua") 29 | dofile(MP.."/export/json.lua") 30 | 31 | if not monitoring.settings.builtin_disable then 32 | print("[monitoring] registering builtin metrics") 33 | 34 | dofile(MP.."/builtin/version.lua") 35 | dofile(MP.."/builtin/abm_calls.lua") 36 | dofile(MP.."/builtin/api.lua") 37 | dofile(MP.."/builtin/auth_fail.lua") 38 | dofile(MP.."/builtin/chat_count.lua") 39 | dofile(MP.."/builtin/cheat_count.lua") 40 | dofile(MP.."/builtin/craft_count.lua") 41 | dofile(MP.."/builtin/dig_count.lua") 42 | dofile(MP.."/builtin/eat_count.lua") 43 | dofile(MP.."/builtin/forceload_blocks.lua") 44 | dofile(MP.."/builtin/after.lua") 45 | dofile(MP.."/builtin/generated.lua") 46 | dofile(MP.."/builtin/globalstep.lua") 47 | dofile(MP.."/builtin/jit.lua") 48 | dofile(MP.."/builtin/join_count.lua") 49 | dofile(MP.."/builtin/lag.lua") 50 | dofile(MP.."/builtin/lbm_calls.lua") 51 | dofile(MP.."/builtin/leave_count.lua") 52 | dofile(MP.."/builtin/liquid_transformed.lua") 53 | dofile(MP.."/builtin/luamem.lua") 54 | dofile(MP.."/builtin/log.lua") 55 | dofile(MP.."/builtin/place_count.lua") 56 | dofile(MP.."/builtin/nodetimer_calls.lua") 57 | dofile(MP.."/builtin/on_joinplayer.lua") 58 | dofile(MP.."/builtin/on_prejoinplayer.lua") 59 | dofile(MP.."/builtin/on_step.lua") 60 | dofile(MP.."/builtin/playercount.lua") 61 | dofile(MP.."/builtin/protection_violation_count.lua") 62 | dofile(MP.."/builtin/protection.lua") 63 | dofile(MP.."/builtin/punchplayer.lua") 64 | dofile(MP.."/builtin/received_fields.lua") 65 | dofile(MP.."/builtin/registered_count.lua") 66 | dofile(MP.."/builtin/ticks.lua") 67 | dofile(MP.."/builtin/time.lua") 68 | dofile(MP.."/builtin/uptime.lua") 69 | dofile(MP.."/builtin/settings.lua") 70 | end 71 | 72 | if minetest.get_modpath("technic") then 73 | print("[monitoring] enabling technic integrations") 74 | dofile(MP.."/mods/technic/abm.lua") 75 | dofile(MP.."/mods/technic/quarry.lua") 76 | dofile(MP.."/mods/technic/switch.lua") 77 | dofile(MP.."/mods/technic/technic_run.lua") 78 | end 79 | 80 | if minetest.get_modpath("basic_machines") then 81 | print("[monitoring] enabling basic_machines integrations") 82 | dofile(MP.."/mods/basic_machines/init.lua") 83 | end 84 | 85 | if minetest.get_modpath("digilines") then 86 | print("[monitoring] enabling digilines integrations") 87 | dofile(MP.."/mods/digilines/init.lua") 88 | dofile(MP.."/mods/digilines/metric_controller.lua") 89 | end 90 | 91 | if minetest.get_modpath("mesecons") then 92 | print("[monitoring] enabling mesecons integrations") 93 | dofile(MP.."/mods/mesecons/action_on.lua") 94 | dofile(MP.."/mods/mesecons/functions.lua") 95 | dofile(MP.."/mods/mesecons/globals.lua") 96 | dofile(MP.."/mods/mesecons/luac.lua") 97 | dofile(MP.."/mods/mesecons/queue.lua") 98 | end 99 | 100 | if minetest.get_modpath("pipeworks") then 101 | print("[monitoring] enabling pipeworks integrations") 102 | dofile(MP.."/mods/pipeworks/init.lua") 103 | dofile(MP.."/mods/pipeworks/entity_count.lua") 104 | dofile(MP.."/mods/pipeworks/expiration.lua") 105 | dofile(MP.."/mods/pipeworks/flush.lua") 106 | dofile(MP.."/mods/pipeworks/filter_action_on.lua") 107 | dofile(MP.."/mods/pipeworks/globalsteps.lua") 108 | dofile(MP.."/mods/pipeworks/metrics.lua") 109 | dofile(MP.."/mods/pipeworks/can_go.lua") 110 | dofile(MP.."/mods/pipeworks/teleport_tubes.lua") 111 | dofile(MP.."/mods/pipeworks/tube_inject_item.lua") 112 | dofile(MP.."/mods/pipeworks/inject_limiter.lua") 113 | dofile(MP.."/mods/pipeworks/chatcommands.lua") 114 | end 115 | 116 | if minetest.get_modpath("advtrains") then 117 | print("[monitoring] enabling advtrains integrations") 118 | dofile(MP.."/mods/advtrains/chatcommands.lua") 119 | dofile(MP.."/mods/advtrains/metrics.lua") 120 | end 121 | 122 | if minetest.get_modpath("nc_optics") then 123 | print("[monitoring] enabling nc_optics integrations") 124 | dofile(MP.."/mods/nodecore/nc_optics.lua") 125 | end 126 | 127 | if monitoring.settings.prom_push_url then 128 | if not http then 129 | error("prom_push_url defined but http not available!") 130 | end 131 | 132 | print("[monitoring] enabling prometheus push") 133 | monitoring.prometheus_push_init() 134 | end 135 | 136 | if monitoring.settings.csv_enable then 137 | print("[monitoring] enabling csv export") 138 | monitoring.csv_init() 139 | end 140 | 141 | if monitoring.settings.json_enable then 142 | print("[monitoring] enabling json export") 143 | monitoring.json_init() 144 | end 145 | 146 | if minetest.get_modpath("mtt") and mtt.enabled then 147 | -- test utils 148 | dofile(MP.."/init.spec.lua") 149 | dofile(MP.."/metrictypes/counter.spec.lua") 150 | dofile(MP.."/export/prometheus_push.spec.lua") 151 | end -------------------------------------------------------------------------------- /init.spec.lua: -------------------------------------------------------------------------------- 1 | 2 | mtt.register("smoketest", function(callback) 3 | callback() 4 | end) -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | License of source code 2 | ---------------------- 3 | 4 | The MIT License (MIT) 5 | Copyright (C) 2018-2019 Thomas Rudin 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 8 | software and associated documentation files (the "Software"), to deal in the Software 9 | without restriction, including without limitation the rights to use, copy, modify, merge, 10 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit 11 | persons to whom the Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all copies or 14 | substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 17 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 18 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 19 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | 23 | For more details: 24 | https://opensource.org/licenses/MIT 25 | 26 | 27 | Licenses of media (textures) 28 | ---------------------------- 29 | 30 | Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0) 31 | Copyright (C) 2019 Thomas Rudin 32 | 33 | You are free to: 34 | Share — copy and redistribute the material in any medium or format. 35 | Adapt — remix, transform, and build upon the material for any purpose, even commercially. 36 | The licensor cannot revoke these freedoms as long as you follow the license terms. 37 | 38 | Under the following terms: 39 | 40 | Attribution — You must give appropriate credit, provide a link to the license, and 41 | indicate if changes were made. You may do so in any reasonable manner, but not in any way 42 | that suggests the licensor endorses you or your use. 43 | 44 | ShareAlike — If you remix, transform, or build upon the material, you must distribute 45 | your contributions under the same license as the original. 46 | 47 | No additional restrictions — You may not apply legal terms or technological measures that 48 | legally restrict others from doing anything the license permits. 49 | 50 | Notices: 51 | 52 | You do not have to comply with the license for elements of the material in the public 53 | domain or where your use is permitted by an applicable exception or limitation. 54 | No warranties are given. The license may not give you all of the permissions necessary 55 | for your intended use. For example, other rights such as publicity, privacy, or moral 56 | rights may limit how you use the material. 57 | -------------------------------------------------------------------------------- /metrictypes/counter.lua: -------------------------------------------------------------------------------- 1 | 2 | local get_us_time = minetest.get_us_time 3 | local reentry_map = {} 4 | 5 | monitoring.counter = function(name, help, options) 6 | local metric = { 7 | name = name, 8 | help = help, 9 | type = "counter", 10 | value = 0, 11 | options = options or {} 12 | } 13 | 14 | -- increment counter 15 | metric.inc = function(value) 16 | metric.value = metric.value + (value or 1) 17 | end 18 | 19 | -- set counter manually 20 | metric.set = function(value) 21 | metric.value = value 22 | end 23 | 24 | -- wrap a function and increment counter on every call 25 | metric.wrap = function(f) 26 | return function(...) 27 | metric.value = metric.value + 1 28 | return f(...) 29 | end 30 | end 31 | 32 | -- wrap a function and increment time on every call 33 | metric.wraptime = function(f) 34 | return function(...) 35 | if reentry_map[metric.name] then 36 | -- already measuring time for this metric 37 | return f(...) 38 | end 39 | 40 | local t0 = get_us_time() 41 | 42 | reentry_map[metric.name] = true 43 | local results = {f(...)} 44 | reentry_map[metric.name] = nil 45 | 46 | local t1 = get_us_time() 47 | local diff = t1 - t0 48 | 49 | metric.value = metric.value + diff 50 | 51 | return unpack(results) 52 | end 53 | end 54 | 55 | table.insert(monitoring.metrics, metric) 56 | monitoring.metrics_mapped[name] = metric 57 | 58 | return metric 59 | end 60 | -------------------------------------------------------------------------------- /metrictypes/counter.spec.lua: -------------------------------------------------------------------------------- 1 | 2 | mtt.register("counter", function(callback) 3 | 4 | local my_fn_callcount = 0 5 | local my_fn = function(a, b, c) 6 | my_fn_callcount = my_fn_callcount + 1 7 | return a+1, b+1, c+1 8 | end 9 | 10 | local c = monitoring.counter("my_name", "something helpful") 11 | my_fn = c.wraptime(my_fn) 12 | 13 | local x, y, z = my_fn(1,2,3) 14 | assert(my_fn_callcount == 1) 15 | assert(x == 2) 16 | assert(y == 3) 17 | assert(z == 4) 18 | 19 | callback() 20 | end) -------------------------------------------------------------------------------- /metrictypes/gauge.lua: -------------------------------------------------------------------------------- 1 | 2 | monitoring.gauge = function(name, help, options) 3 | local metric = { 4 | name = name, 5 | help = help, 6 | type = "gauge", 7 | options = options or {} 8 | } 9 | 10 | metric.set = function(value) 11 | metric.value = value 12 | end 13 | 14 | metric.setmax = function(value) 15 | if metric.value then 16 | if value > metric.value then 17 | -- new value is greater 18 | metric.value = value 19 | end 20 | else 21 | -- no previous value, set current 22 | metric.value = value 23 | end 24 | end 25 | 26 | metric.setmin = function(value) 27 | if metric.value then 28 | if value < metric.value then 29 | -- new value is smaller 30 | metric.value = value 31 | end 32 | else 33 | -- no previous value, set current 34 | metric.value = value 35 | end 36 | end 37 | 38 | table.insert(monitoring.metrics, metric) 39 | monitoring.metrics_mapped[name] = metric 40 | 41 | return metric 42 | end 43 | -------------------------------------------------------------------------------- /metrictypes/histogram.lua: -------------------------------------------------------------------------------- 1 | 2 | local get_us_time = minetest.get_us_time 3 | 4 | monitoring.histogram = function(name, help, buckets) 5 | buckets = buckets or {0.01, 0.05, 0.1, 0.2, 0.5, 1.0} 6 | local bucketvalues = {} 7 | for k in ipairs(buckets) do 8 | bucketvalues[k] = 0 9 | end 10 | 11 | local metric = { 12 | name = name, 13 | help = help, 14 | type = "histogram", 15 | buckets = buckets, 16 | bucketvalues = bucketvalues, 17 | sum = 0, 18 | count = 0, 19 | infcount = 0 20 | } 21 | 22 | table.insert(monitoring.metrics, metric) 23 | monitoring.metrics_mapped[name] = metric 24 | 25 | -- adds the value to the buckets 26 | local add_value = function(seconds) 27 | local matched = false 28 | 29 | metric.count = metric.count + 1 30 | metric.sum = metric.sum + seconds 31 | 32 | for k,v in ipairs(buckets) do 33 | if seconds <= v then 34 | metric.bucketvalues[k] = metric.bucketvalues[k] + 1 35 | matched = true 36 | end 37 | end 38 | 39 | if not matched then 40 | metric.infcount = metric.infcount + 1 41 | end 42 | end 43 | 44 | -- manual count 45 | metric.observe = function(seconds) 46 | add_value(seconds) 47 | end 48 | 49 | -- timer based count 50 | metric.timer = function() 51 | local t0 = minetest.get_us_time() 52 | 53 | return { 54 | observe = function() 55 | local t1 = get_us_time() 56 | local us = t1 - t0 57 | local seconds = us / 1000000 58 | 59 | add_value(seconds) 60 | end 61 | } 62 | end 63 | 64 | -- wrap a function 65 | metric.wrap = function(f) 66 | return function(...) 67 | local t0 = get_us_time() 68 | 69 | local r1, r2, r3 = f(...) 70 | 71 | local t1 = get_us_time() 72 | local us = t1 - t0 73 | local seconds = us / 1000000 74 | 75 | add_value(seconds) 76 | 77 | return r1, r2, r3 78 | end 79 | end 80 | 81 | return metric 82 | end 83 | -------------------------------------------------------------------------------- /mod.conf: -------------------------------------------------------------------------------- 1 | name = monitoring 2 | optional_depends = """ 3 | qos, 4 | mtt, 5 | technic, 6 | basic_machines, 7 | digilines, 8 | digicontrol, 9 | mesecons, 10 | mesecons_pistons, 11 | mesecons_movestones, 12 | mesecons_luacontroller, 13 | pipeworks, 14 | protector_redo, 15 | protector, 16 | priv_protector, 17 | areas, 18 | xp_redo, 19 | advtrains, 20 | advtrains_luaautomation, 21 | nc_optics 22 | """ 23 | -------------------------------------------------------------------------------- /mods/advtrains/chatcommands.lua: -------------------------------------------------------------------------------- 1 | 2 | local metric_train_count = monitoring.gauge("advtrains_active_train_count", "count of active trains") 3 | 4 | 5 | -- list of active trains 6 | local active_trains = {} 7 | 8 | local timer = 0 9 | minetest.register_globalstep(function(dtime) 10 | timer = timer + dtime 11 | 12 | -- every 10 seconds 13 | if timer < 10 then return end 14 | timer = 0 15 | 16 | for _, train in pairs(advtrains.trains) do 17 | if train.velocity > 0 then 18 | -- train moved, mark as active 19 | active_trains[train.id] = true 20 | end 21 | end 22 | 23 | -- update metrics 24 | local active_train_count = 0 25 | for _ in pairs(active_trains) do 26 | active_train_count = active_train_count + 1 27 | end 28 | metric_train_count.set(active_train_count) 29 | 30 | end) 31 | 32 | 33 | minetest.register_chatcommand("advtrains_remove", { 34 | description = "cleans up the train with the given id", 35 | privs = { server = true }, 36 | func = function(_, param) 37 | advtrains.remove_train(param) 38 | end 39 | }) 40 | 41 | minetest.register_chatcommand("advtrains_cleanup", { 42 | description = "cleans up all non-automated trains", 43 | privs = { server = true }, 44 | func = function() 45 | local count = 0 46 | for _, train in pairs(advtrains.trains) do 47 | if not active_trains[train.id] then 48 | advtrains.remove_train(train.id) 49 | count = count + 1 50 | end 51 | end 52 | return true, "Removed " .. count .. " trains" 53 | end 54 | }) 55 | 56 | minetest.register_chatcommand("advtrains_stats", { 57 | description = "show advtrains stats", 58 | func = function() 59 | local train_count = 0 60 | local wagon_count = 0 61 | local active_train_count = 0 62 | 63 | for _ in pairs(active_trains) do 64 | active_train_count = active_train_count + 1 65 | end 66 | 67 | for _, train in pairs(advtrains.trains) do 68 | train_count = train_count + 1 69 | for _, part in pairs(train.trainparts) do 70 | local wagon = advtrains.wagons[part] 71 | if wagon ~= nil then 72 | wagon_count = wagon_count + 1 73 | end 74 | end 75 | end 76 | 77 | return true, "Trains: " .. train_count .. " Wagons: " .. wagon_count .. " Active trains: " .. active_train_count 78 | end 79 | }) 80 | -------------------------------------------------------------------------------- /mods/advtrains/metrics.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | monitoring.wrap_global({"advtrains", "mainloop_trainlogic"}, "advtrains_mainloop_trainlogic") 4 | monitoring.wrap_global({"advtrains", "ndb", "save_data"}, "advtrains_ndb_save_data") 5 | monitoring.wrap_global({"advtrains", "avt_save"}, "advtrains_avt_save") 6 | monitoring.wrap_global({"atlatc", "mainloop_stepcode"}, "advtrains_atlatc_mainloop_stepcode") 7 | monitoring.wrap_global({"atlatc", "interrupt", "mainloop"}, "advtrains_atlatc_interrupt_mainloop") 8 | monitoring.wrap_global({"atlatc", "run_stepcode"}, "advtrains_atlatc_run_stepcode") 9 | 10 | if minetest.settings:get_bool("monitoring.advtrains.verbose") then 11 | 12 | -- contained in advtrains.mainloop_trainlogic() 13 | monitoring.wrap_global({"advtrains", "train_ensure_init"}, "advtrains_train_ensure_init") 14 | monitoring.wrap_global({"advtrains", "train_step_b"}, "advtrains_train_step_b") 15 | monitoring.wrap_global({"advtrains", "train_step_c"}, "advtrains_train_step_c") 16 | 17 | -- utils, used by advtrains.train_step_c() 18 | monitoring.wrap_global({"advtrains", "path_clear_unused"}, "advtrains_path_clear_unused") 19 | monitoring.wrap_global({"advtrains", "path_setrestore"}, "advtrains_path_setrestore") 20 | monitoring.wrap_global({"advtrains", "spawn_wagons"}, "advtrains_spawn_wagons") 21 | monitoring.wrap_global({"advtrains", "train_check_couples"}, "advtrains_train_check_couples") 22 | monitoring.wrap_global({"advtrains", "path_get_index_by_offset"}, "advtrains_path_get_index_by_offset") 23 | monitoring.wrap_global({"advtrains", "path_get"}, "advtrains_path_get") 24 | 25 | monitoring.wrap_global({"advtrains", "get_adjacent_rail"}, "advtrains_get_adjacent_rail") 26 | monitoring.wrap_global({"advtrains", "path_get_adjacent"}, "advtrains_path_get_adjacent") 27 | monitoring.wrap_global({"advtrains", "path_get_interpolated"}, "advtrains_path_get_interpolated") 28 | monitoring.wrap_global({"advtrains", "get_rail_info_at"}, "advtrains_get_rail_info_at") 29 | monitoring.wrap_global({"advtrains", "ndb", "get_node_or_nil"}, "advtrains_ndb_get_node_or_nil") 30 | monitoring.wrap_global({"advtrains", "ndb", "update"}, "advtrains_ndb_update") 31 | -- monitoring.wrap_global({"advtrains", ""}, "advtrains_") 32 | 33 | end 34 | 35 | local metric_ndb_count = monitoring.gauge("advtrains_ndb_count", "count of advtrains ndb items") 36 | local metric_train_count = monitoring.gauge("advtrains_train_count", "count of trains") 37 | local metric_wagon_count = monitoring.gauge("advtrains_wagon_count", "count of wagons") 38 | 39 | 40 | local timer = 0 41 | minetest.register_globalstep(function(dtime) 42 | timer = timer + dtime 43 | if timer < 60 then return end 44 | timer=0 45 | 46 | local count = 0 47 | local ndb_nodes = advtrains.ndb.get_nodes() 48 | for _, ny in pairs(ndb_nodes) do 49 | for _, nx in pairs(ny) do 50 | for _ in pairs(nx) do 51 | count = count + 1 52 | end 53 | end 54 | end 55 | metric_ndb_count.set(count) 56 | 57 | local traincount = 0 58 | local wagoncount = 0 59 | for _, train in pairs(advtrains.trains) do 60 | traincount = traincount + 1 61 | 62 | for _ in pairs(train.trainparts) do 63 | wagoncount = wagoncount + 1 64 | end 65 | end 66 | metric_train_count.set(traincount) 67 | metric_wagon_count.set(wagoncount) 68 | 69 | end) 70 | 71 | 72 | local stepnum = 1 73 | 74 | -- advtrains globalstep 75 | for i, globalstep in ipairs(minetest.registered_globalsteps) do 76 | local info = minetest.callback_origins[globalstep] 77 | if not info then 78 | break 79 | end 80 | 81 | local modname = info.mod 82 | 83 | if modname == "advtrains" then 84 | 85 | local metric_globalstep = monitoring.counter( 86 | "advtrains_globalstep_time_" .. stepnum, 87 | "timing or the advtrains globalstep #" .. stepnum 88 | ) 89 | 90 | local fn = metric_globalstep.wraptime(globalstep) 91 | minetest.callback_origins[fn] = info 92 | minetest.registered_globalsteps[i] = fn 93 | 94 | stepnum = stepnum + 1 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /mods/basic_machines/init.lua: -------------------------------------------------------------------------------- 1 | local function register_effector_metric(nodename, metricname, prettyname) 2 | local nodedef = minetest.registered_nodes[nodename] 3 | if not nodedef then 4 | return 5 | end 6 | local metric_count = monitoring.counter("basic_machines_" .. metricname .. "_count", 7 | "number of " .. prettyname .. " executes") 8 | local metric_time = monitoring.counter("basic_machines_" .. metricname .. "_time", 9 | "total time of " .. prettyname .. " executes in us") 10 | 11 | 12 | if nodedef.effector and nodedef.effector.action_on then 13 | nodedef.effector.action_on = metric_count.wrap( metric_time.wraptime(nodedef.effector.action_on) ) 14 | end 15 | end 16 | 17 | register_effector_metric("basic_machines:autocrafter", "autocrafter", "Autocrafter") 18 | register_effector_metric("basic_machines:ball_spawner", "ball_spawner", "Ball Spawner") 19 | register_effector_metric("basic_machines:mover", "mover", "Mover") 20 | register_effector_metric("basic_machines:keypad", "keypad", "Keypad") 21 | register_effector_metric("basic_machines:detector", "detector", "Detector") 22 | register_effector_metric("basic_machines:distributor", "distributor", "Distributor") 23 | register_effector_metric("basic_machines:light_off", "light_off", "Light off") 24 | register_effector_metric("basic_machines:light_on", "light_on", "Light on") 25 | -------------------------------------------------------------------------------- /mods/digilines/init.lua: -------------------------------------------------------------------------------- 1 | monitoring.wrap_global({"digilines", "receptor_send"}, "digilines_receptor_send") 2 | monitoring.wrap_global({"digilines", "transmit"}, "digilines_transmit") 3 | -------------------------------------------------------------------------------- /mods/digilines/metric_controller.lua: -------------------------------------------------------------------------------- 1 | 2 | minetest.register_node(":monitoring_digilines:metric_controller", { 3 | description = "Monitoring metric controller", 4 | groups = { 5 | cracky=3 6 | }, 7 | is_ground_content = false, 8 | 9 | on_construct = function(pos) 10 | local meta = minetest.get_meta(pos) 11 | meta:set_string("formspec","field[channel;Channel;${channel}") 12 | end, 13 | 14 | tiles = { 15 | "monitoring_controller_top.png", 16 | "jeija_microcontroller_bottom.png", 17 | "jeija_microcontroller_sides.png", 18 | "jeija_microcontroller_sides.png", 19 | "jeija_microcontroller_sides.png", 20 | "jeija_microcontroller_sides.png" 21 | }, 22 | 23 | inventory_image = "monitoring_controller_top.png", 24 | drawtype = "nodebox", 25 | selection_box = { 26 | --From luacontroller 27 | type = "fixed", 28 | fixed = { -8/16, -8/16, -8/16, 8/16, -5/16, 8/16 }, 29 | }, 30 | node_box = { 31 | --From Luacontroller 32 | type = "fixed", 33 | fixed = { 34 | {-8/16, -8/16, -8/16, 8/16, -7/16, 8/16}, -- Bottom slab 35 | {-5/16, -7/16, -5/16, 5/16, -6/16, 5/16}, -- Circuit board 36 | {-3/16, -6/16, -3/16, 3/16, -5/16, 3/16}, -- IC 37 | } 38 | }, 39 | 40 | paramtype = "light", 41 | sunlight_propagates = true, 42 | 43 | on_receive_fields = function(pos, _, fields, sender) 44 | local name = sender:get_player_name() 45 | if minetest.is_protected(pos,name) and not minetest.check_player_privs(name,{protection_bypass=true}) then 46 | minetest.record_protection_violation(pos,name) 47 | return 48 | end 49 | local meta = minetest.get_meta(pos) 50 | if fields.channel then meta:set_string("channel",fields.channel) end 51 | end, 52 | 53 | digiline = { 54 | receptor = {}, 55 | effector = { 56 | action = function(pos, _, channel, msg) 57 | local meta = minetest.get_meta(pos) 58 | if meta:get_string("channel") ~= channel then 59 | return 60 | end 61 | 62 | if type(msg) == "table" and msg.metric and msg.value and 63 | type(msg.metric) == "string" and type(msg.value) == "number" then 64 | -- set and/or create metric 65 | 66 | local metric = monitoring.metrics_mapped[msg.metric] 67 | if not metric then 68 | -- create metric 69 | if msg.counter then 70 | metric = monitoring.counter(msg.metric, msg.help or "counter for " .. msg.metric) 71 | else 72 | metric = monitoring.gauge(msg.metric, msg.help or "gauge for " .. msg.metric) 73 | end 74 | end 75 | 76 | if metric.type == "gauge" then 77 | metric.value = msg.value 78 | elseif metric.type == "counter" then 79 | if msg.increment then 80 | metric.value = metric.value + msg.value 81 | else 82 | metric.value = msg.value 83 | end 84 | end 85 | end 86 | 87 | if type(msg) == "string" then 88 | -- simple string to get metrics from 89 | local metric = monitoring.metrics_mapped[msg] 90 | if metric then 91 | digiline:receptor_send(pos, digiline.rules.default, channel, metric) 92 | end 93 | end 94 | end 95 | } 96 | } 97 | }) 98 | 99 | minetest.register_alias("monitoring:metric_controller", "monitoring_digilines:metric_controller") 100 | -------------------------------------------------------------------------------- /mods/mesecons/action_on.lua: -------------------------------------------------------------------------------- 1 | 2 | local function register_action_on_metric(nodename, metricname, prettyname) 3 | local nodedef = minetest.registered_nodes[nodename] 4 | if nodedef and nodedef.mesecons and nodedef.mesecons.effector and nodedef.mesecons.effector.action_on then 5 | local metric_count = monitoring.counter("mesecons_" .. metricname .. "_count", 6 | "number of " .. prettyname .. " executes") 7 | local metric_time = monitoring.counter("mesecons_" .. metricname .. "_time", 8 | "total time of " .. prettyname .. " executes in us") 9 | 10 | nodedef.mesecons.effector.action_on = metric_count.wrap( metric_time.wraptime(nodedef.mesecons.effector.action_on) ) 11 | 12 | end 13 | end 14 | 15 | register_action_on_metric("mesecons_pistons:piston_normal_off", "piston_normal_on", "Piston") 16 | register_action_on_metric("mesecons_pistons:piston_sticky_off", "piston_sticky_on", "Sticky piston") 17 | 18 | register_action_on_metric("mesecons_movestones:movestone", "movestone", "Movestone") 19 | register_action_on_metric("mesecons_movestones:sticky_movestone", "sticky_movestone", "Sticky movestone") 20 | register_action_on_metric("mesecons_movestones:movestone_vertical", "movestone_vertical", "Vertical movestone") 21 | register_action_on_metric("mesecons_movestones:sticky_movestone_vertical", 22 | "sticky_movestone_vertical", "Vertical sticky movestone") 23 | -------------------------------------------------------------------------------- /mods/mesecons/functions.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | local metrics = {} -- { fn_name = { count=, time= }, ... } 4 | 5 | 6 | local function register_function(name) 7 | local entry = {} 8 | entry.count = monitoring.counter( 9 | "mesecons_function_" .. name .. "_count", 10 | "number of " .. name .. " function executes" 11 | ) 12 | entry.time = monitoring.counter( 13 | "mesecons_function_" .. name .. "_time", 14 | "time of " .. name .." function executes" 15 | ) 16 | 17 | metrics[name] = entry 18 | end 19 | 20 | register_function("change"); 21 | register_function("lc_interrupt"); 22 | register_function("lc_digiline_relay"); 23 | register_function("deactivate"); 24 | register_function("activate"); 25 | register_function("receptor_on"); 26 | register_function("receptor_off"); 27 | register_function("other"); 28 | 29 | -- enable/disable 30 | local enable_function_monitoring = false 31 | minetest.register_chatcommand("mesecons_verbose_monitoring", { 32 | privs = { server = true }, 33 | description = "enables/disables the verbose mesecons monitoring", 34 | params = "[true|false]", 35 | func = function(_, param) 36 | enable_function_monitoring = param == "true" 37 | return true, "Verbose monitoring: " .. (enable_function_monitoring and "enabled" or "disabled") 38 | end 39 | }) 40 | 41 | -- action queue override 42 | local old_execute = mesecon.queue.execute 43 | mesecon.queue.execute = function(self, action) 44 | if not enable_function_monitoring then 45 | -- not enabled, skip time measuring and just execute the action 46 | return old_execute(self, action) 47 | end 48 | 49 | local t0 = minetest.get_us_time() 50 | old_execute(self, action) 51 | local t1 = minetest.get_us_time() 52 | local delta_t = t1 - t0 53 | 54 | local metric = metrics[action.func] 55 | if not metric then 56 | metric = metrics["other"] 57 | end 58 | 59 | metric.count.inc() 60 | metric.time.inc(delta_t) 61 | end 62 | -------------------------------------------------------------------------------- /mods/mesecons/globals.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | monitoring.wrap_global({"mesecon", "queue", "add_action"}, "mesecons_queue_action_add") 4 | local execute_calls_metric = monitoring.wrap_global({"mesecon", "queue", "execute"}, "mesecons_queue_execute") 5 | 6 | if monitoring.settings.handle_errors then 7 | -- enable error handling in mesecons queue 8 | local old_execute = mesecon.queue.execute 9 | mesecon.queue.execute = function(self, action) 10 | monitoring.protected_call(execute_calls_metric, function() 11 | old_execute(self, action) 12 | end, action.pos) 13 | end 14 | end 15 | 16 | monitoring.wrap_global({"mesecon", "vm_begin"}, "mesecons_vm_begin") 17 | monitoring.wrap_global({"mesecon", "vm_abort"}, "mesecons_vm_abort") 18 | monitoring.wrap_global({"mesecon", "vm_commit"}, "mesecons_vm_commit") 19 | 20 | if minetest.settings:get_bool("monitoring.mesecons.verbose") then 21 | monitoring.wrap_global({"mesecon", "get_node_force"}, "mesecons_get_node_force") 22 | monitoring.wrap_global({"mesecon", "activate"}, "mesecons_activate") 23 | monitoring.wrap_global({"mesecon", "deactivate"}, "mesecons_deactivate") 24 | monitoring.wrap_global({"mesecon", "turnon"}, "mesecons_turnon") 25 | monitoring.wrap_global({"mesecon", "turnoff"}, "mesecons_turnoff") 26 | monitoring.wrap_global({"mesecon", "changesignal"}, "mesecons_changesignal") 27 | monitoring.wrap_global({"mesecon", "swap_node_force"}, "swap_node_force") 28 | end 29 | -------------------------------------------------------------------------------- /mods/mesecons/luac.lua: -------------------------------------------------------------------------------- 1 | 2 | local BASENAME = "mesecons_luacontroller:luacontroller" 3 | 4 | 5 | local metric_count = monitoring.counter( 6 | "mesecons_luacontroller_nodetimer_count", 7 | "number of luac nodetimer calls" 8 | ); 9 | 10 | local metric_time = monitoring.counter( 11 | "mesecons_luacontroller_nodetimer_time", 12 | "time of luac nodetimer calls" 13 | ); 14 | 15 | local metric_time_max = monitoring.gauge( 16 | "mesecons_luacontroller_nodetimer_time_max", 17 | "max time of luac nodetimer calls", 18 | { autoflush=true } 19 | ) 20 | 21 | for a = 0, 1 do -- 0 = off 1 = on 22 | for b = 0, 1 do 23 | for c = 0, 1 do 24 | for d = 0, 1 do 25 | local cid = tostring(d)..tostring(c)..tostring(b)..tostring(a) 26 | local node_name = BASENAME..cid 27 | 28 | local def = minetest.registered_nodes[node_name] 29 | 30 | if def then 31 | local old_on_timer = def.on_timer 32 | 33 | -- intercept on_timer call to measure time usage 34 | def.on_timer = function(...) 35 | local t0 = minetest.get_us_time() 36 | local result = old_on_timer(...) 37 | local t1 = minetest.get_us_time() 38 | local diff = t1 -t0 39 | 40 | metric_time_max.setmax(diff) 41 | metric_time.inc(diff) 42 | metric_count.inc(1) 43 | return result 44 | end 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /mods/mesecons/queue.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -- queue size metric 5 | local metric_action_queue_size = monitoring.gauge( 6 | "mesecons_action_queue_size", 7 | "size of action queue" 8 | ) 9 | 10 | local metric_action_queue_size_max = monitoring.gauge( 11 | "mesecons_action_queue_size_max", 12 | "max size of action queue", 13 | { autoflush=true } 14 | ) 15 | 16 | minetest.register_globalstep(function() 17 | if not mesecon.queue.actions then 18 | return 19 | end 20 | 21 | metric_action_queue_size.set(#mesecon.queue.actions) 22 | metric_action_queue_size_max.setmax(#mesecon.queue.actions) 23 | end) 24 | -------------------------------------------------------------------------------- /mods/nodecore/nc_optics.lua: -------------------------------------------------------------------------------- 1 | monitoring.wrap_global({"nodecore", "optic_check"}, "nodecore_optic_check") 2 | monitoring.wrap_global({"nodecore", "optic_scan_recv"}, "nodecore_optic_scan_recv") 3 | monitoring.wrap_global({"nodecore", "optic_scan"}, "nodecore_optic_scan") 4 | monitoring.wrap_global({"nodecore", "optic_immediate"}, "nodecore_optic_immediate") 5 | monitoring.wrap_global({"nodecore", "optic_check_dependents"}, "nodecore_optic_check_dependents") 6 | -------------------------------------------------------------------------------- /mods/pipeworks/can_go.lua: -------------------------------------------------------------------------------- 1 | 2 | -- all registered metrics 3 | local metrics = {} 4 | 5 | local function get_or_create_metric_counter(name, help) 6 | if not metrics[name] then 7 | metrics[name] = monitoring.counter(name, help) 8 | end 9 | return metrics[name] 10 | end 11 | 12 | local function profile_can_go(name, def, prefix) 13 | if def.tube and def.tube.can_go and type(def.tube.can_go) == "function" then 14 | -- valid function, intercept can_go function 15 | print("[pipeworks_monitoring] intercepting can_go call for " .. name) 16 | local metric_calls = get_or_create_metric_counter(prefix .. "_calls", "calls to " .. prefix) 17 | local metric_time = get_or_create_metric_counter(prefix .. "_time", " timeing of " .. prefix) 18 | 19 | local wrapped_time = metric_time.wraptime(def.tube.can_go) 20 | local wrapped_count = metric_calls.wrap(wrapped_time) 21 | def.tube.can_go = wrapped_count 22 | end 23 | end 24 | 25 | for name, def in pairs(minetest.registered_nodes) do 26 | if string.find(name, "pipeworks:teleport_tube") then 27 | profile_can_go(name, def, "pipeworks_teleport_tube") 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /mods/pipeworks/chatcommands.lua: -------------------------------------------------------------------------------- 1 | minetest.register_chatcommand("pipeworks_flush", { 2 | description = "flushes the pipeworks tubes", 3 | privs = {server=true}, 4 | func = function(name) 5 | minetest.log("warning", "Player " .. name .. " flushes the pipeworks tubes") 6 | local count = monitoring.pipeworks.flush() 7 | minetest.log("warning", "Flushed: " .. count .. " items") 8 | return true, "Flushed: " .. count .. " items" 9 | end 10 | }) 11 | 12 | minetest.register_chatcommand("pipeworks_stats", { 13 | description = "Returns some pipeworks stats", 14 | privs = {interact=true}, 15 | func = function() 16 | local count = 0 17 | for _, _ in pairs(pipeworks.luaentity.entities) do 18 | count = count + 1 19 | end 20 | return true, "Items in tubes: " .. count 21 | end 22 | }) 23 | 24 | minetest.register_chatcommand("pipeworks_enable", { 25 | description = "enables the pipeworks mod", 26 | privs = {server=true}, 27 | func = function(name) 28 | monitoring.pipeworks.enabled = true 29 | minetest.log("warning", "Player " .. name .. " enables the pipeworks mod") 30 | return true, "Pipeworks enabled" 31 | end 32 | }) 33 | 34 | minetest.register_chatcommand("pipeworks_disable", { 35 | description = "disables the pipeworks mod", 36 | privs = {server=true}, 37 | func = function(name) 38 | -- flush pipes and disable 39 | monitoring.pipeworks.flush() 40 | monitoring.pipeworks.enabled = false 41 | minetest.log("warning", "Player " .. name .. " disables the pipeworks mod") 42 | return true, "Pipeworks disabled" 43 | end 44 | }) 45 | 46 | minetest.register_chatcommand("pipeworks_check_limit", { 47 | description = "checks if the injection limit is reached at the current position", 48 | func = function(name) 49 | local player = minetest.get_player_by_name(name) 50 | local pos = player:get_pos() 51 | local count = monitoring.pipeworks.inject_limiter_count(pos) 52 | return true, "Count: " .. count .. " max: " .. monitoring.pipeworks.max_inject_items 53 | end 54 | }) 55 | 56 | minetest.register_chatcommand("pipeworks_limit_stats", { 57 | description = "shows the chunk with the highest injection rate", 58 | func = function() 59 | 60 | local pos, count = monitoring.pipeworks.inject_limiter_max_pos() 61 | local msg = "no stats available" 62 | if pos then 63 | msg = "Chunk: " .. minetest.pos_to_string(pos) .. " count: " .. count .. 64 | " max: " .. monitoring.pipeworks.max_inject_items 65 | end 66 | 67 | return true, msg 68 | end 69 | }) 70 | -------------------------------------------------------------------------------- /mods/pipeworks/entity_count.lua: -------------------------------------------------------------------------------- 1 | 2 | local metric_entity_count = monitoring.gauge("pipeworks_entity_count", "count of pipeworks entities") 3 | 4 | 5 | local timer = 0 6 | minetest.register_globalstep(function(dtime) 7 | timer = timer + dtime 8 | if timer < 5 then return end 9 | timer=0 10 | 11 | local count = 0 12 | for _ in pairs(pipeworks.luaentity.entities) do 13 | count = count + 1 14 | end 15 | 16 | metric_entity_count.set(count) 17 | 18 | end) 19 | -------------------------------------------------------------------------------- /mods/pipeworks/expiration.lua: -------------------------------------------------------------------------------- 1 | local expired_items_metric 2 | 3 | if minetest.get_modpath("monitoring") then 4 | expired_items_metric = monitoring.counter( 5 | "pipeworks_expired_items_count", 6 | "Number of expired items" 7 | ) 8 | end 9 | 10 | local function cleanup() 11 | local now = os.time() 12 | for _, entity in pairs(pipeworks.luaentity.entities) do 13 | if not entity.ctime then 14 | -- set creation time if not already set 15 | entity.ctime = now 16 | else 17 | -- check expiration time 18 | local delta = now - entity.ctime 19 | if delta > monitoring.pipeworks.item_expiration_seconds then 20 | -- entity expired, remove 21 | entity:remove() 22 | 23 | if expired_items_metric then 24 | expired_items_metric.inc() 25 | end 26 | end 27 | end 28 | end 29 | 30 | minetest.after(10, cleanup) 31 | end 32 | 33 | -- start initial cleanup after 10 minutes 34 | minetest.after(monitoring.pipeworks.item_expiration_seconds, cleanup) 35 | -------------------------------------------------------------------------------- /mods/pipeworks/filter_action_on.lua: -------------------------------------------------------------------------------- 1 | local function register_action_on_metric(nodename, metricname, prettyname) 2 | local nodedef = minetest.registered_nodes[nodename] 3 | if nodedef and nodedef.mesecons and nodedef.mesecons.effector and nodedef.mesecons.effector.action_on then 4 | local metric_count = monitoring.counter( 5 | "pipeworks_" .. metricname .. "_count", 6 | "number of " .. prettyname .. " executes" 7 | ) 8 | 9 | local metric_time = monitoring.counter( 10 | "pipeworks_" .. metricname .. "_time", 11 | "total time of " .. prettyname .. " executes in us" 12 | ) 13 | 14 | nodedef.mesecons.effector.action_on = metric_count.wrap( metric_time.wraptime(nodedef.mesecons.effector.action_on) ) 15 | 16 | end 17 | end 18 | 19 | register_action_on_metric("pipeworks:mese_filter", "mese_filter", "Mese filter") 20 | register_action_on_metric("pipeworks:filter", "filter", "Filter") 21 | 22 | register_action_on_metric("pipeworks:dispenser_off", "dispenser", "Dispenser") 23 | register_action_on_metric("pipeworks:deployer_off", "deployer", "Deployer") 24 | register_action_on_metric("pipeworks:nodebreaker_off", "nodebreaker", "Nodebreaker") 25 | -------------------------------------------------------------------------------- /mods/pipeworks/flush.lua: -------------------------------------------------------------------------------- 1 | function monitoring.pipeworks.flush() 2 | local count = 0 3 | for _, entity in pairs(pipeworks.luaentity.entities) do 4 | entity:remove() 5 | count = count + 1 6 | end 7 | return count 8 | end 9 | -------------------------------------------------------------------------------- /mods/pipeworks/globalsteps.lua: -------------------------------------------------------------------------------- 1 | 2 | local stepnum = 1 3 | 4 | for i, globalstep in ipairs(minetest.registered_globalsteps) do 5 | local info = minetest.callback_origins[globalstep] 6 | if not info then 7 | break 8 | end 9 | 10 | local modname = info.mod 11 | 12 | if modname == "pipeworks" then 13 | 14 | local metric_globalstep = monitoring.counter( 15 | "pipeworks_globalstep_time_" .. stepnum, 16 | "timing or the pipeworks globalstep #" .. stepnum 17 | ) 18 | 19 | -- local proxy function that manages the override and calls the mod's globalsteps 20 | local fn = function(dtime) 21 | if not monitoring.pipeworks.enabled then 22 | -- pipeworks is disabled, skip this globalstep 23 | return 24 | end 25 | local wrappedFn = metric_globalstep.wraptime(globalstep) 26 | 27 | -- call the actual mod callback 28 | wrappedFn(dtime); 29 | end 30 | minetest.callback_origins[fn] = info 31 | minetest.registered_globalsteps[i] = fn 32 | 33 | stepnum = stepnum + 1 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /mods/pipeworks/init.lua: -------------------------------------------------------------------------------- 1 | monitoring.pipeworks = { 2 | -- global mod enabled flag 3 | enabled = true, 4 | 5 | -- 10 minutes item expiration 6 | item_expiration_seconds = 10 * 60, 7 | 8 | -- only allow this many items per second per mapchunk 9 | max_inject_items = 100 10 | } 11 | -------------------------------------------------------------------------------- /mods/pipeworks/inject_limiter.lua: -------------------------------------------------------------------------------- 1 | -- chunk-based pipeworks injection limiter 2 | 3 | local function get_chunk_pos(pos) 4 | return vector.floor(vector.divide(pos, 80)) 5 | end 6 | 7 | -- map of [chunkpos]count 8 | local data = {} 9 | 10 | local function increment_and_get_count(pos) 11 | local chunk_pos = get_chunk_pos(pos) 12 | local hash = minetest.hash_node_position(chunk_pos) 13 | local count = data[hash] or 0 14 | count = count + 1 15 | data[hash] = count 16 | return count 17 | end 18 | 19 | -- returns true if the limit is exceeded 20 | function monitoring.pipeworks.inject_limiter(pos) 21 | local count = increment_and_get_count(pos) 22 | return count > monitoring.pipeworks.max_inject_items 23 | end 24 | 25 | -- returns the mapblock pos with the highest injection rate 26 | function monitoring.pipeworks.inject_limiter_max_pos() 27 | local max_count = 0 28 | local max_hash 29 | for hash, count in pairs(data) do 30 | if count > max_count then 31 | max_hash = hash 32 | max_count = count 33 | end 34 | end 35 | 36 | if max_hash then 37 | local pos = minetest.get_position_from_hash(max_hash) 38 | return pos, max_count 39 | end 40 | end 41 | 42 | -- returns the current count 43 | function monitoring.pipeworks.inject_limiter_count(pos) 44 | local chunk_pos = get_chunk_pos(pos) 45 | local hash = minetest.hash_node_position(chunk_pos) 46 | return data[hash] or 0 47 | end 48 | 49 | -- clear cache periodically 50 | local function clear_cache() 51 | data = {} 52 | minetest.after(1, clear_cache) 53 | end 54 | 55 | minetest.after(1, clear_cache) 56 | -------------------------------------------------------------------------------- /mods/pipeworks/metrics.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | monitoring.wrap_global({"pipeworks", "create_fake_player"}, "pipeworks_create_fake_player") 4 | monitoring.wrap_global({"pipeworks", "load_position"}, "pipeworks_load_position") 5 | -------------------------------------------------------------------------------- /mods/pipeworks/teleport_tubes.lua: -------------------------------------------------------------------------------- 1 | 2 | if not pipeworks.tptube or not pipeworks.tptube.get_db then 3 | -- tp-tubes not exposed, skip 4 | return 5 | end 6 | 7 | local metric_tptube_count = monitoring.gauge("pipeworks_tptube_count", "count of teleport tubes in the db") 8 | 9 | 10 | local function job() 11 | local db = pipeworks.tptube.get_db() 12 | local count = 0 13 | for _ in pairs(db) do 14 | count = count + 1 15 | end 16 | metric_tptube_count.set(count) 17 | 18 | minetest.after(20, job) 19 | end 20 | 21 | minetest.after(0, job) 22 | -------------------------------------------------------------------------------- /mods/pipeworks/tube_inject_item.lua: -------------------------------------------------------------------------------- 1 | local metric_tube_inject_item_calls = monitoring.counter( 2 | "pipeworks_tube_inject_item_calls", 3 | "count of pipeworks.tube_inject_item calls" 4 | ) 5 | 6 | local metric_tube_inject_item_time = monitoring.counter( 7 | "pipeworks_tube_inject_item_time", 8 | "time of pipeworks.tube_inject_item calls" 9 | ) 10 | 11 | local metric_tube_inject_item_limited_calls = monitoring.counter( 12 | "pipeworks_tube_inject_item_limited_calls", 13 | "count of pipeworks.tube_inject_item calls that were blocked" 14 | ) 15 | 16 | local old_inject_item = pipeworks.tube_inject_item 17 | 18 | pipeworks.tube_inject_item = function(pos, start_pos, velocity, item, owner, tags) 19 | if not monitoring.pipeworks.enabled then 20 | -- only allow call if the mod is in "enabled" state 21 | return 22 | end 23 | 24 | local limit_reached = monitoring.pipeworks.inject_limiter(pos) 25 | if limit_reached then 26 | -- limit exceeded 27 | metric_tube_inject_item_limited_calls.inc() 28 | return 29 | end 30 | 31 | -- everything ok, let it go into tubes 32 | old_inject_item(pos, start_pos, velocity, item, owner, tags) 33 | end 34 | 35 | -- wrap metrics interceptor around it 36 | pipeworks.tube_inject_item = metric_tube_inject_item_calls.wrap( 37 | metric_tube_inject_item_time.wraptime(pipeworks.tube_inject_item) 38 | ) 39 | -------------------------------------------------------------------------------- /mods/technic/abm.lua: -------------------------------------------------------------------------------- 1 | 2 | local max_time_metric = monitoring.gauge( 3 | "technic_switching_station_abm_time_max", 4 | "max time of technic switch abm calls", 5 | { autoflush=true } 6 | ) 7 | 8 | 9 | for _, abm in ipairs(minetest.registered_abms) do 10 | 11 | if abm.label == "Switching Station" then 12 | print("[monitoring] wrapping switching station abm") 13 | 14 | -- max time peaks for switching stations 15 | local old_action = abm.action 16 | abm.action = function(...) 17 | 18 | local t0 = minetest.get_us_time() 19 | old_action(...) 20 | local t1 = minetest.get_us_time() 21 | local diff = t1 -t0 22 | 23 | max_time_metric.setmax(diff) 24 | end 25 | 26 | abm.action = monitoring 27 | .counter("technic_switching_station_abm_count", "number of technic switch abm calls") 28 | .wrap(abm.action) 29 | 30 | abm.action = monitoring 31 | .counter("technic_switching_station_abm_time", "time of technic switch abm calls") 32 | .wraptime(abm.action) 33 | end 34 | 35 | if abm.label == "Radiation damage" then 36 | abm.action = monitoring 37 | .counter("technic_radiation_abm_count", "number of radiation abm calls") 38 | .wrap(abm.action) 39 | 40 | abm.action = monitoring 41 | .counter("technic_radiation_abm_time", "time of radiation abm calls") 42 | .wraptime(abm.action) 43 | end 44 | 45 | 46 | end 47 | -------------------------------------------------------------------------------- /mods/technic/quarry.lua: -------------------------------------------------------------------------------- 1 | 2 | local quarry_def = minetest.registered_nodes["technic:quarry"] 3 | assert(quarry_def) 4 | 5 | quarry_def.technic_run = 6 | monitoring.counter("technic_quarry_dig_count", "number of technic quarry digs") 7 | .wrap(quarry_def.technic_run) 8 | -------------------------------------------------------------------------------- /mods/technic/switch.lua: -------------------------------------------------------------------------------- 1 | assert(type(technic.network_run) == "function") 2 | assert(type(technic.active_networks) == "table") 3 | 4 | technic.network_run = 5 | monitoring.counter("technic_switching_stations_usage", "usage in microseconds cpu time") 6 | .wraptime(technic.network_run) 7 | 8 | local active_switching_stations_metric = monitoring.gauge( 9 | "technic_active_switching_stations", 10 | "Number of active switching stations" 11 | ) 12 | 13 | local function count_stations() 14 | local count = 0 15 | for _ in pairs(technic.active_networks) do 16 | count = count + 1 17 | end 18 | active_switching_stations_metric.set(count) 19 | minetest.after(5, count_stations) 20 | end 21 | count_stations() -------------------------------------------------------------------------------- /mods/technic/technic_run.lua: -------------------------------------------------------------------------------- 1 | 2 | minetest.register_on_mods_loaded(function() 3 | for name, def in pairs(minetest.registered_nodes) do 4 | if def.technic_run then 5 | 6 | local sanitized_name = string.gsub(name, ":", "_") 7 | 8 | def.technic_run = monitoring 9 | .counter("technic_machine_" .. sanitized_name .. "_count", "number of machine calls") 10 | .wrap(def.technic_run) 11 | 12 | def.technic_run = monitoring 13 | .counter("technic_machine_" .. sanitized_name .. "_time", "time of machine calls") 14 | .wraptime(def.technic_run) 15 | end 16 | end 17 | end) 18 | -------------------------------------------------------------------------------- /pics/craft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-monitoring/monitoring/a1b1e41593ce01a49ce390683894e4e7fe1a294b/pics/craft.png -------------------------------------------------------------------------------- /pics/lag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-monitoring/monitoring/a1b1e41593ce01a49ce390683894e4e7fe1a294b/pics/lag.png -------------------------------------------------------------------------------- /prom_push_example.sh: -------------------------------------------------------------------------------- 1 | cat < 0 do 8 | get_us_time() 9 | count = count - 1 10 | end 11 | 12 | local diff = get_us_time() - start 13 | 14 | print("[monitoring] sampling: " .. iterations .. " calls took " .. diff .. " us") 15 | -------------------------------------------------------------------------------- /settingtypes.txt: -------------------------------------------------------------------------------- 1 | monitoring.prometheus_push_url (monitoring push url) string 2 | 3 | monitoring.verbose (verbose monitoring) bool false 4 | -------------------------------------------------------------------------------- /textures/monitoring_controller_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-monitoring/monitoring/a1b1e41593ce01a49ce390683894e4e7fe1a294b/textures/monitoring_controller_top.png --------------------------------------------------------------------------------