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
2127start (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+
36104stop (Host ) ->
37105 handle_hooks (get_opt (hooks , Host ), Host , unsubscribe , []),
38- ok .
106+ gen_mod : stop_child ( ? MODULE , Host ) .
39107
40108reload (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
48118depends (_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
350426handle_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
397488duration_histogram_name (Hook ) ->
398489 list_to_atom (atom_to_list (Hook ) ++ " _duration_milliseconds" ).
0 commit comments