1CDEFTUTORIAL(1)                     rrdtool                    CDEFTUTORIAL(1)
2
3
4

NAME

6       cdeftutorial - Alex van den Bogaerdt's CDEF tutorial
7

DESCRIPTION

9       Intention of this document: to provide some examples of the commonly
10       used parts of RRDtool's CDEF language.
11
12       If you think some important feature is not explained properly, and if
13       adding it to this document would benefit most users, please do ask me
14       to add it.  I will then try to provide an answer in the next release of
15       this tutorial.  No feedback equals no changes! Additions to this
16       document are also welcome.  -- Alex van den Bogaerdt
17       <alex@vandenbogaerdt.nl>
18
19   Why this tutorial?
20       One of the powerful parts of RRDtool is its ability to do all sorts of
21       calculations on the data retrieved from its databases. However,
22       RRDtool's many options and syntax make it difficult for the average
23       user to understand. The manuals are good at explaining what these
24       options do; however they do not (and should not) explain in detail why
25       they are useful. As with my RRDtool tutorial: if you want a simple
26       document in simple language you should read this tutorial.  If you are
27       happy with the official documentation, you may find this document too
28       simple or even boring. If you do choose to read this tutorial, I also
29       expect you to have read and fully understand my other tutorial.
30
31   More reading
32       If you have difficulties with the way I try to explain it please read
33       Steve Rader's rpntutorial. It may help you understand how this all
34       works.
35

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 time
39       is the x-axis). The value of this variable is what is found in the
40       database at that particular time and you can't do any modifications on
41       it. This is what CDEFs are for: they takes values from DEFs and perform
42       calculations on them.
43

Syntax

45          DEF:var_name_1=some.rrd:ds_name:CF
46          CDEF:var_name_2=RPN_expression
47
48       You first define "var_name_1" to be data collected from data source
49       "ds_name" found in RRD "some.rrd" with consolidation function "CF".
50
51       Assume the ifInOctets SNMP counter is saved in mrtg.rrd as the DS "in".
52       Then the following DEF defines a variable for the average of that data
53       source:
54
55          DEF:inbytes=mrtg.rrd:in:AVERAGE
56
57       Say you want to display bits per second (instead of bytes per second as
58       stored in the database.)  You have to define a calculation (hence
59       "CDEF") on variable "inbytes" and use that variable (inbits) instead of
60       the original:
61
62          CDEF:inbits=inbytes,8,*
63
64       This tells RRDtool to multiply inbytes by eight to get inbits. I'll
65       explain later how this works. In the graphing or printing functions,
66       you can now use inbits where you would use inbytes otherwise.
67
68       Note that the variable name used in the CDEF (inbits) must not be the
69       same as the variable named in the DEF (inbytes)!
70

RPN-expressions

