summaryrefslogtreecommitdiffhomepage
path: root/applications/luci-statistics/luasrc/statistics/rrdtool.lua
diff options
context:
space:
mode:
Diffstat (limited to 'applications/luci-statistics/luasrc/statistics/rrdtool.lua')
-rw-r--r--applications/luci-statistics/luasrc/statistics/rrdtool.lua264
1 files changed, 264 insertions, 0 deletions
diff --git a/applications/luci-statistics/luasrc/statistics/rrdtool.lua b/applications/luci-statistics/luasrc/statistics/rrdtool.lua
new file mode 100644
index 000000000..b399866a8
--- /dev/null
+++ b/applications/luci-statistics/luasrc/statistics/rrdtool.lua
@@ -0,0 +1,264 @@
+module("luci.statistics.rrdtool", package.seeall)
+
+require("luci.statistics.datatree")
+require("luci.statistics.rrdtool.colors")
+require("luci.statistics.rrdtool.definitions")
+require("luci.util")
+require("luci.bits")
+require("luci.fs")
+
+
+Graph = luci.util.class()
+
+function Graph.__init__( self, timespan, opts )
+
+ opts = opts or { }
+ opts.width = opts.width or "400"
+
+ self.colors = luci.statistics.rrdtool.colors.Instance()
+ self.defs = luci.statistics.rrdtool.definitions.Instance()
+ self.tree = luci.statistics.datatree.Instance()
+
+ -- rrdtool defalt args
+ self.args = {
+ "-a", "PNG",
+ "-s", "NOW-" .. ( timespan or 900 ),
+ "-w", opts.width
+ }
+end
+
+function Graph.mktitle( self, host, plugin, plugin_instance, dtype, dtype_instance )
+ local t = host .. "/" .. plugin
+ if type(plugin_instance) == "string" and plugin_instance:len() > 0 then
+ t = t .. "-" .. plugin_instance
+ end
+ t = t .. "/" .. dtype
+ if type(dtype_instance) == "string" and dtype_instance:len() > 0 then
+ t = t .. "-" .. dtype_instance
+ end
+ return t
+end
+
+function Graph.mkrrdpath( self, ... )
+ return string.format( "/tmp/%s.rrd", self:mktitle( ... ) )
+end
+
+function Graph.mkpngpath( self, ... )
+ return string.format( "/tmp/rrdimg/%s.png", self:mktitle( ... ) )
+end
+
+function Graph._push( self, elem )
+
+ if type(elem) == "string" then
+ table.insert( self.args, elem )
+ else
+ for i, item in ipairs(elem) do
+ table.insert( self.args, item )
+ end
+ end
+
+ return( self.args )
+end
+
+function Graph._clearargs( self )
+ for i = #self.args, 7, -1 do
+ table.remove( self.args, i )
+ end
+end
+
+function Graph._forcelol( self, list )
+ if type(list[1]) ~= "table" then
+ return( { list } )
+ end
+ return( list )
+end
+
+function Graph._rrdtool( self, png, rrd )
+
+ -- prepare directory
+ local dir = png:gsub("/[^/]+$","")
+ luci.fs.mkdir( dir, true )
+
+ -- construct commandline
+ local cmdline = "rrdtool graph " .. png
+
+ for i, opt in ipairs(self.args) do
+
+ opt = opt .. "" -- force string
+
+ if rrd then
+ opt = opt:gsub( "{file}", rrd )
+ end
+
+ if opt:match("[^%w]") then
+ cmdline = cmdline .. " '" .. opt .. "'"
+ else
+ cmdline = cmdline .. " " .. opt
+ end
+ end
+
+ -- execute rrdtool
+ local rrdtool = io.popen( cmdline )
+ rrdtool:close()
+end
+
+function Graph._generic( self, opts )
+
+ local images = { }
+
+ -- remember images
+ table.insert( images, opts.image )
+
+ -- insert provided addition rrd options
+ self:_push( { "-t", opts.title or "Unknown title" } )
+ self:_push( opts.rrd )
+
+ -- construct an array of safe instance names
+ local inst_names = { }
+ for i, source in ipairs(opts.sources) do
+ inst_names[i] = i .. source.name:gsub("[^A-Za-z0-9%-_]","_")
+ end
+
+ -- create DEF statements for each instance, find longest instance name
+ local longest_name = 0
+ for i, source in ipairs(opts.sources) do
+ if source.name:len() > longest_name then
+ longest_name = source.name:len()
+ end
+
+ local ds = source.ds or "value"
+
+ self:_push( "DEF:" .. inst_names[i] .. "_min=" ..source.rrd .. ":" .. ds .. ":MIN" )
+ self:_push( "DEF:" .. inst_names[i] .. "_avg=" ..source.rrd .. ":" .. ds .. ":AVERAGE" )
+ self:_push( "DEF:" .. inst_names[i] .. "_max=" ..source.rrd .. ":" .. ds .. ":MAX" )
+ self:_push( "CDEF:" .. inst_names[i] .. "_nnl=" .. inst_names[i] .. "_avg,UN,0," .. inst_names[i] .. "_avg,IF" )
+ end
+
+ -- create CDEF statement for last instance name
+ self:_push( "CDEF:" .. inst_names[#inst_names] .. "_stk=" .. inst_names[#inst_names] .. "_nnl" )
+
+ -- create CDEF statements for each instance
+ for i, source in ipairs(inst_names) do
+ if i > 1 then
+ self:_push(
+ "CDEF:" ..
+ inst_names[1 + #inst_names - i] .. "_stk=" ..
+ inst_names[1 + #inst_names - i] .. "_nnl," ..
+ inst_names[2 + #inst_names - i] .. "_stk,+"
+ )
+ end
+ end
+
+ -- create LINE and GPRINT statements for each instance
+ for i, source in ipairs(opts.sources) do
+
+ local legend = string.format(
+ "%-" .. longest_name .. "s",
+ source.name
+ )
+
+ local numfmt = opts.number_format or "%6.1lf"
+
+ local line_color
+ local area_color
+
+ -- find color: try source, then opts.colors; fall back to random color
+ if type(source.color) == "string" then
+ line_color = source.color
+ area_color = self.colors:from_string( line_color )
+ elseif type(opts.colors[source.name:gsub("[^%w]","_")]) == "string" then
+ line_color = opts.colors[source.name:gsub("[^%w]","_")]
+ area_color = self.colors:from_string( line_color )
+ else
+ area_color = self.colors:random()
+ line_color = self.colors:to_string( area_color )
+ end
+
+ -- derive area background color from line color
+ area_color = self.colors:to_string( self.colors:faded( area_color ) )
+
+
+ self:_push( "AREA:" .. inst_names[i] .. "_stk#" .. area_color )
+ self:_push( "LINE1:" .. inst_names[i] .. "_stk#" .. line_color .. ":" .. legend )
+ self:_push( "GPRINT:" .. inst_names[i] .. "_min:MIN:" .. numfmt .. " Min" )
+ self:_push( "GPRINT:" .. inst_names[i] .. "_avg:AVERAGE:" .. numfmt .. " Avg" )
+ self:_push( "GPRINT:" .. inst_names[i] .. "_max:MAX:" .. numfmt .. " Max" )
+ self:_push( "GPRINT:" .. inst_names[i] .. "_avg:LAST:" .. numfmt .. " Last\\l" )
+ end
+
+ return images
+end
+
+function Graph.render( self, host, plugin, plugin_instance )
+
+ dtype_instances = dtype_instances or { "" }
+ local pngs = { }
+
+ -- check for a whole graph handler
+ local plugin_def = "luci.statistics.rrdtool.definitions." .. plugin
+ local stat, def = pcall( require, plugin_def )
+
+ if stat and def and type(def.rrdargs) == "function" then
+ for i, opts in ipairs( self:_forcelol( def.rrdargs( self, host, plugin, plugin_instance, dtype ) ) ) do
+ for i, png in ipairs( self:_generic( opts ) ) do
+ table.insert( pngs, png )
+
+ -- exec
+ self:_rrdtool( png )
+
+ -- clear args
+ self:_clearargs()
+ end
+ end
+ else
+
+ -- no graph handler, iterate over data types
+ for i, dtype in ipairs( self.tree:data_types( plugin, plugin_instance ) ) do
+
+ -- check for data type handler
+ local dtype_def = plugin_def .. "." .. dtype
+ local stat, def = pcall( require, dtype_def )
+
+ if stat and def and type(def.rrdargs) == "function" then
+ for i, opts in ipairs( self:_forcelol( def.rrdargs( self, host, plugin, plugin_instance, dtype ) ) ) do
+ for i, png in ipairs( self:_generic( opts ) ) do
+ table.insert( pngs, png )
+
+ -- exec
+ self:_rrdtool( png )
+
+ -- clear args
+ self:_clearargs()
+ end
+ end
+ else
+
+ -- no data type handler, fall back to builtin definition
+ if type(self.defs.definitions[dtype]) == "table" then
+
+ -- iterate over data type instances
+ for i, inst in ipairs( self.tree:data_instances( plugin, plugin_instance, dtype ) ) do
+
+ local title = self:mktitle( host, plugin, plugin_instance, dtype, inst )
+ local png = self:mkpngpath( host, plugin, plugin_instance, dtype, inst )
+ local rrd = self:mkrrdpath( host, plugin, plugin_instance, dtype, inst )
+
+ self:_push( { "-t", title } )
+ self:_push( self.defs.definitions[dtype] )
+
+ table.insert( pngs, png )
+
+ -- exec
+ self:_rrdtool( png, rrd )
+
+ -- clear args
+ self:_clearargs()
+ end
+ end
+ end
+ end
+ end
+
+ return pngs
+end
+