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 $all = $dbh->selectall_arrayref("
125 SELECT $bweb->{sql}->{SEC_TO_INT}( $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)
126 - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime))
127 AS duration, JobBytes, JobFiles, JobId, $jobt.Name
129 FROM $jobt, Client $filter $groupf
130 WHERE $jobt.ClientId = Client.ClientId
140 my $b = new GBalloon(width=>$arg->{width},
141 height =>$arg->{height});
142 $b->set_legend_axis(x_title => 'Time',
144 POSIX::strftime('%H:%M', gmtime($_[0]))
146 y_title => 'Size', y_func => \&Bweb::human_size,
147 z_title => 'Nb files');
149 foreach my $a (@$all) {
150 $b->add_point($a->[0], $a->[1], $a->[2],
151 "?action=job_zoom;jobid=$a->[3]",
152 "$a->[4] $a->[2] files");
158 my $md5_rep = md5_hex(join(":", map { $arg->{$_} } sort keys %$arg));
160 # need to cleanup this path
161 open(FP, ">$conf->{fv_write_path}/$md5_rep.png");
162 print FP $GBalloon::gd->png;
165 print $b->get_imagemap("Job overview", "/bweb/fv/$md5_rep.png");
170 print CGI::header('image/png');
176 if ($gtype eq 'lines') {
177 use GD::Graph::lines;
178 $graph = GD::Graph::lines->new ( $arg->{width}, $arg->{height} );
180 } elsif ($gtype eq 'bars') {
182 $graph = GD::Graph::bars->new ( $arg->{width}, $arg->{height} );
184 } elsif ($gtype eq 'linespoints') {
185 use GD::Graph::linespoints;
186 $graph = GD::Graph::linespoints->new ( $arg->{width}, $arg->{height} );
188 # this doesnt works at this time
189 # } elsif ($gtype eq 'bars3d') {
190 # use GD::Graph::bars3d;
191 # $graph = GD::Graph::bars3d->new ( $arg->{width}, $arg->{height} );
197 $graph->set('x_label' => 'Time',
198 'x_number_format' => sub { strftime('%D', localtime($_[0])) },
199 'x_tick_number' => 1,
215 foreach my $row (@$all_row) {
216 my $label = $row->[1] . "/" . $row->[2] ; # client/backup name
218 $ret->{date}->[$i] = $row->[0];
219 $ret->{$label}->[$i] = $row->[3];
221 $last_date = $row->[0];
224 # insert a fake element
225 foreach my $elt ( keys %{$ret}) {
226 $ret->{$elt}->[$i] = undef;
229 $ret->{date}->[$i] = $last_date + 1;
231 my $date = $ret->{date} ;
234 return ($date, $ret);
246 foreach my $row (@$all_row) {
247 $ret->{date}->[$i] = $row->[0];
248 $ret->{nb}->[$i] = $row->[1];
255 if ($graph eq 'job_size') {
259 UNIX_TIMESTAMP($jobt.StartTime) AS starttime,
260 Client.Name AS clientname,
261 $jobt.Name AS jobname,
262 $jobt.JobBytes AS jobbytes
263 FROM $jobt, FileSet, Client $filter $groupf
264 WHERE $jobt.ClientId = Client.ClientId
265 AND $jobt.FileSetId = FileSet.FileSetId
276 print STDERR $query if ($debug);
278 my $obj = get_graph('title' => "Job Size : $arg->{jclients}/$arg->{jjobnames}",
281 'y_number_format' => \&Bweb::human_size,
284 my $all = $dbh->selectall_arrayref($query) ;
286 my ($d, $ret) = make_tab($all);
288 $obj->set_legend(keys %$ret);
290 print $obj->plot([$d, values %$ret])->png;
293 if ($graph eq 'job_file') {
297 UNIX_TIMESTAMP($jobt.StartTime) AS starttime,
298 Client.Name AS clientname,
299 $jobt.Name AS jobname,
300 $jobt.JobFiles AS jobfiles
301 FROM $jobt, FileSet, Client $filter $groupf
302 WHERE $jobt.ClientId = Client.ClientId
303 AND $jobt.FileSetId = FileSet.FileSetId
314 print STDERR $query if ($debug);
316 my $obj = get_graph('title' => "Job Files : $arg->{jclients}/$arg->{jjobnames}",
317 'y_label' => 'Number Files',
321 my $all = $dbh->selectall_arrayref($query) ;
323 my ($d, $ret) = make_tab($all);
325 $obj->set_legend(keys %$ret);
327 print $obj->plot([$d, values %$ret])->png;
330 # it works only with postgresql at this time
331 # we dont use $jobt because we use File, so job is in Job table
332 elsif ($graph eq 'file_histo' and $arg->{where}) {
334 my $dir = $dbh->quote(dirname($arg->{where}) . '/');
335 my $file = $dbh->quote(basename($arg->{where}));
338 SELECT UNIX_TIMESTAMP(Job.StartTime) AS starttime,
339 Client.Name AS client,
341 base64_decode_lstat(8,LStat) AS lstat
343 FROM Job, FileSet, Filename, Path, File, Client $filter
344 WHERE Job.ClientId = Client.ClientId
345 AND Job.FileSetId = FileSet.FileSetId
347 AND File.JobId = Job.JobId
348 AND File.FilenameId = Filename.FilenameId
349 AND File.PathId = Path.PathId
351 AND Filename.Name = $file
360 print STDERR $query if ($debug);
362 my $all = $dbh->selectall_arrayref($query) ;
364 my $obj = get_graph('title' => "File size : $arg->{where}",
365 'y_label' => 'File size',
368 'y_number_format' => \&Bweb::human_size,
372 my ($d, $ret) = make_tab($all);
374 $obj->set_legend(keys %$ret);
376 print $obj->plot([$d, values %$ret])->png;
379 # it works only with postgresql at this time
380 # TODO: use brestore_missing_path
381 elsif ($graph eq 'rep_histo' and $arg->{where}) {
383 my $dir = $arg->{where};
384 $dir .= '/' if ($dir !~ m!/$!);
385 $dir = $dbh->quote($dir);
388 SELECT UNIX_TIMESTAMP(Job.StartTime) AS starttime,
389 Client.Name AS client,
391 brestore_pathvisibility.size AS size
393 FROM Job, Client $filter, FileSet, Path, brestore_pathvisibility
394 WHERE Job.ClientId = Client.ClientId
395 AND Job.FileSetId = FileSet.FileSetId
397 AND Job.JobId = brestore_pathvisibility.JobId
398 AND Path.PathId = brestore_pathvisibility.PathId
408 print STDERR $query if ($debug);
410 my $all = $dbh->selectall_arrayref($query) ;
412 my $obj = get_graph('title' => "Directory size : $arg->{where}",
413 'y_label' => 'Directory size',
416 'y_number_format' => \&Bweb::human_size,
420 my ($d, $ret) = make_tab($all);
422 $obj->set_legend(keys %$ret);
424 print $obj->plot([$d, values %$ret])->png;
427 elsif ($graph eq 'job_rate') {
431 UNIX_TIMESTAMP($jobt.StartTime) AS starttime,
432 Client.Name AS clientname,
433 $jobt.Name AS jobname,
435 ($bweb->{sql}->{SEC_TO_INT}(
436 $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)
437 - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime)) + 0.01)
440 FROM $jobt, FileSet, Client $filter $groupf
441 WHERE $jobt.ClientId = Client.ClientId
442 AND $jobt.FileSetId = FileSet.FileSetId
453 print STDERR $query if ($debug);
455 my $obj = get_graph('title' => "Job Rate : $arg->{jclients}/$arg->{jjobnames}",
456 'y_label' => 'Rate b/s',
458 'y_number_format' => \&Bweb::human_size,
461 my $all = $dbh->selectall_arrayref($query) ;
463 my ($d, $ret) = make_tab($all);
465 $obj->set_legend(keys %$ret);
467 print $obj->plot([$d, values %$ret])->png;
472 elsif ($graph eq 'job_duration') {
476 UNIX_TIMESTAMP($jobt.StartTime) AS starttime,
477 Client.Name AS clientname,
478 $jobt.Name AS jobname,
479 $bweb->{sql}->{SEC_TO_INT}( $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)
480 - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime))
482 FROM $jobt, FileSet, Client $filter $groupf
483 WHERE $jobt.ClientId = Client.ClientId
484 AND $jobt.FileSetId = FileSet.FileSetId
495 print STDERR $query if ($debug);
497 my $obj = get_graph('title' => "Job Duration : $arg->{jclients}/$arg->{jjobnames}",
498 'y_label' => 'Duration',
500 'y_number_format' => \&Bweb::human_sec,
502 my $all = $dbh->selectall_arrayref($query) ;
504 my ($d, $ret) = make_tab($all);
506 $obj->set_legend(keys %$ret);
508 print $obj->plot([$d, values %$ret])->png;
511 # number of job per day/hour
512 } elsif ($graph =~ /^job_(count|sum|avg)_((p?)(day|hour|month))$/) {
516 my ($limit, $label) = $bweb->get_limit(age => $arg->{age},
517 limit => $arg->{limit},
518 offset=> $arg->{offset},
522 my @arg; # arg for plotting
524 if (!$per_t) { # much better aspect
527 push @arg, ("x_number_format" => undef,
532 if ($t eq 'sum' or $t eq 'avg') {
533 push @arg, ('y_number_format' => \&Bweb::human_size);
536 my $stime = $bweb->{sql}->{"STARTTIME_$d"};
537 $stime =~ s/Job\./$jobt\./;
541 " . ($per_t?"":"UNIX_TIMESTAMP") . "($stime) AS A,
543 FROM $jobt, FileSet, Client $filter $groupf
544 WHERE $jobt.ClientId = Client.ClientId
545 AND $jobt.FileSetId = FileSet.FileSetId
556 print STDERR $query if ($debug);
558 my $obj = get_graph('title' => "Job $t : $arg->{jclients}/$arg->{jjobnames}",
564 my $all = $dbh->selectall_arrayref($query) ;
565 # print STDERR Data::Dumper::Dumper($all);
566 my ($ret) = make_tab_sum($all);
568 print $obj->plot([$ret->{date}, $ret->{nb}])->png;