Recent Posts


« | Main | »

Creating Color Gradients with RRDTOOL & Bash (using temperatures)

By Dale Reagan | May 3, 2012

You would think that it would be easy to locate just about any information on the Internet but that’s simply not true.  While I found many examples (using various approaches) of generating graphs with gradients using rrdtool I did not locate any dynamic solutions, i.e.:

An excellent summary of how to create gradients with rrdtool is found the Circles of Archimedes blog.  For this project, the target is something similar to the sample ‘Temperature’ graph by João Marcelo Ceron.  For this post I will present a set of graphs based on rrdtool commands initially generated with drraw (an excellent graphing tool supporting simultaneous data extraction from multiple RRDs.)  The author, Christophe Kalt, describes the tool as:

The interface is relatively simple and does require some work to set up your RRDs (I have 40+) but it allows you to create templates and ‘clone’ your graphs/charts.  If you want to quickly see how to create your own set of rrdtool graphing commands then drraw has an option to show/hide the commands being used for any graph that you create – super feature!  The only downside is that the last release of the tool is from 2007 (of course, this is an Open Source tool so you are free to enhance/modify…)  :)

36 hour Attic Temperature ranges

36 hour Attic Temperature ranges

The graph/chart above uses temperature data captured from a 1-Wire network in the attic that is using DS18B20 sensors (from Maxim IC,)  The sensors are located:

  1. in the office (R_Ofc_00 – baseline, indoor, conditioned space – dark blue line)
  2. in the attic above the insulation (floor level, sensors labeled as ‘Ai’) and
  3. in the attic below the insulation (below insulation at the floor level, sensors labeled as ‘Bi’.)
  4. in the attic directly above a North East soffit vent (the sensor labeled as G_ST_B7_SV_NE.)

A discussion about the ‘meaning’ of this data is outside of the scope of this post, however it does appear that the G_Db_23_Bi sensor is a ‘bit warmer’ that perhaps it should be (most likely due to variances in the amount of insulation directly above the sensor.)  I would also expect that sensors located above the insulation would be ‘warmer’ (part of my reason for choosing reddish color labels for them.)

The rrdtool ‘code’ to generate this chart is a bit long (as created by drraw) – below is a partial example:

rrdtool graph - \
--start=end - 1 day \
--end=now \
--title=Above & Below Insulation \
--imgformat=PNG \
--width=800 \
--base=1000 \
--height=120 \
--alt-autoscale \
--interlaced \
--watermark \
(C)_2012_Dale_Reagan__http:/ \
DEF:b=/path_to_RRDs/i686_rrds/G_ST_B7_SV_NE.rrd:G_ST_B7_SV_NE:AVERAGE \
DEF:e=/path_to_RRDs/i686_rrds/R_Ofc_00.rrd:R_Ofc_00:AVERAGE \
DEF:l=/path_to_RRDs/i686_rrds/B_Db_22_Ai.rrd:B_Db_22_Ai:AVERAGE \
DEF:m=/path_to_RRDs/i686_rrds/P_Sb21_Ai.rrd:P_Sb21_Ai:AVERAGE \
DEF:n=/path_to_RRDs/i686_rrds/Y_Sb12_Ai.rrd:Y_Sb12_Ai:AVERAGE \
DEF:x=/path_to_RRDs/i686_rrds/B_Sb52_Bi.rrd:B_Sb52_Bi:AVERAGE \
DEF:y=/path_to_RRDs/i686_rrds/G_Db_23_Bi.rrd:G_Db_23_Bi:AVERAGE \
DEF:z=/path_to_RRDs/i686_rrds/P_Sb24_Bi.rrd:P_Sb24_Bi:AVERAGE \
GPRINT:b_MIN:Min\: %8.2lf%s \
... <snip>
GPRINT:z_LAST:Last\: %8.2lf%s\n \
COMMENT:Wed 2012-05-02 13\:02 - Thu 2012-05-03 13\:02\c \
COMMENT:Created onThu 2012-05-03 13\:02\r

Generating Gradients with rrdtool



36 Hour Attic Temperatures with Color Gradient

The above is a ‘bit much’ to grasp at once so I will use a CPU temperature RRD to show how to generate a gradient using rrdtool. The details on creating RRDs for capturing system temperatures are detailed in a previous post: Simple rrdtool Linux System temperatures;  Using the sys-temps.rrd (see the ‘simple rrdtool post’) we note the following:

