.\" Automatically generated by Pod::Man 4.10 (Pod::Simple 3.35) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). .\" Fear. Run. Save yourself. No user-serviceable parts. . \" fudge factors for nroff and troff .if n \{\ . ds #H 0 . ds #V .8m . ds #F .3m . ds #[ \f1 . ds #] \fP .\} .if t \{\ . ds #H ((1u-(\\\\n(.fu%2u))*.13m) . ds #V .6m . ds #F 0 . ds #[ \& . ds #] \& .\} . \" simple accents for nroff and troff .if n \{\ . ds ' \& . ds ` \& . ds ^ \& . ds , \& . ds ~ ~ . ds / .\} .if t \{\ . ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" . ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' . ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' . ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' . ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' . ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' .\} . \" troff and (daisy-wheel) nroff accents .ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' .ds 8 \h'\*(#H'\(*b\h'-\*(#H' .ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] .ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' .ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' .ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] .ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] .ds ae a\h'-(\w'a'u*4/10)'e .ds Ae A\h'-(\w'A'u*4/10)'E . \" corrections for vroff .if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' .if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' . \" for low resolution devices (crt and lpr) .if \n(.H>23 .if \n(.V>19 \ \{\ . ds : e . ds 8 ss . ds o a . ds d- d\h'-1'\(ga . ds D- D\h'-1'\(hy . ds th \o'bp' . ds Th \o'LP' . ds ae ae . ds Ae AE .\} .rm #[ #] #H #V #F C .\" ======================================================================== .\" .IX Title "RRD-BEGINNERS 1" .TH RRD-BEGINNERS 1 "2022-04-14" "1.0.50" "RRDtool" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" rrd\-beginners \- Beginners guide .SH "SYNOPSIS" .IX Header "SYNOPSIS" Helping new RRDTool users to understand the basics of RRDTool .SH "DESCRIPTION" .IX Header "DESCRIPTION" This manual is an attempt to assist beginners in understanding the concepts of RRDTool. It sheds a light on differences between RRDTool and other databases. With help of an example, it explains structure of RRDTool database. This is followed by an overview of the \*(L"graph\*(R" feature of RRDTool. At the end, it has sample scripts that illustrates the usage/wrapping of RRDTool within Shell or Perl scripts. .SS "What makes RRDTool so special?" .IX Subsection "What makes RRDTool so special?" RRDTool is \s-1GNU\s0 licensed software developed by Tobias Oetiker, a system manager at the Swiss Federal Institute of Technology. Though it is a database, there are distinct differences between RRDTool database and other databases as listed below: .IP "\(bu" 4 RRDTool stores data; that makes it a back end tool. The RRDTool command set allows the creation of graphs; that makes it a front end tool as well. Other databases just stores data and can not create graphs. .IP "\(bu" 4 In case of linear databases, new data gets appended at the bottom of the database table. Thus its size keeps on increasing, whereas size of an RRDTool database is determined at creation time. Imagine an RRDTool database as the perimeter of a circle. Data is added along the perimeter. When new data reaches the starting point, it overwrites existing data. This way, the size of an RRDTool database always remains constant. The name \*(L"Round Robin\*(R" stems from this attribute. .IP "\(bu" 4 Other databases store the values as supplied. RRDTool can be configured to calculate the rate of change from the previous to the current value and store this information instead. .IP "\(bu" 4 Other databases get updated when values are supplied. The RRDTool database is structured in such a way that it needs data at predefined time intervals. If it does not get a new value during the interval, it stores an \&\s-1UNKNOWN\s0 value for that interval. So, when using the RRDTool database, it is imperative to use scripts that runs at regular intervals to ensure a constant data flow to update the RRDTool database. .PP RRDTool has a lot to do with time. With every data update, it also needs to know the time when that update occurred. Time is always expressed in seconds passed since epoch (01\-01\-1970). RRDTool can be installed on Unix as well as Windows. It has command set to carry out various operations on \s-1RRD\s0 database. This command set can be accessed from the command line, and from Shell or Perl scripts. The scripts act as wrappers for accessing data stored in RRDTool database. .SS "Understanding by an example" .IX Subsection "Understanding by an example" The structure of an \s-1RRD\s0 database is different than other linear databases. Other databases define tables with columns, and many other parameters. These definitions sometime are very complex, especially in large databases. RRDTool databases are primarily used for monitoring purposes and hence are very simple in structure. The parameters that need to be defined are variables that hold values and archives of those values. Being time sensitive, a couple of time related parameters are also defined. Because of its structure, the definition of an RRDTool database also includes a provision to specify specific actions to take in the absence of update values. Data Source (\s-1DS\s0), heartbeat, Date Source Type (\s-1DST\s0), Round Robin Archive (\s-1RRA\s0), and Consolidation Function (\s-1CF\s0) are some of the terminologies related to RRDTool databases. .PP The structure of a database and the terminology associated with it can be best explained with an example. .PP .Vb 6 \& rrdtool create target.rrd \& \-\-start 1023654125 \& \-\-step 300 \& DS:mem:GAUGE:600:0:671744 \& RRA:AVERAGE:0.5:12:24 \& RRA:AVERAGE:0.5:288:31 .Ve .PP This example creates a database named \fItarget.rrd\fR. Start time (1023654125) is specified in total number of seconds since epoch (time in seconds since 01\-01\-1970). While updating the database, update time is also specified. This update time \s-1MUST\s0 occur after start time and \s-1MUST\s0 be in seconds since epoch. .PP The step of 300 seconds indicates that database expects new values every 300 seconds. The wrapper script should be scheduled to run every \fBstep\fR seconds so that it updates the database every \fBstep\fR seconds. .PP \&\s-1DS\s0 (Data Source) is the actual variable which relates to the parameter on the device that has to be monitored. Its syntax is .PP .Vb 1 \& DS:variable_name:DST:heartbeat:min:max .Ve .PP \&\fB\s-1DS\s0\fR is a key word. \f(CW\*(C`variable_name\*(C'\fR is a name under which the parameter is saved in database. There can be as many DSs in a database as needed. After every step interval, a new value of \s-1DS\s0 is supplied to update the database. This value is also called as Primary Data Point \fB(\s-1PDP\s0)\fR. In our example mentioned above, a new \s-1PDP\s0 is generated every 300 seconds. .PP Note, that if you do \s-1NOT\s0 supply new datapoints exactly every 300 seconds, this is not problem, RRDTool will interpolate the data accordingly. .PP \&\fB\s-1DST\s0\fR (Data Source Type) defines type of \s-1DS.\s0 It can be \s-1COUNTER, DERIVE, ABSOLUTE, GAUGE. A DS\s0 declared as \s-1COUNTER\s0 will save the rate of change of the value over a step period. This assumes that the value is always increasing (difference between last two values is more than 0). Traffic counters on a router is an ideal candidate for using \s-1COUNTER\s0 as \s-1DST. DERIVE\s0 is same as \s-1COUNTER\s0 but it allows negative values as well. If you want to see the rate of \fIchange\fR in free diskspace on your server, then you might want to use the \s-1DERIVE\s0 data type. \s-1ABSOLUTE\s0 also saves the rate of change but it assumes that previous value is set to 0. The difference between current and previous value is always equal to the current value. So, it stores the current value divided by step interval (300 seconds in our example). \s-1GAUGE\s0 does not save the rate of change. It saves the actual value itself. There are no divisions/calculations. Memory consumption in a server is an ideal example of gauge. Difference among different types DSTs can be explained better with following example: .PP .Vb 6 \& Values = 300, 600, 900, 1200 \& Step = 300 seconds \& COUNTER DS = 1, 1, 1, 1 \& DERIVE DS = 1, 1, 1, 1 \& ABSOLUTE DS = 1, 2, 3, 4 \& GAUGE DS = 300, 600, 900, 1200 .Ve .PP The next parameter is \fBheartbeat\fR. In our example, heartbeat is 600 seconds. If database does not get a new \s-1PDP\s0 within 300 seconds, it will wait for another 300 seconds (total 600 seconds). If it doesnt receive any \s-1PDP\s0 with in 600 seconds, it will save an \s-1UNKNOWN\s0 value into database. This \s-1UNKNOWN\s0 value is a special feature of RRDTool \- it is much better than to assume a missing value was 0 (zero). For example, the traffic flow counter on a router keeps on increasing. Lets say, a value is missed for an interval and 0 is stored instead of \s-1UNKNOWN.\s0 Now when next value becomes available, it will calculate difference between current value and previous value (0) which is not correct. So, inserting value \s-1UNKNOWN\s0 makes much more sense here. .PP The next two parameters are the minimum and maximum value respectively. If variable to be stored has predictable maximum and minimum value, this should be specified here. Any update value falling out of this range will be saved as \&\s-1UNKNOWN.\s0 .PP The next line declares a round robin archive (\s-1RRA\s0). The syntax for declaring an \s-1RRA\s0 is .PP .Vb 1 \& RRA:CF:xff:step:rows .Ve .PP \&\s-1RRA\s0 is the keyword to declare RRAs. The consolidation function (\s-1CF\s0) can be \&\s-1AVERAGE, MINIMUM, MAXIMUM,\s0 and \s-1LAST.\s0 The concept of the consolidated data point (\s-1CDP\s0) comes into the picture here. A \s-1CDP\s0 is CFed (averaged, maximum/minimum value or last value) from \fIstep\fR number of PDPs. This \s-1RRA\s0 will hold \fIrows\fR CDPs. .PP Lets have a look at the example above. For the first \s-1RRA, 12\s0 (steps) PDPs (\s-1DS\s0 variables) are AVERAGEed (\s-1CF\s0) to form one \s-1CDP. 24\s0 (rows) of theses CDPs are archived. Each \s-1PDP\s0 occurs at 300 seconds. 12 PDPs represent 12 times 300 seconds which is 1 hour. It means 1 \s-1CDP\s0 (which is equal to 12 PDPs) represents data worth 1 hour. 24 such CDPs represent 1 day (1 hour times 24 CDPs). It means, this \s-1RRA\s0 is an archive for one day. After 24 CDPs, \s-1CDP\s0 number 25 will replace the 1st \s-1CDP.\s0 Second \s-1RRA\s0 saves 31 CDPs; each \s-1CPD\s0 represents an \s-1AVERAGE\s0 value for a day (288 PDPs, each covering 300 seconds = 24 hours). Therefore this \s-1RRA\s0 is an archive for one month. A single database can have many RRAs. If there are multiple DSs, each individual \s-1RRA\s0 will save data for all the DSs in the database. For example, if a database has 3 DSs; and daily, weekly, monthly, and yearly RRAs are declared, then each \s-1RRA\s0 will hold data from all 3 data sources. .SS "Graphical Magic" .IX Subsection "Graphical Magic" Another important feature of RRDTool is its ability to create graphs. The \&\*(L"graph\*(R" command uses \*(L"fetch\*(R" command internally to retrieve values from the database. With the retrieved values, it draws graphs as defined by the parameters supplied on the command line. A single graph can show different \&\s-1DS\s0 (Data Sources0) from a database. It is also possible to show the values from more than one databases into a single graph. Often, it is necessary to perform some math on the values retrieved from database, before plotting them. For example, in \s-1SNMP\s0 replies, memory consumption values are usually specified in KBytes and traffic flow on interfaces is specified in Bytes. Graphs for these values will be more senseful if values are represented in MBytes and mbps. the RRDTool graph command allows to define such conversions. Apart from mathematical calculations, it is also possible to perform logical operations such as greater than, less than, and if then else. If a database contains more than one \s-1RRA\s0 archive, then a question may arise \- how does RRDTool decide which \s-1RRA\s0 archive to use for retrieving the values? RRDTool takes looks at several things when making its choice. First it makes sure that the \s-1RRA\s0 covers as much of the graphing time frame as possible. Second it looks at the resolution of the \s-1RRA\s0 compared to the resolution of the graph. It tries to find one which has the same or better resolution. With the \*(L"\-r\*(R" option you can force RRDTool to assume a different resolution than the one calculated from the pixel width of the graph. .PP Values of different variables can be presented in 5 different shapes in a graph \- \s-1AREA, LINE1, LINE2, LINE3,\s0 and \s-1STACK. AREA\s0 is represented by a solid colored area with values as the boundary of this area. \s-1LINE1/2/3\s0 (increasing width) are just plain lines representing the values. \s-1STACK\s0 is also an area but it is \*(L"stack\*(R"ed on \s-1AREA\s0 or \s-1LINE1/2/3.\s0 Another important thing to note, is that variables are plotted in the order they are defined in graph command. So, care must be taken to define \s-1STACK\s0 only after defining \&\s-1AREA/LINE.\s0 It is also possible to put formatted comments within the graph. Detailed instructions be found under graph manual. .SS "Wrapping RRDTool within Shell/Perl script" .IX Subsection "Wrapping RRDTool within Shell/Perl script" After understanding RRDTool, it is now a time to actually use RRDTool in scripts. Tasks involved in network management are data collection, data storage, and data retrieval. In the following example, the previously created target.rrd database is used. Data collection and data storage is done using Shell scrip. Data retrieval and report generation is done using Perl script. These scripts are as shown below: .PP \fIShell script (collects data, updates database)\fR .IX Subsection "Shell script (collects data, updates database)" .PP .Vb 10 \& #!/bin/sh \& a=0 \& while [ "$a" == 0 ]; do \& snmpwalk \-c public 192.168.1.250 hrSWRunPerfMem > snmp_reply \& total_mem=\`awk \*(AqBEGIN {tot_mem=0} \& { if ($NF == "KBytes") \& {tot_mem=tot_mem+$(NF\-1)} \& } \& END {print tot_mem}\*(Aq snmp_reply\` \& # I can use N as a replacement for the current time \& rrdtool update target.rrd N:$total_mem \& # sleep until the next 300 seconds are full \& perl \-e \*(Aqsleep 300 \- time % 300\*(Aq \& done # end of while loop .Ve .PP \fIPerl script (retrieves data from database and generates graphs and statistics)\fR .IX Subsection "Perl script (retrieves data from database and generates graphs and statistics)" .PP .Vb 3 \& #!/usr/bin/perl \-w \& #This script fetch data from target.rrd, creates graph of memory consumption \& on target (Dual P3 Processor 1 GHz, 656 MB RAM) \& \& #calling RRD perl module \& use lib qw( /usr/local/rrdtool\-1.0.41/lib/perl ../lib/perl ); \& use RRDs; \& my $cur_time = time(); # setting current time \& my $end_time = $cur_time \- 86400; # setting end time to 24 hours behind current time \& my $start_time = $end_time \- 2592000; # setting start time to 30 days from end time \& \& #fetching average values from RRD database between start and end time \& my ($start,$step,$ds_names,$data) = \& RRDs::fetch("target.rrd", "AVERAGE", \& "\-r", "600", "\-s", "$start_time", "\-e", "$end_time"); \& #saving fetched values in 2\-dimensional array \& my $rows = 0; \& my $columns = 0; \& my $time_variable = $start; \& foreach $line (@$data) { \& $vals[$rows][$columns] = $time_variable; \& $time_variable = $time_variable + $step; \& foreach $val (@$line) { \& $vals[$rows][++$columns] = $val;} \& $rows++; \& $columns = 0; \& } \& my $tot_time = 0; \& my $count = 0; \& #saving values from 2\-dimensional into 1\-dimensional array \& for $i ( 0 .. $#vals ) { \& $tot_mem[$count] = $vals[$i][1]; \& $count++; \& } \& my $tot_mem_sum = 0; \& #calculating total of all values \& for $i ( 0 .. ($count\-1) ) { \& $tot_mem_sum = $tot_mem_sum + $tot_mem[$i]; \& } \& #calculating average of array \& my $tot_mem_ave = $tot_mem_sum/($count); \& #creating graph \& RRDs::graph ("/images/mem_$count.gif", \e \& "\-\-title= Memory Usage", \e \& "\-\-vertical\-label=Memory Consumption (MB)", \e \& "\-\-start=$start_time", \e \& "\-\-end=$end_time", \e \& "\-\-color=BACK#CCCCCC", \e \& "\-\-color=CANVAS#CCFFFF", \e \& "\-\-color=SHADEB#9999CC", \e \& "\-\-height=125", \e \& "\-\-upper\-limit=656", \e \& "\-\-lower\-limit=0", \e \& "\-\-rigid", \e \& "\-\-base=1024", \e \& "DEF:tot_mem=target.rrd:mem:AVERAGE", \e \& "CDEF:correct_tot_mem=tot_mem,0,671744,LIMIT,UN,0,tot_mem,IF,1024,/",\e \& "CDEF:machine_mem=tot_mem,656,+,tot_mem,\-",\e \& "COMMENT:Memory Consumption between $start_time",\e \& "COMMENT: and $end_time ",\e \& "HRULE:656#000000:Maximum Available Memory \- 656 MB",\e \& "AREA:machine_mem#CCFFFF:Memory Unused", \e \& "AREA:correct_tot_mem#6699CC:Total memory consumed in MB"); \& my $err=RRDs::error; \& if ($err) {print "problem generating the graph: $err\en";} \& #printing the output \& print "Average memory consumption is "; \& printf "%5.2f",$tot_mem_ave/1024; \& print " MB. Graphical representation can be found at /images/mem_$count.gif."; .Ve .SH "AUTHOR" .IX Header "AUTHOR" Ketan Patel