Mercurial > prosody-modules
comparison mod_prometheus/mod_prometheus.lua @ 3125:07a2ba55de4d
mod_prometheus: Add a new statistics export module, for Prometheus.
| author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
|---|---|
| date | Thu, 21 Jun 2018 21:37:13 +0200 |
| parents | |
| children | 888375de933c |
comparison
equal
deleted
inserted
replaced
| 3124:cabe58ae17c9 | 3125:07a2ba55de4d |
|---|---|
| 1 -- Log common stats to statsd | |
| 2 -- | |
| 3 -- Copyright (C) 2014 Daurnimator | |
| 4 -- | |
| 5 -- This module is MIT/X11 licensed. | |
| 6 | |
| 7 module:set_global(); | |
| 8 module:depends "http"; | |
| 9 | |
| 10 local s_format = string.format; | |
| 11 local t_insert = table.insert; | |
| 12 local socket = require "socket"; | |
| 13 local mt = require "util.multitable"; | |
| 14 | |
| 15 local meta = mt.new(); meta.data = module:shared"meta"; | |
| 16 local data = mt.new(); data.data = module:shared"data"; | |
| 17 | |
| 18 local function escape(text) | |
| 19 return text:gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\n", "\\n"); | |
| 20 end | |
| 21 | |
| 22 local function escape_name(name) | |
| 23 return name:gsub("[^A-Za-z0-9_]", "_"):gsub("^[^A-Za-z_]", "_%1"); | |
| 24 end | |
| 25 | |
| 26 local function get_timestamp() | |
| 27 -- Using LuaSocket for that because os.time() only has second precision. | |
| 28 return math.floor(socket.gettime() * 1000); | |
| 29 end | |
| 30 | |
| 31 local function repr_help(metric, docstring) | |
| 32 docstring = docstring:gsub("\\", "\\\\"):gsub("\n", "\\n"); | |
| 33 return "# HELP "..escape_name(metric).." "..docstring.."\n"; | |
| 34 end | |
| 35 | |
| 36 -- local allowed_types = { counter = true, gauge = true, histogram = true, summary = true, untyped = true }; | |
| 37 -- local allowed_types = { "counter", "gauge", "histogram", "summary", "untyped" }; | |
| 38 local function repr_type(metric, type_) | |
| 39 -- if not allowed_types:contains(type_) then | |
| 40 -- return; | |
| 41 -- end | |
| 42 return "# TYPE "..escape_name(metric).." "..type_.."\n"; | |
| 43 end | |
| 44 | |
| 45 local function repr_label(key, value) | |
| 46 return key.."=\""..escape(value).."\""; | |
| 47 end | |
| 48 | |
| 49 local function repr_labels(labels) | |
| 50 local values = {} | |
| 51 for key, value in pairs(labels) do | |
| 52 t_insert(values, repr_label(escape_name(key), escape(value))); | |
| 53 end | |
| 54 if #values == 0 then | |
| 55 return ""; | |
| 56 end | |
| 57 return "{"..table.concat(values, ", ").."}"; | |
| 58 end | |
| 59 | |
| 60 local function repr_sample(metric, labels, value, timestamp) | |
| 61 return escape_name(metric)..repr_labels(labels).." "..value.." "..timestamp.."\n"; | |
| 62 end | |
| 63 | |
| 64 module:hook("stats-updated", function (event) | |
| 65 local all_stats, this = event.stats_extra; | |
| 66 local host, sect, name, typ, key; | |
| 67 for stat, value in pairs(event.changed_stats) do | |
| 68 this = all_stats[stat]; | |
| 69 -- module:log("debug", "changed_stats[%q] = %s", stat, tostring(value)); | |
| 70 host, sect, name, typ = stat:match("^/([^/]+)/([^/]+)/(.+):(%a+)$"); | |
| 71 if host == nil then | |
| 72 sect, name, typ, host = stat:match("^([^.]+)%.([^:]+):(%a+)$"); | |
| 73 elseif host == "*" then | |
| 74 host = nil; | |
| 75 end | |
| 76 if sect:find("^mod_measure_.") then | |
| 77 sect = sect:sub(13); | |
| 78 elseif sect:find("^mod_statistics_.") then | |
| 79 sect = sect:sub(16); | |
| 80 end | |
| 81 key = escape_name(s_format("%s_%s_%s", host or "global", sect, typ)); | |
| 82 | |
| 83 if not meta:get(key) then | |
| 84 if host then | |
| 85 meta:set(key, "", "graph_title", s_format("%s %s on %s", sect, typ, host)); | |
| 86 else | |
| 87 meta:set(key, "", "graph_title", s_format("Global %s %s", sect, typ, host)); | |
| 88 end | |
| 89 meta:set(key, "", "graph_vlabel", this and this.units or typ); | |
| 90 meta:set(key, "", "graph_category", sect); | |
| 91 | |
| 92 meta:set(key, name, "label", name); | |
| 93 elseif not meta:get(key, name, "label") then | |
| 94 meta:set(key, name, "label", name); | |
| 95 end | |
| 96 | |
| 97 data:set(key, name, value); | |
| 98 end | |
| 99 end); | |
| 100 | |
| 101 local function get_metrics(event) | |
| 102 local response = event.response; | |
| 103 response.headers.content_type = "text/plain; version=0.4.4"; | |
| 104 | |
| 105 local response = {}; | |
| 106 local timestamp = tostring(get_timestamp()); | |
| 107 for section, data in pairs(data.data) do | |
| 108 for key, value in pairs(data) do | |
| 109 local name = section.."_"..key; | |
| 110 t_insert(response, repr_help(name, "TODO: add a description here.")); | |
| 111 t_insert(response, repr_type(name, "gauge")); | |
| 112 t_insert(response, repr_sample(name, {}, value, timestamp)); | |
| 113 end | |
| 114 end | |
| 115 return table.concat(response, ""); | |
| 116 end | |
| 117 | |
| 118 function module.add_host(module) | |
| 119 module:provides("http", { | |
| 120 default_path = "metrics"; | |
| 121 route = { | |
| 122 GET = get_metrics; | |
| 123 }; | |
| 124 }); | |
| 125 end |
