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 $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, $jobt.Name
156 FROM $jobt, Client $filter $groupf
157 WHERE $jobt.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' => 1,
233 foreach my $row (@$all_row) {
234 my $label = $row->[1] . "/" . $row->[2] ; # client/backup name
236 $ret->{date}->[$i] = $row->[0];
237 $ret->{$label}->[$i] = $row->[3];
239 $last_date = $row->[0];
242 # insert a fake element
243 foreach my $elt ( keys %{$ret}) {
244 $ret->{$elt}->[$i] = undef;
247 $ret->{date}->[$i] = $last_date + 1;
249 my $date = $ret->{date} ;
252 return ($date, $ret);
264 foreach my $row (@$all_row) {
265 $ret->{date}->[$i] = $row->[0];
266 $ret->{nb}->[$i] = $row->[1];
273 if ($graph eq 'job_size') {
277 UNIX_TIMESTAMP($jobt.StartTime) AS starttime,
278 Client.Name AS clientname,
279 $jobt.Name AS jobname,
280 $jobt.JobBytes AS jobbytes
281 FROM $jobt, FileSet, Client $filter $groupf
282 WHERE $jobt.ClientId = Client.ClientId
283 AND $jobt.FileSetId = FileSet.FileSetId
294 print STDERR $query if ($debug);
296 my $obj = get_graph('title' => "Job Size : $arg->{jclients}/$arg->{jjobnames}",
299 'y_number_format' => \&Bweb::human_size,
302 my $all = $dbh->selectall_arrayref($query) ;
304 my ($d, $ret) = make_tab($all);
306 $obj->set_legend(keys %$ret);
308 print $obj->plot([$d, values %$ret])->png;
311 if ($graph eq 'job_file') {
315 UNIX_TIMESTAMP($jobt.StartTime) AS starttime,
316 Client.Name AS clientname,
317 $jobt.Name AS jobname,
318 $jobt.JobFiles AS jobfiles
319 FROM $jobt, FileSet, Client $filter $groupf
320 WHERE $jobt.ClientId = Client.ClientId
321 AND $jobt.FileSetId = FileSet.FileSetId
332 print STDERR $query if ($debug);
334 my $obj = get_graph('title' => "Job Files : $arg->{jclients}/$arg->{jjobnames}",
335 'y_label' => 'Number Files',
339 my $all = $dbh->selectall_arrayref($query) ;
341 my ($d, $ret) = make_tab($all);
343 $obj->set_legend(keys %$ret);
345 print $obj->plot([$d, values %$ret])->png;
348 # it works only with postgresql at this time
349 # we dont use $jobt because we use File, so job is in Job table
350 elsif ($graph eq 'file_histo' and $arg->{where}) {
352 my $dir = $dbh->quote(dirname($arg->{where}) . '/');
353 my $file = $dbh->quote(basename($arg->{where}));
356 SELECT UNIX_TIMESTAMP(Job.StartTime) AS starttime,
357 Client.Name AS client,
359 base64_decode_lstat(8,LStat) AS lstat
361 FROM Job, FileSet, Filename, Path, File, Client $filter
362 WHERE Job.ClientId = Client.ClientId
363 AND Job.FileSetId = FileSet.FileSetId
365 AND File.JobId = Job.JobId
366 AND File.FilenameId = Filename.FilenameId
367 AND File.PathId = Path.PathId
369 AND Filename.Name = $file
378 print STDERR $query if ($debug);
380 my $all = $dbh->selectall_arrayref($query) ;
382 my $obj = get_graph('title' => "File size : $arg->{where}",
383 'y_label' => 'File size',
386 'y_number_format' => \&Bweb::human_size,
390 my ($d, $ret) = make_tab($all);
392 $obj->set_legend(keys %$ret);
394 print $obj->plot([$d, values %$ret])->png;
397 # it works only with postgresql at this time
398 # TODO: use brestore_missing_path
399 elsif ($graph eq 'rep_histo' and $arg->{where}) {
401 my $dir = $arg->{where};
402 $dir .= '/' if ($dir !~ m!/$!);
403 $dir = $dbh->quote($dir);
406 SELECT UNIX_TIMESTAMP(Job.StartTime) AS starttime,
407 Client.Name AS client,
409 brestore_pathvisibility.size AS size
411 FROM Job, Client $filter, FileSet, Path, brestore_pathvisibility
412 WHERE Job.ClientId = Client.ClientId
413 AND Job.FileSetId = FileSet.FileSetId
415 AND Job.JobId = brestore_pathvisibility.JobId
416 AND Path.PathId = brestore_pathvisibility.PathId
426 print STDERR $query if ($debug);
428 my $all = $dbh->selectall_arrayref($query) ;
430 my $obj = get_graph('title' => "Directory size : $arg->{where}",
431 'y_label' => 'Directory size',
434 'y_number_format' => \&Bweb::human_size,
438 my ($d, $ret) = make_tab($all);
440 $obj->set_legend(keys %$ret);
442 print $obj->plot([$d, values %$ret])->png;
445 elsif ($graph eq 'job_rate') {
449 UNIX_TIMESTAMP($jobt.StartTime) AS starttime,
450 Client.Name AS clientname,
451 $jobt.Name AS jobname,
453 ($bweb->{sql}->{SEC_TO_INT}(
454 $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)
455 - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime)) + 0.01)
458 FROM $jobt, FileSet, Client $filter $groupf
459 WHERE $jobt.ClientId = Client.ClientId
460 AND $jobt.FileSetId = FileSet.FileSetId
471 print STDERR $query if ($debug);
473 my $obj = get_graph('title' => "Job Rate : $arg->{jclients}/$arg->{jjobnames}",
474 'y_label' => 'Rate b/s',
476 'y_number_format' => \&Bweb::human_size,
479 my $all = $dbh->selectall_arrayref($query) ;
481 my ($d, $ret) = make_tab($all);
483 $obj->set_legend(keys %$ret);
485 print $obj->plot([$d, values %$ret])->png;
490 elsif ($graph eq 'job_duration') {
494 UNIX_TIMESTAMP($jobt.StartTime) AS starttime,
495 Client.Name AS clientname,
496 $jobt.Name AS jobname,
497 $bweb->{sql}->{SEC_TO_INT}( $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)
498 - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime))
500 FROM $jobt, FileSet, Client $filter $groupf
501 WHERE $jobt.ClientId = Client.ClientId
502 AND $jobt.FileSetId = FileSet.FileSetId
513 print STDERR $query if ($debug);
515 my $obj = get_graph('title' => "Job Duration : $arg->{jclients}/$arg->{jjobnames}",
516 'y_label' => 'Duration',
518 'y_number_format' => \&Bweb::human_sec,
520 my $all = $dbh->selectall_arrayref($query) ;
522 my ($d, $ret) = make_tab($all);
524 $obj->set_legend(keys %$ret);
526 print $obj->plot([$d, values %$ret])->png;
529 # number of job per day/hour
530 } elsif ($graph =~ /^job_(count|sum|avg)_((p?)(day|hour|month))$/) {
534 my ($limit, $label) = $bweb->get_limit(age => $arg->{age},
535 limit => $arg->{limit},
536 offset=> $arg->{offset},
540 my @arg; # arg for plotting
542 if (!$per_t) { # much better aspect
545 push @arg, ("x_number_format" => undef,
550 if ($t eq 'sum' or $t eq 'avg') {
551 push @arg, ('y_number_format' => \&Bweb::human_size);
554 my $stime = $bweb->{sql}->{"STARTTIME_$d"};
555 $stime =~ s/Job\./$jobt\./;
559 " . ($per_t?"":"UNIX_TIMESTAMP") . "($stime) AS A,
561 FROM $jobt, FileSet, Client $filter $groupf
562 WHERE $jobt.ClientId = Client.ClientId
563 AND $jobt.FileSetId = FileSet.FileSetId
574 print STDERR $query if ($debug);
576 my $obj = get_graph('title' => "Job $t : $arg->{jclients}/$arg->{jjobnames}",
582 my $all = $dbh->selectall_arrayref($query) ;
583 # print STDERR Data::Dumper::Dumper($all);
584 my ($ret) = make_tab_sum($all);
586 print $obj->plot([$ret->{date}, $ret->{nb}])->png;