#! /usr/local/bin/ruby
# Copyright (c) 2006 Frédéric Senault. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of Frédéric Senault or any contributors may be
# used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
$LOAD_PATH << File.dirname($0)
require 'syslog'
require 'common'
require 'cgi'
# The part responsible for the interactions with rrdtool graph and the web server.
class GMain < Main
# Loads the graphers and instanciate them ; sets up a few default
# config options, too.
def loadgraphers()
$conf[:legends] = [ RRLegend.new('Maximum', 'max', :MAXIMUM),
RRLegend.new('Moyenne', 'avg', :AVERAGE),
]
Dir.glob($conf[:graphpath]) do |f|
$mtime = File.mtime(f)
begin
require "#{f}"
rescue LoadError => err
puts err
end
end
$conf[:graphs] = []
Grapher.globgraphs.sort! { |a, b| a.order <=> b.order }
Grapher.globgraphs.each do |g|
ng = g.new
ng.mtime = Grapher.globtimes[g.to_s]
$conf[:graphs].push(ng)
end
end
# The first method to be called. Parses the link to know if we're at the
# general menu step, or looking at a specific graph (grapher only).
def parsequery()
c = CGI.new()
if(c.has_key?('page')) then
p = c['page']
f = ( c['fch'] == '' ? p : c['fch'] )
d = ( c['debug'] != '' )
d = true if(ENV['HTTP_USER_AGENT'] =~ \
/(Crawler|Googlebot|htdig|Inktomi|Scooter|Slurp|Bandit|Jeeves|msnbot)/i)
if((p + f) =~ /\^[hs]\^/) then
r = ENV['HTTP_REFERER']
if(r.nil?) then
l = "#{$conf[:indexurl]}"
else
y= { 'h' => '', 's' => '' }
{ 'h' => 'host' ,
's' => 'service' }.each do |k, v|
if(r =~ /[?&]#{v}=([^&]+)/) then
y[k] = $1.to_s.normalize()
end
p.gsub!(/\^#{k}\^/, y[k])
f.gsub!(/\^#{k}\^/, y[k])
end
l = "#{$conf[:graphurl]}?page=#{p}&fch=#{f}"
end
puts "Location: #{l}"
puts
else
puts 'Content-Type: text/html'
puts
graphpage(p, f, d)
end
else
puts 'Content-Type: text/html'
puts
graphindex()
end
end
# Used to generate the general index of the graphs ; calls every plugin in turn to
# see what they have to offer.
def graphindex()
puts "
"
ig = {}
dolink = lambda do |g, f|
g.index(f) do |l, c|
ig[c] = "- " + \
c + "
\n"
end
end
$conf[:graphs].each do |lg|
if(lg.iterate) then
Dir.glob($conf[:rrdspath] + '/*.rrd') do |lf|
lf = File.basename(lf, '.rrd')
dolink.call(lg, lf)
end
else
dolink.call(lg, '.')
end
end
ig.keys.sort.each do |k|
puts ig[k]
end
puts "
"
end
# Used to display one graphs page.
def graphpage(link, file, debug = false)
file = link if(file.nil?)
RRG.resetcol
$conf[:graphs].each do |gfr|
id = []
gfr.register(link) do |i|
id.push(i)
end
if (id.length > 0) then
t = ''
tg = {}
gfr.index(file) do |nl, t|
if(nl == link) then
break
end
end
id.each do |i|
gfx = RRGGroup.new()
gfx.debug = debug
gfr.graph(link, i, gfx, file)
tg[i] = gfx
end
gfr.initrras
puts "#{CGI.escapeHTML(t)}
"
gfr.rras.each do |r|
puts "#{r.title}
"
id.each do |i|
puts ""
op, tn = tg[i].to_rrdtool(link, i, gfr, r)
puts op
puts "
"
puts "#{tn}
" unless(tn.nil? || tn == '')
end
end
break
end
end
end
end
# The main grapher class ; it has to be inherited by all the plugins.
# Fort the main page, the flow is :
# 1. Query the iterate property.
# 2. If it is true, iterate over the filenames in the $conf[:rrdspath]
# directory and call the index method with the filename.
# Otherwise, try once with a single period (.).
# 3. If the index has something to do with that RRD, it has to yield a link id and
# a caption
# 4. All the captions are sorted, and used to make the main menu.
#
# For each graph, the flow is :
# 1. Query the register method, with the link id.
# 2. If the plugin recognises the link, it has to yield one or more graph identifiers.
# 3. The index method is queried again, to yield the title of the page.
# 4. A RRGGroup object is created per id, to hold the graph informations.
# 5. The graph method of the plugin is called, with the link, the id, the RRGGroup
# object, and the filename.
# 6. The graph method is supposed to call one or more of RRGGroup#adddef,
# RRGGroup#addcdef, RRGGroup#addgraph.
# 7. At last, the subtitle method is called with the group id to decide the title that
# will be written inside the graph.
class Grapher
# The list of round-robin archives to graph. It should be the same
# archives than the ones stored in the file by the testers, but it's not
# strictly necessary. By default, the list is $conf[rras]. Alter with
# addrra.
attr_reader :rras
# The last modification time of the plugin source file. Allows to regenerate
# the graphs of a page if the code is modified. Otherwise, some discrepancies
# will appear, mostly in the longer term graphs.
attr_accessor :mtime
@@ggraphs = []
@@gtimes = {}
# Creation of a new grapher.
def initalize()
@rras = nil
end
# This is triggered when the class is inherited ; it allows the class to maintain
# a class variable to store it's children.
def Grapher.inherited(c)
@@ggraphs.push(c)
@@gtimes[c.to_s] = $mtime
end
# The list of plugins.
def Grapher.globgraphs
@@ggraphs
end
# The modification times of the plugins.
def Grapher.globtimes
@@gtimes
end
# The order in which the plugins will be interrogated (lowest order first).
def self.order
999
end
# If false, the plugin's method index is called only once. Otherwise,
# it's called once per RRD file. By default, true.
def iterate
true
end
# Used to write the title inside the graph ; defauts to the name of the class
# followed by the identifier.
def subtitle(id)
self.class.to_s + ', ' + id.to_s
end
# Used to generate the main menu page, and the title of each graph page. Must
# be implemented, and must yield two parameters : the link id, and the caption
# of the link / title of the page.
def index(file)
raise "index must be implemented"
end
# Used with the link id, to find all the distinct graph groups inside a page.
# It must only respond if the link is appropriate, by yielding one ore more
# group identifiers. Must be implemented.
def register(link)
raise "register must be implemented"
end
# The main graphing method. Gets the link id, the group id, the RRGGroup
# object itself, and the file name that triggered the link. Must be
# implemented.
def graph(link, id, gfx, file)
raise "graph must be implemented"
end
# Called internally ; intiliazes the rra array to the default if it
# has not been touched by the plugin.
def initrras()
@rras = $conf[:rras] if(@rras.nil?)
end
# Adds a customized rra ; see RRA#new for the parameters ; if this method
# is called once, the default $conf[:rras] is not used anymore.
def addrra(funct = :AVERAGE, xff = 0.5, steps = nil, \
rows = nil, title = nil, start = nil)
@rras ||= []
@rras.push(RRA.new(funct, xff, steps, rows, title, start))
end
# Helper method which returns the correct rpn function to implement this ruby
# equivalent :
# ( value.unknown? ? uvalue : value )
def rpn_ifu(value, uvalue)
"#{value},UN,#{uvalue.to_s},#{value},IF"
end
# Helper method which returns the correct rpn function to implement this ruby
# equivalent :
# ( value.unknown? ? 0 : value )
def rpn_zu(value)
rpn_ifu(value, 0)
end
# Helper method which returns the correct rpn function to implement a sum
# of an array of values (which is at first flattened). If an unknown value
# is seen, it's assumed to be 0.
def rpn_sum(*values)
v = values.flatten
v[1..-1].inject(rpn_ifu(v[0], 0)) { |s, e| s += ',' + rpn_ifu(e, 0) + ',+' }
end
# Helper method which returns the correct rpn function to implement the average
# of an array of values (which is at first flattened). If an unknown value
# is seen, it's assumed to be 0.
def rpn_avg(*values)
v = values.flatten
rpn_sum(v) + ',/,' + v.length.to_i
end
end
# Represents a group of graphs with the same structure, drawn over different
# periods (according to the Grapher#rra array). The properties to use from the
# plugins are :
# - logarithmic : to set a logarithmic scale
# - upperbound, lowerbound, strict : the bounding values, and how they must be
# enforced.
# - percent : when set to true, enforces bounds of [0..100].
# - base : the base of the unit (usually 1000 - the default, or 1024).
# - colors : with a symbol parameter, to set the general drawing area colors
# (can be set generally in $config[:colors])
# The methods to use are :
# - adddef : to read data from a rrd file (must have at least one, returns a
# RRDef object)
# - addcdef : to compute a value from other defs and rpn calculations (returns
# a RRCDef object).
# - addlegend : the legend part of the graph is computed from the visible defs
# or cdefs, with one column per legend object ; returns a RRLegend object ;
# if left undefined, uses $conf[:legends].
# - addgraph : to actually draw something.
# An optional helper function is defined :
# - fetch_data : which allows to query existing RRD databases to fech values,
# over a defined sample period.
class RRGGroup
# Use a logarithmic Y scale (default : false) ?
attr_accessor :logarithmic
# The upper limit of the Y scale (automatically computed by default).
attr_accessor :upperbound
# The lower limit of the Y scale (automatically computed by default).
attr_accessor :lowerbound
# Must the upper and lower limits we enforced if values are out of bound
# (not by default).
attr_accessor :strict
# The base for the SI units (1000 by default).
attr_accessor :base
# In debugging mode, the definition of the graph is issued instead of the
# graph itself.
attr_accessor :debug
# An array of RRLegend objects.
attr_reader :legends
# The empty RRGGroup is created ; usually, it's done internally; it can be
# useful to create one by hand to use the fetch_data method.
def initialize()
@defs = []
@cdefs = []
@graphs = []
@colors = $conf[:colors].dup
@legends = nil
@title = nil
@strict = false
@logarithmic = false
@upperbound = nil
@lowerbound = nil
@base = nil
@debug = false
#resetcol
end
# When set to true, sets the graph in percent mode (bounds at [0,
# 100], strictly enforced).
def percent=(v)
if(v) then
@strict = true
@lowerbound = 0
@upperbound = 100
else
@strict = false
@lowerbound = nil
@upperbound = nil
end
end
# Reset the rotation of the global colors array between each graph.
def resetcol
RRG.resetcol
end
# Add a graph element ; returns a RRG object (see RRG#new for the
# parameters).
def addgraph(type, data, color, label, stack = nil)
stack = false if(@graphs.length == 0)
g = RRG.new(type, data, color, label, stack)
@graphs.push(g)
g
end
# Add a definition ; returns a RRDef object (see RRDef#new for the
# parameters)
def adddef(name = nil, rrd = nil, data = nil, funct = :AVERAGE, label = '_', fmt = '%7.2lf')
d = RRDef.new(name, rrd, data, funct, label, fmt)
@defs.push(d)
d
end
# Add a computed line ; returns a RRCDef object (see RRCDef#new for the
# parameters)
def addcdef(name = nil, expr = nil, label = '_', fmt = '%7.2lf')
c = RRCDef.new(name, expr, label, fmt)
@cdefs.push(c)
c
end
# Add a legend group ; returns a RRLegend object (see RRLegend#new for the
# parameters ; by default, $conf[:legends] is used)
def addlegend(label, funct)
@legendss = [] if(@legendss.nil?)
l = RRLegend.new(label, funct)
@legendss.push(l)
l
end
# Removes any legend groups.
def nolegend
@legendss = []
end
# Sets the color of diverse elements of the drawing area. The accepted
# colortags are : :BACK, :CANEVAS, :SHADEA,
# :SHADEB, :GRID, :MGRID, :FONT,
# :AXIS, :FRAME, :ARROW. The colors themselves
# can be RGB triplets (with an optional alpha channel), or symbols present
# in the $color variable (see Main#colors).
def setcolor(colortag, color = nil)
if(color.nil?) then
@colors.delete(colortag)
else
if($colors.has_key?(color)) then
@colors[colortag] = $colors[color]
else
@colors[colortag] = color
end
end
end
# Helper function to compute an expression list, a hash of variable names
# associated with rpn expressions based on the definitions of the RRGroup.
# It evaluates the expression on a sample of (by default 10) data
# points, using the specified archive (by default, the second more precise
# one, usually the monthly one). It returns a hash table with the
# variables and their computed results. If something fails, the special
# key ##err## contains the message and the rrdtool
# invocation line.
def fetch_data(exprlist, sample = 10, rra = nil)
rd = {}
c = []
rra ||= $conf[:rras][1]
sample ||= 10
c.push("graph #{$conf[:imgpath]}/tmpfile")
c.push("#{$conf[:rrgopts]}")
c.push("-e '#{rra.end}'")
c.push("-s '#{rra.end - (sample * rra.sec_steps)}'")
@defs.each do |d|
c.push(d.to_rrdef)
end
@cdefs.each do |d|
c.push(d.to_rrcdef)
end
exprlist.each do |v, e|
t = RRCDef.new(v, e, nil, "%lf")
c.push(t.to_rrvdef)
c.push(t.to_tempprint)
end
c.flatten!
lc = c.join(' ')
od = @debug
e = ''
if(!od) then
r, e = system3($conf[:rrdtool] + ' ' + lc)
if(e != '') then
od = true
else
r.scan(/@@([^=]+)=([^@]+)@@/) do |k, v|
rd[k] = v.to_f unless(v == 'nan')
end
end
end
if(od) then
r = ""
r << "Graphe #{@name} : #{e}
"
r << c.join("\n").gsub(/[<>&\n]/) do |i|
case i
when "<" then "<"
when ">" then ">"
when "&" then "&"
when "\n" then "
\n"
end
end
r << "
"
rd['##err##'] = r
end
rd
end
# The main graphing function, called internally. It generates the
# rrdtool graph command line, and returns the link to the
# graph, or the error message with the command line.
def to_rrdtool(link, id, gfr, rra)
@legends = $conf[:legends] if(@legends.nil?)
c = []
tt = "#{gfr.subtitle(id)} (#{rra.title})"
th = CGI.escapeHTML(tt)
fn = $conf[:imgpath] + '/' + link + rra.start.gsub(/-/, '_') + \
'_' + id.to_s + '.' + $conf[:rrgfmt]
c.push("graph #{fn}")
c.push("#{$conf[:rrgopts]}")
c.push("-e '#{rra.end}'")
c.push("-s '#{rra.start}'")
c.push("-a #{$conf[:rrgfmt].upcase}")
c.push("-w #{$conf[:rrgsizex]}")
c.push("-h #{$conf[:rrgsizey]}")
c.push("-f \"
\"")
c.push("-t \"#{tt}\"")
c.push("-o") if(@logarithmic)
c.push("-z") if(File.exists?(fn) && File.mtime(fn) > gfr.mtime)
c.push("-u #{@upperbound}") if(@upperbound)
c.push("-l #{@lowerbound}") if(@lowerbound)
c.push("-r") if(@strict)
c.push("-b #{@base}") if(@base)
@colors.each do |t, c|
c.push("-c #{t.to_s}##{c.to_s}")
end
@defs.each do |d|
c.push(d.to_rrdef)
end
@cdefs.each do |d|
c.push(d.to_rrcdef)
end
@legends.each do |l|
(@defs + @cdefs).each do |d|
c.push(l.to_vdef(d)) unless(d.label.nil?)
end
end
c.push(RRLegend.make_comment(@legends, (@defs + @cdefs)))
@graphs.each do |g|
c.push(g.to_rrg)
end
c.flatten!
lc = c.join(' ')
od = @debug
e = ''
if(!od) then
r, e = system3($conf[:rrdtool] + ' ' + lc)
if(e != '') then
od = true
else
r.gsub!(/^\d+x\d+[\r\n]*/, '')
tn = (File.exists?(fn) ? File.mtime(fn).to_s : 'inconnu')
end
end
if(od) then
r = ""
r << "Graphe #{@name} : #{e}
"
r << c.join("\n").gsub(/[<>&]/) do |i|
case i
when "<" then "<"
when ">" then ">"
when "&" then "&"
end
end
r << "
"
tn = ''
end
[ r, tn ]
end
end
# This class represents a rrdtool def, that is the simplest
# kind of data, directly extracted from a data source.
# It is associated with a label and a format for the legend generation.
class RRDef
# The name of the variable, that will then be used for cdefs (via RRCDef
# objects) or graphs (via RRGraph objects).
attr_accessor :name
# The source name ; it is the file name, stripped of its path and its
# .rrd extension
attr_accessor :rrd
# The data source to use.
attr_accessor :data
# The consolidation function, which must correspond to an existing archive
# in the file ; may be :AVERAGE, :MAX, :MIN
# or :LAST.
attr_accessor :funct
# The label of the serie in the legend ; if nil, it will not
# generate a legend.
attr_accessor :label
# The format of the values in the legend.
attr_accessor :fmt
# Creates a new RRDef ; by default, the function will be :AVERAGE,
# the label will be the variable name and the format %7.2lf.
def initialize(name = nil, rrd = nil, data = nil, \
funct = :AVERAGE, label = '_', fmt = '%7.2lf')
@name = name
@rrd = rrd
@data = data
@funct = funct
@label = ( label == '_' ? data : label )
@fmt = fmt
end
# Used internally to generate the rrdtool graph command line.
def to_rrdef
[ 'DEF', @name + '=' + $conf[:rrdspath] + '/' + @rrd + '.rrd', \
@data, @funct.to_s ].join(':')
end
end
# This class represents a rrdtool cdef, that is a computed
# serie based on defined rrdtool def series, and a rpn formula.
# It is associated with a label and a format for the legend generation.
# The Grapher#rpn_ifu, Grapher#rpn_zu, Grapher#rpn_sum and Grapher#rpn_avg
# helper functions can be used to create rpn expressions.
class RRCDef
# The variable name.
attr_accessor :name
# The rpn expression.
attr_accessor :expr
# The label of the serie in the legend ; if nil, it will not
# generate a legend.
attr_accessor :label
# The format of the values in the legend.
attr_accessor :fmt
# Creates a new RRCDef ; the label and format defaults are the
# same than for the RRDef, that is the variable name and %7.2lf.
def initialize(name = nil, expr = nil, label = '_', fmt = '%7.2lf')
@name = name
@expr = expr
@label = ( label == '_' ? name : label )
@fmt = fmt
end
# Used internally to generate the rrdtool graph line.
def to_rrcdef
'CDEF:' + @name + '=' + @expr
end
# Used internally to generate the rrdtool graph line, this part
# for the legends.
def to_rrvdef
'VDEF:' + @name + '=' + @expr
end
# Used internally to generate the rrdtool graph line, this part
# being used for RRGGroup#fetch_data.
def to_tempprint
'PRINT:' + @name + ":@@#{@name}=#{@fmt}@@"
end
end
# A legend group object, used to generate automatically the legend below the
# graphs. This object represents a function and labels that will be applied
# to all the RRDef and RRCDef items that define their own labels. The legend
# label is used above the columns, while the data labels are used before the
# lines.
class RRLegend
# The label is placed above the columns.
attr_accessor :label
# The tag is concatenated to each variable name to create an unique
# name.
attr_accessor :tag
# The consolidation function ; can be :AVERAGE, :MINIMUM,
# :MAXIMUM, :LAST, :FIRST, :TOTAL, and
# a few otehrs.
attr_accessor :funct
# Create the RRDLegend object. No possible sensible defaults here.
def initialize(label = nil, tag = nil, funct = nil)
@label = label
@tag = tag
@funct = funct
end
# Internally used to generate the rrdtool graph function,
# along with a RRDef or RRCDef object.
def to_vdef(d)
'VDEF:' + d.name + '_' + @tag + '=' + d.name + ',' + @funct.to_s
end
# Internally used to estimate the width of a format string.
def RRLegend.lfmt(f)
t = f.dup
t.gsub!(/%([0-9])*\.([0-9])*(?:lf|le)/) do
'x' * (($1 && $1 != '') ? $1.to_i : 5)
end
t.gsub!(/%(s|S)/) { 'x' }
t.length
end
# Internally used to output the comment and gprint
# parts in the rrdtool graph line. If there are two values,
# display the labels on the center, with the values on each side.
# Otherwise, go to a more classical labels on the left, and column of
# values on the right.
def RRLegend.make_comment(legends, defs)
c = []
dw = legends.inject(0) do |m, l|
m = (l.label.length < m ? m : l.label.length)
end
dw = defs.inject(dw) do |m, d|
lf = (d.fmt.nil? ? 0 : RRLegend.lfmt(d.fmt))
m = (lf < m ? m : lf)
end
c.push('COMMENT:"\s"')
if(legends.length == 2) then
lw = $conf[:rrgchars] - dw * 2
c.push('COMMENT:"' + \
legends[0].label.rjust(dw) + \
' ' * lw + \
legends[1].label.rjust(dw) + ' \c"')
defs.reject { |d| d.label.nil? }.each do |d|
c.push([ 'GPRINT', \
d.name + '_' + legends[0].tag, \
'"' + (' ' * (dw - RRLegend.lfmt(d.fmt))) + d.fmt + \
'\g"' ].join(':'))
c.push('COMMENT:"' + (' ' + d.label + ' ').center(lw, '.') + '\g"')
c.push([ 'GPRINT', \
d.name + '_' + legends[1].tag, \
'"' + (' ' * (dw - RRLegend.lfmt(d.fmt))) + d.fmt + \
'\c"' ].join(':'))
end
else
dw += 2
lw = $conf[:rrgchars] - dw * legends.length
c.push('COMMENT:"' + \
' ' * lw + \
legends.inject('') { |m, l| m += l.label.rjust(dw) } + \
' \c"')
defs.reject { |d| d.label.nil? }.each do |d|
c.push('COMMENT:"' + (d.label + ' ').ljust(lw, '.') + '\g"')
legends.each_index do |i|
c.push([ 'GPRINT', \
d.name + '_' + legends[i].tag, \
'"' + (' ' * (dw - RRLegend.lfmt(d.fmt))) + d.fmt + \
( i == legends.length - 1 ? '\c"' : '\g"') ].join(':'))
end
end
end
c
end
end
# This represents one serie of data on the graph, based on a RRDef or RRCDef.
# If it has a color and a label, it will genereate a small legend below with the
# color. It can also automatically attribute colors to the graphs, trying not
# to reuse colors even between different groups.
class RRG
@@ncol = []
# Used to reinitialize the order of the automatic color chooser.
def RRG.resetcol
@@ncol = [ :blue, :green, :red, :yellow,
:cyan, :magenta, :lightgray, :darkgray,
:orange, :violet ]
end
# The type of the element ; can be :AREA, :LINE, with
# an optional width parameter (e.g. :LINE3).
attr_accessor :type
# The name of the RRDef or RRCDef to plot.
attr_accessor :data
# The color of the element.
attr_accessor :color
# The label to display in the legend.
attr_accessor :label
# Should the element be stacked on top of the previous.
attr_accessor :stack
# Create a new RRG ; the default type is a line, the color will be
# chosen so that it's unique between graphs in the group, the label
# will be the data name by default, and it will not stack by default ;
# for the first serie of a graph, the stack parameter is ignored.
def initialize(type = :LINE, data = nil, color = nil, label = nil, stack = nil)
@type = ( type.nil? ? :LINE : type )
@data = data
if(color.nil?) then
@color = $colors[@@ncol.shift]
elsif($colors.has_key?(color)) then
@color = $colors[color]
@@ncol.delete(color)
else
@color = color
end
@label = label || data
@stack = stack || false
end
# Called internally to generate the rrdtool graph command line.
def to_rrg
[ @type.to_s, @data + ( @color == '' ? '' : '#' + @color ), \
'"' + @label + '"' + (@stack ? ':STACK' : '') ].join(':')
end
end
main = GMain.new()
main.readparam()
main.loadgraphers()
main.parsequery()