rrdtool info sys-temps.rrd | more
filename = "sys-temps.rrd"
rrd_version = "0003"
step = 300
last_update = 1336067832
ds[temp1].type = "GAUGE"
ds[temp1].minimal_heartbeat = 300
ds[temp1].min = NaN
ds[temp1].max = NaN
ds[temp1].last_ds = "U"
ds[temp1].value = 0.0000000000e+00
ds[temp1].unknown_sec = 132
ds[temp2].type = "GAUGE"
ds[temp2].minimal_heartbeat = 300
ds[temp2].min = NaN
ds[temp2].max = NaN
ds[temp2].last_ds = "U"
ds[temp2].value = 0.0000000000e+00
ds[temp2].unknown_sec = 132
rra[0].cf = "AVERAGE"
rra[0].rows = 12
rra[0].cur_row = 8
rra[0].pdp_per_row = 1
rra[0].xff = 5.0000000000e-01
rra[0].cdp_prep[0].value = NaN
rra[0].cdp_prep[0].unknown_datapoints = 0
rra[0].cdp_prep[1].value = NaN
rra[0].cdp_prep[1].unknown_datapoints = 0
rra[4].cf = "AVERAGE"
rra[4].rows = 365
rra[4].cur_row = 351
rra[4].pdp_per_row = 288
rra[4].xff = 5.0000000000e-01
rra[4].cdp_prep[0].value = NaN
rra[4].cdp_prep[0].unknown_datapoints = 215
rra[4].cdp_prep[1].value = NaN
rra[4].cdp_prep[1].unknown_datapoints = 215

Creating Graphs from RRDs

Local Host - System Board Temperatures

Simple RRD Graph of Linux System Temperatures

The simple chart above covers one day of data – it was generated using drraw – the rrdtool command was:

rrdtool graph - \
--start=end - 1 day \
--end=now \
--title=Localhost -Temps \
--imgformat=PNG \
--width=800 \
--base=1000 \
--height=220 \
--alt-autoscale \
--interlaced \
DEF:a=/tmp/RRDTOOL/sys-temps.rrd:temp1:AVERAGE \
DEF:b=/tmp/RRDTOOL/sys-temps.rrd:temp2:AVERAGE \
LINE2:a#FFA500:temp1 AVERAGE \
GPRINT:a_MIN:Min\: %8.2lf%s \
GPRINT:a_AVERAGE:Avg\: %8.2lf%s \
GPRINT:a_MAX:Max\: %8.2lf%s \
GPRINT:a_LAST:Last\: %8.2lf%s\n \
LINE3:b#FF0000:temp2 AVERAGE \
GPRINT:b_MIN:Min\: %8.2lf%s \
GPRINT:b_AVERAGE:Avg\: %8.2lf%s \
GPRINT:b_MAX:Max\: %8.2lf%s \
GPRINT:b_LAST:Last\: %8.2lf%s\n \
COMMENT:Wed 2012-05-02 14\:11 - Thu 2012-05-03 14\:11\c \
COMMENT:Created onThu 2012-05-03 14\:11\r

Summary of rrdtool graph command options from previous example

Quite a bit of ‘auto-magic’ is included during the graph generation process used by rrdtool – but you do have options for refining the scale and other aspects of the output.  (Note:  the above snippet will not ‘work’ via the Bash shell since many of the options need to be enclosed within quotes…)

We can create a gradient by adding a set of ‘color range definitions’ that fall within temperature ranges.  First, we establish our starting temperature (in this case, a maximum) and then we establish additional range values using RPN:

  1. “DEF:temp=/tmp/RRDTOOL/sys-temps.rrd:temp2:MAX”  ## use the MAX DS value as a starting point
  2. define additional temperature variables (CDEFS, temp[z-a]) using Reverse Polish Notation (RPN) – to calculate/compare values, and then
  3. we associate AREA colors for each range.

Instead of having many, many lines of ‘command data’ I group them as ‘color range units‘, i.e.:

"CDEF:tempz=temp,130,LT,temp,130,IF" "CDEF:tempzNoUnk=temp,UN,0,tempz,IF" "AREA:tempzNoUnk#ff0000" \
"CDEF:tempy=temp,129,LT,temp,129,IF" "CDEF:tempyNoUnk=temp,UN,0,tempy,IF" "AREA:tempyNoUnk#ff0000" \
"CDEF:tempb=temp,106,LT,temp,106,IF" "CDEF:tempbNoUnk=temp,UN,0,tempb,IF" "AREA:tempbNoUnk#00e4ff" \
"CDEF:tempa=temp,105,LT,temp,105,IF" "CDEF:tempaNoUnk=temp,UN,0,tempa,IF" "AREA:tempaNoUnk#00beff" \

Using the range of 105 to 130 – the new chart/graph is:

System temperatures displaye with temperature color gradient

System temperatures displayed with temperature color gradient

While the basic information is the same the I think that the gradient has a bit more ‘eye appeal’.  The script below lacks error checking but may provide the reader with a starting place for their own experiments.

