BenBE's humble thoughts Thoughts the world doesn't need yet …

22.07.2009

RRDTool für PHP

Filed under: Software — Schlagwörter: , , , , , — BenBE @ 20:11:25

Mehr oder weniger aus eigenem Interesse habe ich die Tage einmal nach einer Möglichkeit gesucht, die Funktionalität von Mailgraph und Bindgraph, die auf meinem Webspace werkeln, mit einem eigenen PHP-Skript nutzen zu können. Diese Scripte nutzen beide RRDTool, also so ziemlich der Standard in dieser Hinsicht. Bindings sind auch für Python und Perl vorhanden – jedoch für PHP gibt es kein offizielles. Durch einen Blogeintrag bei IONCANNON, der zugleich eine einfache Einführung in die Nutzung gibt, bin ich auf einen etwas älteren Versuch aufmerksam geworden, mit dem jemand eine solche Möglichkeit geschaffen hat.

Da jedoch die Links, die zu der an vielen Stellen erwähnten Extension führten, oftmals nicht mehr funktionierten, brachte mich erst ein Beitrag einer Mailingliste (dessen Link auch inkorrekt ist), auf die richtige Spur: Unter http://oss.oetiker.ch/rrdtool/pub/contrib/ wird man mit der Datei php_rrdtool.tar.gz fündig. Deren Installation wird wiederum beim PRB-Projekt recht anschaulich beschrieben. Jedoch sollte man nicht voreilig sein, da mir beim betrachten des Sources ein paar potenzielle Stolpersteine aufgefallen sind. Dazu aber gleich mehr.

Wo wir gerade die Dokumentation angesprochen haben: Wer einen Blick auf den oben genannten Blog-Eintrag geworfen hat, wird festgestellt haben, dass dort mehrfach ein Array mit kryptischen Argumenten definiert wurde. Wer RRDTool kennt, dem werden diese Angaben sicherlich bekannt vorkommen; wer sich aber unsicher fühlt, kann einen Blick in die Original-Dokumentation von RRDTool werfen, die mit einer sehr guten Einführung in die Thematik aufwartet.

Aber wie es immer so mit Tutorials ist: Irgendwas möchte immer nicht 😉 Und genau das war bei mir gestern der Fall, als ich die Demos aus dem oben erwähnten Tutorial versuch habe. Je mehr ich versucht habe, desto bizarrer die Fehlermeldungen. Dass die Steps-Size nicht 0 sein darf, obwohl –step 300 angegeben war, war da noch eher das Harmloseste in der Sammlung der Fehlermeldungen. Aber mit etwas Trial&Error war auch dies zu bewerkstelligen. Am Ende fielen aus meinem Editor 3 funktionierende PHP-Dateien rrdc.php, rrdu.php und rrdg.php:

rrdc.php:

<?php

$fname = "net.rrd";

$opts = array(
  "--step", "300", "--start", "0",
  "DS:input:COUNTER:600:U:U",
  "DS:output:COUNTER:600:U:U",
  "RRA:AVERAGE:0.5:1:600",
  "RRA:AVERAGE:0.5:6:700",
  "RRA:AVERAGE:0.5:24:775",
  "RRA:AVERAGE:0.5:288:797",
  "RRA:MAX:0.5:1:600",
  "RRA:MAX:0.5:6:700",
  "RRA:MAX:0.5:24:775",
  "RRA:MAX:0.5:288:797"
  );

$ret = rrd_create($fname, $opts, count($opts));

if( !$ret )
{
  $err = rrd_error();
  echo "Create error: $err\n";
}

?>

rrdu.php:

<?php

$fname = "net.rrd";

$total_input_traffic = 0;
$total_output_traffic = 0;

$t = time();

while($t < time()+86400)
{
  $total_input_traffic += rand(100000, 1500000);
  $total_output_traffic += rand(100000, 3000000);

  $t += 300;
  echo $t . ": " . $total_input_traffic . " and " . $total_output_traffic . "\n";

  $ret = rrd_update($fname, "$t:$total_input_traffic:$total_output_traffic");

  if( !$ret )
  {
    $err = rrd_error();
    echo "ERROR occurred: $err\n";
  }

}

?>

rrdg.php:

<?php

