1 /**
2 * PHP bindings to the rrdtool
3 *
4 * This source file is subject to the BSD license that is bundled
5 * with this package in the file LICENSE.
6 * ---------------------------------------------------------------
7 * Author: Miroslav Kubelik <koubel@php.net>
8 * ---------------------------------------------------------------
9 */
10 #ifdef HAVE_CONFIG_H
11 #include "config.h"
12 #endif
13
14 #include "php.h"
15 #include "ext/standard/php_smart_string.h"
16 #include "ext/standard/php_array.h"
17 #include "ext/standard/info.h"
18
19 #include <rrd.h>
20 #ifdef HAVE_RRDC_DISCONNECT
21 #include <rrd_client.h>
22 #endif
23
24 #include "php_rrd.h"
25 #include "rrd_graph.h"
26 #include "rrd_create.h"
27 #include "rrd_update.h"
28 #include "rrd_info.h"
29
30 /* {{{ proto string rrd_error()
31 Get the error message set by the last rrd tool function call, this function
32 clear error buffer also.
33 */
PHP_FUNCTION(rrd_error)34 PHP_FUNCTION(rrd_error)
35 {
36 if (zend_parse_parameters_none() == FAILURE) {
37 return;
38 }
39
40 if (!rrd_test_error()) RETURN_FALSE;
41
42 RETVAL_STRING(rrd_get_error());
43 rrd_clear_error();
44 }
45 /* }}} */
46
47 /* {{{ proto array rrd_fetch(string file, array options)
48 Fetch data from RRD in requested resolution.
49 */
PHP_FUNCTION(rrd_fetch)50 PHP_FUNCTION(rrd_fetch)
51 {
52 char *filename;
53 size_t filename_length;
54 zval *zv_arr_options;
55 rrd_args *argv;
56 /* returned values if rrd_fetch doesn't fail */
57 time_t start, end;
58 u_long step,
59 ds_cnt; /* count of data sources */
60 char **ds_namv; /* list of data source names */
61 rrd_value_t *ds_data; /* all data from all sources */
62
63 if (zend_parse_parameters(ZEND_NUM_ARGS(), "pa", &filename,
64 &filename_length, &zv_arr_options) == FAILURE) {
65 return;
66 }
67
68 if (php_check_open_basedir(filename)) RETURN_FALSE;
69
70 argv = rrd_args_init_by_phparray("fetch", filename, zv_arr_options);
71 if (!argv) {
72 zend_error(E_WARNING, "cannot allocate arguments options");
73 RETURN_FALSE;
74 }
75
76 if (rrd_test_error()) rrd_clear_error();
77
78 /* call rrd_fetch and test if fails */
79 if (rrd_fetch(argv->count - 1, &argv->args[1], &start, &end, &step, &ds_cnt,
80 &ds_namv, &ds_data) == -1 ) {
81 rrd_args_free(argv);
82 RETURN_FALSE;
83 }
84
85 /* making return array */
86 array_init(return_value);
87 add_assoc_long(return_value, "start", start);
88 add_assoc_long(return_value, "end", end);
89 add_assoc_long(return_value, "step", step);
90
91 /* add "ds_namv" and "data" array into return values if there is any
92 * result data
93 */
94 if (!ds_data || !ds_namv || !ds_cnt) {
95 add_assoc_null(return_value, "data");
96 } else {
97 rrd_value_t *datap = ds_data;
98 uint timestamp, ds_counter;
99 /* final array for all data from all data sources */
100 zval zv_data_array;
101
102 array_init(&zv_data_array);
103
104 /* add arrays for each data source, each array will be filled with
105 * retrieved data for a particular data source
106 */
107 for (ds_counter = 0; ds_counter < ds_cnt; ds_counter++) {
108 zval zv_ds_data_array;
109 array_init(&zv_ds_data_array);
110
111 add_assoc_zval(&zv_data_array, ds_namv[ds_counter], &zv_ds_data_array);
112 }
113
114 for (timestamp = start + step; timestamp <= end; timestamp += step) {
115 /* try to find current data source result array in the
116 * zv_data_array
117 */
118 zend_hash_internal_pointer_reset(Z_ARRVAL(zv_data_array));
119 for (ds_counter = 0; ds_counter < ds_cnt; ds_counter++) {
120 /* pointer for one data source retrieved data */
121 zval *ds_data_array;
122 /* value for key (timestamp) in data array */
123 char str_timestamp[11];
124 ZEND_LTOA((zend_ulong)timestamp, str_timestamp, sizeof(str_timestamp));
125
126 /* gets pointer for data source result array */
127 ds_data_array = zend_hash_get_current_data(Z_ARRVAL(zv_data_array));
128
129 add_assoc_double(ds_data_array, str_timestamp, *(datap++));
130 zend_hash_move_forward(Z_ARRVAL(zv_data_array));
131 }
132 }
133 add_assoc_zval(return_value, "data", &zv_data_array);
134
135 /* free data from rrd_fetch */
136 free(ds_data);
137 for (ds_counter = 0; ds_counter < ds_cnt; ds_counter++) {
138 free(ds_namv[ds_counter]);
139 }
140 free(ds_namv);
141 }
142
143 rrd_args_free(argv);
144 }
145 /* }}} */
146
147 /* {{{ proto int rrd_first(string file [, int rraindex = 0])
148 Gets first update time of an RRD file
149 */
PHP_FUNCTION(rrd_first)150 PHP_FUNCTION(rrd_first)
151 {
152 char *filename;
153 size_t filename_length;
154 long rraindex = 0;
155 /* return value from rrd_first_r call */
156 time_t rrd_first_return_val;
157
158 if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|l", &filename,
159 &filename_length, &rraindex) == FAILURE) {
160 return;
161 }
162
163 if (rraindex < 0) {
164 rrd_set_error("invalid rraindex number, rraindex must be >= 0");
165 RETURN_FALSE;
166 }
167
168 if (php_check_open_basedir(filename)) {
169 RETURN_FALSE;
170 }
171
172 if (rrd_test_error()) rrd_clear_error();
173
174 /* call rrd_first and test if fails */
175 rrd_first_return_val = rrd_first_r(filename, rraindex);
176 if (rrd_first_return_val == -1) {
177 RETURN_FALSE;
178 }
179 RETURN_LONG(rrd_first_return_val);
180 }
181 /* }}} */
182
183 /* {{{ proto int rrd_last(string file)
184 Gets last update time of an RRD file
185 */
PHP_FUNCTION(rrd_last)186 PHP_FUNCTION(rrd_last)
187 {
188 char *filename;
189 size_t filename_length;
190 /* return value from rrd_first_r call */
191 time_t rrd_last_return_val;
192
193 if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &filename,
194 &filename_length) == FAILURE) {
195 return;
196 }
197
198 if (php_check_open_basedir(filename)) {
199 RETURN_FALSE;
200 }
201
202 if (rrd_test_error()) rrd_clear_error();
203
204 /* call rrd_last and test if fails */
205 rrd_last_return_val = rrd_last_r(filename);
206 if (rrd_last_return_val == -1) {
207 RETURN_FALSE;
208 }
209 RETURN_LONG(rrd_last_return_val);
210 }
211 /* }}} */
212
213 /* {{{ proto int rrd_lastupdate(string file)
214 Gets last update details of an RRD file */
PHP_FUNCTION(rrd_lastupdate)215 PHP_FUNCTION(rrd_lastupdate)
216 {
217 char *filename;
218 size_t filename_length;
219 /* list of arguments for rrd_lastupdate call, it's more efficient then
220 * usage of rrd_args, because there isn't array of arguments in parameters
221 */
222 char *argv[3];
223 /* return values from rrd_lastupdate_r function */
224 time_t last_update;
225 unsigned long ds_cnt;
226 char **ds_namv;
227 char **last_ds;
228
229 if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &filename,
230 &filename_length) == FAILURE) {
231 return;
232 }
233
234 if (php_check_open_basedir(filename)) {
235 RETURN_FALSE;
236 }
237
238 argv[0] = "dummy";
239 argv[1] = estrdup("lastupdate");
240 argv[2] = estrndup(filename, filename_length);
241
242 if (rrd_test_error()) rrd_clear_error();
243
244 #ifdef HAVE_RRD_LASTUPDATE_R
245 if (rrd_lastupdate_r(argv[2], &last_update, &ds_cnt, &ds_namv,
246 &last_ds) == -1) {
247 #else
248 if (rrd_lastupdate(2, &argv[1], &last_update, &ds_cnt, &ds_namv,
249 &last_ds) == -1) {
250 #endif
251 efree(argv[2]); efree(argv[1]);
252 RETURN_FALSE;
253 }
254
255 efree(argv[2]); efree(argv[1]);
256
257 /* making return array*/
258 array_init(return_value);
259 add_assoc_long(return_value, "last_update", last_update);
260 add_assoc_long(return_value, "ds_cnt", ds_cnt);
261
262 /* "ds_navm" return array or null, if no available */
263 if (!ds_namv || !ds_cnt) {
264 add_assoc_null(return_value, "ds_namv");
265 } else {
266 uint i;
267 zval zv_ds_namv_array;
268 array_init(&zv_ds_namv_array);
269
270 for (i = 0; i < ds_cnt; i++) {
271 add_next_index_string(&zv_ds_namv_array, ds_namv[i]);
272 free(ds_namv[i]);
273 }
274 free(ds_namv);
275 add_assoc_zval(return_value, "ds_navm", &zv_ds_namv_array);
276 }
277
278 /* "data" return array or null, if no available */
279 if (!last_ds || !ds_cnt) {
280 add_assoc_null(return_value, "data");
281 } else {
282 uint i;
283 zval zv_data_array;
284 array_init(&zv_data_array);
285
286 /* simple array for "data" is enough, data source names and timestamps are
287 * available under other return value keys
288 */
289 for (i = 0; i < ds_cnt; i++) {
290 add_next_index_string(&zv_data_array, last_ds[i]);
291 free(last_ds[i]);
292 }
293 free(last_ds);
294 add_assoc_zval(return_value, "data", &zv_data_array);
295 }
296 }
297
298 /* {{{ proto array rrd_restore(string xmlFile, string rrdFile [, array options])
299 Restores an RRD file from a XML dump */
300 PHP_FUNCTION(rrd_restore)
301 {
302 char *xml_filename, *rrd_filename;
303 size_t xml_filename_length, rrd_filename_length;
304 zval *zv_arr_options = NULL;
305 /* this is merge of options and rrd_filename. This is needed because
306 * rrd_args_init_by_phparray allows only one filename as argument, so
307 * rrd_filename mugst be part of array of arguments
308 */
309 zval zv_options;
310 rrd_args *argv;
311
312 if (zend_parse_parameters(ZEND_NUM_ARGS(), "pp|a", &xml_filename,
313 &xml_filename_length, &rrd_filename, &rrd_filename_length,
314 &zv_arr_options) == FAILURE) {
315 return;
316 }
317
318 if (php_check_open_basedir(xml_filename) ||
319 php_check_open_basedir(rrd_filename)) {
320 RETURN_FALSE;
321 }
322
323 /* zv_options = merge rrd_filename and zv_arr_options content */
324 array_init(&zv_options);
325 add_next_index_string(&zv_options, rrd_filename);
326 if (zv_arr_options && Z_TYPE_P(zv_arr_options) == IS_ARRAY) {
327 php_array_merge(Z_ARRVAL(zv_options), Z_ARRVAL_P(zv_arr_options));
328 }
329
330 argv = rrd_args_init_by_phparray("restore", xml_filename, &zv_options);
331 if (!argv) {
332 zend_error(E_WARNING, "cannot allocate arguments options");
333 zval_dtor(&zv_options);
334 RETURN_FALSE;
335 }
336
337 if (rrd_test_error()) rrd_clear_error();
338
339 /* call rrd_ restore and test if fails */
340 if (rrd_restore(argv->count-1, &argv->args[1]) == -1) {
341 RETVAL_FALSE;
342 } else {
343 RETVAL_TRUE;
344 }
345 zval_dtor(&zv_options);
346 rrd_args_free(argv);
347 }
348 /* }}} */
349
350 /* {{{ proto bool rrd_tune(string file, array options)
351 Tune an RRD file with the options passed (passed via array) */
352 PHP_FUNCTION(rrd_tune)
353 {
354 char *filename;
355 size_t filename_length;
356 zval *zv_arr_options;
357 rrd_args *argv;
358
359 if (zend_parse_parameters(ZEND_NUM_ARGS(), "pa", &filename,
360 &filename_length, &zv_arr_options) == FAILURE) {
361 return;
362 }
363
364 if (!zend_hash_num_elements(Z_ARRVAL_P(zv_arr_options))) {
365 zend_error(E_WARNING, "options array mustn't be empty");
366 RETURN_FALSE;
367 }
368
369 if (php_check_open_basedir(filename)) RETURN_FALSE;
370
371 argv = rrd_args_init_by_phparray("tune", filename, zv_arr_options);
372 if (!argv) {
373 zend_error(E_WARNING, "cannot allocate arguments options");
374 RETURN_FALSE;
375 }
376
377 if (rrd_test_error()) rrd_clear_error();
378
379 /* call rrd_tune and test if fails */
380 if (rrd_tune(argv->count-1, &argv->args[1]) == -1 ) {
381 RETVAL_FALSE;
382 } else {
383 RETVAL_TRUE;
384 }
385 rrd_args_free(argv);
386 }
387 /* }}} */
388
389 /* {{{ proto array rrd_xport(array options)
390 * Creates a graph based on options passed via an array
391 */
392 PHP_FUNCTION(rrd_xport)
393 {
394 zval *zv_arr_options;
395 rrd_args *argv;
396 /* return values from rrd_xport */
397 int xxsize;
398 time_t start, end, time_index;
399 u_long step, outvar_count;
400 char **legend_v;
401 rrd_value_t *data, *data_ptr;
402 zval zv_data;
403 u_long outvar_index;
404
405 if (zend_parse_parameters(ZEND_NUM_ARGS(), "a", &zv_arr_options) == FAILURE) {
406 return;
407 }
408
409 argv = rrd_args_init_by_phparray("xport", "", zv_arr_options);
410 if (!argv) {
411 zend_error(E_WARNING, "cannot allocate arguments options");
412 RETURN_FALSE;
413 }
414
415 if (rrd_test_error()) rrd_clear_error();
416
417 /* call rrd_xport and test if fails */
418 if (rrd_xport(argv->count-1, &argv->args[1], &xxsize, &start, &end, &step,
419 &outvar_count, &legend_v, &data) == -1) {
420 php_printf("rrd_xport failed");
421 rrd_args_free(argv);
422 RETURN_FALSE;
423 }
424
425 rrd_args_free(argv);
426
427 /* fill rrd_xport return values into array */
428 array_init(return_value);
429
430 add_assoc_long(return_value, "start", start + step);
431 add_assoc_long(return_value, "end", end);
432 add_assoc_long(return_value, "step", step);
433
434 /* no data available */
435 if (!data) {
436 add_assoc_null(return_value, "data");
437 return;
438 }
439
440 array_init(&zv_data);
441
442 for (outvar_index = 0; outvar_index < outvar_count; outvar_index++) {
443 /* array for a whole one output variable data, it contains indexes
444 * array(
445 * "legend" => "variable legend",
446 * "data" => array(
447 * 920807400 => 0.14,
448 * 920807800 => 21,
449 * ...
450 * ))
451 */
452 zval zv_var_data, time_data;
453 array_init(&zv_var_data);
454 array_init(&time_data);
455
456 add_assoc_string(&zv_var_data, "legend", legend_v[outvar_index]);
457 free(legend_v[outvar_index]);
458
459 data_ptr = data + outvar_index;
460 for (time_index = start + step; time_index <= end; time_index += step) {
461 /* value for key (timestamp) in data array */
462 char str_timestamp[11];
463 ZEND_LTOA((zend_ulong)time_index, str_timestamp, sizeof(str_timestamp));
464
465 add_assoc_double(&time_data, str_timestamp, *data_ptr);
466 data_ptr += outvar_count;
467 }
468 add_assoc_zval(&zv_var_data, "data", &time_data);
469 add_next_index_zval(&zv_data, &zv_var_data);
470 }
471 add_assoc_zval(return_value, "data", &zv_data);
472 free(legend_v);
473 free(data);
474 }
475 /* }}} */
476
477 #ifdef HAVE_RRDC_DISCONNECT
478 /* {{{ proto void rrdc_disconnect()
479 * Close any outstanding connection to rrd cache daemon.
480 */
481 PHP_FUNCTION(rrdc_disconnect)
482 {
483 if (zend_parse_parameters_none() == FAILURE) {
484 return;
485 }
486
487 rrdc_disconnect();
488 }
489 #endif
490
491 /* {{{ proto string rrd_version()
492 * Gets version of underlying librrd.
493 */
494 PHP_FUNCTION(rrd_version)
495 {
496 if (zend_parse_parameters_none() == FAILURE) {
497 return;
498 }
499
500 RETVAL_STRING(rrd_strversion());
501 }
502
503 /* {{{ arguments */
504 ZEND_BEGIN_ARG_INFO(arginfo_rrd_fetch, 0)
505 ZEND_ARG_INFO(0, file)
506 ZEND_ARG_INFO(0, options)
507 ZEND_END_ARG_INFO()
508
509 ZEND_BEGIN_ARG_INFO_EX(arginfo_rrd_first, 0, 0, 1)
510 ZEND_ARG_INFO(0, file)
511 ZEND_ARG_INFO(0, raaindex)
512 ZEND_END_ARG_INFO()
513
514 ZEND_BEGIN_ARG_INFO(arginfo_rrd_last, 0)
515 ZEND_ARG_INFO(0, file)
516 ZEND_END_ARG_INFO()
517
518 ZEND_BEGIN_ARG_INFO(arginfo_rrd_lastupdate, 0)
519 ZEND_ARG_INFO(0, file)
520 ZEND_END_ARG_INFO()
521
522 ZEND_BEGIN_ARG_INFO_EX(arginfo_rrd_restore, 0, 0, 2)
523 ZEND_ARG_INFO(0, xml_file)
524 ZEND_ARG_INFO(0, rrd_file)
525 ZEND_ARG_INFO(0, options)
526 ZEND_END_ARG_INFO()
527
528 ZEND_BEGIN_ARG_INFO(arginfo_rrd_tune, 0)
529 ZEND_ARG_INFO(0, file)
530 ZEND_ARG_INFO(0, options)
531 ZEND_END_ARG_INFO()
532
533 ZEND_BEGIN_ARG_INFO(arginfo_rrd_xport, 0)
534 ZEND_ARG_INFO(0, options)
535 ZEND_END_ARG_INFO()
536
537 ZEND_BEGIN_ARG_INFO(arginfo_rrd_info, 0)
538 ZEND_ARG_INFO(0, file)
539 ZEND_END_ARG_INFO()
540
541 ZEND_BEGIN_ARG_INFO(arginfo_rrd_graph, 0)
542 ZEND_ARG_INFO(0, file)
543 ZEND_ARG_INFO(0, options)
544 ZEND_END_ARG_INFO()
545
546 ZEND_BEGIN_ARG_INFO(arginfo_rrd_create, 0)
547 ZEND_ARG_INFO(0, filename)
548 ZEND_ARG_INFO(0, options)
549 ZEND_END_ARG_INFO()
550
551 ZEND_BEGIN_ARG_INFO(arginfo_rrd_update, 0)
552 ZEND_ARG_INFO(0, file)
553 ZEND_ARG_INFO(0, options)
554 ZEND_END_ARG_INFO()
555 /* }}} */
556
557 /* {{{ */
558 static zend_function_entry rrd_functions[] = {
559 PHP_FE(rrd_update, arginfo_rrd_update)
560 PHP_FE(rrd_create, arginfo_rrd_create)
561 PHP_FE(rrd_graph, arginfo_rrd_graph)
562 PHP_FE(rrd_error, NULL)
563 PHP_FE(rrd_fetch, arginfo_rrd_fetch)
564 PHP_FE(rrd_first, arginfo_rrd_first)
565 PHP_FE(rrd_info, arginfo_rrd_info)
566 PHP_FE(rrd_last, arginfo_rrd_last)
567 PHP_FE(rrd_lastupdate, arginfo_rrd_lastupdate)
568 PHP_FE(rrd_restore, arginfo_rrd_restore)
569 PHP_FE(rrd_tune, arginfo_rrd_tune)
570 PHP_FE(rrd_xport, arginfo_rrd_xport)
571 #ifdef HAVE_RRDC_DISCONNECT
572 PHP_FE(rrdc_disconnect, NULL)
573 #endif
574 PHP_FE(rrd_version, NULL)
575 PHP_FE_END
576 };
577 /* }}} */
578
579 #ifdef COMPILE_DL_RRD
580 ZEND_GET_MODULE(rrd)
581 #endif
582
583 /* {{{ PHP_MINIT_FUNCTION */
584 static PHP_MINIT_FUNCTION(rrd)
585 {
586 rrd_graph_minit();
587 rrd_create_minit();
588 rrd_update_minit();
589 return SUCCESS;
590 }
591 /* }}} */
592
593 /* {{{ PHP_MINFO_FUNCTION */
594 static PHP_MINFO_FUNCTION(rrd)
595 {
596 php_info_print_table_start();
597 php_info_print_table_header(2, "rrd tool module", "enabled");
598 php_info_print_table_row(2, "rrd tool module version", PHP_RRD_VERSION);
599 php_info_print_table_row(2, "rrdtool library version", rrd_strversion());
600 php_info_print_table_end();
601 }
602 /* }}} */
603
604
605 /* {{{ PHP_MSHUTDOWN_FUNCTION */
606 static PHP_MSHUTDOWN_FUNCTION(rrd)
607 {
608 #ifdef HAVE_RRDC_DISCONNECT
609 /* ensure that any connection to rrd cache deamon will be closed */
610 rrdc_disconnect();
611 #endif
612 return SUCCESS;
613 }
614 /* }}} */
615
616 /* {{{ rrd module_entry */
617 zend_module_entry rrd_module_entry = {
618 STANDARD_MODULE_HEADER,
619 "rrd",
620 rrd_functions,
621 PHP_MINIT(rrd),
622 PHP_MSHUTDOWN(rrd),
623 NULL, /* PHP_RINIT(rrd) */
624 NULL, /* PHP_RSHUTDOWN(rrd) */
625 PHP_MINFO(rrd),
626 PHP_RRD_VERSION,
627 STANDARD_MODULE_PROPERTIES
628 };
629 /* }}} */
630
631 /* {{{ Inits rrd arguments object for a particular rrd command by php array
632 * filename paremeter is optional.
633 */
634 rrd_args *rrd_args_init_by_phparray(const char *command_name, const char *filename,
635 const zval *options)
636 {
637 uint i, option_count, args_counter = 2;
638 rrd_args *result;
639
640 if (Z_TYPE_P(options) != IS_ARRAY) return NULL;
641 option_count = zend_hash_num_elements(Z_ARRVAL_P(options));
642 if (!option_count) return NULL;
643 if (!strlen(command_name)) return NULL;
644
645 result = (rrd_args *)emalloc(sizeof(rrd_args));
646 /* "dummy" + command_name + filename if presented */
647 result->count = option_count + (strlen(filename) ? 3 : 2);
648 result->args = (char **)safe_emalloc(result->count, sizeof(char *), 0);
649
650 /* "dummy" and command_name are always needed */
651 result->args[0] = "dummy";
652 result->args[1] = estrdup(command_name);
653
654 /* append filename if it's presented */
655 if (strlen(filename)) result->args[args_counter++] = estrdup(filename);
656
657 zend_hash_internal_pointer_reset(Z_ARRVAL_P(options));
658 for (i=0; i < option_count; i++) {
659 zval *item;
660 smart_string option = {0}; /* one argument option */
661
662 /* force using strings as array items */
663 item = zend_hash_get_current_data(Z_ARRVAL_P(options));
664 if (Z_TYPE_P(item) != IS_STRING) convert_to_string(item);
665 smart_string_appendl(&option, Z_STRVAL_P(item), Z_STRLEN_P(item));
666 smart_string_0(&option);
667
668 result->args[args_counter++] = estrdup(option.c);
669 smart_string_free(&option);
670
671 zend_hash_move_forward(Z_ARRVAL_P(options));
672 }
673
674 return result;
675 }
676 /* }}} */
677
678 /* {{{ Frees all memory for arguments object
679 */
680 void rrd_args_free(rrd_args *args)
681 {
682 int i;
683 if (!args || !args->args) return;
684
685 for (i = 1; i < args->count; i++) {
686 efree(args->args[i]);
687 }
688
689 efree(args->args);
690 efree(args);
691 }
692 /* }}} */
693