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