<?php
/**
rrdtool - php class for interfacing to rrdtool
Version 0.1-alpha
Copyright (C) 2003 Greg MacLellan
Aug 27/2003

greg at mtechsolutions dot ca

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    http://www.gnu.org/licenses/lgpl.html


This is an interface to the excellent RRD Tool (www.rrdtool.com) program written by
Tobi Oetiker.

Yes, I know there is a php4 extension for rrdtool. I wrote this anyways, for a couple
reasons:
- As far as I can tell, the extension is experimental. I don't want to add something
  that's experimental to all php processes.
- I couldn't get the dynamic version to compile, and figured this would take less time
  than messing with it.
- This is a more portable solution, as you just need rrdtool and php, and don't need to
  specifically build the rrdtool extensions.
- These classes can easily be rewritten to use the extension functions, and you still
  get the benefit of OOP.


When called, this class opens a new pipe to rrdtool. Be sure to call rrdtool::close()
when finished. Using a pipe allows you to make multiple UPDATE calls (or whatever)
without using a ton of exec() calls.

Fairly simple. There's some rough examples at the bottom.

- Greg

*/


/** rrdtool functions
 */
class rrdtool {
    var 
$debug false;

    var 
$_pipes;
    var 
$_proc;

    
/** create a new rrdtool handle
     * @param string    $rrdtool_bin    The path to the rrdtool binary
     */
    
function rrdtool($rrdtool_bin) {
        
$descriptorspec = array(
           
=> array("pipe""r"),  // stdin is a pipe that the child will read from
           
=> array("pipe""w"),  // stdout is a pipe that the child will write to
           
=> array("pipe""w")   // stderr is a pipe that the child will write to
        
);

        
// proc_open rrdtool, using - as a parameter, to specify "remote control mode"
        
$this->_proc proc_open($rrdtool_bin." -"$descriptorspec$this->_pipes);
        
var_dump($this->_proc);
        
var_dump($this->_pipes);
    }

    function 
close() {
        
fclose($this->_pipes[0]);
        
fclose($this->_pipes[1]);
        
fclose($this->_pipes[2]);
        
proc_close($this->_proc);
    }

    function 
_run($command) {
        
fwrite($this->_pipes[0], $command."\n");
        
$data "";
        
$line "";
        while (
substr($line,0,2) != "OK") {
            
$line fgets($this->_pipes[1],1024);
            
$data .= $line;
        }
        if (
$this->debug) {
            
var_dump($data);
        }

        
// not sure if this is 100% safe or not, according to the docs,
        // it looks like it should be
        
return (substr($data,0,5) != "ERROR");
    }

    
/** Create a new rra
     * @param string    $filename    Full path and name of the rrd file to create
     * @param int        $start        Start time, timestamp or AT spec (--start)
     * @param int        $step        Step value, in seconds (--step)
     * @param array        $ds        Array of rrdtool_ds objects for specifying
     *                    data sources
     * @param array        $rra        Array of rrdtool_rra objects for specifying
     *                    round-robin archives
     */
    
function create($filename$start$step$ds$rra) {
        
$options = array();

        
// create DS options string
        
foreach ($ds as $rrd_ds) {
            
$options[] = "DS:".$rrd_ds->name.":".$rrd_ds->type.":".$rrd_ds->heartbeat.":".$rrd_ds->min.":".$rrd_ds->max;
        }

        
// create RRA options string
        
foreach ($rra as $rrd_rra) {
            
$options[] = "RRA:".$rrd_rra->cf.":".$rrd_rra->xff.":".$rrd_rra->steps.":".$rrd_rra->rows;
        }

        
$args "--step ".$step." ".implode(" ",$options);
        return 
$this->_run("create ".$filename." ".$args);

    }

    
/** Update data in an rra
     * @param string    $filename    Full path and name of the rrd file
     * @param array        $data        Data values. If array keys are specified,
     *                    they should be the DS names, and a template
     *                    will be used (--template).
     * @param int        $time        The update time to use, or null for 'N' (rrdtool
     *                    uses current time)
     */
    
function update($filename$data$time null) {
        
$args "";
        if (!isset(
$data[0])) {
            
// assume there are names for all the items, so use a template
            
$args .= "-t ".implode(":",array_keys($data))." ";
        }
        
$args .= (!empty($time) ? "N" $time).implode(":",$data);

        return 
$this->_run("update ".$filename." ".$args);
    }

    
/** Create a graph
     * This function can be called either instantized, or statically. If it's an instance,
     * $outputfile must be specified. If called statically, rrdtool is called directly by
     * an exec() system call (not via pipes, like normal), and if $outputfile is null, then
     * the raw image data will be returned, and no image file needs to be created on disk
     *
     * @param array        $options    General options. Should be specified using
     *                    their full option name as a key. For example, to
     *                    set "--upper-limit 10", use
     *                    array("upper-limit"=>10)
     * @param array        $items        An array of items inherited from the rrdtool_graph
     *                    class. Passed as arguments, in the array's order
     * @param string    $outputfile    The graph file to create. Note: If being called
     *                    statically (ie: rrdtool::graph()) then this should
     *                    be the path to the rrdtool binary.
    */
    
function graph($options$items$outputfile) {
        
$args;
        foreach (
$options as $name=>$value) {
            
$args .= "--".$name." ".$value."  ";
        }

        foreach (
array_keys($items) as $key) { // use array_keys to prevent copying
            
$args .= $items[$key]->output()." ";
        }

        if (isset(
$this)) {
            
// being called as part of a class, run normally
            
return $this->_run("graph ".$args);
        } else {
            
// special case: being called statically
            // Note: $args is not necessarily safe for commandline - be careful
            //       with where the input comes from
            
exec(escapeshellcmd($outputfile)." ".$args);
        }

    }

