* ffluci/statistcs: implement rrdtool stuff, extend controller to public pages - wip

This commit is contained in:
Jo-Philipp Wich 2008-05-25 02:37:39 +00:00
parent 32aa488d9b
commit 1e70a05156
12 changed files with 3801 additions and 47 deletions

View File

@ -1,19 +1,14 @@
--[[
Luci controller for statistics
Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
$Id$
]]--
module("ffluci.controller.luci_statistics.luci_statistics", package.seeall)
fs = require("ffluci.fs")
tpl = require("ffluci.template")
local fs = require("ffluci.fs")
local tpl = require("ffluci.template")
local rrd = require("ffluci.statistics.rrdtool")
local data = require("ffluci.statistics.datatree").Instance()
function _entry( path, ... )
if fs.isfile( "/usr/lib/collectd/" .. path[4] .. ".so" ) then
local file = path[4] or path[3]
if fs.isfile( "/usr/lib/collectd/" .. file .. ".so" ) then
entry( path, ... )
end
end
@ -32,10 +27,11 @@ function index()
entry({"admin", "statistics", "system"}, statistics_systemplugins, "Systemplugins", 30)
_entry({"admin", "statistics", "system", "exec"}, cbi("luci_statistics/exec"), "Exec", 10)
_entry({"admin", "statistics", "system", "email"}, cbi("luci_statistics/email"), "E-Mail", 20)
_entry({"admin", "statistics", "system", "df"}, cbi("luci_statistics/df"), "Speicherplatz", 30)
_entry({"admin", "statistics", "system", "disk"}, cbi("luci_statistics/disk"), "Datenträger", 40)
_entry({"admin", "statistics", "system", "irq"}, cbi("luci_statistics/irq"), "Interrupts", 50)
_entry({"admin", "statistics", "system", "processes"}, cbi("luci_statistics/processes"), "Prozesse", 60)
_entry({"admin", "statistics", "system", "cpu"}, cbi("luci_statistics/cpu"), "Prozessor", 30)
_entry({"admin", "statistics", "system", "df"}, cbi("luci_statistics/df"), "Speicherplatz", 40)
_entry({"admin", "statistics", "system", "disk"}, cbi("luci_statistics/disk"), "Datenträger", 50)
_entry({"admin", "statistics", "system", "irq"}, cbi("luci_statistics/irq"), "Interrupts", 60)
_entry({"admin", "statistics", "system", "processes"}, cbi("luci_statistics/processes"), "Prozesse", 70)
entry({"admin", "statistics", "network"}, statistics_networkplugins, "Netzwerkplugins", 40)
_entry({"admin", "statistics", "network", "interface"}, cbi("luci_statistics/interface"), "Schnittstellen", 10)
@ -44,6 +40,14 @@ function index()
_entry({"admin", "statistics", "network", "tcpconns"}, cbi("luci_statistics/tcpconns"), "Verbindungen", 40)
_entry({"admin", "statistics", "network", "ping"}, cbi("luci_statistics/ping"), "Ping", 50)
_entry({"admin", "statistics", "network", "dns"}, cbi("luci_statistics/dns"), "DNS", 60)
-- public views
entry({"freifunk", "statistics"}, statistics_index, "Statistiken", 80)
for i, plugin in ipairs( data:plugins() ) do
_entry({"freifunk", "statistics", plugin}, statistics_render, plugin, i)
end
end
@ -86,3 +90,18 @@ function statistics_networkplugins()
tpl.render("admin_statistics/networkplugins", {plugins=plugins})
end
function statistics_render()
local plugin = ffluci.dispatcher.request[3]
local images = { }
for i, inst in ipairs( data:plugin_instances( plugin ) ) do
local graph = rrd.Graph()
for i, img in ipairs( graph:render( "OpenWrt", plugin, inst ) ) do
table.insert( images, img )
end
end
tpl.render("public_statistics/graph", { images=images, plugin=plugin } )
end

View File

