1 |
|
-module(service_mongoose_system_metrics). |
2 |
|
-author('jan.ciesla@erlang-solutions.com'). |
3 |
|
|
4 |
|
-behaviour(mongoose_service). |
5 |
|
-behaviour(gen_server). |
6 |
|
|
7 |
|
-include("mongoose_config_spec.hrl"). |
8 |
|
|
9 |
|
-ifdef(PROD_NODE). |
10 |
|
-define(TRACKING_ID, "UA-151671255-3"). |
11 |
|
-else. |
12 |
|
-define(TRACKING_ID, "UA-151671255-2"). |
13 |
|
-endif. |
14 |
|
-define(TRACKING_ID_CI, "UA-151671255-1"). |
15 |
|
|
16 |
|
-include("mongoose.hrl"). |
17 |
|
|
18 |
|
-export([start/1, stop/0, config_spec/0]). |
19 |
|
-export([start_link/1, |
20 |
|
init/1, |
21 |
|
handle_call/3, |
22 |
|
handle_cast/2, |
23 |
|
handle_info/2, |
24 |
|
terminate/2]). |
25 |
|
|
26 |
|
-export([verify_if_configured/0]). |
27 |
|
|
28 |
|
-ignore_xref([start_link/1]). |
29 |
|
|
30 |
|
-record(system_metrics_state, |
31 |
|
{report_after :: non_neg_integer(), |
32 |
|
reporter_monitor = none :: none | reference(), |
33 |
|
reporter_pid = none :: none | pid(), |
34 |
|
prev_report = [] :: [mongoose_system_metrics_collector:report_struct()], |
35 |
|
tracking_ids :: [tracking_id()]}). |
36 |
|
|
37 |
|
-type system_metrics_state() :: #system_metrics_state{}. |
38 |
|
-type client_id() :: string(). |
39 |
|
-type tracking_id() :: string(). |
40 |
|
|
41 |
|
-spec verify_if_configured() -> ok | ignore. |
42 |
|
verify_if_configured() -> |
43 |
84 |
case mongoose_service:is_loaded(?MODULE) of |
44 |
|
false -> |
45 |
|
%% Technically, notice level. |
46 |
|
%% Though make it louder, in case people set minimum level as warning. |
47 |
1 |
?LOG_WARNING(#{what => system_metrics_disabled, |
48 |
:-( |
text => msg_removed_from_config()}), |
49 |
1 |
ignore; |
50 |
|
true -> |
51 |
83 |
ok |
52 |
|
end. |
53 |
|
|
54 |
|
-spec start(mongoose_service:options()) -> {ok, pid()}. |
55 |
|
start(Opts) -> |
56 |
106 |
Spec = {?MODULE, {?MODULE, start_link, [Opts]}, temporary, brutal_kill, worker, [?MODULE]}, |
57 |
106 |
{ok, _} = ejabberd_sup:start_child(Spec). |
58 |
|
|
59 |
|
-spec stop() -> ok. |
60 |
|
stop() -> |
61 |
107 |
ejabberd_sup:stop_child(?MODULE). |
62 |
|
|
63 |
|
-spec config_spec() -> mongoose_config_spec:config_section(). |
64 |
|
config_spec() -> |
65 |
83 |
#section{ |
66 |
|
items = #{<<"initial_report">> => #option{type = integer, |
67 |
|
validate = non_negative}, |
68 |
|
<<"periodic_report">> => #option{type = integer, |
69 |
|
validate = non_negative}, |
70 |
|
<<"report">> => #option{type = boolean}, |
71 |
|
<<"tracking_id">> => #option{type = string, |
72 |
|
validate = non_empty} |
73 |
|
}, |
74 |
|
defaults = #{<<"initial_report">> => timer:minutes(5), |
75 |
|
<<"periodic_report">> => timer:hours(3)} |
76 |
|
}. |
77 |
|
|
78 |
|
-spec start_link(mongoose_service:options()) -> {ok, pid()}. |
79 |
|
start_link(Opts) -> |
80 |
106 |
gen_server:start_link({local, ?MODULE}, ?MODULE, Opts, []). |
81 |
|
|
82 |
|
-spec init(mongoose_service:options()) -> {ok, system_metrics_state()}. |
83 |
|
init(Opts) -> |
84 |
106 |
case report_transparency(Opts) of |
85 |
1 |
skip -> ignore; |
86 |
|
continue -> |
87 |
105 |
#{initial_report := InitialReport, periodic_report := PeriodicReport} = Opts, |
88 |
105 |
TrackingIds = tracking_ids(Opts), |
89 |
105 |
erlang:send_after(InitialReport, self(), spawn_reporter), |
90 |
105 |
{ok, #system_metrics_state{report_after = PeriodicReport, |
91 |
|
tracking_ids = TrackingIds}} |
92 |
|
end. |
93 |
|
|
94 |
|
handle_info(spawn_reporter, #system_metrics_state{report_after = ReportAfter, |
95 |
|
reporter_monitor = none, |
96 |
|
reporter_pid = none, |
97 |
|
prev_report = PrevReport, |
98 |
|
tracking_ids = TrackingIds} = State) -> |
99 |
35 |
ServicePid = self(), |
100 |
35 |
case get_client_id() of |
101 |
|
{ok, ClientId} -> |
102 |
35 |
{Pid, Monitor} = spawn_monitor( |
103 |
|
fun() -> |
104 |
35 |
Reports = mongoose_system_metrics_collector:collect(PrevReport), |
105 |
35 |
mongoose_system_metrics_sender:send(ClientId, Reports, TrackingIds), |
106 |
35 |
mongoose_system_metrics_file:save(Reports), |
107 |
35 |
ServicePid ! {prev_report, Reports} |
108 |
|
end), |
109 |
35 |
erlang:send_after(ReportAfter, self(), spawn_reporter), |
110 |
35 |
{noreply, State#system_metrics_state{reporter_monitor = Monitor, |
111 |
|
reporter_pid = Pid}}; |
112 |
:-( |
{error, _} -> {stop, no_client_id, State} |
113 |
|
end; |
114 |
|
handle_info(spawn_reporter, #system_metrics_state{reporter_pid = Pid} = State) -> |
115 |
:-( |
exit(Pid, kill), |
116 |
:-( |
self() ! spawn_reporter, |
117 |
:-( |
{noreply, State#system_metrics_state{reporter_monitor = none, reporter_pid = none}}; |
118 |
|
handle_info({'DOWN', CollectorMonitor, _, _, _}, |
119 |
|
#system_metrics_state{reporter_monitor = CollectorMonitor} = State) -> |
120 |
21 |
{noreply, State#system_metrics_state{reporter_monitor = none, reporter_pid = none}}; |
121 |
|
handle_info({prev_report, Report}, State) -> |
122 |
21 |
{noreply, State#system_metrics_state{prev_report = Report}}; |
123 |
|
handle_info(_Message, State) -> |
124 |
:-( |
{noreply, State}. |
125 |
|
|
126 |
|
|
127 |
|
% %%----------------------------------------- |
128 |
|
% %% Helpers |
129 |
|
% %%----------------------------------------- |
130 |
|
|
131 |
|
-spec get_client_id() -> {ok, client_id()} | {error, any()}. |
132 |
|
get_client_id() -> |
133 |
35 |
case mongoose_cluster_id:get_cached_cluster_id() of |
134 |
:-( |
{error, _} = Err -> Err; |
135 |
35 |
{ok, ID} when is_binary(ID) -> {ok, binary_to_list(ID)} |
136 |
|
end. |
137 |
|
|
138 |
|
-spec tracking_ids(mongoose_service:options()) -> [tracking_id()]. |
139 |
|
tracking_ids(#{tracking_id := ExtraTrackingId}) -> |
140 |
1 |
[predefined_tracking_id(), ExtraTrackingId]; |
141 |
|
tracking_ids(#{}) -> |
142 |
104 |
[predefined_tracking_id()]. |
143 |
|
|
144 |
|
-spec predefined_tracking_id() -> tracking_id(). |
145 |
|
predefined_tracking_id() -> |
146 |
105 |
case os:getenv("CI") of |
147 |
105 |
"true" -> ?TRACKING_ID_CI; |
148 |
:-( |
_ -> ?TRACKING_ID |
149 |
|
end. |
150 |
|
|
151 |
|
-spec report_transparency(mongoose_service:options()) -> skip | continue. |
152 |
|
report_transparency(#{report := false}) -> |
153 |
1 |
skip; |
154 |
|
report_transparency(#{report := true}) -> |
155 |
1 |
continue; |
156 |
|
report_transparency(#{}) -> |
157 |
104 |
File = mongoose_system_metrics_file:location(), |
158 |
104 |
Text = iolist_to_binary(io_lib:format(msg_accept_terms_and_conditions(), [File])), |
159 |
104 |
?LOG_WARNING(#{what => report_transparency, text => Text, report_filename => File}), |
160 |
104 |
continue. |
161 |
|
|
162 |
|
% %%----------------------------------------- |
163 |
|
% %% Unused |
164 |
|
% %%----------------------------------------- |
165 |
|
|
166 |
|
handle_cast(_Request, State) -> |
167 |
:-( |
{noreply, State}. |
168 |
|
handle_call(_Request, _From, State) -> |
169 |
:-( |
{reply, ok, State}. |
170 |
|
terminate(_Reason, _State) -> |
171 |
:-( |
ok. |
172 |
|
|
173 |
|
%%----------------------------------------- |
174 |
|
%% Internal |
175 |
|
%%----------------------------------------- |
176 |
|
msg_removed_from_config() -> |
177 |
1 |
<<"We're sorry to hear you don't want to share the system's metrics with us. " |
178 |
|
"These metrics would enable us to improve MongooseIM and know where to focus our efforts. " |
179 |
|
"To stop being notified, you can add this to the services section of your config file: \n" |
180 |
|
" [services.service_mongoose_system_metrics]\n" |
181 |
|
" report = false\n" |
182 |
|
"For more info on how to customise, read, enable, and disable the metrics visit: \n" |
183 |
|
"- MongooseIM docs - \n" |
184 |
|
" https://esl.github.io/MongooseDocs/latest/operation-and-maintenance/System-Metrics-Privacy-Policy/ \n" |
185 |
|
"- MongooseIM GitHub page - https://github.com/esl/MongooseIM">>. |
186 |
|
|
187 |
|
msg_accept_terms_and_conditions() -> |
188 |
104 |
<<"We are gathering the MongooseIM system's metrics to analyse the trends and needs of our users, " |
189 |
|
"improve MongooseIM, and know where to focus our efforts. " |
190 |
|
"For more info on how to customise, read, enable, and disable these metrics visit: \n" |
191 |
|
"- MongooseIM docs - \n" |
192 |
|
" https://esl.github.io/MongooseDocs/latest/operation-and-maintenance/System-Metrics-Privacy-Policy/ \n" |
193 |
|
"- MongooseIM GitHub page - https://github.com/esl/MongooseIM \n" |
194 |
|
"The last sent report is also written to a file ~s">>. |