6 Bweb - A Bacula web interface
7 Bacula® - The Network Backup Solution
9 Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
11 The main author of Bweb is Eric Bollengier.
12 The main author of Bacula is Kern Sibbald, with contributions from
13 many others, a complete list can be found in the file AUTHORS.
15 This program is Free Software; you can redistribute it and/or
16 modify it under the terms of version two of the GNU General Public
17 License as published by the Free Software Foundation plus additions
18 that are listed in the file LICENSE.
20 This program is distributed in the hope that it will be useful, but
21 WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 General Public License for more details.
25 You should have received a copy of the GNU General Public License
26 along with this program; if not, write to the Free Software
27 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
30 Bacula® is a registered trademark of John Walker.
31 The licensor of Bacula is the Free Software Foundation Europe
32 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich,
33 Switzerland, email:ftf@fsfeurope.org.
46 use POSIX qw/strftime/;
47 use File::Basename qw/basename dirname/;
49 my $conf = new Bweb::Config(config_file => $Bweb::config_file);
51 my $bweb = new Bweb(info => $conf);
53 my $dbh = $bweb->{dbh};
54 my $debug = $bweb->{debug};
56 # Job table keep use Media or Job retention, so it's quite enought
58 # CREATE TABLE job_old (LIKE Job);
60 # (SELECT * FROM Job WHERE JobId NOT IN (SELECT JobId FROM job_old) );
61 my $jobt = $conf->{stat_job_table} || 'Job';
63 my $graph = CGI::param('graph') || 'job_size';
64 my $legend = CGI::param('legend') || 'on' ;
65 $legend = ($legend eq 'on')?1:0;
67 my $arg = $bweb->get_form(qw/width height limit offset age where jobid
68 jfilesets level status jjobnames jclients jclient_groups/);
70 my ($limitq, $label) = $bweb->get_limit(age => $arg->{age},
71 limit => $arg->{limit},
72 offset=> $arg->{offset},
73 order => "$jobt.StartTime ASC",
77 if ($arg->{status} and $arg->{status} ne 'Any') {
78 $statusq = " AND $jobt.JobStatus = '$arg->{status}' ";
82 if ($arg->{level} and $arg->{level} ne 'Any') {
83 $levelq = " AND $jobt.Level = '$arg->{level}' ";
87 if ($arg->{jfilesets}) {
88 $filesetq = " AND FileSet.FileSet IN ($arg->{qfilesets}) ";
92 if ($arg->{jjobnames}) {
93 $jobnameq = " AND $jobt.Name IN ($arg->{jjobnames}) ";
95 $arg->{jjobnames} = 'all'; # skip warning
99 if ($arg->{jclients}) {
100 $clientq = " AND Client.Name IN ($arg->{jclients}) ";
102 $arg->{jclients} = 'all'; # skip warning
105 my $groupf=''; # from clause
106 my $groupq=''; # whre clause
107 if ($arg->{jclient_groups}) {
108 $groupf = " JOIN client_group_member ON (Client.ClientId = client_group_member.clientid)
109 JOIN client_group USING (client_group_id)";
110 $groupq = " AND client_group_name IN ($arg->{jclient_groups}) ";
113 my $gtype = CGI::param('gtype') || 'bars';
115 print CGI::header('image/png');
121 if ($gtype eq 'lines') {
122 use GD::Graph::lines;
123 $graph = GD::Graph::lines->new ( $arg->{width}, $arg->{height} );
125 } elsif ($gtype eq 'bars') {
127 $graph = GD::Graph::bars->new ( $arg->{width}, $arg->{height} );
129 } elsif ($gtype eq 'linespoints') {
130 use GD::Graph::linespoints;
131 $graph = GD::Graph::linespoints->new ( $arg->{width}, $arg->{height} );
133 # this doesnt works at this time
134 # } elsif ($gtype eq 'bars3d') {
135 # use GD::Graph::bars3d;
136 # $graph = GD::Graph::bars3d->new ( $arg->{width}, $arg->{height} );
142 $graph->set('x_label' => 'Time',
143 'x_number_format' => sub { strftime('%D', localtime($_[0])) },
144 'x_tick_number' => 1,
160 foreach my $row (@$all_row) {
161 my $label = $row->[1] . "/" . $row->[2] ; # client/backup name
163 $ret->{date}->[$i] = $row->[0];
164 $ret->{$label}->[$i] = $row->[3];
166 $last_date = $row->[0];
169 # insert a fake element
170 foreach my $elt ( keys %{$ret}) {
171 $ret->{$elt}->[$i] = undef;
174 $ret->{date}->[$i] = $last_date + 1;
176 my $date = $ret->{date} ;
179 return ($date, $ret);
191 foreach my $row (@$all_row) {
192 $ret->{date}->[$i] = $row->[0];
193 $ret->{nb}->[$i] = $row->[1];
200 if ($graph eq 'job_size') {
204 UNIX_TIMESTAMP($jobt.StartTime) AS starttime,
205 Client.Name AS clientname,
206 $jobt.Name AS jobname,
207 $jobt.JobBytes AS jobbytes
208 FROM $jobt, FileSet, Client $groupf
209 WHERE $jobt.ClientId = Client.ClientId
210 AND $jobt.FileSetId = FileSet.FileSetId
221 print STDERR $query if ($debug);
223 my $obj = get_graph('title' => "Job Size : $arg->{jclients}/$arg->{jjobnames}",
226 'y_number_format' => \&Bweb::human_size,
229 my $all = $dbh->selectall_arrayref($query) ;
231 my ($d, $ret) = make_tab($all);
233 $obj->set_legend(keys %$ret);
235 print $obj->plot([$d, values %$ret])->png;
238 if ($graph eq 'job_file') {
242 UNIX_TIMESTAMP($jobt.StartTime) AS starttime,
243 Client.Name AS clientname,
244 $jobt.Name AS jobname,
245 $jobt.JobFiles AS jobfiles
246 FROM $jobt, FileSet, Client $groupf
247 WHERE $jobt.ClientId = Client.ClientId
248 AND $jobt.FileSetId = FileSet.FileSetId
259 print STDERR $query if ($debug);
261 my $obj = get_graph('title' => "Job Files : $arg->{jclients}/$arg->{jjobnames}",
262 'y_label' => 'Number Files',
266 my $all = $dbh->selectall_arrayref($query) ;
268 my ($d, $ret) = make_tab($all);
270 $obj->set_legend(keys %$ret);
272 print $obj->plot([$d, values %$ret])->png;
275 # it works only with postgresql at this time
276 # we dont use $jobt because we use File, so job is in Job table
277 elsif ($graph eq 'file_histo' and $arg->{where}) {
279 my $dir = $dbh->quote(dirname($arg->{where}) . '/');
280 my $file = $dbh->quote(basename($arg->{where}));
283 SELECT UNIX_TIMESTAMP(Job.StartTime) AS starttime,
284 Client.Name AS client,
286 base64_decode_lstat(8,LStat) AS lstat
288 FROM Job, FileSet, Filename, Path, File, Client
289 WHERE Job.ClientId = Client.ClientId
290 AND Job.FileSetId = FileSet.FileSetId
292 AND File.JobId = Job.JobId
293 AND File.FilenameId = Filename.FilenameId
294 AND File.PathId = Path.PathId
296 AND Filename.Name = $file
305 print STDERR $query if ($debug);
307 my $all = $dbh->selectall_arrayref($query) ;
309 my $obj = get_graph('title' => "File size : $arg->{where}",
310 'y_label' => 'File size',
313 'y_number_format' => \&Bweb::human_size,
317 my ($d, $ret) = make_tab($all);
319 $obj->set_legend(keys %$ret);
321 print $obj->plot([$d, values %$ret])->png;
324 # it works only with postgresql at this time
325 # TODO: use brestore_missing_path
326 elsif ($graph eq 'rep_histo' and $arg->{where}) {
328 my $dir = $arg->{where};
329 $dir .= '/' if ($dir !~ m!/$!);
330 $dir = $dbh->quote($dir);
333 SELECT UNIX_TIMESTAMP(Job.StartTime) AS starttime,
334 Client.Name AS client,
336 brestore_pathvisibility.size AS size
338 FROM Job, Client, FileSet, Path, brestore_pathvisibility
339 WHERE Job.ClientId = Client.ClientId
340 AND Job.FileSetId = FileSet.FileSetId
342 AND Job.JobId = brestore_pathvisibility.JobId
343 AND Path.PathId = brestore_pathvisibility.PathId
353 print STDERR $query if ($debug);
355 my $all = $dbh->selectall_arrayref($query) ;
357 my $obj = get_graph('title' => "Directory size : $arg->{where}",
358 'y_label' => 'Directory size',
361 'y_number_format' => \&Bweb::human_size,
365 my ($d, $ret) = make_tab($all);
367 $obj->set_legend(keys %$ret);
369 print $obj->plot([$d, values %$ret])->png;
372 elsif ($graph eq 'job_rate') {
376 UNIX_TIMESTAMP($jobt.StartTime) AS starttime,
377 Client.Name AS clientname,
378 $jobt.Name AS jobname,
380 ($bweb->{sql}->{SEC_TO_INT}(
381 $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)
382 - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime)) + 0.01)
385 FROM $jobt, FileSet, Client $groupf
386 WHERE $jobt.ClientId = Client.ClientId
387 AND $jobt.FileSetId = FileSet.FileSetId
398 print STDERR $query if ($debug);
400 my $obj = get_graph('title' => "Job Rate : $arg->{jclients}/$arg->{jjobnames}",
401 'y_label' => 'Rate b/s',
403 'y_number_format' => \&Bweb::human_size,
406 my $all = $dbh->selectall_arrayref($query) ;
408 my ($d, $ret) = make_tab($all);
410 $obj->set_legend(keys %$ret);
412 print $obj->plot([$d, values %$ret])->png;
417 elsif ($graph eq 'job_duration') {
421 UNIX_TIMESTAMP($jobt.StartTime) AS starttime,
422 Client.Name AS clientname,
423 $jobt.Name AS jobname,
424 $bweb->{sql}->{SEC_TO_INT}( $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)
425 - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime))
427 FROM $jobt, FileSet, Client $groupf
428 WHERE $jobt.ClientId = Client.ClientId
429 AND $jobt.FileSetId = FileSet.FileSetId
440 print STDERR $query if ($debug);
442 my $obj = get_graph('title' => "Job Duration : $arg->{jclients}/$arg->{jjobnames}",
443 'y_label' => 'Duration',
445 'y_number_format' => \&Bweb::human_sec,
447 my $all = $dbh->selectall_arrayref($query) ;
449 my ($d, $ret) = make_tab($all);
451 $obj->set_legend(keys %$ret);
453 print $obj->plot([$d, values %$ret])->png;
456 # number of job per day/hour
457 } elsif ($graph =~ /^job_(count|sum|avg)_((p?)(day|hour|month))$/) {
461 my ($limit, $label) = $bweb->get_limit(age => $arg->{age},
462 limit => $arg->{limit},
463 offset=> $arg->{offset},
467 my @arg; # arg for plotting
469 if (!$per_t) { # much better aspect
472 push @arg, ("x_number_format" => undef,
477 if ($t eq 'sum' or $t eq 'avg') {
478 push @arg, ('y_number_format' => \&Bweb::human_size);
481 my $stime = $bweb->{sql}->{"STARTTIME_$d"};
482 $stime =~ s/Job\./$jobt\./;
486 " . ($per_t?"":"UNIX_TIMESTAMP") . "($stime) AS A,
488 FROM $jobt, FileSet, Client $groupf
489 WHERE $jobt.ClientId = Client.ClientId
490 AND $jobt.FileSetId = FileSet.FileSetId
501 print STDERR $query if ($debug);
503 my $obj = get_graph('title' => "Job $t : $arg->{jclients}/$arg->{jjobnames}",
509 my $all = $dbh->selectall_arrayref($query) ;
510 print STDERR Data::Dumper::Dumper($all);
511 my ($ret) = make_tab_sum($all);
513 print $obj->plot([$ret->{date}, $ret->{nb}])->png;