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