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 use GD::Graph::colour qw(:colours);
196 if ($gtype eq 'lines') {
197 use GD::Graph::lines;
198 $graph = GD::Graph::lines->new ( $arg->{width}, $arg->{height} );
200 } elsif ($gtype eq 'bars') {
202 $graph = GD::Graph::bars->new ( $arg->{width}, $arg->{height} );
204 } elsif ($gtype eq 'linespoints') {
205 use GD::Graph::linespoints;
206 $graph = GD::Graph::linespoints->new ( $arg->{width}, $arg->{height} );
208 # this doesnt works at this time
209 # } elsif ($gtype eq 'bars3d') {
210 # use GD::Graph::bars3d;
211 # $graph = GD::Graph::bars3d->new ( $arg->{width}, $arg->{height} );
217 $graph->set('x_label' => 'Time',
218 'x_number_format' => sub { strftime('%D', localtime($_[0])) },
219 'x_tick_number' => 5*$arg->{width}/800,
221 dclrs => [ add_colour("#afd8f8"), add_colour("#f6bd0f"),
222 add_colour("#8bba00"), add_colour("#ff8e46"),
223 add_colour("#008e8e"), add_colour("#d64646"),
224 add_colour("#8e468e"), add_colour("#588526"),
225 add_colour("#b3aa00"), add_colour("#008ed6"),
226 add_colour("#9d080d"), add_colour("#a186be"),
243 foreach my $row (@$all_row) {
244 my $label = $row->[1] . "/" . $row->[2] ; # client/backup name
246 if ($arg->{level} eq 'All') { # can separate level
247 $label = $row->[4] . ': ' . $label; # if users ask for
250 $ret->{date}->[$i] = $row->[0];
251 $ret->{$label}->[$i] = $row->[3];
253 $last_date = $row->[0];
256 # insert a fake element
257 foreach my $elt ( keys %{$ret}) {
258 $ret->{$elt}->[$i] = undef;
261 $ret->{date}->[$i] = $last_date + 1;
263 my $date = $ret->{date} ;
266 return ($date, $ret);
278 foreach my $row (@$all_row) {
279 $ret->{date}->[$i] = $row->[0];
280 $ret->{nb}->[$i] = $row->[1];
287 if ($graph eq 'job_size') {
291 UNIX_TIMESTAMP(Job.StartTime) AS starttime,
292 Client.Name AS clientname,
294 Job.JobBytes AS jobbytes,
295 Job.Level AS joblevel
296 FROM $jobt AS Job, FileSet, Client $filter $groupf
297 WHERE Job.ClientId = Client.ClientId
298 AND Job.FileSetId = FileSet.FileSetId
309 print STDERR $query if ($debug);
311 my $obj = get_graph('title' => "Job Size : $arg->{jclients}/$arg->{jjobnames}",
314 'y_number_format' => \&Bweb::human_size,
317 my $all = $dbh->selectall_arrayref($query) ;
319 my ($d, $ret) = make_tab($all);
321 $obj->set_legend(keys %$ret);
323 print $obj->plot([$d, values %$ret])->png;
326 if ($graph eq 'job_file') {
330 UNIX_TIMESTAMP(Job.StartTime) AS starttime,
331 Client.Name AS clientname,
333 Job.JobFiles AS jobfiles,
334 Job.Level AS joblevel
335 FROM $jobt AS Job, FileSet, Client $filter $groupf
336 WHERE Job.ClientId = Client.ClientId
337 AND Job.FileSetId = FileSet.FileSetId
348 print STDERR $query if ($debug);
350 my $obj = get_graph('title' => "Job Files : $arg->{jclients}/$arg->{jjobnames}",
351 'y_label' => 'Number Files',
355 my $all = $dbh->selectall_arrayref($query) ;
357 my ($d, $ret) = make_tab($all);
359 $obj->set_legend(keys %$ret);
361 print $obj->plot([$d, values %$ret])->png;
364 # it works only with postgresql at this time
365 # we dont use $jobt because we use File, so job is in Job table
366 elsif ($graph eq 'file_histo' and $arg->{where}) {
368 my $dir = $dbh->quote(dirname($arg->{where}) . '/');
369 my $file = $dbh->quote(basename($arg->{where}));
372 SELECT UNIX_TIMESTAMP(Job.StartTime) AS starttime,
373 Client.Name AS client,
375 base64_decode_lstat(8,LStat) AS lstat,
376 Job.Level AS joblevel
378 FROM Job, FileSet, Filename, Path, File, Client $filter
379 WHERE Job.ClientId = Client.ClientId
380 AND Job.FileSetId = FileSet.FileSetId
382 AND File.JobId = Job.JobId
383 AND File.FilenameId = Filename.FilenameId
384 AND File.PathId = Path.PathId
386 AND Filename.Name = $file
395 print STDERR $query if ($debug);
397 my $all = $dbh->selectall_arrayref($query) ;
399 my $obj = get_graph('title' => "File size : $arg->{where}",
400 'y_label' => 'File size',
403 'y_number_format' => \&Bweb::human_size,
407 my ($d, $ret) = make_tab($all);
409 $obj->set_legend(keys %$ret);
411 print $obj->plot([$d, values %$ret])->png;
414 # it works only with postgresql at this time
415 # TODO: use brestore_missing_path
416 elsif ($graph eq 'rep_histo' and $arg->{where}) {
418 my $dir = $arg->{where};
419 $dir .= '/' if ($dir !~ m!/$!);
420 $dir = $dbh->quote($dir);
423 SELECT UNIX_TIMESTAMP(Job.StartTime) AS starttime,
424 Client.Name AS client,
426 brestore_pathvisibility.size AS size,
427 Job.Level AS joblevel
429 FROM Job, Client $filter, FileSet, Path, brestore_pathvisibility
430 WHERE Job.ClientId = Client.ClientId
431 AND Job.FileSetId = FileSet.FileSetId
433 AND Job.JobId = brestore_pathvisibility.JobId
434 AND Path.PathId = brestore_pathvisibility.PathId
444 print STDERR $query if ($debug);
446 my $all = $dbh->selectall_arrayref($query) ;
448 my $obj = get_graph('title' => "Directory size : $arg->{where}",
449 'y_label' => 'Directory size',
452 'y_number_format' => \&Bweb::human_size,
456 my ($d, $ret) = make_tab($all);
458 $obj->set_legend(keys %$ret);
460 print $obj->plot([$d, values %$ret])->png;
463 elsif ($graph eq 'job_rate') {
467 UNIX_TIMESTAMP(Job.StartTime) AS starttime,
468 Client.Name AS clientname,
471 ($bweb->{sql}->{SEC_TO_INT}(
472 $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)
473 - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime)) + 0.01)
475 Job.Level AS joblevel
477 FROM $jobt AS Job, FileSet, Client $filter $groupf
478 WHERE Job.ClientId = Client.ClientId
479 AND Job.FileSetId = FileSet.FileSetId
490 print STDERR $query if ($debug);
492 my $obj = get_graph('title' => "Job Rate : $arg->{jclients}/$arg->{jjobnames}",
493 'y_label' => 'Rate b/s',
495 'y_number_format' => \&Bweb::human_size,
498 my $all = $dbh->selectall_arrayref($query) ;
500 my ($d, $ret) = make_tab($all);
502 $obj->set_legend(keys %$ret);
504 print $obj->plot([$d, values %$ret])->png;
509 elsif ($graph eq 'job_duration') {
513 UNIX_TIMESTAMP(Job.StartTime) AS starttime,
514 Client.Name AS clientname,
516 $bweb->{sql}->{SEC_TO_INT}( $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)
517 - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime))
519 Job.Level AS joblevel
521 FROM $jobt AS Job, FileSet, Client $filter $groupf
522 WHERE Job.ClientId = Client.ClientId
523 AND Job.FileSetId = FileSet.FileSetId
534 print STDERR $query if ($debug);
536 my $obj = get_graph('title' => "Job Duration : $arg->{jclients}/$arg->{jjobnames}",
537 'y_label' => 'Duration',
539 'y_number_format' => \&Bweb::human_sec,
541 my $all = $dbh->selectall_arrayref($query) ;
543 my ($d, $ret) = make_tab($all);
545 $obj->set_legend(keys %$ret);
547 print $obj->plot([$d, values %$ret])->png;
550 # number of job per day/hour
551 } elsif ($graph =~ /^job_(count|sum|avg)_((p?)(day|hour|month))$/) {
555 my ($limit, $label) = $bweb->get_limit(age => $arg->{age},
556 limit => $arg->{limit},
557 offset=> $arg->{offset},
561 my @arg; # arg for plotting
563 if (!$per_t) { # much better aspect
566 push @arg, ("x_number_format" => undef,
571 if ($t eq 'sum' or $t eq 'avg') {
572 push @arg, ('y_number_format' => \&Bweb::human_size);
575 my $stime = $bweb->{sql}->{"STARTTIME_$d"};
579 " . ($per_t?"":"UNIX_TIMESTAMP") . "($stime) AS A,
581 FROM $jobt AS Job, FileSet, Client $filter $groupf
582 WHERE Job.ClientId = Client.ClientId
583 AND Job.FileSetId = FileSet.FileSetId
594 print STDERR $query if ($debug);
596 my $obj = get_graph('title' => "Job $t : $arg->{jclients}/$arg->{jjobnames}",
602 my $all = $dbh->selectall_arrayref($query) ;
603 # print STDERR Data::Dumper::Dumper($all);
604 my ($ret) = make_tab_sum($all);
606 print $obj->plot([$ret->{date}, $ret->{nb}])->png;