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 = $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} ne '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,
233 foreach my $row (@$all_row) {
234 # Todo, add Level to label if option is set ->[4]
235 my $label = $row->[1] . "/" . $row->[2] ; # client/backup name
237 $ret->{date}->[$i] = $row->[0];
238 $ret->{$label}->[$i] = $row->[3];
240 $last_date = $row->[0];
243 # insert a fake element
244 foreach my $elt ( keys %{$ret}) {
245 $ret->{$elt}->[$i] = undef;
248 $ret->{date}->[$i] = $last_date + 1;
250 my $date = $ret->{date} ;
253 return ($date, $ret);
265 foreach my $row (@$all_row) {
266 $ret->{date}->[$i] = $row->[0];
267 $ret->{nb}->[$i] = $row->[1];
274 if ($graph eq 'job_size') {
278 UNIX_TIMESTAMP(Job.StartTime) AS starttime,
279 Client.Name AS clientname,
281 Job.JobBytes AS jobbytes,
282 Job.Level AS joblevel
283 FROM $jobt AS Job, FileSet, Client $filter $groupf
284 WHERE Job.ClientId = Client.ClientId
285 AND Job.FileSetId = FileSet.FileSetId
296 print STDERR $query if ($debug);
298 my $obj = get_graph('title' => "Job Size : $arg->{jclients}/$arg->{jjobnames}",
301 'y_number_format' => \&Bweb::human_size,
304 my $all = $dbh->selectall_arrayref($query) ;
306 my ($d, $ret) = make_tab($all);
308 $obj->set_legend(keys %$ret);
310 print $obj->plot([$d, values %$ret])->png;
313 if ($graph eq 'job_file') {
317 UNIX_TIMESTAMP(Job.StartTime) AS starttime,
318 Client.Name AS clientname,
320 Job.JobFiles AS jobfiles,
321 Job.Level AS joblevel
322 FROM $jobt AS Job, FileSet, Client $filter $groupf
323 WHERE Job.ClientId = Client.ClientId
324 AND Job.FileSetId = FileSet.FileSetId
335 print STDERR $query if ($debug);
337 my $obj = get_graph('title' => "Job Files : $arg->{jclients}/$arg->{jjobnames}",
338 'y_label' => 'Number Files',
342 my $all = $dbh->selectall_arrayref($query) ;
344 my ($d, $ret) = make_tab($all);
346 $obj->set_legend(keys %$ret);
348 print $obj->plot([$d, values %$ret])->png;
351 # it works only with postgresql at this time
352 # we dont use $jobt because we use File, so job is in Job table
353 elsif ($graph eq 'file_histo' and $arg->{where}) {
355 my $dir = $dbh->quote(dirname($arg->{where}) . '/');
356 my $file = $dbh->quote(basename($arg->{where}));
359 SELECT UNIX_TIMESTAMP(Job.StartTime) AS starttime,
360 Client.Name AS client,
362 base64_decode_lstat(8,LStat) AS lstat,
363 Job.Level AS joblevel
365 FROM Job, FileSet, Filename, Path, File, Client $filter
366 WHERE Job.ClientId = Client.ClientId
367 AND Job.FileSetId = FileSet.FileSetId
369 AND File.JobId = Job.JobId
370 AND File.FilenameId = Filename.FilenameId
371 AND File.PathId = Path.PathId
373 AND Filename.Name = $file
382 print STDERR $query if ($debug);
384 my $all = $dbh->selectall_arrayref($query) ;
386 my $obj = get_graph('title' => "File size : $arg->{where}",
387 'y_label' => 'File size',
390 'y_number_format' => \&Bweb::human_size,
394 my ($d, $ret) = make_tab($all);
396 $obj->set_legend(keys %$ret);
398 print $obj->plot([$d, values %$ret])->png;
401 # it works only with postgresql at this time
402 # TODO: use brestore_missing_path
403 elsif ($graph eq 'rep_histo' and $arg->{where}) {
405 my $dir = $arg->{where};
406 $dir .= '/' if ($dir !~ m!/$!);
407 $dir = $dbh->quote($dir);
410 SELECT UNIX_TIMESTAMP(Job.StartTime) AS starttime,
411 Client.Name AS client,
413 brestore_pathvisibility.size AS size,
414 Job.Level AS joblevel
416 FROM Job, Client $filter, FileSet, Path, brestore_pathvisibility
417 WHERE Job.ClientId = Client.ClientId
418 AND Job.FileSetId = FileSet.FileSetId
420 AND Job.JobId = brestore_pathvisibility.JobId
421 AND Path.PathId = brestore_pathvisibility.PathId
431 print STDERR $query if ($debug);
433 my $all = $dbh->selectall_arrayref($query) ;
435 my $obj = get_graph('title' => "Directory size : $arg->{where}",
436 'y_label' => 'Directory size',
439 'y_number_format' => \&Bweb::human_size,
443 my ($d, $ret) = make_tab($all);
445 $obj->set_legend(keys %$ret);
447 print $obj->plot([$d, values %$ret])->png;
450 elsif ($graph eq 'job_rate') {
454 UNIX_TIMESTAMP(Job.StartTime) AS starttime,
455 Client.Name AS clientname,
458 ($bweb->{sql}->{SEC_TO_INT}(
459 $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)
460 - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime)) + 0.01)
462 Job.Level AS joblevel
464 FROM $jobt AS Job, FileSet, Client $filter $groupf
465 WHERE Job.ClientId = Client.ClientId
466 AND Job.FileSetId = FileSet.FileSetId
477 print STDERR $query if ($debug);
479 my $obj = get_graph('title' => "Job Rate : $arg->{jclients}/$arg->{jjobnames}",
480 'y_label' => 'Rate b/s',
482 'y_number_format' => \&Bweb::human_size,
485 my $all = $dbh->selectall_arrayref($query) ;
487 my ($d, $ret) = make_tab($all);
489 $obj->set_legend(keys %$ret);
491 print $obj->plot([$d, values %$ret])->png;
496 elsif ($graph eq 'job_duration') {
500 UNIX_TIMESTAMP(Job.StartTime) AS starttime,
501 Client.Name AS clientname,
503 $bweb->{sql}->{SEC_TO_INT}( $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)
504 - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime))
506 Job.Level AS joblevel
508 FROM $jobt AS Job, FileSet, Client $filter $groupf
509 WHERE Job.ClientId = Client.ClientId
510 AND Job.FileSetId = FileSet.FileSetId
521 print STDERR $query if ($debug);
523 my $obj = get_graph('title' => "Job Duration : $arg->{jclients}/$arg->{jjobnames}",
524 'y_label' => 'Duration',
526 'y_number_format' => \&Bweb::human_sec,
528 my $all = $dbh->selectall_arrayref($query) ;
530 my ($d, $ret) = make_tab($all);
532 $obj->set_legend(keys %$ret);
534 print $obj->plot([$d, values %$ret])->png;
537 # number of job per day/hour
538 } elsif ($graph =~ /^job_(count|sum|avg)_((p?)(day|hour|month))$/) {
542 my ($limit, $label) = $bweb->get_limit(age => $arg->{age},
543 limit => $arg->{limit},
544 offset=> $arg->{offset},
548 my @arg; # arg for plotting
550 if (!$per_t) { # much better aspect
553 push @arg, ("x_number_format" => undef,
558 if ($t eq 'sum' or $t eq 'avg') {
559 push @arg, ('y_number_format' => \&Bweb::human_size);
562 my $stime = $bweb->{sql}->{"STARTTIME_$d"};
566 " . ($per_t?"":"UNIX_TIMESTAMP") . "($stime) AS A,
568 FROM $jobt AS Job, FileSet, Client $filter $groupf
569 WHERE Job.ClientId = Client.ClientId
570 AND Job.FileSetId = FileSet.FileSetId
581 print STDERR $query if ($debug);
583 my $obj = get_graph('title' => "Job $t : $arg->{jclients}/$arg->{jjobnames}",
589 my $all = $dbh->selectall_arrayref($query) ;
590 # print STDERR Data::Dumper::Dumper($all);
591 my ($ret) = make_tab_sum($all);
593 print $obj->plot([$ret->{date}, $ret->{nb}])->png;