./ct_report/coverage/service_mongoose_system_metrics.COVER.html

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