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