Code

Imported upstream version 1.4.8
[pkg-rrdtool.git] / doc / cdeftutorial.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 "CDEFTUTORIAL 1"
127 .TH CDEFTUTORIAL 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 cdeftutorial \- Alex van den Bogaerdt's CDEF tutorial
134 .SH "DESCRIPTION"
135 .IX Header "DESCRIPTION"
136 Intention of this document: to provide some examples of the commonly
137 used parts of RRDtool's \s-1CDEF\s0 language.
138 .PP
139 If you think some important feature is not explained properly, and if
140 adding it to this document would benefit most users, please do ask me
141 to add it.  I will then try to provide an answer in the next release
142 of this tutorial.  No feedback equals no changes! Additions to
143 this document are also welcome.  \*(-- Alex van den Bogaerdt
144 <alex@vandenbogaerdt.nl>
145 .SS "Why this tutorial?"
146 .IX Subsection "Why this tutorial?"
147 One of the powerful parts of RRDtool is its ability to do all sorts
148 of calculations on the data retrieved from its databases. However,
149 RRDtool's many options and syntax make it difficult for the average
150 user to understand. The manuals are good at explaining what these
151 options do; however they do not (and should not) explain in detail
152 why they are useful. As with my RRDtool tutorial: if you want a
153 simple document in simple language you should read this tutorial.
154 If you are happy with the official documentation, you may find this
155 document too simple or even boring. If you do choose to read this
156 tutorial, I also expect you to have read and fully understand my
157 other tutorial.
158 .SS "More reading"
159 .IX Subsection "More reading"
160 If you have difficulties with the way I try to explain it please read
161 Steve Rader's rpntutorial. It may help you understand how this all works.
162 .SH "What are CDEFs?"
163 .IX Header "What are CDEFs?"
164 When retrieving data from an \s-1RRD\s0, you are using a \*(L"\s-1DEF\s0\*(R" to work with
165 that data. Think of it as a variable that changes over time (where
166 time is the x\-axis). The value of this variable is what is found in
167 the database at that particular time and you can't do any
168 modifications on it. This is what CDEFs are for: they takes values
169 from DEFs and perform calculations on them.
170 .SH "Syntax"
171 .IX Header "Syntax"
172 .Vb 2
173 \&   DEF:var_name_1=some.rrd:ds_name:CF
174 \&   CDEF:var_name_2=RPN_expression
175 .Ve
176 .PP
177 You first define \*(L"var_name_1\*(R" to be data collected from data source
178 \&\*(L"ds_name\*(R" found in \s-1RRD\s0 \*(L"some.rrd\*(R" with consolidation function \*(L"\s-1CF\s0\*(R".
179 .PP
180 Assume the ifInOctets \s-1SNMP\s0 counter is saved in mrtg.rrd as the \s-1DS\s0 \*(L"in\*(R".
181 Then the following \s-1DEF\s0 defines a variable for the average of that
182 data source:
183 .PP
184 .Vb 1
185 \&   DEF:inbytes=mrtg.rrd:in:AVERAGE
186 .Ve
187 .PP
188 Say you want to display bits per second (instead of bytes per second
189 as stored in the database.)  You have to define a calculation
190 (hence \*(L"\s-1CDEF\s0\*(R") on variable \*(L"inbytes\*(R" and use that variable (inbits)
191 instead of the original:
192 .PP
193 .Vb 1
194 \&   CDEF:inbits=inbytes,8,*
195 .Ve
196 .PP
197 This tells RRDtool to multiply inbytes by eight to get inbits. I'll
198 explain later how this works. In the graphing or printing functions,
199 you can now use inbits where you would use inbytes otherwise.
200 .PP
201 Note that the variable name used in the \s-1CDEF\s0 (inbits) must not be the
202 same as the variable named in the \s-1DEF\s0 (inbytes)!
203 .SH "RPN-expressions"
204 .IX Header "RPN-expressions"
205 \&\s-1RPN\s0 is short-hand for Reverse Polish Notation. It works as follows.
206 You put the variables or numbers on a stack. You also put operations
207 (things-to-do) on the stack and this stack is then processed. The result
208 will be placed on the stack. At the end, there should be exactly one
209 number left: the outcome of the series of operations. If there is not
210 exactly one number left, RRDtool will complain loudly.
211 .PP
212 Above multiplication by eight will look like:
213 .IP "1." 4
214 Start with an empty stack
215 .IP "2." 4
216 Put the content of variable inbytes on the stack
217 .IP "3." 4
218 Put the number eight on the stack
219 .IP "4." 4
220 Put the operation multiply on the stack
221 .IP "5." 4
222 Process the stack
223 .IP "6." 4
224 Retrieve the value from the stack and put it in variable inbits
225 .PP
226 We will now do an example with real numbers. Suppose the variable
227 inbytes would have value 10, the stack would be:
228 .IP "1." 4
229 ||
230 .IP "2." 4
231 |10|
232 .IP "3." 4
233 |10|8|
234 .IP "4." 4
235 |10|8|*|
236 .IP "5." 4
237 |80|
238 .IP "6." 4
239 ||
240 .PP
241 Processing the stack (step 5) will retrieve one value from the stack
242 (from the right at step 4). This is the operation multiply and this
243 takes two values off the stack as input. The result is put back on the
244 stack (the value 80 in this case). For multiplication the order doesn't
245 matter, but for other operations like subtraction and division it does.
246 Generally speaking you have the following order:
247 .PP
248 .Vb 1
249 \&   y = A \- B  \-\->  y=minus(A,B)  \-\->  CDEF:y=A,B,\-
250 .Ve
251 .PP
252 This is not very intuitive (at least most people don't think so). For
253 the function f(A,B) you reverse the position of \*(L"f\*(R", but you do not
254 reverse the order of the variables.
255 .SH "Converting your wishes to RPN"
256 .IX Header "Converting your wishes to RPN"
257 First, get a clear picture of what you want to do. Break down the problem
258 in smaller portions until they cannot be split anymore. Then it is rather
259 simple to convert your ideas into \s-1RPN\s0.
260 .PP
261 Suppose you have several RRDs and would like to add up some counters in
262 them. These could be, for instance, the counters for every \s-1WAN\s0 link you
263 are monitoring.
264 .PP
265 You have:
266 .PP
267 .Vb 3
268 \&   router1.rrd with link1in link2in
269 \&   router2.rrd with link1in link2in
270 \&   router3.rrd with link1in link2in
271 .Ve
272 .PP
273 Suppose you would like to add up all these counters, except for link2in
274 inside router2.rrd. You need to do:
275 .PP
276 (in this example, \*(L"router1.rrd:link1in\*(R" means the \s-1DS\s0 link1in inside the
277 \&\s-1RRD\s0 router1.rrd)
278 .PP
279 .Vb 7
280 \&   router1.rrd:link1in
281 \&   router1.rrd:link2in
282 \&   router2.rrd:link1in
283 \&   router3.rrd:link1in
284 \&   router3.rrd:link2in
285 \&   \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-   +
286 \&   (outcome of the sum)
287 .Ve
288 .PP
289 As a mathematical function, this could be written:
290 .PP
291 \&\f(CW\*(C`add(router1.rrd:link1in , router1.rrd:link2in , router2.rrd:link1in , router3.rrd:link1in , router3.rrd:link2.in)\*(C'\fR
292 .PP
293 With RRDtool and \s-1RPN\s0, first, define the inputs:
294 .PP
295 .Vb 5
296 \&   DEF:a=router1.rrd:link1in:AVERAGE
297 \&   DEF:b=router1.rrd:link2in:AVERAGE
298 \&   DEF:c=router2.rrd:link1in:AVERAGE
299 \&   DEF:d=router3.rrd:link1in:AVERAGE
300 \&   DEF:e=router3.rrd:link2in:AVERAGE
301 .Ve
302 .PP
303 Now, the mathematical function becomes: \f(CW\*(C`add(a,b,c,d,e)\*(C'\fR
304 .PP
305 In \s-1RPN\s0, there's no operator that sums more than two values so you need
306 to do several additions. You add a and b, add c to the result, add d
307 to the result and add e to the result.
308 .PP
309 .Vb 5
310 \&   push a:         a     stack contains the value of a
311 \&   push b and add: b,+   stack contains the result of a+b
312 \&   push c and add: c,+   stack contains the result of a+b+c
313 \&   push d and add: d,+   stack contains the result of a+b+c+d
314 \&   push e and add: e,+   stack contains the result of a+b+c+d+e
315 .Ve
316 .PP
317 What was calculated here would be written down as:
318 .PP
319 .Vb 1
320 \&   ( ( ( (a+b) + c) + d) + e) >
321 .Ve
322 .PP
323 This is in \s-1RPN:\s0  \f(CW\*(C`CDEF:result=a,b,+,c,+,d,+,e,+\*(C'\fR
324 .PP
325 This is correct but it can be made more clear to humans. It does
326 not matter if you add a to b and then add c to the result or first
327 add b to c and then add a to the result. This makes it possible to
328 rewrite the \s-1RPN\s0 into \f(CW\*(C`CDEF:result=a,b,c,d,e,+,+,+,+\*(C'\fR which is
329 evaluated differently:
330 .PP
331 .Vb 10
332 \&   push value of variable a on the stack: a
333 \&   push value of variable b on the stack: a b
334 \&   push value of variable c on the stack: a b c
335 \&   push value of variable d on the stack: a b c d
336 \&   push value of variable e on the stack: a b c d e
337 \&   push operator + on the stack:          a b c d e +
338 \&   and process it:                        a b c P   (where P == d+e)
339 \&   push operator + on the stack:          a b c P +
340 \&   and process it:                        a b Q     (where Q == c+P)
341 \&   push operator + on the stack:          a b Q +
342 \&   and process it:                        a R       (where R == b+Q)
343 \&   push operator + on the stack:          a R +
344 \&   and process it:                        S         (where S == a+R)
345 .Ve
346 .PP
347 As you can see the \s-1RPN\s0 expression \f(CW\*(C`a,b,c,d,e,+,+,+,+,+\*(C'\fR will evaluate in
348 \&\f(CW\*(C`((((d+e)+c)+b)+a)\*(C'\fR and it has the same outcome as \f(CW\*(C`a,b,+,c,+,d,+,e,+\*(C'\fR.
349 This is called the commutative law of addition,
350 but you may forget this right away, as long as you remember what it
351 means.
352 .PP
353 Now look at an expression that contains a multiplication:
354 .PP
355 First in normal math: \f(CW\*(C`let result = a+b*c\*(C'\fR. In this case you can't
356 choose the order yourself, you have to start with the multiplication
357 and then add a to it. You may alter the position of b and c, you must
358 not alter the position of a and b.
359 .PP
360 You have to take this in consideration when converting this expression
361 into \s-1RPN\s0. Read it as: \*(L"Add the outcome of b*c to a\*(R" and then it is
362 easy to write the \s-1RPN\s0 expression: \f(CW\*(C`result=a,b,c,*,+\*(C'\fR
363 Another expression that would return the same: \f(CW\*(C`result=b,c,*,a,+\*(C'\fR
364 .PP
365 In normal math, you may encounter something like \*(L"a*(b+c)\*(R" and this
366 can also be converted into \s-1RPN\s0. The parenthesis just tell you to first
367 add b and c, and then multiply a with the result. Again, now it is
368 easy to write it in \s-1RPN:\s0 \f(CW\*(C`result=a,b,c,+,*\*(C'\fR. Note that this is very
369 similar to one of the expressions in the previous paragraph, only the
370 multiplication and the addition changed places.
371 .PP
372 When you have problems with \s-1RPN\s0 or when RRDtool is complaining, it's
373 usually a good thing to write down the stack on a piece of paper
374 and see what happens. Have the manual ready and pretend to be RRDtool.
375 Just do all the math by hand to see what happens, I'm sure this will
376 solve most, if not all, problems you encounter.
377 .SH "Some special numbers"
378 .IX Header "Some special numbers"
379 .SS "The unknown value"
380 .IX Subsection "The unknown value"
381 Sometimes collecting your data will fail. This can be very common,
382 especially when querying over busy links. RRDtool can be configured
383 to allow for one (or even more) unknown value(s) and calculate the missing
384 update. You can, for instance, query your device every minute. This is
385 creating one so called \s-1PDP\s0 or primary data point per minute. If you
386 defined your \s-1RRD\s0 to contain an \s-1RRA\s0 that stores 5\-minute values, you need
387 five of those PDPs to create one \s-1CDP\s0 (consolidated data point).
388 These PDPs can become unknown in two cases:
389 .IP "1." 4
390 The updates are too far apart. This is tuned using the \*(L"heartbeat\*(R" setting.
391 .IP "2." 4
392 The update was set to unknown on purpose by inserting no value (using the
393 template option) or by using \*(L"U\*(R" as the value to insert.
394 .PP
395 When a \s-1CDP\s0 is calculated, another mechanism determines if this \s-1CDP\s0 is valid
396 or not. If there are too many PDPs unknown, the \s-1CDP\s0 is unknown as well.
397 This is determined by the xff factor. Please note that one unknown counter
398 update can result in two unknown PDPs! If you only allow for one unknown
399 \&\s-1PDP\s0 per \s-1CDP\s0, this makes the \s-1CDP\s0 go unknown!
400 .PP
401 Suppose the counter increments with one per second and you retrieve it
402 every minute:
403 .PP
404 .Vb 7
405 \&   counter value    resulting rate
406 \&   10\*(Aq000
407 \&   10\*(Aq060            1; (10\*(Aq060\-10\*(Aq000)/60 == 1
408 \&   10\*(Aq120            1; (10\*(Aq120\-10\*(Aq060)/60 == 1
409 \&   unknown           unknown; you don\*(Aqt know the last value
410 \&   10\*(Aq240            unknown; you don\*(Aqt know the previous value
411 \&   10\*(Aq300            1; (10\*(Aq300\-10\*(Aq240)/60 == 1
412 .Ve
413 .PP
414 If the \s-1CDP\s0 was to be calculated from the last five updates, it would get
415 two unknown PDPs and three known PDPs. If xff would have been set to 0.5
416 which by the way is a commonly used factor, the \s-1CDP\s0 would have a known
417 value of 1. If xff would have been set to 0.2 then the resulting \s-1CDP\s0
418 would be unknown.
419 .PP
420 You have to decide the proper values for heartbeat, number of PDPs per
421 \&\s-1CDP\s0 and the xff factor. As you can see from the previous text they define
422 the behavior of your \s-1RRA\s0.
423 .SS "Working with unknown data in your database"
424 .IX Subsection "Working with unknown data in your database"
425 As you have read in the previous chapter, entries in an \s-1RRA\s0 can be
426 set to the unknown value. If you do calculations with this type of
427 value, the result has to be unknown too. This means that an expression
428 such as \f(CW\*(C`result=a,b,+\*(C'\fR will be unknown if either a or b is unknown.
429 It would be wrong to just ignore the unknown value and return the value
430 of the other parameter. By doing so, you would assume \*(L"unknown\*(R" means \*(L"zero\*(R"
431 and this is not true.
432 .PP
433 There has been a case where somebody was collecting data for over a year.
434 A new piece of equipment was installed, a new \s-1RRD\s0 was created and the
435 scripts were changed to add a counter from the old database and a counter
436 from the new database. The result was disappointing, a large part of
437 the statistics seemed to have vanished mysteriously ...
438 They of course didn't, values from the old database (known values) were
439 added to values from the new database (unknown values) and the result was
440 unknown.
441 .PP
442 In this case, it is fairly reasonable to use a \s-1CDEF\s0 that alters unknown
443 data into zero. The counters of the device were unknown (after all, it
444 wasn't installed yet!) but you know that the data rate through the device
445 had to be zero (because of the same reason: it was not installed).
446 .PP
447 There are some examples below that make this change.
448 .SS "Infinity"
449 .IX Subsection "Infinity"
450 Infinite data is another form of a special number. It cannot be
451 graphed because by definition you would never reach the infinite
452 value. You can think of positive and negative infinity depending on
453 the position relative to zero.
454 .PP
455 RRDtool is capable of representing (\-not\- graphing!) infinity by stopping
456 at its current maximum (for positive infinity) or minimum (for negative
457 infinity) without knowing this maximum (minimum).
458 .PP
459 Infinity in RRDtool is mostly used to draw an \s-1AREA\s0 without knowing its
460 vertical dimensions. You can think of it as drawing an \s-1AREA\s0 with an
461 infinite height and displaying only the part that is visible in the
462 current graph. This is probably a good way to approximate infinity
463 and it sure allows for some neat tricks. See below for examples.
464 .SS "Working with unknown data and infinity"
465 .IX Subsection "Working with unknown data and infinity"
466 Sometimes you would like to discard unknown data and pretend it is zero
467 (or any other value for that matter) and sometimes you would like to
468 pretend that known data is unknown (to discard known-to-be-wrong data).
469 This is why CDEFs have support for unknown data. There are also examples
470 available that show unknown data by using infinity.
471 .SH "Some examples"
472 .IX Header "Some examples"
473 .SS "Example: using a recently created \s-1RRD\s0"
474 .IX Subsection "Example: using a recently created RRD"
475 You are keeping statistics on your router for over a year now. Recently
476 you installed an extra router and you would like to show the combined
477 throughput for these two devices.
478 .PP
479 If you just add up the counters from router.rrd and router2.rrd, you
480 will add known data (from router.rrd) to unknown data (from router2.rrd) for
481 the bigger part of your stats. You could solve this in a few ways:
482 .IP "\(bu" 4
483 While creating the new database, fill it with zeros from the start to now.
484 You have to make the database start at or before the least recent time in
485 the other database.
486 .IP "\(bu" 4
487 Alternatively, you could use \s-1CDEF\s0 and alter unknown data to zero.
488 .PP
489 Both methods have their pros and cons. The first method is troublesome and
490 if you want to do that you have to figure it out yourself. It is not
491 possible to create a database filled with zeros, you have to put them in
492 manually. Implementing the second method is described next:
493 .PP
494 What we want is: \*(L"if the value is unknown, replace it with zero\*(R". This
495 could be written in pseudo-code as:  if (value is unknown) then (zero)
496 else (value). When reading the rrdgraph manual you notice the \*(L"\s-1UN\s0\*(R"
497 function that returns zero or one. You also notice the \*(L"\s-1IF\s0\*(R" function
498 that takes zero or one as input.
499 .PP
500 First look at the \*(L"\s-1IF\s0\*(R" function. It takes three values from the stack,
501 the first value is the decision point, the second value is returned to
502 the stack if the evaluation is \*(L"true\*(R" and if not, the third value is
503 returned to the stack. We want the \*(L"\s-1UN\s0\*(R" function to decide what happens
504 so we combine those two functions in one \s-1CDEF\s0.
505 .PP
506 Lets write down the two possible paths for the \*(L"\s-1IF\s0\*(R" function:
507 .PP
508 .Vb 2
509 \&   if true  return a
510 \&   if false return b
511 .Ve
512 .PP
513 In \s-1RPN:\s0  \f(CW\*(C`result=x,a,b,IF\*(C'\fR where \*(L"x\*(R" is either true or false.
514 .PP
515 Now we have to fill in \*(L"x\*(R", this should be the \*(L"(value is unknown)\*(R" part
516 and this is in \s-1RPN:\s0  \f(CW\*(C`result=value,UN\*(C'\fR
517 .PP
518 We now combine them: \f(CW\*(C`result=value,UN,a,b,IF\*(C'\fR and when we fill in the
519 appropriate things for \*(L"a\*(R" and \*(L"b\*(R" we're finished:
520 .PP
521 \&\f(CW\*(C`CDEF:result=value,UN,0,value,IF\*(C'\fR
522 .PP
523 You may want to read Steve Rader's \s-1RPN\s0 guide if you have difficulties
524 with the way I explained this last example.
525 .PP
526 If you want to check this \s-1RPN\s0 expression, just mimic RRDtool behavior:
527 .PP
528 .Vb 4
529 \&   For any known value, the expression evaluates as follows:
530 \&   CDEF:result=value,UN,0,value,IF  (value,UN) is not true so it becomes 0
531 \&   CDEF:result=0,0,value,IF         "IF" will return the 3rd value
532 \&   CDEF:result=value                The known value is returned
533 \&
534 \&   For the unknown value, this happens:
535 \&   CDEF:result=value,UN,0,value,IF  (value,UN) is true so it becomes 1
536 \&   CDEF:result=1,0,value,IF         "IF" sees 1 and returns the 2nd value
537 \&   CDEF:result=0                    Zero is returned
538 .Ve
539 .PP
540 Of course, if you would like to see another value instead of zero, you
541 can use that other value.
542 .PP
543 Eventually, when all unknown data is removed from the \s-1RRD\s0, you may want
544 to remove this rule so that unknown data is properly displayed.
545 .SS "Example: better handling of unknown data, by using time"
546 .IX Subsection "Example: better handling of unknown data, by using time"
547 The above example has one drawback. If you do log unknown data in
548 your database after installing your new equipment, it will also be
549 translated into zero and therefore you won't see that there was a
550 problem. This is not good and what you really want to do is:
551 .IP "\(bu" 4
552 If there is unknown data, look at the time that this sample was taken.
553 .IP "\(bu" 4
554 If the unknown value is before time xxx, make it zero.
555 .IP "\(bu" 4
556 If it is after time xxx, leave it as unknown data.
557 .PP
558 This is doable: you can compare the time that the sample was taken
559 to some known time. Assuming you started to monitor your device on
560 Friday September 17, 1999, 00:35:57 \s-1MET\s0 \s-1DST\s0. Translate this time in seconds
561 since 1970\-01\-01 and it becomes 937'521'357. If you process unknown values
562 that were received after this time, you want to leave them unknown and
563 if they were \*(L"received\*(R" before this time, you want to translate them
564 into zero (so you can effectively ignore them while adding them to your
565 other routers counters).
566 .PP
567 Translating Friday September 17, 1999, 00:35:57 \s-1MET\s0 \s-1DST\s0 into 937'521'357 can
568 be done by, for instance, using gnu date:
569 .PP
570 .Vb 1
571 \&   date \-d "19990917 00:35:57" +%s
572 .Ve
573 .PP
574 You could also dump the database and see where the data starts to be
575 known. There are several other ways of doing this, just pick one.
576 .PP
577 Now we have to create the magic that allows us to process unknown
578 values different depending on the time that the sample was taken.
579 This is a three step process:
580 .IP "1." 4
581 If the timestamp of the value is after 937'521'357, leave it as is.
582 .IP "2." 4
583 If the value is a known value, leave it as is.
584 .IP "3." 4
585 Change the unknown value into zero.
586 .PP
587 Lets look at part one:
588 .PP
589 .Vb 1
590 \&    if (true) return the original value
591 .Ve
592 .PP
593 We rewrite this:
594 .PP
595 .Vb 2
596 \&    if (true) return "a"
597 \&    if (false) return "b"
598 .Ve
599 .PP
600 We need to calculate true or false from step 1. There is a function
601 available that returns the timestamp for the current sample. It is
602 called, how surprisingly, \*(L"\s-1TIME\s0\*(R". This time has to be compared to
603 a constant number, we need \*(L"\s-1GT\s0\*(R". The output of \*(L"\s-1GT\s0\*(R" is true or false
604 and this is good input to \*(L"\s-1IF\s0\*(R". We want \*(L"if (time > 937521357) then
605 (return a) else (return b)\*(R".
606 .PP
607 This process was already described thoroughly in the previous chapter
608 so lets do it quick:
609 .PP
610 .Vb 4
611 \&   if (x) then a else b
612 \&      where x represents "time>937521357"
613 \&      where a represents the original value
614 \&      where b represents the outcome of the previous example
615 \&
616 \&   time>937521357       \-\-> TIME,937521357,GT
617 \&
618 \&   if (x) then a else b \-\-> x,a,b,IF
619 \&   substitute x         \-\-> TIME,937521357,GT,a,b,IF
620 \&   substitute a         \-\-> TIME,937521357,GT,value,b,IF
621 \&   substitute b         \-\-> TIME,937521357,GT,value,value,UN,0,value,IF,IF
622 .Ve
623 .PP
624 We end up with:
625 \&\f(CW\*(C`CDEF:result=TIME,937521357,GT,value,value,UN,0,value,IF,IF\*(C'\fR
626 .PP
627 This looks very complex, however, as you can see, it was not too hard to
628 come up with.
629 .SS "Example: Pretending weird data isn't there"
630 .IX Subsection "Example: Pretending weird data isn't there"
631 Suppose you have a problem that shows up as huge spikes in your graph.
632 You know this happens and why, so you decide to work around the problem.
633 Perhaps you're using your network to do a backup at night and by doing
634 so you get almost 10mb/s while the rest of your network activity does
635 not produce numbers higher than 100kb/s.
636 .PP
637 There are two options:
638 .IP "1." 4
639 If the number exceeds 100kb/s it is wrong and you want it masked out
640 by changing it into unknown.
641 .IP "2." 4
642 You don't want the graph to show more than 100kb/s.
643 .PP
644 Pseudo code: if (number > 100) then unknown else number
645 or
646 Pseudo code: if (number > 100) then 100 else number.
647 .PP
648 The second \*(L"problem\*(R" may also be solved by using the rigid option of
649 RRDtool graph, however this has not the same result. In this example
650 you can end up with a graph that does autoscaling. Also, if you use
651 the numbers to display maxima they will be set to 100kb/s.
652 .PP
653 We use \*(L"\s-1IF\s0\*(R" and \*(L"\s-1GT\s0\*(R" again. \*(L"if (x) then (y) else (z)\*(R" is written
654 down as \*(L"CDEF:result=x,y,z,IF\*(R"; now fill in x, y and z.
655 For x you fill in \*(L"number greater than 100kb/s\*(R" becoming
656 \&\*(L"number,100000,GT\*(R" (kilo is 1'000 and b/s is what we measure!).
657 The \*(L"z\*(R" part is \*(L"number\*(R" in both cases and the \*(L"y\*(R" part is either
658 \&\*(L"\s-1UNKN\s0\*(R" for unknown or \*(L"100000\*(R" for 100kb/s.
659 .PP
660 The two \s-1CDEF\s0 expressions would be:
661 .PP
662 .Vb 2
663 \&    CDEF:result=number,100000,GT,UNKN,number,IF
664 \&    CDEF:result=number,100000,GT,100000,number,IF
665 .Ve
666 .SS "Example: working on a certain time span"
667 .IX Subsection "Example: working on a certain time span"
668 If you want a graph that spans a few weeks, but would only want to
669 see some routers' data for one week, you need to \*(L"hide\*(R" the rest of
670 the time frame. Don't ask me when this would be useful, it's just
671 here for the example :)
672 .PP
673 We need to compare the time stamp to a begin date and an end date.
674 Comparing isn't difficult:
675 .PP
676 .Vb 2
677 \&        TIME,begintime,GE
678 \&        TIME,endtime,LE
679 .Ve
680 .PP
681 These two parts of the \s-1CDEF\s0 produce either 0 for false or 1 for true.
682 We can now check if they are both 0 (or 1) using a few \s-1IF\s0 statements
683 but, as Wataru Satoh pointed out, we can use the \*(L"*\*(R" or \*(L"+\*(R" functions
684 as logical \s-1AND\s0 and logical \s-1OR\s0.
685 .PP
686 For \*(L"*\*(R", the result will be zero (false) if either one of the two
687 operators is zero.  For \*(L"+\*(R", the result will only be false (0) when
688 two false (0) operators will be added.  Warning: *any* number not
689 equal to 0 will be considered \*(L"true\*(R". This means that, for instance,
690 \&\*(L"\-1,1,+\*(R" (which should be \*(L"true or true\*(R") will become \s-1FALSE\s0 ...
691 In other words, use \*(L"+\*(R" only if you know for sure that you have positive
692 numbers (or zero) only.
693 .PP
694 Let's compile the complete \s-1CDEF:\s0
695 .PP
696 .Vb 2
697 \&        DEF:ds0=router1.rrd:AVERAGE
698 \&        CDEF:ds0modified=TIME,begintime,GT,TIME,endtime,LE,*,ds0,UNKN,IF
699 .Ve
700 .PP
701 This will return the value of ds0 if both comparisons return true. You
702 could also do it the other way around:
703 .PP
704 .Vb 2
705 \&        DEF:ds0=router1.rrd:AVERAGE
706 \&        CDEF:ds0modified=TIME,begintime,LT,TIME,endtime,GT,+,UNKN,ds0,IF
707 .Ve
708 .PP
709 This will return an \s-1UNKNOWN\s0 if either comparison returns true.
710 .SS "Example: You suspect to have problems and want to see unknown data."
711 .IX Subsection "Example: You suspect to have problems and want to see unknown data."
712 Suppose you add up the number of active users on several terminal servers.
713 If one of them doesn't give an answer (or an incorrect one) you get \*(L"NaN\*(R"
714 in the database (\*(L"Not a Number\*(R") and NaN is evaluated as Unknown.
715 .PP
716 In this case, you would like to be alerted to it and the sum of the
717 remaining values is of no value to you.
718 .PP
719 It would be something like:
720 .PP
721 .Vb 5
722 \&    DEF:users1=location1.rrd:onlineTS1:LAST
723 \&    DEF:users2=location1.rrd:onlineTS2:LAST
724 \&    DEF:users3=location2.rrd:onlineTS1:LAST
725 \&    DEF:users4=location2.rrd:onlineTS2:LAST
726 \&    CDEF:allusers=users1,users2,users3,users4,+,+,+
727 .Ve
728 .PP
729 If you now plot allusers, unknown data in one of users1..users4 will
730 show up as a gap in your graph. You want to modify this to show a
731 bright red line, not a gap.
732 .PP
733 Define an extra \s-1CDEF\s0 that is unknown if all is okay and is infinite if
734 there is an unknown value:
735 .PP
736 .Vb 1
737 \&    CDEF:wrongdata=allusers,UN,INF,UNKN,IF
738 .Ve
739 .PP
740 \&\*(L"allusers,UN\*(R" will evaluate to either true or false, it is the (x) part
741 of the \*(L"\s-1IF\s0\*(R" function and it checks if allusers is unknown.
742 The (y) part of the \*(L"\s-1IF\s0\*(R" function is set to \*(L"\s-1INF\s0\*(R" (which means infinity)
743 and the (z) part of the function returns \*(L"\s-1UNKN\s0\*(R".
744 .PP
745 The logic is: if (allusers == unknown) then return \s-1INF\s0 else return \s-1UNKN\s0.
746 .PP
747 You can now use \s-1AREA\s0 to display this \*(L"wrongdata\*(R" in bright red. If it
748 is unknown (because allusers is known) then the red \s-1AREA\s0 won't show up.
749 If the value is \s-1INF\s0 (because allusers is unknown) then the red \s-1AREA\s0 will
750 be filled in on the graph at that particular time.
751 .PP
752 .Vb 2
753 \&   AREA:allusers#0000FF:combined user count
754 \&   AREA:wrongdata#FF0000:unknown data
755 .Ve
756 .SS "Same example useful with STACKed data:"
757 .IX Subsection "Same example useful with STACKed data:"
758 If you use stack in the previous example (as I would do) then you don't
759 add up the values. Therefore, there is no relationship between the
760 four values and you don't get a single value to test.
761 Suppose users3 would be unknown at one point in time: users1 is plotted,
762 users2 is stacked on top of users1, users3 is unknown and therefore
763 nothing happens, users4 is stacked on top of users2.
764 Add the extra CDEFs anyway and use them to overlay the \*(L"normal\*(R" graph:
765 .PP
766 .Vb 11
767 \&   DEF:users1=location1.rrd:onlineTS1:LAST
768 \&   DEF:users2=location1.rrd:onlineTS2:LAST
769 \&   DEF:users3=location2.rrd:onlineTS1:LAST
770 \&   DEF:users4=location2.rrd:onlineTS2:LAST
771 \&   CDEF:allusers=users1,users2,users3,users4,+,+,+
772 \&   CDEF:wrongdata=allusers,UN,INF,UNKN,IF
773 \&   AREA:users1#0000FF:users at ts1
774 \&   STACK:users2#00FF00:users at ts2
775 \&   STACK:users3#00FFFF:users at ts3
776 \&   STACK:users4#FFFF00:users at ts4
777 \&   AREA:wrongdata#FF0000:unknown data
778 .Ve
779 .PP
780 If there is unknown data in one of users1..users4, the \*(L"wrongdata\*(R" \s-1AREA\s0
781 will be drawn and because it starts at the X\-axis and has infinite height
782 it will effectively overwrite the STACKed parts.
783 .PP
784 You could combine the two \s-1CDEF\s0 lines into one (we don't use \*(L"allusers\*(R")
785 if you like.  But there are good reasons for writing two \s-1CDEFS:\s0
786 .IP "\(bu" 4
787 It improves the readability of the script.
788 .IP "\(bu" 4
789 It can be used inside \s-1GPRINT\s0 to display the total number of users.
790 .PP
791 If you choose to combine them, you can substitute the \*(L"allusers\*(R" in the
792 second \s-1CDEF\s0 with the part after the equal sign from the first line:
793 .PP
794 .Vb 1
795 \&   CDEF:wrongdata=users1,users2,users3,users4,+,+,+,UN,INF,UNKN,IF
796 .Ve
797 .PP
798 If you do so, you won't be able to use these next GPRINTs:
799 .PP
800 .Vb 5
801 \&   COMMENT:"Total number of users seen"
802 \&   GPRINT:allusers:MAX:"Maximum: %6.0lf"
803 \&   GPRINT:allusers:MIN:"Minimum: %6.0lf"
804 \&   GPRINT:allusers:AVERAGE:"Average: %6.0lf"
805 \&   GPRINT:allusers:LAST:"Current: %6.0lf\en"
806 .Ve
807 .SH "The examples from the RRD graph manual page"
808 .IX Header "The examples from the RRD graph manual page"
809 .SS "Degrees Celsius vs. Degrees Fahrenheit"
810 .IX Subsection "Degrees Celsius vs. Degrees Fahrenheit"
811 To convert Celsius into Fahrenheit use the formula
812 F=9/5*C+32
813 .PP
814 .Vb 5
815 \&   rrdtool graph demo.png \-\-title="Demo Graph" \e
816 \&      DEF:cel=demo.rrd:exhaust:AVERAGE \e
817 \&      CDEF:far=9,5,/,cel,*,32,+ \e
818 \&      LINE2:cel#00a000:"D. Celsius" \e
819 \&      LINE2:far#ff0000:"D. Fahrenheit\ec"
820 .Ve
821 .PP
822 This example gets the \s-1DS\s0 called \*(L"exhaust\*(R" from database \*(L"demo.rrd\*(R"
823 and puts the values in variable \*(L"cel\*(R". The \s-1CDEF\s0 used is evaluated
824 as follows:
825 .PP
826 .Vb 10
827 \&   CDEF:far=9,5,/,cel,*,32,+
828 \&   1. push 9, push 5
829 \&   2. push function "divide" and process it
830 \&      the stack now contains 9/5
831 \&   3. push variable "cel"
832 \&   4. push function "multiply" and process it
833 \&      the stack now contains 9/5*cel
834 \&   5. push 32
835 \&   6. push function "plus" and process it
836 \&      the stack contains now the temperature in Fahrenheit
837 .Ve
838 .SS "Changing unknown into zero"
839 .IX Subsection "Changing unknown into zero"
840 .Vb 9
841 \&   rrdtool graph demo.png \-\-title="Demo Graph" \e
842 \&      DEF:idat1=interface1.rrd:ds0:AVERAGE \e
843 \&      DEF:idat2=interface2.rrd:ds0:AVERAGE \e
844 \&      DEF:odat1=interface1.rrd:ds1:AVERAGE \e
845 \&      DEF:odat2=interface2.rrd:ds1:AVERAGE \e
846 \&      CDEF:agginput=idat1,UN,0,idat1,IF,idat2,UN,0,idat2,IF,+,8,* \e
847 \&      CDEF:aggoutput=odat1,UN,0,odat1,IF,odat2,UN,0,odat2,IF,+,8,* \e
848 \&      AREA:agginput#00cc00:Input Aggregate \e
849 \&      LINE1:aggoutput#0000FF:Output Aggregate
850 .Ve
851 .PP
852 These two CDEFs are built from several functions. It helps to split
853 them when viewing what they do. Starting with the first \s-1CDEF\s0 we would
854 get:
855 .PP
856 .Vb 4
857 \& idat1,UN \-\-> a
858 \& 0        \-\-> b
859 \& idat1    \-\-> c
860 \& if (a) then (b) else (c)
861 .Ve
862 .PP
863 The result is therefore \*(L"0\*(R" if it is true that \*(L"idat1\*(R" equals \*(L"\s-1UN\s0\*(R".
864 If not, the original value of \*(L"idat1\*(R" is put back on the stack.
865 Lets call this answer \*(L"d\*(R". The process is repeated for the next
866 five items on the stack, it is done the same and will return answer
867 \&\*(L"h\*(R". The resulting stack is therefore \*(L"d,h\*(R".
868 The expression has been simplified to \*(L"d,h,+,8,*\*(R" and it will now be
869 easy to see that we add \*(L"d\*(R" and \*(L"h\*(R", and multiply the result with eight.
870 .PP
871 The end result is that we have added \*(L"idat1\*(R" and \*(L"idat2\*(R" and in the
872 process we effectively ignored unknown values. The result is multiplied
873 by eight, most likely to convert bytes/s to bits/s.
874 .SS "Infinity demo"
875 .IX Subsection "Infinity demo"
876 .Vb 10
877 \&   rrdtool graph example.png \-\-title="INF demo" \e
878 \&      DEF:val1=some.rrd:ds0:AVERAGE \e
879 \&      DEF:val2=some.rrd:ds1:AVERAGE \e
880 \&      DEF:val3=some.rrd:ds2:AVERAGE \e
881 \&      DEF:val4=other.rrd:ds0:AVERAGE \e
882 \&      CDEF:background=val4,POP,TIME,7200,%,3600,LE,INF,UNKN,IF \e
883 \&      CDEF:wipeout=val1,val2,val3,val4,+,+,+,UN,INF,UNKN,IF \e
884 \&      AREA:background#F0F0F0 \e
885 \&      AREA:val1#0000FF:Value1 \e
886 \&      STACK:val2#00C000:Value2 \e
887 \&      STACK:val3#FFFF00:Value3 \e
888 \&      STACK:val4#FFC000:Value4 \e
889 \&      AREA:whipeout#FF0000:Unknown
890 .Ve
891 .PP
892 This demo demonstrates two ways to use infinity. It is a bit tricky
893 to see what happens in the \*(L"background\*(R" \s-1CDEF\s0.
894 .PP
895 .Vb 1
896 \&   "val4,POP,TIME,7200,%,3600,LE,INF,UNKN,IF"
897 .Ve
898 .PP
899 This \s-1RPN\s0 takes the value of \*(L"val4\*(R" as input and then immediately
900 removes it from the stack using \*(L"\s-1POP\s0\*(R". The stack is now empty but
901 as a side effect we now know the time that this sample was taken.
902 This time is put on the stack by the \*(L"\s-1TIME\s0\*(R" function.
903 .PP
904 \&\*(L"\s-1TIME\s0,7200,%\*(R" takes the modulo of time and 7'200 (which is two hours).
905 The resulting value on the stack will be a number in the range from
906 0 to 7199.
907 .PP
908 For people who don't know the modulo function: it is the remainder
909 after an integer division. If you divide 16 by 3, the answer would
910 be 5 and the remainder would be 1. So, \*(L"16,3,%\*(R" returns 1.
911 .PP
912 We have the result of \*(L"\s-1TIME\s0,7200,%\*(R" on the stack, lets call this
913 \&\*(L"a\*(R". The start of the \s-1RPN\s0 has become \*(L"a,3600,LE\*(R" and this checks
914 if \*(L"a\*(R" is less or equal than \*(L"3600\*(R". It is true half of the time.
915 We now have to process the rest of the \s-1RPN\s0 and this is only a simple
916 \&\*(L"\s-1IF\s0\*(R" function that returns either \*(L"\s-1INF\s0\*(R" or \*(L"\s-1UNKN\s0\*(R" depending on the
917 time. This is returned to variable \*(L"background\*(R".
918 .PP
919 The second \s-1CDEF\s0 has been discussed earlier in this document so we
920 won't do that here.
921 .PP
922 Now you can draw the different layers. Start with the background
923 that is either unknown (nothing to see) or infinite (the whole
924 positive part of the graph gets filled).
925 .PP
926 Next you draw the data on top of this background, it will overlay
927 the background. Suppose one of val1..val4 would be unknown, in that
928 case you end up with only three bars stacked on top of each other.
929 You don't want to see this because the data is only valid when all
930 four variables are valid. This is why you use the second \s-1CDEF\s0, it
931 will overlay the data with an \s-1AREA\s0 so the data cannot be seen anymore.
932 .PP
933 If your data can also have negative values you also need to overwrite
934 the other half of your graph. This can be done in a relatively simple
935 way: what you need is the \*(L"wipeout\*(R" variable and place a negative
936 sign before it:  \*(L"CDEF:wipeout2=wipeout,\-1,*\*(R"
937 .SS "Filtering data"
938 .IX Subsection "Filtering data"
939 You may do some complex data filtering:
940 .PP
941 .Vb 1
942 \&  MEDIAN FILTER: filters shot noise
943 \&
944 \&    DEF:var=database.rrd:traffic:AVERAGE
945 \&    CDEF:prev1=PREV(var)
946 \&    CDEF:prev2=PREV(prev1)
947 \&    CDEF:prev3=PREV(prev2)
948 \&    CDEF:median=prev1,prev2,prev3,+,+,3,/
949 \&    LINE3:median#000077:filtered
950 \&    LINE1:prev2#007700:\*(Aqraw data\*(Aq
951 \&
952 \&
953 \&  DERIVATE:
954 \&
955 \&    DEF:var=database.rrd:traffic:AVERAGE
956 \&    CDEF:prev1=PREV(var)
957 \&    CDEF:time=var,POP,TIME
958 \&    CDEF:prevtime=PREV(time)
959 \&    CDEF:derivate=var,prev1,\-,time,prevtime,\-,/
960 \&    LINE3:derivate#000077:derivate
961 \&    LINE1:var#007700:\*(Aqraw data\*(Aq
962 .Ve
963 .SH "Out of ideas for now"
964 .IX Header "Out of ideas for now"
965 This document was created from questions asked by either myself or by
966 other people on the RRDtool mailing list. Please let me know if you
967 find errors in it or if you have trouble understanding it. If you
968 think there should be an addition, mail me:
969 <alex@vandenbogaerdt.nl>
970 .PP
971 Remember: \fBNo feedback equals no changes!\fR
972 .SH "SEE ALSO"
973 .IX Header "SEE ALSO"
974 The RRDtool manpages
975 .SH "AUTHOR"
976 .IX Header "AUTHOR"
977 Alex van den Bogaerdt
978 <alex@vandenbogaerdt.nl>