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