Local Host - System Board Temperatures

Simple RRD Graph of Linux System Temperatures

# Generate RRDTool radient temp graph
RANGE="$1" ## how many hours to plot

case ${RANGE}
          36) ((INTERVAL=86400+43200)) ;;
         day) INTERVAL=86400 ;;
        week) INTERVAL=604800 ;;
       month) INTERVAL=2678400 ;;
        year) INTERVAL=31622400 ;;
           *) INTERVAL=86400 ;;

S_TIME=$(date +%s) ## get current time
((START=S_TIME-INTERVAL)) ## calculate Start time
TITLE="Localhost - System Temps"

rrdtool graph ${OUT} \
--imginfo "\"${TITLE}\"/" \
--start=${START} \
--end=now \
--title="${TITLE}" \
--imgformat=PNG \
--width=800 \
--base=1000 \
--height=200 \
--alt-autoscale \
--interlaced \
--lower-limit 99 \
--rigid \
--watermark "© $(date +%Y) Dale Reagan" \
--zoom 1.5 \
"DEF:a=/tmp/RRDTOOL/sys-temps.rrd:temp1:AVERAGE" \
"DEF:b=/tmp/RRDTOOL/sys-temps.rrd:temp2:AVERAGE" \
"DEF:temp=/var/DATA/System/i686/sys-temps.rrd:temp2:MAX" \
"CDEF:tempz=temp,130,LT,temp,130,IF" "CDEF:tempzNoUnk=temp,UN,0,tempz,IF" "AREA:tempzNoUnk#ff0000" \
"CDEF:tempy=temp,129,LT,temp,129,IF" "CDEF:tempyNoUnk=temp,UN,0,tempy,IF" "AREA:tempyNoUnk#ff0000" \
"CDEF:tempx=temp,128,LT,temp,128,IF" "CDEF:tempxNoUnk=temp,UN,0,tempx,IF" "AREA:tempxNoUnk#ff0000" \
"CDEF:tempw=temp,127,LT,temp,127,IF" "CDEF:tempwNoUnk=temp,UN,0,tempw,IF" "AREA:tempwNoUnk#ff0000" \
"CDEF:tempv=temp,126,LT,temp,126,IF" "CDEF:tempvNoUnk=temp,UN,0,tempv,IF" "AREA:tempvNoUnk#ff1b00" \
"CDEF:tempu=temp,125,LT,temp,125,IF" "CDEF:tempuNoUnk=temp,UN,0,tempu,IF" "AREA:tempuNoUnk#ff4100" \
"CDEF:tempt=temp,124,LT,temp,124,IF" "CDEF:temptNoUnk=temp,UN,0,tempt,IF" "AREA:temptNoUnk#ff6600" \
"CDEF:temps=temp,123,LT,temp,123,IF" "CDEF:tempsNoUnk=temp,UN,0,temps,IF" "AREA:tempsNoUnk#ff8e00" \
"CDEF:tempr=temp,122,LT,temp,122,IF" "CDEF:temprNoUnk=temp,UN,0,tempr,IF" "AREA:temprNoUnk#ffb500" \
"CDEF:tempq=temp,121,LT,temp,121,IF" "CDEF:tempqNoUnk=temp,UN,0,tempq,IF" "AREA:tempqNoUnk#ffdb00" \
"CDEF:tempp=temp,120,LT,temp,120,IF" "CDEF:temppNoUnk=temp,UN,0,tempp,IF" "AREA:temppNoUnk#fdff00" \
"CDEF:tempo=temp,119,LT,temp,119,IF" "CDEF:tempoNoUnk=temp,UN,0,tempo,IF" "AREA:tempoNoUnk#d7ff00" \
"CDEF:tempn=temp,118,LT,temp,118,IF" "CDEF:tempnNoUnk=temp,UN,0,tempn,IF" "AREA:tempnNoUnk#b0ff00" \
"CDEF:tempm=temp,117,LT,temp,117,IF" "CDEF:tempmNoUnk=temp,UN,0,tempm,IF" "AREA:tempmNoUnk#8aff00" \
"CDEF:templ=temp,116,LT,temp,116,IF" "CDEF:templNoUnk=temp,UN,0,templ,IF" "AREA:templNoUnk#65ff00" \
"CDEF:tempk=temp,115,LT,temp,115,IF" "CDEF:tempkNoUnk=temp,UN,0,tempk,IF" "AREA:tempkNoUnk#3eff00" \
"CDEF:tempj=temp,114,LT,temp,114,IF" "CDEF:tempjNoUnk=temp,UN,0,tempj,IF" "AREA:tempjNoUnk#17ff00" \
"CDEF:tempi=temp,113,LT,temp,113,IF" "CDEF:tempiNoUnk=temp,UN,0,tempi,IF" "AREA:tempiNoUnk#00ff10" \
"CDEF:temph=temp,112,LT,temp,112,IF" "CDEF:temphNoUnk=temp,UN,0,temph,IF" "AREA:temphNoUnk#00ff36" \
"CDEF:tempg=temp,111,LT,temp,111,IF" "CDEF:tempgNoUnk=temp,UN,0,tempg,IF" "AREA:tempgNoUnk#00ff5c" \
"CDEF:tempf=temp,110,LT,temp,110,IF" "CDEF:tempfNoUnk=temp,UN,0,tempf,IF" "AREA:tempfNoUnk#00ff83" \
"CDEF:tempe=temp,109,LT,temp,109,IF" "CDEF:tempeNoUnk=temp,UN,0,tempe,IF" "AREA:tempeNoUnk#00ffa8" \
"CDEF:tempd=temp,108,LT,temp,108,IF" "CDEF:tempdNoUnk=temp,UN,0,tempd,IF" "AREA:tempdNoUnk#00ffd0" \
"CDEF:tempc=temp,107,LT,temp,107,IF" "CDEF:tempcNoUnk=temp,UN,0,tempc,IF" "AREA:tempcNoUnk#00fff4" \
"CDEF:tempb=temp,106,LT,temp,106,IF" "CDEF:tempbNoUnk=temp,UN,0,tempb,IF" "AREA:tempbNoUnk#00e4ff" \
"CDEF:tempa=temp,105,LT,temp,105,IF" "CDEF:tempaNoUnk=temp,UN,0,tempa,IF" "AREA:tempaNoUnk#00beff" \
"LINE2:a#FFA500:temp1 AVERAGE" \
"GPRINT:a_MIN:Min\: %8.2lf%s" \
"GPRINT:a_AVERAGE:Avg\: %8.2lf%s" \
"GPRINT:a_MAX:Max\: %8.2lf%s" \
"GPRINT:a_LAST:Last\: %8.2lf%s\n" \
"LINE3:b#FF0000:temp2 AVERAGE" \
"GPRINT:b_MIN:Min\: %8.2lf%s" \
"GPRINT:b_AVERAGE:Avg\: %8.2lf%s" \
"GPRINT:b_MAX:Max\: %8.2lf%s" \
"GPRINT:b_LAST:Last\: %8.2lf%s\n" \
"COMMENT:Wed 2012-05-02 14\:11 - Thu 2012-05-03 14\:11\c" \
"COMMENT:Created onThu 2012-05-03 14\:11\r"

