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 Kern Sibbald.
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 JobHistory (LIKE Job);
59 # INSERT INTO JobHistory
60 # (SELECT * FROM Job WHERE JobId NOT IN (SELECT JobId FROM JobHistory) );
61 my $jobt = $bweb->get_stat_table();
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 => "Job.StartTime ASC",
77 if ($arg->{status} and $arg->{status} ne 'Any') {
78 $statusq = " AND Job.JobStatus = '$arg->{status}' ";
82 if ($arg->{level} and $arg->{level} !~ 'All|Any') {
83 $levelq = " AND Job.Level = '$arg->{level}' ";
87 if ($arg->{jfilesets}) {
88 $filesetq = " AND FileSet.FileSet IN ($arg->{qfilesets}) ";
92 if ($arg->{jjobnames}) {
93 $jobnameq = " AND Job.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=''; # where 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 $bweb->can_do('r_view_stat');
114 my $filter = $bweb->get_client_filter();
116 my $gtype = CGI::param('gtype') || 'bars';
119 # in this mode, we generate an image and an imagemap
120 if ($gtype eq 'balloon') {
121 use Digest::MD5 qw(md5_hex);
124 my $b = new GBalloon(width=>$arg->{width},
125 height =>$arg->{height});
128 my %legend = (x_title => 'Time',
130 POSIX::strftime('%H:%M', gmtime($_[0]))
133 if ($graph eq 'job_time_size') {
134 $order = 'JobFiles,JobBytes';
136 $legend{y_title} = 'Nb files';
137 $legend{y_func} = sub { int(shift)};
138 $legend{z_title} = 'Size';
139 $legend{z_func} = \&Bweb::human_size;
141 $order = 'JobBytes,JobFiles';
143 $legend{y_title} = 'Size';
144 $legend{y_func} = \&Bweb::human_size;
145 $legend{z_title} = 'Nb files';
146 $legend{z_func} = sub { int(shift)};
149 $b->set_legend_axis(%legend);
151 my $all = $dbh->selectall_arrayref("
152 SELECT $bweb->{sql}->{SEC_TO_INT}( $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)
153 - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime))
154 AS duration, $order, JobId, Job.Name
156 FROM $jobt AS Job, Client $filter $groupf
157 WHERE Job.ClientId = Client.ClientId
167 foreach my $a (@$all) {
168 $b->add_point($a->[0], $a->[1], $a->[2],
169 "?action=job_zoom;jobid=$a->[3]",
170 "$a->[4] $legend{z_title} " . $legend{z_func}($a->[2]));
176 my $md5_rep = md5_hex(join(":", map { $arg->{$_} } sort keys %$arg));
178 # need to cleanup this path
179 open(FP, ">$conf->{fv_write_path}/$md5_rep.png");
180 print FP $GBalloon::gd->png;
183 print $b->get_imagemap("Job overview", "/bweb/fv/$md5_rep.png");
188 print CGI::header('image/png');
194 if ($gtype eq 'lines') {
195 use GD::Graph::lines;
196 $graph = GD::Graph::lines->new ( $arg->{width}, $arg->{height} );
198 } elsif ($gtype eq 'bars') {
200 $graph = GD::Graph::bars->new ( $arg->{width}, $arg->{height} );
202 } elsif ($gtype eq 'linespoints') {
203 use GD::Graph::linespoints;
204 $graph = GD::Graph::linespoints->new ( $arg->{width}, $arg->{height} );
206 # this doesnt works at this time
207 # } elsif ($gtype eq 'bars3d') {
208 # use GD::Graph::bars3d;
209 # $graph = GD::Graph::bars3d->new ( $arg->{width}, $arg->{height} );
215 $graph->set('x_label' => 'Time',
216 'x_number_format' => sub { strftime('%D', localtime($_[0])) },
217 'x_tick_number' => 5*$arg->{width}/800,
234 foreach my $row (@$all_row) {
235 my $label = $row->[1] . "/" . $row->[2] ; # client/backup name
237 if ($arg->{level} eq 'All') { # can separate level
238 $label = $row->[4] . ': ' . $label; # if users ask for
241 $ret->{date}->[$i] = $row->[0];
242 $ret->{$label}->[$i] = $row->[3];
244 $last_date = $row->[0];
247 # insert a fake element
248 foreach my $elt ( keys %{$ret}) {
249 $ret->{$elt}->[$i] = undef;
252 $ret->{date}->[$i] = $last_date + 1;
254 my $date = $ret->{date} ;
257 return ($date, $ret);
269 foreach my $row (@$all_row) {
270 $ret->{date}->[$i] = $row->[0];
271 $ret->{nb}->[$i] = $row->[1];
278 if ($graph eq 'job_size') {
282 UNIX_TIMESTAMP(Job.StartTime) AS starttime,
283 Client.Name AS clientname,
285 Job.JobBytes AS jobbytes,
286 Job.Level AS joblevel
287 FROM $jobt AS Job, FileSet, Client $filter $groupf
288 WHERE Job.ClientId = Client.ClientId
289 AND Job.FileSetId = FileSet.FileSetId
300 print STDERR $query if ($debug);
302 my $obj = get_graph('title' => "Job Size : $arg->{jclients}/$arg->{jjobnames}",
305 'y_number_format' => \&Bweb::human_size,
308 my $all = $dbh->selectall_arrayref($query) ;
310 my ($d, $ret) = make_tab($all);
312 $obj->set_legend(keys %$ret);
314 print $obj->plot([$d, values %$ret])->png;
317 if ($graph eq 'job_file') {
321 UNIX_TIMESTAMP(Job.StartTime) AS starttime,
322 Client.Name AS clientname,
324 Job.JobFiles AS jobfiles,
325 Job.Level AS joblevel
326 FROM $jobt AS Job, FileSet, Client $filter $groupf
327 WHERE Job.ClientId = Client.ClientId
328 AND Job.FileSetId = FileSet.FileSetId
339 print STDERR $query if ($debug);
341 my $obj = get_graph('title' => "Job Files : $arg->{jclients}/$arg->{jjobnames}",
342 'y_label' => 'Number Files',
346 my $all = $dbh->selectall_arrayref($query) ;
348 my ($d, $ret) = make_tab($all);
350 $obj->set_legend(keys %$ret);
352 print $obj->plot([$d, values %$ret])->png;
355 # it works only with postgresql at this time
356 # we dont use $jobt because we use File, so job is in Job table
357 elsif ($graph eq 'file_histo' and $arg->{where}) {
359 my $dir = $dbh->quote(dirname($arg->{where}) . '/');
360 my $file = $dbh->quote(basename($arg->{where}));
363 SELECT UNIX_TIMESTAMP(Job.StartTime) AS starttime,
364 Client.Name AS client,
366 base64_decode_lstat(8,LStat) AS lstat,
367 Job.Level AS joblevel
369 FROM Job, FileSet, Filename, Path, File, Client $filter
370 WHERE Job.ClientId = Client.ClientId
371 AND Job.FileSetId = FileSet.FileSetId
373 AND File.JobId = Job.JobId
374 AND File.FilenameId = Filename.FilenameId
375 AND File.PathId = Path.PathId
377 AND Filename.Name = $file
386 print STDERR $query if ($debug);
388 my $all = $dbh->selectall_arrayref($query) ;
390 my $obj = get_graph('title' => "File size : $arg->{where}",
391 'y_label' => 'File size',
394 'y_number_format' => \&Bweb::human_size,
398 my ($d, $ret) = make_tab($all);
400 $obj->set_legend(keys %$ret);
402 print $obj->plot([$d, values %$ret])->png;
405 # it works only with postgresql at this time
406 # TODO: use brestore_missing_path
407 elsif ($graph eq 'rep_histo' and $arg->{where}) {
409 my $dir = $arg->{where};
410 $dir .= '/' if ($dir !~ m!/$!);
411 $dir = $dbh->quote($dir);
414 SELECT UNIX_TIMESTAMP(Job.StartTime) AS starttime,
415 Client.Name AS client,
417 brestore_pathvisibility.size AS size,
418 Job.Level AS joblevel
420 FROM Job, Client $filter, FileSet, Path, brestore_pathvisibility
421 WHERE Job.ClientId = Client.ClientId
422 AND Job.FileSetId = FileSet.FileSetId
424 AND Job.JobId = brestore_pathvisibility.JobId
425 AND Path.PathId = brestore_pathvisibility.PathId
435 print STDERR $query if ($debug);
437 my $all = $dbh->selectall_arrayref($query) ;
439 my $obj = get_graph('title' => "Directory size : $arg->{where}",
440 'y_label' => 'Directory size',
443 'y_number_format' => \&Bweb::human_size,
447 my ($d, $ret) = make_tab($all);
449 $obj->set_legend(keys %$ret);
451 print $obj->plot([$d, values %$ret])->png;
454 elsif ($graph eq 'job_rate') {
458 UNIX_TIMESTAMP(Job.StartTime) AS starttime,
459 Client.Name AS clientname,
462 ($bweb->{sql}->{SEC_TO_INT}(
463 $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)
464 - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime)) + 0.01)
466 Job.Level AS joblevel
468 FROM $jobt AS Job, FileSet, Client $filter $groupf
469 WHERE Job.ClientId = Client.ClientId
470 AND Job.FileSetId = FileSet.FileSetId
481 print STDERR $query if ($debug);
483 my $obj = get_graph('title' => "Job Rate : $arg->{jclients}/$arg->{jjobnames}",
484 'y_label' => 'Rate b/s',
486 'y_number_format' => \&Bweb::human_size,
489 my $all = $dbh->selectall_arrayref($query) ;
491 my ($d, $ret) = make_tab($all);
493 $obj->set_legend(keys %$ret);
495 print $obj->plot([$d, values %$ret])->png;
500 elsif ($graph eq 'job_duration') {
504 UNIX_TIMESTAMP(Job.StartTime) AS starttime,
505 Client.Name AS clientname,
507 $bweb->{sql}->{SEC_TO_INT}( $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)
508 - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime))
510 Job.Level AS joblevel
512 FROM $jobt AS Job, FileSet, Client $filter $groupf
513 WHERE Job.ClientId = Client.ClientId
514 AND Job.FileSetId = FileSet.FileSetId
525 print STDERR $query if ($debug);
527 my $obj = get_graph('title' => "Job Duration : $arg->{jclients}/$arg->{jjobnames}",
528 'y_label' => 'Duration',
530 'y_number_format' => \&Bweb::human_sec,
532 my $all = $dbh->selectall_arrayref($query) ;
534 my ($d, $ret) = make_tab($all);
536 $obj->set_legend(keys %$ret);
538 print $obj->plot([$d, values %$ret])->png;
541 # number of job per day/hour
542 } elsif ($graph =~ /^job_(count|sum|avg)_((p?)(day|hour|month))$/) {
546 my ($limit, $label) = $bweb->get_limit(age => $arg->{age},
547 limit => $arg->{limit},
548 offset=> $arg->{offset},
552 my @arg; # arg for plotting
554 if (!$per_t) { # much better aspect
557 push @arg, ("x_number_format" => undef,
562 if ($t eq 'sum' or $t eq 'avg') {
563 push @arg, ('y_number_format' => \&Bweb::human_size);
566 my $stime = $bweb->{sql}->{"STARTTIME_$d"};
570 " . ($per_t?"":"UNIX_TIMESTAMP") . "($stime) AS A,
572 FROM $jobt AS Job, FileSet, Client $filter $groupf
573 WHERE Job.ClientId = Client.ClientId
574 AND Job.FileSetId = FileSet.FileSetId
585 print STDERR $query if ($debug);
587 my $obj = get_graph('title' => "Job $t : $arg->{jclients}/$arg->{jjobnames}",
593 my $all = $dbh->selectall_arrayref($query) ;
594 # print STDERR Data::Dumper::Dumper($all);
595 my ($ret) = make_tab_sum($all);
597 print $obj->plot([$ret->{date}, $ret->{nb}])->png;