]> git.sur5r.net Git - bacula/bacula/blob - gui/bweb/cgi/bgraph.pl
bweb: Update some GPL2 notice to AGPL
[bacula/bacula] / gui / bweb / cgi / bgraph.pl
1 #!/usr/bin/perl -w
2 use strict;
3
4 =head1 LICENSE
5
6    Bweb - A Bacula web interface
7    Bacula® - The Network Backup Solution
8
9    Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
10
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.
14    This program is Free Software; you can redistribute it and/or
15    modify it under the terms of version three of the GNU Affero General Public
16    License as published by the Free Software Foundation and included
17    in the file LICENSE.
18
19    This program is distributed in the hope that it will be useful, but
20    WITHOUT ANY WARRANTY; without even the implied warranty of
21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22    Affero General Public License for more details.
23
24    You should have received a copy of the GNU Affero General Public License
25    along with this program; if not, write to the Free Software
26    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
27    02110-1301, USA.
28
29    Bacula® is a registered trademark of Kern Sibbald.
30    The licensor of Bacula is the Free Software Foundation Europe
31    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
32    Switzerland, email:ftf@fsfeurope.org.
33
34 =head1 VERSION
35
36     $Id$
37
38 =cut
39
40 use Bweb;
41
42 use Data::Dumper;
43 use CGI;
44
45 use POSIX qw/strftime/;
46 use File::Basename qw/basename dirname/;
47
48 my $conf = new Bweb::Config(config_file => $Bweb::config_file);
49 $conf->load();
50 my $bweb = new Bweb(info => $conf);
51 $bweb->connect_db();
52 my $dbh = $bweb->{dbh};
53 my $debug = $bweb->{debug};
54
55 # Job table keep use Media or Job retention, so it's quite enought
56 # for good statistics
57 # CREATE TABLE JobHistory (LIKE Job);
58 # INSERT INTO JobHistory
59 #    (SELECT * FROM Job WHERE JobId NOT IN (SELECT JobId FROM JobHistory) );
60 my $jobt = $bweb->get_stat_table();
61
62 my $graph = CGI::param('graph') || 'job_size';
63 my $legend = CGI::param('legend') || 'on' ;
64 $legend = ($legend eq 'on')?1:0;
65
66 my $arg = $bweb->get_form(qw/width height limit offset age where jobid
67                              jfilesets level status jjobnames jclients jclient_groups/);
68
69 my ($limitq, $label) = $bweb->get_limit(age   => $arg->{age},
70                                         limit => $arg->{limit},
71                                         offset=> $arg->{offset},
72                                         order => "Job.StartTime ASC",
73                                         );
74
75 my $statusq='';
76 if ($arg->{status} and $arg->{status} ne 'Any') {
77     $statusq = " AND Job.JobStatus = '$arg->{status}' ";
78 }
79     
80 my $levelq='';
81 if ($arg->{level} and $arg->{level} !~ 'All|Any') {
82     $levelq = " AND Job.Level = '$arg->{level}' ";
83
84
85 my $filesetq='';
86 if ($arg->{jfilesets}) {
87     $filesetq = " AND FileSet.FileSet IN ($arg->{qfilesets}) ";
88
89
90 my $jobnameq='';
91 if ($arg->{jjobnames}) {
92     $jobnameq = " AND Job.Name IN ($arg->{jjobnames}) ";
93 } else {
94     $arg->{jjobnames} = 'all';  # skip warning
95
96
97 my $clientq='';
98 if ($arg->{jclients}) {
99     $clientq = " AND Client.Name IN ($arg->{jclients}) ";
100 } else {
101     $arg->{jclients} = 'all';   # skip warning
102 }
103
104 my $groupf='';                  # from clause
105 my $groupq='';                  # where clause
106 if ($arg->{jclient_groups}) {
107     $groupf = " JOIN client_group_member ON (Client.ClientId = client_group_member.clientid) 
108                 JOIN client_group USING (client_group_id)";
109     $groupq = " AND client_group_name IN ($arg->{jclient_groups}) ";
110 }
111
112 $bweb->can_do('r_view_stat');
113 my $filter = $bweb->get_client_filter();
114
115 my $gtype = CGI::param('gtype') || 'bars';
116
117
118 # in this mode, we generate an image and an imagemap
119 if ($gtype eq 'balloon') {
120     use Digest::MD5 qw(md5_hex);
121     use GBalloon;
122
123     my $b = new GBalloon(width=>$arg->{width}, 
124                          height =>$arg->{height});
125
126     my $order;
127     my %legend = (x_title => 'Time', 
128                   x_func => sub { 
129                       POSIX::strftime('%H:%M', gmtime($_[0])) 
130                       }
131                   ) ;
132     if ($graph eq 'job_time_size') {
133         $order = 'JobFiles,JobBytes';
134
135         $legend{y_title} = 'Nb files';
136         $legend{y_func} = sub { int(shift)};
137         $legend{z_title} = 'Size';
138         $legend{z_func} = \&Bweb::human_size;
139     } else {
140         $order = 'JobBytes,JobFiles';
141
142         $legend{y_title} = 'Size';
143         $legend{y_func} = \&Bweb::human_size;
144         $legend{z_title} = 'Nb files';
145         $legend{z_func} = sub { int(shift)};
146     }
147
148     $b->set_legend_axis(%legend);
149
150     my $all = $dbh->selectall_arrayref("
151 SELECT $bweb->{sql}->{SEC_TO_INT}(  $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)
152                                   - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime))
153          AS duration, $order, JobId, Job.Name
154        
155  FROM $jobt AS Job, Client $filter $groupf
156 WHERE Job.ClientId = Client.ClientId
157   AND Job.Type = 'B'
158   $clientq
159   $statusq
160   $levelq
161   $jobnameq
162   $groupq
163 $limitq
164 ");
165
166     foreach my $a (@$all) {
167         $b->add_point($a->[0], $a->[1], $a->[2], 
168                       "?action=job_zoom;jobid=$a->[3]",
169                       "$a->[4] $legend{z_title} " . $legend{z_func}($a->[2]));
170     }
171     
172     $b->init_gd();
173     $b->finalize();
174
175     my $md5_rep = md5_hex(join(":", map { $arg->{$_} } sort keys %$arg));
176
177     # need to cleanup this path
178     open(FP, ">$conf->{fv_write_path}/$md5_rep.png");
179     print FP $GBalloon::gd->png;
180     close(FP);
181     
182     print $b->get_imagemap("Job overview", "/bweb/fv/$md5_rep.png");
183
184     exit 0;
185 }
186
187 print CGI::header('image/png');
188
189 sub get_graph
190 {
191     my (@options) = @_;
192     my $graph;
193     use GD::Graph::colour qw(:colours);
194
195     if ($gtype eq 'lines') {
196         use GD::Graph::lines;
197         $graph = GD::Graph::lines->new ( $arg->{width}, $arg->{height} );
198
199     } elsif ($gtype eq 'bars') {
200         use GD::Graph::bars;
201         $graph = GD::Graph::bars->new ( $arg->{width}, $arg->{height} );
202
203     } elsif ($gtype eq 'linespoints') {
204         use GD::Graph::linespoints;
205         $graph = GD::Graph::linespoints->new ( $arg->{width}, $arg->{height} );
206
207 #   this doesnt works at this time
208 #    } elsif ($gtype eq 'bars3d') {
209 #       use GD::Graph::bars3d;
210 #       $graph = GD::Graph::bars3d->new ( $arg->{width}, $arg->{height} );
211
212     } else {
213         return undef;
214     }
215
216     $graph->set('x_label' => 'Time',
217                 'x_number_format' => sub { strftime('%D', localtime($_[0])) },
218                 'x_tick_number' => 5*$arg->{width}/800,
219                 'overwrite' => 1,
220                 dclrs => [ "lred", add_colour("#008e8e"), 
221                            add_colour("#afd8f8"), add_colour("#f6bd0f"),
222                            add_colour("#8bba00"), add_colour("#ff8e46"),
223                            add_colour("#d64646"),
224                            add_colour("#8e468e"), add_colour("#588526"),
225                            add_colour("#b3aa00"), add_colour("#008ed6"),
226                            add_colour("#9d080d"), add_colour("#a186be"),
227                 ],
228                 @options,
229                 );
230     if ($conf->{graph_font} && -f $conf->{graph_font}) {
231         $graph->set_title_font([$conf->{graph_font}], 12);
232         $graph->set_legend_font([$conf->{graph_font}], 11);
233     }
234     return $graph;
235 }
236
237 sub make_tab
238 {
239     my ($all_row) = @_;
240
241     my $i=0;
242     my $last_date=0;
243
244     my $ret = {};
245     
246     foreach my $row (@$all_row) {
247         my $label = $row->[1] . "/" . $row->[2] ; # client/backup name
248
249         if ($arg->{level} && $arg->{level} eq 'All') {  # can separate level
250             $label = $row->[4] . ': ' . $label;   # if users ask for
251         }
252
253         $ret->{date}->[$i]   = $row->[0];       
254         $ret->{$label}->[$i] = $row->[3];
255         $i++;
256         $last_date = $row->[0];
257     }
258
259     # insert a fake element
260     foreach my $elt ( keys %{$ret}) {
261         $ret->{$elt}->[$i] =  undef;
262     }
263
264     $ret->{date}->[$i] = $last_date + 1;
265
266     my $date = $ret->{date} ;
267     delete $ret->{date};
268
269     return ($date, $ret);
270 }
271
272 sub make_tab_sum
273 {
274     my ($all_row) = @_;
275
276     my $i=0;
277     my $last_date=0;
278
279     my $ret = {};
280     
281     foreach my $row (@$all_row) {
282         $ret->{date}->[$i]   = $row->[0];       
283         $ret->{nb}->[$i] = $row->[1];
284         $i++;
285     }
286
287     return ($ret);
288 }
289
290 if ($graph eq 'job_size') {
291
292     my $query = "
293 SELECT 
294        UNIX_TIMESTAMP(Job.StartTime)  AS starttime,
295        Client.Name                    AS clientname,
296        Job.Name                       AS jobname,
297        Job.JobBytes                   AS jobbytes,
298        Job.Level                      AS joblevel
299 FROM $jobt AS Job, FileSet, Client $filter $groupf
300 WHERE Job.ClientId = Client.ClientId
301   AND Job.FileSetId = FileSet.FileSetId
302   AND Job.Type = 'B'
303   $clientq
304   $statusq
305   $filesetq
306   $levelq
307   $jobnameq
308   $groupq
309 $limitq
310 ";
311
312     print STDERR $query if ($debug);
313
314     my $obj = get_graph('title' => "Job Size : $arg->{jclients}/$arg->{jjobnames}",
315                         'y_label' => 'Size',
316                         'y_min_value' => 0,
317                         'y_number_format' => \&Bweb::human_size,
318                         );
319
320     my $all = $dbh->selectall_arrayref($query) ;
321
322     my ($d, $ret) = make_tab($all);
323     if ($legend) {
324         $obj->set_legend(keys %$ret);
325     }
326     print $obj->plot([$d, values %$ret])->png;
327 }
328
329 if ($graph eq 'job_file') {
330
331     my $query = "
332 SELECT 
333        UNIX_TIMESTAMP(Job.StartTime)  AS starttime,
334        Client.Name                    AS clientname,
335        Job.Name                       AS jobname,
336        Job.JobFiles                   AS jobfiles,
337        Job.Level                      AS joblevel
338 FROM $jobt AS Job, FileSet, Client $filter $groupf
339 WHERE Job.ClientId = Client.ClientId
340   AND Job.FileSetId = FileSet.FileSetId
341   AND Job.Type = 'B'
342   $clientq
343   $statusq
344   $filesetq
345   $levelq
346   $jobnameq
347   $groupq
348 $limitq
349 ";
350
351     print STDERR $query if ($debug);
352
353     my $obj = get_graph('title' => "Job Files : $arg->{jclients}/$arg->{jjobnames}",
354                         'y_label' => 'Number Files',
355                         'y_min_value' => 0,
356                         );
357
358     my $all = $dbh->selectall_arrayref($query) ;
359
360     my ($d, $ret) = make_tab($all);
361     if ($legend) {
362         $obj->set_legend(keys %$ret);
363     }
364     print $obj->plot([$d, values %$ret])->png;
365 }
366
367 # it works only with postgresql at this time
368 # we don't use $jobt because we use File, so job is in Job table
369 elsif ($graph eq 'file_histo' and $arg->{where}) {
370     
371     my $dir  = $dbh->quote(dirname($arg->{where}) . '/');
372     my $file = $dbh->quote(basename($arg->{where}));
373
374     my $query = "
375 SELECT UNIX_TIMESTAMP(Job.StartTime)    AS starttime,
376        Client.Name                      AS client,
377        Job.Name                         AS jobname,
378        base64_decode_lstat(8,LStat)     AS lstat,
379        Job.Level                        AS joblevel
380
381 FROM Job, FileSet, Filename, Path, File, Client $filter
382 WHERE Job.ClientId = Client.ClientId
383   AND Job.FileSetId = FileSet.FileSetId
384   AND Job.Type = 'B'
385   AND File.JobId = Job.JobId
386   AND File.FilenameId = Filename.FilenameId
387   AND File.PathId = Path.PathId
388   AND Path.Path = $dir
389   AND Filename.Name = $file
390   $clientq
391   $statusq
392   $filesetq
393   $levelq
394   $jobnameq
395 $limitq
396 ";
397
398     print STDERR $query if ($debug);
399
400     my $all = $dbh->selectall_arrayref($query) ;
401
402     my $obj = get_graph('title' => "File size : $arg->{where}",
403                         'y_label' => 'File size',
404                         'y_min_value' => 0,
405                         'y_min_value' => 0,
406                         'y_number_format' => \&Bweb::human_size,
407                         );
408
409
410     my ($d, $ret) = make_tab($all);
411     if ($legend) {
412         $obj->set_legend(keys %$ret);
413     }
414     print $obj->plot([$d, values %$ret])->png;
415 }
416
417 # it works only with postgresql at this time
418 # TODO: use brestore_missing_path
419 elsif ($graph eq 'rep_histo' and $arg->{where}) {
420     
421     my $dir  = $arg->{where};
422     $dir .= '/' if ($dir !~ m!/$!);
423     $dir = $dbh->quote($dir);
424
425     my $query = "
426 SELECT UNIX_TIMESTAMP(Job.StartTime) AS starttime,
427        Client.Name                   AS client,
428        Job.Name                      AS jobname,
429        brestore_pathvisibility.size  AS size,
430        Job.Level                     AS joblevel
431
432 FROM Job, Client $filter, FileSet, Path, brestore_pathvisibility
433 WHERE Job.ClientId = Client.ClientId
434   AND Job.FileSetId = FileSet.FileSetId
435   AND Job.Type = 'B'
436   AND Job.JobId = brestore_pathvisibility.JobId
437   AND Path.PathId = brestore_pathvisibility.PathId
438   AND Path.Path = $dir
439   $clientq
440   $statusq
441   $filesetq
442   $levelq
443   $jobnameq
444 $limitq
445 ";
446
447     print STDERR $query if ($debug);
448
449     my $all = $dbh->selectall_arrayref($query) ;
450
451     my $obj = get_graph('title' => "Directory size : $arg->{where}",
452                         'y_label' => 'Directory size',
453                         'y_min_value' => 0,
454                         'y_min_value' => 0,
455                         'y_number_format' => \&Bweb::human_size,
456                         );
457
458
459     my ($d, $ret) = make_tab($all);
460     if ($legend) {
461         $obj->set_legend(keys %$ret);
462     }
463     print $obj->plot([$d, values %$ret])->png;
464 }
465
466 elsif ($graph eq 'job_rate') {
467
468     my $query = "
469 SELECT 
470        UNIX_TIMESTAMP(Job.StartTime)  AS starttime,
471        Client.Name                    AS clientname,
472        Job.Name                       AS jobname,
473        Job.JobBytes /
474        ($bweb->{sql}->{SEC_TO_INT}(
475                           $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)  
476                         - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime)) + 0.01) 
477          AS rate,
478        Job.Level                      AS joblevel
479
480 FROM $jobt AS Job, FileSet, Client $filter $groupf
481 WHERE Job.ClientId = Client.ClientId
482   AND Job.FileSetId = FileSet.FileSetId
483   AND Job.Type = 'B'
484   $clientq
485   $statusq
486   $filesetq
487   $levelq
488   $jobnameq
489   $groupq
490 $limitq
491 ";
492
493     print STDERR $query if ($debug);
494
495     my $obj = get_graph('title' => "Job Rate : $arg->{jclients}/$arg->{jjobnames}",
496                         'y_label' => 'Rate b/s',
497                         'y_min_value' => 0,
498                         'y_number_format' => \&Bweb::human_size,
499                         );
500
501     my $all = $dbh->selectall_arrayref($query) ;
502
503     my ($d, $ret) = make_tab($all);    
504     if ($legend) {
505         $obj->set_legend(keys %$ret);
506     }
507     print $obj->plot([$d, values %$ret])->png;
508 }
509
510
511
512 elsif ($graph eq 'job_duration') {
513
514     my $query = "
515 SELECT 
516        UNIX_TIMESTAMP(Job.StartTime)       AS starttime,
517        Client.Name                         AS clientname,
518        Job.Name                            AS jobname,
519   $bweb->{sql}->{SEC_TO_INT}(  $bweb->{sql}->{UNIX_TIMESTAMP}(EndTime)  
520                              - $bweb->{sql}->{UNIX_TIMESTAMP}(StartTime)) 
521          AS duration,
522        Job.Level                      AS joblevel
523
524 FROM $jobt AS Job, FileSet, Client $filter $groupf
525 WHERE Job.ClientId = Client.ClientId
526   AND Job.FileSetId = FileSet.FileSetId
527   AND Job.Type = 'B'
528   $clientq
529   $statusq
530   $filesetq
531   $levelq
532   $jobnameq
533   $groupq
534 $limitq
535 ";
536
537     print STDERR $query if ($debug);
538
539     my $obj = get_graph('title' => "Job Duration : $arg->{jclients}/$arg->{jjobnames}",
540                         'y_label' => 'Duration',
541                         'y_min_value' => 0,
542                         'y_number_format' => \&Bweb::human_sec,
543                         );
544     my $all = $dbh->selectall_arrayref($query) ;
545
546     my ($d, $ret) = make_tab($all);
547     if ($legend) {
548         $obj->set_legend(keys %$ret);
549     }
550     print $obj->plot([$d, values %$ret])->png;
551
552
553 # number of job per day/hour
554 } elsif ($graph =~ /^job_(count|sum|avg)_((p?)(day|hour|month))$/) {
555     my $t = $1;
556     my $d = uc($2);
557     my $per_t = $3;
558     my ($limit, $label) = $bweb->get_limit(age   => $arg->{age},
559                                            limit => $arg->{limit},
560                                            offset=> $arg->{offset},
561                                            groupby => "A",
562                                            order => "A",
563                                            );
564     my @arg;                    # arg for plotting
565
566     if (!$per_t) {              # much better aspect
567         #$gtype = 'lines';
568     } else {
569         push @arg, ("x_number_format" => undef,
570                     "x_min_value" => 0,
571                     );
572     }
573
574     if ($t eq 'sum' or $t eq 'avg') {
575         push @arg, ('y_number_format' => \&Bweb::human_size);
576     }
577     
578     my $stime = $bweb->{sql}->{"STARTTIME_$d"};
579
580     my $query = "
581 SELECT
582      " . ($per_t?"":"UNIX_TIMESTAMP") . "($stime) AS A,
583      $t(JobBytes)                  AS nb
584 FROM $jobt AS Job, FileSet, Client $filter $groupf
585 WHERE Job.ClientId = Client.ClientId
586   AND Job.FileSetId = FileSet.FileSetId
587   AND Job.Type = 'B'
588   $clientq
589   $statusq
590   $filesetq
591   $levelq
592   $jobnameq
593   $groupq
594 $limit
595 ";
596
597     print STDERR $query  if ($debug);
598
599     my $obj = get_graph('title' => "Job $t : $arg->{jclients}/$arg->{jjobnames}",
600                         'y_label' => $t,
601                         'y_min_value' => 0,
602                         @arg,
603                         );
604
605     my $all = $dbh->selectall_arrayref($query) ;
606 #    print STDERR Data::Dumper::Dumper($all);
607     my ($ret) = make_tab_sum($all);
608
609     print $obj->plot([$ret->{date}, $ret->{nb}])->png;    
610
611
612
613 $dbh->disconnect();