man > CDEFTUTORIAL(1)

CDEFTUTORIAL(1)                                rrdtool                               CDEFTUTORIAL(1)

NAME
       cdeftutorial - Alex van den Bogaerdt's CDEF tutorial

DESCRIPTION
       Intention of this document: to provide some examples of the commonly used parts of RRDtool's
       CDEF language.

       If you think some important feature is not explained properly, and if adding it to this
       document would benefit most users, please do ask me to add it.  I will then try to provide an
       answer in the next release of this tutorial.  No feedback equals no changes! Additions to
       this document are also welcome.  -- Alex van den Bogaerdt <alex AT vandenbogaerdt.nl>

   Why this tutorial?
       One of the powerful parts of RRDtool is its ability to do all sorts of calculations on the
       data retrieved from its databases. However, RRDtool's many options and syntax make it
       difficult for the average user to understand. The manuals are good at explaining what these
       options do; however they do not (and should not) explain in detail why they are useful. As
       with my RRDtool tutorial: if you want a simple document in simple language you should read
       this tutorial.  If you are happy with the official documentation, you may find this document
       too simple or even boring. If you do choose to read this tutorial, I also expect you to have
       read and fully understand my other tutorial.

   More reading
       If you have difficulties with the way I try to explain it please read Steve Rader's
       rpntutorial. It may help you understand how this all works.

What are CDEFs?
       When retrieving data from an RRD, you are using a "DEF" to work with that data. Think of it
       as a variable that changes over time (where time is the x-axis). The value of this variable
       is what is found in the database at that particular time and you can't do any modifications
       on it. This is what CDEFs are for: they takes values from DEFs and perform calculations on
       them.

Syntax
          DEF:var_name_1=some.rrd:ds_name:CF
          CDEF:var_name_2=RPN_expression

       You first define "var_name_1" to be data collected from data source "ds_name" found in RRD
       "some.rrd" with consolidation function "CF".

       Assume the ifInOctets SNMP counter is saved in mrtg.rrd as the DS "in".  Then the following
       DEF defines a variable for the average of that data source:

          DEF:inbytes=mrtg.rrd:in:AVERAGE

       Say you want to display bits per second (instead of bytes per second as stored in the
       database.)  You have to define a calculation (hence "CDEF") on variable "inbytes" and use
       that variable (inbits) instead of the original:

          CDEF:inbits=inbytes,8,*

       This tells RRDtool to multiply inbytes by eight to get inbits. I'll explain later how this
       works. In the graphing or printing functions, you can now use inbits where you would use
       inbytes otherwise.

       Note that the variable name used in the CDEF (inbits) must not be the same as the variable
       named in the DEF (inbytes)!

