File Coverage

blib/lib/RRD/CGI/Image.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package RRD::CGI::Image;
2              
3 1     1   20015 use warnings;
  1         2  
  1         28  
4 1     1   4 use strict;
  1         2  
  1         31  
5 1     1   732 use Spiffy '-base';
  1         5084  
  1         5  
6 1     1   640 use RRDs;
  0            
  0            
7             use Carp;
8             use HTML::Entities;
9             use URI::Escape;
10             use POSIX 'tzset';
11              
12             our $VERSION = '0.01';
13              
14             field 'output_file' => '-'; # STDOUT
15             field 'params' => ();
16             field 'rrd_base' => '/var/rrd/';
17             field 'error_img' => '/var/www/graphing_error.png';
18             field 'logging' => 0;
19              
20             sub print_graph {
21             my $self = shift;
22             carp "print_graph( %cgi_params_with_values ) called without args" unless @_;
23             croak "rrd_base: " . $self->rrd_base . " does not exist." unless -d $self->rrd_base;
24              
25             $self->params( \@_ );
26             $self->normalize_params;
27              
28             WWW $self if $self->logging;
29            
30             RRDs::graph( $self->output_file, @{ $self->params } );
31             $self->print_error_img if RRDs::error();
32             }
33              
34             sub normalize_params {
35             my $self = shift;
36              
37             # User is not allowed to submit an output filename via query string.
38             # It must be set using output_file() instead.
39             #
40             $self->print_error_img( "Bad first argument" ) unless $self->params->[0] =~ /^\s*--?\w/;
41              
42             my @rrd_args;
43             my @params = @{ $self->params };
44              
45             while ( my ($k, $v) = splice @params, 0, 2 ) {
46             # translate   æ, etc.
47             $k = decode_entities( $k );
48             $v = decode_entities( $v ) if defined $v;
49              
50             # translate %20, %3a, etc.
51             $k = uri_unescape( $k );
52             $v = uri_unescape( $v ) if defined $v;
53              
54             # User not allowed to poke around above the rrd_base() dir.
55             $self->print_error_img( "Bad DEF path" ) if $k =~ /^DEF .* \.\./x;
56             $self->print_error_img( "Bad DEF path" ) if defined $v && $v =~ /^DEF .* \.\./x;
57              
58             # Insert rrd_base() into the DEF statement
59             if ( $k =~ /^DEF/ ) {
60             $v = $self->rrd_base . $v;
61             }
62              
63             # Set and strip any timezone params - it's not a valid RRDs::graph() argument
64             if ( $k =~ /tz/i ) {
65             $self->tz( $v );
66             next;
67             }
68            
69             # Some of the keypair args will be split by CGI's param handler. We need to join the
70             # args like "DEF:ds0=..." back together.
71             #
72             # Args like --height=120 should turn into a Perl key-value tuple instead.
73             if ( $k =~ /^-/ ) {
74             push @rrd_args, defined $v && $v ne '' ? ( $k => $v ) : $k;
75             }
76             else {
77             push @rrd_args, defined $v && $v ne '' ? "$k=$v" : $k;
78             }
79             }
80              
81             return $self->params( \@rrd_args );
82             }
83              
84             sub tz {
85             my $self = shift;
86             $self->{tz} = shift if $_[0];
87              
88             if ( $self->{tz} ) {
89             $ENV{TZ} = $self->{tz};
90             tzset();
91             }
92              
93             return $self->{tz};
94             }
95              
96             sub print_error_img {
97             my $self = shift;
98            
99             my $errmsg = shift || RRDs::error() || "Unknown error";
100             warn $errmsg;
101            
102             open F, $self->error_img or warn "error_img: " . $self->error_img . " not found";
103             print ;
104             close F;
105             }
106              
107             =head1 NAME
108              
109             RRD::CGI::Image - accept CGI-style rrdgraph args to build and print image
110              
111             =head1 NOTICE
112              
113             This is development code - the API may change!
114              
115             =head1 SYNOPSIS
116              
117             use RRD::CGI::Image;
118             use CGI qw[Vars header];
119              
120             my $image = RRD::CGI::Image->new(
121             rrd_base => '/var/rrd',
122             error_img => '/var/www/.../path/to/graphing_error.png',
123             );
124              
125             print header( 'image/png' );
126             $image->print_graph( Vars() );
127              
128             =head1 METHODS
129              
130             =head2 new() - create new object to handle your bidding
131              
132             Behaves like any other new(), really.
133              
134             =head2 print_graph() - accepts CGI params, parses them, and prints a graph
135              
136             The graph will be sent to the location specified by output_file(); STDOUT by default.
137              
138             In addition to the regular rrdgraph options, you can also add a B param which will render the graph in the given timezone.
139              
140             The key-value pairs need a little translation to get them successfully passed through the URL. Your URL or CGI library will probably handle most of this automatically. Here's the full examplanation.
141              
142             Let's convert a fairly standard set of args for RRDs::graph() to GET-style CGI params, starting with:
143              
144             RRDs::graph(
145             '/path/to/output/file.png',
146             '--start' => '-1d',
147             '--end' => 'now',
148             '--height' => 200,
149             '--width' => 600,
150             '--imgformat' => 'PNG',
151             '--lower-limit' => 0,
152             '--title' => 'This is a title',
153             '--vertical-label' => 'bps',
154             'DEF:ds0' => '/var/rrd/router/data/router.example.com/gigabitethernet101.rrd:ds0:MAX',
155             'DEF:ds1' => '/var/rrd/router/data/router.example.com/gigabitethernet101.rrd:ds1:MAX',
156             'CDEF:in:ds0,8,*', # convert bytes to bits
157             'CDEF:out:ds1,8,*',
158             'LINE1:in#33ee33:Input',
159             'LINE1:out#0000ff:Output',
160             );
161              
162             First, completely drop the first argument. We don't need an output filename anymore - that's handled by output_file() instead.
163              
164             Next, change the Perl hash-style key-value params to from B value> to CGI-style: B;
165              
166             Next, delete the first half of the path - the rrd_base() - from your DEF statements. That will change the DEF lines to:
167              
168             'DEF:ds0' => 'router.example.com/gigabitethernet101.rrd:ds0:MAX',
169             'DEF:ds1' => 'router.example.com/gigabitethernet101.rrd:ds1:MAX',
170              
171             Finally, make sure your params are encoded so they pass through the CGI interface. URI::Escape::uri_escape() will, for example, convert the hashmarks in LINE1 statements from # to %23. Here's what the LINE1 entries should look like after encoding:
172              
173             'LINE1:in%2333ee33:Input',
174             'LINE1:out%230000ff:Output',
175              
176             Many of these will be handled automatically if you're relying on a CGI or URL module to construct the URL for you.
177              
178             =head2 output_file() - where will the new graph be created?
179              
180             Defaults to STDOUT (-).
181              
182             =head2 rrd_base() - pathname to your RRD files.
183              
184             Users will be able to specify partial paths to the RRDs beneath this directory in their DEF declarations but they
185             will be sandboxed into this directory. Don't be too permissive - it's a security risk.
186              
187             Must end with "/".
188              
189             =head2 error_img() - pathname (not URL) to an image that says "an error happened".
190              
191             Check your webserver's logs to see what went wrong.
192              
193             =head2 tz() - get/set the timezone for the graph.
194              
195             Pertinent if you have RRDs in different timezones.
196              
197             =head2 normalize_params() - clean up and reassemble the input params
198              
199             Called internally.
200              
201             =head2 print_error_img( $opt_errmsg ) - prints the error_img() file to output_file() location (STDOUT by default) and writes the error to log
202              
203             =head2 logging() - if true, will print the normalized (processed) params to log
204              
205             =head1 AUTHOR
206              
207             Joshua Keroes, C<< >>
208              
209             =head1 BUGS
210              
211             Please report any bugs or feature requests to C, or through
212             the web interface at L. I will be notified, and then you'll
213             automatically be notified of progress on your bug as I make changes.
214              
215             =head1 SUPPORT
216              
217             You can find documentation for this module with the perldoc command.
218              
219             perldoc RRD::CGI::Image
220              
221             You can also look for information at:
222              
223             =over 4
224              
225             =item * RT: CPAN's request tracker
226              
227             L
228              
229             =item * AnnoCPAN: Annotated CPAN documentation
230              
231             L
232              
233             =item * CPAN Ratings
234              
235             L
236              
237             =item * Search CPAN
238              
239             L
240              
241             =back
242              
243             =head1 SEE ALSO
244              
245             L
246              
247             =head1 COPYRIGHT & LICENSE
248              
249             Copyright 2008 Joshua Keroes, all rights reserved.
250              
251             This program is free software; you can redistribute it and/or modify it
252             under the same terms as Perl itself.
253              
254              
255             =cut
256              
257             1; # End of RRD::CGI::Image
258              
259             __END__