1 |
dpavlin |
1 |
#!/usr/bin/perl -w |
2 |
|
|
############################################################ |
3 |
|
|
# |
4 |
|
|
# $Id: rrd-browse.cgi 692 2006-06-26 19:11:14Z nicolaw $ |
5 |
|
|
# rrd-browse.cgi - Graph browser CGI script for RRD::Simple |
6 |
|
|
# |
7 |
|
|
# Copyright 2006,2007 Nicola Worthington |
8 |
|
|
# |
9 |
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
10 |
|
|
# you may not use this file except in compliance with the License. |
11 |
|
|
# You may obtain a copy of the License at |
12 |
|
|
# |
13 |
|
|
# http://www.apache.org/licenses/LICENSE-2.0 |
14 |
|
|
# |
15 |
|
|
# Unless required by applicable law or agreed to in writing, software |
16 |
|
|
# distributed under the License is distributed on an "AS IS" BASIS, |
17 |
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
18 |
|
|
# See the License for the specific language governing permissions and |
19 |
|
|
# limitations under the License. |
20 |
|
|
# |
21 |
|
|
############################################################ |
22 |
|
|
# vim:ts=4:sw=4:tw=78 |
23 |
|
|
|
24 |
|
|
# User defined constants |
25 |
|
|
use constant BASEDIR => '/home/nicolaw/webroot/www/rrd.me.uk'; |
26 |
|
|
use constant RRDURL => ''; |
27 |
|
|
use constant CACHE => 1; |
28 |
|
|
|
29 |
|
|
############################################################ |
30 |
|
|
|
31 |
|
|
|
32 |
|
|
|
33 |
|
|
|
34 |
|
|
use 5.8.0; |
35 |
|
|
use warnings; |
36 |
|
|
use strict; |
37 |
|
|
use CGI; |
38 |
|
|
use CGI::Carp qw(fatalsToBrowser); |
39 |
|
|
use HTML::Template::Expr; |
40 |
|
|
use File::Basename qw(basename); |
41 |
|
|
use File::Spec::Functions qw(tmpdir catdir catfile); |
42 |
|
|
use vars qw(%LIST_CACHE %GRAPH_CACHE); |
43 |
|
|
|
44 |
|
|
use lib (BASEDIR.'/cgi-bin'); |
45 |
|
|
use RRDBrowseCommon qw(slurp by_domain alpha_period list_dir graph_def read_graph_data); |
46 |
|
|
|
47 |
|
|
# Enable some basic caching |
48 |
|
|
if (CACHE) { |
49 |
|
|
# Cache calls to list_dir() and graph_def() |
50 |
|
|
require Memoize; |
51 |
|
|
Memoize::memoize('list_dir', LIST_CACHE => [HASH => \%LIST_CACHE]); |
52 |
|
|
Memoize::memoize('graph_def', SCALAR_CACHE => [HASH => \%GRAPH_CACHE]); |
53 |
|
|
} |
54 |
|
|
|
55 |
|
|
|
56 |
|
|
# Grab CGI paramaters |
57 |
|
|
my $cgi = new CGI; |
58 |
|
|
my %q = $cgi->Vars; |
59 |
|
|
|
60 |
|
|
# cd to the righr location and define directories |
61 |
|
|
my %dir = map { ( $_ => BASEDIR."/$_" ) } qw(data etc graphs cgi-bin thumbnails); |
62 |
|
|
chdir $dir{'cgi-bin'} || die sprintf("Unable to chdir to '%s': %s", $dir{'cgi-bin'}, $!); |
63 |
|
|
|
64 |
|
|
# Create the initial %tmpl data hash |
65 |
|
|
my %tmpl = %ENV; |
66 |
|
|
$tmpl{template} = defined $q{template} && -f $q{template} ? $q{template} : 'export.tmpl'; |
67 |
|
|
$tmpl{title} = ucfirst(basename($tmpl{template},'.tmpl')); $tmpl{title} =~ s/[_\-]/ /g; |
68 |
|
|
$tmpl{self_url} = $cgi->self_url(-absolute => 1, -query_string => 0, -path_info => 0); |
69 |
|
|
$tmpl{rrd_url} = RRDURL; |
70 |
|
|
|
71 |
|
|
# Generate and send an XLS document |
72 |
|
|
if ($q{HOST} && $q{RRD}) { |
73 |
|
|
my $xls = ''; |
74 |
|
|
eval { $xls = generate_xls(catfile($dir{data},$q{HOST},$q{RRD})); }; |
75 |
|
|
|
76 |
|
|
if ($@ || !defined($xls) || !length($xls)) { |
77 |
|
|
$tmpl{error} = $@; |
78 |
|
|
$tmpl{template} = 'error.tmpl'; |
79 |
|
|
|
80 |
|
|
} else { |
81 |
|
|
print $cgi->header( |
82 |
|
|
-type => 'application/vnd.ms-excel', |
83 |
|
|
-content_disposition => sprintf('attachment; filename=%s', 'filename.xls'), |
84 |
|
|
-content_length => length($xls), |
85 |
|
|
-cache_control => 'no-cache', |
86 |
|
|
-expires => '0', |
87 |
|
|
); |
88 |
|
|
print $xls; |
89 |
|
|
exit; |
90 |
|
|
} |
91 |
|
|
} |
92 |
|
|
|
93 |
|
|
# Go read a bunch of stuff from disk to pump in to %tmpl in a moment |
94 |
|
|
my $gdefs = read_graph_data("$dir{etc}/graph.defs"); |
95 |
|
|
my @graphs = list_dir($dir{graphs}); |
96 |
|
|
my $tmpl_cache = { hosts => [], }; |
97 |
|
|
my $html = { last_update => 0, html => '' }; |
98 |
|
|
|
99 |
|
|
# Build the data |
100 |
|
|
for my $host (sort by_domain list_dir($dir{data})) { |
101 |
|
|
next unless -d catfile($dir{data},$host); |
102 |
|
|
|
103 |
|
|
# NEECHI-HACK! |
104 |
|
|
next if defined($q{HOST}) && $q{HOST} ne $host; |
105 |
|
|
next if defined($q{LIKE}) && $tmpl{template} =~ /^by_host\.[^\.]+$/i && $host !~ /$q{LIKE}/i; |
106 |
|
|
|
107 |
|
|
(my $node = $host) =~ s/\..*//; |
108 |
|
|
(my $domain = $host) =~ s/^.*?\.//; |
109 |
|
|
(my $domain2 = $domain) =~ s/[^a-zA-Z0-9\_]/_/g; |
110 |
|
|
(my $host2 = $host) =~ s/[^a-zA-Z0-9\_]/_/g; |
111 |
|
|
|
112 |
|
|
my %host = ( node => $node, |
113 |
|
|
host => $host, host2 => $host2, |
114 |
|
|
domain => $domain, domain2 => $domain2, ); |
115 |
|
|
|
116 |
|
|
# Build a hash of potential files that users can slurp() or include |
117 |
|
|
# in their output template on a per host basis. |
118 |
|
|
for my $file (grep(/\.(?:te?xt|s?html?|xslt?|xml|css|tmpl)$/i, |
119 |
|
|
glob("$dir{data}/$host/include*.*"))) { |
120 |
|
|
(my $base = basename($file)) =~ s/\./_/g; |
121 |
|
|
$host{$base} = $file; |
122 |
|
|
} |
123 |
|
|
|
124 |
|
|
push @{$tmpl_cache->{hosts}}, \%host; |
125 |
|
|
} |
126 |
|
|
|
127 |
|
|
# Merge cache data in |
128 |
|
|
$tmpl{hosts} = $tmpl_cache->{hosts}; |
129 |
|
|
|
130 |
|
|
# Render the output |
131 |
|
|
if (exists $q{DEBUG} && $q{DEBUG} eq 'insecure') { |
132 |
|
|
require Data::Dumper; |
133 |
|
|
$tmpl{DEBUG} = Data::Dumper::Dumper(\%tmpl); |
134 |
|
|
} |
135 |
|
|
my $template = HTML::Template::Expr->new( |
136 |
|
|
filename => $tmpl{template}, |
137 |
|
|
associate => $cgi, |
138 |
|
|
case_sensitive => 1, |
139 |
|
|
loop_context_vars => 1, |
140 |
|
|
max_includes => 5, |
141 |
|
|
global_vars => 1, |
142 |
|
|
die_on_bad_params => 0, |
143 |
|
|
functions => { |
144 |
|
|
slurp => \&slurp, |
145 |
|
|
like => sub { return defined($_[0]) && defined($_[1]) && $_[0] =~ /$_[1]/i ? 1 : 0; }, |
146 |
|
|
not => sub { !$_[0]; }, |
147 |
|
|
equal_or_like => sub { |
148 |
|
|
return 1 if (!defined($_[1]) || !length($_[1])) && (!defined($_[2]) || !length($_[2])); |
149 |
|
|
#(warn "$_[0] eq $_[1]\n" && return 1) if defined $_[1] && "$_[0]" eq "$_[1]"; |
150 |
|
|
(return 1) if defined $_[1] && "$_[0]" eq "$_[1]"; |
151 |
|
|
return 1 if defined $_[2] && "$_[0]" =~ /$_[2]/; |
152 |
|
|
return 0; |
153 |
|
|
}, |
154 |
|
|
}, |
155 |
|
|
); |
156 |
|
|
$template->param(\%tmpl); |
157 |
|
|
|
158 |
|
|
$html->{html} = $template->output(); |
159 |
|
|
$html->{last_update} = time; |
160 |
|
|
print $cgi->header(-content => 'text/html'), $html->{html}; |
161 |
|
|
|
162 |
|
|
exit; |
163 |
|
|
|
164 |
|
|
1; |
165 |
|
|
|
166 |
|
|
|
167 |
|
|
sub generate_xls { |
168 |
|
|
my $rrdfile = shift; |
169 |
|
|
return unless defined($rrdfile) && -f $rrdfile; |
170 |
|
|
|
171 |
|
|
require RRDs; |
172 |
|
|
require RRD::Simple; |
173 |
|
|
require Spreadsheet::WriteExcel; |
174 |
|
|
|
175 |
|
|
# Create an RRD object |
176 |
|
|
my $rrd = RRD::Simple->new(file => $rrdfile) |
177 |
|
|
|| die "Unable to instanciate RRD::Simple object for file '$rrdfile'"; |
178 |
|
|
my @sources = $rrd->sources; |
179 |
|
|
my $info = $rrd->info; |
180 |
|
|
|
181 |
|
|
# Create a workbook |
182 |
|
|
open my $fh, '>', \my $xls or die "Failed to open filehandle: $!"; |
183 |
|
|
my $workbook = Spreadsheet::WriteExcel->new($fh); |
184 |
|
|
|
185 |
|
|
my %labels = ( |
186 |
|
|
'300-1' => 'Daily', |
187 |
|
|
'300-6' => 'Weekly', |
188 |
|
|
'300-24' => 'Monthly', |
189 |
|
|
'300-288' => 'Annual', |
190 |
|
|
); |
191 |
|
|
|
192 |
|
|
# Create the overview worksheet |
193 |
|
|
my @sheet; |
194 |
|
|
OVERVIEW: { |
195 |
|
|
my $sheet = $workbook->add_worksheet('Summary'); |
196 |
|
|
$sheet->set_zoom(80); |
197 |
|
|
$sheet->freeze_panes(1, 1); |
198 |
|
|
my ($row, $col) = (0, 0); |
199 |
|
|
|
200 |
|
|
my @fields = sort(keys(%{$info->{rra}->[0]})); |
201 |
|
|
$sheet->write_row($row, $col, [( '', @fields )] ); |
202 |
|
|
|
203 |
|
|
for my $rra (@{$info->{rra}}) { $row++; |
204 |
|
|
my $label = sprintf('%s %s', |
205 |
|
|
(exists $labels{"$info->{step}-$rra->{pdp_per_row}"} ? |
206 |
|
|
$labels{"$info->{step}-$rra->{pdp_per_row}"} : rand(999) ), |
207 |
|
|
ucfirst(lc($rra->{cf}))); |
208 |
|
|
$sheet->write_row($row, $col, [( $label, map { $rra->{$_} } @fields )] ); |
209 |
|
|
} |
210 |
|
|
|
211 |
|
|
push @sheet, $sheet; |
212 |
|
|
} |
213 |
|
|
|
214 |
|
|
# Create the detail worksheets |
215 |
|
|
for my $rra (@{$info->{rra}}) { |
216 |
|
|
my $label = sprintf('%s %s', |
217 |
|
|
(exists $labels{"$info->{step}-$rra->{pdp_per_row}"} ? |
218 |
|
|
$labels{"$info->{step}-$rra->{pdp_per_row}"} : rand(999) ), |
219 |
|
|
ucfirst(lc($rra->{cf}))); |
220 |
|
|
|
221 |
|
|
my $sheet = $workbook->add_worksheet($label); |
222 |
|
|
$sheet->set_zoom(80); |
223 |
|
|
$sheet->freeze_panes(1, 1); |
224 |
|
|
my ($row, $col) = (0, 0); |
225 |
|
|
|
226 |
|
|
my ($start,$step,$names,$data) = RRDs::fetch($rrdfile, $rra->{cf}, '-s', 60*60*24*365*10); |
227 |
|
|
$sheet->write_row($row, $col, [( '', @{$names} )] ); |
228 |
|
|
for my $line (@{$data}) { $row++; |
229 |
|
|
$sheet->write_row($row, $col, [( '', @{$line} )] ); |
230 |
|
|
} |
231 |
|
|
|
232 |
|
|
# my ($start,$step,$names,$data) = RRDs::fetch ... |
233 |
|
|
# print "Start: ", scalar localtime($start), " ($start)\n"; |
234 |
|
|
# print "Step size: $step seconds\n"; |
235 |
|
|
# print "DS names: ", join (", ", @$names)."\n"; |
236 |
|
|
# print "Data points: ", $#$data + 1, "\n"; |
237 |
|
|
# print "Data:\n"; |
238 |
|
|
# foreach my $line (@$data) { |
239 |
|
|
# print " ", scalar localtime($start), " ($start) "; |
240 |
|
|
# $start += $step; |
241 |
|
|
# foreach my $val (@$line) { |
242 |
|
|
# printf "%12.1f ", $val; |
243 |
|
|
# } |
244 |
|
|
# print "\n"; |
245 |
|
|
# } |
246 |
|
|
|
247 |
|
|
push @sheet, $sheet; |
248 |
|
|
} |
249 |
|
|
|
250 |
|
|
$workbook->close; |
251 |
|
|
return $xls; |
252 |
|
|
} |
253 |
|
|
|