RPN-expressions
       RPN is short-hand for Reverse Polish Notation. It works as follows.  You put the variables or
       numbers on a stack. You also put operations (things-to-do) on the stack and this stack is
       then processed. The result will be placed on the stack. At the end, there should be exactly
       one number left: the outcome of the series of operations. If there is not exactly one number
       left, RRDtool will complain loudly.

       Above multiplication by eight will look like:

       1.  Start with an empty stack

       2.  Put the content of variable inbytes on the stack

       3.  Put the number eight on the stack

       4.  Put the operation multiply on the stack

       5.  Process the stack

       6.  Retrieve the value from the stack and put it in variable inbits

       We  will  now  do an example with real numbers. Suppose the variable inbytes would have value
       10, the stack would be:

       1.  ||

       2.  |10|

       3.  |10|8|

       4.  |10|8|*|

       5.  |80|

       6.  ||

       Processing the stack (step 5) will retrieve one value from the stack (from the right at  step
       4).  This  is  the  operation  multiply and this takes two values off the stack as input. The
       result is put back on the stack (the value 80 in this case).  For  multiplication  the  order
       doesn't  matter,  but  for other operations like subtraction and division it does.  Generally
       speaking you have the following order:

          y = A - B  -->  y=minus(A,B)  -->  CDEF:y=A,B,-

       This is not very intuitive (at least most people don't think so). For the function f(A,B) you
       reverse the position of "f", but you do not reverse the order of the variables.

Converting your wishes to RPN
       First, get a clear picture of what you want to do. Break down the problem in smaller portions
       until they cannot be split anymore. Then it is rather simple to convert your ideas into RPN.

       Suppose you have several RRDs and would like to add up some counters in them. These could be,
       for instance, the counters for every WAN link you are monitoring.

       You have:

          router1.rrd with link1in link2in
          router2.rrd with link1in link2in
          router3.rrd with link1in link2in

       Suppose you would like to add up all these counters, except for link2in  inside  router2.rrd.
       You need to do:

       (in this example, "router1.rrd:link1in" means the DS link1in inside the RRD router1.rrd)

          router1.rrd:link1in
          router1.rrd:link2in
          router2.rrd:link1in
          router3.rrd:link1in
          router3.rrd:link2in
          --------------------   +
          (outcome of the sum)

       As a mathematical function, this could be written:

       "add(router1.rrd:link1in  , router1.rrd:link2in , router2.rrd:link1in , router3.rrd:link1in ,
       router3.rrd:link2.in)"

       With RRDtool and RPN, first, define the inputs:

          DEF:a=router1.rrd:link1in:AVERAGE
          DEF:b=router1.rrd:link2in:AVERAGE
          DEF:c=router2.rrd:link1in:AVERAGE
          DEF:d=router3.rrd:link1in:AVERAGE
          DEF:e=router3.rrd:link2in:AVERAGE

       Now, the mathematical function becomes: "add(a,b,c,d,e)"

       In RPN, there's no operator that sums more  than  two  values  so  you  need  to  do  several
       additions. You add a and b, add c to the result, add d to the result and add e to the result.

          push a:         a     stack contains the value of a
          push b and add: b,+   stack contains the result of a+b
          push c and add: c,+   stack contains the result of a+b+c
          push d and add: d,+   stack contains the result of a+b+c+d
          push e and add: e,+   stack contains the result of a+b+c+d+e

       What was calculated here would be written down as:

          ( ( ( (a+b) + c) + d) + e) >

       This is in RPN:  "CDEF:result=a,b,+,c,+,d,+,e,+"

       This is correct but it can be made more clear to humans. It does not matter if you add a to b
       and  then add c to the result or first add b to c and then add a to the result. This makes it
       possible  to  rewrite  the  RPN  into  "CDEF:result=a,b,c,d,e,+,+,+,+"  which  is   evaluated
       differently:

          push value of variable a on the stack: a
          push value of variable b on the stack: a b
          push value of variable c on the stack: a b c
          push value of variable d on the stack: a b c d
          push value of variable e on the stack: a b c d e
          push operator + on the stack:          a b c d e +
          and process it:                        a b c P   (where P == d+e)
          push operator + on the stack:          a b c P +
          and process it:                        a b Q     (where Q == c+P)
          push operator + on the stack:          a b Q +
          and process it:                        a R       (where R == b+Q)
          push operator + on the stack:          a R +
          and process it:                        S         (where S == a+R)

       As  you can see the RPN expression "a,b,c,d,e,+,+,+,+,+" will evaluate in "((((d+e)+c)+b)+a)"
       and it has the same outcome as "a,b,+,c,+,d,+,e,+".  This is called the  commutative  law  of
       addition, but you may forget this right away, as long as you remember what it means.

       Now look at an expression that contains a multiplication:

       First in normal math: "let result = a+b*c". In this case you can't choose the order yourself,
       you have to start with the multiplication and then add a to it. You may alter the position of
       b and c, you must not alter the position of a and b.

       You  have to take this in consideration when converting this expression into RPN. Read it as:
       "Add the  outcome  of  b*c  to  a"  and  then  it  is  easy  to  write  the  RPN  expression:
       "result=a,b,c,*,+" Another expression that would return the same: "result=b,c,*,a,+"

       In  normal  math,  you  may encounter something like "a*(b+c)" and this can also be converted
       into RPN. The parenthesis just tell you to first add b and c, and then multiply  a  with  the
       result.  Again, now it is easy to write it in RPN: "result=a,b,c,+,*". Note that this is very
       similar to one of the expressions in the previous paragraph, only the multiplication and  the
       addition changed places.

       When  you have problems with RPN or when RRDtool is complaining, it's usually a good thing to
       write down the stack on a piece of paper and see what happens.  Have  the  manual  ready  and
       pretend  to be RRDtool.  Just do all the math by hand to see what happens, I'm sure this will
       solve most, if not all, problems you encounter.

