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';
118 print CGI::header('image/png');
124 if ($gtype eq 'lines') {
125 use GD::Graph::lines;
126 $graph = GD::Graph::lines->new ( $arg->{width}, $arg->{height} );
128 } elsif ($gtype eq 'bars') {
130 $graph = GD::Graph::bars->new ( $arg->{width}, $arg->{height} );
132 } elsif ($gtype eq 'linespoints') {
133 use GD::Graph::linespoints;
134 $graph = GD::Graph::linespoints->new ( $arg->{width}, $arg->{height} );
136 # this doesnt works at this time
137 # } elsif ($gtype eq 'bars3d') {
138 # use GD::Graph::bars3d;
139 # $graph = GD::Graph::bars3d->new ( $arg->{width}, $arg->{height} );
145 $graph->set('x_label' => 'Time',
146 'x_number_format' => sub { strftime('%D', localtime($_[0])) },
147 'x_tick_number' => 1,
163 foreach my $row (@$all_row) {
164 my $label = $row->[1] . "/" . $row->[2] ; # client/backup name
166 $ret->{date}->[$i] = $row->[0];
167 $ret->{$label}->[$i] = $row->[3];
169 $last_date = $row->[0];
172 # insert a fake element
173 foreach my $elt ( keys %{$ret}) {
174 $ret->{$elt}->[$i] = undef;
177 $ret->{date}->[$i] = $last_date + 1;
179 my $date = $ret->{date} ;
182 return ($date, $ret);
194 foreach my $row (@$all_row) {
195 $ret->{date}->[$i] = $row->[0];
196 $ret->{nb}->[$i] = $row->[1];
203 if ($graph eq 'job_size') {
207 UNIX_TIMESTAMP($jobt.StartTime) AS starttime,
208 Client.Name AS clientname,
209 $jobt.Name AS jobname,
210 $jobt.JobBytes AS jobbytes
211 FROM $jobt, FileSet, Client $filter $groupf
212 WHERE $jobt.ClientId = Client.ClientId
213 AND $jobt.FileSetId = FileSet.FileSetId
224 print STDERR $query if ($debug);
226 my $obj = get_graph('title' => "Job Size : $arg->{jclients}/$arg->{jjobnames}",
229 'y_number_format' => \&Bweb::human_size,
232 my $all = $dbh->selectall_arrayref($query) ;
234 my ($d, $ret) = make_tab($all);
236 $obj->set_legend(keys %$ret);
238 print $obj->plot([$d, values %$ret])->png;
241 if ($graph eq 'job_file') {
245 UNIX_TIMESTAMP($jobt.StartTime) AS starttime,
246 Client.Name AS clientname,
247 $jobt.Name AS jobname,
248 $jobt.JobFiles AS jobfiles
249 FROM $jobt, FileSet, Client $filter $groupf
250 WHERE $jobt.ClientId = Client.ClientId
251 AND $jobt.FileSetId = FileSet.FileSetId
262 print STDERR $query if ($debug);
264 my $obj = get_graph('title' => "Job Files : $arg->{jclients}/$arg->{jjobnames}",
265 'y_label' => 'Number Files',
269 my $all = $dbh->selectall_arrayref($query) ;
271 my ($d, $ret) = make_tab($all);
273 $obj->set_legend(keys %$ret);
275 print $obj->plot([$d, values %$ret])->png;
278 # it works only with postgresql at this time
279 # we dont use $jobt because we use File, so job is in Job table
280 elsif ($graph eq 'file_histo' and $arg->{where}) {
282 my $dir = $dbh->quote(dirname($arg->{where}) . '/');
283 my $file = $dbh->quote(basename($arg->{where}));
286 SELECT UNIX_TIMESTAMP(Job.StartTime) AS starttime,
287 Client.Name AS client,
289 base64_decode_lstat(8,LStat) AS lstat
291 FROM Job, FileSet, Filename, Path, File, Client $filter
292 WHERE Job.ClientId = Client.ClientId
293 AND Job.FileSetId = FileSet.FileSetId
295 AND File.JobId = Job.JobId
296 AND File.FilenameId = Filename.FilenameId
297 AND File.PathId = Path.PathId
299 AND Filename.Name = $file
308 print STDERR $query if ($debug);
310 my $all = $dbh->selectall_arrayref($query) ;
312 my $obj = get_graph('title' => "File size : $arg->{where}",
313 'y_label' => 'File size',
316 'y_number_format' => \&Bweb::human_size,
320 my ($d, $ret) = make_tab($all);
322 $obj->set_legend(keys %$ret);
324 print $obj->plot([$d, values %$ret])->png;
327 # it works only with postgresql at this time
328 # TODO: use brestore_missing_path
329 elsif ($graph eq 'rep_histo' and $arg->{where}) {
331 my $dir = $arg->{where};
332 $dir .= '/' if ($dir !~ m!/$!);
333 $dir = $dbh->quote($dir);
336 SELECT UNIX_TIMESTAMP(Job.StartTime) AS starttime,
337 Client.Name AS client,
339 brestore_pathvisibility.size AS size
341 FROM Job, Client $filter, FileSet, Path, brestore_pathvisibility
342 WHERE Job.ClientId = Client.ClientId
343 AND Job.FileSetId = FileSet.FileSetId
345 AND Job.JobId = brestore_pathvisibility.JobId
346 AND Path.PathId = brestore_pathvisibility.PathId
356 print STDERR $query if ($debug);
358 my $all = $dbh->selectall_arrayref($query) ;
360 my $obj = get_graph('title' => "Directory size : $arg->{where}",
361 'y_label' => 'Directory size',
364 'y_number_format' => \&Bweb::human_size,
368 my ($d, $ret) = make_tab($all);
370 $obj->set_legend(keys %$ret);
372 print $obj->plot([$d, values %$ret])->png;
375 elsif ($graph eq 'job_rate') {
379 UNIX_TIMESTAMP($jobt.StartTime) AS starttime,
380 Client.Name AS clientname,
381 $jobt.Name AS jobname,
383 ($bweb->{sql}->{SEC_TO_INT}(
384 $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)
385 - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime)) + 0.01)
388 FROM $jobt, FileSet, Client $filter $groupf
389 WHERE $jobt.ClientId = Client.ClientId
390 AND $jobt.FileSetId = FileSet.FileSetId
401 print STDERR $query if ($debug);
403 my $obj = get_graph('title' => "Job Rate : $arg->{jclients}/$arg->{jjobnames}",
404 'y_label' => 'Rate b/s',
406 'y_number_format' => \&Bweb::human_size,
409 my $all = $dbh->selectall_arrayref($query) ;
411 my ($d, $ret) = make_tab($all);
413 $obj->set_legend(keys %$ret);
415 print $obj->plot([$d, values %$ret])->png;
420 elsif ($graph eq 'job_duration') {
424 UNIX_TIMESTAMP($jobt.StartTime) AS starttime,
425 Client.Name AS clientname,
426 $jobt.Name AS jobname,
427 $bweb->{sql}->{SEC_TO_INT}( $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)
428 - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime))
430 FROM $jobt, FileSet, Client $filter $groupf
431 WHERE $jobt.ClientId = Client.ClientId
432 AND $jobt.FileSetId = FileSet.FileSetId
443 print STDERR $query if ($debug);
445 my $obj = get_graph('title' => "Job Duration : $arg->{jclients}/$arg->{jjobnames}",
446 'y_label' => 'Duration',
448 'y_number_format' => \&Bweb::human_sec,
450 my $all = $dbh->selectall_arrayref($query) ;
452 my ($d, $ret) = make_tab($all);
454 $obj->set_legend(keys %$ret);
456 print $obj->plot([$d, values %$ret])->png;
459 # number of job per day/hour
460 } elsif ($graph =~ /^job_(count|sum|avg)_((p?)(day|hour|month))$/) {
464 my ($limit, $label) = $bweb->get_limit(age => $arg->{age},
465 limit => $arg->{limit},
466 offset=> $arg->{offset},
470 my @arg; # arg for plotting
472 if (!$per_t) { # much better aspect
475 push @arg, ("x_number_format" => undef,
480 if ($t eq 'sum' or $t eq 'avg') {
481 push @arg, ('y_number_format' => \&Bweb::human_size);
484 my $stime = $bweb->{sql}->{"STARTTIME_$d"};
485 $stime =~ s/Job\./$jobt\./;
489 " . ($per_t?"":"UNIX_TIMESTAMP") . "($stime) AS A,
491 FROM $jobt, FileSet, Client $filter $groupf
492 WHERE $jobt.ClientId = Client.ClientId
493 AND $jobt.FileSetId = FileSet.FileSetId
504 print STDERR $query if ($debug);
506 my $obj = get_graph('title' => "Job $t : $arg->{jclients}/$arg->{jjobnames}",
512 my $all = $dbh->selectall_arrayref($query) ;
513 # print STDERR Data::Dumper::Dumper($all);
514 my ($ret) = make_tab_sum($all);
516 print $obj->plot([$ret->{date}, $ret->{nb}])->png;