]>
Commit | Line | Data |
---|---|---|
3dfe317a DM |
1 | package PMG::CLI::pmgreport; |
2 | ||
3 | use strict; | |
4 | use Data::Dumper; | |
5 | use Template; | |
6 | use POSIX qw(strftime); | |
7 | ||
8 | use PVE::INotify; | |
9 | use PVE::CLIHandler; | |
10 | ||
11 | use PMG::Utils; | |
12 | use PMG::Config; | |
13 | use PMG::RESTEnvironment; | |
308c2bfc | 14 | |
3dfe317a | 15 | use PMG::API2::Nodes; |
308c2bfc DM |
16 | use PMG::ClusterConfig; |
17 | use PMG::Cluster; | |
18 | use PMG::API2::Cluster; | |
02251e49 DM |
19 | use PMG::RuleDB; |
20 | use PMG::Statistic; | |
3dfe317a DM |
21 | |
22 | use base qw(PVE::CLIHandler); | |
23 | ||
24 | my $nodename = PVE::INotify::nodename(); | |
25 | ||
26 | sub setup_environment { | |
27 | PMG::RESTEnvironment->setup_default_cli_env(); | |
308c2bfc DM |
28 | |
29 | my $rpcenv = PMG::RESTEnvironment->get(); | |
30 | # API /config/cluster/nodes need a ticket to connect to other nodes | |
31 | my $ticket = PMG::Ticket::assemble_ticket('root@pam'); | |
32 | $rpcenv->set_ticket($ticket); | |
3dfe317a DM |
33 | } |
34 | ||
35 | my $get_system_table_data = sub { | |
36 | ||
37 | my $ni = PMG::API2::NodeInfo->status({ node => $nodename }); | |
38 | ||
39 | my $data = []; | |
40 | ||
41 | push @$data, { text => 'Hostname', value => $nodename }; | |
42 | ||
43 | my $uptime = $ni->{uptime} ? PMG::Utils::format_uptime($ni->{uptime}) : '-'; | |
44 | ||
45 | push @$data, { text => 'Uptime', value => $uptime }; | |
46 | ||
47 | push @$data, { text => 'Version', value => $ni->{pmgversion} }; | |
48 | ||
49 | my $loadavg15 = '-'; | |
50 | if (my $d = $ni->{loadavg}) { | |
308c2bfc | 51 | $loadavg15 = sprintf("%.2f", $d->[2]); |
3dfe317a DM |
52 | } |
53 | push @$data, { text => 'Load', value => $loadavg15 }; | |
54 | ||
55 | my $mem = '-'; | |
56 | if (my $d = $ni->{memory}) { | |
57 | $mem = sprintf("%.2f%%", $d->{used}*100/$d->{total}); | |
58 | } | |
59 | push @$data, { text => 'Memory', value => $mem }; | |
60 | ||
61 | my $disk = '-'; | |
62 | if (my $d = $ni->{rootfs}) { | |
63 | $disk = sprintf("%.2f%%", $d->{used}*100/$d->{total}); | |
64 | } | |
65 | push @$data, { text => 'Disk', value => $disk }; | |
66 | ||
67 | return $data | |
68 | }; | |
69 | ||
308c2bfc DM |
70 | my $get_cluster_table_data = sub { |
71 | ||
72 | my $res = PMG::API2::Cluster->status({}); | |
73 | return undef if !scalar(@$res); | |
74 | ||
75 | my $data = []; | |
76 | ||
77 | foreach my $ni (@$res) { | |
78 | my $state = 'A'; | |
79 | $state = 'S' if !$ni->{insync}; | |
80 | ||
81 | my $loadavg1 = '-'; | |
82 | if (my $d = $ni->{loadavg}) { | |
83 | $loadavg1 = sprintf("%.2f", $d->[0]); | |
84 | } | |
85 | ||
86 | my $memory = '-'; | |
87 | if (my $d = $ni->{memory}) { | |
88 | $memory = sprintf("%.2f%%", $d->{used}*100/$d->{total}); | |
89 | } | |
90 | ||
91 | my $disk = '-'; | |
92 | if (my $d = $ni->{rootfs}) { | |
93 | $disk = sprintf("%.2f%%", $d->{used}*100/$d->{total}); | |
94 | } | |
95 | ||
96 | push @$data, { | |
97 | hostname => $ni->{name}, | |
98 | ip => $ni->{ip}, | |
99 | type => $ni->{type}, | |
100 | state => $state, | |
101 | loadavg1 => $loadavg1, | |
102 | memory => $memory, | |
103 | disk => $disk, | |
104 | }; | |
105 | }; | |
106 | ||
107 | return $data; | |
108 | }; | |
3dfe317a | 109 | |
30898745 DM |
110 | my $get_incoming_table_data = sub { |
111 | my ($totals) = @_; | |
112 | ||
113 | my $data = []; | |
114 | ||
115 | push @$data, { | |
116 | text => 'Incoming Mails', | |
117 | value => $totals->{count_in}, | |
118 | percentage => $totals->{count_in_per}, | |
119 | }; | |
120 | ||
121 | push @$data, { | |
122 | text => 'Spam Mails', | |
123 | value => $totals->{spamcount_in}, | |
124 | percentage => $totals->{spamcount_in_per}, | |
125 | }; | |
126 | ||
127 | push @$data, { | |
128 | text => 'Virus Mails', | |
129 | value => $totals->{viruscount_in}, | |
130 | percentage => $totals->{viruscount_in_per}, | |
131 | }; | |
132 | ||
133 | push @$data, { | |
134 | text => 'SPF rejects', | |
135 | value => $totals->{spfcount}, | |
136 | percentage => $totals->{spfcount_per}, | |
137 | }; | |
138 | ||
139 | push @$data, { | |
140 | text => 'Mail Traffic', | |
141 | value => sprintf ("%.3f MByte", $totals->{traffic_in}), | |
142 | }; | |
143 | ||
144 | return $data; | |
145 | }; | |
146 | ||
147 | my $get_outgoing_table_data = sub { | |
148 | my ($totals) = @_; | |
149 | ||
150 | my $data = []; | |
151 | ||
152 | push @$data, { | |
153 | text => 'Outgoing Mails', | |
154 | value => $totals->{count_out}, | |
155 | percentage => $totals->{count_out_per}, | |
156 | }; | |
157 | ||
158 | push @$data, { | |
159 | text => 'Bounces', | |
160 | value => $totals->{bounces_out}, | |
161 | percentage => $totals->{bounces_out_per}, | |
162 | }; | |
163 | ||
164 | push @$data, { | |
165 | text => 'Mail Traffic', | |
166 | value => sprintf ("%.3f MByte", $totals->{traffic_out}), | |
167 | }; | |
168 | ||
169 | return $data; | |
170 | }; | |
171 | ||
d68581ee DM |
172 | my $get_virus_table_data = sub { |
173 | my ($virusinfo) = @_; | |
174 | ||
175 | my $data = []; | |
176 | ||
177 | foreach my $entry (@$virusinfo) { | |
178 | next if !$entry->{count}; | |
179 | last if scalar(@$data) >= 10; | |
180 | push @$data, { name => $entry->{name}, count => $entry->{count} }; | |
181 | } | |
182 | ||
183 | return undef if !scalar(@$data); | |
184 | ||
185 | return $data; | |
186 | }; | |
187 | ||
68b5b6c1 DM |
188 | my $get_quarantine_table_data = sub { |
189 | my ($dbh, $qtype) = @_; | |
190 | ||
191 | # Note;: We try to estimate used disk space - each mail | |
192 | # is stored in an extra file ... | |
193 | ||
194 | my $bs = 4096; | |
195 | ||
196 | my $sth = $dbh->prepare( | |
197 | "SELECT count(ID) as count, sum (ceil((Bytes+$bs-1)/$bs)*$bs) / (1024*1024) as mbytes, " . | |
198 | "avg(Bytes) as avgbytes, avg(Spamlevel) as avgspam " . | |
199 | "FROM CMailStore WHERE QType = ?"); | |
200 | ||
201 | $sth->execute($qtype); | |
202 | ||
203 | my $ref = $sth->fetchrow_hashref(); | |
204 | ||
205 | $sth->finish; | |
206 | ||
207 | return undef if !($ref && $ref->{count}); | |
208 | ||
209 | my $data = []; | |
210 | ||
211 | push @$data, { | |
212 | text => "Quarantine Size (MBytes)", | |
213 | value => int($ref->{mbytes}), | |
214 | }; | |
215 | ||
216 | push @$data, { | |
217 | text => "Number of Mails", | |
218 | value => $ref->{count}, | |
219 | }; | |
220 | ||
221 | push @$data, { | |
222 | text => "Average Size (Bytes)", | |
223 | value => int($ref->{avgbytes}), | |
224 | }; | |
225 | ||
226 | if ($qtype eq 'S') { | |
227 | push @$data, { | |
228 | text => "Average Spam Level", | |
229 | value => int($ref->{avgspam}), | |
230 | }; | |
231 | } | |
232 | ||
233 | return $data; | |
234 | }; | |
235 | ||
3dfe317a DM |
236 | __PACKAGE__->register_method ({ |
237 | name => 'pmgreport', | |
238 | path => 'pmgreport', | |
239 | method => 'POST', | |
240 | description => "Generate and send daily system report email.", | |
241 | parameters => { | |
242 | additionalProperties => 0, | |
308c2bfc DM |
243 | properties => { |
244 | debug => { | |
245 | description => "Debug mode. Print raw email to stdout instead of sending them.", | |
246 | type => 'boolean', | |
247 | optional => 1, | |
248 | default => 0, | |
249 | }, | |
250 | auto => { | |
251 | description => "Auto mode. Use setting from system configuration (set when invoked fron cron).", | |
252 | type => 'boolean', | |
253 | optional => 1, | |
254 | default => 0, | |
255 | }, | |
256 | receiver => { | |
257 | description => "Send report to this email address. Default is the administratior email address.", | |
258 | type => 'string', format => 'email', | |
259 | optional => 1, | |
260 | }, | |
2f7031b7 DM |
261 | timespan => { |
262 | description => "Select time span for included email statistics.\n\nNOTE: System and cluster performance data is always from current time (when script is run).", | |
263 | type => 'string', | |
264 | enum => ['today', 'yesterday'], | |
265 | default => 'today', | |
266 | optional => 1, | |
267 | }, | |
308c2bfc | 268 | }, |
3dfe317a DM |
269 | }, |
270 | returns => { type => 'null'}, | |
271 | code => sub { | |
272 | my ($param) = @_; | |
273 | ||
2f7031b7 DM |
274 | my $timespan = $param->{timespan} // 'today'; |
275 | my ($start, $end) = PMG::Utils::lookup_timespan($timespan); | |
3dfe317a | 276 | |
2f7031b7 | 277 | my $fqdn = PVE::Tools::get_fqdn($nodename); |
3dfe317a DM |
278 | |
279 | my $vars = { | |
280 | hostname => $nodename, | |
281 | fqdn => $fqdn, | |
282 | date => strftime("%F", localtime($end - 1)), | |
283 | }; | |
284 | ||
308c2bfc DM |
285 | my $cinfo = PMG::ClusterConfig->new(); |
286 | my $role = $cinfo->{local}->{type} // '-'; | |
287 | ||
288 | if ($role eq '-') { | |
289 | $vars->{system} = $get_system_table_data->(); | |
290 | } else { | |
291 | $vars->{cluster} = $get_cluster_table_data->(); | |
292 | if ($role eq 'master') { | |
293 | # OK | |
294 | } else { | |
295 | return undef if $param->{auto}; # silent exit - do not send report | |
296 | } | |
297 | } | |
3dfe317a | 298 | |
02251e49 DM |
299 | |
300 | my $stat = PMG::Statistic->new ($start, $end); | |
301 | my $rdb = PMG::RuleDB->new(); | |
302 | ||
303 | # update statistics | |
304 | PMG::Statistic::update_stats($rdb->{dbh}, $cinfo); | |
305 | ||
306 | my $totals = $stat->total_mail_stat($rdb); | |
02251e49 | 307 | |
30898745 | 308 | $vars->{incoming} = $get_incoming_table_data->($totals); |
02251e49 | 309 | |
30898745 | 310 | $vars->{outgoing} = $get_outgoing_table_data->($totals); |
02251e49 | 311 | |
d68581ee DM |
312 | my $virusinfo = $stat->total_virus_stat ($rdb); |
313 | if (my $data = $get_virus_table_data->($virusinfo)) { | |
314 | $vars->{virusstat} = $data; | |
315 | } | |
316 | ||
68b5b6c1 DM |
317 | if (my $data = $get_quarantine_table_data->($rdb->{dbh}, 'V')) { |
318 | $vars->{virusquar} = $data; | |
319 | } | |
320 | ||
321 | if (my $data = $get_quarantine_table_data->($rdb->{dbh}, 'S')) { | |
322 | $vars->{spamquar} = $data; | |
323 | } | |
324 | ||
3dfe317a DM |
325 | my $tt = PMG::Config::get_template_toolkit(); |
326 | ||
327 | my $cfg = PMG::Config->new(); | |
308c2bfc | 328 | my $email = $param->{receiver} // $cfg->get ('admin', 'email'); |
3dfe317a DM |
329 | |
330 | if (!defined($email)) { | |
308c2bfc DM |
331 | return undef if $param->{auto}; # silent exit - do not send report |
332 | die "no receiver configured\n"; | |
3dfe317a DM |
333 | } |
334 | ||
335 | my $mailfrom = "Proxmox Mail Gateway <postmaster>"; | |
336 | PMG::Utils::finalize_report($tt, 'pmgreport.tt', $vars, $mailfrom, $email, $param->{debug}); | |
337 | ||
338 | return undef; | |
339 | }}); | |
340 | ||
341 | our $cmddef = [ __PACKAGE__, 'pmgreport', [], undef ]; | |
342 | ||
343 | 1; |