Some special numbers
   The unknown value
       Sometimes collecting your data will fail. This can be very common, especially  when  querying
       over  busy  links. RRDtool can be configured to allow for one (or even more) unknown value(s)
       and calculate the missing update. You can, for instance, query your device every minute. This
       is creating one so called PDP or primary data point per minute. If you defined  your  RRD  to
       contain  an  RRA  that  stores 5-minute values, you need five of those PDPs to create one CDP
       (consolidated data point).  These PDPs can become unknown in two cases:

       1.  The updates are too far apart. This is tuned using the "heartbeat" setting.

       2.  The update was set to unknown on purpose  by  inserting  no  value  (using  the  template
           option) or by using "U" as the value to insert.

       When  a CDP is calculated, another mechanism determines if this CDP is valid or not. If there
       are too many PDPs unknown, the CDP is unknown as well.  This is determined by the xff factor.
       Please note that one unknown counter update can result in two unknown PDPs! If you only allow
       for one unknown PDP per CDP, this makes the CDP go unknown!

       Suppose the counter increments with one per second and you retrieve it every minute:

          counter value    resulting rate
          10'000
          10'060            1; (10'060-10'000)/60 == 1
          10'120            1; (10'120-10'060)/60 == 1
          unknown           unknown; you don't know the last value
          10'240            unknown; you don't know the previous value
          10'300            1; (10'300-10'240)/60 == 1

       If the CDP was to be calculated from the last five updates, it would get two unknown PDPs and
       three known PDPs. If xff would have been set to 0.5 which by  the  way  is  a  commonly  used
       factor,  the  CDP  would  have a known value of 1. If xff would have been set to 0.2 then the
       resulting CDP would be unknown.

       You have to decide the proper values for heartbeat, number  of  PDPs  per  CDP  and  the  xff
       factor. As you can see from the previous text they define the behavior of your RRA.

   Working with unknown data in your database
       As  you have read in the previous chapter, entries in an RRA can be set to the unknown value.
       If you do calculations with this type of value, the result has to be unknown too. This  means
       that  an  expression  such as "result=a,b,+" will be unknown if either a or b is unknown.  It
       would be wrong to just ignore the unknown value and return the value of the other  parameter.
       By doing so, you would assume "unknown" means "zero" and this is not true.

       There  has  been  a  case where somebody was collecting data for over a year.  A new piece of
       equipment was installed, a new RRD was created and the scripts were changed to add a  counter
       from  the  old  database and a counter from the new database. The result was disappointing, a
       large part of the statistics seemed to have vanished mysteriously ...  They of course didn't,
       values from the old database (known values) were  added  to  values  from  the  new  database
       (unknown values) and the result was unknown.

       In  this  case, it is fairly reasonable to use a CDEF that alters unknown data into zero. The
       counters of the device were unknown (after all, it wasn't installed yet!) but you  know  that
       the  data  rate  through  the  device  had to be zero (because of the same reason: it was not
       installed).

       There are some examples below that make this change.

   Infinity
       Infinite data is another form of a special number. It cannot be graphed because by definition
       you would never reach the infinite value. You can think of  positive  and  negative  infinity
       depending on the position relative to zero.

       RRDtool  is  capable  of  representing  (-not- graphing!) infinity by stopping at its current
       maximum (for positive infinity) or minimum  (for  negative  infinity)  without  knowing  this
       maximum (minimum).

       Infinity  in  RRDtool is mostly used to draw an AREA without knowing its vertical dimensions.
       You can think of it as drawing an AREA with an infinite height and displaying only  the  part
       that is visible in the current graph. This is probably a good way to approximate infinity and
       it sure allows for some neat tricks. See below for examples.

   Working with unknown data and infinity
       Sometimes  you  would like to discard unknown data and pretend it is zero (or any other value
       for that matter) and sometimes you would like to pretend  that  known  data  is  unknown  (to
       discard  known-to-be-wrong data).  This is why CDEFs have support for unknown data. There are
       also examples available that show unknown data by using infinity.

Some examples
   Example: using a recently created RRD
       You are keeping statistics on your router for over a year  now.  Recently  you  installed  an
       extra router and you would like to show the combined throughput for these two devices.

       If  you  just  add  up  the counters from router.rrd and router2.rrd, you will add known data
       (from router.rrd) to unknown data (from router2.rrd) for the bigger part of your  stats.  You
       could solve this in a few ways:

       •   While  creating  the new database, fill it with zeros from the start to now.  You have to
           make the database start at or before the least recent time in the other database.

       •   Alternatively, you could use CDEF and alter unknown data to zero.

       Both methods have their pros and cons. The first method is troublesome and if you want to  do
       that  you have to figure it out yourself. It is not possible to create a database filled with
       zeros, you have to put them in manually. Implementing the second method is described next:

       What we want is: "if the value is unknown, replace it with zero". This could  be  written  in
       pseudo-code  as:   if  (value is unknown) then (zero) else (value). When reading the rrdgraph
       manual you notice the "UN" function that returns zero  or  one.  You  also  notice  the  "IF"
       function that takes zero or one as input.

       First look at the "IF" function. It takes three values from the stack, the first value is the
       decision  point, the second value is returned to the stack if the evaluation is "true" and if
       not, the third value is returned to the stack. We want  the  "UN"  function  to  decide  what
       happens so we combine those two functions in one CDEF.

       Lets write down the two possible paths for the "IF" function:

          if true  return a
          if false return b

       In RPN:  "result=x,a,b,IF" where "x" is either true or false.

       Now  we have to fill in "x", this should be the "(value is unknown)" part and this is in RPN:
       "result=value,UN"

       We now combine them: "result=value,UN,a,b,IF" and when we fill in the appropriate things  for
       "a" and "b" we're finished:

       "CDEF:result=value,UN,0,value,IF"

       You  may  want  to  read  Steve  Rader's  RPN  guide  if you have difficulties with the way I
       explained this last example.

       If you want to check this RPN expression, just mimic RRDtool behavior:

          For any known value, the expression evaluates as follows:
          CDEF:result=value,UN,0,value,IF  (value,UN) is not true so it becomes 0
          CDEF:result=0,0,value,IF         "IF" will return the 3rd value
          CDEF:result=value                The known value is returned

          For the unknown value, this happens:
          CDEF:result=value,UN,0,value,IF  (value,UN) is true so it becomes 1
          CDEF:result=1,0,value,IF         "IF" sees 1 and returns the 2nd value
          CDEF:result=0                    Zero is returned

       Of course, if you would like to see another value instead of zero, you  can  use  that  other
       value.

       Eventually,  when  all unknown data is removed from the RRD, you may want to remove this rule
       so that unknown data is properly displayed.

   Example: better handling of unknown data, by using time
       The above example has one drawback. If you  do  log  unknown  data  in  your  database  after
       installing  your  new equipment, it will also be translated into zero and therefore you won't
       see that there was a problem. This is not good and what you really want to do is:

       •   If there is unknown data, look at the time that this sample was taken.

       •   If the unknown value is before time xxx, make it zero.

       •   If it is after time xxx, leave it as unknown data.

       This is doable: you can compare the time that the  sample  was  taken  to  some  known  time.
       Assuming  you  started to monitor your device on Friday September 17, 1999, 00:35:57 MET DST.
       Translate this time in seconds since 1970-01-01 and it becomes 937'521'357.  If  you  process
       unknown values that were received after this time, you want to leave them unknown and if they
       were  "received"  before  this  time,  you  want  to  translate  them  into  zero (so you can
       effectively ignore them while adding them to your other routers counters).

       Translating Friday September 17, 1999, 00:35:57 MET DST into 937'521'357 can be done by,  for
       instance, using gnu date:

          date -d "19990917 00:35:57" +%s

       You could also dump the database and see where the data starts to be known. There are several
       other ways of doing this, just pick one.

       Now  we have to create the magic that allows us to process unknown values different depending
       on the time that the sample was taken.  This is a three step process:

       1.  If the timestamp of the value is after 937'521'357, leave it as is.

       2.  If the value is a known value, leave it as is.

       3.  Change the unknown value into zero.

       Lets look at part one:

           if (true) return the original value

       We rewrite this:

           if (true) return "a"
           if (false) return "b"

       We need to calculate true or false from step 1. There is a function  available  that  returns
       the  timestamp  for the current sample. It is called, how surprisingly, "TIME". This time has
       to be compared to a constant number, we need "GT". The output of "GT" is true  or  false  and
       this is good input to "IF". We want "if (time > 937521357) then (return a) else (return b)".

       This process was already described thoroughly in the previous chapter so lets do it quick:

          if (x) then a else b
             where x represents "time>937521357"
             where a represents the original value
             where b represents the outcome of the previous example

          time>937521357       --> TIME,937521357,GT

          if (x) then a else b --> x,a,b,IF
          substitute x         --> TIME,937521357,GT,a,b,IF
          substitute a         --> TIME,937521357,GT,value,b,IF
          substitute b         --> TIME,937521357,GT,value,value,UN,0,value,IF,IF

       We end up with: "CDEF:result=TIME,937521357,GT,value,value,UN,0,value,IF,IF"

       This looks very complex, however, as you can see, it was not too hard to come up with.

   Example: Pretending weird data isn't there
       Suppose you have a problem that shows up as huge spikes in your graph.  You know this happens
       and why, so you decide to work around the problem.  Perhaps you're using your network to do a
       backup at night and by doing so you get almost 10mb/s while the rest of your network activity
       does not produce numbers higher than 100kb/s.

       There are two options:

       1.  If  the number exceeds 100kb/s it is wrong and you want it masked out by changing it into
           unknown.

       2.  You don't want the graph to show more than 100kb/s.

       Pseudo code: if (number > 100) then unknown else number or Pseudo code:  if  (number  >  100)
       then 100 else number.

       The  second  "problem" may also be solved by using the rigid option of RRDtool graph, however
       this has not the same result. In this  example  you  can  end  up  with  a  graph  that  does
       autoscaling. Also, if you use the numbers to display maxima they will be set to 100kb/s.

       We   use   "IF"   and   "GT"   again.  "if  (x)  then  (y)  else  (z)"  is  written  down  as
       "CDEF:result=x,y,z,IF"; now fill in x, y and z.  For x  you  fill  in  "number  greater  than
       100kb/s"  becoming  "number,100000,GT"  (kilo is 1'000 and b/s is what we measure!).  The "z"
       part is "number" in both cases and the "y" part is either "UNKN" for unknown or "100000"  for
       100kb/s.

       The two CDEF expressions would be:

           CDEF:result=number,100000,GT,UNKN,number,IF
           CDEF:result=number,100000,GT,100000,number,IF

   Example: working on a certain time span
       If you want a graph that spans a few weeks, but would only want to see some routers' data for
       one  week,  you  need  to  "hide" the rest of the time frame. Don't ask me when this would be
       useful, it's just here for the example :)

       We need to compare the time stamp  to  a  begin  date  and  an  end  date.   Comparing  isn't
       difficult:

               TIME,begintime,GE
               TIME,endtime,LE

       These  two  parts  of the CDEF produce either 0 for false or 1 for true.  We can now check if
       they are both 0 (or 1) using a few IF statements but, as Wataru Satoh pointed out, we can use
       the "*" or "+" functions as logical AND and logical OR.

       For "*", the result will be zero (false) if either one of the two  operators  is  zero.   For
       "+",  the result will only be false (0) when two false (0) operators will be added.  Warning:
       *any* number not equal to 0 will  be  considered  "true".  This  means  that,  for  instance,
       "-1,1,+" (which should be "true or true") will become FALSE ...  In other words, use "+" only
       if you know for sure that you have positive numbers (or zero) only.

       Let's compile the complete CDEF:

               DEF:ds0=router1.rrd:AVERAGE
               CDEF:ds0modified=TIME,begintime,GT,TIME,endtime,LE,*,ds0,UNKN,IF

       This  will  return the value of ds0 if both comparisons return true. You could also do it the
       other way around:

               DEF:ds0=router1.rrd:AVERAGE
               CDEF:ds0modified=TIME,begintime,LT,TIME,endtime,GT,+,UNKN,ds0,IF

       This will return an UNKNOWN if either comparison returns true.

   Example: You suspect to have problems and want to see unknown data.
       Suppose you add up the number of active users on several terminal servers.  If  one  of  them
       doesn't  give  an answer (or an incorrect one) you get "NaN" in the database ("Not a Number")
       and NaN is evaluated as Unknown.

       In this case, you would like to be alerted to it and the sum of the remaining values is of no
       value to you.

       It would be something like:

           DEF:users1=location1.rrd:onlineTS1:LAST
           DEF:users2=location1.rrd:onlineTS2:LAST
           DEF:users3=location2.rrd:onlineTS1:LAST
           DEF:users4=location2.rrd:onlineTS2:LAST
           CDEF:allusers=users1,users2,users3,users4,+,+,+

       If you now plot allusers, unknown data in one of users1..users4 will show up as a gap in your
       graph. You want to modify this to show a bright red line, not a gap.

       Define an extra CDEF that is unknown if all is okay and is infinite if there  is  an  unknown
       value:

           CDEF:wrongdata=allusers,UN,INF,UNKN,IF

       "allusers,UN"  will evaluate to either true or false, it is the (x) part of the "IF" function
       and it checks if allusers is unknown.  The (y) part of the "IF"  function  is  set  to  "INF"
       (which means infinity) and the (z) part of the function returns "UNKN".

       The logic is: if (allusers == unknown) then return INF else return UNKN.

       You  can  now  use  AREA to display this "wrongdata" in bright red. If it is unknown (because
       allusers is known) then the red AREA won't show up.  If the value is INF (because allusers is
       unknown) then the red AREA will be filled in on the graph at that particular time.

          AREA:allusers#0000FF:combined user count
          AREA:wrongdata#FF0000:unknown data

   Same example useful with STACKed data:
       If you use stack in the previous example (as I would do) then you don't add  up  the  values.
       Therefore,  there is no relationship between the four values and you don't get a single value
       to test.  Suppose users3 would be unknown at one point in time: users1 is plotted, users2  is
       stacked  on top of users1, users3 is unknown and therefore nothing happens, users4 is stacked
       on top of users2.  Add the extra CDEFs anyway and use them to overlay the "normal" graph:

          DEF:users1=location1.rrd:onlineTS1:LAST
          DEF:users2=location1.rrd:onlineTS2:LAST
          DEF:users3=location2.rrd:onlineTS1:LAST
          DEF:users4=location2.rrd:onlineTS2:LAST
          CDEF:allusers=users1,users2,users3,users4,+,+,+
          CDEF:wrongdata=allusers,UN,INF,UNKN,IF
          AREA:users1#0000FF:users at ts1
          STACK:users2#00FF00:users at ts2
          STACK:users3#00FFFF:users at ts3
          STACK:users4#FFFF00:users at ts4
          AREA:wrongdata#FF0000:unknown data

       If there is unknown data in one of users1..users4, the "wrongdata" AREA  will  be  drawn  and
       because  it  starts  at  the X-axis and has infinite height it will effectively overwrite the
       STACKed parts.

       You could combine the two CDEF lines into one (we don't use "allusers")  if  you  like.   But
       there are good reasons for writing two CDEFS:

       •   It improves the readability of the script.

       •   It can be used inside GPRINT to display the total number of users.

       If  you choose to combine them, you can substitute the "allusers" in the second CDEF with the
       part after the equal sign from the first line:

          CDEF:wrongdata=users1,users2,users3,users4,+,+,+,UN,INF,UNKN,IF

       If you do so, you won't be able to use these next GPRINTs:

          COMMENT:"Total number of users seen"
          GPRINT:allusers:MAX:"Maximum: %6.0lf"
          GPRINT:allusers:MIN:"Minimum: %6.0lf"
          GPRINT:allusers:AVERAGE:"Average: %6.0lf"
          GPRINT:allusers:LAST:"Current: %6.0lf\n"

The examples from the RRD graph manual page
   Degrees Celsius vs. Degrees Fahrenheit
       To convert Celsius into Fahrenheit use the formula F=9/5*C+32

          rrdtool graph demo.png --title="Demo Graph" \
             DEF:cel=demo.rrd:exhaust:AVERAGE \
             CDEF:far=9,5,/,cel,*,32,+ \
             LINE2:cel#00a000:"D. Celsius" \
             LINE2:far#ff0000:"D. Fahrenheit\c"

       This example gets the DS called "exhaust" from database "demo.rrd" and  puts  the  values  in
       variable "cel". The CDEF used is evaluated as follows:

          CDEF:far=9,5,/,cel,*,32,+
          1. push 9, push 5
          2. push function "divide" and process it
             the stack now contains 9/5
          3. push variable "cel"
          4. push function "multiply" and process it
             the stack now contains 9/5*cel
          5. push 32
          6. push function "plus" and process it
             the stack contains now the temperature in Fahrenheit

   Changing unknown into zero
          rrdtool graph demo.png --title="Demo Graph" \
             DEF:idat1=interface1.rrd:ds0:AVERAGE \
             DEF:idat2=interface2.rrd:ds0:AVERAGE \
             DEF:odat1=interface1.rrd:ds1:AVERAGE \
             DEF:odat2=interface2.rrd:ds1:AVERAGE \
             CDEF:agginput=idat1,UN,0,idat1,IF,idat2,UN,0,idat2,IF,+,8,* \
             CDEF:aggoutput=odat1,UN,0,odat1,IF,odat2,UN,0,odat2,IF,+,8,* \
             AREA:agginput#00cc00:Input Aggregate \
             LINE1:aggoutput#0000FF:Output Aggregate

       These  two  CDEFs  are built from several functions. It helps to split them when viewing what
       they do. Starting with the first CDEF we would get:

        idat1,UN --> a
        0        --> b
        idat1    --> c
        if (a) then (b) else (c)

       The result is therefore "0" if it is true that "idat1" equals "UN".   If  not,  the  original
       value  of  "idat1"  is  put  back  on  the  stack.  Lets call this answer "d". The process is
       repeated for the next five items on the stack, it is done the same  and  will  return  answer
       "h".  The  resulting  stack  is  therefore  "d,h".   The  expression  has  been simplified to
       "d,h,+,8,*" and it will now be easy to see that we add "d" and "h", and multiply  the  result
       with eight.

       The  end  result  is that we have added "idat1" and "idat2" and in the process we effectively
       ignored unknown values. The result is multiplied by eight, most likely to convert bytes/s  to
       bits/s.

   Infinity demo
          rrdtool graph example.png --title="INF demo" \
             DEF:val1=some.rrd:ds0:AVERAGE \
             DEF:val2=some.rrd:ds1:AVERAGE \
             DEF:val3=some.rrd:ds2:AVERAGE \
             DEF:val4=other.rrd:ds0:AVERAGE \
             CDEF:background=val4,POP,TIME,7200,%,3600,LE,INF,UNKN,IF \
             CDEF:wipeout=val1,val2,val3,val4,+,+,+,UN,INF,UNKN,IF \
             AREA:background#F0F0F0 \
             AREA:val1#0000FF:Value1 \
             STACK:val2#00C000:Value2 \
             STACK:val3#FFFF00:Value3 \
             STACK:val4#FFC000:Value4 \
             AREA:whipeout#FF0000:Unknown

       This  demo  demonstrates  two ways to use infinity. It is a bit tricky to see what happens in
       the "background" CDEF.

          "val4,POP,TIME,7200,%,3600,LE,INF,UNKN,IF"

       This RPN takes the value of "val4" as input and then immediately removes it  from  the  stack
       using  "POP".  The  stack  is  now  empty but as a side effect we now know the time that this
       sample was taken.  This time is put on the stack by the "TIME" function.

       "TIME,7200,%" takes the modulo of time and 7'200 (which is two hours).  The  resulting  value
       on the stack will be a number in the range from 0 to 7199.

       For people who don't know the modulo function: it is the remainder after an integer division.
       If  you  divide  16  by  3,  the answer would be 5 and the remainder would be 1. So, "16,3,%"
       returns 1.

       We have the result of "TIME,7200,%" on the stack, lets call this "a". The start  of  the  RPN
       has  become  "a,3600,LE" and this checks if "a" is less or equal than "3600". It is true half
       of the time.  We now have to process the rest of the RPN and  this  is  only  a  simple  "IF"
       function  that  returns  either  "INF"  or  "UNKN" depending on the time. This is returned to
       variable "background".

       The second CDEF has been discussed earlier in this document so we won't do that here.

       Now you can draw the different layers. Start with  the  background  that  is  either  unknown
       (nothing to see) or infinite (the whole positive part of the graph gets filled).

       Next you draw the data on top of this background, it will overlay the background. Suppose one
       of  val1..val4  would be unknown, in that case you end up with only three bars stacked on top
       of each other.  You don't want to see this because the data  is  only  valid  when  all  four
       variables  are  valid.  This is why you use the second CDEF, it will overlay the data with an
       AREA so the data cannot be seen anymore.

       If your data can also have negative values you also need to overwrite the other half of  your
       graph.  This  can be done in a relatively simple way: what you need is the "wipeout" variable
       and place a negative sign before it:  "CDEF:wipeout2=wipeout,-1,*"

   Filtering data
       You may do some complex data filtering:

         MEDIAN FILTER: filters shot noise

           DEF:var=database.rrd:traffic:AVERAGE
           CDEF:prev1=PREV(var)
           CDEF:prev2=PREV(prev1)
           CDEF:median=var,prev1,prev2,3,SORT,POP,EXC,POP
           LINE3:median#000077:filtered
           LINE1:prev2#007700:'raw data'


         DERIVATE:

           DEF:var=database.rrd:traffic:AVERAGE
           CDEF:prev1=PREV(var)
           CDEF:time=var,POP,TIME
           CDEF:prevtime=PREV(time)
           CDEF:derivate=var,prev1,-,time,prevtime,-,/
           LINE3:derivate#000077:derivate
           LINE1:var#007700:'raw data'

Out of ideas for now
       This document was created from questions asked by either myself or by  other  people  on  the
       RRDtool  mailing  list.  Please  let  me know if you find errors in it or if you have trouble
       understanding it. If you think there should be an addition, mail me: <alex AT vandenbogaerdt.nl>

       Remember: No feedback equals no changes!

SEE ALSO
       The RRDtool manpages

AUTHOR
       Alex van den Bogaerdt <alex AT vandenbogaerdt.nl>

1.7.2                                        2024-03-31                              CDEFTUTORIAL(1)
CDEFTUTORIAL(1) □✕
NAME DESCRIPTION
Why this tutorial? More reading
Syntax Converting your wishes to RPN Some special numbers
The unknown value Working with unknown data in your database Infinity Working with unknown data and infinity
Some examples
Example: using a recently created RRD Example: better handling of unknown data, by using time Example: Pretending weird data isn't there Example: working on a certain time span Example: You suspect to have problems and want to see unknown data. Same example useful with STACKed data:
The examples from the RRD graph manual page
Degrees Celsius vs. Degrees Fahrenheit Changing unknown into zero Infinity demo Filtering data
Out of ideas for now SEE ALSO AUTHOR

Generated by phpman v4.11.2-59-gf06411a · Markdown · JSON Author: Che Dong Under GNU General Public License
2026-10-14 09:54 @2600:1f28:365:80b0:e850:d0cd:2068:7271
CrawledBy CCBot/2.0 (https://commoncrawl.org/faq/)
Valid XHTML 1.0 Transitional!Valid CSS!

^_top_^