From ef4ffb1589ca2a1cb2b448b3583c1571b9918807 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 8 Jul 2017 11:43:42 +0200 Subject: [PATCH 1/2] Fixed issue #1506: Add garbage collection statistics feature to Xdebug. Configuration is done through INI settings: xdebug.gc_stats_enable=1 Enables the collection of Garbage collection stats Or explicitly in PHP code with: +--EXPECTF-- +Garbage Collection Report +version: 1 +creator: xdebug %d.%s (PHP %s) + +Collected | Efficiency% | Duration | Memory Before | Memory After | Reduction% | Function +----------+-------------+----------+---------------+--------------+------------+--------- diff --git a/tests/xdebug_gc_stats2.phpt b/tests/xdebug_gc_stats2.phpt new file mode 100644 index 000000000..9889c6ad6 --- /dev/null +++ b/tests/xdebug_gc_stats2.phpt @@ -0,0 +1,31 @@ +--TEST-- +GC Stats: Run gc_collect_cyles(); and collect stats +--INI-- +zend.enable_gc=1 +xdebug.gc_stats_enable=1 +--FILE-- +a = $a; + $a->b = $b; + unset($a, $b); +} +gc_collect_cycles(); + +echo file_get_contents(xdebug_get_gcstats_filename()); +xdebug_stop_gcstats(); +unlink(xdebug_get_gcstats_filename()); +?> +--EXPECTF-- +string(1) "1" +Garbage Collection Report +version: 1 +creator: xdebug %d.%s (PHP %s) + +Collected | Efficiency% | Duration | Memory Before | Memory After | Reduction% | Function +----------+-------------+----------+---------------+--------------+------------+--------- + 200 | 2.00 % | %s ms | %d | %d | %s % | gc_collect_cycles diff --git a/tests/xdebug_gc_stats3.phpt b/tests/xdebug_gc_stats3.phpt new file mode 100644 index 000000000..689f55c9d --- /dev/null +++ b/tests/xdebug_gc_stats3.phpt @@ -0,0 +1,36 @@ +--TEST-- +GC Stats: Stats about trigger garbage collection automatically +--INI-- +zend.enable_gc=1 +xdebug.gc_stats_enable=1 +--FILE-- +a = $a; + $a->b = $b; + unset($a, $b); + } +} + +foo(); + +$lines = file(xdebug_get_gcstats_filename()); +xdebug_stop_gcstats(); +unlink(xdebug_get_gcstats_filename()); + +var_dump(count($lines) >= 6); +?> +--EXPECTF-- +bool(true) diff --git a/tests/xdebug_gc_stats4.phpt b/tests/xdebug_gc_stats4.phpt new file mode 100644 index 000000000..19a67f028 --- /dev/null +++ b/tests/xdebug_gc_stats4.phpt @@ -0,0 +1,61 @@ +--TEST-- +GC Stats: Class with garbage +--INI-- +zend.enable_gc=1 +xdebug.gc_stats_enable=1 +--FILE-- +a = $a; + $a->b = $b; + unset($a, $b); + } +} + +class Garbage +{ + public function produce() + { + $foo = new stdClass(); + + for ($i = 0; $i < 20000; $i++) + { + $a = new stdClass(); + $b = new stdClass(); + $b->foo = $foo; + $b->a = $a; + $a->b = $b; + unset($a, $b); + } + + unset($foo); + gc_collect_cycles(); + } +} + +foo(); +(new Garbage())->produce(); + +$data = file_get_contents(xdebug_get_gcstats_filename()); +xdebug_stop_gcstats(); +unlink(xdebug_get_gcstats_filename()); + +var_dump(substr_count($data, "bar") >= 3); +var_dump(substr_count($data, "Garbage::produce") >= 4); +var_dump(substr_count($data, "gc_collect_cycles") == 1); +?> +--EXPECTF-- +bool(true) +bool(true) +bool(true) diff --git a/tests/xdebug_gc_stats5.phpt b/tests/xdebug_gc_stats5.phpt new file mode 100644 index 000000000..77d8c6ad6 --- /dev/null +++ b/tests/xdebug_gc_stats5.phpt @@ -0,0 +1,46 @@ +--TEST-- +GC Stats: Start with xdebug_start_gcstats() +--INI-- +zend.enable_gc=1 +xdebug.gc_stats_enable=0 +--FILE-- +a = $a; + $a->b = $b; + unset($a, $b); +} +gc_collect_cycles(); + +xdebug_start_gcstats(); + +for ($i = 0; $i < 100; $i++) +{ + $a = new stdClass(); + $b = new stdClass(); + $b->a = $a; + $a->b = $b; + unset($a, $b); +} +gc_collect_cycles(); + +echo file_get_contents(xdebug_get_gcstats_filename()); +xdebug_stop_gcstats(); +unlink(xdebug_get_gcstats_filename()); +?> +--EXPECTF-- +string(1) "0" +Garbage Collection Report +version: 1 +creator: xdebug %d.%s (PHP %s) + +Collected | Efficiency% | Duration | Memory Before | Memory After | Reduction% | Function +----------+-------------+----------+---------------+--------------+------------+--------- + 200 | 2.00 % | %s ms | %d | %d | %s % | gc_collect_cycles + diff --git a/tests/xdebug_gc_stats6.phpt b/tests/xdebug_gc_stats6.phpt new file mode 100644 index 000000000..15b48e7e9 --- /dev/null +++ b/tests/xdebug_gc_stats6.phpt @@ -0,0 +1,21 @@ +--TEST-- +GC Stats: Start with xdebug_start_gcstats() and filename +--INI-- +zend.enable_gc=1 +xdebug.gc_stats_enable=0 +--FILE-- + +--EXPECTF-- +bool(false) +bool(true) +string(%d) "%sgcstats.txt" diff --git a/tests/xdebug_gc_stats8.phpt b/tests/xdebug_gc_stats8.phpt new file mode 100644 index 000000000..b37776351 --- /dev/null +++ b/tests/xdebug_gc_stats8.phpt @@ -0,0 +1,59 @@ +--TEST-- +GC Stats: Closure with garbage +--INI-- +zend.enable_gc=1 +xdebug.gc_stats_enable=1 +--FILE-- +a = $a; + $a->b = $b; + unset($a, $b); + } +} + +$closure = function() { + $foo = new stdClass(); + + for ($i = 0; $i < 20000; $i++) + { + $a = new stdClass(); + $b = new stdClass(); + $b->foo = $foo; + $b->a = $a; + $a->b = $b; + unset($a, $b); + } + + unset($foo); + gc_collect_cycles(); +}; + +foo(); +$closure(); + +$data = file_get_contents(xdebug_get_gcstats_filename()); +xdebug_stop_gcstats(); +unlink(xdebug_get_gcstats_filename()); + +var_dump(substr_count($data, "bar") >= 3); +var_dump(substr_count($data, "{closure:") >= 4); +var_dump(substr_count($data, "xdebug_gc_stats8.php:20-35}") >= 4); +var_dump(substr_count($data, "gc_collect_cycles") == 1); +?> +--EXPECTF-- +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/xdebug.c b/xdebug.c index cc4f4211e..5497bb451 100644 --- a/xdebug.c +++ b/xdebug.c @@ -54,6 +54,7 @@ #include "xdebug_code_coverage.h" #include "xdebug_com.h" #include "xdebug_filter.h" +#include "xdebug_gc_stats.h" #include "xdebug_llist.h" #include "xdebug_mm.h" #include "xdebug_monitor.h" @@ -159,6 +160,13 @@ ZEND_BEGIN_ARG_INFO_EX(xdebug_stop_code_coverage_args, ZEND_SEND_BY_VAL, ZEND_RE ZEND_ARG_INFO(0, cleanup) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(xdebug_start_gcstats_args, ZEND_SEND_BY_VAL, ZEND_RETURN_VALUE, 0) + ZEND_ARG_INFO(0, fname) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(xdebug_stop_gcstats_args, ZEND_SEND_BY_VAL, ZEND_RETURN_VALUE, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_INFO_EX(xdebug_set_filter_args, ZEND_SEND_BY_VAL, ZEND_RETURN_VALUE, 3) ZEND_ARG_INFO(0, filter_group) ZEND_ARG_INFO(0, filter_type) @@ -194,6 +202,10 @@ zend_function_entry xdebug_functions[] = { PHP_FE(xdebug_dump_aggr_profiling_data, xdebug_dump_aggr_profiling_data_args) PHP_FE(xdebug_clear_aggr_profiling_data, xdebug_void_args) + PHP_FE(xdebug_start_gcstats, xdebug_start_gcstats_args) + PHP_FE(xdebug_stop_gcstats, xdebug_stop_gcstats_args) + PHP_FE(xdebug_get_gcstats_filename, xdebug_void_args) + PHP_FE(xdebug_memory_usage, xdebug_void_args) PHP_FE(xdebug_peak_memory_usage, xdebug_void_args) PHP_FE(xdebug_time_index, xdebug_void_args) @@ -384,6 +396,11 @@ PHP_INI_BEGIN() /* Scream support */ STD_PHP_INI_BOOLEAN("xdebug.scream", "0", PHP_INI_ALL, OnUpdateBool, do_scream, zend_xdebug_globals, xdebug_globals) + + /* GC Stats support */ + STD_PHP_INI_BOOLEAN("xdebug.gc_stats_enable", "0", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateBool, gc_stats_enable, zend_xdebug_globals, xdebug_globals) + STD_PHP_INI_ENTRY("xdebug.gc_stats_output_dir", XDEBUG_TEMP_DIR, PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateString, gc_stats_output_dir, zend_xdebug_globals, xdebug_globals) + STD_PHP_INI_ENTRY("xdebug.gc_stats_output_name", "gcstats.%p", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateString, gc_stats_output_name, zend_xdebug_globals, xdebug_globals) PHP_INI_END() static void php_xdebug_init_globals (zend_xdebug_globals *xg TSRMLS_DC) @@ -430,6 +447,10 @@ static void php_xdebug_init_globals (zend_xdebug_globals *xg TSRMLS_DC) xg->filters_tracing = NULL; xg->filters_code_coverage = NULL; + xg->gc_stats_file = NULL; + xg->gc_stats_filename = NULL; + xg->gc_stats_enabled = 0; + xdebug_llist_init(&xg->server, xdebug_superglobals_dump_dtor); xdebug_llist_init(&xg->get, xdebug_superglobals_dump_dtor); xdebug_llist_init(&xg->post, xdebug_superglobals_dump_dtor); @@ -721,6 +742,10 @@ PHP_MINIT_FUNCTION(xdebug) xdebug_old_error_cb = zend_error_cb; xdebug_new_error_cb = xdebug_error_cb; + /* Replace garbage collection handler with our own */ + xdebug_old_gc_collect_cycles = gc_collect_cycles; + gc_collect_cycles = xdebug_gc_collect_cycles; + /* Get reserved offsets */ zend_xdebug_cc_run_offset = zend_get_resource_handle(&dummy_ext); zend_xdebug_filter_offset = zend_get_resource_handle(&dummy_ext); @@ -876,6 +901,7 @@ PHP_MSHUTDOWN_FUNCTION(xdebug) zend_execute_ex = xdebug_old_execute_ex; zend_execute_internal = xdebug_old_execute_internal; zend_error_cb = xdebug_old_error_cb; + gc_collect_cycles = xdebug_old_gc_collect_cycles; zend_hash_destroy(&XG(aggr_calls)); @@ -1182,6 +1208,9 @@ PHP_RINIT_FUNCTION(xdebug) XG(code_coverage_filter_offset) = zend_xdebug_filter_offset; XG(previous_filename) = ""; XG(previous_file) = NULL; + XG(gc_stats_file) = NULL; + XG(gc_stats_filename) = NULL; + XG(gc_stats_enabled) = 0; xdebug_init_auto_globals(TSRMLS_C); @@ -1302,6 +1331,14 @@ ZEND_MODULE_POST_ZEND_DEACTIVATE_D(xdebug) XG(profile_filename_refs) = NULL; XG(profile_functionname_refs) = NULL; + if (XG(gc_stats_enabled)) { + xdebug_gc_stats_stop(); + } + + if (XG(gc_stats_filename)) { + xdfree(XG(gc_stats_filename)); + } + if (XG(ide_key)) { xdfree(XG(ide_key)); XG(ide_key) = NULL; @@ -1763,6 +1800,12 @@ void xdebug_execute_ex(zend_execute_data *execute_data TSRMLS_DC) * value, but we still have to free it. */ xdfree(xdebug_start_trace(NULL, STR_NAME_VAL(op_array->filename), XG(trace_options) TSRMLS_CC)); } + + if (!XG(gc_stats_enabled) && XG(gc_stats_enable)) { + if (xdebug_gc_stats_init(NULL, STR_NAME_VAL(op_array->filename)) == SUCCESS) { + XG(gc_stats_enabled) = 1; + } + } } XG(level)++; diff --git a/xdebug_gc_stats.c b/xdebug_gc_stats.c new file mode 100644 index 000000000..f22256c8d --- /dev/null +++ b/xdebug_gc_stats.c @@ -0,0 +1,236 @@ +/* + +----------------------------------------------------------------------+ + | Xdebug | + +----------------------------------------------------------------------+ + | Copyright (c) 2002-2017 Derick Rethans | + +----------------------------------------------------------------------+ + | This source file is subject to version 1.0 of the Xdebug license, | + | that is bundled with this package in the file LICENSE, and is | + | available at through the world-wide-web at | + | http://xdebug.derickrethans.nl/license.php | + | If you did not receive a copy of the Xdebug license and are unable | + | to obtain it through the world-wide-web, please send a note to | + | xdebug@derickrethans.nl so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Benjamin Eberlei | + +----------------------------------------------------------------------+ + */ + +#include "php_xdebug.h" +#include "xdebug_gc_stats.h" +#include "xdebug_stack.h" +#include "zend_builtin_functions.h" +#include "SAPI.h" + +ZEND_EXTERN_MODULE_GLOBALS(xdebug) + +static void xdebug_gc_stats_print_run(xdebug_gc_run *run); +static void xdebug_gc_stats_run_free(xdebug_gc_run *run); + +int xdebug_gc_collect_cycles(void) +{ + int ret; + uint32_t collected; + xdebug_gc_run *run; + zend_execute_data *execute_data; + long int memory; + double start; + xdebug_func tmp; + + if (!XG(gc_stats_enabled)) { + return xdebug_old_gc_collect_cycles(); + } + + execute_data = EG(current_execute_data); + + collected = GC_G(collected); + start = xdebug_get_utime(); + memory = zend_memory_usage(0); + + ret = xdebug_old_gc_collect_cycles(); + + run = xdmalloc(sizeof(xdebug_gc_run)); + run->function_name = NULL; + run->class_name = NULL; + + run->collected = GC_G(collected) - collected; + run->duration = xdebug_get_utime() - start; + run->memory_before = memory; + run->memory_after = zend_memory_usage(0); + + xdebug_build_fname(&tmp, execute_data); + + run->function_name = tmp.function ? xdstrdup(tmp.function) : NULL; + run->class_name = tmp.class ? xdstrdup(tmp.class) : NULL; + + xdebug_gc_stats_print_run(run); + + xdebug_gc_stats_run_free(run); + + return ret; +} + +static void xdebug_gc_stats_run_free(xdebug_gc_run *run) +{ + if (run) { + if (run->function_name) { + xdfree(run->function_name); + } + if (run->class_name) { + xdfree(run->class_name); + } + xdfree(run); + } +} + +int xdebug_gc_stats_init(char *fname, char *script_name) +{ + char *filename = NULL; + + if (fname && strlen(fname)) { + filename = xdstrdup(fname); + } else { + if (!strlen(XG(gc_stats_output_name)) || + xdebug_format_output_filename(&fname, XG(gc_stats_output_name), script_name) <= 0) + { + return FAILURE; + } + + if (IS_SLASH(XG(gc_stats_output_dir)[strlen(XG(gc_stats_output_dir)) - 1])) { + filename = xdebug_sprintf("%s%s", XG(gc_stats_output_dir), fname); + } else { + filename = xdebug_sprintf("%s%c%s", XG(gc_stats_output_dir), DEFAULT_SLASH, fname); + } + xdfree(fname); + } + + XG(gc_stats_file) = xdebug_fopen(filename, "w", NULL, &XG(gc_stats_filename)); + xdfree(filename); + + if (!XG(gc_stats_file)) { + return FAILURE; + } + + fprintf(XG(gc_stats_file), "Garbage Collection Report\n"); + fprintf(XG(gc_stats_file), "version: 1\ncreator: xdebug %s (PHP %s)\n\n", XDEBUG_VERSION, PHP_VERSION); + + fprintf(XG(gc_stats_file), "Collected | Efficiency%% | Duration | Memory Before | Memory After | Reduction%% | Function\n"); + fprintf(XG(gc_stats_file), "----------+-------------+----------+---------------+--------------+------------+---------\n"); + + fflush(XG(gc_stats_file)); + + return SUCCESS; +} + +void xdebug_gc_stats_stop() +{ + XG(gc_stats_enabled) = 0; + + if (XG(gc_stats_file)) { + fclose(XG(gc_stats_file)); + XG(gc_stats_file) = NULL; + } +} + +static void xdebug_gc_stats_print_run(xdebug_gc_run *run) +{ + double reduction = (1 - (float)run->memory_after / (float)run->memory_before) * 100.0; + + if (!XG(gc_stats_file)) { + return; + } + + if (!run->function_name) { + fprintf(XG(gc_stats_file), + "%9lu | %9.2f %% | %5.2f ms | %13lu | %12lu | %8.2f %% | -\n", + run->collected, + (run->collected / 10000.0) * 100.0, + run->duration / 1000.0, + run->memory_before, + run->memory_after, + reduction + ); + } else if (!run->class_name && run->function_name) { + fprintf(XG(gc_stats_file), + "%9lu | %9.2f %% | %5.2f ms | %13lu | %12lu | %8.2f %% | %s\n", + run->collected, + (run->collected / 10000.0) * 100.0, + run->duration / 1000.0, + run->memory_before, + run->memory_after, + reduction, + run->function_name + ); + } else if (run->class_name && run->function_name) { + fprintf(XG(gc_stats_file), + "%9lu | %9.2f %% | %5.2f ms | %13lu | %12lu | %8.2f %% | %s::%s\n", + run->collected, + (run->collected / 10000.0) * 100.0, + run->duration / 1000.0, + run->memory_before, + run->memory_after, + reduction, + run->class_name, + run->function_name + ); + } + + fflush(XG(gc_stats_file)); +} + +/* {{{ proto void xdebug_get_gcstats_filename() + Returns the name of the current garbage collection statistics report file */ +PHP_FUNCTION(xdebug_get_gcstats_filename) +{ + if (XG(gc_stats_filename)) { + RETURN_STRING(XG(gc_stats_filename)); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto void xdebug_start_gcstats([string $fname]) + Start collecting garbage collection statistics */ +PHP_FUNCTION(xdebug_start_gcstats) +{ + char *fname = NULL; + size_t fname_len = 0; + function_stack_entry *fse; + + if (XG(gc_stats_enabled) == 0) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &fname, &fname_len) == FAILURE) { + return; + } + + fse = xdebug_get_stack_frame(0 TSRMLS_CC); + + if (xdebug_gc_stats_init(fname, fse->filename) == SUCCESS) { + XG(gc_stats_enabled) = 1; + RETVAL_STRING(XG(gc_stats_filename)); + return; + } else { + php_error(E_NOTICE, "Garbage Collection statistics could not be started"); + } + + XG(gc_stats_enabled) = 0; + RETURN_FALSE; + } else { + php_error(E_NOTICE, "Garbage Collection statistics are already being collected."); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto void xdebug_stop_gcstats() + Stop collecting garbage collection statistics */ +PHP_FUNCTION(xdebug_stop_gcstats) +{ + if (XG(gc_stats_enabled) == 1) { + RETVAL_STRING(XG(gc_stats_filename)); + xdebug_gc_stats_stop(); + } else { + RETVAL_FALSE; + php_error(E_NOTICE, "Garbage Collection statistics was not started"); + } +} diff --git a/xdebug_gc_stats.h b/xdebug_gc_stats.h new file mode 100644 index 000000000..ad6b6d7ea --- /dev/null +++ b/xdebug_gc_stats.h @@ -0,0 +1,37 @@ +/* + +----------------------------------------------------------------------+ + | Xdebug | + +----------------------------------------------------------------------+ + | Copyright (c) 2002-2017 Derick Rethans | + +----------------------------------------------------------------------+ + | This source file is subject to version 1.0 of the Xdebug license, | + | that is bundled with this package in the file LICENSE, and is | + | available at through the world-wide-web at | + | http://xdebug.derickrethans.nl/license.php | + | If you did not receive a copy of the Xdebug license and are unable | + | to obtain it through the world-wide-web, please send a note to | + | xdebug@derickrethans.nl so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Benjamin Eberlei | + +----------------------------------------------------------------------+ + */ + +#ifndef __XDEBUG_GC_STATS_H__ +#define __XDEBUG_GC_STATS_H__ + +typedef struct _xdebug_gc_run { + zend_long collected; + zend_long duration; + zend_long memory_before; + zend_long memory_after; + char *function_name; + char *class_name; +} xdebug_gc_run; + +int (*xdebug_old_gc_collect_cycles)(void); + +int xdebug_gc_stats_init(char *fname, char *script_name); +void xdebug_gc_stats_stop(); +int xdebug_gc_collect_cycles(void); + +#endif diff --git a/xdebug_stack.c b/xdebug_stack.c index 4eea84172..827990628 100644 --- a/xdebug_stack.c +++ b/xdebug_stack.c @@ -1014,7 +1014,7 @@ static int find_line_number_for_current_execute_point(zend_execute_data *edata T return 0; } -static void xdebug_build_fname(xdebug_func *tmp, zend_execute_data *edata TSRMLS_DC) +void xdebug_build_fname(xdebug_func *tmp, zend_execute_data *edata TSRMLS_DC) { memset(tmp, 0, sizeof(xdebug_func)); diff --git a/xdebug_stack.h b/xdebug_stack.h index 971445a37..db3c7de1d 100644 --- a/xdebug_stack.h +++ b/xdebug_stack.h @@ -23,6 +23,7 @@ #define XDEBUG_STACK_NO_DESC 0x01 +void xdebug_build_fname(xdebug_func *tmp, zend_execute_data *edata TSRMLS_DC); function_stack_entry *xdebug_add_stack_frame(zend_execute_data *zdata, zend_op_array *op_array, int type TSRMLS_DC); void xdebug_append_error_head(xdebug_str *str, int html, char *error_type_str TSRMLS_DC); void xdebug_append_error_description(xdebug_str *str, int html, const char *error_type_str, char *buffer, const char *error_filename, const int error_lineno TSRMLS_DC); From 506ebad617aca400345bae53293f5ff43c25da7b Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 31 Dec 2017 10:13:49 +0100 Subject: [PATCH 2/2] Fixed issue #1507: Add functions to access Zend Engine garbage collection metrics Number of garbage collection runs and total of collected roots metrics exist in Zend Engine, but are not exposed to userland. These are helpful to integrate in development to find out scripts that trigger GC. --- php_xdebug.h | 2 ++ tests/xdebug_gc_stats7.phpt | 22 ++++++++++++++++++++++ xdebug.c | 2 ++ xdebug_gc_stats.c | 14 ++++++++++++++ 4 files changed, 40 insertions(+) create mode 100644 tests/xdebug_gc_stats7.phpt diff --git a/php_xdebug.h b/php_xdebug.h index cb1bdd434..8bad1e0ea 100644 --- a/php_xdebug.h +++ b/php_xdebug.h @@ -121,6 +121,8 @@ PHP_FUNCTION(xdebug_clear_aggr_profiling_data); PHP_FUNCTION(xdebug_start_gcstats); PHP_FUNCTION(xdebug_stop_gcstats); PHP_FUNCTION(xdebug_get_gcstats_filename); +PHP_FUNCTION(xdebug_get_gc_run_count); +PHP_FUNCTION(xdebug_get_gc_total_collected_roots); /* misc functions */ PHP_FUNCTION(xdebug_dump_superglobals); diff --git a/tests/xdebug_gc_stats7.phpt b/tests/xdebug_gc_stats7.phpt new file mode 100644 index 000000000..c0a139836 --- /dev/null +++ b/tests/xdebug_gc_stats7.phpt @@ -0,0 +1,22 @@ +--TEST-- +GC Stats: userland statistic functions +--INI-- +zend.enable_gc=1 +--FILE-- +a = $a; + $a->b = $b; + unset($a, $b); +} +gc_collect_cycles(); + +printf("runs: %d\n", xdebug_get_gc_run_count()); +printf("collected: %d\n", xdebug_get_gc_total_collected_roots()); +?> +--EXPECTF-- +runs: 1 +collected: 200 diff --git a/xdebug.c b/xdebug.c index 5497bb451..a413fedb8 100644 --- a/xdebug.c +++ b/xdebug.c @@ -205,6 +205,8 @@ zend_function_entry xdebug_functions[] = { PHP_FE(xdebug_start_gcstats, xdebug_start_gcstats_args) PHP_FE(xdebug_stop_gcstats, xdebug_stop_gcstats_args) PHP_FE(xdebug_get_gcstats_filename, xdebug_void_args) + PHP_FE(xdebug_get_gc_run_count, xdebug_void_args) + PHP_FE(xdebug_get_gc_total_collected_roots, xdebug_void_args) PHP_FE(xdebug_memory_usage, xdebug_void_args) PHP_FE(xdebug_peak_memory_usage, xdebug_void_args) diff --git a/xdebug_gc_stats.c b/xdebug_gc_stats.c index f22256c8d..c09038712 100644 --- a/xdebug_gc_stats.c +++ b/xdebug_gc_stats.c @@ -234,3 +234,17 @@ PHP_FUNCTION(xdebug_stop_gcstats) php_error(E_NOTICE, "Garbage Collection statistics was not started"); } } + +/* {{{ proto void xdebug_get_gc_run_count() + Return number of times garbage collection was triggered. */ +PHP_FUNCTION(xdebug_get_gc_run_count) +{ + RETURN_LONG(GC_G(gc_runs)); +} + +/* {{{ proto void xdebug_get_gc_total_collected_roots() + Return total number of collected root variables during garbage collection. */ +PHP_FUNCTION(xdebug_get_gc_total_collected_roots) +{ + RETURN_LONG(GC_G(collected)); +}