1 #!/usr/bin/perl
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.
18 use strict;
19 use warnings;
20 use lib ('../lib');
21 use utf8;
23 use FindBin ('$RealBin');
24 use CGI (':cgi');
25 use CGI::Carp ('fatalsToBrowser');
26 use HTML::Entities ('encode_entities');
28 use Data::Dumper;
30 use Collectd::Graph::Config (qw(gc_read_config));
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));
35 use Collectd::Graph::Type ();
37 our $Debug = param ('debug') ? 1 : 0;
39 our $TimeSpans =
40 {
41 Hour => 3600,
42 Day => 86400,
43 Week => 7 * 86400,
44 Month => 31 * 86400,
45 Year => 366 * 86400
46 };
48 my $action = param ('action') || 'list_hosts';
49 our %Actions =
50 (
51 list_hosts => \&action_list_hosts,
52 show_selection => \&action_show_selection
53 );
55 if (!exists ($Actions{$action}))
56 {
57 print STDERR "No such action: $action\n";
58 exit 1;
59 }
61 gc_read_config ("$RealBin/../etc/collection.conf");
63 $Actions{$action}->();
64 exit (0);
66 sub can_handle_xhtml
67 {
68 my %types = ();
70 if (!defined $ENV{'HTTP_ACCEPT'})
71 {
72 return;
73 }
75 for (split (',', $ENV{'HTTP_ACCEPT'}))
76 {
77 my $type = lc ($_);
78 my $q = 1.0;
80 if ($type =~ m#^([^;]+);q=([0-9\.]+)$#)
81 {
82 $type = $1;
83 $q = 0.0 + $2;
84 }
85 $types{$type} = $q;
86 }
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
106 {my $html_started;
107 sub start_html
108 {
109 return if ($html_started);
111 my $end;
112 my $begin;
113 my $timespan;
115 $end = time ();
116 $timespan = get_timespan_selection ();
117 $begin = $end - $timespan;
119 if (can_handle_xhtml ())
120 {
121 print <<HTML;
122 Content-Type: application/xhtml+xml; charset=UTF-8
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
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 }}
155 sub end_html
156 {
157 print <<HTML;
158 </body>
159 </html>
160 HTML
161 }
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 ();
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 <fieldset>
214 <legend>Move all graphs</legend>
215 <input type="button" name="earlier" value="←" title="Earlier"
216 onclick="nav_move_earlier ('*');" />
217 <input type="button" name="zoom_out" value="-" title="Zoom out"
218 onclick="nav_zoom_out ('*');" />
219 <input type="button" name="zoom_in" value="+" title="Zoom in"
220 onclick="nav_zoom_in ('*');" />
221 <input type="button" name="later" value="→" title="Later"
222 onclick="nav_move_later ('*');" />
223 </fieldset>
224 </form>
225 HTML
226 } # show_selector
228 sub action_list_hosts
229 {
230 start_html ();
231 show_selector ();
233 my @hosts = get_all_hosts ();
234 print " <ul>\n";
235 for (sort @hosts)
236 {
237 my $url = encode_entities (script_name () . "?action=show_selection;hostname=$_");
238 my $name = encode_entities ($_);
239 print qq# <li><a href="$url">$name</a></li>\n#;
240 }
241 print " </ul>\n";
243 end_html ();
244 } # action_list_hosts
246 =head1 MODULE LOADING
248 This script makes use of the various B<Collectd::Graph::Type::*> modules. If a
249 file like C<foo.rrd> is encountered it tries to load the
250 B<Collectd::Graph::Type::Foo> module and, if that fails, falls back to the
251 B<Collectd::Graph::Type> base class.
253 If you want to create a specialized graph for a certain type, you have to
254 create a new module which inherits from the B<Collectd::Graph::Type> base
255 class. A description of provided (and used) methods can be found in the inline
256 documentation of the B<Collectd::Graph::Type> module.
258 There are other, more specialized, "abstract" classes that possibly better fit
259 your need. Unfortunately they are not yet documented.
261 =over 4
263 =item B<Collectd::Graph::Type::GenericStacked>
265 Specialized class that groups files by their plugin instance and stacks them on
266 top of each other. Example types that inherit from this class are
267 B<Collectd::Graph::Type::Cpu> and B<Collectd::Graph::Type::Memory>.
269 =item B<Collectd::Graph::Type::GenericIO>
271 Specialized class for input/output graphs. This class can only handle files
272 with exactly two data sources, input and output. Example types that inherit
273 from this class are B<Collectd::Graph::Type::DiskOctets> and
274 B<Collectd::Graph::Type::IfOctets>.
276 =back
278 =cut
280 sub action_show_selection
281 {
282 start_html ();
283 show_selector ();
285 my $ident = {};
287 my $all_files;
288 my $types = {};
290 my $id_counter = 0;
292 $all_files = get_selected_files ();
294 if ($Debug)
295 {
296 print "<pre>", Data::Dumper->Dump ([$all_files], ['all_files']), "</pre>\n";
297 }
299 for (@$all_files)
300 {
301 my $file = $_;
302 my $type = ucfirst (lc ($file->{'type'}));
304 $type =~ s/[^A-Za-z_]//g;
305 $type =~ s/_([A-Za-z])/\U$1\E/g;
307 if (!defined ($types->{$type}))
308 {
309 $types->{$type} = tl_load_type ($file->{'type'});
310 if (!$types->{$type})
311 {
312 cluck ("tl_load_type (" . $file->{'type'} . ") failed");
313 next;
314 }
315 }
317 $types->{$type}->addFiles ($file);
318 }
319 #print STDOUT Data::Dumper->Dump ([$types], ['types']);
321 print qq# <table>\n#;
322 for (sort (keys %$types))
323 {
324 my $type = $_;
325 my $graphs_num = $types->{$type}->getGraphsNum ();
327 my $timespan = get_timespan_selection ();
329 for (my $i = 0; $i < $graphs_num; $i++)
330 {
331 my $args = $types->{$type}->getGraphArgs ($i);
332 my $url = encode_entities ("graph.cgi?$args;begin=-$timespan");
333 my $id = sprintf ("graph%04i", $id_counter++);
335 print " <tr>\n";
336 print " <td rowspan=\"$graphs_num\">$type</td>\n" if ($i == 0);
337 print <<EOF;
338 <td>
339 <div class="graph_canvas">
340 <div class="graph_float">
341 <img id="${id}" class="graph_image"
342 alt="A graph"
343 src="$url"
344 ondblclick="nav_handle_dblclick (event);" />
345 <div class="controls zoom">
346 <div title="Earlier"
347 onclick="nav_move_earlier ('${id}');">←</div>
348 <div title="Zoom out"
349 onclick="nav_zoom_out ('${id}');">-</div>
350 <div title="Zoom in"
351 onclick="nav_zoom_in ('${id}');">+</div>
352 <div title="Later"
353 onclick="nav_move_later ('${id}');">→</div>
354 </div>
355 <div class="controls preset">
356 <div title="Show current hour"
357 onclick="nav_time_reset ('${id}', 3600);">H</div>
358 <div title="Show current day"
359 onclick="nav_time_reset ('${id}', 86400);">D</div>
360 <div title="Show current week"
361 onclick="nav_time_reset ('${id}', 7 * 86400);">W</div>
362 <div title="Show current month"
363 onclick="nav_time_reset ('${id}', 31 * 86400);">M</div>
364 <div title="Show current year"
365 onclick="nav_time_reset ('${id}', 366 * 86400);">Y</div>
366 </div>
367 </div>
368 </div>
369 </td>
370 EOF
371 # print qq# <td><img src="$url" /></td>\n#;
372 print " </tr>\n";
373 }
374 }
376 print " </table>\n";
377 end_html ();
378 }
380 =head1 SEE ALSO
382 L<Collectd::Graph::Type>
384 =head1 AUTHOR AND LICENSE
386 Copyright (c) 2008 by Florian Forster
387 E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>. Licensed under the terms of the GNU
388 General Public License, VersionE<nbsp>2 (GPLv2).
390 =cut
392 # vim: set shiftwidth=2 softtabstop=2 tabstop=8 :