1 | 1 | simandl | # |
2 | | | # $Id: HotSaNICdiagram.pm,v 1.29 2004/09/19 10:07:19 bernisys Exp $ |
3 | | | # |
4 | | | |
5 | | | package HotSaNICdiagram; |
6 | | | |
7 | | | use RRDs; |
8 | | | |
9 | | | ($VERSION = '$Revision: 1.29 $') =~ s/.*(\d+\.\d+).*/$1/; |
10 | | | |
11 | | | my @Weekday=("Sun","Mon","Tue","Wed","Thu","Fri","Sat"); |
12 | | | |
13 | | | ###################################################################### |
14 | | | # |
15 | | | # evaluates some diagram properties from the given range |
16 | | | # |
17 | | | # Usage: |
18 | | | # ($description,$file_description,$build_interval,$diagram_range,$timestring)=get_diagram_properties($range); |
19 | | | # |
20 | | | sub get_diagram_properties { |
21 | | | my ($range)=@_; |
22 | | | my ($descr,$file,$build,$fullrange); |
23 | | | if ($range eq "1h") { $descr="hour"; $file=$descr; $build=0; $fullrange=3600; } |
24 | | | elsif ($range eq "6h") { $descr="6 hours"; $file="6h"; $build=30; $fullrange=21600; } |
25 | | | elsif ($range eq "1day") { $descr="day"; $file=$descr; $build=120; $fullrange=86400; } |
26 | | | elsif ($range eq "1week") { $descr="week"; $file=$descr; $build=1000; $fullrange=604800; } |
27 | | | elsif ($range eq "1month") { $descr="month"; $file=$descr; $build=4300; $fullrange=2592000; } |
28 | | | elsif ($range eq "1year") { $descr="year"; $file=$descr; $build=52500; $fullrange=31536000; } |
29 | | | |
30 | | | ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); |
31 | | | $year+=1900; |
32 | | | $mon++; |
33 | | | if ($mon<10) { $mon="0".$mon; } |
34 | | | if ($mday<10) { $mday="0".$mday; } |
35 | | | if ($hour<10) { $hour="0".$hour; } |
36 | | | if ($min<10) { $min="0".$min; } |
37 | | | $buildtime="$Weekday[$wday] $year-$mon-$mday $hour:$min"; |
38 | | | |
39 | | | return ($descr,$file,$build,$fullrange,$buildtime); |
40 | | | } |
41 | | | |
42 | | | ###################################################################### |
43 | | | # |
44 | | | # Creates a diagram from the options passed within a config hash |
45 | | | # |
46 | | | # Usage: |
47 | | | # graph(%CONFIGHASH); |
48 | | | # |
49 | | | # Hints: |
50 | | | # The hash has to contain the following items: |
51 | | | # |
52 | | | # %CONFIGHASH=( FILENAME => "full path to output file without suffix" |
53 | | | # FORMAT => "gif" # ("png" or "gif") |
54 | | | # INTERVAL => <seconds between neccessary update> |
55 | | | # FORCE => 0 for automatic checking, >0 forces plot |
56 | | | # RANGE => plot range of diagram (1year 6h 1h ...) |
57 | | | # DEBUGLEVEL => everything >1 plots complete optiin array |
58 | | | # OPTIONS => array of options that will be passed tpo RRDs |
59 | | | # assign it like this: => [(@a1,@a2,...)] |
60 | | | # |
61 | | | sub graph { |
62 | | | my %CONF=@_; |
63 | | | |
64 | | | my $FILE=$CONF{FILENAME}.".".$CONF{FORMAT}; |
65 | | | my $TEMPFILE=$CONF{FILENAME}."-temp.".$CONF{FORMAT}; |
66 | | | |
67 | | | my @OPTIONS=@{$CONF{OPTIONS}}; |
68 | | | |
69 | | | if (! -e $FILE) { $CONF{FORCE}++; } |
70 | | | else { $CONF{FORCE}++ if time >= (stat($FILE))[9]+$CONF{INTERVAL}; } |
71 | | | |
72 | | | if ($CONF{FORCE} > 0) { |
73 | | | if ($CONF{DEBUGLEVEL} > 1) { print join("\n",$TEMPFILE,@OPTIONS),"\n"; } |
74 | | | my ($prints,$xs,$ys)=RRDs::graph $TEMPFILE,@OPTIONS; |
75 | | | if ($ERROR = RRDs::error) { print time," ",$CONF{MODNAME},": $ERROR\n"; } |
76 | | | else { |
77 | | | rename $TEMPFILE,$FILE; |
78 | | | printf " %9s %4d x %4d %s\n", $CONF{RANGE},$xs,$ys,$FILE; |
79 | | | } |
80 | | | } |
81 | | | } |
82 | | | |
83 | | | sub check_args { |
84 | | | my $OPT=0; |
85 | | | $FORCE=0; |
86 | | | $DEBUGLEVEL=-1; |
87 | | | %PLOT=(); |
88 | | | foreach (@_) { |
89 | | | $FORCE=1 if $_ eq "a"; |
90 | | | $DEBUGLEVEL=100 if $_ eq "d"; |
91 | | | if ($OPT==1) { $PLOT{$_}=1; $OPT=0; } |
92 | | | $OPT=1 if $_ eq "g"; |
93 | | | if ( ($_ eq "?") or ($_ eq "h") ) { |
94 | | | print "options:\n |
95 | | | g <name> only graph item <name> |
96 | | | (can be used multiple times, default: plot all)\n |
97 | | | a plot all time-graphs of selected items |
98 | | | (default: plot only expired graphs)\n |
99 | | | d enable debug mode (prints all graph options - |
100 | | | much output!)\n |
101 | | | ? or h display this help-text |
102 | | | \n"; |
103 | | | exit; |
104 | | | } |
105 | | | } |
106 | | | return ($FORCE,$DEBUGLEVEL,%PLOT); |
107 | | | } |
108 | | | |
109 | | | ######################################## |
110 | | | # get_common_options calculates an array of common module-specific and |
111 | | | # global diagram options to be passed to RRDs::graph. it accepts a hash |
112 | | | # containing both modulespecific and global settings. |
113 | | | # |
114 | | | sub get_common_options { |
115 | | | my %CONFIG=@_; |
116 | | | my @OPTIONS=(); |
117 | | | if ($CONFIG{GRAPH_RIGID} == 1 ) { push @OPTIONS,"-r"; } |
118 | | | if ($CONFIG{GRAPH_FORCE_LEGEND} == 1 ) { push @OPTIONS,"-F"; } |
119 | | | if ($CONFIG{GRAPH_STYLE} eq "log" ) { |
120 | | | push @OPTIONS,"-o"; |
121 | | | $CONFIG{GRAPH_MIN}=$CONFIG{GRAPH_MIN_LOG}; |
122 | | | } |
123 | | | if (defined $CONFIG{GRAPH_BASE}) { |
124 | | | if ( ($CONFIG{GRAPH_BASE} eq "percent") or ($CONFIG{GRAPH_BASE} eq "%") ) { |
125 | | | if (defined $CONFIG{GRAPH_MAX}) { $CONFIG{GRAPH_MAX}*=100; } |
126 | | | if (defined $CONFIG{GRAPH_MIN}) { $CONFIG{GRAPH_MIN}*=100; } |
127 | | | } |
128 | | | if ($CONFIG{GRAPH_BASE} eq "bits") { |
129 | | | if (defined $CONFIG{GRAPH_MAX}) { $CONFIG{GRAPH_MAX}*=8; } |
130 | | | if (defined $CONFIG{GRAPH_MIN}) { $CONFIG{GRAPH_MIN}*=8; } |
131 | | | } |
132 | | | elsif ($CONFIG{GRAPH_BASE} eq "ms") { |
133 | | | if (defined $CONFIG{GRAPH_MAX}) { $CONFIG{GRAPH_MAX}/=1000; } |
134 | | | if (defined $CONFIG{GRAPH_MIN}) { $CONFIG{GRAPH_MIN}/=1000; } |
135 | | | } |
136 | | | elsif ($CONFIG{GRAPH_BASE} eq "us") { |
137 | | | if (defined $CONFIG{GRAPH_MAX}) { $CONFIG{GRAPH_MAX}/=1000000; } |
138 | | | if (defined $CONFIG{GRAPH_MIN}) { $CONFIG{GRAPH_MIN}/=1000000; } |
139 | | | } |
140 | | | } |
141 | | | |
142 | | | push @OPTIONS,( |
143 | | | "-i", # graphic interlaced mode |
144 | | | "-w",$CONFIG{WIDTH}, # drawing area width |
145 | | | "-h",$CONFIG{HEIGHT}, # drawing area height |
146 | | | "-b",$CONFIG{GRAPH_UNIT}, # value of "1k" (1000 or 1024) |
147 | | | "-u",$CONFIG{GRAPH_MAX}, # upper initial border |
148 | | | "-l",$CONFIG{GRAPH_MIN}, # lower initial border |
149 | | | "-a",uc($CONFIG{IMAGEFORMAT}) # image format |
150 | | | ); |
151 | | | return @OPTIONS; |
152 | | | } |
153 | | | |
154 | | | |
155 | | | ###################################################################### |
156 | | | # |
157 | | | # generates an array suitable to feed to RRDs::graph to build a graph |
158 | | | # including a legend with the desired consolidation functions. |
159 | | | # |
160 | | | # Usage: |
161 | | | # insert_data(type,var,color,description,legends,unit,use_SI); |
162 | | | # |
163 | | | # type: AREA LINE1 LINE2 LINE3 STACK |
164 | | | # |
165 | | | # var: RRD variable to be graphed and variables which will |
166 | | | # be assigned to min, average, max and now. They |
167 | | | # have to be separated by spaces. If less than five |
168 | | | # variables are stated, the LAST entry will be copied |
169 | | | # to the remaining positions. |
170 | | | # If you just want to insert a legend without adding a |
171 | | | # graph, the first element must be a numerical zero "0" |
172 | | | # The "description" can be used for padding the legend. |
173 | | | # |
174 | | | # color: graph's color in RRDs::graph notation ( RRGGBB ) |
175 | | | # |
176 | | | # description: name of the graph or short description that will be |
177 | | | # used in the legend |
178 | | | # |
179 | | | # legends: one or more space-separated occurances of: |
180 | | | # min avg max cur now ("now" is the same as "cur") |
181 | | | # |
182 | | | # unit: the unit that will be appended after the last legend |
183 | | | # |
184 | | | # use_SI: 0 -> just print the plain value |
185 | | | # 1 -> print values in SI units (micro, milli, kilo, mega, ...) |
186 | | | # -1 -> same as "1", but SI-units will be added without a space |
187 | | | # |
188 | | | # EXAMPLE: |
189 | | | # |
190 | | | # insert_data("AREA","abc xyz","#0000ff","test legend","min max","bytes",1); |
191 | | | # |
192 | | | # -> draw a blue area with the rrd variable "abc" and add a corrosponding |
193 | | | # legend to it, but refer the legend to the variable "xyz". The legend |
194 | | | # will use SI-units and the unit "bytes". It will look like this: |
195 | | | # |
196 | | | # [#] test legend (min: 1.23k max: 12.34M bytes) |
197 | | | # |
198 | | | sub insert_data { |
199 | | | my $graphtype=shift || "LINE1"; |
200 | | | my $rrdvar=shift || ""; |
201 | | | my $color=shift || "000000"; |
202 | | | my $description=shift || ""; |
203 | | | my $legends=shift || "min avg max"; |
204 | | | my $unit=shift || ""; |
205 | | | my $use_SI_units=shift || 0; |
206 | | | |
207 | | | my @legends=split / /,$legends; |
208 | | | |
209 | | | my @vars=split / /,$rrdvar; |
210 | | | while ($#vars<4) { push @vars,$vars[$#vars]; } |
211 | | | |
212 | | | my $lead=""; |
213 | | | my @array=(); |
214 | | | if ($vars[0] ne "0") { push @array,"$graphtype:$vars[0]#$color:$description"; $description=""; } |
215 | | | else { $lead=" "; } |
216 | | | |
217 | | | $SI=""; |
218 | | | if ($use_SI_units == 1) { $SI=" %s"; } |
219 | | | elsif ($use_SI_units == -1) { $SI="%s"; } |
220 | | | |
221 | | | if ($unit ne "") { $unit=" $unit"; } |
222 | | | |
223 | | | my $num=0; |
224 | | | foreach $legend (@legends) { |
225 | | | my $open=""; |
226 | | | my $close=""; |
227 | | | if ($num == 0) { $open="$lead$description$lead("; } |
228 | | | if ($num == $#legends) { $close="$unit)\\n"; } |
229 | | | $num++; |
230 | | | if ($legend eq "min") { push @array,"GPRINT:$vars[1]:MIN:$open"."min\\:%7.2lf$SI$close"; } |
231 | | | elsif ($legend eq "avg") { push @array,"GPRINT:$vars[2]:AVERAGE:$open"."avg\\:%7.2lf$SI$close"; } |
232 | | | elsif ($legend eq "max") { push @array,"GPRINT:$vars[3]:MAX:$open"."max\\:%7.2lf$SI$close"; } |
233 | | | elsif ($legend eq "now") { push @array,"GPRINT:$vars[4]:LAST:$open"."now\\:%7.2lf$SI$close"; } |
234 | | | elsif ($legend eq "cur") { push @array,"GPRINT:$vars[4]:LAST:$open"."now\\:%7.2lf$SI$close"; } |
235 | | | $description=""; |
236 | | | $lead=""; |
237 | | | } |
238 | | | |
239 | | | return @array; |
240 | | | } |
241 | | | |
242 | | | ###################################################################### |
243 | | | # |
244 | | | # generates an array suitable to feed to RRDs::graph to build a min/max |
245 | | | # graph including a legend with the desired consolidation functions. |
246 | | | # |
247 | | | # Usage: |
248 | | | # insert_minmax(var,areacolor,bordercolor,description,unit,use_SI); |
249 | | | # |
250 | | | # var: RRD variables to be graphed and assigned to min and max. |
251 | | | # They have to be separated by spaces. |
252 | | | # If you just want to insert a legend without adding a |
253 | | | # graph, the first element must be a numerical zero "0" |
254 | | | # The "description" can be used for padding the legend. |
255 | | | # |
256 | | | # areacolor: graph area color in RRDs::graph notation ( #RRGGBB ) |
257 | | | # |
258 | | | # bordercolor: border color in RRDs::graph notation ( #RRGGBB ) |
259 | | | # |
260 | | | # description: short description that will be appended in the legend |
261 | | | # |
262 | | | # unit: the unit that will be appended after the last legend |
263 | | | # |
264 | | | # use_SI: 0 -> just print the plain value |
265 | | | # 1 -> print values in SI units (micro, milli, kilo, mega, ...) |
266 | | | # -1 -> same as "1", but SI-units will be added without a space |
267 | | | # |
268 | | | # EXAMPLE: |
269 | | | # |
270 | | | # insert_data("AREA","abc xyz","0000ff","test legend","min max","bytes",1); |
271 | | | # |
272 | | | # -> draw a blue area with the rrd variable "abc" and add a corrosponding |
273 | | | # legend to it, but refer the legend to the variable "xyz". The legend |
274 | | | # will use SI-units and the unit "bytes". It will look like this: |
275 | | | # |
276 | | | # [#] test legend (min: 1.23k max: 12.34M bytes) |
277 | | | # |
278 | | | sub insert_minmax { |
279 | | | my $rrdvar=shift || ""; |
280 | | | my $areacolor=shift || "000000"; |
281 | | | my $bordercolor=shift || ""; |
282 | | | my $description=shift || ""; |
283 | | | my $unit=shift || ""; |
284 | | | my $use_SI_units=shift || 0; |
285 | | | |
286 | | | my @vars=split / /,$rrdvar; |
287 | | | |
288 | | | my $lead=""; |
289 | | | my @array=(); |
290 | | | if ($vars[0] ne "0") { |
291 | | | push @array,( |
292 | | | "CDEF:negmin$vars[0]=$vars[0],0,LT,$vars[0],0,IF", |
293 | | | "CDEF:negmax$vars[1]=$vars[1],0,LT,$vars[1],0,IF", |
294 | | | "AREA:$vars[1]#$areacolor:$description", |
295 | | | "AREA:$vars[0]#FFFFFF:", |
296 | | | "AREA:negmin$vars[0]#$areacolor:", |
297 | | | "AREA:negmax$vars[1]#FFFFFF:" |
298 | | | ); |
299 | | | if ($bordercolor ne "") { |
300 | | | push @array,( |
301 | | | "LINE1:$vars[0]#$bordercolor:", |
302 | | | "LINE1:$vars[1]#$bordercolor:" |
303 | | | ); |
304 | | | } |
305 | | | $description=""; |
306 | | | } |
307 | | | else { $lead=" "; } |
308 | | | |
309 | | | if (! defined $vars[2]) { push @vars,$vars[0]; } |
310 | | | if (! defined $vars[3]) { push @vars,$vars[1]; } |
311 | | | |
312 | | | $SI=""; |
313 | | | if ($use_SI_units == 1) { $SI=" %s"; } |
314 | | | elsif ($use_SI_units == -1) { $SI="%s"; } |
315 | | | |
316 | | | if ($unit ne "") { $unit=" $unit"; } |
317 | | | |
318 | | | push @array,( |
319 | | | "GPRINT:$vars[2]:MIN:$lead$description$lead(min\\:%7.2lf$SI", |
320 | | | "GPRINT:$vars[2]:MAX:$lead$description$lead/%7.2lf$SI", |
321 | | | "GPRINT:$vars[3]:MIN:max\\:%7.2lf$SI", |
322 | | | "GPRINT:$vars[3]:MAX:/%7.2lf$SI$unit)\\n" |
323 | | | ); |
324 | | | |
325 | | | return @array; |
326 | | | } |
327 | | | |
328 | | | |
329 | | | ###################################################################### |
330 | | | # |
331 | | | # generates an array suitable to feed to RRDs::graph to mark |
332 | | | # areas of unknown values |
333 | | | # |
334 | | | # Usage: |
335 | | | # insert_unknown_area($vars,$color,$description); |
336 | | | # |
337 | | | # vars: space separated list of all variables to be taken |
338 | | | # into account. If one of them is unknown, the area |
339 | | | # will be marked |
340 | | | # |
341 | | | # color: graph's color in RRDs::graph notation ( RRGGBB ) |
342 | | | # |
343 | | | # description: some additional legend that will (if defined) show |
344 | | | # up in brackets after the string "data unknown". |
345 | | | # |
346 | | | sub insert_unknown_area { |
347 | | | my $vars=shift; |
348 | | | my $color=shift || "ffffa0"; |
349 | | | my $description=shift || ""; |
350 | | | |
351 | | | if ($description ne "") { $description=" ($description)"; } |
352 | | | |
353 | | | # the scaring ;) definition of "wrongdata": |
354 | | | # if TIME < now { if x unknown { INF else 0 } else 0 } |
355 | | | # CDEF:wrongdata=TIME,$DATE,LT,x,UN,INF,0,IF,0,IF \ |
356 | | | # where "x" is the result of the addition of all variables |
357 | | | |
358 | | | my @vars=split /\s+/,$vars; |
359 | | | my $string=shift @vars; |
360 | | | if (@vars) { $string = $string.",".join (",+,",@vars).",+"; } |
361 | | | |
362 | | | return ( |
363 | | | "CDEF:wrongdata=TIME,".time.",LT,$string,UN,INF,0,IF,0,IF", |
364 | | | "AREA:wrongdata#$color:data unknown$description\\n", |
365 | | | "CDEF:wrongdatainv=0,wrongdata,-", |
366 | | | "AREA:wrongdatainv#$color:" |
367 | | | ); |
368 | | | } |
369 | | | |
370 | | | |
371 | | | sub insert_lines { |
372 | | | my %CONFIG=@_; |
373 | | | my @array=("HRULE:0#000000"); |
374 | | | if (defined $CONFIG{GRAPH_ADDLINE}) { |
375 | | | foreach (@{$CONFIG{GRAPH_ADDLINE}}) { |
376 | | | my ($pos,$color,$comment)=split /,/; |
377 | | | if (defined $CONFIG{$color}) { $color=$CONFIG{$color}; } |
378 | | | push @array,"HRULE:$pos#$color:$comment"; |
379 | | | } |
380 | | | } |
381 | | | return @array |
382 | | | } |
383 | | | |
384 | | | sub insert_vars { |
385 | | | my $dbname=shift; |
386 | | | my $dbvar=shift; |
387 | | | my $graphvar=shift; |
388 | | | my $which=shift; |
389 | | | my @array=(); |
390 | | | foreach my $consolidation (split /\s+/,$which) { |
391 | | | my $consname=lc $consolidation; |
392 | | | if ($consname eq "average") { $consname="avg"; } |
393 | | | push @array,"DEF:$graphvar"."_"."$consname=$dbname:$dbvar:$consolidation" |
394 | | | } |
395 | | | return @array; |
396 | | | } |
397 | | | |
398 | | | 1; |
399 | | | |