$opts = array(
  "-s-1d",
  "-e+1d",
  //"-–vertical-label=B/s",
  "DEF:inoctets=net.rrd:input:AVERAGE",
  "DEF:outoctets=net.rrd:output:AVERAGE",
  "AREA:inoctets#00FF00:In traffic",
  "LINE1:outoctets#0000FF:Out traffic",
  "CDEF:inbits=inoctets,8,*",
  "CDEF:outbits=outoctets,8,*",
  "COMMENT: ",
  "GPRINT:inbits:AVERAGE:Avg In traffic\: %6.2lf %Sbps",
  "COMMENT: ",
  "GPRINT:inbits:MAX:Max In traffic\: %6.2lf %Sbps",
  "GPRINT:outbits:AVERAGE:Avg Out traffic\: %6.2lf %Sbps",
  "COMMENT: ",
  "GPRINT:outbits:MAX:Max Out traffic\: %6.2lf %Sbps"
  );

$ret = rrd_graph("net_1d.gif", $opts, count($opts));

if( !is_array($ret) ) {
  $err = rrd_error();
  echo "rrd_graph() ERROR: $err\n";
} else {
  print_r($ret);
}

Während rrdc.php das Erzeugen eines Round-Robin-Archives demonstriert und genauso wie rrdg.php (Ausgabe als Grafik) bis auf kleinere Bugfixes mit dem Original identisch ist, ist rrdu.php eine Version des Originals für Ungeduldige, die mal eben für 24h ein paar Daten generiert. Der Rest bleibt aber mit den Code-Vorlagen identisch und darf daher in der ursprünglichen Anleitung nachgeschlagen werden.

Wie oben erwähnt, gab es nun noch ein paar Probleme mit der Extension selbst. Neben einem mehr oder weniger geringfügigen Schreibfehler in der Infoseite der Extension und einer Reihe deplazierter Spaces fiel mir das Fehlen der Prüfungen für die open_basedir Restriction auf. Diese sollte man sich (mit den anderen Änderungen) mit folgendem Patch vor dem Installieren der Extension einpflegen:

--- rrdtool.c	2005-12-03 13:34:06.000000000 +0000
+++ rrdtool.c	2009-07-22 16:49:47.000000000 +0000
@@ -12,6 +12,9 @@
  *       Jeffrey Wheat <jeff@cetlink.net> - 10/01/2002
  *       - Fixed to build with php-4.2.3
  *
+ *       Benny Baumann <BenBE@geshi.org> - 07/22/2009
+ *       - Added open_basedir checks
+ *
  * See README, INSTALL, and USAGE files for more details.
  *
  * $Id: rrdtool.c,v 1.1.1.1 2002/02/26 10:21:20 oetiker Exp $
