Code

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