./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 -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">>.
Line Hits Source