679c1eeade3a71da59c467ad365dc6ece2d8738d
[collectd.git] / contrib / collection3 / bin / index.cgi
1 #!/usr/bin/perl
2
3 # Copyright (C) 2008-2011  Florian Forster
4 # Copyright (C) 2011       noris network AG
5 #
6 # This program is free software; you can redistribute it and/or modify it under
7 # the terms of the GNU General Public License as published by the Free Software
8 # Foundation; only version 2 of the License is applicable.
9 #
10 # This program is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
13 # details.
14 #
15 # You should have received a copy of the GNU General Public License along with
16 # this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 #
19 # Authors:
20 #   Florian "octo" Forster <octo at collectd.org>
21
22 use strict;
23 use warnings;
24 use utf8;
25 use vars (qw($BASE_DIR));
26
27 BEGIN
28 {
29   if (defined $ENV{'SCRIPT_FILENAME'})
30   {
31     if ($ENV{'SCRIPT_FILENAME'} =~ m{^(/.+)/bin/[^/]+$})
32     {
33       $BASE_DIR = $1;
34       unshift (@INC, "$BASE_DIR/lib");
35     }
36   }
37 }
38
39 use Carp (qw(cluck confess));
40 use CGI (':cgi');
41 use CGI::Carp ('fatalsToBrowser');
42 use HTML::Entities ('encode_entities');
43
44 use Data::Dumper;
45
46 use Collectd::Graph::Config (qw(gc_read_config gc_get_scalar));
47 use Collectd::Graph::TypeLoader (qw(tl_load_type));
48 use Collectd::Graph::Common (qw(get_files_from_directory get_all_hosts
49       get_timespan_selection get_selected_files get_host_selection
50       get_plugin_selection flush_files));
51 use Collectd::Graph::Type ();
52
53 our $TimeSpans =
54 {
55   Hour  =>        3600,
56   Day   =>       86400,
57   Week  =>   7 * 86400,
58   Month =>  31 * 86400,
59   Year  => 366 * 86400
60 };
61
62 my %Actions =
63 (
64   list_hosts => \&action_list_hosts,
65   show_selection => \&action_show_selection
66 );
67
68 my $have_init = 0;
69 sub init
70 {
71   if ($have_init)
72   {
73     return;
74   }
75
76   print STDERR "INC = (" . join (', ', @INC) . ");\n";
77
78   gc_read_config ("$BASE_DIR/etc/collection.conf");
79
80   $have_init = 1;
81 }
82
83 sub main
84 {
85   my $Debug = param ('debug') ? 1 : 0;
86   my $action = param ('action') || 'list_hosts';
87
88   if (!exists ($Actions{$action}))
89   {
90     print STDERR "No such action: $action\n";
91     return (1);
92   }
93
94   init ();
95
96   $Actions{$action}->();
97   return (1);
98 } # sub main
99
100 sub can_handle_xhtml
101 {
102   my %types = ();
103
104   if (!defined $ENV{'HTTP_ACCEPT'})
105   {
106     return;
107   }
108
109   for (split (',', $ENV{'HTTP_ACCEPT'}))
110   {
111     my $type = lc ($_);
112     my $q = 1.0;
113
114     if ($type =~ m#^([^;]+);q=([0-9\.]+)$#)
115     {
116       $type = $1;
117       $q = 0.0 + $2;
118     }
119     $types{$type} = $q;
120   }
121
122   if (!defined ($types{'application/xhtml+xml'}))
123   {
124     return;
125   }
126   elsif (!defined ($types{'text/html'}))
127   {
128     return (1);
129   }
130   elsif ($types{'application/xhtml+xml'} < $types{'text/html'})
131   {
132     return;
133   }
134   else
135   {
136     return (1);
137   }
138 } # can_handle_xhtml
139
140 my $html_started;
141 sub start_html
142 {
143   return if ($html_started);
144
145   my $end;
146   my $begin;
147   my $timespan;
148
149   $end = time ();
150   $timespan = get_timespan_selection ();
151   $begin = $end - $timespan;
152
153   if (can_handle_xhtml ())
154   {
155     print header (-Content_Type => 'application/xhtml+xml; charset=UTF-8');
156     print <<HTML;
157 <?xml version="1.0" encoding="UTF-8"?>
158 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
159     "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
160 <html xmlns="http://www.w3.org/1999/xhtml"
161     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
162     xsi:schemaLocation="http://www.w3.org/MarkUp/SCHEMA/xhtml11.xsd"
163     xml:lang="en">
164 HTML
165   }
166   else
167   {
168     print header (-Content_Type => 'text/html; charset=UTF-8');
169     print <<HTML;
170 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
171     "http://www.w3.org/TR/html4/strict.dtd">
172 <html>
173 HTML
174   }
175   print <<HTML;
176   <head>
177     <title>collection.cgi, Version 3</title>
178     <link rel="icon" href="../share/shortcut-icon.png" type="image/png" />
179     <link rel="stylesheet" href="../share/style.css" type="text/css" />
180     <script type="text/javascript" src="../share/navigate.js"></script>
181   </head>
182   <body onload="nav_init ($begin, $end);">
183 HTML
184   $html_started = 1;
185 }
186
187 sub end_html
188 {
189   print <<HTML;
190   </body>
191 </html>
192 HTML
193   $html_started = 0;
194 }
195
196 sub show_selector
197 {
198   my $timespan_selection = get_timespan_selection ();
199   my $host_selection = get_host_selection ();
200   my $plugin_selection = get_plugin_selection ();
201
202   print <<HTML;
203     <form action="${\script_name ()}" method="get">
204       <fieldset>
205         <legend>Data selection</legend>
206         <select name="hostname" multiple="multiple" size="15">
207 HTML
208   for (sort (keys %$host_selection))
209   {
210     my $host = encode_entities ($_);
211     my $selected = $host_selection->{$_}
212     ? ' selected="selected"'
213     : '';
214     print qq#          <option value="$host"$selected>$host</option>\n#;
215   }
216   print <<HTML;
217         </select>
218         <select name="plugin" multiple="multiple" size="15">
219 HTML
220   for (sort (keys %$plugin_selection))
221   {
222     my $plugin = encode_entities ($_);
223     my $selected = $plugin_selection->{$_}
224     ? ' selected="selected"'
225     : '';
226     print qq#          <option value="$plugin"$selected>$plugin</option>\n#;
227   }
228   print <<HTML;
229         </select>
230         <select name="timespan">
231 HTML
232   for (sort { $TimeSpans->{$a} <=> $TimeSpans->{$b} } (keys (%$TimeSpans)))
233   {
234     my $name = encode_entities ($_);
235     my $value = $TimeSpans->{$_};
236     my $selected = ($value == $timespan_selection)
237     ? ' selected="selected"'
238     : '';
239     print qq#          <option value="$value"$selected>$name</option>\n#;
240   }
241   print <<HTML;
242         </select>
243         <input type="hidden" name="action" value="show_selection" />
244         <input type="submit" name="ok_button" value="OK" />
245       </fieldset>
246     </form>
247 HTML
248 } # show_selector
249
250 sub action_list_hosts
251 {
252   start_html ();
253   show_selector ();
254
255   my @hosts = get_all_hosts ();
256   print "    <ul>\n";
257   for (sort @hosts)
258   {
259     my $url = encode_entities (script_name () . "?action=show_selection;hostname=$_");
260     my $name = encode_entities ($_);
261     print qq#      <li><a href="$url">$name</a></li>\n#;
262   }
263   print "    </ul>\n";
264
265   end_html ();
266 } # action_list_hosts
267
268 =head1 MODULE LOADING
269
270 This script makes use of the various B<Collectd::Graph::Type::*> modules. If a
271 file like C<foo.rrd> is encountered it tries to load the
272 B<Collectd::Graph::Type::Foo> module and, if that fails, falls back to the
273 B<Collectd::Graph::Type> base class.
274
275 If you want to create a specialized graph for a certain type, you have to
276 create a new module which inherits from the B<Collectd::Graph::Type> base
277 class. A description of provided (and used) methods can be found in the inline
278 documentation of the B<Collectd::Graph::Type> module.
279
280 There are other, more specialized, "abstract" classes that possibly better fit
281 your need. Unfortunately they are not yet documented.
282
283 =over 4
284
285 =item B<Collectd::Graph::Type::GenericStacked>
286
287 Specialized class that groups files by their plugin instance and stacks them on
288 top of each other. Example types that inherit from this class are
289 B<Collectd::Graph::Type::Cpu> and B<Collectd::Graph::Type::Memory>.
290
291 =item B<Collectd::Graph::Type::GenericIO>
292
293 Specialized class for input/output graphs. This class can only handle files
294 with exactly two data sources, input and output. Example types that inherit
295 from this class are B<Collectd::Graph::Type::DiskOctets> and
296 B<Collectd::Graph::Type::IfOctets>.
297
298 =back
299
300 =cut
301
302 sub action_show_selection
303 {
304   start_html ();
305   show_selector ();
306
307   my $all_files;
308   my $timespan;
309
310   my $types = {};
311
312   my $id_counter = 0;
313
314   $all_files = get_selected_files ();
315   $timespan = get_timespan_selection ();
316
317   if (param ('debug'))
318   {
319     print "<pre>", Data::Dumper->Dump ([$all_files], ['all_files']), "</pre>\n";
320   }
321
322   # Send FLUSH command to the daemon if necessary and possible.
323   flush_files ($all_files,
324       begin => time () - $timespan,
325       end => time (),
326       addr => gc_get_scalar ('UnixSockAddr', undef),
327       interval => gc_get_scalar ('Interval', 10));
328
329   for (@$all_files)
330   {
331     my $file = $_;
332     my $type = ucfirst (lc ($file->{'type'}));
333
334     $type =~ s/[^A-Za-z0-9_]//g;
335     $type =~ s/_([A-Za-z0-9])/\U$1\E/g;
336
337     if (!defined ($types->{$type}))
338     {
339       $types->{$type} = tl_load_type ($file->{'type'});
340       if (!$types->{$type})
341       {
342         cluck ("tl_load_type (" . $file->{'type'} . ") failed");
343         next;
344       }
345     }
346
347     $types->{$type}->addFiles ($file);
348   }
349 #print STDOUT Data::Dumper->Dump ([$types], ['types']);
350
351   print qq#    <table>\n#;
352   for (sort (keys %$types))
353   {
354     my $type = $_;
355
356     if (!defined ($types->{$type}))
357     {
358       next;
359     }
360
361     my $graphs_num = $types->{$type}->getGraphsNum ();
362
363     for (my $i = 0; $i < $graphs_num; $i++)
364     {
365       my $args = $types->{$type}->getGraphArgs ($i);
366       my $url = encode_entities ("graph.cgi?$args;begin=-$timespan");
367       my $id = sprintf ("graph%04i", $id_counter++);
368
369       print "      <tr>\n";
370       print "        <td rowspan=\"$graphs_num\">$type</td>\n" if ($i == 0);
371       print <<EOF;
372         <td>
373           <div class="graph_canvas">
374             <div class="graph_float">
375               <img id="${id}" class="graph_image"
376                 alt="A graph"
377                 src="$url" />
378               <div class="controls zoom">
379                 <div title="Earlier"
380                   onclick="nav_move_earlier ('${id}');">&#x2190;</div>
381                 <div title="Zoom out"
382                   onclick="nav_zoom_out ('${id}');">-</div>
383                 <div title="Zoom in"
384                   onclick="nav_zoom_in ('${id}');">+</div>
385                 <div title="Later"
386                   onclick="nav_move_later ('${id}');">&#x2192;</div>
387               </div>
388               <div class="controls preset">
389                 <div title="Show current hour"
390                   onclick="nav_time_reset ('${id}', 3600);">H</div>
391                 <div title="Show current day"
392                   onclick="nav_time_reset ('${id}', 86400);">D</div>
393                 <div title="Show current week"
394                   onclick="nav_time_reset ('${id}', 7 * 86400);">W</div>
395                 <div title="Show current month"
396                   onclick="nav_time_reset ('${id}', 31 * 86400);">M</div>
397                 <div title="Show current year"
398                   onclick="nav_time_reset ('${id}', 366 * 86400);">Y</div>
399                 <div title="Set all images to this timespan"
400                   onclick="nav_set_reference ('${id}');">!</div>
401               </div>
402             </div>
403           </div>
404         </td>
405 EOF
406       # print qq#        <td><img src="$url" /></td>\n#;
407       print "      </tr>\n";
408     }
409   }
410
411   print "    </table>\n";
412   end_html ();
413 }
414
415 main ();
416
417 =head1 SEE ALSO
418
419 L<Collectd::Graph::Type>
420
421 =head1 AUTHOR AND LICENSE
422
423 Copyright (c) 2008 by Florian Forster
424 E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>. Licensed under the terms of the GNU
425 General Public License, VersionE<nbsp>2 (GPLv2).
426
427 =cut
428
429 # vim: set shiftwidth=2 softtabstop=2 tabstop=8 :