72       RPN is short-hand for Reverse Polish Notation. It works as follows.
73       You put the variables or numbers on a stack. You also put operations
74       (things-to-do) on the stack and this stack is then processed. The
75       result will be placed on the stack. At the end, there should be exactly
76       one number left: the outcome of the series of operations. If there is
77       not exactly one number left, RRDtool will complain loudly.
78
79       Above multiplication by eight will look like:
80
81       1.  Start with an empty stack
82
83       2.  Put the content of variable inbytes on the stack
84
85       3.  Put the number eight on the stack
86
87       4.  Put the operation multiply on the stack
88
89       5.  Process the stack
90
91       6.  Retrieve the value from the stack and put it in variable inbits
92
93       We will now do an example with real numbers. Suppose the variable
94       inbytes would have value 10, the stack would be:
95
96       1.  ||
97
98       2.  |10|
99
100       3.  |10|8|
101
102       4.  |10|8|*|
103
104       5.  |80|
105
106       6.  ||
107
108       Processing the stack (step 5) will retrieve one value from the stack
109       (from the right at step 4). This is the operation multiply and this
110       takes two values off the stack as input. The result is put back on the
111       stack (the value 80 in this case). For multiplication the order doesn't
112       matter, but for other operations like subtraction and division it does.
113       Generally speaking you have the following order:
114
115          y = A - B  -->  y=minus(A,B)  -->  CDEF:y=A,B,-
116
117       This is not very intuitive (at least most people don't think so). For
118       the function f(A,B) you reverse the position of "f", but you do not
119       reverse the order of the variables.
120

Converting your wishes to RPN

122       First, get a clear picture of what you want to do. Break down the
123       problem in smaller portions until they cannot be split anymore. Then it
124       is rather simple to convert your ideas into RPN.
125
126       Suppose you have several RRDs and would like to add up some counters in
127       them. These could be, for instance, the counters for every WAN link you
128       are monitoring.
129
130       You have:
131
132          router1.rrd with link1in link2in
133          router2.rrd with link1in link2in
134          router3.rrd with link1in link2in
135
136       Suppose you would like to add up all these counters, except for link2in
137       inside router2.rrd. You need to do:
138
139       (in this example, "router1.rrd:link1in" means the DS link1in inside the
140       RRD router1.rrd)
141
142          router1.rrd:link1in
143          router1.rrd:link2in
144          router2.rrd:link1in
145          router3.rrd:link1in
146          router3.rrd:link2in
147          --------------------   +
148          (outcome of the sum)
149
150       As a mathematical function, this could be written:
151
152       "add(router1.rrd:link1in , router1.rrd:link2in , router2.rrd:link1in ,
153       router3.rrd:link1in , router3.rrd:link2.in)"
154
155       With RRDtool and RPN, first, define the inputs:
156
157          DEF:a=router1.rrd:link1in:AVERAGE
158          DEF:b=router1.rrd:link2in:AVERAGE
159          DEF:c=router2.rrd:link1in:AVERAGE
160          DEF:d=router3.rrd:link1in:AVERAGE
161          DEF:e=router3.rrd:link2in:AVERAGE
162
163       Now, the mathematical function becomes: "add(a,b,c,d,e)"
164
165       In RPN, there's no operator that sums more than two values so you need
166       to do several additions. You add a and b, add c to the result, add d to
167       the result and add e to the result.
168
169          push a:         a     stack contains the value of a
170          push b and add: b,+   stack contains the result of a+b
171          push c and add: c,+   stack contains the result of a+b+c
172          push d and add: d,+   stack contains the result of a+b+c+d
173          push e and add: e,+   stack contains the result of a+b+c+d+e
174
175       What was calculated here would be written down as:
176
177          ( ( ( (a+b) + c) + d) + e) >
178
179       This is in RPN:  "CDEF:result=a,b,+,c,+,d,+,e,+"
180
181       This is correct but it can be made more clear to humans. It does not
182       matter if you add a to b and then add c to the result or first add b to
183       c and then add a to the result. This makes it possible to rewrite the
184       RPN into "CDEF:result=a,b,c,d,e,+,+,+,+" which is evaluated
185       differently:
186
187          push value of variable a on the stack: a
188          push value of variable b on the stack: a b
189          push value of variable c on the stack: a b c
190          push value of variable d on the stack: a b c d
191          push value of variable e on the stack: a b c d e
192          push operator + on the stack:          a b c d e +
193          and process it:                        a b c P   (where P == d+e)
194          push operator + on the stack:          a b c P +
195          and process it:                        a b Q     (where Q == c+P)
196          push operator + on the stack:          a b Q +
197          and process it:                        a R       (where R == b+Q)
198          push operator + on the stack:          a R +
199          and process it:                        S         (where S == a+R)
200
201       As you can see the RPN expression "a,b,c,d,e,+,+,+,+,+" will evaluate
202       in "((((d+e)+c)+b)+a)" and it has the same outcome as
203       "a,b,+,c,+,d,+,e,+".  This is called the commutative law of addition,
204       but you may forget this right away, as long as you remember what it
205       means.
206
207       Now look at an expression that contains a multiplication:
208
209       First in normal math: "let result = a+b*c". In this case you can't
210       choose the order yourself, you have to start with the multiplication
211       and then add a to it. You may alter the position of b and c, you must
212       not alter the position of a and b.
213
214       You have to take this in consideration when converting this expression
215       into RPN. Read it as: "Add the outcome of b*c to a" and then it is easy
216       to write the RPN expression: "result=a,b,c,*,+" Another expression that
217       would return the same: "result=b,c,*,a,+"
218
219       In normal math, you may encounter something like "a*(b+c)" and this can
220       also be converted into RPN. The parenthesis just tell you to first add
221       b and c, and then multiply a with the result. Again, now it is easy to
222       write it in RPN: "result=a,b,c,+,*". Note that this is very similar to
223       one of the expressions in the previous paragraph, only the
224       multiplication and the addition changed places.
225
226       When you have problems with RPN or when RRDtool is complaining, it's
227       usually a good thing to write down the stack on a piece of paper and
228       see what happens. Have the manual ready and pretend to be RRDtool.
229       Just do all the math by hand to see what happens, I'm sure this will
230       solve most, if not all, problems you encounter.
231