Note:  working with script generated ‘date values’ requires some tinkering when using rrdtool, i.e.

A slightly more enhanced version of the above script:  Generate RRD Graph of system temperatures – (click to download.)  It includes some help from ‘awk & sed ‘ to format the dates as need by rrdtool.

NOW=$(date +%s) ## get current time
((START=NOW-INTERVAL)) ## calculate Start time

## rrdtool does not behave well with ‘:’s in dates…
START_FORMATTED=$(echo “${START}” | awk ‘{printf “%s\n”, strftime(“%c”,$1)}’ | sed -e ‘s/:/\\:/g’)
NOW_FORMATTED=$(echo “${NOW}” | awk ‘{printf “%s\n”, strftime(“%c”,$1)}’ | sed -e ‘s/:/\\:/g’) also includes a range modification – the ‘top color’ gradient output from the script on this page ‘stops’ at whatever MAX temperature is found in the DS record.  By adding some number to this value you can ‘extend’ the gradient, i.e.:

"DEF:rrdmax=${RRD}:temp2:MAX" ## what is current MAX from the rrd?
"CDEF:temp=rrdmax,25,+"       ## set top value for temp to MAX + 25

If you use the above approach you may need to tinker a bit to find appropriate values to extend the range.  Here is a sample with the ‘extended gradient’:


Simple, extended gradient using CDEFs & RPN

The Bash script (see link above) could easily be extended to loop through a set of RRDs – I may extend it at some point.  Look for a future Bash scripting post providing a function to customize the min/max temperature ranges for CDEFs.

As always, your gradients should vary – at least a bit.  :)

Topics: Problem Solving, Unix-Linux-Os, Web Problem Solving, Web Technologies | Comments Off

Comments are closed.

YOUR GeoIP Data | Ip:
Continent: NA | Country Code: US | Country Name: United States
Region: VA | State/Region Name: Virginia | City: Ashburn
(US only) Area Code: 703 | Postal code/Zip:
Latitude: 39.043701 | Longitude: -77.487503
Note - if using a mobile device your physical location may NOT be accurate...

Georgia-USA.Com - Web Hosting for Business