@@ -40,13 +43,13 @@
  */

 function_entry rrdtool_functions[] = {
-	PHP_FE(rrd_graph,				NULL)
-	PHP_FE(rrd_fetch,				NULL)
-	PHP_FE(rrd_error,				NULL)
+	PHP_FE(rrd_graph,			NULL)
+	PHP_FE(rrd_fetch,			NULL)
+	PHP_FE(rrd_error,			NULL)
 	PHP_FE(rrd_clear_error,			NULL)
-	PHP_FE(rrd_update,				NULL)
-	PHP_FE(rrd_last,				NULL)
-	PHP_FE(rrd_create,				NULL)
+	PHP_FE(rrd_update,			NULL)
+	PHP_FE(rrd_last,			NULL)
+	PHP_FE(rrd_create,			NULL)
 	PHP_FE(rrdtool_info,			NULL)
 	PHP_FE(rrdtool_logo_guid,		NULL)
 	{NULL, NULL, NULL}
@@ -86,8 +89,8 @@
 /* {{{ PHP_MINIT_FUNCTION */
 PHP_MINIT_FUNCTION(rrdtool)
 {
-	php_register_info_logo(RRDTOOL_LOGO_GUID   , "image/gif", rrdtool_logo   , sizeof(rrdtool_logo));
-
+	php_register_info_logo(RRDTOOL_LOGO_GUID, "image/gif", rrdtool_logo, sizeof(rrdtool_logo));
+
 	return SUCCESS;
 }
 /* }}} */
@@ -96,7 +99,7 @@
 PHP_MSHUTDOWN_FUNCTION(rrdtool)
 {
 	php_unregister_info_logo(RRDTOOL_LOGO_GUID);
-
+
 	return SUCCESS;
 }
 /* }}} */
@@ -109,7 +112,7 @@
 	if (SG(request_info).request_uri) {
 		PUTS(SG(request_info).request_uri);
 	}
-	PUTS("?="RRDTOOL_LOGO_GUID"\" alt=\"ClamAV logo\" /></a>\n");
+	PUTS("?="RRDTOOL_LOGO_GUID"\" alt=\"RRDTool logo\" /></a>\n");
 	php_printf("<h1 class=\"p\">rrdtool Version %s</h1>\n", PHP_RRD_VERSION_STRING);
 	php_info_print_box_end();
 	php_info_print_table_start();
@@ -129,25 +132,29 @@
 	int i, xsize, ysize, argc;
 	double ymin,ymax;
 	char **argv, **calcpr;
-

 	if ( rrd_test_error() )
 		rrd_clear_error();
-
+
 	if ( (ZEND_NUM_ARGS() >= 3 && ZEND_NUM_ARGS() <= 6) && zend_get_parameters(ht, 3, &file, &args, &p_argc) == SUCCESS)
 	{
 		if ( args->type != IS_ARRAY )
-		{
+		{
 			php_error(E_WARNING, "2nd Variable passed to rrd_graph is not an array!\n");
 			RETURN_FALSE;
 		}
-
+
 		convert_to_long(p_argc);
 		convert_to_string(file);

 		convert_to_array(args);
 		args_arr = args->value.ht;

+		//BenBE: Added Basedir Restriction ...
+		if (php_check_open_basedir(file TSRMLS_CC)) {
+			RETURN_FALSE;
+		}
+
 		argc = p_argc->value.lval + 3;
 		argv = (char **) emalloc(argc * sizeof(char *));

@@ -210,7 +217,7 @@
 		efree(argv);
 	}
 	else
-	{
+	{
 		WRONG_PARAM_COUNT;
 	}
 	return;
@@ -229,30 +236,35 @@
 	int i, j, argc;
 	time_t start, end;
 	unsigned long step, ds_cnt;
-	char **argv, **ds_namv;
+	char **argv, **ds_namv;
 	rrd_value_t *data, *datap;
-
+
 	if ( rrd_test_error() )
 		rrd_clear_error();
-
-	if ( ZEND_NUM_ARGS() == 3 &&
-		 zend_get_parameters(ht, 3, &file, &args, &p_argc) == SUCCESS)
+
+	if ( ZEND_NUM_ARGS() == 3 &&
+		zend_get_parameters(ht, 3, &file, &args, &p_argc) == SUCCESS)
 	{
 		if ( args->type != IS_ARRAY )
-		{
+		{
 			php_error(E_WARNING, "2nd Variable passed to rrd_fetch is not an array!\n");
 			RETURN_FALSE;
 		}
-
+
 		convert_to_long(p_argc);
 		convert_to_string(file);

 		convert_to_array(args);
 		args_arr = args->value.ht;

+		//BenBE: Added Basedir Restriction ...
+		if (php_check_open_basedir(file TSRMLS_CC)) {
+			RETURN_FALSE;
+		}
+
 		argc = p_argc->value.lval + 3;
 		argv = (char **) emalloc(argc * sizeof(char *));
-
+
 		argv[0] = "dummy";
 		argv[1] = estrdup("fetch");
 		argv[2] = estrdup(file->value.str.val);
@@ -274,7 +286,7 @@
 			if ( i < argc )
 				zend_hash_move_forward(args_arr);
 		}
-
+
 		optind = 0; opterr = 0;

 		if ( rrd_fetch(argc-1, &argv[1], &start,&end,&step,&ds_cnt,&ds_namv,&data) != -1 )
@@ -303,11 +315,11 @@
 			if (data)
 			{
 				datap = data;
-
+
 				for (i = start; i <= end; i += step)
 					for (j = 0; j < ds_cnt; j++)
 						add_next_index_double(p_data, *(datap++));
-
+
 				free(data);
 			}

@@ -326,7 +338,7 @@
 		efree(argv);
 	}
 	else
-	{
+	{
 		WRONG_PARAM_COUNT;
 	}
 	return;
@@ -341,8 +353,7 @@

 	if ( rrd_test_error() )
 	{
-		msg = rrd_get_error();
-
+		msg = rrd_get_error();
 		RETVAL_STRING(msg, 1);
 		rrd_clear_error();
 	}
@@ -372,12 +383,17 @@
 	if ( rrd_test_error() )
 		rrd_clear_error();

-	if ( ZEND_NUM_ARGS() == 2 &&
-		 zend_get_parameters(ht, 2, &file, &opt) == SUCCESS )
+	if ( ZEND_NUM_ARGS() == 2 &&
+		zend_get_parameters(ht, 2, &file, &opt) == SUCCESS )
 	{
 		convert_to_string(file);
 		convert_to_string(opt);

+		//BenBE: Added Basedir Restriction ...
+		if (php_check_open_basedir(file TSRMLS_CC)) {
+			RETURN_FALSE;
+		}
+
 		argv = (char **) emalloc(4 * sizeof(char *));

 		argv[0] = "dummy";
@@ -413,14 +429,19 @@
 	unsigned long retval;

 	char **argv = (char **) emalloc(3 * sizeof(char *));
-
+
 	if ( rrd_test_error() )
 		rrd_clear_error();
-
+
 	if (zend_get_parameters(ht, 1, &file) == SUCCESS)
 	{
 		convert_to_string(file);

+		//BenBE: Added Basedir Restriction ...
+		if (php_check_open_basedir(file TSRMLS_CC)) {
+			RETURN_FALSE;
+		}
+
 		argv[0] = "dummy";
 		argv[1] = estrdup("last");
 		argv[2] = estrdup(file->value.str.val);
@@ -453,7 +474,7 @@
 	if ( rrd_test_error() )
 		rrd_clear_error();

-	if ( ZEND_NUM_ARGS() == 3 &&
+	if ( ZEND_NUM_ARGS() == 3 &&
 		getParameters(ht, 3, &file, &args, &p_argc) == SUCCESS )
 	{
 		if ( args->type != IS_ARRAY )
@@ -464,11 +485,16 @@

 		convert_to_long(p_argc);
 		convert_to_string(file);
-
+
 		convert_to_array(args);
 		args_arr = args->value.ht;
 		zend_hash_internal_pointer_reset(args_arr);

+		//BenBE: Added Basedir Restriction ...
+		if (php_check_open_basedir(file TSRMLS_CC)) {
+			RETURN_FALSE;
+		}
+
 		argc = p_argc->value.lval + 3;
 		argv = (char **) emalloc(argc * sizeof(char *));

@@ -493,10 +519,10 @@
 			if ( i < argc )
 				zend_hash_move_forward(args_arr);
 		}
-
+
 		optind = 0;  opterr = 0;

-		if ( rrd_create(argc-1, &argv[1]) != -1 )
+		if ( rrd_create(argc - 1, &argv[1]) != -1 )
 		{
 			RETVAL_TRUE;
 		}
@@ -511,7 +537,7 @@
 	}
 	else
 	{
-	    WRONG_PARAM_COUNT;
+		WRONG_PARAM_COUNT;
 	}
 	return;
 }

Dieser Patch fügt für die wichtigsten Dateioperationen eine Basedir-Prüfung hinzu; was er jedoch nicht prüft, sind Pfadangaben innerhalb der Argumente! Nach dem man diesen Patch erfolgreich angewendet hat, erfolgt die Installation wie folgt:

phpize5
./configure
make
make install

Wenn dies erfolgreich durchläuft (librrd4-dev und die anderen Header sind auffindbar), kann man die oben erwähnten Scripts ausprobieren. Jetzt fehlt nur noch ein Wrapper, der einem erspart, ständig diese kryptischen Arrays zu schreiben …

Flattr this!

Ein Kommentar »

  1. […] haben, dass manchmal Graphen auftauchen, die mit RRDTool erzeugt wurden. Nun habe ich ja bereits einmal zu RRDTool etwas geschrieben, aber den von mir geschriebenen Wrapper, sowie den aktuellen Code-Stand (nach angewendetem Patch) […]

    Pingback by RRDTool mit PHP nutzen « BenBE's humble thoughts — 26.03.2012 @ 00:03:47

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress