line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
############################################################ |
2
|
|
|
|
|
|
|
# |
3
|
|
|
|
|
|
|
# $Id: Simple.pm 1100 2008-01-24 17:39:35Z nicolaw $ |
4
|
|
|
|
|
|
|
# RRD::Simple - Simple interface to create and store data in RRD files |
5
|
|
|
|
|
|
|
# |
6
|
|
|
|
|
|
|
# Copyright 2005,2006,2007,2008 Nicola Worthington |
7
|
|
|
|
|
|
|
# |
8
|
|
|
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
9
|
|
|
|
|
|
|
# you may not use this file except in compliance with the License. |
10
|
|
|
|
|
|
|
# You may obtain a copy of the License at |
11
|
|
|
|
|
|
|
# |
12
|
|
|
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0 |
13
|
|
|
|
|
|
|
# |
14
|
|
|
|
|
|
|
# Unless required by applicable law or agreed to in writing, software |
15
|
|
|
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS, |
16
|
|
|
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
17
|
|
|
|
|
|
|
# See the License for the specific language governing permissions and |
18
|
|
|
|
|
|
|
# limitations under the License. |
19
|
|
|
|
|
|
|
# |
20
|
|
|
|
|
|
|
############################################################ |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
package RRD::Simple; |
23
|
|
|
|
|
|
|
# vim:ts=8:sw=8:tw=78 |
24
|
|
|
|
|
|
|
|
25
|
1
|
|
|
1
|
|
17660
|
use strict; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
46
|
|
26
|
|
|
|
|
|
|
require Exporter; |
27
|
1
|
|
|
1
|
|
406
|
use RRDs; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
use POSIX qw(strftime); # Used for strftime in graph() method |
29
|
|
|
|
|
|
|
use Carp qw(croak cluck confess carp); |
30
|
|
|
|
|
|
|
use File::Spec qw(); # catfile catdir updir path rootdir tmpdir |
31
|
|
|
|
|
|
|
use File::Basename qw(fileparse dirname basename); |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
use vars qw($VERSION $DEBUG $DEFAULT_DSTYPE |
34
|
|
|
|
|
|
|
@EXPORT @EXPORT_OK %EXPORT_TAGS @ISA); |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
$VERSION = '1.44' || sprintf('%d', q$Revision: 1100 $ =~ /(\d+)/g); |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
@ISA = qw(Exporter); |
39
|
|
|
|
|
|
|
@EXPORT = qw(); |
40
|
|
|
|
|
|
|
@EXPORT_OK = qw(create update last_update graph info rename_source |
41
|
|
|
|
|
|
|
add_source sources retention_period last_values |
42
|
|
|
|
|
|
|
heartbeat); |
43
|
|
|
|
|
|
|
# delete_source minimum maximum |
44
|
|
|
|
|
|
|
%EXPORT_TAGS = (all => \@EXPORT_OK); |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
$DEBUG ||= $ENV{DEBUG} ? 1 : 0; |
47
|
|
|
|
|
|
|
$DEFAULT_DSTYPE ||= exists $ENV{DEFAULT_DSTYPE} |
48
|
|
|
|
|
|
|
? $ENV{DEFAULT_DSTYPE} : 'GAUGE'; |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
my $objstore = {}; |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
# |
55
|
|
|
|
|
|
|
# Methods |
56
|
|
|
|
|
|
|
# |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
# Create a new object |
59
|
|
|
|
|
|
|
sub new { |
60
|
|
|
|
|
|
|
TRACE(">>> new()"); |
61
|
|
|
|
|
|
|
ref(my $class = shift) && croak 'Class name required'; |
62
|
|
|
|
|
|
|
croak 'Odd number of elements passed when even was expected' if @_ % 2; |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
# Conjure up an invisible object |
65
|
|
|
|
|
|
|
my $self = bless \(my $dummy), $class; |
66
|
|
|
|
|
|
|
$objstore->{_refaddr($self)} = {@_}; |
67
|
|
|
|
|
|
|
my $stor = $objstore->{_refaddr($self)}; |
68
|
|
|
|
|
|
|
#my $self = { @_ }; |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
# - Added "file" support in 1.42 - see sub _guess_filename. |
71
|
|
|
|
|
|
|
# - Added "on_missing_ds"/"on_missing_source" support in 1.44 |
72
|
|
|
|
|
|
|
# - Added "tmpdir" support in 1.44 |
73
|
|
|
|
|
|
|
my @validkeys = qw(rrdtool cf default_dstype default_dst tmpdir |
74
|
|
|
|
|
|
|
file on_missing_ds on_missing_source); |
75
|
|
|
|
|
|
|
my $validkeys = join('|', @validkeys); |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
cluck('Unrecognised parameters passed: '. |
78
|
|
|
|
|
|
|
join(', ',grep(!/^$validkeys$/,keys %{$stor}))) |
79
|
|
|
|
|
|
|
if (grep(!/^$validkeys$/,keys %{$stor}) && $^W); |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
$stor->{rrdtool} = _find_binary(exists $stor->{rrdtool} ? |
82
|
|
|
|
|
|
|
$stor->{rrdtool} : 'rrdtool'); |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# Check that "default_dstype" isn't complete rubbish (validation from v1.44+) |
85
|
|
|
|
|
|
|
# GAUGE | COUNTER | DERIVE | ABSOLUTE | COMPUTE |
86
|
|
|
|
|
|
|
# http://oss.oetiker.ch/rrdtool/doc/rrdcreate.en.html |
87
|
|
|
|
|
|
|
$stor->{default_dstype} ||= $stor->{default_dst}; |
88
|
|
|
|
|
|
|
croak "Invalid value passed in parameter default_dstype; '$stor->{default_dstype}'" |
89
|
|
|
|
|
|
|
if defined $stor->{default_dstype} |
90
|
|
|
|
|
|
|
&& $stor->{default_dstype} !~ /^(GAUGE|COUNTER|DERIVE|ABSOLUTE|COMPUTE|[A-Z]{1,10})$/i; |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
# Check that "on_missing_ds" isn't complete rubbish. |
93
|
|
|
|
|
|
|
# Added "on_missing_ds"/"on_missing_source" support in 1.44 |
94
|
|
|
|
|
|
|
$stor->{on_missing_ds} ||= $stor->{on_missing_source}; |
95
|
|
|
|
|
|
|
if (defined $stor->{on_missing_ds}) { |
96
|
|
|
|
|
|
|
$stor->{on_missing_ds} = lc($stor->{on_missing_ds}); |
97
|
|
|
|
|
|
|
croak "Invalid value passed in parameter on_missing_ds; '$stor->{on_missing_ds}'" |
98
|
|
|
|
|
|
|
if $stor->{on_missing_ds} !~ /^\s*(add|ignore|die|croak)\s*$/i; |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
$stor->{on_missing_ds} ||= 'add'; # default to add |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
#$stor->{cf} ||= [ qw(AVERAGE MIN MAX LAST) ]; |
103
|
|
|
|
|
|
|
# By default, now only create RRAs for AVERAGE and MAX, like |
104
|
|
|
|
|
|
|
# mrtg v2.13.2. This is to save disk space and processing time |
105
|
|
|
|
|
|
|
# during updates etc. |
106
|
|
|
|
|
|
|
$stor->{cf} ||= [ qw(AVERAGE MAX) ]; |
107
|
|
|
|
|
|
|
$stor->{cf} = [ $stor->{cf} ] if !ref($stor->{cf}); |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
DUMP($class,$self); |
110
|
|
|
|
|
|
|
DUMP('$stor',$stor); |
111
|
|
|
|
|
|
|
return $self; |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
# Create a new RRD file |
116
|
|
|
|
|
|
|
sub create { |
117
|
|
|
|
|
|
|
TRACE(">>> create()"); |
118
|
|
|
|
|
|
|
my $self = shift; |
119
|
|
|
|
|
|
|
unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { |
120
|
|
|
|
|
|
|
unshift @_, $self unless $self eq __PACKAGE__; |
121
|
|
|
|
|
|
|
$self = new __PACKAGE__; |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
my $stor = $objstore->{_refaddr($self)}; |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
# |
127
|
|
|
|
|
|
|
# |
128
|
|
|
|
|
|
|
# |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
# Grab or guess the filename |
131
|
|
|
|
|
|
|
my $rrdfile = $stor->{file}; |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
# Odd number of values and first is not a valid scheme |
134
|
|
|
|
|
|
|
# then the first value is likely an RRD file name. |
135
|
|
|
|
|
|
|
if (@_ % 2 && !_valid_scheme($_[0])) { |
136
|
|
|
|
|
|
|
$rrdfile = shift; |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
# Even number of values and the second value is a valid |
139
|
|
|
|
|
|
|
# scheme then the first value is likely an RRD file name. |
140
|
|
|
|
|
|
|
} elsif (!(@_ % 2) && _valid_scheme($_[1])) { |
141
|
|
|
|
|
|
|
$rrdfile = shift; |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
# If we still don't have an RRD file name then try and |
144
|
|
|
|
|
|
|
# guess what it is |
145
|
|
|
|
|
|
|
} elsif (!defined $rrdfile) { |
146
|
|
|
|
|
|
|
$rrdfile = _guess_filename($stor); |
147
|
|
|
|
|
|
|
} |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
# |
150
|
|
|
|
|
|
|
# |
151
|
|
|
|
|
|
|
# |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
# Barf if the rrd file already exists |
154
|
|
|
|
|
|
|
croak "RRD file '$rrdfile' already exists" if -f $rrdfile; |
155
|
|
|
|
|
|
|
TRACE("Using filename: $rrdfile"); |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
# We've been given a scheme specifier |
158
|
|
|
|
|
|
|
# Until v1.32 'year' was the default. As of v1.33 'mrtg' |
159
|
|
|
|
|
|
|
# is the new default scheme. |
160
|
|
|
|
|
|
|
#my $scheme = 'year'; |
161
|
|
|
|
|
|
|
my $scheme = 'mrtg'; |
162
|
|
|
|
|
|
|
if (@_ % 2 && _valid_scheme($_[0])) { |
163
|
|
|
|
|
|
|
$scheme = _valid_scheme($_[0]); |
164
|
|
|
|
|
|
|
shift @_; |
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
TRACE("Using scheme: $scheme"); |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
croak 'Odd number of elements passed when even was expected' if @_ % 2; |
169
|
|
|
|
|
|
|
my %ds = @_; |
170
|
|
|
|
|
|
|
DUMP('%ds',\%ds); |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
my $rrdDef = _rrd_def($scheme); |
173
|
|
|
|
|
|
|
my @def = ('-b', time - _seconds_in($scheme,120)); |
174
|
|
|
|
|
|
|
push @def, '-s', ($rrdDef->{step} || 300); |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
# Add data sources |
177
|
|
|
|
|
|
|
for my $ds (sort keys %ds) { |
178
|
|
|
|
|
|
|
$ds =~ s/[^a-zA-Z0-9_-]//g; |
179
|
|
|
|
|
|
|
push @def, sprintf('DS:%s:%s:%s:%s:%s', |
180
|
|
|
|
|
|
|
substr($ds,0,19), |
181
|
|
|
|
|
|
|
uc($ds{$ds}), |
182
|
|
|
|
|
|
|
($rrdDef->{heartbeat} || 600), |
183
|
|
|
|
|
|
|
'U','U' |
184
|
|
|
|
|
|
|
); |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
# Add RRA definitions |
188
|
|
|
|
|
|
|
my %cf; |
189
|
|
|
|
|
|
|
for my $cf (@{$stor->{cf}}) { |
190
|
|
|
|
|
|
|
$cf{$cf} = $rrdDef->{rra}; |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
for my $cf (sort keys %cf) { |
193
|
|
|
|
|
|
|
for my $rra (@{$cf{$cf}}) { |
194
|
|
|
|
|
|
|
push @def, sprintf('RRA:%s:%s:%s:%s', |
195
|
|
|
|
|
|
|
$cf, 0.5, $rra->{step}, $rra->{rows} |
196
|
|
|
|
|
|
|
); |
197
|
|
|
|
|
|
|
} |
198
|
|
|
|
|
|
|
} |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
DUMP('@def',\@def); |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
# Pass to RRDs for execution |
203
|
|
|
|
|
|
|
my @rtn = RRDs::create($rrdfile, @def); |
204
|
|
|
|
|
|
|
my $error = RRDs::error(); |
205
|
|
|
|
|
|
|
croak($error) if $error; |
206
|
|
|
|
|
|
|
DUMP('RRDs::info',RRDs::info($rrdfile)); |
207
|
|
|
|
|
|
|
return wantarray ? @rtn : \@rtn; |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
# Update an RRD file with some data values |
212
|
|
|
|
|
|
|
sub update { |
213
|
|
|
|
|
|
|
TRACE(">>> update()"); |
214
|
|
|
|
|
|
|
my $self = shift; |
215
|
|
|
|
|
|
|
unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { |
216
|
|
|
|
|
|
|
unshift @_, $self unless $self eq __PACKAGE__; |
217
|
|
|
|
|
|
|
$self = new __PACKAGE__; |
218
|
|
|
|
|
|
|
} |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
my $stor = $objstore->{_refaddr($self)}; |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
# |
223
|
|
|
|
|
|
|
# |
224
|
|
|
|
|
|
|
# |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
# Grab or guess the filename |
227
|
|
|
|
|
|
|
my $rrdfile = $stor->{file}; |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
# Odd number of values and first is does not look |
230
|
|
|
|
|
|
|
# like a recent unix time stamp then the first value |
231
|
|
|
|
|
|
|
# is likely to be an RRD file name. |
232
|
|
|
|
|
|
|
if (@_ % 2 && $_[0] !~ /^[1-9][0-9]{8,10}$/i) { |
233
|
|
|
|
|
|
|
$rrdfile = shift; |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
# Even number of values and the second value looks like |
236
|
|
|
|
|
|
|
# a recent unix time stamp then the first value is |
237
|
|
|
|
|
|
|
# likely to be an RRD file name. |
238
|
|
|
|
|
|
|
} elsif (!(@_ % 2) && $_[1] =~ /^[1-9][0-9]{8,10}$/i) { |
239
|
|
|
|
|
|
|
$rrdfile = shift; |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
# If we still don't have an RRD file name then try and |
242
|
|
|
|
|
|
|
# guess what it is |
243
|
|
|
|
|
|
|
} elsif (!defined $rrdfile) { |
244
|
|
|
|
|
|
|
$rrdfile = _guess_filename($stor); |
245
|
|
|
|
|
|
|
} |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
# |
248
|
|
|
|
|
|
|
# |
249
|
|
|
|
|
|
|
# |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
# We've been given an update timestamp |
252
|
|
|
|
|
|
|
my $time = time(); |
253
|
|
|
|
|
|
|
if (@_ % 2 && $_[0] =~ /^([1-9][0-9]{8,10})$/i) { |
254
|
|
|
|
|
|
|
$time = $1; |
255
|
|
|
|
|
|
|
shift @_; |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
TRACE("Using update time: $time"); |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
# Try to automatically create it |
260
|
|
|
|
|
|
|
unless (-f $rrdfile) { |
261
|
|
|
|
|
|
|
my $default_dstype = defined $stor->{default_dstype} ? $stor->{default_dstype} : $DEFAULT_DSTYPE; |
262
|
|
|
|
|
|
|
cluck("RRD file '$rrdfile' does not exist; attempting to create it ", |
263
|
|
|
|
|
|
|
"using default DS type of '$default_dstype'") if $^W; |
264
|
|
|
|
|
|
|
my @args; |
265
|
|
|
|
|
|
|
for (my $i = 0; $i < @_; $i++) { |
266
|
|
|
|
|
|
|
push @args, ($_[$i],$default_dstype) unless $i % 2; |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
$self->create($rrdfile,@args); |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
croak "RRD file '$rrdfile' does not exist" unless -f $rrdfile; |
272
|
|
|
|
|
|
|
TRACE("Using filename: $rrdfile"); |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
croak 'Odd number of elements passed when even was expected' if @_ % 2; |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
my %ds; |
277
|
|
|
|
|
|
|
while (my $ds = shift(@_)) { |
278
|
|
|
|
|
|
|
$ds =~ s/[^a-zA-Z0-9_-]//g; |
279
|
|
|
|
|
|
|
$ds = substr($ds,0,19); |
280
|
|
|
|
|
|
|
$ds{$ds} = shift(@_); |
281
|
|
|
|
|
|
|
$ds{$ds} = 'U' if !defined($ds{$ds}); |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
DUMP('%ds',\%ds); |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
# Validate the data source names as we add them |
286
|
|
|
|
|
|
|
my @sources = $self->sources($rrdfile); |
287
|
|
|
|
|
|
|
for my $ds (sort keys %ds) { |
288
|
|
|
|
|
|
|
# Check the data source names |
289
|
|
|
|
|
|
|
if (!grep(/^$ds$/,@sources)) { |
290
|
|
|
|
|
|
|
TRACE("Supplied data source '$ds' does not exist in pre-existing ". |
291
|
|
|
|
|
|
|
"RRD data source list: ". join(', ',@sources)); |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
# If someone got the case wrong, remind and correct them |
294
|
|
|
|
|
|
|
if (grep(/^$ds$/i,@sources)) { |
295
|
|
|
|
|
|
|
cluck("Data source '$ds' does not exist; automatically ", |
296
|
|
|
|
|
|
|
"correcting it to '",(grep(/^$ds$/i,@sources))[0], |
297
|
|
|
|
|
|
|
"' instead") if $^W; |
298
|
|
|
|
|
|
|
$ds{(grep(/^$ds$/i,@sources))[0]} = $ds{$ds}; |
299
|
|
|
|
|
|
|
delete $ds{$ds}; |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
# If it's not just a case sensitivity typo and the data source |
302
|
|
|
|
|
|
|
# name really doesn't exist in this RRD file at all, regardless |
303
|
|
|
|
|
|
|
# of case, then ... |
304
|
|
|
|
|
|
|
} else { |
305
|
|
|
|
|
|
|
# Ignore the offending missing data source name |
306
|
|
|
|
|
|
|
if ($stor->{on_missing_ds} eq 'ignore') { |
307
|
|
|
|
|
|
|
TRACE("on_missing_ds = ignore; ignoring data supplied for missing data source '$ds'"); |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
# Fall on our bum and die horribly if requested to do so |
310
|
|
|
|
|
|
|
} elsif ($stor->{on_missing_ds} eq 'die' || $stor->{on_missing_ds} eq 'croak') { |
311
|
|
|
|
|
|
|
croak "Supplied data source '$ds' does not exist in RRD file '$rrdfile'"; |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
# Default behaviour is to automatically add the new data source |
314
|
|
|
|
|
|
|
# to the RRD file in order to preserve the existing default |
315
|
|
|
|
|
|
|
# functionality of RRD::Simple |
316
|
|
|
|
|
|
|
} else { |
317
|
|
|
|
|
|
|
TRACE("on_missing_ds = add (or not set at all/default); ". |
318
|
|
|
|
|
|
|
"automatically adding new data source '$ds'"); |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
# Otherwise add any missing or new data sources on the fly |
321
|
|
|
|
|
|
|
# Decide what DS type and heartbeat to use |
322
|
|
|
|
|
|
|
my $info = RRDs::info($rrdfile); |
323
|
|
|
|
|
|
|
my $error = RRDs::error(); |
324
|
|
|
|
|
|
|
croak($error) if $error; |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
my %dsTypes; |
327
|
|
|
|
|
|
|
for my $key (grep(/^ds\[.+?\]\.type$/,keys %{$info})) { |
328
|
|
|
|
|
|
|
$dsTypes{$info->{$key}}++; |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
DUMP('%dsTypes',\%dsTypes); |
331
|
|
|
|
|
|
|
my $dstype = (sort { $dsTypes{$b} <=> $dsTypes{$a} } |
332
|
|
|
|
|
|
|
keys %dsTypes)[0]; |
333
|
|
|
|
|
|
|
TRACE("\$dstype = $dstype"); |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
$self->add_source($rrdfile,$ds,$dstype); |
336
|
|
|
|
|
|
|
} |
337
|
|
|
|
|
|
|
} |
338
|
|
|
|
|
|
|
} |
339
|
|
|
|
|
|
|
} |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
# Build the def |
342
|
|
|
|
|
|
|
my @def = ('--template'); |
343
|
|
|
|
|
|
|
push @def, join(':',sort keys %ds); |
344
|
|
|
|
|
|
|
push @def, join(':',$time,map { $ds{$_} } sort keys %ds); |
345
|
|
|
|
|
|
|
DUMP('@def',\@def); |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
# Pass to RRDs to execute the update |
348
|
|
|
|
|
|
|
my @rtn = RRDs::update($rrdfile, @def); |
349
|
|
|
|
|
|
|
my $error = RRDs::error(); |
350
|
|
|
|
|
|
|
croak($error) if $error; |
351
|
|
|
|
|
|
|
return wantarray ? @rtn : \@rtn; |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
# Get the last time an RRD was updates |
356
|
|
|
|
|
|
|
sub last_update { __PACKAGE__->last(@_); } |
357
|
|
|
|
|
|
|
sub last { |
358
|
|
|
|
|
|
|
TRACE(">>> last()"); |
359
|
|
|
|
|
|
|
my $self = shift; |
360
|
|
|
|
|
|
|
unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { |
361
|
|
|
|
|
|
|
unshift @_, $self unless $self eq __PACKAGE__; |
362
|
|
|
|
|
|
|
$self = new __PACKAGE__; |
363
|
|
|
|
|
|
|
} |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
my $stor = $objstore->{_refaddr($self)}; |
366
|
|
|
|
|
|
|
my $rrdfile = shift || _guess_filename($stor); |
367
|
|
|
|
|
|
|
croak "RRD file '$rrdfile' does not exist" unless -f $rrdfile; |
368
|
|
|
|
|
|
|
TRACE("Using filename: $rrdfile"); |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
my $last = RRDs::last($rrdfile); |
371
|
|
|
|
|
|
|
my $error = RRDs::error(); |
372
|
|
|
|
|
|
|
croak($error) if $error; |
373
|
|
|
|
|
|
|
return $last; |
374
|
|
|
|
|
|
|
} |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
# Get a list of data sources from an RRD file |
378
|
|
|
|
|
|
|
sub sources { |
379
|
|
|
|
|
|
|
TRACE(">>> sources()"); |
380
|
|
|
|
|
|
|
my $self = shift; |
381
|
|
|
|
|
|
|
unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { |
382
|
|
|
|
|
|
|
unshift @_, $self unless $self eq __PACKAGE__; |
383
|
|
|
|
|
|
|
$self = new __PACKAGE__; |
384
|
|
|
|
|
|
|
} |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
my $stor = $objstore->{_refaddr($self)}; |
387
|
|
|
|
|
|
|
my $rrdfile = shift || _guess_filename($stor); |
388
|
|
|
|
|
|
|
croak "RRD file '$rrdfile' does not exist" unless -f $rrdfile; |
389
|
|
|
|
|
|
|
TRACE("Using filename: $rrdfile"); |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
my $info = RRDs::info($rrdfile); |
392
|
|
|
|
|
|
|
my $error = RRDs::error(); |
393
|
|
|
|
|
|
|
croak($error) if $error; |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
my @ds; |
396
|
|
|
|
|
|
|
foreach (keys %{$info}) { |
397
|
|
|
|
|
|
|
if (/^ds\[(.+)?\]\.type$/) { |
398
|
|
|
|
|
|
|
push @ds, $1; |
399
|
|
|
|
|
|
|
} |
400
|
|
|
|
|
|
|
} |
401
|
|
|
|
|
|
|
return wantarray ? @ds : \@ds; |
402
|
|
|
|
|
|
|
} |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
# Add a new data source to an RRD file |
406
|
|
|
|
|
|
|
sub add_source { |
407
|
|
|
|
|
|
|
TRACE(">>> add_source()"); |
408
|
|
|
|
|
|
|
my $self = shift; |
409
|
|
|
|
|
|
|
unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { |
410
|
|
|
|
|
|
|
unshift @_, $self unless $self eq __PACKAGE__; |
411
|
|
|
|
|
|
|
$self = new __PACKAGE__; |
412
|
|
|
|
|
|
|
} |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
# Grab or guess the filename |
415
|
|
|
|
|
|
|
my $stor = $objstore->{_refaddr($self)}; |
416
|
|
|
|
|
|
|
my $rrdfile = @_ % 2 ? shift : _guess_filename($stor); |
417
|
|
|
|
|
|
|
unless (-f $rrdfile) { |
418
|
|
|
|
|
|
|
cluck("RRD file '$rrdfile' does not exist; attempting to create it") |
419
|
|
|
|
|
|
|
if $^W; |
420
|
|
|
|
|
|
|
return $self->create($rrdfile,@_); |
421
|
|
|
|
|
|
|
} |
422
|
|
|
|
|
|
|
croak "RRD file '$rrdfile' does not exist" unless -f $rrdfile; |
423
|
|
|
|
|
|
|
TRACE("Using filename: $rrdfile"); |
424
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
# Check that we will understand this RRD file version first |
426
|
|
|
|
|
|
|
my $info = $self->info($rrdfile); |
427
|
|
|
|
|
|
|
# croak "Unable to add a new data source to $rrdfile; ", |
428
|
|
|
|
|
|
|
# "RRD version $info->{rrd_version} is too new" |
429
|
|
|
|
|
|
|
# if ($info->{rrd_version}+1-1) > 1; |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
my ($ds,$dstype) = @_; |
432
|
|
|
|
|
|
|
TRACE("\$ds = $ds"); |
433
|
|
|
|
|
|
|
TRACE("\$dstype = $dstype"); |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
my $rrdfileBackup = "$rrdfile.bak"; |
436
|
|
|
|
|
|
|
confess "$rrdfileBackup already exists; please investigate" |
437
|
|
|
|
|
|
|
if -e $rrdfileBackup; |
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
# Decide what heartbeat to use |
440
|
|
|
|
|
|
|
my $heartbeat = $info->{ds}->{(sort { |
441
|
|
|
|
|
|
|
$info->{ds}->{$b}->{minimal_heartbeat} <=> |
442
|
|
|
|
|
|
|
$info->{ds}->{$b}->{minimal_heartbeat} |
443
|
|
|
|
|
|
|
} keys %{$info->{ds}})[0]}->{minimal_heartbeat}; |
444
|
|
|
|
|
|
|
TRACE("\$heartbeat = $heartbeat"); |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
# Make a list of expected sources after the addition |
447
|
|
|
|
|
|
|
my $TgtSources = join(',',sort(($self->sources($rrdfile),$ds))); |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
# Add the data source |
450
|
|
|
|
|
|
|
my $new_rrdfile = ''; |
451
|
|
|
|
|
|
|
eval { |
452
|
|
|
|
|
|
|
$new_rrdfile = _modify_source( |
453
|
|
|
|
|
|
|
$rrdfile,$stor,$ds, |
454
|
|
|
|
|
|
|
'add',$dstype,$heartbeat, |
455
|
|
|
|
|
|
|
); |
456
|
|
|
|
|
|
|
}; |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
# Barf if the eval{} got upset |
459
|
|
|
|
|
|
|
if ($@) { |
460
|
|
|
|
|
|
|
croak "Failed to add new data source '$ds' to RRD file '$rrdfile': $@"; |
461
|
|
|
|
|
|
|
} |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
# Barf of the new RRD file doesn't exist |
464
|
|
|
|
|
|
|
unless (-f $new_rrdfile) { |
465
|
|
|
|
|
|
|
croak "Failed to add new data source '$ds' to RRD file '$rrdfile': ", |
466
|
|
|
|
|
|
|
"new RRD file '$new_rrdfile' does not exist"; |
467
|
|
|
|
|
|
|
} |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
# Barf is the new data source isn't in our new RRD file |
470
|
|
|
|
|
|
|
unless ($TgtSources eq join(',',sort($self->sources($new_rrdfile)))) { |
471
|
|
|
|
|
|
|
croak "Failed to add new data source '$ds' to RRD file '$rrdfile': ", |
472
|
|
|
|
|
|
|
"new RRD file '$new_rrdfile' does not contain expected data ", |
473
|
|
|
|
|
|
|
"source names"; |
474
|
|
|
|
|
|
|
} |
475
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
# Try and move the new RRD file in to place over the existing one |
477
|
|
|
|
|
|
|
# and then remove the backup RRD file if sucessfull |
478
|
|
|
|
|
|
|
if (File::Copy::move($rrdfile,$rrdfileBackup) && |
479
|
|
|
|
|
|
|
File::Copy::move($new_rrdfile,$rrdfile)) { |
480
|
|
|
|
|
|
|
unless (unlink($rrdfileBackup)) { |
481
|
|
|
|
|
|
|
cluck("Failed to remove back RRD file '$rrdfileBackup': $!") |
482
|
|
|
|
|
|
|
if $^W; |
483
|
|
|
|
|
|
|
} |
484
|
|
|
|
|
|
|
} else { |
485
|
|
|
|
|
|
|
croak "Failed to move new RRD file in to place: $!"; |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
} |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
# Make a number of graphs for an RRD file |
491
|
|
|
|
|
|
|
sub graph { |
492
|
|
|
|
|
|
|
TRACE(">>> graph()"); |
493
|
|
|
|
|
|
|
my $self = shift; |
494
|
|
|
|
|
|
|
unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { |
495
|
|
|
|
|
|
|
unshift @_, $self unless $self eq __PACKAGE__; |
496
|
|
|
|
|
|
|
$self = new __PACKAGE__; |
497
|
|
|
|
|
|
|
} |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
# Grab or guess the filename |
500
|
|
|
|
|
|
|
my $stor = $objstore->{_refaddr($self)}; |
501
|
|
|
|
|
|
|
my $rrdfile = @_ % 2 ? shift : _guess_filename($stor); |
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
# How much data do we have to graph? |
504
|
|
|
|
|
|
|
my $period = $self->retention_period($rrdfile); |
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
# Check at RRA CFs are available and graph the best one |
507
|
|
|
|
|
|
|
my $info = $self->info($rrdfile); |
508
|
|
|
|
|
|
|
my $cf = 'AVERAGE'; |
509
|
|
|
|
|
|
|
for my $rra (@{$info->{rra}}) { |
510
|
|
|
|
|
|
|
if ($rra->{cf} eq 'AVERAGE') { |
511
|
|
|
|
|
|
|
$cf = 'AVERAGE'; last; |
512
|
|
|
|
|
|
|
} elsif ($rra->{cf} eq 'MAX') { |
513
|
|
|
|
|
|
|
$cf = 'MAX'; |
514
|
|
|
|
|
|
|
} elsif ($rra->{cf} eq 'MIN' && $cf ne 'MAX') { |
515
|
|
|
|
|
|
|
$cf = 'MIN'; |
516
|
|
|
|
|
|
|
} elsif ($cf ne 'MAX' && $cf ne 'MIN') { |
517
|
|
|
|
|
|
|
$cf = $rra->{cf}; |
518
|
|
|
|
|
|
|
} |
519
|
|
|
|
|
|
|
} |
520
|
|
|
|
|
|
|
TRACE("graph() - \$cf = $cf"); |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
# Create graphs which we have enough data to populate |
523
|
|
|
|
|
|
|
# Version 1.39 - Change the return from an array to a hash (semi backward compatible) |
524
|
|
|
|
|
|
|
# my @rtn; |
525
|
|
|
|
|
|
|
my %rtn; |
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
## |
528
|
|
|
|
|
|
|
## TODO |
529
|
|
|
|
|
|
|
## 1.45 Only generate hour, 6hour and 12hour graphs if the |
530
|
|
|
|
|
|
|
### data resolution (stepping) is fine enough (sub minute) |
531
|
|
|
|
|
|
|
## |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
#i my @graph_periods = qw(hour 6hour 12hour day week month year 3years); |
534
|
|
|
|
|
|
|
my @graph_periods; |
535
|
|
|
|
|
|
|
my %param = @_; |
536
|
|
|
|
|
|
|
if (defined $param{'periods'}) { |
537
|
|
|
|
|
|
|
my %map = qw(daily day weekly week monthly month annual year 3years 3years); |
538
|
|
|
|
|
|
|
for my $period (_convert_to_array($param{'periods'})) { |
539
|
|
|
|
|
|
|
$period = lc($period); |
540
|
|
|
|
|
|
|
if (_valid_scheme($period)) { |
541
|
|
|
|
|
|
|
push @graph_periods, $period; |
542
|
|
|
|
|
|
|
} elsif (_valid_scheme($map{$period})) { |
543
|
|
|
|
|
|
|
push @graph_periods, $map{$period}; |
544
|
|
|
|
|
|
|
} else { |
545
|
|
|
|
|
|
|
croak "Invalid period value passed in parameter periods; '$period'"; |
546
|
|
|
|
|
|
|
} |
547
|
|
|
|
|
|
|
} |
548
|
|
|
|
|
|
|
} |
549
|
|
|
|
|
|
|
push @graph_periods, qw(day week month year 3years) unless @graph_periods; |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
for my $type (@graph_periods) { |
552
|
|
|
|
|
|
|
next if $period < _seconds_in($type); |
553
|
|
|
|
|
|
|
TRACE("graph() - \$type = $type"); |
554
|
|
|
|
|
|
|
# push @rtn, [ ($self->_create_graph($rrdfile, $type, $cf, @_)) ]; |
555
|
|
|
|
|
|
|
$rtn{_alt_graph_name($type)} = [ ($self->_create_graph($rrdfile, $type, $cf, @_)) ]; |
556
|
|
|
|
|
|
|
} |
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
# return @rtn; |
559
|
|
|
|
|
|
|
return wantarray ? %rtn : \%rtn; |
560
|
|
|
|
|
|
|
} |
561
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
# Rename an existing data source |
564
|
|
|
|
|
|
|
sub rename_source { |
565
|
|
|
|
|
|
|
TRACE(">>> rename_source()"); |
566
|
|
|
|
|
|
|
my $self = shift; |
567
|
|
|
|
|
|
|
unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { |
568
|
|
|
|
|
|
|
unshift @_, $self unless $self eq __PACKAGE__; |
569
|
|
|
|
|
|
|
$self = new __PACKAGE__; |
570
|
|
|
|
|
|
|
} |
571
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
# Grab or guess the filename |
573
|
|
|
|
|
|
|
my $stor = $objstore->{_refaddr($self)}; |
574
|
|
|
|
|
|
|
my $rrdfile = @_ % 2 ? shift : _guess_filename($stor); |
575
|
|
|
|
|
|
|
croak "RRD file '$rrdfile' does not exist" unless -f $rrdfile; |
576
|
|
|
|
|
|
|
TRACE("Using filename: $rrdfile"); |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
my ($old,$new) = @_; |
579
|
|
|
|
|
|
|
croak "No old data source name specified" unless defined $old && length($old); |
580
|
|
|
|
|
|
|
croak "No new data source name specified" unless defined $new && length($new); |
581
|
|
|
|
|
|
|
croak "Data source '$old' does not exist in RRD file '$rrdfile'" |
582
|
|
|
|
|
|
|
unless grep($_ eq $old, $self->sources($rrdfile)); |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
my @rtn = RRDs::tune($rrdfile,'-r',"$old:$new"); |
585
|
|
|
|
|
|
|
my $error = RRDs::error(); |
586
|
|
|
|
|
|
|
croak($error) if $error; |
587
|
|
|
|
|
|
|
return wantarray ? @rtn : \@rtn; |
588
|
|
|
|
|
|
|
} |
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
# Get or set a data source heartbeat |
592
|
|
|
|
|
|
|
sub heartbeat { |
593
|
|
|
|
|
|
|
TRACE(">>> heartbeat()"); |
594
|
|
|
|
|
|
|
my $self = shift; |
595
|
|
|
|
|
|
|
unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { |
596
|
|
|
|
|
|
|
unshift @_, $self unless $self eq __PACKAGE__; |
597
|
|
|
|
|
|
|
$self = new __PACKAGE__; |
598
|
|
|
|
|
|
|
} |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
# Grab or guess the filename |
601
|
|
|
|
|
|
|
my $stor = $objstore->{_refaddr($self)}; |
602
|
|
|
|
|
|
|
my $rrdfile = @_ >= 3 ? shift : |
603
|
|
|
|
|
|
|
_isLegalDsName($_[0]) && $_[1] =~ /^[0-9]+$/ ? |
604
|
|
|
|
|
|
|
_guess_filename($stor) : shift; |
605
|
|
|
|
|
|
|
croak "RRD file '$rrdfile' does not exist" unless -f $rrdfile; |
606
|
|
|
|
|
|
|
TRACE("Using filename: $rrdfile"); |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
# Explode if we get no data source name |
609
|
|
|
|
|
|
|
my ($ds,$new_heartbeat) = @_; |
610
|
|
|
|
|
|
|
croak "No data source name was specified" unless defined $ds && length($ds); |
611
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
# Check the data source name exists |
613
|
|
|
|
|
|
|
my $info = $self->info($rrdfile); |
614
|
|
|
|
|
|
|
my $heartbeat = $info->{ds}->{$ds}->{minimal_heartbeat}; |
615
|
|
|
|
|
|
|
croak "Data source '$ds' does not exist in RRD file '$rrdfile'" |
616
|
|
|
|
|
|
|
unless defined $heartbeat && $heartbeat; |
617
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
if (!defined $new_heartbeat) { |
619
|
|
|
|
|
|
|
return wantarray ? ($heartbeat) : $heartbeat; |
620
|
|
|
|
|
|
|
} |
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
my @rtn = !defined $new_heartbeat ? ($heartbeat) : (); |
623
|
|
|
|
|
|
|
# Redefine the data source heartbeat |
624
|
|
|
|
|
|
|
if (defined $new_heartbeat) { |
625
|
|
|
|
|
|
|
croak "New minimal heartbeat '$new_heartbeat' is not a valid positive integer" |
626
|
|
|
|
|
|
|
unless $new_heartbeat =~ /^[1-9][0-9]*$/; |
627
|
|
|
|
|
|
|
my @rtn = RRDs::tune($rrdfile,'-h',"$ds:$new_heartbeat"); |
628
|
|
|
|
|
|
|
my $error = RRDs::error(); |
629
|
|
|
|
|
|
|
croak($error) if $error; |
630
|
|
|
|
|
|
|
} |
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
return wantarray ? @rtn : \@rtn; |
633
|
|
|
|
|
|
|
} |
634
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
# Fetch data point information from an RRD file |
637
|
|
|
|
|
|
|
sub fetch { |
638
|
|
|
|
|
|
|
TRACE(">>> fetch()"); |
639
|
|
|
|
|
|
|
my $self = shift; |
640
|
|
|
|
|
|
|
unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { |
641
|
|
|
|
|
|
|
unshift @_, $self unless $self eq __PACKAGE__; |
642
|
|
|
|
|
|
|
$self = new __PACKAGE__; |
643
|
|
|
|
|
|
|
} |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
# Grab or guess the filename |
646
|
|
|
|
|
|
|
my $stor = $objstore->{_refaddr($self)}; |
647
|
|
|
|
|
|
|
my $rrdfile = @_ % 2 ? shift : _guess_filename($stor); |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
} |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
# Fetch the last values inserted in to an RRD file |
653
|
|
|
|
|
|
|
sub last_values { |
654
|
|
|
|
|
|
|
TRACE(">>> last_values()"); |
655
|
|
|
|
|
|
|
my $self = shift; |
656
|
|
|
|
|
|
|
unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { |
657
|
|
|
|
|
|
|
unshift @_, $self unless $self eq __PACKAGE__; |
658
|
|
|
|
|
|
|
$self = new __PACKAGE__; |
659
|
|
|
|
|
|
|
} |
660
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
# Grab or guess the filename |
662
|
|
|
|
|
|
|
my $stor = $objstore->{_refaddr($self)}; |
663
|
|
|
|
|
|
|
my $rrdfile = @_ % 2 ? shift : _guess_filename($stor); |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
# When was the RRD last updated? |
666
|
|
|
|
|
|
|
my $lastUpdated = $self->last($rrdfile); |
667
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
# Is there a LAST RRA? |
669
|
|
|
|
|
|
|
my $info = $self->info($rrdfile); |
670
|
|
|
|
|
|
|
my $hasLastRRA = 0; |
671
|
|
|
|
|
|
|
for my $rra (@{$info->{rra}}) { |
672
|
|
|
|
|
|
|
$hasLastRRA++ if $rra->{cf} eq 'LAST'; |
673
|
|
|
|
|
|
|
} |
674
|
|
|
|
|
|
|
return if !$hasLastRRA; |
675
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
# What's the largest heartbeat in the RRD file data sources? |
677
|
|
|
|
|
|
|
my $largestHeartbeat = 1; |
678
|
|
|
|
|
|
|
for (map { $info->{ds}->{$_}->{'minimal_heartbeat'} } keys(%{$info->{ds}})) { |
679
|
|
|
|
|
|
|
$largestHeartbeat = $_ if $_ > $largestHeartbeat; |
680
|
|
|
|
|
|
|
} |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
my @def = ('LAST', |
683
|
|
|
|
|
|
|
'-s', $lastUpdated - ($largestHeartbeat * 2), |
684
|
|
|
|
|
|
|
'-e', $lastUpdated |
685
|
|
|
|
|
|
|
); |
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
# Pass to RRDs to execute |
688
|
|
|
|
|
|
|
my ($time,$heartbeat,$ds,$data) = RRDs::fetch($rrdfile, @def); |
689
|
|
|
|
|
|
|
my $error = RRDs::error(); |
690
|
|
|
|
|
|
|
croak($error) if $error; |
691
|
|
|
|
|
|
|
|
692
|
|
|
|
|
|
|
# Put it in to a nice easy format |
693
|
|
|
|
|
|
|
my %rtn = (); |
694
|
|
|
|
|
|
|
for my $rec (reverse @{$data}) { |
695
|
|
|
|
|
|
|
for (my $i = 0; $i < @{$rec}; $i++) { |
696
|
|
|
|
|
|
|
if (defined $rec->[$i] && !exists($rtn{$ds->[$i]})) { |
697
|
|
|
|
|
|
|
$rtn{$ds->[$i]} = $rec->[$i]; |
698
|
|
|
|
|
|
|
} |
699
|
|
|
|
|
|
|
} |
700
|
|
|
|
|
|
|
} |
701
|
|
|
|
|
|
|
|
702
|
|
|
|
|
|
|
# Well, I'll be buggered if the LAST CF does what you'd think |
703
|
|
|
|
|
|
|
# it's meant to do. If anybody can give me some decent documentation |
704
|
|
|
|
|
|
|
# on what the LAST CF does, and/or how to get the last value put |
705
|
|
|
|
|
|
|
# in to an RRD, then I'll admit that this method exists and export |
706
|
|
|
|
|
|
|
# it too. |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
return wantarray ? %rtn : \%rtn; |
709
|
|
|
|
|
|
|
} |
710
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
# Return how long this RRD retains data for |
713
|
|
|
|
|
|
|
sub retention_period { |
714
|
|
|
|
|
|
|
TRACE(">>> retention_period()"); |
715
|
|
|
|
|
|
|
my $self = shift; |
716
|
|
|
|
|
|
|
unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { |
717
|
|
|
|
|
|
|
unshift @_, $self unless $self eq __PACKAGE__; |
718
|
|
|
|
|
|
|
$self = new __PACKAGE__; |
719
|
|
|
|
|
|
|
} |
720
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
my $info = $self->info(@_); |
722
|
|
|
|
|
|
|
return if !defined($info); |
723
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
my $duration = $info->{step}; |
725
|
|
|
|
|
|
|
for my $rra (@{$info->{rra}}) { |
726
|
|
|
|
|
|
|
my $secs = ($rra->{pdp_per_row} * $info->{step}) * $rra->{rows}; |
727
|
|
|
|
|
|
|
$duration = $secs if $secs > $duration; |
728
|
|
|
|
|
|
|
} |
729
|
|
|
|
|
|
|
|
730
|
|
|
|
|
|
|
return wantarray ? ($duration) : $duration; |
731
|
|
|
|
|
|
|
} |
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
|
734
|
|
|
|
|
|
|
# Fetch information about an RRD file |
735
|
|
|
|
|
|
|
sub info { |
736
|
|
|
|
|
|
|
TRACE(">>> info()"); |
737
|
|
|
|
|
|
|
my $self = shift; |
738
|
|
|
|
|
|
|
unless (ref $self && UNIVERSAL::isa($self, __PACKAGE__)) { |
739
|
|
|
|
|
|
|
unshift @_, $self unless $self eq __PACKAGE__; |
740
|
|
|
|
|
|
|
$self = new __PACKAGE__; |
741
|
|
|
|
|
|
|
} |
742
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
# Grab or guess the filename |
744
|
|
|
|
|
|
|
my $stor = $objstore->{_refaddr($self)}; |
745
|
|
|
|
|
|
|
my $rrdfile = @_ % 2 ? shift : _guess_filename($stor); |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
my $info = RRDs::info($rrdfile); |
748
|
|
|
|
|
|
|
my $error = RRDs::error(); |
749
|
|
|
|
|
|
|
croak($error) if $error; |
750
|
|
|
|
|
|
|
DUMP('$info',$info); |
751
|
|
|
|
|
|
|
|
752
|
|
|
|
|
|
|
my $rtn; |
753
|
|
|
|
|
|
|
for my $key (sort(keys(%{$info}))) { |
754
|
|
|
|
|
|
|
if ($key =~ /^rra\[(\d+)\]\.([a-z_]+)/) { |
755
|
|
|
|
|
|
|
$rtn->{rra}->[$1]->{$2} = $info->{$key}; |
756
|
|
|
|
|
|
|
} elsif (my (@dsKey) = $key =~ /^ds\[([[A-Za-z0-9\_]+)?\]\.([a-z_]+)/) { |
757
|
|
|
|
|
|
|
$rtn->{ds}->{$1}->{$2} = $info->{$key}; |
758
|
|
|
|
|
|
|
} elsif ($key !~ /\[[\d_a-z]+\]/i) { |
759
|
|
|
|
|
|
|
$rtn->{$key} = $info->{$key}; |
760
|
|
|
|
|
|
|
} |
761
|
|
|
|
|
|
|
} |
762
|
|
|
|
|
|
|
|
763
|
|
|
|
|
|
|
# Return the information |
764
|
|
|
|
|
|
|
DUMP('$rtn',$rtn); |
765
|
|
|
|
|
|
|
return $rtn; |
766
|
|
|
|
|
|
|
} |
767
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
|
769
|
|
|
|
|
|
|
# Convert a string or an array reference to an array |
770
|
|
|
|
|
|
|
sub _convert_to_array { |
771
|
|
|
|
|
|
|
return unless defined $_[0]; |
772
|
|
|
|
|
|
|
if (!ref $_[0]) { |
773
|
|
|
|
|
|
|
$_[0] =~ /^\s+|\s+$/g; |
774
|
|
|
|
|
|
|
return split(/(?:\s+|\s*,\s*)/,$_[0]); |
775
|
|
|
|
|
|
|
} elsif (ref($_[0]) eq 'ARRAY') { |
776
|
|
|
|
|
|
|
return @{$_[0]}; |
777
|
|
|
|
|
|
|
} |
778
|
|
|
|
|
|
|
return; |
779
|
|
|
|
|
|
|
} |
780
|
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
|
782
|
|
|
|
|
|
|
# Make a single graph image |
783
|
|
|
|
|
|
|
sub _create_graph { |
784
|
|
|
|
|
|
|
TRACE(">>> _create_graph()"); |
785
|
|
|
|
|
|
|
my $self = shift; |
786
|
|
|
|
|
|
|
my $rrdfile = shift; |
787
|
|
|
|
|
|
|
my $type = _valid_scheme(shift) || 'day'; |
788
|
|
|
|
|
|
|
my $cf = shift || 'AVERAGE'; |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
my $command_regex = qr/^([VC]?DEF|G?PRINT|COMMENT|[HV]RULE\d*|LINE\d*|AREA|TICK|SHIFT|STACK):.+/; |
791
|
|
|
|
|
|
|
$command_regex = qr/^([VC]?DEF|G?PRINT|COMMENT|[HV]RULE\d*|LINE\d*|AREA|TICK|SHIFT|STACK|TEXTALIGN):.+/ |
792
|
|
|
|
|
|
|
if $RRDs::VERSION >= 1.3; # http://oss.oetiker.ch/rrdtool-trac/wiki/RRDtool13 |
793
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
my %param; |
795
|
|
|
|
|
|
|
my @command_param; |
796
|
|
|
|
|
|
|
while (my $k = shift) { |
797
|
|
|
|
|
|
|
if ($k =~ /$command_regex/) { |
798
|
|
|
|
|
|
|
push @command_param, $k; |
799
|
|
|
|
|
|
|
shift; |
800
|
|
|
|
|
|
|
} else { |
801
|
|
|
|
|
|
|
$k =~ s/_/-/g; |
802
|
|
|
|
|
|
|
$param{lc($k)} = shift; |
803
|
|
|
|
|
|
|
} |
804
|
|
|
|
|
|
|
} |
805
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
# If we get this custom parameter then it would have already |
807
|
|
|
|
|
|
|
# been dealt with by the calling graph() method so we should |
808
|
|
|
|
|
|
|
# ditch it right here and now! |
809
|
|
|
|
|
|
|
delete $param{'periods'}; |
810
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
# Specify some default values |
812
|
|
|
|
|
|
|
$param{'end'} ||= $self->last($rrdfile) || time(); |
813
|
|
|
|
|
|
|
$param{'imgformat'} ||= 'PNG'; # RRDs >1.3 now support PDF, SVG and EPS |
814
|
|
|
|
|
|
|
# $param{'alt-autoscale'} ||= ''; |
815
|
|
|
|
|
|
|
# $param{'alt-y-grid'} ||= ''; |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
# Define what to call the image |
818
|
|
|
|
|
|
|
my $basename = defined $param{'basename'} && |
819
|
|
|
|
|
|
|
$param{'basename'} =~ /^[0-9a-z_\.-]+$/i ? |
820
|
|
|
|
|
|
|
$param{'basename'} : |
821
|
|
|
|
|
|
|
(fileparse($rrdfile,'\.[^\.]+'))[0]; |
822
|
|
|
|
|
|
|
delete $param{'basename'}; |
823
|
|
|
|
|
|
|
|
824
|
|
|
|
|
|
|
# Define where to write the image |
825
|
|
|
|
|
|
|
my $image = sprintf('%s-%s.%s',$basename, |
826
|
|
|
|
|
|
|
_alt_graph_name($type), lc($param{'imgformat'})); |
827
|
|
|
|
|
|
|
if ($param{'destination'}) { |
828
|
|
|
|
|
|
|
$image = File::Spec->catfile($param{'destination'},$image); |
829
|
|
|
|
|
|
|
} |
830
|
|
|
|
|
|
|
delete $param{'destination'}; |
831
|
|
|
|
|
|
|
|
832
|
|
|
|
|
|
|
# Specify timestamps- new for version 1.41 |
833
|
|
|
|
|
|
|
my $timestamp = !defined $param{'timestamp'} || |
834
|
|
|
|
|
|
|
$param{'timestamp'} !~ /^(graph|rrd|both|none)$/i |
835
|
|
|
|
|
|
|
? 'graph' |
836
|
|
|
|
|
|
|
: lc($param{'timestamp'}); |
837
|
|
|
|
|
|
|
delete $param{'timestamp'}; |
838
|
|
|
|
|
|
|
|
839
|
|
|
|
|
|
|
# Specify extended legend - new for version 1.35 |
840
|
|
|
|
|
|
|
my $extended_legend = defined $param{'extended-legend'} && |
841
|
|
|
|
|
|
|
$param{'extended-legend'} ? 1 : 0; |
842
|
|
|
|
|
|
|
delete $param{'extended-legend'}; |
843
|
|
|
|
|
|
|
|
844
|
|
|
|
|
|
|
# Define how thick the graph lines should be |
845
|
|
|
|
|
|
|
my $line_thickness = defined $param{'line-thickness'} && |
846
|
|
|
|
|
|
|
$param{'line-thickness'} =~ /^[123]$/ ? |
847
|
|
|
|
|
|
|
$param{'line-thickness'} : 1; |
848
|
|
|
|
|
|
|
delete $param{'line-thickness'}; |
849
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
# Colours is an alias to colors |
851
|
|
|
|
|
|
|
if (exists $param{'source-colours'} && !exists $param{'source-colors'}) { |
852
|
|
|
|
|
|
|
$param{'source-colors'} = $param{'source-colours'}; |
853
|
|
|
|
|
|
|
delete $param{'source-colours'}; |
854
|
|
|
|
|
|
|
} |
855
|
|
|
|
|
|
|
|
856
|
|
|
|
|
|
|
# Allow source line colors to be set |
857
|
|
|
|
|
|
|
my @source_colors = (); |
858
|
|
|
|
|
|
|
my %source_colors = (); |
859
|
|
|
|
|
|
|
if (defined $param{'source-colors'}) { |
860
|
|
|
|
|
|
|
#if (ref($param{'source-colors'}) eq 'ARRAY') { |
861
|
|
|
|
|
|
|
# @source_colors = @{$param{'source-colors'}}; |
862
|
|
|
|
|
|
|
if (ref($param{'source-colors'}) eq 'HASH') { |
863
|
|
|
|
|
|
|
%source_colors = %{$param{'source-colors'}}; |
864
|
|
|
|
|
|
|
} else { |
865
|
|
|
|
|
|
|
@source_colors = _convert_to_array($param{'source-colors'}); |
866
|
|
|
|
|
|
|
} |
867
|
|
|
|
|
|
|
} |
868
|
|
|
|
|
|
|
delete $param{'source-colors'}; |
869
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
# Define which data sources we should plot |
871
|
|
|
|
|
|
|
my @rrd_sources = $self->sources($rrdfile); |
872
|
|
|
|
|
|
|
my @ds = !exists $param{'sources'} |
873
|
|
|
|
|
|
|
? @rrd_sources |
874
|
|
|
|
|
|
|
#: defined $param{'sources'} && ref($param{'sources'}) eq 'ARRAY' |
875
|
|
|
|
|
|
|
#? @{$param{'sources'}} |
876
|
|
|
|
|
|
|
: defined $param{'sources'} |
877
|
|
|
|
|
|
|
? _convert_to_array($param{'sources'}) |
878
|
|
|
|
|
|
|
: (); |
879
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
# Allow source legend source_labels to be set |
881
|
|
|
|
|
|
|
my %source_labels = (); |
882
|
|
|
|
|
|
|
if (defined $param{'source-labels'}) { |
883
|
|
|
|
|
|
|
if (ref($param{'source-labels'}) eq 'HASH') { |
884
|
|
|
|
|
|
|
%source_labels = %{$param{'source-labels'}}; |
885
|
|
|
|
|
|
|
} elsif (ref($param{'source-labels'}) eq 'ARRAY') { |
886
|
|
|
|
|
|
|
if (defined $param{'sources'} && ref($param{'sources'}) eq 'ARRAY') { |
887
|
|
|
|
|
|
|
for (my $i = 0; $i < @{$param{'source-labels'}}; $i++) { |
888
|
|
|
|
|
|
|
$source_labels{$ds[$i]} = $param{'source-labels'}->[$i] |
889
|
|
|
|
|
|
|
if defined $ds[$i]; |
890
|
|
|
|
|
|
|
} |
891
|
|
|
|
|
|
|
} elsif ($^W) { |
892
|
|
|
|
|
|
|
carp "source_labels may only be an array if sources is also ". |
893
|
|
|
|
|
|
|
"an specified and valid array"; |
894
|
|
|
|
|
|
|
} |
895
|
|
|
|
|
|
|
} |
896
|
|
|
|
|
|
|
} |
897
|
|
|
|
|
|
|
delete $param{'source-labels'}; |
898
|
|
|
|
|
|
|
|
899
|
|
|
|
|
|
|
# Allow source legend source_drawtypes to be set |
900
|
|
|
|
|
|
|
# ... "oops" ... yes, this is quite obviously |
901
|
|
|
|
|
|
|
# copy and paste code from the chunk above. I'm |
902
|
|
|
|
|
|
|
# sorry. I'll rationalise it some other day if |
903
|
|
|
|
|
|
|
# it's necessary. |
904
|
|
|
|
|
|
|
my %source_drawtypes = (); |
905
|
|
|
|
|
|
|
if (defined $param{'source-drawtypes'}) { |
906
|
|
|
|
|
|
|
if (ref($param{'source-drawtypes'}) eq 'HASH') { |
907
|
|
|
|
|
|
|
%source_drawtypes = %{$param{'source-drawtypes'}}; |
908
|
|
|
|
|
|
|
} elsif (ref($param{'source-drawtypes'}) eq 'ARRAY') { |
909
|
|
|
|
|
|
|
if (defined $param{'sources'} && ref($param{'sources'}) eq 'ARRAY') { |
910
|
|
|
|
|
|
|
for (my $i = 0; $i < @{$param{'source-drawtypes'}}; $i++) { |
911
|
|
|
|
|
|
|
$source_drawtypes{$ds[$i]} = $param{'source-drawtypes'}->[$i] |
912
|
|
|
|
|
|
|
if defined $ds[$i]; |
913
|
|
|
|
|
|
|
} |
914
|
|
|
|
|
|
|
} elsif ($^W) { |
915
|
|
|
|
|
|
|
carp "source_drawtypes may only be an array if sources is ". |
916
|
|
|
|
|
|
|
"also an specified and valid array" |
917
|
|
|
|
|
|
|
} |
918
|
|
|
|
|
|
|
} |
919
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
# Validate the values we have and set default thickness |
921
|
|
|
|
|
|
|
while (my ($k,$v) = each %source_drawtypes) { |
922
|
|
|
|
|
|
|
if ($v !~ /^(LINE[1-9]?|STACK|AREA)$/) { |
923
|
|
|
|
|
|
|
delete $source_drawtypes{$k}; |
924
|
|
|
|
|
|
|
carp "source_drawtypes may be LINE, LINEn, AREA or STACK ". |
925
|
|
|
|
|
|
|
"only; value '$v' is not valid" if $^W; |
926
|
|
|
|
|
|
|
} |
927
|
|
|
|
|
|
|
$source_drawtypes{$k} = uc($v); |
928
|
|
|
|
|
|
|
$source_drawtypes{$k} .= $line_thickness if $v eq 'LINE'; |
929
|
|
|
|
|
|
|
} |
930
|
|
|
|
|
|
|
} |
931
|
|
|
|
|
|
|
delete $param{'source-drawtypes'}; |
932
|
|
|
|
|
|
|
delete $param{'sources'}; |
933
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
# Specify a default start time |
935
|
|
|
|
|
|
|
$param{'start'} ||= $param{'end'} - _seconds_in($type,115); |
936
|
|
|
|
|
|
|
|
937
|
|
|
|
|
|
|
# Suffix the title with the period information |
938
|
|
|
|
|
|
|
$param{'title'} ||= basename($rrdfile); |
939
|
|
|
|
|
|
|
$param{'title'} .= ' - [Hourly Graph]' if $type eq 'hour'; |
940
|
|
|
|
|
|
|
$param{'title'} .= ' - [6 Hour Graph]' if $type eq '6hour' || $type eq 'quarterday'; |
941
|
|
|
|
|
|
|
$param{'title'} .= ' - [12 Hour Graph]' if $type eq '12hour' || $type eq 'halfday'; |
942
|
|
|
|
|
|
|
$param{'title'} .= ' - [Daily Graph]' if $type eq 'day'; |
943
|
|
|
|
|
|
|
$param{'title'} .= ' - [Weekly Graph]' if $type eq 'week'; |
944
|
|
|
|
|
|
|
$param{'title'} .= ' - [Monthly Graph]' if $type eq 'month'; |
945
|
|
|
|
|
|
|
$param{'title'} .= ' - [Annual Graph]' if $type eq 'year'; |
946
|
|
|
|
|
|
|
$param{'title'} .= ' - [3 Year Graph]' if $type eq '3years'; |
947
|
|
|
|
|
|
|
|
948
|
|
|
|
|
|
|
# Convert our parameters in to an RRDs friendly defenition |
949
|
|
|
|
|
|
|
my @def; |
950
|
|
|
|
|
|
|
while (my ($k,$v) = each %param) { |
951
|
|
|
|
|
|
|
if (length($k) == 1) { # Short single character options |
952
|
|
|
|
|
|
|
$k = '-'.uc($k); |
953
|
|
|
|
|
|
|
} else { # Long options |
954
|
|
|
|
|
|
|
$k = "--$k"; |
955
|
|
|
|
|
|
|
} |
956
|
|
|
|
|
|
|
for my $v ((ref($v) eq 'ARRAY' ? @{$v} : ($v))) { |
957
|
|
|
|
|
|
|
if (!defined $v || !length($v)) { |
958
|
|
|
|
|
|
|
push @def, $k; |
959
|
|
|
|
|
|
|
} else { |
960
|
|
|
|
|
|
|
push @def, "$k=$v"; |
961
|
|
|
|
|
|
|
} |
962
|
|
|
|
|
|
|
} |
963
|
|
|
|
|
|
|
} |
964
|
|
|
|
|
|
|
|
965
|
|
|
|
|
|
|
# Populate a cycling tied scalar for line colors |
966
|
|
|
|
|
|
|
@source_colors = qw( |
967
|
|
|
|
|
|
|
FF0000 00FF00 0000FF 00FFFF FF00FF FFFF00 000000 |
968
|
|
|
|
|
|
|
990000 009900 000099 009999 990099 999900 999999 |
969
|
|
|
|
|
|
|
552222 225522 222255 225555 552255 555522 555555 |
970
|
|
|
|
|
|
|
) unless @source_colors > 0; |
971
|
|
|
|
|
|
|
# Pre 1.35 colours |
972
|
|
|
|
|
|
|
# FF0000 00FF00 0000FF FFFF00 00FFFF FF00FF 000000 |
973
|
|
|
|
|
|
|
# 550000 005500 000055 555500 005555 550055 555555 |
974
|
|
|
|
|
|
|
# AA0000 00AA00 0000AA AAAA00 00AAAA AA00AA AAAAAA |
975
|
|
|
|
|
|
|
tie my $colour, 'RRD::Simple::_Colour', \@source_colors; |
976
|
|
|
|
|
|
|
|
977
|
|
|
|
|
|
|
my $fmt = '%s:%s#%s:%s%s'; |
978
|
|
|
|
|
|
|
my $longest_label = 1; |
979
|
|
|
|
|
|
|
if ($extended_legend) { |
980
|
|
|
|
|
|
|
for my $ds (@ds) { |
981
|
|
|
|
|
|
|
my $len = length( defined $source_labels{$ds} ? |
982
|
|
|
|
|
|
|
$source_labels{$ds} : $ds ); |
983
|
|
|
|
|
|
|
$longest_label = $len if $len > $longest_label; |
984
|
|
|
|
|
|
|
} |
985
|
|
|
|
|
|
|
$fmt = "%s:%s#%s:%-${longest_label}s%s"; |
986
|
|
|
|
|
|
|
} |
987
|
|
|
|
|
|
|
|
988
|
|
|
|
|
|
|
|
989
|
|
|
|
|
|
|
|
990
|
|
|
|
|
|
|
## |
991
|
|
|
|
|
|
|
## |
992
|
|
|
|
|
|
|
## |
993
|
|
|
|
|
|
|
|
994
|
|
|
|
|
|
|
# Create the @cmd |
995
|
|
|
|
|
|
|
my @cmd = ($image,@def); |
996
|
|
|
|
|
|
|
|
997
|
|
|
|
|
|
|
# Add the data sources definitions to @cmd |
998
|
|
|
|
|
|
|
for my $ds (@rrd_sources) { |
999
|
|
|
|
|
|
|
# Add the data source definition |
1000
|
|
|
|
|
|
|
push @cmd, sprintf('DEF:%s=%s:%s:%s',$ds,$rrdfile,$ds,$cf); |
1001
|
|
|
|
|
|
|
} |
1002
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
# Add the data source draw commands to the grap/@cmd |
1004
|
|
|
|
|
|
|
for my $ds (@ds) { |
1005
|
|
|
|
|
|
|
# Stack operates differently in RRD 1.2 or higher |
1006
|
|
|
|
|
|
|
my $drawtype = defined $source_drawtypes{$ds} ? $source_drawtypes{$ds} |
1007
|
|
|
|
|
|
|
: "LINE$line_thickness"; |
1008
|
|
|
|
|
|
|
my $stack = ''; |
1009
|
|
|
|
|
|
|
if ($RRDs::VERSION >= 1.2 && $drawtype eq 'STACK') { |
1010
|
|
|
|
|
|
|
$drawtype = 'AREA'; |
1011
|
|
|
|
|
|
|
$stack = ':STACK'; |
1012
|
|
|
|
|
|
|
} |
1013
|
|
|
|
|
|
|
|
1014
|
|
|
|
|
|
|
# Draw the line (and add to the legend) |
1015
|
|
|
|
|
|
|
push @cmd, sprintf($fmt, |
1016
|
|
|
|
|
|
|
$drawtype, |
1017
|
|
|
|
|
|
|
$ds, |
1018
|
|
|
|
|
|
|
(defined $source_colors{$ds} ? $source_colors{$ds} : $colour), |
1019
|
|
|
|
|
|
|
(defined $source_labels{$ds} ? $source_labels{$ds} : $ds), |
1020
|
|
|
|
|
|
|
$stack |
1021
|
|
|
|
|
|
|
); |
1022
|
|
|
|
|
|
|
|
1023
|
|
|
|
|
|
|
# New for version 1.39 |
1024
|
|
|
|
|
|
|
# Return the min,max,last information in the graph() return @rtn |
1025
|
|
|
|
|
|
|
if ($RRDs::VERSION >= 1.2) { |
1026
|
|
|
|
|
|
|
push @cmd, sprintf('VDEF:%sMIN=%s,MINIMUM',$ds,$ds); |
1027
|
|
|
|
|
|
|
push @cmd, sprintf('VDEF:%sMAX=%s,MAXIMUM',$ds,$ds); |
1028
|
|
|
|
|
|
|
push @cmd, sprintf('VDEF:%sLAST=%s,LAST',$ds,$ds); |
1029
|
|
|
|
|
|
|
# Don't automatically add this unless we have to |
1030
|
|
|
|
|
|
|
# push @cmd, sprintf('VDEF:%sAVERAGE=%s,AVERAGE',$ds,$ds); |
1031
|
|
|
|
|
|
|
push @cmd, sprintf('PRINT:%sMIN:%s min %%1.2lf',$ds,$ds); |
1032
|
|
|
|
|
|
|
push @cmd, sprintf('PRINT:%sMAX:%s max %%1.2lf',$ds,$ds); |
1033
|
|
|
|
|
|
|
push @cmd, sprintf('PRINT:%sLAST:%s last %%1.2lf',$ds,$ds); |
1034
|
|
|
|
|
|
|
} else { |
1035
|
|
|
|
|
|
|
push @cmd, sprintf('PRINT:%s:MIN:%s min %%1.2lf',$ds,$ds); |
1036
|
|
|
|
|
|
|
push @cmd, sprintf('PRINT:%s:MAX:%s max %%1.2lf',$ds,$ds); |
1037
|
|
|
|
|
|
|
push @cmd, sprintf('PRINT:%s:LAST:%s last %%1.2lf',$ds,$ds); |
1038
|
|
|
|
|
|
|
} |
1039
|
|
|
|
|
|
|
|
1040
|
|
|
|
|
|
|
# New for version 1.35 |
1041
|
|
|
|
|
|
|
if ($extended_legend) { |
1042
|
|
|
|
|
|
|
if ($RRDs::VERSION >= 1.2) { |
1043
|
|
|
|
|
|
|
# Moved the VDEFs to the block of code above which is |
1044
|
|
|
|
|
|
|
# always run, regardless of the extended legend |
1045
|
|
|
|
|
|
|
push @cmd, sprintf('GPRINT:%sMIN: min\:%%10.2lf\g',$ds); |
1046
|
|
|
|
|
|
|
push @cmd, sprintf('GPRINT:%sMAX: max\:%%10.2lf\g',$ds); |
1047
|
|
|
|
|
|
|
push @cmd, sprintf('GPRINT:%sLAST: last\:%%10.2lf\l',$ds); |
1048
|
|
|
|
|
|
|
} else { |
1049
|
|
|
|
|
|
|
push @cmd, sprintf('GPRINT:%s:MIN: min\:%%10.2lf\g',$ds); |
1050
|
|
|
|
|
|
|
push @cmd, sprintf('GPRINT:%s:MAX: max\:%%10.2lf\g',$ds); |
1051
|
|
|
|
|
|
|
push @cmd, sprintf('GPRINT:%s:LAST: last\:%%10.2lf\l',$ds); |
1052
|
|
|
|
|
|
|
} |
1053
|
|
|
|
|
|
|
} |
1054
|
|
|
|
|
|
|
} |
1055
|
|
|
|
|
|
|
|
1056
|
|
|
|
|
|
|
|
1057
|
|
|
|
|
|
|
|
1058
|
|
|
|
|
|
|
|
1059
|
|
|
|
|
|
|
|
1060
|
|
|
|
|
|
|
|
1061
|
|
|
|
|
|
|
# Push the post command defs on to the stack |
1062
|
|
|
|
|
|
|
push @cmd, @command_param; |
1063
|
|
|
|
|
|
|
|
1064
|
|
|
|
|
|
|
# Add a comment stating when the graph was last updated |
1065
|
|
|
|
|
|
|
if ($timestamp ne 'none') { |
1066
|
|
|
|
|
|
|
#push @cmd, ('COMMENT:\s','COMMENT:\s','COMMENT:\s'); |
1067
|
|
|
|
|
|
|
push @cmd, ('COMMENT:\s','COMMENT:\s'); |
1068
|
|
|
|
|
|
|
push @cmd, 'COMMENT:\s' unless $extended_legend || !@ds; |
1069
|
|
|
|
|
|
|
my $timefmt = '%a %d/%b/%Y %T %Z'; |
1070
|
|
|
|
|
|
|
|
1071
|
|
|
|
|
|
|
if ($timestamp eq 'rrd' || $timestamp eq 'both') { |
1072
|
|
|
|
|
|
|
my $time = sprintf('RRD last updated: %s\r', |
1073
|
|
|
|
|
|
|
strftime($timefmt,localtime((stat($rrdfile))[9])) |
1074
|
|
|
|
|
|
|
); |
1075
|
|
|
|
|
|
|
$time =~ s/:/\\:/g if $RRDs::VERSION >= 1.2; # Only escape for 1.2 |
1076
|
|
|
|
|
|
|
push @cmd, "COMMENT:$time"; |
1077
|
|
|
|
|
|
|
} |
1078
|
|
|
|
|
|
|
|
1079
|
|
|
|
|
|
|
if ($timestamp eq 'graph' || $timestamp eq 'both') { |
1080
|
|
|
|
|
|
|
my $time = sprintf('Graph last updated: %s\r', |
1081
|
|
|
|
|
|
|
strftime($timefmt,localtime(time)) |
1082
|
|
|
|
|
|
|
); |
1083
|
|
|
|
|
|
|
$time =~ s/:/\\:/g if $RRDs::VERSION >= 1.2; # Only escape for 1.2 |
1084
|
|
|
|
|
|
|
push @cmd, "COMMENT:$time"; |
1085
|
|
|
|
|
|
|
} |
1086
|
|
|
|
|
|
|
} |
1087
|
|
|
|
|
|
|
|
1088
|
|
|
|
|
|
|
DUMP('@cmd',\@cmd); |
1089
|
|
|
|
|
|
|
|
1090
|
|
|
|
|
|
|
# Generate the graph |
1091
|
|
|
|
|
|
|
my @rtn = RRDs::graph(@cmd); |
1092
|
|
|
|
|
|
|
my $error = RRDs::error(); |
1093
|
|
|
|
|
|
|
croak($error) if $error; |
1094
|
|
|
|
|
|
|
return ($image,@rtn); |
1095
|
|
|
|
|
|
|
} |
1096
|
|
|
|
|
|
|
|
1097
|
|
|
|
|
|
|
|
1098
|
|
|
|
|
|
|
|
1099
|
|
|
|
|
|
|
|
1100
|
|
|
|
|
|
|
# |
1101
|
|
|
|
|
|
|
# Private subroutines |
1102
|
|
|
|
|
|
|
# |
1103
|
|
|
|
|
|
|
|
1104
|
|
|
|
|
|
|
no warnings 'redefine'; |
1105
|
|
|
|
|
|
|
sub UNIVERSAL::a_sub_not_likely_to_be_here { ref($_[0]) } |
1106
|
|
|
|
|
|
|
use warnings 'redefine'; |
1107
|
|
|
|
|
|
|
|
1108
|
|
|
|
|
|
|
|
1109
|
|
|
|
|
|
|
sub _blessed ($) { |
1110
|
|
|
|
|
|
|
local($@, $SIG{__DIE__}, $SIG{__WARN__}); |
1111
|
|
|
|
|
|
|
return length(ref($_[0])) |
1112
|
|
|
|
|
|
|
? eval { $_[0]->a_sub_not_likely_to_be_here } |
1113
|
|
|
|
|
|
|
: undef |
1114
|
|
|
|
|
|
|
} |
1115
|
|
|
|
|
|
|
|
1116
|
|
|
|
|
|
|
|
1117
|
|
|
|
|
|
|
sub _refaddr($) { |
1118
|
|
|
|
|
|
|
my $pkg = ref($_[0]) or return undef; |
1119
|
|
|
|
|
|
|
if (_blessed($_[0])) { |
1120
|
|
|
|
|
|
|
bless $_[0], 'Scalar::Util::Fake'; |
1121
|
|
|
|
|
|
|
} else { |
1122
|
|
|
|
|
|
|
$pkg = undef; |
1123
|
|
|
|
|
|
|
} |
1124
|
|
|
|
|
|
|
"$_[0]" =~ /0x(\w+)/; |
1125
|
|
|
|
|
|
|
my $i = do { local $^W; hex $1 }; |
1126
|
|
|
|
|
|
|
bless $_[0], $pkg if defined $pkg; |
1127
|
|
|
|
|
|
|
return $i; |
1128
|
|
|
|
|
|
|
} |
1129
|
|
|
|
|
|
|
|
1130
|
|
|
|
|
|
|
|
1131
|
|
|
|
|
|
|
sub _isLegalDsName { |
1132
|
|
|
|
|
|
|
#rrdtool-1.0.49/src/rrd_format.h:#define DS_NAM_FMT "%19[a-zA-Z0-9_-]" |
1133
|
|
|
|
|
|
|
#rrdtool-1.2.11/src/rrd_format.h:#define DS_NAM_FMT "%19[a-zA-Z0-9_-]" |
1134
|
|
|
|
|
|
|
|
1135
|
|
|
|
|
|
|
## |
1136
|
|
|
|
|
|
|
## TODO |
1137
|
|
|
|
|
|
|
## 1.45 - Double check this with the latest 1.3 version of RRDtool |
1138
|
|
|
|
|
|
|
## to see if it has changed or not |
1139
|
|
|
|
|
|
|
## |
1140
|
|
|
|
|
|
|
|
1141
|
|
|
|
|
|
|
return $_[0] =~ /^[a-zA-Z0-9_-]{1,19}$/; |
1142
|
|
|
|
|
|
|
} |
1143
|
|
|
|
|
|
|
|
1144
|
|
|
|
|
|
|
|
1145
|
|
|
|
|
|
|
sub _rrd_def { |
1146
|
|
|
|
|
|
|
croak('Pardon?!') if ref $_[0]; |
1147
|
|
|
|
|
|
|
my $type = _valid_scheme(shift); |
1148
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
# This is calculated the same way as mrtg v2.13.2 |
1150
|
|
|
|
|
|
|
if ($type eq 'mrtg') { |
1151
|
|
|
|
|
|
|
my $step = 5; # 5 minutes |
1152
|
|
|
|
|
|
|
return { |
1153
|
|
|
|
|
|
|
step => $step * 60, |
1154
|
|
|
|
|
|
|
heartbeat => $step * 60 * 2, |
1155
|
|
|
|
|
|
|
rra => [( |
1156
|
|
|
|
|
|
|
{ step => 1, rows => int(4000 / $step) }, # 800 |
1157
|
|
|
|
|
|
|
{ step => int( 30 / $step), rows => 800 }, # if $step < 30 |
1158
|
|
|
|
|
|
|
{ step => int( 120 / $step), rows => 800 }, |
1159
|
|
|
|
|
|
|
{ step => int(1440 / $step), rows => 800 }, |
1160
|
|
|
|
|
|
|
)], |
1161
|
|
|
|
|
|
|
}; |
1162
|
|
|
|
|
|
|
} |
1163
|
|
|
|
|
|
|
|
1164
|
|
|
|
|
|
|
## |
1165
|
|
|
|
|
|
|
## TODO |
1166
|
|
|
|
|
|
|
## 1.45 Add higher resolution for hour, 6hour and 12 hour |
1167
|
|
|
|
|
|
|
## |
1168
|
|
|
|
|
|
|
|
1169
|
|
|
|
|
|
|
my $step = 1; # 1 minute highest resolution |
1170
|
|
|
|
|
|
|
my $rra = { |
1171
|
|
|
|
|
|
|
step => $step * 60, |
1172
|
|
|
|
|
|
|
heartbeat => $step * 60 * 2, |
1173
|
|
|
|
|
|
|
rra => [( |
1174
|
|
|
|
|
|
|
# Actual $step resolution (for 1.25 days retention) |
1175
|
|
|
|
|
|
|
{ step => 1, rows => int( _minutes_in('day',125) / $step) }, |
1176
|
|
|
|
|
|
|
)], |
1177
|
|
|
|
|
|
|
}; |
1178
|
|
|
|
|
|
|
|
1179
|
|
|
|
|
|
|
if ($type =~ /^(week|month|year|3years)$/i) { |
1180
|
|
|
|
|
|
|
push @{$rra->{rra}}, { |
1181
|
|
|
|
|
|
|
step => int( 30 / $step), |
1182
|
|
|
|
|
|
|
rows => int( _minutes_in('week',125) / int(30/$step) ) |
1183
|
|
|
|
|
|
|
}; # 30 minute average |
1184
|
|
|
|
|
|
|
|
1185
|
|
|
|
|
|
|
push @{$rra->{rra}}, { |
1186
|
|
|
|
|
|
|
step => int( 120 / $step), |
1187
|
|
|
|
|
|
|
rows => int( _minutes_in($type eq 'week' ? 'week' : 'month',125) |
1188
|
|
|
|
|
|
|
/ int(120/$step) ) |
1189
|
|
|
|
|
|
|
}; # 2 hour average |
1190
|
|
|
|
|
|
|
} |
1191
|
|
|
|
|
|
|
|
1192
|
|
|
|
|
|
|
if ($type =~ /^(year|3years)$/i) { |
1193
|
|
|
|
|
|
|
push @{$rra->{rra}}, { |
1194
|
|
|
|
|
|
|
step => int(1440 / $step), |
1195
|
|
|
|
|
|
|
rows => int( _minutes_in($type,125) / int(1440/$step) ) |
1196
|
|
|
|
|
|
|
}; # 1 day average |
1197
|
|
|
|
|
|
|
} |
1198
|
|
|
|
|
|
|
|
1199
|
|
|
|
|
|
|
return $rra; |
1200
|
|
|
|
|
|
|
} |
1201
|
|
|
|
|
|
|
|
1202
|
|
|
|
|
|
|
|
1203
|
|
|
|
|
|
|
sub _odd { |
1204
|
|
|
|
|
|
|
return $_[0] % 2; |
1205
|
|
|
|
|
|
|
} |
1206
|
|
|
|
|
|
|
|
1207
|
|
|
|
|
|
|
|
1208
|
|
|
|
|
|
|
sub _even { |
1209
|
|
|
|
|
|
|
return !($_[0] % 2); |
1210
|
|
|
|
|
|
|
} |
1211
|
|
|
|
|
|
|
|
1212
|
|
|
|
|
|
|
|
1213
|
|
|
|
|
|
|
sub _valid_scheme { |
1214
|
|
|
|
|
|
|
TRACE(">>> _valid_scheme()"); |
1215
|
|
|
|
|
|
|
croak('Pardon?!') if ref $_[0]; |
1216
|
|
|
|
|
|
|
#if ($_[0] =~ /^(day|week|month|year|3years|mrtg)$/i) { |
1217
|
|
|
|
|
|
|
if ($_[0] =~ /^((?:6|12)?hour|(?:half)?day|week|month|year|3years|mrtg)$/i) { |
1218
|
|
|
|
|
|
|
TRACE("'".lc($1)."' is a valid scheme."); |
1219
|
|
|
|
|
|
|
return lc($1); |
1220
|
|
|
|
|
|
|
} |
1221
|
|
|
|
|
|
|
TRACE("'@_' is not a valid scheme."); |
1222
|
|
|
|
|
|
|
return undef; |
1223
|
|
|
|
|
|
|
} |
1224
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
|
1226
|
|
|
|
|
|
|
sub _hours_in { return int((_seconds_in(@_)/60)/60); } |
1227
|
|
|
|
|
|
|
sub _minutes_in { return int(_seconds_in(@_)/60); } |
1228
|
|
|
|
|
|
|
sub _seconds_in { |
1229
|
|
|
|
|
|
|
croak('Pardon?!') if ref $_[0]; |
1230
|
|
|
|
|
|
|
my $str = lc(shift); |
1231
|
|
|
|
|
|
|
my $scale = shift || 100; |
1232
|
|
|
|
|
|
|
|
1233
|
|
|
|
|
|
|
return undef if !defined(_valid_scheme($str)); |
1234
|
|
|
|
|
|
|
|
1235
|
|
|
|
|
|
|
my %time = ( |
1236
|
|
|
|
|
|
|
# New for version 1.44 of RRD::Simple by |
1237
|
|
|
|
|
|
|
# popular request |
1238
|
|
|
|
|
|
|
'hour' => 60 * 60, |
1239
|
|
|
|
|
|
|
'6hour' => 60 * 60 * 6, |
1240
|
|
|
|
|
|
|
'quarterday' => 60 * 60 * 6, |
1241
|
|
|
|
|
|
|
'12hour' => 60 * 60 * 12, |
1242
|
|
|
|
|
|
|
'halfday' => 60 * 60 * 12, |
1243
|
|
|
|
|
|
|
|
1244
|
|
|
|
|
|
|
'day' => 60 * 60 * 24, |
1245
|
|
|
|
|
|
|
'week' => 60 * 60 * 24 * 7, |
1246
|
|
|
|
|
|
|
'month' => 60 * 60 * 24 * 31, |
1247
|
|
|
|
|
|
|
'year' => 60 * 60 * 24 * 365, |
1248
|
|
|
|
|
|
|
'3years' => 60 * 60 * 24 * 365 * 3, |
1249
|
|
|
|
|
|
|
'mrtg' => ( int(( 1440 / 5 )) * 800 ) * 60, # mrtg v2.13.2 |
1250
|
|
|
|
|
|
|
); |
1251
|
|
|
|
|
|
|
|
1252
|
|
|
|
|
|
|
my $rtn = $time{$str} * ($scale / 100); |
1253
|
|
|
|
|
|
|
return $rtn; |
1254
|
|
|
|
|
|
|
} |
1255
|
|
|
|
|
|
|
|
1256
|
|
|
|
|
|
|
|
1257
|
|
|
|
|
|
|
sub _alt_graph_name { |
1258
|
|
|
|
|
|
|
croak('Pardon?!') if ref $_[0]; |
1259
|
|
|
|
|
|
|
my $type = _valid_scheme(shift); |
1260
|
|
|
|
|
|
|
return unless defined $type; |
1261
|
|
|
|
|
|
|
|
1262
|
|
|
|
|
|
|
# New for version 1.44 of RRD::Simple by popular request |
1263
|
|
|
|
|
|
|
return 'hourly' if $type eq 'hour'; |
1264
|
|
|
|
|
|
|
return '6hourly' if $type eq '6hour' || $type eq 'quarterday'; |
1265
|
|
|
|
|
|
|
return '12hourly' if $type eq '12hour' || $type eq 'halfday'; |
1266
|
|
|
|
|
|
|
|
1267
|
|
|
|
|
|
|
return 'daily' if $type eq 'day'; |
1268
|
|
|
|
|
|
|
return 'weekly' if $type eq 'week'; |
1269
|
|
|
|
|
|
|
return 'monthly' if $type eq 'month'; |
1270
|
|
|
|
|
|
|
return 'annual' if $type eq 'year'; |
1271
|
|
|
|
|
|
|
return '3years' if $type eq '3years'; |
1272
|
|
|
|
|
|
|
return $type; |
1273
|
|
|
|
|
|
|
} |
1274
|
|
|
|
|
|
|
|
1275
|
|
|
|
|
|
|
|
1276
|
|
|
|
|
|
|
## |
1277
|
|
|
|
|
|
|
## TODO |
1278
|
|
|
|
|
|
|
## 1.45 - Check to see if there is now native support in RRDtool to |
1279
|
|
|
|
|
|
|
## add, remove or change existing sources - and if there is |
1280
|
|
|
|
|
|
|
## make this code only run for onler versions that do not have |
1281
|
|
|
|
|
|
|
## native support. |
1282
|
|
|
|
|
|
|
## |
1283
|
|
|
|
|
|
|
|
1284
|
|
|
|
|
|
|
sub _modify_source { |
1285
|
|
|
|
|
|
|
croak('Pardon?!') if ref $_[0]; |
1286
|
|
|
|
|
|
|
my ($rrdfile,$stor,$ds,$action,$dstype,$heartbeat) = @_; |
1287
|
|
|
|
|
|
|
my $rrdtool = $stor->{rrdtool}; |
1288
|
|
|
|
|
|
|
$rrdtool = '' unless defined $rrdtool; |
1289
|
|
|
|
|
|
|
|
1290
|
|
|
|
|
|
|
# Decide what action we should take |
1291
|
|
|
|
|
|
|
if ($action !~ /^(add|del)$/) { |
1292
|
|
|
|
|
|
|
my $caller = (caller(1))[3]; |
1293
|
|
|
|
|
|
|
$action = $caller =~ /\badd\b/i ? 'add' : |
1294
|
|
|
|
|
|
|
$caller =~ /\bdel(ete)?\b/i ? 'del' : undef; |
1295
|
|
|
|
|
|
|
} |
1296
|
|
|
|
|
|
|
croak "Unknown or no action passed to method _modify_source()" |
1297
|
|
|
|
|
|
|
unless defined $action && $action =~ /^(add|del)$/; |
1298
|
|
|
|
|
|
|
|
1299
|
|
|
|
|
|
|
require File::Copy; |
1300
|
|
|
|
|
|
|
require File::Temp; |
1301
|
|
|
|
|
|
|
|
1302
|
|
|
|
|
|
|
# Generate an XML dump of the RRD file |
1303
|
|
|
|
|
|
|
# - Added "tmpdir" support in 1.44 |
1304
|
|
|
|
|
|
|
my $tmpdir = defined $stor->{tmpdir} ? $stor->{tmpdir} : File::Spec->tmpdir(); |
1305
|
|
|
|
|
|
|
my ($tempXmlFileFH,$tempXmlFile) = File::Temp::tempfile( |
1306
|
|
|
|
|
|
|
DIR => $tmpdir, |
1307
|
|
|
|
|
|
|
TEMPLATE => 'rrdXXXXX', |
1308
|
|
|
|
|
|
|
SUFFIX => '.tmp', |
1309
|
|
|
|
|
|
|
); |
1310
|
|
|
|
|
|
|
|
1311
|
|
|
|
|
|
|
# Check that we managed to get a sane temporary filename |
1312
|
|
|
|
|
|
|
croak "File::Temp::tempfile() failed to return a temporary filename" |
1313
|
|
|
|
|
|
|
unless defined $tempXmlFile; |
1314
|
|
|
|
|
|
|
TRACE("_modify_source(): \$tempXmlFile = $tempXmlFile"); |
1315
|
|
|
|
|
|
|
|
1316
|
|
|
|
|
|
|
# Try the internal perl way first (portable) |
1317
|
|
|
|
|
|
|
eval { |
1318
|
|
|
|
|
|
|
# Patch to rrd_dump.c emailed to Tobi and developers |
1319
|
|
|
|
|
|
|
# list by nicolaw/heds on 2006/01/08 |
1320
|
|
|
|
|
|
|
if ($RRDs::VERSION >= 1.2013) { |
1321
|
|
|
|
|
|
|
my @rtn = RRDs::dump($rrdfile,$tempXmlFile); |
1322
|
|
|
|
|
|
|
my $error = RRDs::error(); |
1323
|
|
|
|
|
|
|
croak($error) if $error; |
1324
|
|
|
|
|
|
|
} |
1325
|
|
|
|
|
|
|
}; |
1326
|
|
|
|
|
|
|
|
1327
|
|
|
|
|
|
|
# Do it the old fashioned way |
1328
|
|
|
|
|
|
|
if ($@ || !-f $tempXmlFile || (stat($tempXmlFile))[7] < 200) { |
1329
|
|
|
|
|
|
|
croak "rrdtool binary '$rrdtool' does not exist or is not executable" |
1330
|
|
|
|
|
|
|
if !defined $rrdtool || !-f $rrdtool || !-x $rrdtool; |
1331
|
|
|
|
|
|
|
_safe_exec(sprintf('%s dump %s > %s',$rrdtool,$rrdfile,$tempXmlFile)); |
1332
|
|
|
|
|
|
|
} |
1333
|
|
|
|
|
|
|
|
1334
|
|
|
|
|
|
|
# Read in the new temporary XML dump file |
1335
|
|
|
|
|
|
|
open(IN, "<$tempXmlFile") || croak "Unable to open '$tempXmlFile': $!"; |
1336
|
|
|
|
|
|
|
|
1337
|
|
|
|
|
|
|
# Open XML output file |
1338
|
|
|
|
|
|
|
# my $tempImportXmlFile = File::Temp::tmpnam(); |
1339
|
|
|
|
|
|
|
# - Added "tmpdir" support in 1.44 |
1340
|
|
|
|
|
|
|
my ($tempImportXmlFileFH,$tempImportXmlFile) = File::Temp::tempfile( |
1341
|
|
|
|
|
|
|
DIR => $tmpdir, |
1342
|
|
|
|
|
|
|
TEMPLATE => 'rrdXXXXX', |
1343
|
|
|
|
|
|
|
SUFFIX => '.tmp', |
1344
|
|
|
|
|
|
|
); |
1345
|
|
|
|
|
|
|
open(OUT, ">$tempImportXmlFile") |
1346
|
|
|
|
|
|
|
|| croak "Unable to open '$tempImportXmlFile': $!"; |
1347
|
|
|
|
|
|
|
|
1348
|
|
|
|
|
|
|
# Create a marker hash ref to store temporary state |
1349
|
|
|
|
|
|
|
my $marker = { |
1350
|
|
|
|
|
|
|
currentDSIndex => 0, |
1351
|
|
|
|
|
|
|
deleteDSIndex => undef, |
1352
|
|
|
|
|
|
|
addedNewDS => 0, |
1353
|
|
|
|
|
|
|
parse => 0, |
1354
|
|
|
|
|
|
|
version => 1, |
1355
|
|
|
|
|
|
|
}; |
1356
|
|
|
|
|
|
|
|
1357
|
|
|
|
|
|
|
# Parse the input XML file |
1358
|
|
|
|
|
|
|
while (local $_ = ) { |
1359
|
|
|
|
|
|
|
chomp; |
1360
|
|
|
|
|
|
|
|
1361
|
|
|
|
|
|
|
# Find out what index number the existing DS definition is in |
1362
|
|
|
|
|
|
|
if ($action eq 'del' && /\s*(\S+)\s*<\/name>/) { |
1363
|
|
|
|
|
|
|
$marker->{deleteIndex} = $marker->{currentDSIndex} if $1 eq $ds; |
1364
|
|
|
|
|
|
|
$marker->{currentDSIndex}++; |
1365
|
|
|
|
|
|
|
} |
1366
|
|
|
|
|
|
|
|
1367
|
|
|
|
|
|
|
# Add the DS definition |
1368
|
|
|
|
|
|
|
if ($action eq 'add' && !$marker->{addedNewDS} && //) { |
1369
|
|
|
|
|
|
|
print OUT <
|
1370
|
|
|
|
|
|
|
|
1371
|
|
|
|
|
|
|
$ds |
1372
|
|
|
|
|
|
|
$dstype |
1373
|
|
|
|
|
|
|
$heartbeat |
1374
|
|
|
|
|
|
|
0.0000000000e+00 |
1375
|
|
|
|
|
|
|
NaN |
1376
|
|
|
|
|
|
|
|
1377
|
|
|
|
|
|
|
|
1378
|
|
|
|
|
|
|
UNKN |
1379
|
|
|
|
|
|
|
0.0000000000e+00 |
1380
|
|
|
|
|
|
|
0 |
1381
|
|
|
|
|
|
|
|
1382
|
|
|
|
|
|
|
|
1383
|
|
|
|
|
|
|
EndDS |
1384
|
|
|
|
|
|
|
$marker->{addedNewDS} = 1; |
1385
|
|
|
|
|
|
|
} |
1386
|
|
|
|
|
|
|
|
1387
|
|
|
|
|
|
|
# Insert DS under CDP_PREP entity |
1388
|
|
|
|
|
|
|
if ($action eq 'add' && /<\/cdp_prep>/) { |
1389
|
|
|
|
|
|
|
# Version 0003 RRD from rrdtool 1.2x |
1390
|
|
|
|
|
|
|
if ($marker->{version} >= 3) { |
1391
|
|
|
|
|
|
|
print OUT " \n"; |
1392
|
|
|
|
|
|
|
print OUT " 0.0000000000e+00 \n"; |
1393
|
|
|
|
|
|
|
print OUT " 0.0000000000e+00 \n"; |
1394
|
|
|
|
|
|
|
print OUT " NaN \n"; |
1395
|
|
|
|
|
|
|
print OUT " 0 \n"; |
1396
|
|
|
|
|
|
|
print OUT " \n"; |
1397
|
|
|
|
|
|
|
|
1398
|
|
|
|
|
|
|
# Version 0001 RRD from rrdtool 1.0x |
1399
|
|
|
|
|
|
|
} else { |
1400
|
|
|
|
|
|
|
print OUT " NaN 0 \n"; |
1401
|
|
|
|
|
|
|
} |
1402
|
|
|
|
|
|
|
} |
1403
|
|
|
|
|
|
|
|
1404
|
|
|
|
|
|
|
# Look for the end of an RRA |
1405
|
|
|
|
|
|
|
if (/<\/database>/) { |
1406
|
|
|
|
|
|
|
$marker->{parse} = 0; |
1407
|
|
|
|
|
|
|
|
1408
|
|
|
|
|
|
|
# Find the dumped RRD version (must take from the XML, not the RRD) |
1409
|
|
|
|
|
|
|
} elsif (/\s*([0-9\.]+)\s*<\/version>/) { |
1410
|
|
|
|
|
|
|
$marker->{version} = ($1 + 1 - 1); |
1411
|
|
|
|
|
|
|
} |
1412
|
|
|
|
|
|
|
|
1413
|
|
|
|
|
|
|
# Add the extra " NaN " under the RRAs. Just print normal lines |
1414
|
|
|
|
|
|
|
if ($marker->{parse} == 1) { |
1415
|
|
|
|
|
|
|
if ($_ =~ /^(.+ .+)(<\/row>.*)/) { |
1416
|
|
|
|
|
|
|
print OUT $1; |
1417
|
|
|
|
|
|
|
print OUT " NaN " if $action eq 'add'; |
1418
|
|
|
|
|
|
|
print OUT $2; |
1419
|
|
|
|
|
|
|
print OUT "\n"; |
1420
|
|
|
|
|
|
|
} |
1421
|
|
|
|
|
|
|
} else { |
1422
|
|
|
|
|
|
|
print OUT "$_\n"; |
1423
|
|
|
|
|
|
|
} |
1424
|
|
|
|
|
|
|
|
1425
|
|
|
|
|
|
|
# Look for the start of an RRA |
1426
|
|
|
|
|
|
|
if (//) { |
1427
|
|
|
|
|
|
|
$marker->{parse} = 1; |
1428
|
|
|
|
|
|
|
} |
1429
|
|
|
|
|
|
|
} |
1430
|
|
|
|
|
|
|
|
1431
|
|
|
|
|
|
|
# Close the files |
1432
|
|
|
|
|
|
|
close(IN) || croak "Unable to close '$tempXmlFile': $!"; |
1433
|
|
|
|
|
|
|
close(OUT) || croak "Unable to close '$tempImportXmlFile': $!"; |
1434
|
|
|
|
|
|
|
|
1435
|
|
|
|
|
|
|
# Import the new output file in to the old RRD filename |
1436
|
|
|
|
|
|
|
my $new_rrdfile = File::Temp::tmpnam(); |
1437
|
|
|
|
|
|
|
TRACE("_modify_source(): \$new_rrdfile = $new_rrdfile"); |
1438
|
|
|
|
|
|
|
|
1439
|
|
|
|
|
|
|
# Try the internal perl way first (portable) |
1440
|
|
|
|
|
|
|
eval { |
1441
|
|
|
|
|
|
|
if ($RRDs::VERSION >= 1.0049) { |
1442
|
|
|
|
|
|
|
my @rtn = RRDs::restore($tempImportXmlFile,$new_rrdfile); |
1443
|
|
|
|
|
|
|
my $error = RRDs::error(); |
1444
|
|
|
|
|
|
|
croak($error) if $error; |
1445
|
|
|
|
|
|
|
} |
1446
|
|
|
|
|
|
|
}; |
1447
|
|
|
|
|
|
|
|
1448
|
|
|
|
|
|
|
# Do it the old fashioned way |
1449
|
|
|
|
|
|
|
if ($@ || !-f $new_rrdfile || (stat($new_rrdfile))[7] < 200) { |
1450
|
|
|
|
|
|
|
croak "rrdtool binary '$rrdtool' does not exist or is not executable" |
1451
|
|
|
|
|
|
|
unless (-f $rrdtool && -x $rrdtool); |
1452
|
|
|
|
|
|
|
my $cmd = sprintf('%s restore %s %s',$rrdtool,$tempImportXmlFile,$new_rrdfile); |
1453
|
|
|
|
|
|
|
my $rtn = _safe_exec($cmd); |
1454
|
|
|
|
|
|
|
|
1455
|
|
|
|
|
|
|
# At least check the file is created |
1456
|
|
|
|
|
|
|
unless (-f $new_rrdfile) { |
1457
|
|
|
|
|
|
|
_nuke_tmp($tempXmlFile,$tempImportXmlFile); |
1458
|
|
|
|
|
|
|
croak "Command '$cmd' failed to create the new RRD file '$new_rrdfile': $rtn"; |
1459
|
|
|
|
|
|
|
} |
1460
|
|
|
|
|
|
|
} |
1461
|
|
|
|
|
|
|
|
1462
|
|
|
|
|
|
|
# Remove the temporary files |
1463
|
|
|
|
|
|
|
_nuke_tmp($tempXmlFile,$tempImportXmlFile); |
1464
|
|
|
|
|
|
|
sub _nuke_tmp { |
1465
|
|
|
|
|
|
|
for (@_) { |
1466
|
|
|
|
|
|
|
unlink($_) || |
1467
|
|
|
|
|
|
|
carp("Unable to unlink temporary file '$_': $!"); |
1468
|
|
|
|
|
|
|
} |
1469
|
|
|
|
|
|
|
} |
1470
|
|
|
|
|
|
|
|
1471
|
|
|
|
|
|
|
# Return the new RRD filename |
1472
|
|
|
|
|
|
|
return wantarray ? ($new_rrdfile) : $new_rrdfile; |
1473
|
|
|
|
|
|
|
} |
1474
|
|
|
|
|
|
|
|
1475
|
|
|
|
|
|
|
|
1476
|
|
|
|
|
|
|
## |
1477
|
|
|
|
|
|
|
## TODO |
1478
|
|
|
|
|
|
|
## 1.45 - Improve this _safe_exec function to see if it can be made |
1479
|
|
|
|
|
|
|
## more robust and use any better CPAN modules if that happen |
1480
|
|
|
|
|
|
|
## to already be installed on the users system (don't add any |
1481
|
|
|
|
|
|
|
## new module dependancies though) |
1482
|
|
|
|
|
|
|
## |
1483
|
|
|
|
|
|
|
|
1484
|
|
|
|
|
|
|
sub _safe_exec { |
1485
|
|
|
|
|
|
|
croak('Pardon?!') if ref $_[0]; |
1486
|
|
|
|
|
|
|
my $cmd = shift; |
1487
|
|
|
|
|
|
|
if ($cmd =~ /^([\/\.\_\-a-zA-Z0-9 >]+)$/) { |
1488
|
|
|
|
|
|
|
$cmd = $1; |
1489
|
|
|
|
|
|
|
TRACE($cmd); |
1490
|
|
|
|
|
|
|
system($cmd); |
1491
|
|
|
|
|
|
|
if ($? == -1) { |
1492
|
|
|
|
|
|
|
croak "Failed to execute command '$cmd': $!\n"; |
1493
|
|
|
|
|
|
|
} elsif ($? & 127) { |
1494
|
|
|
|
|
|
|
croak(sprintf("While executing command '%s', child died ". |
1495
|
|
|
|
|
|
|
"with signal %d, %s coredump\n", $cmd, |
1496
|
|
|
|
|
|
|
($? & 127), ($? & 128) ? 'with' : 'without')); |
1497
|
|
|
|
|
|
|
} |
1498
|
|
|
|
|
|
|
my $exit_value = $? >> 8; |
1499
|
|
|
|
|
|
|
croak "Error caught from '$cmd'" if $exit_value != 0; |
1500
|
|
|
|
|
|
|
return $exit_value; |
1501
|
|
|
|
|
|
|
} else { |
1502
|
|
|
|
|
|
|
croak "Unexpected potentially unsafe command will not be executed: $cmd"; |
1503
|
|
|
|
|
|
|
} |
1504
|
|
|
|
|
|
|
} |
1505
|
|
|
|
|
|
|
|
1506
|
|
|
|
|
|
|
|
1507
|
|
|
|
|
|
|
sub _find_binary { |
1508
|
|
|
|
|
|
|
croak('Pardon?!') if ref $_[0]; |
1509
|
|
|
|
|
|
|
my $binary = shift || 'rrdtool'; |
1510
|
|
|
|
|
|
|
return $binary if -f $binary && -x $binary; |
1511
|
|
|
|
|
|
|
|
1512
|
|
|
|
|
|
|
my @paths = File::Spec->path(); |
1513
|
|
|
|
|
|
|
my $rrds_path = dirname($INC{'RRDs.pm'}); |
1514
|
|
|
|
|
|
|
push @paths, $rrds_path; |
1515
|
|
|
|
|
|
|
push @paths, File::Spec->catdir($rrds_path, |
1516
|
|
|
|
|
|
|
File::Spec->updir(),File::Spec->updir(),'bin'); |
1517
|
|
|
|
|
|
|
|
1518
|
|
|
|
|
|
|
for my $path (@paths) { |
1519
|
|
|
|
|
|
|
my $filename = File::Spec->catfile($path,$binary); |
1520
|
|
|
|
|
|
|
return $filename if -f $filename && -x $filename; |
1521
|
|
|
|
|
|
|
} |
1522
|
|
|
|
|
|
|
|
1523
|
|
|
|
|
|
|
my $path = File::Spec->catdir(File::Spec->rootdir(),'usr','local'); |
1524
|
|
|
|
|
|
|
if (opendir(DH,$path)) { |
1525
|
|
|
|
|
|
|
my @dirs = sort { $b cmp $a } grep(/^rrdtool/,readdir(DH)); |
1526
|
|
|
|
|
|
|
closedir(DH) || carp "Unable to close file handle: $!"; |
1527
|
|
|
|
|
|
|
for my $dir (@dirs) { |
1528
|
|
|
|
|
|
|
my $filename = File::Spec->catfile($path,$dir,'bin',$binary); |
1529
|
|
|
|
|
|
|
return $filename if -f $filename && -x $filename; |
1530
|
|
|
|
|
|
|
} |
1531
|
|
|
|
|
|
|
} |
1532
|
|
|
|
|
|
|
} |
1533
|
|
|
|
|
|
|
|
1534
|
|
|
|
|
|
|
|
1535
|
|
|
|
|
|
|
sub _guess_filename { |
1536
|
|
|
|
|
|
|
croak('Pardon?!') if !defined $_[0] || ref($_[0]) ne 'HASH'; |
1537
|
|
|
|
|
|
|
my $stor = shift; |
1538
|
|
|
|
|
|
|
if (defined $stor->{file}) { |
1539
|
|
|
|
|
|
|
TRACE("_guess_filename = \$stor->{file} = $stor->{file}"); |
1540
|
|
|
|
|
|
|
return $stor->{file}; |
1541
|
|
|
|
|
|
|
} |
1542
|
|
|
|
|
|
|
my ($basename, $dirname, $extension) = fileparse($0, '\.[^\.]+'); |
1543
|
|
|
|
|
|
|
TRACE("_guess_filename = calculated = $dirname$basename.rrd"); |
1544
|
|
|
|
|
|
|
return "$dirname$basename.rrd"; |
1545
|
|
|
|
|
|
|
} |
1546
|
|
|
|
|
|
|
|
1547
|
|
|
|
|
|
|
|
1548
|
|
|
|
|
|
|
sub DESTROY { |
1549
|
|
|
|
|
|
|
my $self = shift; |
1550
|
|
|
|
|
|
|
delete $objstore->{_refaddr($self)}; |
1551
|
|
|
|
|
|
|
} |
1552
|
|
|
|
|
|
|
|
1553
|
|
|
|
|
|
|
|
1554
|
|
|
|
|
|
|
sub TRACE { |
1555
|
|
|
|
|
|
|
return unless $DEBUG; |
1556
|
|
|
|
|
|
|
carp(shift()); |
1557
|
|
|
|
|
|
|
} |
1558
|
|
|
|
|
|
|
|
1559
|
|
|
|
|
|
|
|
1560
|
|
|
|
|
|
|
sub DUMP { |
1561
|
|
|
|
|
|
|
return unless $DEBUG; |
1562
|
|
|
|
|
|
|
eval { |
1563
|
|
|
|
|
|
|
require Data::Dumper; |
1564
|
|
|
|
|
|
|
$Data::Dumper::Indent = 2; |
1565
|
|
|
|
|
|
|
$Data::Dumper::Terse = 1; |
1566
|
|
|
|
|
|
|
carp(shift().': '.Data::Dumper::Dumper(shift())); |
1567
|
|
|
|
|
|
|
} |
1568
|
|
|
|
|
|
|
} |
1569
|
|
|
|
|
|
|
|
1570
|
|
|
|
|
|
|
BEGIN { |
1571
|
|
|
|
|
|
|
eval "use RRDs"; |
1572
|
|
|
|
|
|
|
if ($@) { |
1573
|
|
|
|
|
|
|
carp qq{ |
1574
|
|
|
|
|
|
|
+-----------------------------------------------------------------------------+ |
1575
|
|
|
|
|
|
|
| ERROR! -- Could not load RRDs.pm | |
1576
|
|
|
|
|
|
|
| | |
1577
|
|
|
|
|
|
|
| RRD::Simple requires RRDs.pm (a part of RRDtool) in order to function. You | |
1578
|
|
|
|
|
|
|
| can download a copy of RRDtool from http://www.rrdtool.org. See the INSTALL | |
1579
|
|
|
|
|
|
|
| document for more details. | |
1580
|
|
|
|
|
|
|
+-----------------------------------------------------------------------------+ |
1581
|
|
|
|
|
|
|
|
1582
|
|
|
|
|
|
|
} unless $ENV{AUTOMATED_TESTING}; |
1583
|
|
|
|
|
|
|
} |
1584
|
|
|
|
|
|
|
} |
1585
|
|
|
|
|
|
|
|
1586
|
|
|
|
|
|
|
|
1587
|
|
|
|
|
|
|
1; |
1588
|
|
|
|
|
|
|
|
1589
|
|
|
|
|
|
|
|
1590
|
|
|
|
|
|
|
############################################################### |
1591
|
|
|
|
|
|
|
# This tie code is from Tie::Cycle |
1592
|
|
|
|
|
|
|
# written by brian d foy, |
1593
|
|
|
|
|
|
|
|
1594
|
|
|
|
|
|
|
package RRD::Simple::_Colour; |
1595
|
|
|
|
|
|
|
|
1596
|
|
|
|
|
|
|
sub TIESCALAR { |
1597
|
|
|
|
|
|
|
my ($class,$list_ref) = @_; |
1598
|
|
|
|
|
|
|
my @shallow_copy = map { $_ } @$list_ref; |
1599
|
|
|
|
|
|
|
return unless UNIVERSAL::isa( $list_ref, 'ARRAY' ); |
1600
|
|
|
|
|
|
|
my $self = [ 0, scalar @shallow_copy, \@shallow_copy ]; |
1601
|
|
|
|
|
|
|
bless $self, $class; |
1602
|
|
|
|
|
|
|
} |
1603
|
|
|
|
|
|
|
|
1604
|
|
|
|
|
|
|
sub FETCH { |
1605
|
|
|
|
|
|
|
my $self = shift; |
1606
|
|
|
|
|
|
|
my $index = $$self[0]++; |
1607
|
|
|
|
|
|
|
$$self[0] %= $self->[1]; |
1608
|
|
|
|
|
|
|
return $self->[2]->[ $index ]; |
1609
|
|
|
|
|
|
|
} |
1610
|
|
|
|
|
|
|
|
1611
|
|
|
|
|
|
|
sub STORE { |
1612
|
|
|
|
|
|
|
my ($self,$list_ref) = @_; |
1613
|
|
|
|
|
|
|
return unless ref $list_ref eq ref []; |
1614
|
|
|
|
|
|
|
return unless @$list_ref > 1; |
1615
|
|
|
|
|
|
|
$self = [ 0, scalar @$list_ref, $list_ref ]; |
1616
|
|
|
|
|
|
|
} |
1617
|
|
|
|
|
|
|
|
1618
|
|
|
|
|
|
|
1; |
1619
|
|
|
|
|
|
|
|
1620
|
|
|
|
|
|
|
|
1621
|
|
|
|
|
|
|
|
1622
|
|
|
|
|
|
|
|
1623
|
|
|
|
|
|
|
=pod |
1624
|
|
|
|
|
|
|
|
1625
|
|
|
|
|
|
|
=head1 NAME |
1626
|
|
|
|
|
|
|
|
1627
|
|
|
|
|
|
|
RRD::Simple - Simple interface to create and store data in RRD files |
1628
|
|
|
|
|
|
|
|
1629
|
|
|
|
|
|
|
=head1 SYNOPSIS |
1630
|
|
|
|
|
|
|
|
1631
|
|
|
|
|
|
|
use strict; |
1632
|
|
|
|
|
|
|
use RRD::Simple (); |
1633
|
|
|
|
|
|
|
|
1634
|
|
|
|
|
|
|
# Create an interface object |
1635
|
|
|
|
|
|
|
my $rrd = RRD::Simple->new( file => "myfile.rrd" ); |
1636
|
|
|
|
|
|
|
|
1637
|
|
|
|
|
|
|
# Create a new RRD file with 3 data sources called |
1638
|
|
|
|
|
|
|
# bytesIn, bytesOut and faultsPerSec. |
1639
|
|
|
|
|
|
|
$rrd->create( |
1640
|
|
|
|
|
|
|
bytesIn => "GAUGE", |
1641
|
|
|
|
|
|
|
bytesOut => "GAUGE", |
1642
|
|
|
|
|
|
|
faultsPerSec => "COUNTER" |
1643
|
|
|
|
|
|
|
); |
1644
|
|
|
|
|
|
|
|
1645
|
|
|
|
|
|
|
# Put some arbitary data values in the RRD file for the same |
1646
|
|
|
|
|
|
|
# 3 data sources called bytesIn, bytesOut and faultsPerSec. |
1647
|
|
|
|
|
|
|
$rrd->update( |
1648
|
|
|
|
|
|
|
bytesIn => 10039, |
1649
|
|
|
|
|
|
|
bytesOut => 389, |
1650
|
|
|
|
|
|
|
faultsPerSec => 0.4 |
1651
|
|
|
|
|
|
|
); |
1652
|
|
|
|
|
|
|
|
1653
|
|
|
|
|
|
|
# Generate graphs: |
1654
|
|
|
|
|
|
|
# /var/tmp/myfile-daily.png, /var/tmp/myfile-weekly.png |
1655
|
|
|
|
|
|
|
# /var/tmp/myfile-monthly.png, /var/tmp/myfile-annual.png |
1656
|
|
|
|
|
|
|
my %rtn = $rrd->graph( |
1657
|
|
|
|
|
|
|
destination => "/var/tmp", |
1658
|
|
|
|
|
|
|
title => "Network Interface eth0", |
1659
|
|
|
|
|
|
|
vertical_label => "Bytes/Faults", |
1660
|
|
|
|
|
|
|
interlaced => "" |
1661
|
|
|
|
|
|
|
); |
1662
|
|
|
|
|
|
|
printf("Created %s\n",join(", ",map { $rtn{$_}->[0] } keys %rtn)); |
1663
|
|
|
|
|
|
|
|
1664
|
|
|
|
|
|
|
# Return information about an RRD file |
1665
|
|
|
|
|
|
|
my $info = $rrd->info; |
1666
|
|
|
|
|
|
|
require Data::Dumper; |
1667
|
|
|
|
|
|
|
print Data::Dumper::Dumper($info); |
1668
|
|
|
|
|
|
|
|
1669
|
|
|
|
|
|
|
# Get unixtime of when RRD file was last updated |
1670
|
|
|
|
|
|
|
my $lastUpdated = $rrd->last; |
1671
|
|
|
|
|
|
|
print "myfile.rrd was last updated at " . |
1672
|
|
|
|
|
|
|
scalar(localtime($lastUpdated)) . "\n"; |
1673
|
|
|
|
|
|
|
|
1674
|
|
|
|
|
|
|
# Get list of data source names from an RRD file |
1675
|
|
|
|
|
|
|
my @dsnames = $rrd->sources; |
1676
|
|
|
|
|
|
|
print "Available data sources: " . join(", ", @dsnames) . "\n"; |
1677
|
|
|
|
|
|
|
|
1678
|
|
|
|
|
|
|
# And for the ultimately lazy, you could create and update |
1679
|
|
|
|
|
|
|
# an RRD in one go using a one-liner like this: |
1680
|
|
|
|
|
|
|
perl -MRRD::Simple=:all -e"update(@ARGV)" myfile.rrd bytesIn 99999 |
1681
|
|
|
|
|
|
|
|
1682
|
|
|
|
|
|
|
=head1 DESCRIPTION |
1683
|
|
|
|
|
|
|
|
1684
|
|
|
|
|
|
|
RRD::Simple provides a simple interface to RRDTool's RRDs module. |
1685
|
|
|
|
|
|
|
This module does not currently offer a C method that is |
1686
|
|
|
|
|
|
|
available in the RRDs module. |
1687
|
|
|
|
|
|
|
|
1688
|
|
|
|
|
|
|
It does however create RRD files with a sensible set of default RRA |
1689
|
|
|
|
|
|
|
(Round Robin Archive) definitions, and can dynamically add new |
1690
|
|
|
|
|
|
|
data source names to an existing RRD file. |
1691
|
|
|
|
|
|
|
|
1692
|
|
|
|
|
|
|
This module is ideal for quick and simple storage of data within an |
1693
|
|
|
|
|
|
|
RRD file if you do not need to, nor want to, bother defining custom |
1694
|
|
|
|
|
|
|
RRA definitions. |
1695
|
|
|
|
|
|
|
|
1696
|
|
|
|
|
|
|
=head1 METHODS |
1697
|
|
|
|
|
|
|
|
1698
|
|
|
|
|
|
|
=head2 new |
1699
|
|
|
|
|
|
|
|
1700
|
|
|
|
|
|
|
my $rrd = RRD::Simple->new( |
1701
|
|
|
|
|
|
|
file => "myfile.rrd", |
1702
|
|
|
|
|
|
|
rrdtool => "/usr/local/rrdtool-1.2.11/bin/rrdtool", |
1703
|
|
|
|
|
|
|
tmpdir => "/var/tmp", |
1704
|
|
|
|
|
|
|
cf => [ qw(AVERAGE MAX) ], |
1705
|
|
|
|
|
|
|
default_dstype => "GAUGE", |
1706
|
|
|
|
|
|
|
on_missing_ds => "add", |
1707
|
|
|
|
|
|
|
); |
1708
|
|
|
|
|
|
|
|
1709
|
|
|
|
|
|
|
The C parameter is currently optional but will become mandatory in |
1710
|
|
|
|
|
|
|
future releases, replacing the optional C<$rrdfile> parameters on subsequent |
1711
|
|
|
|
|
|
|
methods. This parameter specifies the RRD filename to be used. |
1712
|
|
|
|
|
|
|
|
1713
|
|
|
|
|
|
|
The C parameter is optional. It specifically defines where the |
1714
|
|
|
|
|
|
|
C binary can be found. If not specified, the module will search for |
1715
|
|
|
|
|
|
|
the C binary in your path, an additional location relative to where |
1716
|
|
|
|
|
|
|
the C module was loaded from, and in /usr/local/rrdtool*. |
1717
|
|
|
|
|
|
|
|
1718
|
|
|
|
|
|
|
The C parameter is option and is only used what automatically adding |
1719
|
|
|
|
|
|
|
a new data source to an existing RRD file. By default any temporary files |
1720
|
|
|
|
|
|
|
will be placed in your default system temp directory (typically /tmp on Linux, |
1721
|
|
|
|
|
|
|
or whatever your TMPDIR environment variable is set to). This parameter can |
1722
|
|
|
|
|
|
|
be used for force any temporary files to be created in a specific directory. |
1723
|
|
|
|
|
|
|
|
1724
|
|
|
|
|
|
|
The C binary is only used by the C method, and only |
1725
|
|
|
|
|
|
|
under certain circumstances. The C method may also be called |
1726
|
|
|
|
|
|
|
automatically by the C method, if data point values for a previously |
1727
|
|
|
|
|
|
|
undefined data source are provided for insertion. |
1728
|
|
|
|
|
|
|
|
1729
|
|
|
|
|
|
|
The C parameter is optional, but when specified expects an array |
1730
|
|
|
|
|
|
|
reference. The C parameter defines which consolidation functions are |
1731
|
|
|
|
|
|
|
used in round robin archives (RRAs) when creating new RRD files. Valid |
1732
|
|
|
|
|
|
|
values are AVERAGE, MIN, MAX and LAST. The default value is AVERAGE and |
1733
|
|
|
|
|
|
|
MAX. |
1734
|
|
|
|
|
|
|
|
1735
|
|
|
|
|
|
|
The C parameter is optional. Specifying the default data |
1736
|
|
|
|
|
|
|
source type (DST) through the new() method allows the DST to be localised |
1737
|
|
|
|
|
|
|
to the $rrd object instance rather than be global to the RRD::Simple package. |
1738
|
|
|
|
|
|
|
See L<$RRD::Simple::DEFAULT_DSTYPE>. |
1739
|
|
|
|
|
|
|
|
1740
|
|
|
|
|
|
|
The C parameter is optional and will default to "add" when |
1741
|
|
|
|
|
|
|
not defined. This parameter will determine what will happen if you try |
1742
|
|
|
|
|
|
|
to insert or update data for a data source name that does not exist in |
1743
|
|
|
|
|
|
|
the RRD file. Valid values are "add", "ignore" and "die". |
1744
|
|
|
|
|
|
|
|
1745
|
|
|
|
|
|
|
=head2 create |
1746
|
|
|
|
|
|
|
|
1747
|
|
|
|
|
|
|
$rrd->create($rrdfile, $period, |
1748
|
|
|
|
|
|
|
source_name => "TYPE", |
1749
|
|
|
|
|
|
|
source_name => "TYPE", |
1750
|
|
|
|
|
|
|
source_name => "TYPE" |
1751
|
|
|
|
|
|
|
); |
1752
|
|
|
|
|
|
|
|
1753
|
|
|
|
|
|
|
This method will create a new RRD file on disk. |
1754
|
|
|
|
|
|
|
|
1755
|
|
|
|
|
|
|
C<$rrdfile> is optional and will default to using the RRD filename specified |
1756
|
|
|
|
|
|
|
by the C constructor method, or C<$0.rrd>. (Script basename with the file |
1757
|
|
|
|
|
|
|
extension of .rrd). |
1758
|
|
|
|
|
|
|
|
1759
|
|
|
|
|
|
|
C<$period> is optional and will default to C. Valid options are C, |
1760
|
|
|
|
|
|
|
C<6hour>/C, C<12hour>/C, C, C, C, |
1761
|
|
|
|
|
|
|
C, C<3years> and C. Specifying a data retention period value will |
1762
|
|
|
|
|
|
|
change how long data will be retained for within the RRD file. The C |
1763
|
|
|
|
|
|
|
scheme will try and mimic the data retention period used by MRTG v2.13.2 |
1764
|
|
|
|
|
|
|
(L. |
1765
|
|
|
|
|
|
|
|
1766
|
|
|
|
|
|
|
The C data retention period uses a data stepping resolution of 300 |
1767
|
|
|
|
|
|
|
seconds (5 minutes) and heartbeat of 600 seconds (10 minutes), whereas all the |
1768
|
|
|
|
|
|
|
other data retention periods use a data stepping resolution of 60 seconds |
1769
|
|
|
|
|
|
|
(1 minute) and heartbeat of 120 seconds (2 minutes). |
1770
|
|
|
|
|
|
|
|
1771
|
|
|
|
|
|
|
Each data source name should specify the data source type. Valid data source |
1772
|
|
|
|
|
|
|
types (DSTs) are GAUGE, COUNTER, DERIVE and ABSOLUTE. See the section |
1773
|
|
|
|
|
|
|
regrading DSTs at L |
1774
|
|
|
|
|
|
|
for further information. |
1775
|
|
|
|
|
|
|
|
1776
|
|
|
|
|
|
|
RRD::Simple will croak and die if you try to create an RRD file that already |
1777
|
|
|
|
|
|
|
exists. |
1778
|
|
|
|
|
|
|
|
1779
|
|
|
|
|
|
|
=head2 update |
1780
|
|
|
|
|
|
|
|
1781
|
|
|
|
|
|
|
$rrd->update($rrdfile, $unixtime, |
1782
|
|
|
|
|
|
|
source_name => "VALUE", |
1783
|
|
|
|
|
|
|
source_name => "VALUE", |
1784
|
|
|
|
|
|
|
source_name => "VALUE" |
1785
|
|
|
|
|
|
|
); |
1786
|
|
|
|
|
|
|
|
1787
|
|
|
|
|
|
|
This method will update an RRD file by inserting new data point values |
1788
|
|
|
|
|
|
|
in to the RRD file. |
1789
|
|
|
|
|
|
|
|
1790
|
|
|
|
|
|
|
C<$rrdfile> is optional and will default to using the RRD filename specified |
1791
|
|
|
|
|
|
|
by the C constructor method, or C<$0.rrd>. (Script basename with the file |
1792
|
|
|
|
|
|
|
extension of .rrd). |
1793
|
|
|
|
|
|
|
|
1794
|
|
|
|
|
|
|
C<$unixtime> is optional and will default to C |
1795
|
|
|
|
|
|
|
Specifying this value will determine the date and time that your data point |
1796
|
|
|
|
|
|
|
values will be stored against in the RRD file. |
1797
|
|
|
|
|
|
|
|
1798
|
|
|
|
|
|
|
If you try to update a value for a data source that does not exist, it will |
1799
|
|
|
|
|
|
|
automatically be added for you. The data source type will be set to whatever |
1800
|
|
|
|
|
|
|
is contained in the C<$RRD::Simple::DEFAULT_DSTYPE> variable. (See the |
1801
|
|
|
|
|
|
|
VARIABLES section below). |
1802
|
|
|
|
|
|
|
|
1803
|
|
|
|
|
|
|
If you explicitly do not want this to happen, then you should check that you |
1804
|
|
|
|
|
|
|
are only updating pre-existing data source names using the C method. |
1805
|
|
|
|
|
|
|
You can manually add new data sources to an RRD file by using the C |
1806
|
|
|
|
|
|
|
method, which requires you to explicitly set the data source type. |
1807
|
|
|
|
|
|
|
|
1808
|
|
|
|
|
|
|
If you try to update an RRD file that does not exist, it will attept to create |
1809
|
|
|
|
|
|
|
the RRD file for you using the same behaviour as described above. A warning |
1810
|
|
|
|
|
|
|
message will be displayed indicating that the RRD file is being created for |
1811
|
|
|
|
|
|
|
you if have perl warnings turned on. |
1812
|
|
|
|
|
|
|
|
1813
|
|
|
|
|
|
|
=head2 last |
1814
|
|
|
|
|
|
|
|
1815
|
|
|
|
|
|
|
my $unixtime = $rrd->last($rrdfile); |
1816
|
|
|
|
|
|
|
|
1817
|
|
|
|
|
|
|
This method returns the last (most recent) data point entry time in the RRD |
1818
|
|
|
|
|
|
|
file in UNIX time (seconds since the epoch; Jan 1st 1970). This value should |
1819
|
|
|
|
|
|
|
not be confused with the last modified time of the RRD file. |
1820
|
|
|
|
|
|
|
|
1821
|
|
|
|
|
|
|
C<$rrdfile> is optional and will default to using the RRD filename specified |
1822
|
|
|
|
|
|
|
by the C constructor method, or C<$0.rrd>. (Script basename with the file |
1823
|
|
|
|
|
|
|
extension of .rrd). |
1824
|
|
|
|
|
|
|
|
1825
|
|
|
|
|
|
|
=head2 sources |
1826
|
|
|
|
|
|
|
|
1827
|
|
|
|
|
|
|
my @sources = $rrd->sources($rrdfile); |
1828
|
|
|
|
|
|
|
|
1829
|
|
|
|
|
|
|
This method returns a list of all of the data source names contained within |
1830
|
|
|
|
|
|
|
the RRD file. |
1831
|
|
|
|
|
|
|
|
1832
|
|
|
|
|
|
|
C<$rrdfile> is optional and will default to using the RRD filename specified |
1833
|
|
|
|
|
|
|
by the C constructor method, or C<$0.rrd>. (Script basename with the file |
1834
|
|
|
|
|
|
|
extension of .rrd). |
1835
|
|
|
|
|
|
|
|
1836
|
|
|
|
|
|
|
=head2 add_source |
1837
|
|
|
|
|
|
|
|
1838
|
|
|
|
|
|
|
$rrd->add_source($rrdfile, |
1839
|
|
|
|
|
|
|
source_name => "TYPE" |
1840
|
|
|
|
|
|
|
); |
1841
|
|
|
|
|
|
|
|
1842
|
|
|
|
|
|
|
You may add a new data source to an existing RRD file using this method. Only |
1843
|
|
|
|
|
|
|
one data source name can be added at a time. You must also specify the data |
1844
|
|
|
|
|
|
|
source type. |
1845
|
|
|
|
|
|
|
|
1846
|
|
|
|
|
|
|
C<$rrdfile> is optional and will default to using the RRD filename specified |
1847
|
|
|
|
|
|
|
by the C constructor method, or C<$0.rrd>. (Script basename with the file |
1848
|
|
|
|
|
|
|
extension of .rrd). |
1849
|
|
|
|
|
|
|
|
1850
|
|
|
|
|
|
|
This method can be called internally by the C method to automatically |
1851
|
|
|
|
|
|
|
add missing data sources. |
1852
|
|
|
|
|
|
|
|
1853
|
|
|
|
|
|
|
=head2 rename_source |
1854
|
|
|
|
|
|
|
|
1855
|
|
|
|
|
|
|
$rrd->rename_source($rrdfile, "old_datasource", "new_datasource"); |
1856
|
|
|
|
|
|
|
|
1857
|
|
|
|
|
|
|
You may rename a data source in an existing RRD file using this method. |
1858
|
|
|
|
|
|
|
|
1859
|
|
|
|
|
|
|
C<$rrdfile> is optional and will default to using the RRD filename specified |
1860
|
|
|
|
|
|
|
by the C constructor method, or C<$0.rrd>. (Script basename with the file |
1861
|
|
|
|
|
|
|
extension of .rrd). |
1862
|
|
|
|
|
|
|
|
1863
|
|
|
|
|
|
|
=head2 graph |
1864
|
|
|
|
|
|
|
|
1865
|
|
|
|
|
|
|
my %rtn = $rrd->graph($rrdfile, |
1866
|
|
|
|
|
|
|
destination => "/path/to/write/graph/images", |
1867
|
|
|
|
|
|
|
basename => "graph_basename", |
1868
|
|
|
|
|
|
|
timestamp => "both", # graph, rrd, both or none |
1869
|
|
|
|
|
|
|
periods => [ qw(week month) ], # omit to generate all graphs |
1870
|
|
|
|
|
|
|
sources => [ qw(source_name1 source_name2 source_name3) ], |
1871
|
|
|
|
|
|
|
source_colors => [ qw(ff0000 aa3333 000000) ], |
1872
|
|
|
|
|
|
|
source_labels => [ ("My Source 1", "My Source Two", "Source 3") ], |
1873
|
|
|
|
|
|
|
source_drawtypes => [ qw(LINE1 AREA LINE) ], |
1874
|
|
|
|
|
|
|
line_thickness => 2, |
1875
|
|
|
|
|
|
|
extended_legend => 1, |
1876
|
|
|
|
|
|
|
rrd_graph_option => "value", |
1877
|
|
|
|
|
|
|
rrd_graph_option => "value", |
1878
|
|
|
|
|
|
|
rrd_graph_option => "value" |
1879
|
|
|
|
|
|
|
); |
1880
|
|
|
|
|
|
|
|
1881
|
|
|
|
|
|
|
This method will render one or more graph images that show the data in the |
1882
|
|
|
|
|
|
|
RRD file. |
1883
|
|
|
|
|
|
|
|
1884
|
|
|
|
|
|
|
The number of image files that are created depends on the retention period |
1885
|
|
|
|
|
|
|
of the RRD file. Hourly, 6 hourly, 12 hourly, daily, weekly, monthly, annual |
1886
|
|
|
|
|
|
|
and 3year graphs will be created if there is enough data in the RRD file to |
1887
|
|
|
|
|
|
|
accomodate them. |
1888
|
|
|
|
|
|
|
|
1889
|
|
|
|
|
|
|
The image filenames will start with either the basename of the RRD |
1890
|
|
|
|
|
|
|
file, or whatever is specified by the C parameter. The second part |
1891
|
|
|
|
|
|
|
of the filename will be "-hourly", "-6hourly", "-12hourly", "-daily", |
1892
|
|
|
|
|
|
|
"-weekly", "-monthly", "-annual" or "-3year" depending on the period that |
1893
|
|
|
|
|
|
|
is being graphed. |
1894
|
|
|
|
|
|
|
|
1895
|
|
|
|
|
|
|
C<$rrdfile> is optional and will default to using the RRD filename specified |
1896
|
|
|
|
|
|
|
by the C constructor method, or C<$0.rrd>. (Script basename with the file |
1897
|
|
|
|
|
|
|
extension of .rrd). |
1898
|
|
|
|
|
|
|
|
1899
|
|
|
|
|
|
|
Graph options specific to RRD::Simple are: |
1900
|
|
|
|
|
|
|
|
1901
|
|
|
|
|
|
|
=over 4 |
1902
|
|
|
|
|
|
|
|
1903
|
|
|
|
|
|
|
=item destination |
1904
|
|
|
|
|
|
|
|
1905
|
|
|
|
|
|
|
The C parameter is optional, and it will default to the same |
1906
|
|
|
|
|
|
|
path location as that of the RRD file specified by C<$rrdfile>. Specifying |
1907
|
|
|
|
|
|
|
this value will force the resulting graph images to be written to this path |
1908
|
|
|
|
|
|
|
location. (The specified path must be a valid directory with the sufficient |
1909
|
|
|
|
|
|
|
permissions to write the graph images). |
1910
|
|
|
|
|
|
|
|
1911
|
|
|
|
|
|
|
=item basename |
1912
|
|
|
|
|
|
|
|
1913
|
|
|
|
|
|
|
The C parameter is optional. This parameter specifies the basename |
1914
|
|
|
|
|
|
|
of the graph image files that will be created. If not specified, it will |
1915
|
|
|
|
|
|
|
default to the name of the RRD file. For example, if you specify a basename |
1916
|
|
|
|
|
|
|
name of C, the following graph image files will be created in the |
1917
|
|
|
|
|
|
|
C directory: |
1918
|
|
|
|
|
|
|
|
1919
|
|
|
|
|
|
|
mygraph-daily.png |
1920
|
|
|
|
|
|
|
mygraph-weekly.png |
1921
|
|
|
|
|
|
|
mygraph-monthly.png |
1922
|
|
|
|
|
|
|
mygraph-annual.png |
1923
|
|
|
|
|
|
|
|
1924
|
|
|
|
|
|
|
The default file format is C, but this can be explicitly specified using |
1925
|
|
|
|
|
|
|
the standard RRDs options. (See below). |
1926
|
|
|
|
|
|
|
|
1927
|
|
|
|
|
|
|
=item timestamp |
1928
|
|
|
|
|
|
|
|
1929
|
|
|
|
|
|
|
my %rtn = $rrd->graph($rrdfile, |
1930
|
|
|
|
|
|
|
timestamp => "graph", # graph, rrd, both or none |
1931
|
|
|
|
|
|
|
); |
1932
|
|
|
|
|
|
|
|
1933
|
|
|
|
|
|
|
The C parameter is optional, but will default to "graph". This |
1934
|
|
|
|
|
|
|
parameter specifies which "last updated" timestamps should be added to the |
1935
|
|
|
|
|
|
|
bottom right hand corner of the graph. |
1936
|
|
|
|
|
|
|
|
1937
|
|
|
|
|
|
|
Valid values are: "graph" - the timestamp of when the graph was last rendered |
1938
|
|
|
|
|
|
|
will be used, "rrd" - the timestamp of when the RRD file was last updated will |
1939
|
|
|
|
|
|
|
be used, "both" - both the timestamps of when the graph and RRD file were last |
1940
|
|
|
|
|
|
|
updated will be used, "none" - no timestamp will be used. |
1941
|
|
|
|
|
|
|
|
1942
|
|
|
|
|
|
|
=item periods |
1943
|
|
|
|
|
|
|
|
1944
|
|
|
|
|
|
|
The C parameter is an optional list of periods that graphs should |
1945
|
|
|
|
|
|
|
be generated for. If omitted, all possible graphs will be generated and not |
1946
|
|
|
|
|
|
|
restricted to any specific subset. See the L method for a list of |
1947
|
|
|
|
|
|
|
valid time periods. |
1948
|
|
|
|
|
|
|
|
1949
|
|
|
|
|
|
|
=item sources |
1950
|
|
|
|
|
|
|
|
1951
|
|
|
|
|
|
|
The C parameter is optional. This parameter should be an array of |
1952
|
|
|
|
|
|
|
data source names that you want to be plotted. All data sources will be |
1953
|
|
|
|
|
|
|
plotted by default. |
1954
|
|
|
|
|
|
|
|
1955
|
|
|
|
|
|
|
=item source_colors |
1956
|
|
|
|
|
|
|
|
1957
|
|
|
|
|
|
|
my %rtn = $rrd->graph($rrdfile, |
1958
|
|
|
|
|
|
|
source_colors => [ qw(ff3333 ff00ff ffcc99) ], |
1959
|
|
|
|
|
|
|
); |
1960
|
|
|
|
|
|
|
|
1961
|
|
|
|
|
|
|
%rtn = $rrd->graph($rrdfile, |
1962
|
|
|
|
|
|
|
source_colors => { source_name1 => "ff3333", |
1963
|
|
|
|
|
|
|
source_name2 => "ff00ff", |
1964
|
|
|
|
|
|
|
source_name3 => "ffcc99", }, |
1965
|
|
|
|
|
|
|
); |
1966
|
|
|
|
|
|
|
|
1967
|
|
|
|
|
|
|
The C parameter is optional. This parameter should be an |
1968
|
|
|
|
|
|
|
array or hash of hex triplet colors to be used for the plotted data source |
1969
|
|
|
|
|
|
|
lines. A selection of vivid primary colors will be set by default. |
1970
|
|
|
|
|
|
|
|
1971
|
|
|
|
|
|
|
=item source_labels |
1972
|
|
|
|
|
|
|
|
1973
|
|
|
|
|
|
|
my %rtn = $rrd->graph($rrdfile, |
1974
|
|
|
|
|
|
|
sources => [ qw(source_name1 source_name2 source_name3) ], |
1975
|
|
|
|
|
|
|
source_labels => [ ("My Source 1","My Source Two","Source 3") ], |
1976
|
|
|
|
|
|
|
); |
1977
|
|
|
|
|
|
|
|
1978
|
|
|
|
|
|
|
%rtn = $rrd->graph($rrdfile, |
1979
|
|
|
|
|
|
|
source_labels => { source_name1 => "My Source 1", |
1980
|
|
|
|
|
|
|
source_name2 => "My Source Two", |
1981
|
|
|
|
|
|
|
source_name3 => "Source 3", }, |
1982
|
|
|
|
|
|
|
); |
1983
|
|
|
|
|
|
|
|
1984
|
|
|
|
|
|
|
The C parameter is optional. The parameter should be an |
1985
|
|
|
|
|
|
|
array or hash of labels to be placed in the legend/key underneath the |
1986
|
|
|
|
|
|
|
graph. An array can only be used if the C parameter is also |
1987
|
|
|
|
|
|
|
specified, since the label index position in the array will directly |
1988
|
|
|
|
|
|
|
relate to the data source index position in the C array. |
1989
|
|
|
|
|
|
|
|
1990
|
|
|
|
|
|
|
The data source names will be used in the legend/key by default if no |
1991
|
|
|
|
|
|
|
C parameter is specified. |
1992
|
|
|
|
|
|
|
|
1993
|
|
|
|
|
|
|
=item source_drawtypes |
1994
|
|
|
|
|
|
|
|
1995
|
|
|
|
|
|
|
my %rtn = $rrd->graph($rrdfile, |
1996
|
|
|
|
|
|
|
source_drawtypes => [ qw(LINE1 AREA LINE) ], |
1997
|
|
|
|
|
|
|
); |
1998
|
|
|
|
|
|
|
|
1999
|
|
|
|
|
|
|
%rtn = $rrd->graph($rrdfile, |
2000
|
|
|
|
|
|
|
source_colors => { source_name1 => "LINE1", |
2001
|
|
|
|
|
|
|
source_name2 => "AREA", |
2002
|
|
|
|
|
|
|
source_name3 => "LINE", }, |
2003
|
|
|
|
|
|
|
); |
2004
|
|
|
|
|
|
|
|
2005
|
|
|
|
|
|
|
%rtn = $rrd->graph($rrdfile, |
2006
|
|
|
|
|
|
|
sources => [ qw(system user iowait idle) ] |
2007
|
|
|
|
|
|
|
source_colors => [ qw(AREA STACK STACK STACK) ], |
2008
|
|
|
|
|
|
|
); |
2009
|
|
|
|
|
|
|
|
2010
|
|
|
|
|
|
|
The C parameter is optional. This parameter should be an |
2011
|
|
|
|
|
|
|
array or hash of drawing/plotting types to be used for the plotted data source |
2012
|
|
|
|
|
|
|
lines. By default all data sources are drawn as lines (LINE), but data sources |
2013
|
|
|
|
|
|
|
may also be drawn as filled areas (AREA). Valid values are, LINE, LINEI |
2014
|
|
|
|
|
|
|
(where I represents the thickness of the line in pixels), AREA or STACK. |
2015
|
|
|
|
|
|
|
|
2016
|
|
|
|
|
|
|
=item line_thickness |
2017
|
|
|
|
|
|
|
|
2018
|
|
|
|
|
|
|
Specifies the thickness of the data lines drawn on the graphs for |
2019
|
|
|
|
|
|
|
any data sources that have not had a specific line thickness already |
2020
|
|
|
|
|
|
|
specified using the C option. |
2021
|
|
|
|
|
|
|
Valid values are 1, 2 and 3 (pixels). |
2022
|
|
|
|
|
|
|
|
2023
|
|
|
|
|
|
|
=item extended_legend |
2024
|
|
|
|
|
|
|
|
2025
|
|
|
|
|
|
|
If set to boolean true, prints more detailed information in the graph legend |
2026
|
|
|
|
|
|
|
by adding the minimum, maximum and last values recorded on the graph for each |
2027
|
|
|
|
|
|
|
data source. |
2028
|
|
|
|
|
|
|
|
2029
|
|
|
|
|
|
|
=back |
2030
|
|
|
|
|
|
|
|
2031
|
|
|
|
|
|
|
Common RRD graph options are: |
2032
|
|
|
|
|
|
|
|
2033
|
|
|
|
|
|
|
=over 4 |
2034
|
|
|
|
|
|
|
|
2035
|
|
|
|
|
|
|
=item title |
2036
|
|
|
|
|
|
|
|
2037
|
|
|
|
|
|
|
A horizontal string at the top of the graph. |
2038
|
|
|
|
|
|
|
|
2039
|
|
|
|
|
|
|
=item vertical_label |
2040
|
|
|
|
|
|
|
|
2041
|
|
|
|
|
|
|
A vertically placed string at the left hand side of the graph. |
2042
|
|
|
|
|
|
|
|
2043
|
|
|
|
|
|
|
=item width |
2044
|
|
|
|
|
|
|
|
2045
|
|
|
|
|
|
|
The width of the canvas (the part of the graph with the actual data |
2046
|
|
|
|
|
|
|
and such). This defaults to 400 pixels. |
2047
|
|
|
|
|
|
|
|
2048
|
|
|
|
|
|
|
=item height |
2049
|
|
|
|
|
|
|
|
2050
|
|
|
|
|
|
|
The height of the canvas (the part of the graph with the actual data |
2051
|
|
|
|
|
|
|
and such). This defaults to 100 pixels. |
2052
|
|
|
|
|
|
|
|
2053
|
|
|
|
|
|
|
=back |
2054
|
|
|
|
|
|
|
|
2055
|
|
|
|
|
|
|
For examples on how to best use the C method, refer to the example |
2056
|
|
|
|
|
|
|
scripts that are bundled with this module in the examples/ directory. A |
2057
|
|
|
|
|
|
|
complete list of parameters can be found at |
2058
|
|
|
|
|
|
|
L. |
2059
|
|
|
|
|
|
|
|
2060
|
|
|
|
|
|
|
=head2 retention_period |
2061
|
|
|
|
|
|
|
|
2062
|
|
|
|
|
|
|
my $seconds = $rrd->retention_period($rrdfile); |
2063
|
|
|
|
|
|
|
|
2064
|
|
|
|
|
|
|
This method will return the maximum period of time (in seconds) that the RRD |
2065
|
|
|
|
|
|
|
file will store data for. |
2066
|
|
|
|
|
|
|
|
2067
|
|
|
|
|
|
|
C<$rrdfile> is optional and will default to using the RRD filename specified |
2068
|
|
|
|
|
|
|
by the C constructor method, or C<$0.rrd>. (Script basename with the file |
2069
|
|
|
|
|
|
|
extension of .rrd). |
2070
|
|
|
|
|
|
|
|
2071
|
|
|
|
|
|
|
=head2 info |
2072
|
|
|
|
|
|
|
|
2073
|
|
|
|
|
|
|
my $info = $rrd->info($rrdfile); |
2074
|
|
|
|
|
|
|
|
2075
|
|
|
|
|
|
|
This method will return a complex data structure containing details about |
2076
|
|
|
|
|
|
|
the RRD file, including RRA and data source information. |
2077
|
|
|
|
|
|
|
|
2078
|
|
|
|
|
|
|
C<$rrdfile> is optional and will default to using the RRD filename specified |
2079
|
|
|
|
|
|
|
by the C constructor method, or C<$0.rrd>. (Script basename with the file |
2080
|
|
|
|
|
|
|
extension of .rrd). |
2081
|
|
|
|
|
|
|
|
2082
|
|
|
|
|
|
|
=head2 heartbeat |
2083
|
|
|
|
|
|
|
|
2084
|
|
|
|
|
|
|
my $heartbeat = $rrd->heartbeat($rrdfile, "dsname"); |
2085
|
|
|
|
|
|
|
my @rtn = $rrd->heartbeat($rrdfile, "dsname", 600); |
2086
|
|
|
|
|
|
|
|
2087
|
|
|
|
|
|
|
This method will return the current heartbeat of a data source, or set a |
2088
|
|
|
|
|
|
|
new heartbeat of a data source. |
2089
|
|
|
|
|
|
|
|
2090
|
|
|
|
|
|
|
C<$rrdfile> is optional and will default to using the RRD filename specified |
2091
|
|
|
|
|
|
|
by the C constructor method, or C<$0.rrd>. (Script basename with the file |
2092
|
|
|
|
|
|
|
extension of .rrd). |
2093
|
|
|
|
|
|
|
|
2094
|
|
|
|
|
|
|
=head1 VARIABLES |
2095
|
|
|
|
|
|
|
|
2096
|
|
|
|
|
|
|
=head2 $RRD::Simple::DEBUG |
2097
|
|
|
|
|
|
|
|
2098
|
|
|
|
|
|
|
Debug and trace information will be printed to STDERR if this variable |
2099
|
|
|
|
|
|
|
is set to 1 (boolean true). |
2100
|
|
|
|
|
|
|
|
2101
|
|
|
|
|
|
|
This variable will take its value from C<$ENV{DEBUG}>, if it exists, |
2102
|
|
|
|
|
|
|
otherwise it will default to 0 (boolean false). This is a normal package |
2103
|
|
|
|
|
|
|
variable and may be safely modified at any time. |
2104
|
|
|
|
|
|
|
|
2105
|
|
|
|
|
|
|
=head2 $RRD::Simple::DEFAULT_DSTYPE |
2106
|
|
|
|
|
|
|
|
2107
|
|
|
|
|
|
|
This variable is used as the default data source type when creating or |
2108
|
|
|
|
|
|
|
adding new data sources, when no other data source type is explicitly |
2109
|
|
|
|
|
|
|
specified. |
2110
|
|
|
|
|
|
|
|
2111
|
|
|
|
|
|
|
This variable will take its value from C<$ENV{DEFAULT_DSTYPE}>, if it |
2112
|
|
|
|
|
|
|
exists, otherwise it will default to C. This is a normal package |
2113
|
|
|
|
|
|
|
variable and may be safely modified at any time. |
2114
|
|
|
|
|
|
|
|
2115
|
|
|
|
|
|
|
=head1 EXPORTS |
2116
|
|
|
|
|
|
|
|
2117
|
|
|
|
|
|
|
You can export the following functions if you do not wish to go through |
2118
|
|
|
|
|
|
|
the extra effort of using the OO interface: |
2119
|
|
|
|
|
|
|
|
2120
|
|
|
|
|
|
|
create |
2121
|
|
|
|
|
|
|
update |
2122
|
|
|
|
|
|
|
last_update (synonym for the last() method) |
2123
|
|
|
|
|
|
|
sources |
2124
|
|
|
|
|
|
|
add_source |
2125
|
|
|
|
|
|
|
rename_source |
2126
|
|
|
|
|
|
|
graph |
2127
|
|
|
|
|
|
|
retention_period |
2128
|
|
|
|
|
|
|
info |
2129
|
|
|
|
|
|
|
heartbeat |
2130
|
|
|
|
|
|
|
|
2131
|
|
|
|
|
|
|
The tag C is available to easily export everything: |
2132
|
|
|
|
|
|
|
|
2133
|
|
|
|
|
|
|
use RRD::Simple qw(:all); |
2134
|
|
|
|
|
|
|
|
2135
|
|
|
|
|
|
|
See the examples and unit tests in this distribution for more |
2136
|
|
|
|
|
|
|
details. |
2137
|
|
|
|
|
|
|
|
2138
|
|
|
|
|
|
|
=head1 SEE ALSO |
2139
|
|
|
|
|
|
|
|
2140
|
|
|
|
|
|
|
L, L, L, |
2141
|
|
|
|
|
|
|
L, examples/*.pl, |
2142
|
|
|
|
|
|
|
L, |
2143
|
|
|
|
|
|
|
L |
2144
|
|
|
|
|
|
|
|
2145
|
|
|
|
|
|
|
=head1 VERSION |
2146
|
|
|
|
|
|
|
|
2147
|
|
|
|
|
|
|
$Id: Simple.pm 1100 2008-01-24 17:39:35Z nicolaw $ |
2148
|
|
|
|
|
|
|
|
2149
|
|
|
|
|
|
|
=head1 AUTHOR |
2150
|
|
|
|
|
|
|
|
2151
|
|
|
|
|
|
|
Nicola Worthington |
2152
|
|
|
|
|
|
|
|
2153
|
|
|
|
|
|
|
L |
2154
|
|
|
|
|
|
|
|
2155
|
|
|
|
|
|
|
If you like this software, why not show your appreciation by sending the |
2156
|
|
|
|
|
|
|
author something nice from her |
2157
|
|
|
|
|
|
|
L? |
2158
|
|
|
|
|
|
|
( http://www.amazon.co.uk/gp/registry/1VZXC59ESWYK0?sort=priority ) |
2159
|
|
|
|
|
|
|
|
2160
|
|
|
|
|
|
|
=head1 COPYRIGHT |
2161
|
|
|
|
|
|
|
|
2162
|
|
|
|
|
|
|
Copyright 2005,2006,2007,2008 Nicola Worthington. |
2163
|
|
|
|
|
|
|
|
2164
|
|
|
|
|
|
|
This software is licensed under The Apache Software License, Version 2.0. |
2165
|
|
|
|
|
|
|
|
2166
|
|
|
|
|
|
|
L |
2167
|
|
|
|
|
|
|
|
2168
|
|
|
|
|
|
|
=cut |
2169
|
|
|
|
|
|
|
|
2170
|
|
|
|
|
|
|
|
2171
|
|
|
|
|
|
|
__END__ |