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 => [ "lred", add_colour("#008e8e"),
222 add_colour("#afd8f8"), add_colour("#f6bd0f"),
223 add_colour("#8bba00"), add_colour("#ff8e46"),
224 add_colour("#d64646"),
225 add_colour("#8e468e"), add_colour("#588526"),
226 add_colour("#b3aa00"), add_colour("#008ed6"),
227 add_colour("#9d080d"), add_colour("#a186be"),
231 if ($conf->{graph_font} && -f $conf->{graph_font}) {
232 $graph->set_title_font([$conf->{graph_font}], 12);
233 $graph->set_legend_font([$conf->{graph_font}], 11);
247 foreach my $row (@$all_row) {
248 my $label = $row->[1] . "/" . $row->[2] ; # client/backup name
250 if ($arg->{level} && $arg->{level} eq 'All') { # can separate level
251 $label = $row->[4] . ': ' . $label; # if users ask for
254 $ret->{date}->[$i] = $row->[0];
255 $ret->{$label}->[$i] = $row->[3];
257 $last_date = $row->[0];
260 # insert a fake element
261 foreach my $elt ( keys %{$ret}) {
262 $ret->{$elt}->[$i] = undef;
265 $ret->{date}->[$i] = $last_date + 1;
267 my $date = $ret->{date} ;
270 return ($date, $ret);
282 foreach my $row (@$all_row) {
283 $ret->{date}->[$i] = $row->[0];
284 $ret->{nb}->[$i] = $row->[1];
291 if ($graph eq 'job_size') {
295 UNIX_TIMESTAMP(Job.StartTime) AS starttime,
296 Client.Name AS clientname,
298 Job.JobBytes AS jobbytes,
299 Job.Level AS joblevel
300 FROM $jobt AS Job, FileSet, Client $filter $groupf
301 WHERE Job.ClientId = Client.ClientId
302 AND Job.FileSetId = FileSet.FileSetId
313 print STDERR $query if ($debug);
315 my $obj = get_graph('title' => "Job Size : $arg->{jclients}/$arg->{jjobnames}",
318 'y_number_format' => \&Bweb::human_size,
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 if ($graph eq 'job_file') {
334 UNIX_TIMESTAMP(Job.StartTime) AS starttime,
335 Client.Name AS clientname,
337 Job.JobFiles AS jobfiles,
338 Job.Level AS joblevel
339 FROM $jobt AS Job, FileSet, Client $filter $groupf
340 WHERE Job.ClientId = Client.ClientId
341 AND Job.FileSetId = FileSet.FileSetId
352 print STDERR $query if ($debug);
354 my $obj = get_graph('title' => "Job Files : $arg->{jclients}/$arg->{jjobnames}",
355 'y_label' => 'Number Files',
359 my $all = $dbh->selectall_arrayref($query) ;
361 my ($d, $ret) = make_tab($all);
363 $obj->set_legend(keys %$ret);
365 print $obj->plot([$d, values %$ret])->png;
368 # it works only with postgresql at this time
369 # we dont use $jobt because we use File, so job is in Job table
370 elsif ($graph eq 'file_histo' and $arg->{where}) {
372 my $dir = $dbh->quote(dirname($arg->{where}) . '/');
373 my $file = $dbh->quote(basename($arg->{where}));
376 SELECT UNIX_TIMESTAMP(Job.StartTime) AS starttime,
377 Client.Name AS client,
379 base64_decode_lstat(8,LStat) AS lstat,
380 Job.Level AS joblevel
382 FROM Job, FileSet, Filename, Path, File, Client $filter
383 WHERE Job.ClientId = Client.ClientId
384 AND Job.FileSetId = FileSet.FileSetId
386 AND File.JobId = Job.JobId
387 AND File.FilenameId = Filename.FilenameId
388 AND File.PathId = Path.PathId
390 AND Filename.Name = $file
399 print STDERR $query if ($debug);
401 my $all = $dbh->selectall_arrayref($query) ;
403 my $obj = get_graph('title' => "File size : $arg->{where}",
404 'y_label' => 'File size',
407 'y_number_format' => \&Bweb::human_size,
411 my ($d, $ret) = make_tab($all);
413 $obj->set_legend(keys %$ret);
415 print $obj->plot([$d, values %$ret])->png;
418 # it works only with postgresql at this time
419 # TODO: use brestore_missing_path
420 elsif ($graph eq 'rep_histo' and $arg->{where}) {
422 my $dir = $arg->{where};
423 $dir .= '/' if ($dir !~ m!/$!);
424 $dir = $dbh->quote($dir);
427 SELECT UNIX_TIMESTAMP(Job.StartTime) AS starttime,
428 Client.Name AS client,
430 brestore_pathvisibility.size AS size,
431 Job.Level AS joblevel
433 FROM Job, Client $filter, FileSet, Path, brestore_pathvisibility
434 WHERE Job.ClientId = Client.ClientId
435 AND Job.FileSetId = FileSet.FileSetId
437 AND Job.JobId = brestore_pathvisibility.JobId
438 AND Path.PathId = brestore_pathvisibility.PathId
448 print STDERR $query if ($debug);
450 my $all = $dbh->selectall_arrayref($query) ;
452 my $obj = get_graph('title' => "Directory size : $arg->{where}",
453 'y_label' => 'Directory size',
456 'y_number_format' => \&Bweb::human_size,
460 my ($d, $ret) = make_tab($all);
462 $obj->set_legend(keys %$ret);
464 print $obj->plot([$d, values %$ret])->png;
467 elsif ($graph eq 'job_rate') {
471 UNIX_TIMESTAMP(Job.StartTime) AS starttime,
472 Client.Name AS clientname,
475 ($bweb->{sql}->{SEC_TO_INT}(
476 $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)
477 - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime)) + 0.01)
479 Job.Level AS joblevel
481 FROM $jobt AS Job, FileSet, Client $filter $groupf
482 WHERE Job.ClientId = Client.ClientId
483 AND Job.FileSetId = FileSet.FileSetId
494 print STDERR $query if ($debug);
496 my $obj = get_graph('title' => "Job Rate : $arg->{jclients}/$arg->{jjobnames}",
497 'y_label' => 'Rate b/s',
499 'y_number_format' => \&Bweb::human_size,
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;
513 elsif ($graph eq 'job_duration') {
517 UNIX_TIMESTAMP(Job.StartTime) AS starttime,
518 Client.Name AS clientname,
520 $bweb->{sql}->{SEC_TO_INT}( $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)
521 - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime))
523 Job.Level AS joblevel
525 FROM $jobt AS Job, FileSet, Client $filter $groupf
526 WHERE Job.ClientId = Client.ClientId
527 AND Job.FileSetId = FileSet.FileSetId
538 print STDERR $query if ($debug);
540 my $obj = get_graph('title' => "Job Duration : $arg->{jclients}/$arg->{jjobnames}",
541 'y_label' => 'Duration',
543 'y_number_format' => \&Bweb::human_sec,
545 my $all = $dbh->selectall_arrayref($query) ;
547 my ($d, $ret) = make_tab($all);
549 $obj->set_legend(keys %$ret);
551 print $obj->plot([$d, values %$ret])->png;
554 # number of job per day/hour
555 } elsif ($graph =~ /^job_(count|sum|avg)_((p?)(day|hour|month))$/) {
559 my ($limit, $label) = $bweb->get_limit(age => $arg->{age},
560 limit => $arg->{limit},
561 offset=> $arg->{offset},
565 my @arg; # arg for plotting
567 if (!$per_t) { # much better aspect
570 push @arg, ("x_number_format" => undef,
575 if ($t eq 'sum' or $t eq 'avg') {
576 push @arg, ('y_number_format' => \&Bweb::human_size);
579 my $stime = $bweb->{sql}->{"STARTTIME_$d"};
583 " . ($per_t?"":"UNIX_TIMESTAMP") . "($stime) AS A,
585 FROM $jobt AS Job, FileSet, Client $filter $groupf
586 WHERE Job.ClientId = Client.ClientId
587 AND Job.FileSetId = FileSet.FileSetId
598 print STDERR $query if ($debug);
600 my $obj = get_graph('title' => "Job $t : $arg->{jclients}/$arg->{jjobnames}",
606 my $all = $dbh->selectall_arrayref($query) ;
607 # print STDERR Data::Dumper::Dumper($all);
608 my ($ret) = make_tab_sum($all);
610 print $obj->plot([$ret->{date}, $ret->{nb}])->png;