Code

big spell checking patch -- slif@bellsouth.net
[rrdtool.git] / doc / cdeftutorial.pod
1 =head1 NAME
3 cdeftutorial - Alex van den Bogaerdt's CDEF tutorial
5 =for html <div align="right"><a href="cdeftutorial.pdf">PDF</a> version.</div> 
7 =head1 DESCRIPTION
9 B<You provide a question and I will try to provide an answer in the next
10 release>. B<No feedback equals no changes!>
12 I<Additions to this document are also welcome.>
14 Alex van den Bogaerdt E<lt>alex@ergens.op.het.netE<gt>
16 =head2 Why this tutorial ?
18 One of the powerful parts of RRDtool is its ability to do all sorts
19 of calculations on the data retrieved from it's databases. However
20 RRDtool's many options and syntax make it difficult for the average
21 user to understand. The manuals are good at explaining what these
22 options do; however they do not (and should not) explain in detail
23 why they are useful. As with my RRDtool tutorial: if you want a
24 simple document in simple language you should read this tutorial.
25 If you are happy with the official documentation, you may find this
26 document too simple or even boring. If you do choose to read this
27 tutorial, I also expect you to have read and fully understand my
28 other tutorial. 
30 =head2 More reading
32 If you have difficulties with the way I try to explain it please read
33 Steve Rader's L<rpntutorial>. It may help you understand how this all works.
35 =head1 What are CDEFs ?
37 When retrieving data from an RRD, you are using a "DEF" to work with
38 that data. Think of it as a variable that changes over time (where
39 time is the x-axis). The value of this variable is what is found in
40 the database at that particular time and you can't do any
41 modifications on it. This is what CDEFs are for: they takes values
42 from DEFs and perform calculations on them.
44 =head1 Syntax
46    DEF:var_name_1=some.rrd:ds_name:CF
47    CDEF:var_name_2=RPN_expression
49 You first define "var_name_1" to be data collected from data source
50 "ds_name" found in RRD "some.rrd" with consolidation function "CF".
52 Assume the ifInOctets SNMP counter is saved in mrtg.rrd as the DS "in".
53 Then the following DEF defines a variable for the average of that
54 data source:
56    DEF:inbytes=mrtg.rrd:in:AVERAGE
58 Say you want to display bits per second (instead of bytes per second
59 as stored in the database.)  You have to define a calculation
60 (hence "CDEF") on variable "inbytes" and use that variable (inbits)
61 instead of the original:
63    CDEF:inbits=inbytes,8,*
65 It tells to multiply inbytes by eight to get inbits. I'll explain later
66 how this works. In the graphing or printing functions, you can now use
67 inbits where you would use inbytes otherwise.
69 Note that variable in the CDEF (inbits) must not be the same as the
70 variable (inbytes) in the DEF!
72 =head1 RPN-expressions
74 RPN is short-hand for Reverse Polish Notation. It works as follows.
75 You put the variables or numbers on a stack. You also put operations
76 (things-to-do) on the stack and this stack is then processed. The result
77 will be placed on the stack. At the end, there should be exactly one
78 number left: the outcome of the series of operations. If there is not
79 exactly one number left, RRDtool will complain loudly.
81 Above multiplication by eight will look like:
83 =over 4
85 =item 1.
87 Start with an empty stack
89 =item 2.
91 Put the content of variable inbytes on the stack
93 =item 3.
95 Put the number eight on the stack
97 =item 4.
99 Put the operation multiply on the stack
101 =item 5.
103 Process the stack
105 =item 6.
107 Retrieve the value from the stack and put it in variable inbits
109 =back
111 We will now do an example with real numbers. Suppose the variable
112 inbytes would have value 10, the stack would be:
114 =over 4
116 =item 1.
118 ||
120 =item 2.
122 |10|
124 =item 3.
126 |10|8|
128 =item 4.
130 |10|8|*|
132 =item 5.
134 |80|
136 =item 6.
138 ||
140 =back 
142 Processing the stack (step 5) will retrieve one value from the stack
143 (from the right at step 4). This is the operation multiply and this
144 takes two values off the stack as input. The result is put back on the
145 stack (the value 80 in this case). For multiplication the order doesn't
146 matter but for other operations like subtraction and division it does.
147 Generally speaking you have the following order:
149    y = A - B  -->  y=minus(A,B)  -->  CDEF:y=A,B,-
151 This is not very intuitive (at least most people don't think so). For
152 the function f(A,B) you reverse the position of "f" but you do not
153 reverse the order of the variables. 
155 =head1 Converting your wishes to RPN
157 First, get a clear picture of what you want to do. Break down the problem
158 in smaller portions until they cannot be split anymore. Then it is rather
159 simple to convert your ideas into RPN.
161 Suppose you have several RRDs and would like to add up some counters in
162 them. These could be, for instance, the counters for every WAN link you
163 are monitoring.
165 You have:
167    router1.rrd with link1in link2in
168    router2.rrd with link1in link2in
169    router3.rrd with link1in link2in
171 Suppose you would like to add up all these counters, except for link2in
172 inside router2.rrd. You need to do:
174 (in this example, "router1.rrd:link1in" means the DS link1in inside the
175 RRD router1.rrd)
177    router1.rrd:link1in
178    router1.rrd:link2in
179    router2.rrd:link1in
180    router3.rrd:link1in
181    router3.rrd:link2in 
182    --------------------   +
183    (outcome of the sum)
185 As a mathematical function, this could be written:
187 C<add(router1.rrd:link1in , router1.rrd:link2in , router2.rrd:link1in , router3.rrd:link1in , router3.rrd:link2.in)>
189 With RRDtool and RPN, first, define the inputs:
191    DEF:a=router1.rrd:link1in:AVERAGE
192    DEF:b=router1.rrd:link2in:AVERAGE
193    DEF:c=router2.rrd:link1in:AVERAGE
194    DEF:d=router3.rrd:link1in:AVERAGE
195    DEF:e=router3.rrd:link2in:AVERAGE
197 Now, the mathematical function becomes: C<add(a,b,c,d,e)>
199 In RPN, there's no operator that sums more than two values so you need
200 to do several additions. You add a and b, add c to the result, add d
201 to the result and add e to the result.
203    push a:         a     stack contains the value of a
204    push b and add: b,+   stack contains the result of a+b
205    push c and add: c,+   stack contains the result of a+b+c
206    push d and add: d,+   stack contains the result of a+b+c+d
207    push e and add: e,+   stack contains the result of a+b+c+d+e
209 What was calculated here would be written down as:
211    ( ( ( (a+b) + c) + d) + e) >
213 This is in RPN:  C<CDEF:result=a,b,+,c,+,d,+,e,+>
215 This is correct but it can be made more clear to humans. It does
216 not matter if you add a to b and then add c to the result or first
217 add b to c and then add a to the result. This makes it possible to
218 rewrite the RPN into C<CDEF:result=a,b,c,d,e,+,+,+,+> which is
219 evaluated differently: 
221    push value of variable a on the stack: a
222    push value of variable b on the stack: a b
223    push value of variable c on the stack: a b c
224    push value of variable d on the stack: a b c d
225    push value of variable e on the stack: a b c d e
226    push operator + on the stack:          a b c d e +
227    and process it:                        a b c P   (where P == d+e)
228    push operator + on the stack:          a b c P +
229    and process it:                        a b Q     (where Q == c+P)
230    push operator + on the stack:          a b Q +
231    and process it:                        a R       (where R == b+Q)
232    push operator + on the stack:          a R +
233    and process it:                        S         (where S == a+R)
235 As you can see the RPN expression C<a,b,c,d,e,+,+,+,+,+> will evaluate in
236 C<((((d+e)+c)+b)+a)> and it has the same outcome as C<a,b,+,c,+,d,+,e,+> 
237 According to Steve Rader this is called the commutative law of addition
238 but you may forget this right away, as long as you remember what it
239 represents.
241 Now look at an expression that contains a multiplication:
243 First in normal math: C<let result = a+b*c>. In this case you can't
244 choose the order yourself, you have to start with the multiplication
245 and then add a to it. You may alter the position of b and c, you may
246 not alter the position of a and b. 
248 You have to take this in consideration when converting this expression
249 into RPN. Read it as: "Add the outcome of b*c to a" and then it is
250 easy to write the RPN expression: C<result=a,b,c,*,+>
251 Another expression that would return the same: C<result=b,c,*,a,+>
253 In normal math, you may encounter something like "a*(b+c)" and this
254 can also be converted into RPN. The parenthesis just tell you to first
255 add b and c, and then multiply a with the result. Again, now it is
256 easy to write it in RPN: C<result=a,b,c,+,*>. Note that this is very
257 similar to one of the expressions in the previous paragraph, only the
258 multiplication and the addition changed places.
260 When you have problems with RPN or when RRDtool is complaining, it's 
261 usually a Good Thing to write down the stack on a piece of paper
262 and see what happens. Have the manual ready and pretend to be RRDtool.
263 Just do all the math by hand to see what happens, I'm sure this will
264 solve most, if not all, problems you encounter.
266 =head1 Some special numbers
268 =head2 The unknown value
270 Sometimes collecting your data will fail. This can be very common,
271 especially when querying over busy links. RRDtool can be configured
272 to allow for one (or even more) unknown value and calculate the missing
273 update. You can, for instance, query your device every minute. This is
274 creating one so called PDP or primary data point per minute. If you
275 defined your RRD to contain an RRA that stores 5-minute values, you need
276 five of those PDPs to create one CDP (consolidated data point).
277 These PDPs can become unknown in two cases:
279 =over 4
281 =item 1.
283 The updates are too far apart. This is tuned using the "heartbeat" setting
285 =item 2.
287 The update was set to unknown on purpose by inserting no value (using the
288 template option) or by using "U" as the value to insert.
290 =back
292 When a CDP is calculated, another mechanism determines if this CDP is valid
293 or not. If there are too many PDPs unknown, the CDP is unknown as well.
294 This is determined by the xff factor. Please note that one unknown counter
295 update can result in two unknown PDPs! If you only allow for one unknown
296 PDP per CDP, this makes the CDP go unknown!
298 Suppose the counter increments with one per second and you retrieve it
299 every minute:
301    counter value    resulting rate
302    10000
303    10060            1; (10060-10000)/60 == 1
304    10120            1; (10120-10060)/60 == 1
305    unknown          unknown; you don't know the last value
306    10240            unknown; you don't know the previous value
307    10300            1; (10300-10240)/60 == 1
309 If the CDP was to be calculated from the last five updates, it would get
310 two unknown PDPs and three known PDPs. If xff would have been set to 0.5
311 which by the way is a commonly used factor, the CDP would have a known
312 value of 1. If xff would have been set to 0.2 then the resulting CDP
313 would be unknown.
315 You have to decide the proper values for heartbeat, number of PDPs per
316 CDP and the xff factor. As you can see from the previous text they define
317 the behavior of your RRA.
319 =head2 Working with unknown data in your database
321 As you have read in the previous chapter, entries in an RRA can be
322 set to the unknown value. If you do calculations with this type of
323 value, the result has to be unknown too. This means that an expression
324 such as C<result=a,b,+> will be unknown if either a or b is unknown.
325 It would be wrong to just ignore the unknown value and return the value
326 of the other parameter. By doing so, you would assume "unknown" means "zero"
327 and this is not true.
329 There has been a case where somebody was collecting data for over a year.
330 A new piece of equipment was installed, a new RRD was created and the
331 scripts were changed to add a counter from the old database and a counter
332 from the new database. The result was disappointing, a large part of
333 the statistics seemed to have vanished mysteriously ...
334 They of course didn't, values from the old database (known values) were
335 added to values from the new database (unknown values) and the result was
336 unknown.
338 In this case, it is fairly reasonable to use a CDEF that alters unknown
339 data into zero. The counters of the device were unknown (after all, it
340 wasn't installed yet!) but you know that the data rate through the device
341 had to be zero (because of the same reason: it was not installed).
343 There are some examples further on that make this change.
345 =head2 Infinity
347 Infinite data is another form of a special number. It cannot be graphed
348 because by definition you would never reach the infinite value. You could
349 think of positive and negative infinity (I'm not sure if mathematicians
350 will agree) depending on the position relative to zero.
352 RRDtool is capable of representing (-not- graphing!) infinity by stopping
353 at its current maximum (for positive infinity) or minimum (for negative
354 infinity) without knowing this maximum (minimum).
356 Infinity in RRDtool is mostly used to draw an AREA without knowing its
357 vertical dimensions. You can think of it as drawing an AREA with an
358 infinite height and displaying only the part that is visible in the
359 current graph. This is probably a good way to approximate infinity
360 and it sure allows for some neat tricks. See below for examples.
362 =head2 Working with unknown data and infinity
364 Sometimes you would like to discard unknown data and pretend it is zero
365 (or any other value for that matter) and sometimes you would like to
366 pretend that known data is unknown (to discard known-to-be-wrong data).
367 This is why CDEFs have support for unknown data. There are also examples
368 available that show unknown data by using infinity.
370 =head1 Some examples
372 =head2 Example: using a recently created RRD
374 You are keeping statistics on your router for over a year now. Recently
375 you installed an extra router and you would like to show the combined
376 throughput for these two devices.
378 If you just add up the counters from router.rrd and router2.rrd, you
379 will add known data (from router.rrd) to unknown data (from router2.rrd) for
380 the bigger part of your stats. You could solve this in a few ways:
382 =over 4
384 =item *
386 While creating the new database, fill it with zeros from the start to now.
387 You have to make the database start at or before the least recent time in
388 the other database.
390 =item *
392 Alternately you could use CDEF and alter unknown data to zero.
394 =back
396 Both methods have their pros and cons. The first method is troublesome and
397 if you want to do that you have to figure it out yourself. It is not
398 possible to create a database filled with zeros, you have to put them in
399 on purpose. Implementing the second method is described next:
401 What we want is: "if the value is unknown, replace it with zero". This
402 could be written in pseudo-code as:  if (value is unknown) then (zero)
403 else (value). When reading the L<rrdgraph> manual you notice the "UN"
404 function that returns zero or one. You also notice the "IF" function
405 that takes zero or one as input.
407 First look at the "IF" function. It takes three values from the stack,
408 the first value is the decision point, the second value is returned to
409 the stack if the evaluation is "true" and if not, the third value is
410 returned to the stack. We want the "UN" function to decide what happens
411 so we combine those two functions in one CDEF.
413 Lets write down the two possible paths for the "IF" function:
415    if true  return a
416    if false return b
418 In RPN:  C<result=x,a,b,IF> where "x" is either true or false.
420 Now we have to fill in "x", this should be the "(value is unknown)" part
421 and this is in RPN:  C<result=value,UN>
423 We now combine them: C<result=value,UN,a,b,IF> and when we fill in the
424 appropriate things for "a" and "b" we're finished:
426 C<CDEF:result=value,UN,0,value,IF>
428 You may want to read Steve Rader's RPN guide if you have difficulties
429 with the way I explained this last example.
431 If you want to check this RPN expression, just mimic RRDtool behavior:
433    For any known value, the expression evaluates as follows:
434    CDEF:result=value,UN,0,value,IF  (value,UN) is not true so it becomes 0
435    CDEF:result=0,0,value,IF         "IF" will return the 3rd value
436    CDEF:result=value                The known value is returned
438    For the unknown value, this happens:
439    CDEF:result=value,UN,0,value,IF  (value,UN) is true so it becomes 1
440    CDEF:result=1,0,value,IF         "IF" sees 1 and returns the 2nd value
441    CDEF:result=0                    Zero is returned
443 Of course, if you would like to see another value instead of zero, you
444 can use that other value.
446 Eventually, when all unknown data is removed from the RRD, you may want
447 to remove this rule so that unknown data is properly displayed.
449 =head2 Example: better handling of unknown data, by using time
451 Above example has one drawback. If you do log unknown data in
452 your database after installing your new equipment, it will also be
453 translated into zero and therefore you won't see that there was a
454 problem. This is not good and what you really want to do is:
456 =over 4
458 =item *
460 If there is unknown data, look at the time that this sample was taken
462 =item *
464 If the unknown value is before time xxx, make it zero
466 =item *
468 If it is after time xxx, leave it as unknown data
470 =back
472 This is doable: you can compare the time that the sample was taken
473 to some known time. Assuming you started to monitor your device on
474 Friday September 17, 00:35:57 MET DST. Translate this time in seconds
475 since 1970-01-01 and it becomes 937521357. If you process unknown values
476 that were received after this time, you want to leave them unknown and
477 if they were "received" before this time, you want to translate them
478 into zero (so you can effectively ignore them while adding them to your
479 other routers counters).
481 Translating Friday September 17, 00:35:57 MET DST into 937521357 can
482 be done by, for instance, using gnu date:
484    date -d "19990917 00:35:57" +%s
486 You could also dump the database and see where the data starts to be
487 known. There are several other ways of doing this, just pick one.
489 Now we have to create the magic that allows us to process unknown
490 values different depending on the time that the sample was taken.
491 This is a three step process:
493 =over 4
495 =item 1.
497 If the timestamp of the value is after 937521357, leave it as is
499 =item 2.
501 If the value is a known value, leave it as is
503 =item 3.
505 Change the unknown value into zero.
507 =back
509 Lets look at part one:
511     if (true) return the original value
513 We rewrite this:
515     if (true) return "a"
516     if (false) return "b"
518 We need to calculate true or false from step 1. There is a function
519 available that returns the timestamp for the current sample. It is
520 called, how surprisingly, "TIME". This time has to be compared to
521 a constant number, we need "GT". The output of "GT" is true or false
522 and this is good input to "IF". We want "if (time > 937521357) then
523 (return a) else (return b)".
525 This process was already described thoroughly in the previous chapter
526 so lets do it quick:
528    if (x) then a else b
529       where x represents "time>937521357"
530       where a represents the original value
531       where b represents the outcome of the previous example
532       
533    time>937521357       --> TIME,937521357,GT
535    if (x) then a else b --> x,a,b,IF
536    substitute x         --> TIME,937521357,GT,a,b,IF
537    substitute a         --> TIME,937521357,GT,value,b,IF
538    substitute b         --> TIME,937521357,GT,value,value,UN,0,value,IF,IF
540 We end up with:
541 C<CDEF:result=TIME,937521357,GT,value,value,UN,0,value,IF,IF>
543 This looks very complex however as you can see it was not too hard to
544 come up with.
546 =head2 Example: Pretending weird data isn't there
548 Suppose you have a problem that shows up as huge spikes in your graph.
549 You know this happens and why so you decide to work around the problem.
550 Perhaps you're using your network to do a backup at night and by doing
551 so you get almost 10mb/s while the rest of your network activity does
552 not produce numbers higher than 100kb/s.
554 There are two options:
556 =over 4
558 =item 1.
560 If the number exceeds 100kb/s it is wrong and you want it masked out
561 by changing it into unknown
563 =item 2.
565 You don't want the graph to show more than 100kb/s
567 =back
569 Pseudo code: if (number > 100) then unknown else number
570 or
571 Pseudo code: if (number > 100) then 100 else number.
573 The second "problem" may also be solved by using the rigid option of
574 RRDtool graph, however this has not the same result. In this example
575 you can end up with a graph that does autoscaling. Also, if you use
576 the numbers to display maxima they will be set to 100kb/s.
578 We use "IF" and "GT" again. "if (x) then (y) else (z)" is written
579 down as "CDEF:result=x,y,z,IF"; now fill in x, y and z.
580 For x you fill in "number greater than 100kb/s" becoming
581 "number,100000,GT" (kilo is 1000 and b/s is what we measure!).
582 The "z" part is "number" in both cases and the "y" part is either
583 "UNKN" for unknown or "100000" for 100kb/s.
585 The two CDEF expressions would be:
587     CDEF:result=number,100000,GT,UNKN,number,IF
588     CDEF:result=number,100000,GT,100000,number,IF
590 =head2 Example: working on a certain time span
592 If you want a graph that spans a few weeks, but would only want to
593 see some routers data for one week, you need to "hide" the rest of
594 the time frame. Don't ask me when this would be useful, it's just
595 here for the example :)
597 We need to compare the time stamp to a begin date and an end date.
598 Comparing isn't difficult:
600         TIME,begintime,GE
601         TIME,endtime,LE
603 These two parts of the CDEF produce either 0 for false or 1 for true.
604 We can now check if they are both 0 (or 1) using a few IF statements
605 but, as Wataru Satoh pointed out, we can use the "*" or "+" functions
606 as logical AND and logical OR.
608 For "*", the result will be zero (false) if either one of the two
609 operators is zero.  For "+", the result will only be false (0) when
610 two false (0) operators will be added.  Warning: *any* number not
611 equal to 0 will be considered "true". This means that, for instance,
612 "-1,1,+" (which should be "true or true") will become FALSE ...
613 In other words, use "+" only if you know for sure that you have positive
614 numbers (or zero) only.
616 Let's compile the complete CDEF:
618         DEF:ds0=router1.rrd:AVERAGE
619         CDEF:ds0modified=TIME,begintime,GE,TIME,endtime,LE,*,UNKN,ds0,IF
621 This will return the value of ds0 if both comparisons return true. You
622 could also do it the other way around:
624         DEF:ds0=router1.rrd:AVERAGE
625         CDEF:ds0modified=TIME,begintime,LT,TIME,endtime,GT,+,UNKN,ds0,IF
627 This will return an UNKNOWN if either comparison returns true.
629 =head2 Example: You suspect to have problems and want to see unknown data.
631 Suppose you add up the number of active users on several terminal servers.
632 If one of them doesn't give an answer (or an incorrect one) you get "NaN"
633 in the database ("Not a Number") and NaN is evaluated as Unknown.
635 In this case, you would like to be alerted to it and the sum of the
636 remaining values is of no value to you.
638 It would be something like:
640     DEF:users1=location1.rrd:onlineTS1:LAST
641     DEF:users2=location1.rrd:onlineTS2:LAST
642     DEF:users3=location2.rrd:onlineTS1:LAST
643     DEF:users4=location2.rrd:onlineTS2:LAST
644     CDEF:allusers=users1,users2,users3,users4,+,+,+
646 If you now plot allusers, unknown data in one of users1..users4 will
647 show up as a gap in your graph. You want to modify this to show a
648 bright red line, not a gap.
650 Define an extra CDEF that is unknown if all is okay and is infinite if
651 there is an unknown value:
653     CDEF:wrongdata=allusers,UN,INF,UNKN,IF
655 "allusers,UN" will evaluate to either true or false, it is the (x) part
656 of the "IF" function and it checks if allusers is unknown.
657 The (y) part of the "IF" function is set to "INF" (which means infinity)
658 and the (z) part of the function returns "UNKN".
660 The logic is: if (allusers == unknown) then return INF else return UNKN.
662 You can now use AREA to display this "wrongdata" in bright red. If it
663 is unknown (because allusers is known) then the red AREA won't show up.
664 If the value is INF (because allusers is unknown) then the red AREA will
665 be filled in on the graph at that particular time.
667    AREA:allusers#0000FF:combined user count
668    AREA:wrongdata#FF0000:unknown data
670 =head2 Same example useful with STACKed data:
672 If you use stack in the previous example (as I would do) then you don't
673 add up the values. Therefore, there is no relationship between the
674 four values and you don't get a single value to test.
675 Suppose users3 would be unknown at one point in time: users1 is plotted,
676 users2 is stacked on top of users1, users3 is unknown and therefore
677 nothing happens, users4 is stacked on top of users2.
678 Add the extra CDEFs anyway and use them to overlay the "normal" graph:
680    DEF:users1=location1.rrd:onlineTS1:LAST
681    DEF:users2=location1.rrd:onlineTS2:LAST
682    DEF:users3=location2.rrd:onlineTS1:LAST
683    DEF:users4=location2.rrd:onlineTS2:LAST
684    CDEF:allusers=users1,users2,users3,users4,+,+,+
685    CDEF:wrongdata=allusers,UN,INF,UNKN,IF
686    AREA:users1#0000FF:users at ts1
687    STACK:users2#00FF00:users at ts2
688    STACK:users3#00FFFF:users at ts3
689    STACK:users4#FFFF00:users at ts4
690    AREA:wrongdata#FF0000:unknown data
692 If there is unknown data in one of users1..users4, the "wrongdata" AREA
693 will be drawn and because it starts at the X-axis and has infinite height
694 it will effectively overwrite the STACKed parts.
696 You could combine the two CDEF lines into one (we don't use "allusers")
697 if you like.  But there are good reasons for writing two CDEFS:
699 =over 4
701 =item *
703 It improves the readability of the script
705 =item *
707 It can be used inside GPRINT to display the total number of users
709 =back
711 If you choose to combine them, you can substitute the "allusers" in the
712 second CDEF with the part after the equal sign from the first line:
714    CDEF:wrongdata=users1,users2,users3,users4,+,+,+,UN,INF,UNKN,IF
716 If you do so, you won't be able to use these next GPRINTs:
718    COMMENT:"Total number of users seen"
719    GPRINT:allusers:MAX:"Maximum: %6.0lf"
720    GPRINT:allusers:MIN:"Minimum: %6.0lf"
721    GPRINT:allusers:AVERAGE:"Average: %6.0lf"
722    GPRINT:allusers:LAST:"Current: %6.0lf\n"
724 =head1 The examples from the RRD graph manual page
726 =head2 Degrees Celsius vs. Degrees Fahrenheit
728    rrdtool graph demo.png --title="Demo Graph" \
729       DEF:cel=demo.rrd:exhaust:AVERAGE \
730       CDEF:far=cel,32,-,0.55555,* \
731       LINE2:cel#00a000:"D. Celsius" \
732       LINE2:far#ff0000:"D. Fahrenheit\c"
734 This example gets the DS called "exhaust" from database "demo.rrd"
735 and puts the values in variable "cel". The CDEF used is evaluated
736 as follows:
738    CDEF:far=cel,32,-,0.5555,*
739    1. push variable "cel"
740    2. push 32
741    3. push function "minus" and process it
742       The stack now contains values that are 32 less than "cel"
743    4. push 0.5555
744    5. push function "multiply" and process it
745    6. the resulting value is now "(cel-32)*0.55555"
747 Note that if you take the Celsius to Fahrenheit function you should
748 be doing "5/9*(cel-32)" so 0.55555 is not exactly correct. It is close
749 enough for this purpose and it saves a calculation.
751 =head2 Changing unknown into zero
753    rrdtool graph demo.png --title="Demo Graph" \
754       DEF:idat1=interface1.rrd:ds0:AVERAGE \
755       DEF:idat2=interface2.rrd:ds0:AVERAGE \
756       DEF:odat1=interface1.rrd:ds1:AVERAGE \
757       DEF:odat2=interface2.rrd:ds1:AVERAGE \
758       CDEF:agginput=idat1,UN,0,idat1,IF,idat2,UN,0,idat2,IF,+,8,* \
759       CDEF:aggoutput=odat1,UN,0,odat1,IF,odat2,UN,0,odat2,IF,+,8,* \
760       AREA:agginput#00cc00:Input Aggregate \
761       LINE1:aggoutput#0000FF:Output Aggregate
763 These two CDEFs are built from several functions. It helps to
764 split them when viewing what they do.
765 Starting with the first CDEF we would get:
766       idat1,UN --> a
767       0        --> b
768       idat1    --> c
769       if (a) then (b) else (c)
770 The result is therefore "0" if it is true that "idat1" equals "UN".
771 If not, the original value of "idat1" is put back on the stack.
772 Lets call this answer "d". The process is repeated for the next
773 five items on the stack, it is done the same and will return answer
774 "h". The resulting stack is therefore "d,h".
775 The expression has been simplified to "d,h,+,8,*" and it will now be
776 easy to see that we add "d" and "h", and multiply the result with eight.
778 The end result is that we have added "idat1" and "idat2" and in the
779 process we effectively ignored unknown values. The result is multiplied
780 by eight, most likely to convert bytes/s to bits/s.
782 =head2 Infinity demo
784    rrdtool graph example.png --title="INF demo" \
785       DEF:val1=some.rrd:ds0:AVERAGE \
786       DEF:val2=some.rrd:ds1:AVERAGE \
787       DEF:val3=some.rrd:ds2:AVERAGE \
788       DEF:val4=other.rrd:ds0:AVERAGE \
789       CDEF:background=val4,POP,TIME,7200,%,3600,LE,INF,UNKN,IF \
790       CDEF:wipeout=val1,val2,val3,val4,+,+,+,UN,INF,UNKN,IF \
791       AREA:background#F0F0F0 \
792       AREA:val1#0000FF:Value1 \
793       STACK:val2#00C000:Value2 \
794       STACK:val3#FFFF00:Value3 \
795       STACK:val4#FFC000:Value4 \
796       AREA:whipeout#FF0000:Unknown
798 This demo demonstrates two ways to use infinity. It is a bit tricky
799 to see what happens in the "background" CDEF.
801    "val4,POP,TIME,7200,%,3600,LE,INF,UNKN,IF"
803 This RPN takes the value of "val4" as input and then immediately
804 removes it from the stack using "POP". The stack is now empty but
805 as a side result we now know the time that this sample was taken.
806 This time is put on the stack by the "TIME" function.
808 "TIME,7200,%" takes the modulo of time and 7200 (which is two hours).
809 The resulting value on the stack will be a number in the range from
810 0 to 7199.
812 For people who don't know the modulo function: it is the remainder
813 after an integer division. If you divide 16 by 3, the answer would
814 be 5 and the remainder would be 1. So, "16,3,%" returns 1.
816 We have the result of "TIME,7200,%" on the stack, lets call this
817 "a". The start of the RPN has become "a,3600,LE" and this checks
818 if "a" is less or equal than "3600". It is true half of the time.
819 We now have to process the rest of the RPN and this is only a simple
820 "IF" function that returns either "INF" or "UNKN" depending on the
821 time. This is returned to variable "background".
823 The second CDEF has been discussed earlier in this document so we
824 won't do that here.
826 Now you can draw the different layers. Start with the background
827 that is either unknown (nothing to see) or infinite (the whole
828 positive part of the graph gets filled).
829 Next you draw the data on top of this background. It will overlay
830 the background. Suppose one of val1..val4 would be unknown, in that
831 case you end up with only three bars stacked on top of each other.
832 You don't want to see this because the data is only valid when all
833 four variables are valid. This is why you use the second CDEF, it
834 will overlay the data with an AREA so the data cannot be seen anymore.
836 If your data can also have negative values you also need to overwrite
837 the other half of your graph. This can be done in a relatively simple
838 way: what you need is the "wipeout" variable and place a negative
839 sign before it:  "CDEF:wipeout2=wipeout,-1,*"
840     
841 =head2 Filtering data
843 You may do some complex data filtering:
845   MEDIAN FILTER: filters shot noise
847     DEF:var=database.rrd:traffic:AVERAGE
848     CDEF:prev1=PREV(var)
849     CDEF:prev2=PREV(prev1)
850     CDEF:prev3=PREV(prev2)
851     CDEF:median=prev1,prev2,prev3,+,+,3,/
852     LINE3:median#000077:filtered
853     LINE1:prev2#007700:'raw data'
856   DERIVATE:
858     DEF:var=database.rrd:traffic:AVERAGE
859     CDEF:prev1=PREV(var)
860     CDEF:time=TIME
861     CDEF:prevtime=PREV(time)
862     CDEF:derivate=var,prev1,-,time,prevtime,-,/
863     LINE3:derivate#000077:derivate
864     LINE1:var#007700:'raw data'
867 =head1 Out of ideas for now
869 This document was created from questions asked by either myself or
870 by other people on the list. Please let me know if you find errors
871 in it or if you have trouble understanding it. If you think there
872 should be an addition, mail me: E<lt>alex@ergens.op.het.netE<gt>
874 Remember: B<No feedback equals no changes!>
876 =head1 SEE ALSO
878 The RRDtool manpages
880 =head1 AUTHOR
882 Alex van den Bogaerdt
883 E<lt>alex@ergens.op.het.netE<gt>