@ -0,0 +1,134 @@
module("ffluci.statistics.datatree", package.seeall)
local util = require("ffluci.util")
local sys = require("ffluci.sys")
local fs = require("ffluci.fs")
local uci = require("ffluci.model.uci").Session()
local sections, names = uci:sections( "luci_statistics" )
Instance = util.class()
function Instance.__init__( self, host )
self._host = host or sections.collectd.Hostname or sys.hostname()
self._libdir = sections.collectd.PluginDir or "/usr/lib/collectd"
self._rrddir = sections.collectd_rrdtool.DataDir or "/tmp"
self._libdir = self._libdir:gsub("/$","")
self._rrddir = self._rrddir:gsub("/$","")
self._plugins = { }
self:_scan()
end
function Instance._mkpath( self, plugin, pinstance )
local dir = self._rrddir .. "/" .. self._host
if type(plugin) == "string" and plugin:len() > 0 then
dir = dir .. "/" .. plugin
if type(pinstance) == "string" and pinstance:len() > 0 then
dir = dir .. "-" .. pinstance
end
end
return dir
end
function Instance._notzero( self, table )
for k in pairs(table) do
return true
end
return false
end
function Instance._scan( self )
for i, plugin in ipairs( fs.dir( self._libdir ) ) do
if plugin:match("%w+.so") then
self._plugins[ plugin:gsub(".so", "") ] = { }
end
end
for plugin, instances in pairs( self._plugins ) do
for i, dir in ipairs( fs.dir( self:_mkpath() ) ) do
if dir:find( plugin .. "%-" ) or dir == plugin then
local instance = ""
if dir ~= plugin then
instance = dir:gsub( plugin .. "%-", "", 1 )
end
instances[instance] = { }
end
end
for instance, data_instances in pairs( instances ) do
for i, file in ipairs( fs.dir( self:_mkpath( plugin, instance ) ) ) do
if file:find("%.rrd") then
file = file:gsub("%.rrd","")
local data_type
local data_instance
if file:find("%-") then
data_type = file:gsub( "%-.+","" )
data_instance = file:gsub( "[^%-]-%-", "", 1 )
else
data_type = file
data_instance = ""
end
if not data_instances[data_type] then
data_instances[data_type] = { data_instance }
else
table.insert( data_instances[data_type], data_instance )
end
end
end
end
end
end
function Instance.plugins( self )
local rv = { }
for plugin, val in pairs( self._plugins ) do
if self:_notzero( val ) then
table.insert( rv, plugin )
end
end
return rv
end
function Instance.plugin_instances( self, plugin )
local rv = { }
for instance, val in pairs( self._plugins[plugin] ) do
table.insert( rv, instance )
end
return rv
end
function Instance.data_types( self, plugin, instance )
local rv = { }
for type, val in pairs( self._plugins[plugin][instance] ) do
table.insert( rv, type )
end
return rv
end
function Instance.data_instances( self, plugin, instance, type )
local rv = { }
for i, instance in ipairs( self._plugins[plugin][instance][type] ) do
table.insert( rv, instance )
end
return rv
end

View File