Some special numbers

233   The unknown value
234       Sometimes collecting your data will fail. This can be very common,
235       especially when querying over busy links. RRDtool can be configured to
236       allow for one (or even more) unknown value(s) and calculate the missing
237       update. You can, for instance, query your device every minute. This is
238       creating one so called PDP or primary data point per minute. If you
239       defined your RRD to contain an RRA that stores 5-minute values, you
240       need five of those PDPs to create one CDP (consolidated data point).
241       These PDPs can become unknown in two cases:
242
243       1.  The updates are too far apart. This is tuned using the "heartbeat"
244           setting.
245
246       2.  The update was set to unknown on purpose by inserting no value
247           (using the template option) or by using "U" as the value to insert.
248
249       When a CDP is calculated, another mechanism determines if this CDP is
250       valid or not. If there are too many PDPs unknown, the CDP is unknown as
251       well.  This is determined by the xff factor. Please note that one
252       unknown counter update can result in two unknown PDPs! If you only
253       allow for one unknown PDP per CDP, this makes the CDP go unknown!
254
255       Suppose the counter increments with one per second and you retrieve it
256       every minute:
257
258          counter value    resulting rate
259          10'000
260          10'060            1; (10'060-10'000)/60 == 1
261          10'120            1; (10'120-10'060)/60 == 1
262          unknown           unknown; you don't know the last value
263          10'240            unknown; you don't know the previous value
264          10'300            1; (10'300-10'240)/60 == 1
265
266       If the CDP was to be calculated from the last five updates, it would
267       get two unknown PDPs and three known PDPs. If xff would have been set
268       to 0.5 which by the way is a commonly used factor, the CDP would have a
269       known value of 1. If xff would have been set to 0.2 then the resulting
270       CDP would be unknown.
271
272       You have to decide the proper values for heartbeat, number of PDPs per
273       CDP and the xff factor. As you can see from the previous text they
274       define the behavior of your RRA.
275
276   Working with unknown data in your database
277       As you have read in the previous chapter, entries in an RRA can be set
278       to the unknown value. If you do calculations with this type of value,
279       the result has to be unknown too. This means that an expression such as
280       "result=a,b,+" will be unknown if either a or b is unknown.  It would
281       be wrong to just ignore the unknown value and return the value of the
282       other parameter. By doing so, you would assume "unknown" means "zero"
283       and this is not true.
284
285       There has been a case where somebody was collecting data for over a
286       year.  A new piece of equipment was installed, a new RRD was created
287       and the scripts were changed to add a counter from the old database and
288       a counter from the new database. The result was disappointing, a large
289       part of the statistics seemed to have vanished mysteriously ...  They
290       of course didn't, values from the old database (known values) were
291       added to values from the new database (unknown values) and the result
292       was unknown.
293
294       In this case, it is fairly reasonable to use a CDEF that alters unknown
295       data into zero. The counters of the device were unknown (after all, it
296       wasn't installed yet!) but you know that the data rate through the
297       device had to be zero (because of the same reason: it was not
298       installed).
299
300       There are some examples below that make this change.
301
302   Infinity
303       Infinite data is another form of a special number. It cannot be graphed
304       because by definition you would never reach the infinite value. You can
305       think of positive and negative infinity depending on the position
306       relative to zero.
307
308       RRDtool is capable of representing (-not- graphing!) infinity by
309       stopping at its current maximum (for positive infinity) or minimum (for
310       negative infinity) without knowing this maximum (minimum).
311
312       Infinity in RRDtool is mostly used to draw an AREA without knowing its
313       vertical dimensions. You can think of it as drawing an AREA with an
314       infinite height and displaying only the part that is visible in the
315       current graph. This is probably a good way to approximate infinity and
316       it sure allows for some neat tricks. See below for examples.
317
318   Working with unknown data and infinity
319       Sometimes you would like to discard unknown data and pretend it is zero
320       (or any other value for that matter) and sometimes you would like to
321       pretend that known data is unknown (to discard known-to-be-wrong data).
322       This is why CDEFs have support for unknown data. There are also
323       examples available that show unknown data by using infinity.
324

Some examples

326   Example: using a recently created RRD
327       You are keeping statistics on your router for over a year now. Recently
328       you installed an extra router and you would like to show the combined
329       throughput for these two devices.
330
331       If you just add up the counters from router.rrd and router2.rrd, you
332       will add known data (from router.rrd) to unknown data (from
333       router2.rrd) for the bigger part of your stats. You could solve this in
334       a few ways:
335
336       •   While creating the new database, fill it with zeros from the start
337           to now.  You have to make the database start at or before the least
338           recent time in the other database.
339
340       •   Alternatively, you could use CDEF and alter unknown data to zero.
341
342       Both methods have their pros and cons. The first method is troublesome
343       and if you want to do that you have to figure it out yourself. It is
344       not possible to create a database filled with zeros, you have to put
345       them in manually. Implementing the second method is described next:
346
347       What we want is: "if the value is unknown, replace it with zero". This
348       could be written in pseudo-code as:  if (value is unknown) then (zero)
349       else (value). When reading the rrdgraph manual you notice the "UN"
350       function that returns zero or one. You also notice the "IF" function
351       that takes zero or one as input.
352
353       First look at the "IF" function. It takes three values from the stack,
354       the first value is the decision point, the second value is returned to
355       the stack if the evaluation is "true" and if not, the third value is
356       returned to the stack. We want the "UN" function to decide what happens
357       so we combine those two functions in one CDEF.
358
359       Lets write down the two possible paths for the "IF" function:
360
361          if true  return a
362          if false return b
363
364       In RPN:  "result=x,a,b,IF" where "x" is either true or false.
365
366       Now we have to fill in "x", this should be the "(value is unknown)"
367       part and this is in RPN:  "result=value,UN"
368
369       We now combine them: "result=value,UN,a,b,IF" and when we fill in the
370       appropriate things for "a" and "b" we're finished:
371
372       "CDEF:result=value,UN,0,value,IF"
373
374       You may want to read Steve Rader's RPN guide if you have difficulties
375       with the way I explained this last example.
376
377       If you want to check this RPN expression, just mimic RRDtool behavior:
378
379          For any known value, the expression evaluates as follows:
380          CDEF:result=value,UN,0,value,IF  (value,UN) is not true so it becomes 0
381          CDEF:result=0,0,value,IF         "IF" will return the 3rd value
382          CDEF:result=value                The known value is returned
383
384          For the unknown value, this happens:
385          CDEF:result=value,UN,0,value,IF  (value,UN) is true so it becomes 1
386          CDEF:result=1,0,value,IF         "IF" sees 1 and returns the 2nd value
387          CDEF:result=0                    Zero is returned
388
389       Of course, if you would like to see another value instead of zero, you
390       can use that other value.
391
392       Eventually, when all unknown data is removed from the RRD, you may want
393       to remove this rule so that unknown data is properly displayed.
394
395   Example: better handling of unknown data, by using time
396       The above example has one drawback. If you do log unknown data in your
397       database after installing your new equipment, it will also be
398       translated into zero and therefore you won't see that there was a
399       problem. This is not good and what you really want to do is:
400
401       •   If there is unknown data, look at the time that this sample was
402           taken.
403
404       •   If the unknown value is before time xxx, make it zero.
405
406       •   If it is after time xxx, leave it as unknown data.
407
408       This is doable: you can compare the time that the sample was taken to
409       some known time. Assuming you started to monitor your device on Friday
410       September 17, 1999, 00:35:57 MET DST. Translate this time in seconds
411       since 1970-01-01 and it becomes 937'521'357. If you process unknown
412       values that were received after this time, you want to leave them
413       unknown and if they were "received" before this time, you want to
414       translate them into zero (so you can effectively ignore them while
415       adding them to your other routers counters).
416
417       Translating Friday September 17, 1999, 00:35:57 MET DST into
418       937'521'357 can be done by, for instance, using gnu date:
419
420          date -d "19990917 00:35:57" +%s
421
422       You could also dump the database and see where the data starts to be
423       known. There are several other ways of doing this, just pick one.
424
425       Now we have to create the magic that allows us to process unknown
426       values different depending on the time that the sample was taken.  This
427       is a three step process:
428
429       1.  If the timestamp of the value is after 937'521'357, leave it as is.
430
431       2.  If the value is a known value, leave it as is.
432
433       3.  Change the unknown value into zero.
434
435       Lets look at part one:
436
437           if (true) return the original value
438
439       We rewrite this:
440
441           if (true) return "a"
442           if (false) return "b"
443
444       We need to calculate true or false from step 1. There is a function
445       available that returns the timestamp for the current sample. It is
446       called, how surprisingly, "TIME". This time has to be compared to a
447       constant number, we need "GT". The output of "GT" is true or false and
448       this is good input to "IF". We want "if (time > 937521357) then (return
449       a) else (return b)".
450
451       This process was already described thoroughly in the previous chapter
452       so lets do it quick:
453
454          if (x) then a else b
455             where x represents "time>937521357"
456             where a represents the original value
457             where b represents the outcome of the previous example
458
459          time>937521357       --> TIME,937521357,GT
460
461          if (x) then a else b --> x,a,b,IF
462          substitute x         --> TIME,937521357,GT,a,b,IF
463          substitute a         --> TIME,937521357,GT,value,b,IF
464          substitute b         --> TIME,937521357,GT,value,value,UN,0,value,IF,IF
465
466       We end up with:
467       "CDEF:result=TIME,937521357,GT,value,value,UN,0,value,IF,IF"
468
469       This looks very complex, however, as you can see, it was not too hard
470       to come up with.
471
472   Example: Pretending weird data isn't there
473       Suppose you have a problem that shows up as huge spikes in your graph.
474       You know this happens and why, so you decide to work around the
475       problem.  Perhaps you're using your network to do a backup at night and
476       by doing so you get almost 10mb/s while the rest of your network
477       activity does not produce numbers higher than 100kb/s.
478
479       There are two options:
480
481       1.  If the number exceeds 100kb/s it is wrong and you want it masked
482           out by changing it into unknown.
483
484       2.  You don't want the graph to show more than 100kb/s.
485
486       Pseudo code: if (number > 100) then unknown else number or Pseudo code:
487       if (number > 100) then 100 else number.
488
489       The second "problem" may also be solved by using the rigid option of
490       RRDtool graph, however this has not the same result. In this example
491       you can end up with a graph that does autoscaling. Also, if you use the
492       numbers to display maxima they will be set to 100kb/s.
493
494       We use "IF" and "GT" again. "if (x) then (y) else (z)" is written down
495       as "CDEF:result=x,y,z,IF"; now fill in x, y and z.  For x you fill in
496       "number greater than 100kb/s" becoming "number,100000,GT" (kilo is
497       1'000 and b/s is what we measure!).  The "z" part is "number" in both
498       cases and the "y" part is either "UNKN" for unknown or "100000" for
499       100kb/s.
500
501       The two CDEF expressions would be:
502
503           CDEF:result=number,100000,GT,UNKN,number,IF
504           CDEF:result=number,100000,GT,100000,number,IF
505
506   Example: working on a certain time span
507       If you want a graph that spans a few weeks, but would only want to see
508       some routers' data for one week, you need to "hide" the rest of the
509       time frame. Don't ask me when this would be useful, it's just here for
510       the example :)
511
512       We need to compare the time stamp to a begin date and an end date.
513       Comparing isn't difficult:
514
515               TIME,begintime,GE
516               TIME,endtime,LE
517
518       These two parts of the CDEF produce either 0 for false or 1 for true.
519       We can now check if they are both 0 (or 1) using a few IF statements
520       but, as Wataru Satoh pointed out, we can use the "*" or "+" functions
521       as logical AND and logical OR.
522
523       For "*", the result will be zero (false) if either one of the two
524       operators is zero.  For "+", the result will only be false (0) when two
525       false (0) operators will be added.  Warning: *any* number not equal to
526       0 will be considered "true". This means that, for instance, "-1,1,+"
527       (which should be "true or true") will become FALSE ...  In other words,
528       use "+" only if you know for sure that you have positive numbers (or
529       zero) only.
530
531       Let's compile the complete CDEF:
532
533               DEF:ds0=router1.rrd:AVERAGE
534               CDEF:ds0modified=TIME,begintime,GT,TIME,endtime,LE,*,ds0,UNKN,IF
535
536       This will return the value of ds0 if both comparisons return true. You
537       could also do it the other way around:
538
539               DEF:ds0=router1.rrd:AVERAGE
540               CDEF:ds0modified=TIME,begintime,LT,TIME,endtime,GT,+,UNKN,ds0,IF
541
542       This will return an UNKNOWN if either comparison returns true.
543
544   Example: You suspect to have problems and want to see unknown data.
545       Suppose you add up the number of active users on several terminal
546       servers.  If one of them doesn't give an answer (or an incorrect one)
547       you get "NaN" in the database ("Not a Number") and NaN is evaluated as
548       Unknown.
549
550       In this case, you would like to be alerted to it and the sum of the
551       remaining values is of no value to you.
552
553       It would be something like:
554
555           DEF:users1=location1.rrd:onlineTS1:LAST
556           DEF:users2=location1.rrd:onlineTS2:LAST
557           DEF:users3=location2.rrd:onlineTS1:LAST
558           DEF:users4=location2.rrd:onlineTS2:LAST
559           CDEF:allusers=users1,users2,users3,users4,+,+,+
560
561       If you now plot allusers, unknown data in one of users1..users4 will
562       show up as a gap in your graph. You want to modify this to show a
563       bright red line, not a gap.
564
565       Define an extra CDEF that is unknown if all is okay and is infinite if
566       there is an unknown value:
567
568           CDEF:wrongdata=allusers,UN,INF,UNKN,IF
569
570       "allusers,UN" will evaluate to either true or false, it is the (x) part
571       of the "IF" function and it checks if allusers is unknown.  The (y)
572       part of the "IF" function is set to "INF" (which means infinity) and
573       the (z) part of the function returns "UNKN".
574
575       The logic is: if (allusers == unknown) then return INF else return
576       UNKN.
577
578       You can now use AREA to display this "wrongdata" in bright red. If it
579       is unknown (because allusers is known) then the red AREA won't show up.
580       If the value is INF (because allusers is unknown) then the red AREA
581       will be filled in on the graph at that particular time.
582
583          AREA:allusers#0000FF:combined user count
584          AREA:wrongdata#FF0000:unknown data
585
586   Same example useful with STACKed data:
587       If you use stack in the previous example (as I would do) then you don't
588       add up the values. Therefore, there is no relationship between the four
589       values and you don't get a single value to test.  Suppose users3 would
590       be unknown at one point in time: users1 is plotted, users2 is stacked
591       on top of users1, users3 is unknown and therefore nothing happens,
592       users4 is stacked on top of users2.  Add the extra CDEFs anyway and use
593       them to overlay the "normal" graph:
594
595          DEF:users1=location1.rrd:onlineTS1:LAST
596          DEF:users2=location1.rrd:onlineTS2:LAST
597          DEF:users3=location2.rrd:onlineTS1:LAST
598          DEF:users4=location2.rrd:onlineTS2:LAST
599          CDEF:allusers=users1,users2,users3,users4,+,+,+
600          CDEF:wrongdata=allusers,UN,INF,UNKN,IF
601          AREA:users1#0000FF:users at ts1
602          STACK:users2#00FF00:users at ts2
603          STACK:users3#00FFFF:users at ts3
604          STACK:users4#FFFF00:users at ts4
605          AREA:wrongdata#FF0000:unknown data
606
607       If there is unknown data in one of users1..users4, the "wrongdata" AREA
608       will be drawn and because it starts at the X-axis and has infinite
609       height it will effectively overwrite the STACKed parts.
610
611       You could combine the two CDEF lines into one (we don't use "allusers")
612       if you like.  But there are good reasons for writing two CDEFS:
613
614       •   It improves the readability of the script.
615
616       •   It can be used inside GPRINT to display the total number of users.
617
618       If you choose to combine them, you can substitute the "allusers" in the
619       second CDEF with the part after the equal sign from the first line:
620
621          CDEF:wrongdata=users1,users2,users3,users4,+,+,+,UN,INF,UNKN,IF
622
623       If you do so, you won't be able to use these next GPRINTs:
624
625          COMMENT:"Total number of users seen"
626          GPRINT:allusers:MAX:"Maximum: %6.0lf"
627          GPRINT:allusers:MIN:"Minimum: %6.0lf"
628          GPRINT:allusers:AVERAGE:"Average: %6.0lf"
629          GPRINT:allusers:LAST:"Current: %6.0lf\n"
630

The examples from the RRD graph manual page

632   Degrees Celsius vs. Degrees Fahrenheit
633       To convert Celsius into Fahrenheit use the formula F=9/5*C+32
634
635          rrdtool graph demo.png --title="Demo Graph" \
636             DEF:cel=demo.rrd:exhaust:AVERAGE \
637             CDEF:far=9,5,/,cel,*,32,+ \
638             LINE2:cel#00a000:"D. Celsius" \
639             LINE2:far#ff0000:"D. Fahrenheit\c"
640
641       This example gets the DS called "exhaust" from database "demo.rrd" and
642       puts the values in variable "cel". The CDEF used is evaluated as
643       follows:
644
645          CDEF:far=9,5,/,cel,*,32,+
646          1. push 9, push 5
647          2. push function "divide" and process it
648             the stack now contains 9/5
649          3. push variable "cel"
650          4. push function "multiply" and process it
651             the stack now contains 9/5*cel
652          5. push 32
653          6. push function "plus" and process it
654             the stack contains now the temperature in Fahrenheit
655
656   Changing unknown into zero
657          rrdtool graph demo.png --title="Demo Graph" \
658             DEF:idat1=interface1.rrd:ds0:AVERAGE \
659             DEF:idat2=interface2.rrd:ds0:AVERAGE \
660             DEF:odat1=interface1.rrd:ds1:AVERAGE \
661             DEF:odat2=interface2.rrd:ds1:AVERAGE \
662             CDEF:agginput=idat1,UN,0,idat1,IF,idat2,UN,0,idat2,IF,+,8,* \
663             CDEF:aggoutput=odat1,UN,0,odat1,IF,odat2,UN,0,odat2,IF,+,8,* \
664             AREA:agginput#00cc00:Input Aggregate \
665             LINE1:aggoutput#0000FF:Output Aggregate
666
667       These two CDEFs are built from several functions. It helps to split
668       them when viewing what they do. Starting with the first CDEF we would
669       get:
670
671        idat1,UN --> a
672        0        --> b
673        idat1    --> c
674        if (a) then (b) else (c)
675
676       The result is therefore "0" if it is true that "idat1" equals "UN".  If
677       not, the original value of "idat1" is put back on the stack.  Lets call
678       this answer "d". The process is repeated for the next five items on the
679       stack, it is done the same and will return answer "h". The resulting
680       stack is therefore "d,h".  The expression has been simplified to
681       "d,h,+,8,*" and it will now be easy to see that we add "d" and "h", and
682       multiply the result with eight.
683
684       The end result is that we have added "idat1" and "idat2" and in the
685       process we effectively ignored unknown values. The result is multiplied
686       by eight, most likely to convert bytes/s to bits/s.
687
688   Infinity demo
689          rrdtool graph example.png --title="INF demo" \
690             DEF:val1=some.rrd:ds0:AVERAGE \
691             DEF:val2=some.rrd:ds1:AVERAGE \
692             DEF:val3=some.rrd:ds2:AVERAGE \
693             DEF:val4=other.rrd:ds0:AVERAGE \
694             CDEF:background=val4,POP,TIME,7200,%,3600,LE,INF,UNKN,IF \
695             CDEF:wipeout=val1,val2,val3,val4,+,+,+,UN,INF,UNKN,IF \
696             AREA:background#F0F0F0 \
697             AREA:val1#0000FF:Value1 \
698             STACK:val2#00C000:Value2 \
699             STACK:val3#FFFF00:Value3 \
700             STACK:val4#FFC000:Value4 \
701             AREA:whipeout#FF0000:Unknown
702
703       This demo demonstrates two ways to use infinity. It is a bit tricky to
704       see what happens in the "background" CDEF.
705
706          "val4,POP,TIME,7200,%,3600,LE,INF,UNKN,IF"
707
708       This RPN takes the value of "val4" as input and then immediately
709       removes it from the stack using "POP". The stack is now empty but as a
710       side effect we now know the time that this sample was taken.  This time
711       is put on the stack by the "TIME" function.
712
713       "TIME,7200,%" takes the modulo of time and 7'200 (which is two hours).
714       The resulting value on the stack will be a number in the range from 0
715       to 7199.
716
717       For people who don't know the modulo function: it is the remainder
718       after an integer division. If you divide 16 by 3, the answer would be 5
719       and the remainder would be 1. So, "16,3,%" returns 1.
720
721       We have the result of "TIME,7200,%" on the stack, lets call this "a".
722       The start of the RPN has become "a,3600,LE" and this checks if "a" is
723       less or equal than "3600". It is true half of the time.  We now have to
724       process the rest of the RPN and this is only a simple "IF" function
725       that returns either "INF" or "UNKN" depending on the time. This is
726       returned to variable "background".
727
728       The second CDEF has been discussed earlier in this document so we won't
729       do that here.
730
731       Now you can draw the different layers. Start with the background that
732       is either unknown (nothing to see) or infinite (the whole positive part
733       of the graph gets filled).
734
735       Next you draw the data on top of this background, it will overlay the
736       background. Suppose one of val1..val4 would be unknown, in that case
737       you end up with only three bars stacked on top of each other.  You
738       don't want to see this because the data is only valid when all four
739       variables are valid. This is why you use the second CDEF, it will
740       overlay the data with an AREA so the data cannot be seen anymore.
741
742       If your data can also have negative values you also need to overwrite
743       the other half of your graph. This can be done in a relatively simple
744       way: what you need is the "wipeout" variable and place a negative sign
745       before it:  "CDEF:wipeout2=wipeout,-1,*"
746
747   Filtering data
748       You may do some complex data filtering:
749
750         MEDIAN FILTER: filters shot noise
751
752           DEF:var=database.rrd:traffic:AVERAGE
753           CDEF:prev1=PREV(var)
754           CDEF:prev2=PREV(prev1)
755           CDEF:median=var,prev1,prev2,3,SORT,POP,EXC,POP
756           LINE3:median#000077:filtered
757           LINE1:prev2#007700:'raw data'
758
759
760         DERIVATE:
761
762           DEF:var=database.rrd:traffic:AVERAGE
763           CDEF:prev1=PREV(var)
764           CDEF:time=var,POP,TIME
765           CDEF:prevtime=PREV(time)
766           CDEF:derivate=var,prev1,-,time,prevtime,-,/
767           LINE3:derivate#000077:derivate
768           LINE1:var#007700:'raw data'
769

Out of ideas for now

771       This document was created from questions asked by either myself or by
772       other people on the RRDtool mailing list. Please let me know if you
773       find errors in it or if you have trouble understanding it. If you think
774       there should be an addition, mail me: <alex@vandenbogaerdt.nl>
775
776       Remember: No feedback equals no changes!
777

SEE ALSO

779       The RRDtool manpages
780

AUTHOR

782       Alex van den Bogaerdt <alex@vandenbogaerdt.nl>
783
784
785
7861.8.0                             2022-03-14                   CDEFTUTORIAL(1)
Impressum