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