    function 
info($filename) {
        
// this was more for testing than anything
        
return $this->_run("info ".$filename);
    }
}

/** Data source definition
 */
class rrdtool_ds {
    var 
$name;
    var 
$type;
    var 
$heartbeat;
    var 
$min;
    var 
$max;

    function 
rrdtool_ds($name$type$heartbeat$min "U"$max "U") {
        
$this->name $name;
        
$this->type $type;
        
$this->heartbeat $heartbeat;
        
$this->min $min;
        
$this->max $max;
    }
}

/** Round-robin archive definition
 */
class rrdtool_rra {
    var 
$cf;
    var 
$steps;
    var 
$rows;
    var 
$xff;

    function 
rrdtool_rra($cf$steps$rows$xff 0.5) {
        
$this->cf $cf;
        
$this->steps $steps;
        
$this->rows $rows;
        
$this->xff $xff;
    }
}


class 
rrdtool_graph {
    function 
output() { //abstract -- children must override this
        
return false;
    }
}

class 
rrdtool_graph_def extends rrdtool_graph {
    var 
$vname;
    var 
$rrdfile;
    var 
$ds;
    var 
$cf;

    function 
rrdtool_graph_def($vname$rrdfile$ds$cf) {
        
$this->vname $vname;
        
$this->rrdfile $rrdfile;
        
$this->ds $ds;
        
$this->cf $cf;
    }

    function 
output() {
        
// DEF:vname=rrd:ds-name:CF
        
return "DEF:".$this->vname."=".$this->rrdfile.":".$this->ds.":".$this->cf;
    }
}

class 
rrdtool_graph_cdef extends rrdtool_graph {
    var 
$vname;
    var 
$expr;

    function 
rrdtool_graph_cdef($vname$expr) {
        
$this->vname $vname;
        
$this->expr $expr;
    }

    function 
output() {
        
// CDEF:vname=rpn-expression
        
return "CDEF:".$this->vname."=".$this->expr;
    }
}

class 
rrdtool_graph_print extends rrdtool_graph {
    var 
$vname;
    var 
$cf;
    var 
$format;

    function 
rrdtool_graph_print($vname$cf$format) {
        
$this->vname $vname;
        
$this->cf $cf;
        
$this->format $format;
    }

    function 
output() {
        
// PRINT:vname:CF:format
        
return "PRINT:".$this->vname.":".$this->cf.":".$this->format;
    }
}

class 
rrdtool_graph_gprint extends rrdtool_graph {
    var 
$vname;
    var 
$cf;
    var 
$format;

    function 
rrdtool_graph_gprint($vname$cf$format) {
        
$this->vname $vname;
        
$this->cf $cf;
        
$this->format $format;
    }

    function 
output() {
        
// GPRINT:vname:CF:format
        
return "GPRINT:".$this->vname.":".$this->cf.":".$this->format;
    }
}

