1 module("ffluci.statistics.rrdtool", package.seeall)
3 require("ffluci.statistics.datatree")
4 require("ffluci.statistics.rrdtool.colors")
5 require("ffluci.statistics.rrdtool.definitions")
11 Graph = ffluci.util.class()
13 function Graph.__init__( self, timespan, opts )
16 opts.width = opts.width or "400"
18 self.colors = ffluci.statistics.rrdtool.colors.Instance()
19 self.defs = ffluci.statistics.rrdtool.definitions.Instance()
20 self.tree = ffluci.statistics.datatree.Instance()
22 -- rrdtool defalt args
25 "-s", "NOW-" .. ( timespan or 900 ),
30 function Graph.mktitle( self, host, plugin, plugin_instance, dtype, dtype_instance )
31 local t = host .. "/" .. plugin
32 if type(plugin_instance) == "string" and plugin_instance:len() > 0 then
33 t = t .. "-" .. plugin_instance
36 if type(dtype_instance) == "string" and dtype_instance:len() > 0 then
37 t = t .. "-" .. dtype_instance
42 function Graph.mkrrdpath( self, ... )
43 return string.format( "/tmp/%s.rrd", self:mktitle( ... ) )
46 function Graph.mkpngpath( self, ... )
47 return string.format( "/tmp/rrdimg/%s.png", self:mktitle( ... ) )
50 function Graph._push( self, elem )
52 if type(elem) == "string" then
53 table.insert( self.args, elem )
55 for i, item in ipairs(elem) do
56 table.insert( self.args, item )
63 function Graph._clearargs( self )
64 for i = #self.args, 7, -1 do
65 table.remove( self.args, i )
69 function Graph._rrdtool( self, png, rrd )
70 local cmdline = "rrdtool graph " .. png
72 for i, opt in ipairs(self.args) do
74 opt = opt .. "" -- force string
77 opt = opt:gsub( "{file}", rrd )
80 if opt:match("[^%w]") then
81 cmdline = cmdline .. " '" .. opt .. "'"
83 cmdline = cmdline .. " " .. opt
87 local rrdtool = io.popen( cmdline )
92 function Graph._generic( self, optlist )
96 if type(optlist[1]) ~= "table" then
100 for i, opts in ipairs(optlist) do
102 table.insert( images, opts.image )
104 -- insert provided addition rrd options
105 self:_push( { "-t", opts.title or "Unknown title" } )
106 self:_push( opts.rrd )
108 -- construct an array of safe instance names
109 local inst_names = { }
110 for i, source in ipairs(opts.sources) do
111 inst_names[i] = i .. source.name:gsub("[^A-Za-z0-9%-_]","_")
114 -- create DEF statements for each instance, find longest instance name
115 local longest_name = 0
116 for i, source in ipairs(opts.sources) do
117 if source.name:len() > longest_name then
118 longest_name = source.name:len()
121 self:_push( "DEF:" .. inst_names[i] .. "_min=" ..source.rrd .. ":value:MIN" )
122 self:_push( "DEF:" .. inst_names[i] .. "_avg=" ..source.rrd .. ":value:AVERAGE" )
123 self:_push( "DEF:" .. inst_names[i] .. "_max=" ..source.rrd .. ":value:MAX" )
124 self:_push( "CDEF:" .. inst_names[i] .. "_nnl=" .. inst_names[i] .. "_avg,UN,0," .. inst_names[i] .. "_avg,IF" )
127 -- create CDEF statement for last instance name
128 self:_push( "CDEF:" .. inst_names[#inst_names] .. "_stk=" .. inst_names[#inst_names] .. "_nnl" )
130 -- create CDEF statements for each instance
131 for i, source in ipairs(inst_names) do
135 inst_names[1 + #inst_names - i] .. "_stk=" ..
136 inst_names[1 + #inst_names - i] .. "_nnl," ..
137 inst_names[2 + #inst_names - i] .. "_stk,+"
142 -- create LINE and GPRINT statements for each instance
143 for i, source in ipairs(opts.sources) do
145 local legend = string.format(
146 "%-" .. longest_name .. "s",
150 local numfmt = opts.number_format or "%6.1lf"
155 if type(opts.colors[source.name]) == "string" then
156 line_color = opts.colors[source.name]
157 area_color = self.colors:from_string( line_color )
159 area_color = self.colors:random()
160 line_color = self.colors:to_string( area_color )
163 area_color = self.colors:to_string(
164 self.colors:faded( area_color )
167 self:_push( "AREA:" .. inst_names[i] .. "_stk#" .. area_color )
168 self:_push( "LINE1:" .. inst_names[i] .. "_stk#" .. line_color .. ":" .. legend )
169 self:_push( "GPRINT:" .. inst_names[i] .. "_min:MIN:" .. numfmt .. " Min" )
170 self:_push( "GPRINT:" .. inst_names[i] .. "_avg:AVERAGE:" .. numfmt .. " Avg" )
171 self:_push( "GPRINT:" .. inst_names[i] .. "_max:MAX:" .. numfmt .. " Max" )
172 self:_push( "GPRINT:" .. inst_names[i] .. "_avg:LAST:" .. numfmt .. " Last\\l" )
179 function Graph.render( self, host, plugin, plugin_instance )
181 dtype_instances = dtype_instances or { "" }
184 -- check for a whole graph handler
185 local plugin_def = "ffluci.statistics.rrdtool.definitions." .. plugin
186 local stat, def = pcall( require, plugin_def )
188 if stat and def and type(def.rrdargs) == "function" then
189 for i, png in ipairs( self:_generic( def.rrdargs( self, host, plugin, plugin_instance, dtype ) ) ) do
190 table.insert( pngs, png )
200 -- no graph handler, iterate over data types
201 for i, dtype in ipairs( self.tree:data_types( plugin, plugin_instance ) ) do
203 -- check for data type handler
204 local dtype_def = plugin_def .. "." .. dtype
205 local stat, def = pcall( require, dtype_def )
207 if stat and def and type(def.rrdargs) == "function" then
208 for i, png in ipairs( self:_generic( def.rrdargs( self, host, plugin, plugin_instance, dtype ) ) ) do
209 table.insert( pngs, png )
219 -- no data type handler, fall back to builtin definition
220 if type(self.defs.definitions[dtype]) == "table" then
222 -- iterate over data type instances
223 for i, inst in ipairs( self.tree:data_instances( plugin, plugin_instance, dtype ) ) do
225 local title = self:mktitle( host, plugin, plugin_instance, dtype, inst )
226 local png = self:mkpngpath( host, plugin, plugin_instance, dtype, inst )
227 local rrd = self:mkrrdpath( host, plugin, plugin_instance, dtype, inst )
229 self:_push( { "-t", title } )
230 self:_push( self.defs.definitions[dtype] )
232 table.insert( pngs, png )
235 self:_rrdtool( png, rrd )