@ -0,0 +1,247 @@
module("ffluci.statistics.rrdtool", package.seeall)
require("ffluci.statistics.datatree")
require("ffluci.statistics.rrdtool.colors")
require("ffluci.statistics.rrdtool.definitions")
require("ffluci.util")
require("ffluci.bits")
require("ffluci.fs")
Graph = ffluci.util.class()
function Graph.__init__( self, timespan, opts )
opts = opts or { }
opts.width = opts.width or "400"
self.colors = ffluci.statistics.rrdtool.colors.Instance()
self.defs = ffluci.statistics.rrdtool.definitions.Instance()
self.tree = ffluci.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._rrdtool( self, png, rrd )
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
local rrdtool = io.popen( cmdline )
rrdtool:read("*a")
rrdtool:close()
end
function Graph._generic( self, optlist )
local images = { }
if type(optlist[1]) ~= "table" then
optlist = { optlist }
end
for i, opts in ipairs(optlist) do
-- 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
self:_push( "DEF:" .. inst_names[i] .. "_min=" ..source.rrd .. ":value:MIN" )
self:_push( "DEF:" .. inst_names[i] .. "_avg=" ..source.rrd .. ":value:AVERAGE" )
self:_push( "DEF:" .. inst_names[i] .. "_max=" ..source.rrd .. ":value: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
if type(opts.colors[source.name]) == "string" then
line_color = opts.colors[source.name]
area_color = self.colors:from_string( line_color )
else
area_color = self.colors:random()
line_color = self.colors:to_string( area_color )
end
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
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 = "ffluci.statistics.rrdtool.definitions." .. plugin
local stat, def = pcall( require, plugin_def )
if stat and def and type(def.rrdargs) == "function" then
for i, png in ipairs( self:_generic( def.rrdargs( self, host, plugin, plugin_instance, dtype ) ) ) do
table.insert( pngs, png )
-- exec
self:_rrdtool( png )
-- clear args
self:_clearargs()
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, png in ipairs( self:_generic( def.rrdargs( self, host, plugin, plugin_instance, dtype ) ) ) do
table.insert( pngs, png )
-- exec
self:_rrdtool( png )
-- clear args
self:_clearargs()
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

View File

@ -0,0 +1,59 @@
module("ffluci.statistics.rrdtool.colors", package.seeall)
require("ffluci.util")
require("ffluci.bits")
Instance = ffluci.util.class()
function Instance.from_string( self, s )
return {
ffluci.bits.Hex2Dec(s:sub(1,2)),
ffluci.bits.Hex2Dec(s:sub(3,4)),
ffluci.bits.Hex2Dec(s:sub(5,6))
}
end
function Instance.to_string( self, c )
return string.format(
"%02x%02x%02x",
math.floor(c[1]),
math.floor(c[2]),
math.floor(c[3])
)
end
function Instance.random( self )
local r = math.random(256)
local g = math.random(256)
local min = 1
local max = 256
if ( r + g ) < 256 then
min = 256 - r - g
else
max = 512 - r - g
end
local b = min + math.floor( math.random() * ( max - min ) )
return { r, g, b }
end
function Instance.faded( self, fg, opts )
opts = opts or {}
opts.background = opts.background or { 255, 255, 255 }
opts.alpha = opts.alpha or 0.25
if type(opts.background) == "string" then
opts.background = _string_to_color(opts.background)
end
local bg = opts.background
return {
( opts.alpha * fg[1] ) + ( ( 1.0 - opts.alpha ) * bg[1] ),
( opts.alpha * fg[2] ) + ( ( 1.0 - opts.alpha ) * bg[2] ),
( opts.alpha * fg[3] ) + ( ( 1.0 - opts.alpha ) * bg[3] )
}
end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
module("ffluci.statistics.rrdtool.definitions.cpu.cpu",package.seeall)
function rrdargs( graph, host, plugin, plugin_instance, dtype )
dtype_instances = { "idle", "nice", "system", "user" }
opts = { }
opts.sources = { }
opts.image = graph:mkpngpath( host, plugin, plugin_instance, dtype )
opts.title = graph:mktitle( host, plugin, plugin_instance, dtype )
opts.rrd = { "-v", "Percent" }
opts.colors = {
idle = 'ffffff',
nice = '00e000',
user = '0000ff',
wait = 'ffb000',
system = 'ff0000',
softirq = 'ff00ff',
interrupt = 'a000a0',
steal = '000000'
}
for i, inst in ipairs(dtype_instances) do
opts.sources[i] = {
name = inst,
rrd = graph:mkrrdpath( host, plugin, plugin_instance, dtype, inst )
}
end
return opts
end

View File

@ -0,0 +1,26 @@
module("ffluci.statistics.rrdtool.definitions.iptables.ipt_packets", package.seeall)
function rrdargs( graph, host, plugin, plugin_instance, dtype )
dtype_instances = graph.tree:data_instances( plugin, plugin_instance, dtype )
opts = { }
for i, inst in ipairs(dtype_instances) do
opts[i] = { }
opts[i].image = graph:mkpngpath( host, plugin, plugin_instance, dtype, inst )
opts[i].title = host .. ": Firewall - " .. inst:gsub("_"," ")
opts[i].rrd = { "-v", "Pakete/s" }
opts[i].colors = {
}
opts[i].sources = { {
name = inst,
rrd = graph:mkrrdpath( host, plugin, plugin_instance, dtype, inst )
} }
end
return opts
end

View File

@ -0,0 +1,31 @@
module("ffluci.statistics.rrdtool.definitions.processes.ps_state", package.seeall)
function rrdargs( graph, host, plugin, plugin_instance, dtype )
dtype_instances = {
"sleeping", "running", "paging", "blocked", "stopped", "zombies"
}
opts = { }
opts.sources = { }
opts.image = graph:mkpngpath( host, plugin, plugin_instance, dtype )
opts.title = host .. ": Prozesse"
opts.rrd = { "-v", "Anzahl" }
opts.colors = {
sleeping = "008080",
running = "008000",
paging = "ffff00",
blocked = "ff5000",
stopped = "555555",
zombies = "ff0000"
}
for i, inst in ipairs(dtype_instances) do
opts.sources[i] = {
name = inst,
rrd = graph:mkrrdpath( host, plugin, plugin_instance, "ps_state", inst )
}
end
return opts
end

View File

@ -0,0 +1,27 @@
module("ffluci.statistics.rrdtool.definitions.tcpconns.tcp_connections", package.seeall)
function rrdargs( graph, host, plugin, plugin_instance, dtype )
dtype_instances = {
"SYN_SENT", "SYN_RECV", "LISTEN", "ESTABLISHED", "LAST_ACK", "TIME_WAIT",
"CLOSING", "CLOSE_WAIT", "CLOSED", "FIN_WAIT1", "FIN_WAIT2"
}
opts = { }
opts.sources = { }
opts.image = graph:mkpngpath( host, plugin, plugin_instance, dtype )
opts.title = host .. ": TCP-Verbindungen - Port " .. plugin_instance
opts.rrd = { "-v", "Anzahl" }
opts.colors = {
}
for i, inst in ipairs(dtype_instances) do
opts.sources[i] = {
name = inst,
rrd = graph:mkrrdpath( host, plugin, plugin_instance, dtype, inst )
}
end
return opts
end

View File

@ -0,0 +1,25 @@
module("ffluci.statistics.rrdtool.definitions.wireless", package.seeall)
function rrdargs( graph, host, plugin, plugin_instance )
dtypes = { "signal_power", "signal_noise" }
opts = { }
opts.sources = { }
opts.image = graph:mkpngpath( host, plugin, plugin_instance, "wireless" )
opts.title = graph:mktitle( host, plugin, plugin_instance, "wireless" )
opts.rrd = { "-v", "dBm" }
opts.colors = {
signal_power = '0000ff',
signal_noise = 'ff0000'
}
for i, dtype in ipairs(dtypes) do
opts.sources[i] = {
name = dtype,
rrd = graph:mkrrdpath( host, plugin, plugin_instance, dtype )
}
end
return opts
end

View File

@ -0,0 +1,9 @@
<%+header%>
<h1>Statistik (<%=request%>)</h1>
<% for i, img in ipairs(images) do %>
<img src="<%=img:gsub("/tmp/rrdimg/OpenWrt","/img")%>" />
<% end %>
<%+footer%>

View File

@ -1,31 +0,0 @@
<h1><%:welcome Willkommen%>!</h1>
<p>
Du bist jetzt mit dem freien Funknetz
<a href="<%~freifunk.community.homepage%>"><%~freifunk.community.name%></a> verbunden.<br />
Wir sind ein experimentelles Gemeinschaftsnetzwerk, aber kein Internetanbieter.
</p>
<p>
Ein Zugang <strong>ins Internet</strong> ist trotzdem möglich,
da einige Freifunker ihre privaten Internetzugänge zur Verfügung stellen.
Diese Zugänge müssen sich hier alle teilen.
Bitte sei Dir dessen bewusst und verhalte Dich dementsprechend:
<ul>
<li>bitte <strong>keine Filesharing-Programme</strong> betreiben!</li>
<li>bitte <strong>keine unnötigen Downloads oder Streams</strong> starten!</li>
<li>bitte <strong>keine illegalen Aktivitäten</strong>!</li>
</ul>
</p>
<p>
Wenn Du unsere Idee gut findest, kannst Du uns unterstützen:
<ul>
<li><a href="<%~freifunk.community.homepage%>">Werde selbst Freifunker oder teile deinen Internetzugang!</a></li>
<li>Betreibe deine anderen WLAN-Geräte <em>NICHT</em> auf den Kanälen 1-5, diese stören oft unser Netz.</li>
</ul>
</p>
<p>
Mit einem Klick auf <em><%:accept Annehmen%></em> kannst du für <%~luci_splash.general.leasetime%> Stunden
über unser Netz das Internet verwenden. Dann wirst du erneut aufgefordet, diese Bedingungen zu akzeptieren.
</p>