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 |
77 |
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 |
76 |
ok |
52 |
|
end. |
53 |
|
|
54 |
|
-spec start(mongoose_service:options()) -> {ok, pid()}. |
55 |
|
start(Opts) -> |
56 |
99 |
Spec = {?MODULE, {?MODULE, start_link, [Opts]}, temporary, brutal_kill, worker, [?MODULE]}, |
57 |
99 |
{ok, _} = ejabberd_sup:start_child(Spec). |
58 |
|
|
59 |
|
-spec stop() -> ok. |
60 |
|
stop() -> |
61 |
100 |
ejabberd_sup:stop_child(?MODULE). |
62 |
|
|
63 |
|
-spec config_spec() -> mongoose_config_spec:config_section(). |
64 |
|
config_spec() -> |
65 |
76 |
#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 |
|
format_items = map |
77 |
|
}. |
78 |
|
|
79 |
|
-spec start_link(mongoose_service:options()) -> {ok, pid()}. |
80 |
|
start_link(Opts) -> |
81 |
99 |
gen_server:start_link({local, ?MODULE}, ?MODULE, Opts, []). |
82 |
|
|
83 |
|
-spec init(mongoose_service:options()) -> {ok, system_metrics_state()}. |
84 |
|
init(Opts) -> |
85 |
99 |
case report_transparency(Opts) of |
86 |
1 |
skip -> ignore; |
87 |
|
continue -> |
88 |
98 |
#{initial_report := InitialReport, periodic_report := PeriodicReport} = Opts, |
89 |
98 |
TrackingIds = tracking_ids(Opts), |
90 |
98 |
erlang:send_after(InitialReport, self(), spawn_reporter), |
91 |
98 |
{ok, #system_metrics_state{report_after = PeriodicReport, |
92 |
|
tracking_ids = TrackingIds}} |
93 |
|
end. |
94 |
|
|
95 |
|
handle_info(spawn_reporter, #system_metrics_state{report_after = ReportAfter, |
96 |
|
reporter_monitor = none, |
97 |
|
reporter_pid = none, |
98 |
|
prev_report = PrevReport, |
99 |
|
tracking_ids = TrackingIds} = State) -> |
100 |
33 |
ServicePid = self(), |
101 |
33 |
case get_client_id() of |
102 |
|
{ok, ClientId} -> |
103 |
33 |
{Pid, Monitor} = spawn_monitor( |
104 |
|
fun() -> |
105 |
33 |
Reports = mongoose_system_metrics_collector:collect(PrevReport), |
106 |
33 |
mongoose_system_metrics_sender:send(ClientId, Reports, TrackingIds), |
107 |
33 |
mongoose_system_metrics_file:save(Reports), |
108 |
33 |
ServicePid ! {prev_report, Reports} |
109 |
|
end), |
110 |
33 |
erlang:send_after(ReportAfter, self(), spawn_reporter), |
111 |
33 |
{noreply, State#system_metrics_state{reporter_monitor = Monitor, |
112 |
|
reporter_pid = Pid}}; |
113 |
:-( |
{error, _} -> {stop, no_client_id, State} |
114 |
|
end; |
115 |
|
handle_info(spawn_reporter, #system_metrics_state{reporter_pid = Pid} = State) -> |
116 |
:-( |
exit(Pid, kill), |
117 |
:-( |
self() ! spawn_reporter, |
118 |
:-( |
{noreply, State#system_metrics_state{reporter_monitor = none, reporter_pid = none}}; |
119 |
|
handle_info({'DOWN', CollectorMonitor, _, _, _}, |
120 |
|
#system_metrics_state{reporter_monitor = CollectorMonitor} = State) -> |
121 |
18 |
{noreply, State#system_metrics_state{reporter_monitor = none, reporter_pid = none}}; |
122 |
|
handle_info({prev_report, Report}, State) -> |
123 |
18 |
{noreply, State#system_metrics_state{prev_report = Report}}; |
124 |
|
handle_info(_Message, State) -> |
125 |
:-( |
{noreply, State}. |
126 |
|
|
127 |
|
|
128 |
|
% %%----------------------------------------- |
129 |
|
% %% Helpers |
130 |
|
% %%----------------------------------------- |
131 |
|
|
132 |
|
-spec get_client_id() -> {ok, client_id()} | {error, any()}. |
133 |
|
get_client_id() -> |
134 |
33 |
case mongoose_cluster_id:get_cached_cluster_id() of |
135 |
:-( |
{error, _} = Err -> Err; |
136 |
33 |
{ok, ID} when is_binary(ID) -> {ok, binary_to_list(ID)} |
137 |
|
end. |
138 |
|
|
139 |
|
-spec tracking_ids(mongoose_service:options()) -> [tracking_id()]. |
140 |
|
tracking_ids(#{tracking_id := ExtraTrackingId}) -> |
141 |
1 |
[predefined_tracking_id(), ExtraTrackingId]; |
142 |
|
tracking_ids(#{}) -> |
143 |
97 |
[predefined_tracking_id()]. |
144 |
|
|
145 |
|
-spec predefined_tracking_id() -> tracking_id(). |
146 |
|
predefined_tracking_id() -> |
147 |
98 |
case os:getenv("CI") of |
148 |
98 |
"true" -> ?TRACKING_ID_CI; |
149 |
:-( |
_ -> ?TRACKING_ID |
150 |
|
end. |
151 |
|
|
152 |
|
-spec report_transparency(mongoose_service:options()) -> skip | continue. |
153 |
|
report_transparency(#{report := false}) -> |
154 |
1 |
skip; |
155 |
|
report_transparency(#{report := true}) -> |
156 |
1 |
continue; |
157 |
|
report_transparency(#{}) -> |
158 |
97 |
File = mongoose_system_metrics_file:location(), |
159 |
97 |
Text = iolist_to_binary(io_lib:format(msg_accept_terms_and_conditions(), [File])), |
160 |
97 |
?LOG_WARNING(#{what => report_transparency, text => Text, report_filename => File}), |
161 |
97 |
continue. |
162 |
|
|
163 |
|
% %%----------------------------------------- |
164 |
|
% %% Unused |
165 |
|
% %%----------------------------------------- |
166 |
|
|
167 |
|
handle_cast(_Request, State) -> |
168 |
:-( |
{noreply, State}. |
169 |
|
handle_call(_Request, _From, State) -> |
170 |
:-( |
{reply, ok, State}. |
171 |
|
terminate(_Reason, _State) -> |
172 |
:-( |
ok. |
173 |
|
|
174 |
|
%%----------------------------------------- |
175 |
|
%% Internal |
176 |
|
%%----------------------------------------- |
177 |
|
msg_removed_from_config() -> |
178 |
1 |
<<"We're sorry to hear you don't want to share the system's metrics with us. " |
179 |
|
"These metrics would enable us to improve MongooseIM and know where to focus our efforts. " |
180 |
|
"To stop being notified, you can add this to the services section of your config file: \n" |
181 |
|
" [services.service_mongoose_system_metrics]\n" |
182 |
|
" report = false\n" |
183 |
|
"For more info on how to customise, read, enable, and disable the metrics visit: \n" |
184 |
|
"- MongooseIM docs - \n" |
185 |
|
" https://esl.github.io/MongooseDocs/latest/operation-and-maintenance/System-Metrics-Privacy-Policy/ \n" |
186 |
|
"- MongooseIM GitHub page - https://github.com/esl/MongooseIM">>. |
187 |
|
|
188 |
|
msg_accept_terms_and_conditions() -> |
189 |
97 |
<<"We are gathering the MongooseIM system's metrics to analyse the trends and needs of our users, " |
190 |
|
"improve MongooseIM, and know where to focus our efforts. " |
191 |
|
"For more info on how to customise, read, enable, and disable these metrics visit: \n" |
192 |
|
"- MongooseIM docs - \n" |
193 |
|
" https://esl.github.io/MongooseDocs/latest/operation-and-maintenance/System-Metrics-Privacy-Policy/ \n" |
194 |
|
"- MongooseIM GitHub page - https://github.com/esl/MongooseIM \n" |
195 |
|
"The last sent report is also written to a file ~s">>. |