Skip to content

Commit b557b65

Browse files
authored
Merge pull request #352 from sstrigler/mod_prometheus-add-gauge-type
Add gauge type for own commands
2 parents c9d8fad + 4d42240 commit b557b65

2 files changed

Lines changed: 142 additions & 13 deletions

File tree

mod_prometheus/conf/mod_prometheus.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ modules:
2323
labels:
2424
- host
2525
- stanza
26+
# Can have same hook but as a counter at the same time:
27+
- hook: user_send_packet
28+
type: counter
29+
help: "Number of messages sent"
30+
labels:
31+
- host
32+
- stanza
2633
# Histogram for all modules of a hook:
2734
- hook: user_receive_packet
2835
type: histogram
@@ -66,3 +73,34 @@ modules:
6673
- 750
6774
- 1000
6875
- 1500
76+
# Gauge for a "hook" (name of the metric in this case), value retrieved by
77+
# manually executing given command and args (needs mod_admin_extra in this
78+
# example)
79+
- hook: ejabberd_uptime
80+
type: gauge
81+
help: "Uptime in seconds"
82+
command: stats
83+
args:
84+
- "uptimeseconds"
85+
# Keyword `host` will be replaced with actual hostname, needs label `host`
86+
# as well
87+
- hook: registered_users_num
88+
type: gauge
89+
help: "Number of registered users per host"
90+
labels:
91+
- host
92+
command: stats
93+
args:
94+
- "registeredusers"
95+
- host
96+
# Set `result_type` to `list` if result from command is a list. Will set
97+
# gauge to length of list.
98+
- hook: spam_filter_cache_size
99+
type: gauge
100+
help: "Size of spam filter cache"
101+
labels:
102+
- host
103+
command: get_spam_filter_cache
104+
args:
105+
- host
106+
result_type: list

mod_prometheus/src/mod_prometheus.erl

Lines changed: 104 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
-module(mod_prometheus).
22
-author('pouriya.jahanbakhsh@gmail.com').
3+
-author('stefan@strigler.de').
34
-behaviour(gen_mod).
5+
-behaviour(gen_server).
46

7+
%% gen_mod callbacks
58
-export([start/2, stop/1, reload/3, mod_doc/0,
6-
mod_opt_type/1, mod_options/1,
7-
depends/2]).
9+
mod_opt_type/1, mod_options/1,
10+
depends/2]).
811

12+
%% gen_server callbacks
13+
-export([init/1, terminate/2, handle_call/3,
14+
handle_cast/2, handle_info/2, code_change/3]).
15+
16+
%% internal API
917
-export([process_histogram/5, process_counter/5]).
1018

1119
-export([process/2]).
1220

1321
-include("logger.hrl").
1422
-include_lib("xmpp/include/xmpp.hrl").
15-
-include("mod_roster.hrl").
1623
-include("ejabberd_http.hrl").
17-
-include("ejabberd_web_admin.hrl").
18-
-include("ejabberd_stacktrace.hrl").
19-
-include("translate.hrl").
24+
25+
-define(INTERVAL, 1000).
2026

2127
start(Host, Opts) ->
2228
case lists:member({subscribe, 5}, ejabberd_hooks:module_info(exports)) of
@@ -26,23 +32,87 @@ start(Host, Opts) ->
2632
update_mnesia(get_opt(mnesia, Opts)),
2733
update_vm(get_opt(vm, Opts)),
2834
handle_hooks(get_opt(hooks, Opts), Host, subscribe, []),
29-
ok;
35+
gen_mod:start_child(?MODULE, Host, Opts);
3036
_ ->
3137
Error = "Hook subscriber is not supported. Please Upgrade Ejabberd.",
3238
?ERROR_MSG(Error, []),
3339
{error, Error}
3440
end.
3541