class 
rrdtool_graph_comment extends rrdtool_graph {
    var 
$text;

    function 
rrdtool_graph_comment($text) {
        
$this->text $text;
    }

    function 
output() {
        
// COMMENT:text
        
return "COMMENT:".escapeshellarg($this->text);
    }
}

class 
rrdtool_graph_hrule extends rrdtool_graph {
    var 
$value;
    var 
$color;
    var 
$legend;

    function 
rrdtool_graph_hrule($value$color$legend false) {
        
$this->value $value;
        
$this->color $color// in "rrggbb" hex format
        
$this->legend $legend;
    }

    function 
output() {
        
// HRULE:value#rrggbb[:legend]
        
return "HRULE:".$this->value."#".$this->color.(!empty($this->legend) ? ":".escapeshellarg($this->legend) : "");
    }
}

class 
rrdtool_graph_vrule extends rrdtool_graph {
    var 
$value;
    var 
$color;
    var 
$legend;

    function 
rrdtool_graph_vrule($value$color$legend false) {
        
$this->value $value;
        
$this->color $color// in "rrggbb" hex format
        
$this->legend $legend;
    }

    function 
output() {
        
// VRULE:value#rrggbb[:legend]
        
return "VRULE:".$this->value."#".$this->color.(!empty($this->legend) ? ":".escapeshellarg($this->legend) : "");
    }
}

class 
rrdtool_graph_line extends rrdtool_graph {
    var 
$type;
    var 
$value;
    var 
$color;
    var 
$legend;

    function 
rrdtool_graph_line($type$vname$color false$legend false) {
        
$this->type $type;
        
$this->value $value;
        
$this->color $color// in "rrggbb" hex format
        
$this->legend $legend;
    }

    function 
output() {
        
// LINE{1|2|3}:vname[#rrggbb[:legend]]
        
return "LINE".$this->type.":".$this->value.(!empty($this->color) ? "#".$this->color "").(!empty($this->legend) ? ":".escapeshellarg($this->legend) : "");
    }
}

class 
rrdtool_graph_area extends rrdtool_graph {
    var 
$vname;
    var 
$color;
    var 
$legend;

    function 
rrdtool_graph_area($vname$color$legend false) {
        
$this->vname $vname;
        
$this->color $color// in "rrggbb" hex format
        
$this->legend $legend;
    }

    function 
output() {
        
// AREA:vname#rrggbb[:legend]
        
return "AREA:".$this->vname."#".$this->color.(!empty($this->legend) ? ":".escapeshellarg($this->legend) : "");
    }
}

class 
rrdtool_graph_stack extends rrdtool_graph {
    var 
$vname;
    var 
$color;
    var 
$legend;

    function 
rrdtool_graph_stack($vname$color$legend false) {
        
$this->vname $vname;
        
$this->color $color// in "rrggbb" hex format
        
$this->legend $legend;
    }

    function 
output() {
        
// STACK:vname#rrggbb[:legend]
        
return "STACK:".$this->vname."#".$this->color.(!empty($this->legend) ? ":".escapeshellarg($this->legend) : "");
    }
}


/** Examples:


Update a couple files:

    $rrd = new rrdtool("/usr/local/rrdtool/bin/rrdtool");
    $rrd->update("/path/to/rra/cisternlevel_16.rrd", array(45));
    $rrd->update("/path/to/rra/wellcurrent_17.rrd", array("ds1"=>7,"ds2"=>12));
    $rrd->close();



Generate a graph:

    $options = array(
            "title"=>"Demo Graph",
            "imgformat"=>"PNG"
        );
    $items = array(
            new rrdtool_graph_def("bps-in","bps_router1.rrd","bps-in","AVERAGE"),
            new rrdtool_graph_def("bps-out","bps_router1.rrd","bps-out","AVERAGE"),
            new rrdtool_graph_line("bps-in","0000FF","Traffic out"),
            new rrdtool_graph_area("bps-out","00FF00","Traffic in")
        );

    header("Content-type: image/png");
    echo rrdtool::graph($options, $items);

*/
?>