Code

5c10b1008aa6e55042af6db52b9f432c18778e97
[pkg-rrdtool.git] / doc / rrd-beginners.1
1 .\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.14
2 .\"
3 .\" Standard preamble:
4 .\" ========================================================================
5 .de Sh \" Subsection heading
6 .br
7 .if t .Sp
8 .ne 5
9 .PP
10 \fB\\$1\fR
11 .PP
12 ..
13 .de Sp \" Vertical space (when we can't use .PP)
14 .if t .sp .5v
15 .if n .sp
16 ..
17 .de Vb \" Begin verbatim text
18 .ft CW
19 .nf
20 .ne \\$1
21 ..
22 .de Ve \" End verbatim text
23 .ft R
24 .fi
25 ..
26 .\" Set up some character translations and predefined strings.  \*(-- will
27 .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
28 .\" double quote, and \*(R" will give a right double quote.  | will give a
29 .\" real vertical bar.  \*(C+ will give a nicer C++.  Capital omega is used to
30 .\" do unbreakable dashes and therefore won't be available.  \*(C` and \*(C'
31 .\" expand to `' in nroff, nothing in troff, for use with C<>.
32 .tr \(*W-|\(bv\*(Tr
33 .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
34 .ie n \{\
35 .    ds -- \(*W-
36 .    ds PI pi
37 .    if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
38 .    if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\"  diablo 12 pitch
39 .    ds L" ""
40 .    ds R" ""
41 .    ds C` ""
42 .    ds C' ""
43 'br\}
44 .el\{\
45 .    ds -- \|\(em\|
46 .    ds PI \(*p
47 .    ds L" ``
48 .    ds R" ''
49 'br\}
50 .\"
51 .\" If the F register is turned on, we'll generate index entries on stderr for
52 .\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
53 .\" entries marked with X<> in POD.  Of course, you'll have to process the
54 .\" output yourself in some meaningful fashion.
55 .if \nF \{\
56 .    de IX
57 .    tm Index:\\$1\t\\n%\t"\\$2"
58 ..
59 .    nr % 0
60 .    rr F
61 .\}
62 .\"
63 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
64 .\" way too many mistakes in technical documents.
65 .hy 0
66 .if n .na
67 .\"
68 .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
69 .\" Fear.  Run.  Save yourself.  No user-serviceable parts.
70 .    \" fudge factors for nroff and troff
71 .if n \{\
72 .    ds #H 0
73 .    ds #V .8m
74 .    ds #F .3m
75 .    ds #[ \f1
76 .    ds #] \fP
77 .\}
78 .if t \{\
79 .    ds #H ((1u-(\\\\n(.fu%2u))*.13m)
80 .    ds #V .6m
81 .    ds #F 0
82 .    ds #[ \&
83 .    ds #] \&
84 .\}
85 .    \" simple accents for nroff and troff
86 .if n \{\
87 .    ds ' \&
88 .    ds ` \&
89 .    ds ^ \&
90 .    ds , \&
91 .    ds ~ ~
92 .    ds /
93 .\}
94 .if t \{\
95 .    ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
96 .    ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
97 .    ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
98 .    ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
99 .    ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
100 .    ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
101 .\}
102 .    \" troff and (daisy-wheel) nroff accents
103 .ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
104 .ds 8 \h'\*(#H'\(*b\h'-\*(#H'
105 .ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
106 .ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
107 .ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
108 .ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
109 .ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
110 .ds ae a\h'-(\w'a'u*4/10)'e
111 .ds Ae A\h'-(\w'A'u*4/10)'E
112 .    \" corrections for vroff
113 .if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
114 .if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
115 .    \" for low resolution devices (crt and lpr)
116 .if \n(.H>23 .if \n(.V>19 \
117 \{\
118 .    ds : e
119 .    ds 8 ss
120 .    ds o a
121 .    ds d- d\h'-1'\(ga
122 .    ds D- D\h'-1'\(hy
123 .    ds th \o'bp'
124 .    ds Th \o'LP'
125 .    ds ae ae
126 .    ds Ae AE
127 .\}
128 .rm #[ #] #H #V #F C
129 .\" ========================================================================
130 .\"
131 .IX Title "RRD-BEGINNERS 1"
132 .TH RRD-BEGINNERS 1 "2008-03-15" "1.3.99909060808" "rrdtool"
133 .SH "NAME"
134 rrd\-beginners \- RRDtool Beginners' Guide
135 .SH "SYNOPSIS"
136 .IX Header "SYNOPSIS"
137 Helping new RRDtool users to understand the basics of RRDtool
138 .SH "DESCRIPTION"
139 .IX Header "DESCRIPTION"
140 This manual is an attempt to assist beginners in understanding the concepts
141 of RRDtool. It sheds a light on differences between RRDtool and other
142 databases. With help of an example, it explains the structure of RRDtool
143 database. This is followed by an overview of the \*(L"graph\*(R" feature of RRDtool.
144 At the end, it has sample scripts that illustrate the
145 usage/wrapping of RRDtool within Shell or Perl scripts.
146 .Sh "What makes RRDtool so special?"
147 .IX Subsection "What makes RRDtool so special?"
148 RRDtool is \s-1GNU\s0 licensed software developed by Tobias Oetiker, a system
149 manager at the Swiss Federal Institute of Technology. Though it is a
150 database, there are distinct differences between RRDtool databases and other
151 databases as listed below:
152 .IP "\(bu" 4
153 RRDtool stores data; that makes it a back-end tool. The RRDtool command set
154 allows the creation of graphs; that makes it a front-end tool as well. Other
155 databases just store data and can not create graphs.
156 .IP "\(bu" 4
157 In case of linear databases, new data gets appended at the bottom of
158 the database table. Thus its size keeps on increasing, whereas the size of
159 an RRDtool database is determined at creation time. Imagine an RRDtool
160 database as the perimeter of a circle. Data is added along the
161 perimeter. When new data reaches the starting point, it overwrites
162 existing data. This way, the size of an RRDtool database always
163 remains constant. The name \*(L"Round Robin\*(R" stems from this behavior.
164 .IP "\(bu" 4
165 Other databases store the values as supplied. RRDtool can be configured to
166 calculate the rate of change from the previous to the current value and
167 store this information instead.
168 .IP "\(bu" 4
169 Other databases get updated when values are supplied. The RRDtool database
170 is structured in such a way that it needs data at predefined time
171 intervals. If it does not get a new value during the interval, it stores an
172 \&\s-1UNKNOWN\s0 value for that interval. So, when using the RRDtool database, it is
173 imperative to use scripts that run at regular intervals to ensure a constant
174 data flow to update the RRDtool database.
175 .PP
176 RRDtool is designed to store time series of data. With every data
177 update, an associated time stamp is stored. Time is always expressed
178 in seconds passed since epoch (01\-01\-1970). RRDtool can be installed
179 on Unix as well as Windows. It comes with a command set to carry out
180 various operations on \s-1RRD\s0 databases. This command set can be accessed
181 from the command line, as well as from Shell or Perl scripts. The
182 scripts act as wrappers for accessing data stored in RRDtool
183 databases.
184 .Sh "Understanding by an example"
185 .IX Subsection "Understanding by an example"
186 The structure of an \s-1RRD\s0 database is different than other linear databases.
187 Other databases define tables with columns, and many other parameters. These
188 definitions sometimes are very complex, especially in large databases.
189 RRDtool databases are primarily used for monitoring purposes and
190 hence are very simple in structure. The parameters
191 that need to be defined are variables that hold values and archives of those
192 values. Being time sensitive, a couple of time related parameters are also
193 defined. Because of its structure, the definition of an RRDtool database also
194 includes a provision to specify specific actions to take in the absence of
195 update values. Data Source (\s-1DS\s0), heartbeat, Date Source Type (\s-1DST\s0), Round
196 Robin Archive (\s-1RRA\s0), and Consolidation Function (\s-1CF\s0) are some of the
197 terminologies related to RRDtool databases.
198 .PP
199 The structure of a database and the terminology associated with it can be
200 best explained with an example.
201 .PP
202 .Vb 6
203 \& rrdtool create target.rrd \e
204 \&         \-\-start 1023654125 \e
205 \&         \-\-step 300 \e
206 \&         DS:mem:GAUGE:600:0:671744 \e
207 \&         RRA:AVERAGE:0.5:12:24 \e
208 \&         RRA:AVERAGE:0.5:288:31
209 .Ve
210 .PP
211 This example creates a database named \fItarget.rrd\fR. Start time
212 (1'023'654'125) is specified in total number of seconds since epoch
213 (time in seconds since 01\-01\-1970). While updating the database, the
214 update time is also specified.  This update time \s-1MUST\s0 be large (later)
215 then start time and \s-1MUST\s0 be in seconds since epoch.
216 .PP
217 The step of 300 seconds indicates that database expects new values every
218 300 seconds. The wrapper script should be scheduled to run every \fBstep\fR
219 seconds so that it updates the database every \fBstep\fR seconds.
220 .PP
221 \&\s-1DS\s0 (Data Source) is the actual variable which relates to the parameter on
222 the device that is monitored. Its syntax is
223 .PP
224 .Vb 1
225 \& DS:variable_name:DST:heartbeat:min:max
226 .Ve
227 .PP
228 \&\fB\s-1DS\s0\fR is a key word. \f(CW\*(C`variable_name\*(C'\fR is a name under which the parameter is
229 saved in the database. There can be as many DSs in a database as needed. After
230 every step interval, a new value of \s-1DS\s0 is supplied to update the database.
231 This value is also called Primary Data Point \fB(\s-1PDP\s0)\fR. In our example
232 mentioned above, a new \s-1PDP\s0 is generated every 300 seconds.
233 .PP
234 Note, that if you do \s-1NOT\s0 supply new datapoints exactly every 300 seconds,
235 this is not a problem, RRDtool will interpolate the data accordingly.
236 .PP
237 \&\fB\s-1DST\s0\fR (Data Source Type) defines the type of the \s-1DS\s0. It can be
238 \&\s-1COUNTER\s0, \s-1DERIVE\s0, \s-1ABSOLUTE\s0, \s-1GAUGE\s0. A \s-1DS\s0 declared as \s-1COUNTER\s0 will save
239 the rate of change of the value over a step period. This assumes that
240 the value is always increasing (the difference between the current and
241 the previous value is greater than 0). Traffic counters on a router
242 are an ideal candidate for using \s-1COUNTER\s0 as \s-1DST\s0. \s-1DERIVE\s0 is the same as
243 \&\s-1COUNTER\s0, but it allows negative values as well. If you want to see the
244 rate of \fIchange\fR in free diskspace on your server, then you might
245 want to use the \s-1DERIVE\s0 data type. \s-1ABSOLUTE\s0 also saves the rate of
246 change, but it assumes that the previous value is set to 0. The
247 difference between the current and the previous value is always equal
248 to the current value. Thus it just stores the current value divided by
249 the step interval (300 seconds in our example). \s-1GAUGE\s0 does not save
250 the rate of change. It saves the actual value itself. There are no
251 divisions or calculations. Memory consumption in a server is a typical
252 example of gauge. The difference between the different types DSTs can be
253 explained better with the following example:
254 .PP
255 .Vb 6
256 \& Values       = 300, 600, 900, 1200
257 \& Step         = 300 seconds
258 \& COUNTER DS   =    1,  1,   1,    1
259 \& DERIVE DS    =    1,  1,   1,    1
260 \& ABSOLUTE DS  =    1,  2,   3,    4
261 \& GAUGE DS     = 300, 600, 900, 1200
262 .Ve
263 .PP
264 The next parameter is \fBheartbeat\fR. In our example, heartbeat is 600
265 seconds. If the database does not get a new \s-1PDP\s0 within 300 seconds, it
266 will wait for another 300 seconds (total 600 seconds).  If it doesn't
267 receive any \s-1PDP\s0 within 600 seconds, it will save an \s-1UNKNOWN\s0 value into
268 the database. This \s-1UNKNOWN\s0 value is a special feature of RRDtool \- it
269 is much better than to assume a missing value was 0 (zero) or any
270 other number which might also be a valid data value.  For example, the
271 traffic flow counter on a router keeps increasing. Lets say, a value
272 is missed for an interval and 0 is stored instead of \s-1UNKNOWN\s0. Now when
273 the next value becomes available, it will calculate the difference
274 between the current value and the previous value (0) which is not
275 correct. So, inserting the value \s-1UNKNOWN\s0 makes much more sense here.
276 .PP
277 The next two parameters are the minimum and maximum value,
278 respectively. If the variable to be stored has predictable maximum and
279 minimum values, this should be specified here. Any update value
280 falling out of this range will be stored as \s-1UNKNOWN\s0.
281 .PP
282 The next line declares a round robin archive (\s-1RRA\s0). The syntax for
283 declaring an \s-1RRA\s0 is
284 .PP
285 .Vb 1
286 \& RRA:CF:xff:step:rows
287 .Ve
288 .PP
289 \&\s-1RRA\s0 is the keyword to declare RRAs. The consolidation function (\s-1CF\s0)
290 can be \s-1AVERAGE\s0, \s-1MINIMUM\s0, \s-1MAXIMUM\s0, and \s-1LAST\s0. The concept of the
291 consolidated data point (\s-1CDP\s0) comes into the picture here. A \s-1CDP\s0 is
292 CFed (averaged, maximum/minimum value or last value) from \fIstep\fR
293 number of PDPs. This \s-1RRA\s0 will hold \fIrows\fR CDPs.
294 .PP
295 Lets have a look at the example above. For the first \s-1RRA\s0, 12 (steps)
296 PDPs (\s-1DS\s0 variables) are AVERAGEed (\s-1CF\s0) to form one \s-1CDP\s0. 24 (rows) of
297 theses CDPs are archived. Each \s-1PDP\s0 occurs at 300 seconds. 12 PDPs
298 represent 12 times 300 seconds which is 1 hour. It means 1 \s-1CDP\s0 (which
299 is equal to 12 PDPs) represents data worth 1 hour. 24 such CDPs
300 represent 1 day (1 hour times 24 CDPs). This means, this \s-1RRA\s0 is an
301 archive for one day. After 24 CDPs, \s-1CDP\s0 number 25 will replace the 1st
302 \&\s-1CDP\s0. The second \s-1RRA\s0 saves 31 CDPs; each \s-1CPD\s0 represents an \s-1AVERAGE\s0
303 value for a day (288 PDPs, each covering 300 seconds = 24
304 hours). Therefore this \s-1RRA\s0 is an archive for one month. A single
305 database can have many RRAs. If there are multiple DSs, each
306 individual \s-1RRA\s0 will save data for all the DSs in the database. For
307 example, if a database has 3 DSs and daily, weekly, monthly, and
308 yearly RRAs are declared, then each \s-1RRA\s0 will hold data from all 3 data
309 sources.
310 .Sh "Graphical Magic"
311 .IX Subsection "Graphical Magic"
312 Another important feature of RRDtool is its ability to create
313 graphs. The \*(L"graph\*(R" command uses the \*(L"fetch\*(R" command internally to
314 retrieve values from the database. With the retrieved values it draws
315 graphs as defined by the parameters supplied on the command line. A
316 single graph can show different \s-1DS\s0 (Data Sources) from a database. It
317 is also possible to show the values from more than one database in a
318 single graph. Often, it is necessary to perform some math on the
319 values retrieved from the database before plotting them. For example,
320 in \s-1SNMP\s0 replies, memory consumption values are usually specified in
321 KBytes and traffic flow on interfaces is specified in Bytes. Graphs
322 for these values will be more meaningful if values are represented in
323 MBytes and mbps. The RRDtool graph command allows to define such
324 conversions. Apart from mathematical calculations, it is also possible
325 to perform logical operations such as greater than, less than, and
326 if/then/else. If a database contains more than one \s-1RRA\s0 archive, then a
327 question may arise \- how does RRDtool decide which \s-1RRA\s0 archive to use
328 for retrieving the values? RRDtool looks at several things when making
329 its choice. First it makes sure that the \s-1RRA\s0 covers as much of the
330 graphing time frame as possible. Second it looks at the resolution of
331 the \s-1RRA\s0 compared to the resolution of the graph. It tries to find one
332 which has the same or higher better resolution. With the \*(L"\-r\*(R" option
333 you can force RRDtool to assume a different resolution than the one
334 calculated from the pixel width of the graph.
335 .PP
336 Values of different variables can be presented in 5 different shapes
337 in a graph \- \s-1AREA\s0, \s-1LINE1\s0, \s-1LINE2\s0, \s-1LINE3\s0, and \s-1STACK\s0. \s-1AREA\s0 is represented
338 by a solid colored area with values as the boundary of this
339 area. \s-1LINE1/2/3\s0 (increasing width) are just plain lines representing
340 the values. \s-1STACK\s0 is also an area but it is \*(L"stack\*(R"ed on top \s-1AREA\s0 or
341 \&\s-1LINE1/2/3\s0. Another important thing to note is that variables are
342 plotted in the order they are defined in the graph command. Therefore
343 care must be taken to define \s-1STACK\s0 only after defining \s-1AREA/LINE\s0. It
344 is also possible to put formatted comments within the graph.  Detailed
345 instructions can be found in the graph manual.
346 .Sh "Wrapping RRDtool within Shell/Perl script"
347 .IX Subsection "Wrapping RRDtool within Shell/Perl script"
348 After understanding RRDtool it is now a time to actually use RRDtool
349 in scripts. Tasks involved in network management are data collection,
350 data storage, and data retrieval. In the following example, the
351 previously created target.rrd database is used. Data collection and
352 data storage is done using Shell scripts. Data retrieval and report
353 generation is done using Perl scripts. These scripts are shown below:
354 .PP
355 \fIShell script (collects data, updates database)\fR
356 .IX Subsection "Shell script (collects data, updates database)"
357 .PP
358 .Vb 14
359 \& #!/bin/sh
360 \& a=0
361 \& while [ "$a" == 0 ]; do
362 \& snmpwalk \-c public 192.168.1.250 hrSWRunPerfMem > snmp_reply
363 \&     total_mem=`awk 'BEGIN {tot_mem=0}
364 \&                           { if ($NF == "KBytes")
365 \&                             {tot_mem=tot_mem+$(NF\-1)}
366 \&                           }
367 \&                     END {print tot_mem}' snmp_reply`
368 \&     # I can use N as a replacement for the current time
369 \&     rrdtool update target.rrd N:$total_mem
370 \&     # sleep until the next 300 seconds are full
371 \&     perl \-e 'sleep 300 \- time % 300'
372 \& done # end of while loop
373 .Ve
374 .PP
375 \fIPerl script (retrieves data from database and generates graphs and statistics)\fR
376 .IX Subsection "Perl script (retrieves data from database and generates graphs and statistics)"
377 .PP
378 .Vb 3
379 \& #!/usr/bin/perl \-w
380 \& # This script fetches data from target.rrd, creates a graph of memory
381 \& # consumption on the target (Dual P3 Processor 1 GHz, 656 MB RAM)
382 .Ve
383 .PP
384 .Vb 6
385 \& # call the RRD perl module
386 \& use lib qw( /usr/local/rrdtool\-1.0.41/lib/perl ../lib/perl );
387 \& use RRDs;
388 \& my $cur_time = time();                # set current time
389 \& my $end_time = $cur_time \- 86400;     # set end time to 24 hours ago
390 \& my $start_time = $end_time \- 2592000; # set start 30 days in the past
391 .Ve
392 .PP
393 .Vb 58
394 \& # fetch average values from the RRD database between start and end time
395 \& my ($start,$step,$ds_names,$data) =
396 \&     RRDs::fetch("target.rrd", "AVERAGE",
397 \&                 "\-r", "600", "\-s", "$start_time", "\-e", "$end_time");
398 \& # save fetched values in a 2\-dimensional array
399 \& my $rows = 0;
400 \& my $columns = 0;
401 \& my $time_variable = $start;
402 \& foreach $line (@$data) {
403 \&   $vals[$rows][$columns] = $time_variable;
404 \&   $time_variable = $time_variable + $step;
405 \&   foreach $val (@$line) {
406 \&           $vals[$rows][++$columns] = $val;}
407 \&   $rows++;
408 \&   $columns = 0;
409 \& }
410 \& my $tot_time = 0;
411 \& my $count = 0;
412 \& # save the values from the 2\-dimensional into a 1\-dimensional array
413 \& for $i ( 0 .. $#vals ) {
414 \&     $tot_mem[$count] = $vals[$i][1];
415 \&     $count++;
416 \& }
417 \& my $tot_mem_sum = 0;
418 \& # calculate the total of all values
419 \& for $i ( 0 .. ($count\-1) ) {
420 \&     $tot_mem_sum = $tot_mem_sum + $tot_mem[$i];
421 \& }
422 \& # calculate the average of the array
423 \& my $tot_mem_ave = $tot_mem_sum/($count);
424 \& # create the graph
425 \& RRDs::graph ("/images/mem_$count.png",   \e
426 \&             "\-\-title= Memory Usage",    \e
427 \&             "\-\-vertical\-label=Memory Consumption (MB)", \e
428 \&             "\-\-start=$start_time",      \e
429 \&             "\-\-end=$end_time",          \e
430 \&             "\-\-color=BACK#CCCCCC",      \e
431 \&             "\-\-color=CANVAS#CCFFFF",    \e
432 \&             "\-\-color=SHADEB#9999CC",    \e
433 \&             "\-\-height=125",             \e
434 \&             "\-\-upper\-limit=656",        \e
435 \&             "\-\-lower\-limit=0",          \e
436 \&             "\-\-rigid",                  \e
437 \&             "\-\-base=1024",              \e
438 \&             "DEF:tot_mem=target.rrd:mem:AVERAGE", \e
439 \&             "CDEF:tot_mem_cor=tot_mem,0,671744,LIMIT,UN,0,tot_mem,IF,1024,/",\e
440 \&             "CDEF:machine_mem=tot_mem,656,+,tot_mem,\-",\e
441 \&             "COMMENT:Memory Consumption between $start_time",\e
442 \&             "COMMENT:    and $end_time                     ",\e
443 \&             "HRULE:656#000000:Maximum Available Memory \- 656 MB",\e
444 \&             "AREA:machine_mem#CCFFFF:Memory Unused",   \e
445 \&             "AREA:tot_mem_cor#6699CC:Total memory consumed in MB");
446 \& my $err=RRDs::error;
447 \& if ($err) {print "problem generating the graph: $err\en";}
448 \& # print the output
449 \& print "Average memory consumption is ";
450 \& printf "%5.2f",$tot_mem_ave/1024;
451 \& print " MB. Graphical representation can be found at /images/mem_$count.png.";
452 .Ve
453 .SH "AUTHOR"
454 .IX Header "AUTHOR"
455 Ketan Patel <k2pattu@yahoo.com>