42+
init([Host|_]) ->
43+
process_flag(trap_exit, true),
44+
Opts = gen_mod:get_module_opts(Host, ?MODULE),
45+
TRef = start_timer(),
46+
{ok, [Host, Opts, TRef]}.
47+
48+
terminate(_Reason, _State) ->
49+
ok.
50+
51+
handle_call(stop, _From, [_Host, _Opts, TRef] = State) ->
52+
misc:cancel_timer(TRef),
53+
{stop, normal, ok, State};
54+
handle_call(Request, From, State) ->
55+
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
56+
{noreply, State}.
57+
58+
handle_cast({reload, NewOpts}, [Host, _OldOpts, TRef]) ->
59+
?DEBUG("Reloading opts: ~p", [NewOpts]),
60+
{noreply, [Host, NewOpts, TRef]};
61+
handle_cast(Msg, State) ->
62+
?WARNING_MSG("Unexpected cast: ~p", [Msg]),
63+
{noreply, State}.
64+
65+
handle_info({timeout, _TRef, ping}, [Host, Opts, OldTRef]) ->
66+
?DEBUG("Running commands for ~p", [Host]),
67+
lists:foreach(
68+
fun(#{type := gauge, hook := Hook, command := Cmd} = Opt) ->
69+
OptArgs = maps:get(args, Opt, []),
70+
CmdArgs = replace_host(OptArgs, Host, []),
71+
Res = ejabberdctl(Cmd, CmdArgs, maps:get(result_type, Opt, raw)),
72+
?DEBUG("Got result for command ~p with args ~p on ~s: ~p", [Cmd, CmdArgs, Host, Res]),
73+
LabelNames = maps:get(labels, Opt, []),
74+
Labels = replace_host(LabelNames, Host, []),
75+
prometheus_gauge:set(Hook, Labels, Res);
76+
(_) -> noop
77+
end,
78+
maps:get(hooks, Opts)
79+
),
80+
misc:cancel_timer(OldTRef),
81+
TRef = start_timer(),
82+
{noreply, [Host, Opts, TRef]};
83+
handle_info(Info, State) ->
84+
?WARNING_MSG("Unexpected info: ~p", [Info]),
85+
{noreply, State}.
86+
87+
code_change(_OldVsn, State, _Extra) -> {ok, State}.
88+
89+
start_timer() ->
90+
erlang:start_timer(?INTERVAL, self(), ping).
91+
92+
replace_host([], _Host, Res) ->
93+
lists:reverse(Res);
94+
replace_host([host | Args], Host, Res) ->
95+
replace_host(Args, Host, [Host | Res]);
96+
replace_host([Arg | Args], Host, Res) ->
97+
replace_host(Args, Host, [Arg | Res]).
98+
99+
ejabberdctl(Cmd, Args, raw) ->
100+
ejabberd_commands:execute_command2(Cmd, Args, #{caller_module => ejabberd_ctl}, 10000);
101+
ejabberdctl(Cmd, Args, list) ->
102+
length(ejabberdctl(Cmd, Args, raw)).
103+
36104
stop(Host) ->
37105
handle_hooks(get_opt(hooks, Host), Host, unsubscribe, []),
38-
ok.
106+
gen_mod:stop_child(?MODULE, Host).
39107

40108
reload(Host, NewOpts, OldOpts) ->
41109
prometheus_registry:clear(),
42110
handle_hooks(get_opt(hooks, OldOpts), Host, unsubscribe, []),
43111
handle_hooks(get_opt(hooks, NewOpts), Host, subscribe, []),
44112
update_mnesia(get_opt(mnesia, NewOpts)),
45113
update_vm(get_opt(vm, NewOpts)),
114+
Proc = gen_mod:get_module_proc(Host, ?MODULE),
115+
gen_server:cast(Proc, {reload, NewOpts}),
46116
ok.
47117

48118
depends(_Host, _Opts) ->
@@ -66,10 +136,13 @@ mod_opt_type(hooks) ->
66136
econf:options(
67137
#{
68138
hook => econf:atom(),
69-
type => econf:enum([histogram, counter]),
139+
type => econf:enum([histogram, counter, gauge]),
70140
buckets => econf:list(econf:int(0, 150000)),
71141
labels => econf:list(econf:enum([host, stanza, module])),
72142
help => econf:string(),
143+
command => econf:atom(),
144+
args => econf:list(econf:either(host, econf:binary())),
145+
result_type => econf:enum([raw, list]),
73146
collect => econf:either(
74147
all,
75148
econf:list(
@@ -252,7 +325,6 @@ process_counter(
252325
LabelNames = maps:get(labels, State, []),
253326
case lists:member(module, LabelNames) of
254327
true ->
255-
LabelNames = maps:get(labels, State, []),
256328
Labels = replace_labels(LabelNames, Args, Host, Mod),
257329
prometheus_counter:inc(Name, Labels);
258330
_ ->
@@ -293,7 +365,9 @@ handle_hooks([], Host, Action, [{Type, Name, MName, Opts, InitArg} | Metrics]) -
293365
histogram ->
294366
handle_histogram(Name, MName, Opts, Host, Action, InitArg);
295367
counter ->
296-
handle_counter(Name, MName, Opts, Host, Action, InitArg)
368+
handle_counter(Name, MName, Opts, Host, Action, InitArg);
369+
gauge ->
370+
handle_gauge(Name, MName, Opts, Host, Action, InitArg)
297371
end
298372
end,
299373
handle_hooks([], Host, Action, Metrics);
@@ -344,7 +418,9 @@ handle_hook(#{hook := Name}=Opts) ->
344418
}
345419
|| #{module := Mod, function := Func}=Callback <- Callbacks
346420
]
347-
end
421+
end;
422+
gauge ->
423+
[{Type, Name, Name, Opts, #{hook => Name}}]
348424
end.
349425

350426
handle_histogram(Name, HName, HistogramOpts, Host, Action, State) ->
@@ -392,7 +468,22 @@ handle_counter(Name, HName, CounterOpts, Host, Action, State) ->
392468
ejabberd_hooks:unsubscribe(Name, Host, ?MODULE, process_counter, InitArg)
393469
end.
394470

395-
471+
handle_gauge(_Name, GName, GaugeOpts, _Host, Action, _State) ->
472+
LabelNames = maps:get(labels, GaugeOpts, []),
473+
case Action of
474+
subscribe ->
475+
prometheus_gauge:declare(
476+
[{name, GName}, {help, maps:get(help, GaugeOpts, "No help")}, {labels, LabelNames}]
477+
),
478+
?INFO_MSG("Created new Prometheus gauge for ~p with labels ~p", [GName, LabelNames]);
479+
_ ->
480+
try prometheus_gauge:deregister(GName) of
481+
_ ->
482+
?INFO_MSG("Removed Prometheus gauge for ~p with labels ~p", [GName, LabelNames])
483+
catch _:{unknown_metric, _, _} ->
484+
ok
485+
end
486+
end.
396487

397488
duration_histogram_name(Hook) ->
398489
list_to_atom(atom_to_list(Hook) ++ "_duration_milliseconds").

0 commit comments

Comments
 (0)