From c8d6e55cbe315e84cbfeb076ea9cebcdcab53ed0 Mon Sep 17 00:00:00 2001 From: Benoit Jacquemont Date: Fri, 10 Nov 2017 15:38:55 +0100 Subject: [PATCH] Simplifies code and add summary analyzer --- .travis.yml | 29 +++ README.md | 169 ++++--------- .../Analyzer/SummaryCreatorSpec.php | 176 ++++++++++++++ .../PhpMemInfo/Analyzer/SummaryCreator.php | 65 +++++ .../BitOne/PhpMemInfo/Console/Application.php | 2 + .../Console/Command/SummaryCommand.php | 82 +++++++ extension/examples/gc_roots_list.php | 27 --- extension/examples/info_dump.php | 44 ---- extension/examples/info_dump_object.php | 15 -- extension/examples/objects_in_function.php | 19 -- extension/examples/objects_list.php | 57 ----- extension/examples/objects_list_refcount.php | 24 -- extension/examples/objects_summary.php | 57 ----- extension/examples/structs_size.php | 4 - extension/examples/symbol_table.php | 27 --- extension/php5/meminfo.c | 217 +---------------- extension/php5/php_meminfo.h | 20 +- extension/php5/tests/fixture/book.xml | 45 ---- extension/php5/tests/fixtures/books.php | 40 ++++ extension/php5/tests/info_dump.phpt | 11 +- .../php5/tests/info_dump_memory-leak.phpt | 9 +- .../tests/objects_list-deleted-object.phpt | 18 -- .../php5/tests/objects_list-no_objects.phpt | 9 - extension/php5/tests/objects_list.phpt | 17 -- .../php5/tests/objects_list_refcount.phpt | 27 --- .../tests/objects_summary-no-objects.phpt | 10 - extension/php5/tests/objects_summary.phpt | 29 --- extension/php7/meminfo.c | 225 ++++-------------- extension/php7/php_meminfo.h | 13 +- extension/php7/tests/fixtures/books.php | 40 ++++ extension/php7/tests/info_dump.phpt | 115 +++++++++ .../php7/tests/info_dump_memory-leak.phpt | 36 +++ .../tests/objects_list-deleted-object.phpt | 18 -- .../php7/tests/objects_list-no_objects.phpt | 9 - extension/php7/tests/objects_list.phpt | 17 -- .../php7/tests/objects_list_refcount.phpt | 25 -- .../tests/objects_summary-no-objects.phpt | 10 - extension/php7/tests/objects_summary.phpt | 29 --- 38 files changed, 702 insertions(+), 1084 deletions(-) create mode 100644 .travis.yml create mode 100644 analyzer/spec/BitOne/PhpMemInfo/Analyzer/SummaryCreatorSpec.php create mode 100644 analyzer/src/BitOne/PhpMemInfo/Analyzer/SummaryCreator.php create mode 100644 analyzer/src/BitOne/PhpMemInfo/Console/Command/SummaryCommand.php delete mode 100644 extension/examples/gc_roots_list.php delete mode 100644 extension/examples/info_dump.php delete mode 100644 extension/examples/info_dump_object.php delete mode 100644 extension/examples/objects_in_function.php delete mode 100644 extension/examples/objects_list.php delete mode 100644 extension/examples/objects_list_refcount.php delete mode 100644 extension/examples/objects_summary.php delete mode 100644 extension/examples/structs_size.php delete mode 100644 extension/examples/symbol_table.php delete mode 100644 extension/php5/tests/fixture/book.xml create mode 100644 extension/php5/tests/fixtures/books.php delete mode 100644 extension/php5/tests/objects_list-deleted-object.phpt delete mode 100644 extension/php5/tests/objects_list-no_objects.phpt delete mode 100644 extension/php5/tests/objects_list.phpt delete mode 100644 extension/php5/tests/objects_list_refcount.phpt delete mode 100644 extension/php5/tests/objects_summary-no-objects.phpt delete mode 100644 extension/php5/tests/objects_summary.phpt create mode 100644 extension/php7/tests/fixtures/books.php create mode 100644 extension/php7/tests/info_dump.phpt create mode 100644 extension/php7/tests/info_dump_memory-leak.phpt delete mode 100644 extension/php7/tests/objects_list-deleted-object.phpt delete mode 100644 extension/php7/tests/objects_list-no_objects.phpt delete mode 100644 extension/php7/tests/objects_list.phpt delete mode 100644 extension/php7/tests/objects_list_refcount.phpt delete mode 100644 extension/php7/tests/objects_summary-no-objects.phpt delete mode 100644 extension/php7/tests/objects_summary.phpt diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..57df2e1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +sudo: false + +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - 7.2 + - nightly + +matrix: + allow_failures: + - php: nightly + +before_script: + - phpenv config-rm xdebug.ini + +script: + - cd php$(echo $TRAVIS_PHP_VERSION | cut -b 1)/ + - phpize + - configure + - make + - make test + +after_script: + - ./.travis.scripts/show-errors.sh diff --git a/README.md b/README.md index 018d4e1..21b5703 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,7 @@ One of the main source of inspiration for this tool is the Java jmap tool with t Compatibility ------------- -Compiled and tested on: - - - PHP 5.4.4 (Debian 7) - - PHP 5.5.8 (Ubuntu 12.04 LTS) - - PHP 5.5.20 (CentOS 7) - - PHP 5.6.17 (Debian 8) +PHP 5.6 and PHP 7.1 (may work on PHP 7.0 and 7.2, but not tested yet). Compilation instructions ------------------------ @@ -21,11 +16,16 @@ You will need the `phpize` command. It can be installed on a Debian based system ```bash $ apt-get install php5-dev ``` +for PHP 5, or +```bash +$ apt-get install php7.1-dev +``` +for PHP 7.1 when using the Ondrej repository from sury.org. Once you have this command, follow this steps: ## Compilation -From the root of the `extension/` directory: +From the root of the `extension/php5` for PHP 5 or `extension/php7` for PHP 7 directory: ```bash $ phpize @@ -47,61 +47,57 @@ Analyzers allow to analyze a memory dump (see below). ```bash $ cd analyzers -$ composer update +$ composer install ``` Usage ----- -All meminfo functions take a stream handle as a parameter. It allows you to specify a file (ex `fopen('/tmp/file.txt', 'w')`, as well as to use standard output with the `php://stdout` stream. - -## Object instances count per class -Display the number of instances per class, ordered descending. Very useful to identify the content of a memory leak. - -```php - meminfo_objects_summary(fopen('php://stdout','w')); -``` +The extension has one main function: `meminfo_info_dump`. -The result will provide something similar to the following example generated at the end of a Symfony2 console launch: +This function generates a dump of the PHP memory in a JSON format. This dump can be later analyzed by the provided analyzers. -``` - Instances count by class: - num #instances class - ----------------------------------------------------------------- - 1 181 Symfony\Component\Console\Input\InputOption - 2 88 Symfony\Component\Console\Input\InputDefinition - 3 77 ReflectionObject - 4 46 Symfony\Component\Console\Input\InputArgument - 5 2 Symfony\Bridge\Monolog\Logger - 6 1 Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher - 7 1 Doctrine\Bundle\MigrationsBundle\Command\MigrationsDiffDoctrineCommand - ... -``` - -Note: It's a good idea to call the `gc_collect_cycles()` function before executing `meminfo_objects_summary()`, as it will collect dead objects that has not been reclaimed by the ref counter due to circular references. See http://www.php.net/manual/en/features.gc.php for more details. - - -### Examples -The `examples/` directory at the root of the repository contains more detailed examples. -```bash - $ php examples/objects_summary.php -``` +This functions takes a stream handle as a parameter. It allows you to specify a file (ex `fopen('/tmp/file.txt', 'w')`, as well as to use standard output with the `php://stdout` stream. -## Memory state dump -This feature allow to dump the list of items present in memory at the time of the function execution. Each memory items (string, boolean, objects, array, etc...) are dumped in a JSON format, with the following information: - - in memory address +## Information gathered +Each memory item (string, boolean, objects, array, etc...) is dumped with the following information: + - in-memory address - type (object, array, int, string, ...) - class (only for objects) - - object handle (only for objects. + - object handle (only for objects) - self size (without the size of the linked objects) - - is_root (tells if the item is directly linked to a variable) - - symbol name (variable name, if linked to a variable) + - is_root (tells if the item is directly linked to a declared variable in the PHP program) + - symbol name (name of the variable name in the PHP program, if the item is linked to a variable) - execution frame (name of the method where the variable has been declared) - - children: list of linked items with the key value if array or property name if object and the item address in memory + - children: list of linked items with the key name in case of array or property name if object, associated to their address in memory ### Dumping memory info ```php meminfo_info_dump(fopen('/tmp/my_dump_file.json', 'w')); + ``` +Memory Leak Consequences +------------------------ + +The main consequences of a memory leak are: + - increasing memory usage + - decreasing performances + +Decreasing performances is usually the most visible part. As memory leak saturates the garbage collector buffer, it runs far more often, without being able to free any memory. This leads to a high CPU usage of the process, with a lot of time spent in the garbage collector instead of your code (garbage collector doesn't run in parallel with the user code in PHP, it has to interrupt it). + +See https://speakerdeck.com/bitone/hunting-down-memory-leaks-with-php-meminfo for a more detailed insight on how memory leak can occur. + +Memory Leak Hunting Process +---------------------------- +## Overview + 1. dump memory state with `meminfo_info_dump` + 2. use the *summary* command of the analyzer to display the item type that is the most present in memory. It's even better to use the summary to display the evolution of objects in memory in order, as the evolution will show where the memory leak really is + 3. use the *query* command of the analyzer to find one item from the class that is leaking + 4. use the *ref-path* command analyzer to find out the references that still hold this object in memory + +## Object Leaks +On object oriented programming, a memory leak usually consists of *objects* leak. + +## Memory state dump ### Analyzing a memory dump The analyzer is available from the `analyzer/` directory. It will be invoked with: @@ -129,87 +125,12 @@ When you are tracking down a memory leak, it's very interesting to understand wh The analyzer provides the `ref-path` command that load the memory dump as a graph in memory and findout all paths linking an item to a root (a variable define in an execution frame). -Without the `-v` option, the output will contains only item memory adress and key/property name. Adding the `-v` option will display all the information of the linked items. +Without the `-v` option, the output will contains only item memory address and key/property name. Adding the `-v` option will display all the information of the linked items. ```bash $ bin/analyzer ref-path my_dump_file.json 0x12345678 ``` -## List of items in memory -Provides a list of items in memory (objects, arrays, string, etc.) with their sizes. - -```php - meminfo_size_info(fopen('php://stdout','w')); -``` - -For example: -```json -// ... - "0x7fe06ea50a40" : { - "type" : "array", - "size" : "96", - "children" : { - "0":"0x7fe06ea649b0" - } - - }, - "0x7fe06ea649b0" : { - "type" : "string", - "size" : "99" - - }, -//... -``` - -Note: The same remark about `gc_collect_cycles()` before `meminfo_objects_summary()` applies as well for this function. - -### Examples -The `examples/` directory at the root of the repository contains more detailed examples. - - php examples/size_info.php - -##List of currently active objects -Provides a list of live objects with their class and their handle, as well as the total number of active objects and the total number of allocated object buckets. - -```php - meminfo_objects_list(fopen('php://stdout','w')); -``` - -For example: - - Objects list: - - Class MyClassB, handle 2, refCount 1 - - Class MyClassC, handle 5, refCount 1 - - Class MyClassC, handle 6, refCount 1 - - Class MyClassC, handle 7, refcount 1 - Total object buckets: 7. Current objects: 4. - -Note: The same remark about `gc_collect_cycles()` before `meminfo_objects_summary()` applies as well for this function. - -### Examples -The `examples/` directory at the root of the repository contains more detailed examples. - - php examples/objects_list.php - -## Information on structs size -Display size in byte of main data structs size in PHP. Will mainly differ between 32bits et 64bits environments. - -```php - meminfo_structs_size(fopen('php://stdout','w')); -``` - -It can be useful to understand difference in memory usage between two platforms. - -Example Output on 64bits environment: - -``` - Structs size on this platform: - Class (zend_class_entry): 568 bytes. - Object (zend_object): 32 bytes. - Variable (zval): 24 bytes. - Variable value (zvalue_value): 16 bytes. -``` - Usage in production ------------------- PHP Meminfo can be used in production, as it does not have any impact on performances outside of the call to the `meminfo` functions. @@ -227,12 +148,12 @@ Provides aggregated data about memory usage by functions. Far less resource inte Troubleshooting --------------- -## "Call to undefined function" when calling meminfo_* functions +## "Call to undefined function" when calling `meminfo_info_dump` It certainly means the extension is not enabled. Check the PHP Info output and look for the MemInfo data. -To see the PHP Info output, just create a page calling the `phpinfo();` function, and load it from your browser, or call `php -i` from command line. +To see the PHP Info output, just create a page calling the `phpinfo();` function, and load it from your browser, or call `php -i` from the command line. Credits ------- diff --git a/analyzer/spec/BitOne/PhpMemInfo/Analyzer/SummaryCreatorSpec.php b/analyzer/spec/BitOne/PhpMemInfo/Analyzer/SummaryCreatorSpec.php new file mode 100644 index 0000000..5c27f1e --- /dev/null +++ b/analyzer/spec/BitOne/PhpMemInfo/Analyzer/SummaryCreatorSpec.php @@ -0,0 +1,176 @@ +beConstructedWith([]); + $this->shouldHaveType('BitOne\PhpMemInfo\Analyzer\SummaryCreator'); + } + + function it_creates_summary_with_primitives() + { + $this->beConstructedWith([ + "0x7fb321a94050" => [ + "type" => "string", + "size" => "29" + ], + "0x7fb321a94080" => [ + "type" => "string", + "size" => "29" + ], + "0x7fb321a94378" => [ + "type" => "array", + "size" => "96" + ], + "0x7fb321a94108" => [ + "type" => "string", + "size" => "37" + ], + "0x7fb321a941e0" => [ + "type" => "integer", + "size" => "24" + ], + "0x7fb321a94268" => [ + "type" => "integer", + "size" => "24" + ] + ]); + + $this->createSummary()->shouldReturn([ + "string" => [ + "count" => 3, + "self_size" => 95 + ], + "integer" => [ + "count" => 2, + "self_size" => 48 + ], + "array" => [ + "count" => 1, + "self_size" => 96 + ] + ]); + + } + + function it_creates_summary_with_objects() + { + $this->beConstructedWith([ + "0x7fb321a94050" => [ + "type" => "object", + "class" => "MyClassA", + "size" => "56" + ], + "0x7fb321a94080" => [ + "type" => "object", + "class" => "MyClassA", + "size" => "56" + ], + "0x7fb321a94378" => [ + "type" => "object", + "class" => "MyClassB", + "size" => "56" + ], + "0x7fb321a94108" => [ + "type" => "object", + "class" => "MyClassA", + "size" => "56" + ], + "0x7fb321a941e0" => [ + "type" => "object", + "class" => "MyClassC", + "size" => "56" + ], + "0x7fb321a94268" => [ + "type" => "object", + "class" => "MyClassC", + "size" => "56" + ] + ]); + + $this->createSummary()->shouldReturn([ + "MyClassA" => [ + "count" => 3, + "self_size" => 168 + ], + "MyClassC" => [ + "count" => 2, + "self_size" => 112 + ], + "MyClassB" => [ + "count" => 1, + "self_size" => 56 + ] + ]); + } + + function it_creates_summary_with_primitives_and_objects() + { + $this->beConstructedWith([ + "0x7fb321a94050" => [ + "type" => "string", + "size" => "29" + ], + "0x7fb321a94080" => [ + "type" => "string", + "size" => "29" + ], + "0x7fb321a94108" => [ + "type" => "string", + "size" => "37" + ], + "0x7fe321a94108" => [ + "type" => "string", + "size" => "45" + ], + "0x7fb321a941e0" => [ + "type" => "integer", + "size" => "24" + ], + "0x7fb321a94268" => [ + "type" => "integer", + "size" => "24" + ], + "0x7fc321a94050" => [ + "type" => "object", + "class" => "MyClassA", + "size" => "56" + ], + "0x7fc321a94080" => [ + "type" => "object", + "class" => "MyClassA", + "size" => "56" + ], + "0x7fc321a94378" => [ + "type" => "object", + "class" => "MyClassB", + "size" => "56" + ] + ]); + + $this->createSummary()->shouldReturn([ + "string" => [ + "count" => 4, + "self_size" => 140 + ], + "MyClassA" => [ + "count" => 2, + "self_size" => 112 + ], + "integer" => [ + "count" => 2, + "self_size" => 48 + ], + "MyClassB" => [ + "count" => 1, + "self_size" => 56 + ] + ]); + } +} diff --git a/analyzer/src/BitOne/PhpMemInfo/Analyzer/SummaryCreator.php b/analyzer/src/BitOne/PhpMemInfo/Analyzer/SummaryCreator.php new file mode 100644 index 0000000..d44aeba --- /dev/null +++ b/analyzer/src/BitOne/PhpMemInfo/Analyzer/SummaryCreator.php @@ -0,0 +1,65 @@ + + * @copyright 2017 Benoit Jacquemont + * @license http://opensource.org/licenses/MIT MIT + */ +class SummaryCreator +{ + /** @var array */ + protected $items; + + /** + * @param array $items + */ + public function __construct(array $items) + { + $this->items = $items; + } + + /** + * Create a summary from the existing items, + * aggregated by type/class and sorted by count. + * + * @return array + */ + public function createSummary() + { + $summary = []; + + foreach ($this->items as $item) { + $type = $item['type']; + if ('object' === $type) { + $type = $item['class']; + } + if (!isset($summary[$type])) { + $summary[$type] = ['count' => 0, 'self_size' => 0]; + } + $summary[$type]['count']++; + $summary[$type]['self_size']+= $item['size']; + } + + uasort($summary, function ($a, $b) { + $aCount = $a['count']; + $bCount = $b['count']; + + if ($a === $b) { + return 0; + } + if ($a['count'] > $b['count']) { + return -1; + } else { + return 1; + } + } + ); + + return $summary; + } +} diff --git a/analyzer/src/BitOne/PhpMemInfo/Console/Application.php b/analyzer/src/BitOne/PhpMemInfo/Console/Application.php index 9d565a3..4e69d1a 100644 --- a/analyzer/src/BitOne/PhpMemInfo/Console/Application.php +++ b/analyzer/src/BitOne/PhpMemInfo/Console/Application.php @@ -4,6 +4,7 @@ use BitOne\PhpMemInfo\Console\Command\QueryCommand; use BitOne\PhpMemInfo\Console\Command\ReferencePathCommand; +use BitOne\PhpMemInfo\Console\Command\SummaryCommand; use Symfony\Component\Console\Application as BaseApplication; /** @@ -19,5 +20,6 @@ public function __construct() $this->add(new QueryCommand()); $this->add(new ReferencePathCommand()); + $this->add(new SummaryCommand()); } } diff --git a/analyzer/src/BitOne/PhpMemInfo/Console/Command/SummaryCommand.php b/analyzer/src/BitOne/PhpMemInfo/Console/Command/SummaryCommand.php new file mode 100644 index 0000000..c5f2344 --- /dev/null +++ b/analyzer/src/BitOne/PhpMemInfo/Console/Command/SummaryCommand.php @@ -0,0 +1,82 @@ + + * @copyright 2016 Benoit Jacquemont + * @license http://opensource.org/licenses/MIT MIT + */ +class SummaryCommand extends Command +{ + /** + * {@inheritedDoc}. + */ + protected function configure() + { + $this + ->setName('summary') + ->setDescription('Display a summary of items by type from a dump file') + ->addArgument( + 'dump-file', + InputArgument::REQUIRED, + 'PHP Meminfo Dump File in JSON format' + ); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $dumpFilename = $input->getArgument('dump-file'); + + $loader = new Loader(); + + $items = $loader->load($dumpFilename); + + $summaryCreator = new SummaryCreator($items); + + $summary = $summaryCreator->createSummary(); + + $table = new Table($output); + $this->formatTable($summary, $table); + + $table->render(); + + return 0; + } + + /** + * Format data into a detailed table. + * + * @param array $summary + * @param Table $table + */ + protected function formatTable(array $summary, Table $table) + { + $formatter = $this->getHelper('formatter'); + + $table->setHeaders(['Type', 'Instances Count', 'Cumulated Self Size (bytes)']); + + $rows = []; + + foreach($summary as $type => $stats) { + $rows[] = [$type, $stats['count'], $stats['self_size']]; + } + + $table->setRows($rows); + } +} diff --git a/extension/examples/gc_roots_list.php b/extension/examples/gc_roots_list.php deleted file mode 100644 index a818036..0000000 --- a/extension/examples/gc_roots_list.php +++ /dev/null @@ -1,27 +0,0 @@ -myDouble = $myDouble; - $myObject->myString = $myString; - - $myObject2 = new StdClass(); - - $myObjectRef = $myObject; - - $myArray = []; - $myArray[] = "TEST"; - $myArray[] = $myDouble; - $myArray[] = $myString; - $myArray[] = $myArray; - - $myHash = []; - $myHash["entry_one"] = $myString; - $myHash["entry_two"] = $myDouble; - $myHash["entry_\"three\""] = "It has a key with double quotes"; - - $myHash2 = []; - - testObject($myObject); diff --git a/extension/examples/info_dump_object.php b/extension/examples/info_dump_object.php deleted file mode 100644 index 9abd201..0000000 --- a/extension/examples/info_dump_object.php +++ /dev/null @@ -1,15 +0,0 @@ -v = 5; - -function myFunction() { - $myDate = new \DateTime(); - - echo "\n* Objects summary in function call with inside DateTime object\n"; - meminfo_objects_list(fopen('php://stdout', 'w')); - meminfo_objects_summary(fopen('php://stdout', 'w')); -} - -myFunction(); - diff --git a/extension/examples/objects_list.php b/extension/examples/objects_list.php deleted file mode 100644 index 58d42db..0000000 --- a/extension/examples/objects_list.php +++ /dev/null @@ -1,57 +0,0 @@ -= 20010901 STANDARD_MODULE_HEADER, -#endif "meminfo", meminfo_functions, NULL, @@ -37,184 +31,10 @@ zend_module_entry meminfo_module_entry = { NULL, NULL, NULL, -#if ZEND_MODULE_API_NO >= 20010901 MEMINFO_VERSION, -#endif STANDARD_MODULE_PROPERTIES }; -#ifdef COMPILE_DL_MEMINFO -ZEND_GET_MODULE(meminfo) -#endif - -php_stream * get_php_stream_from_zval(zval *zval_stream) -{ - php_stream *stream; - - stream = php_stream_from_zval_no_verify(stream, &zval_stream); - - return stream; -} - -PHP_FUNCTION(meminfo_structs_size) -{ - zval *zval_stream; - php_stream *stream; - - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zval_stream) == FAILURE) { - return; - } - - php_stream_from_zval(stream, &zval_stream); - - php_stream_printf(stream TSRMLS_CC, "Simple Zend Type size on this platform\n"); - php_stream_printf(stream TSRMLS_CC, " Zend Unsigned Int (zend_uint): %ld bytes.\n", sizeof(zend_uint)); - php_stream_printf(stream TSRMLS_CC, " Zend Unsigned Char (zend_uchar): %ld bytes.\n", sizeof(zend_uchar)); - - php_stream_printf(stream TSRMLS_CC, "Structs size on this platform:\n"); - php_stream_printf(stream TSRMLS_CC, " Variable value (zvalue_value): %ld bytes.\n", sizeof(zvalue_value)); - php_stream_printf(stream TSRMLS_CC, " Variable (zval): %ld bytes.\n", sizeof(zval)); - php_stream_printf(stream TSRMLS_CC, " Class (zend_class_entry): %ld bytes.\n", sizeof(zend_class_entry)); - php_stream_printf(stream TSRMLS_CC, " Object (zend_object): %ld bytes.\n", sizeof(zend_object)); -} - -PHP_FUNCTION(meminfo_objects_list) -{ - zval *zval_stream; - php_stream *stream; - - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zval_stream) == FAILURE) { - return; - } - - php_stream_from_zval(stream, &zval_stream); - - php_stream_printf(stream TSRMLS_CC, "Objects list:\n"); - -// TODO: check if object_buckets exists ? See gc_collect_roots from zend_gc.c - zend_objects_store *objects = &EG(objects_store); - zend_uint i; - zend_uint total_objects_buckets = objects->top - 1; - zend_uint current_objects = 0; - zend_object * object; - zend_class_entry * class_entry; - - for (i = 1; i < objects->top ; i++) { - if (objects->object_buckets[i].valid) { - struct _store_object *obj = &objects->object_buckets[i].bucket.obj; - - php_stream_printf(stream TSRMLS_CC, " - Class %s, handle %d, refCount %d\n", meminfo_get_classname(i), i, obj->refcount); - - current_objects++; - } - } - - php_stream_printf(stream TSRMLS_CC, "Total object buckets: %d. Current objects: %d.\n", total_objects_buckets, current_objects); -} - -PHP_FUNCTION(meminfo_objects_summary) -{ - zval *zval_stream = NULL; - php_stream *stream = NULL; - HashTable *classes = NULL; - - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zval_stream) == FAILURE) { - return; - } - php_stream_from_zval(stream, &zval_stream); - - ALLOC_HASHTABLE(classes); - - zend_hash_init(classes, 1000, NULL, NULL, 0); - - zend_objects_store *objects = &EG(objects_store); - zend_uint i; - zend_object *object; - - for (i = 1; i < objects->top ; i++) { - if (objects->object_buckets[i].valid && !objects->object_buckets[i].destructor_called) { - - const char *class_name; - zend_uint *p_instances_count; - - class_name = meminfo_get_classname(i); - - if (zend_hash_find(classes, class_name, strlen(class_name)+1, (void **) &p_instances_count) == SUCCESS) { - (*p_instances_count)++; - } else { - zend_uint instances_count; - instances_count = 1; - p_instances_count = &instances_count; - - zend_hash_update(classes, class_name, strlen(class_name)+1, p_instances_count, sizeof(zend_uint *), NULL); - } - } - } - - zend_hash_sort(classes, zend_qsort, meminfo_instances_count_compare, 0 TSRMLS_CC); - - php_stream_printf(stream TSRMLS_CC, "Instances count by class:\n"); - - php_stream_printf(stream TSRMLS_CC, "%-12s %-12s %s\n", "rank", "#instances", "class"); - php_stream_printf(stream TSRMLS_CC, "-----------------------------------------------------------------\n"); - - zend_uint rank = 1; - - HashPosition position; - - zend_uint *p_instances_count; - - for (zend_hash_internal_pointer_reset_ex(classes, &position); - zend_hash_get_current_data_ex(classes, (void **) &p_instances_count, &position) == SUCCESS; - zend_hash_move_forward_ex(classes, &position)) { - - char *class_name = NULL; - uint class_name_len; - ulong index; - - zend_hash_get_current_key_ex(classes, &class_name, &class_name_len, &index, 0, &position); - php_stream_printf(stream TSRMLS_CC, "%-12d %-12d %s\n", rank, *p_instances_count, class_name); - - rank++; - } - - zend_hash_destroy(classes); - FREE_HASHTABLE(classes); -} - -PHP_FUNCTION(meminfo_gc_roots_list) -{ - zval *zval_stream; - php_stream *stream; - zval* pz; - - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zval_stream) == FAILURE) { - return; - } - - php_stream_from_zval(stream, &zval_stream); - php_stream_printf(stream TSRMLS_CC, "GC roots list:\n"); - - gc_root_buffer *current = GC_G(roots).next; - - while (current != &GC_G(roots)) { - pz = current->u.pz; - php_stream_printf(stream TSRMLS_CC, " zval pointer: %p ", (void *) pz); - if (current->handle) { - php_stream_printf( - stream TSRMLS_CC, - " Class %s, handle %d\n", - meminfo_get_classname(current->handle), - current->handle); - } else { - php_stream_printf(stream TSRMLS_CC, " Type: %s", zend_get_type_by_const(Z_TYPE_P(pz))); - php_stream_printf(stream TSRMLS_CC, ", Ref count GC %d\n", pz->refcount__gc); - - } - current = current->next; - - } -} /** * Generate a JSON output of the list of items in memory (objects, arrays, string, etc...) @@ -294,33 +114,6 @@ PHP_FUNCTION(meminfo_info_dump) FREE_HASHTABLE(visited_items); } -/** - Compare two hashtable buckets by extracting their - int value and return the comparision result. -*/ -static int meminfo_instances_count_compare(const void *a, const void *b TSRMLS_DC) -{ - const Bucket *bucket_a; - const Bucket *bucket_b; - - bucket_a = *((const Bucket **) a); - bucket_b = *((const Bucket **) b); - - zend_uint instances_count_a; - zend_uint instances_count_b; - - instances_count_a = *((zend_uint *) bucket_a->pData); - instances_count_b = *((zend_uint *) bucket_b->pData); - - if (instances_count_a > instances_count_b) { - return -1; - } else if (instances_count_a == instances_count_b) { - return 0; - } else { - return 1; - } -} - /** * Return the class associated to the provided object handle * @@ -411,7 +204,7 @@ void meminfo_hash_dump(php_stream *stream, HashTable *ht, zend_bool is_object, H if (is_object) { const char *property_name, *class_name; - int mangled = zend_unmangle_property_name(key, key_len - 1, &class_name, &property_name); + zend_unmangle_property_name(key, key_len - 1, &class_name, &property_name); char_buf = meminfo_escape_for_json(property_name); php_stream_printf(stream TSRMLS_CC, " \"%s\":\"%p\"", char_buf, *zval ); efree(char_buf); @@ -629,6 +422,7 @@ char * meminfo_escape_for_json(const char *s) { int new_str_len; char *s1, *s2; + s1 = php_str_to_str((char *) s, strlen(s), "\\", 1, "\\\\", 2, &new_str_len); s2 = php_str_to_str(s1, strlen(s1), "\"", 1, "\\\"", 2, &new_str_len); @@ -673,3 +467,10 @@ char * meminfo_info_dump_header(char * header, int header_len) return header; } + +#ifdef COMPILE_DL_MEMINFO +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE(); +#endif +ZEND_GET_MODULE(meminfo) +#endif diff --git a/extension/php5/php_meminfo.h b/extension/php5/php_meminfo.h index 64e8fb8..637dc1e 100644 --- a/extension/php5/php_meminfo.h +++ b/extension/php5/php_meminfo.h @@ -1,20 +1,15 @@ #ifndef PHP_MEMINFO_H #define PHP_MEMINFO_H 1 +#define phpext_meminfo_ptr &meminfo_module_entry + #define MEMINFO_NAME "PHP Meminfo" -#define MEMINFO_VERSION "0.3" +#define MEMINFO_VERSION "1.0" #define MEMINFO_AUTHOR "Benoit Jacquemont" -#define MEMINFO_COPYRIGHT "Copyright (c) 2010-2016 by Benoit Jacquemont" -#define MEMINFO_COPYRIGHT_SHORT "Copyright (c) 2011-2016" - -PHP_FUNCTION(meminfo_structs_size); -PHP_FUNCTION(meminfo_objects_list); -PHP_FUNCTION(meminfo_objects_summary); -PHP_FUNCTION(meminfo_gc_roots_list); -PHP_FUNCTION(meminfo_symbol_table); -PHP_FUNCTION(meminfo_info_dump); +#define MEMINFO_COPYRIGHT "Copyright (c) 2010-2017 by Benoit Jacquemont" +#define MEMINFO_COPYRIGHT_SHORT "Copyright (c) 2011-2017" -static int meminfo_instances_count_compare(const void *a, const void *b TSRMLS_DC); +PHP_FUNCTION(meminfo_info_dump); const char * meminfo_get_classname(zend_object_handle handle); zend_ulong meminfo_get_element_size(zval* z); @@ -28,9 +23,8 @@ int meminfo_visit_item(const char * item_label, HashTable *visited_items); void meminfo_build_frame_label(char * frame_label, int frame_label_len, zend_execute_data* frame); char * meminfo_escape_for_json(const char *s); +char * meminfo_info_dump_header(char * header, int header_len); extern zend_module_entry meminfo_entry; -char * meminfo_info_dump_header(char * header, int header_len); -#define phpext_meminfo_ptr &meminfo_module_entry #endif diff --git a/extension/php5/tests/fixture/book.xml b/extension/php5/tests/fixture/book.xml deleted file mode 100644 index e87fb91..0000000 --- a/extension/php5/tests/fixture/book.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - My lists - - My books - - - - - - Title - Author - Language - ISBN - - - - - The Grapes of Wrath - John Steinbeck - en - 0140186409 - - - The Pearl - John Steinbeck - en - 014017737X - - - Samarcande - Amine Maalouf - fr - 2253051209 - - - - - - - - diff --git a/extension/php5/tests/fixtures/books.php b/extension/php5/tests/fixtures/books.php new file mode 100644 index 0000000..9cc8959 --- /dev/null +++ b/extension/php5/tests/fixtures/books.php @@ -0,0 +1,40 @@ +title = "My lists"; +$books->chapter = [ + "title" => "My books", + "para" => [ + "informaltable" => [ + "tgroup" => [ + "thead" => [ + "Title", + "Author", + "Language", + "ISBN" + ], + "tbody" => [ + [ + "The Grapes of Wrath", + "John Steinbeck", + "en", + "0140186409" + ], + [ + "The Pearl", + "John Steinbeck", + "en", + "014017737X" + ], + [ + "Samarcande", + "Amine Maalouf", + "fr", + "2253051209" + ] + ] + ] + ] + ] +]; diff --git a/extension/php5/tests/info_dump.phpt b/extension/php5/tests/info_dump.phpt index f5e5b45..e3dbdbe 100644 --- a/extension/php5/tests/info_dump.phpt +++ b/extension/php5/tests/info_dump.phpt @@ -1,9 +1,12 @@ --TEST-- meminfo_objects_list with some objects +--SKIPIF-- + --FILE-- load(__DIR__.'/fixture/book.xml'); + require dirname(__FILE__) . '/fixtures/books.php'; $rFilePointer = fopen('php://memory', 'rw'); @@ -13,8 +16,8 @@ meminfo_objects_list with some objects 'itemDoubles' => 1.2e3, 'itemNull' => null, 'itemString' => 'hello', - 'itemObject' => $docTest, - 'itemArray' => (array) $docTest, + 'itemObject' => $books, + 'itemArray' => (array) $books, 'itemResource' => $rFilePointer, ]; diff --git a/extension/php5/tests/info_dump_memory-leak.phpt b/extension/php5/tests/info_dump_memory-leak.phpt index f4bba2a..345a9fc 100644 --- a/extension/php5/tests/info_dump_memory-leak.phpt +++ b/extension/php5/tests/info_dump_memory-leak.phpt @@ -2,8 +2,7 @@ meminfo_objects_list with some objects --FILE-- load(__DIR__.'/fixture/book.xml'); + require dirname(__FILE__) . '/fixtures/books.php'; $rFilePointer = fopen('/dev/null', 'rw'); @@ -13,8 +12,8 @@ meminfo_objects_list with some objects 'itemDoubles' => 1.2e3, 'itemNull' => null, 'itemString' => 'hello', - 'itemObject' => $docTest, - 'itemArray' => (array) $docTest, + 'itemObject' => $books, + 'itemArray' => (array) $books, 'itemResource' => $rFilePointer, ]; $attemptCount = 1000; @@ -26,7 +25,7 @@ meminfo_objects_list with some objects fclose($rFilePointer); gc_collect_cycles(); $endM = memory_get_usage(true); - if ($endM / $startM < 2) { + if ($endM - $startM === 0) { echo 'Memory leak test was successful'; } else { echo "Memory leak test was failed\n"; diff --git a/extension/php5/tests/objects_list-deleted-object.phpt b/extension/php5/tests/objects_list-deleted-object.phpt deleted file mode 100644 index fcfc572..0000000 --- a/extension/php5/tests/objects_list-deleted-object.phpt +++ /dev/null @@ -1,18 +0,0 @@ ---TEST-- -meminfo_objects_list with a deleted object ---FILE-- - ---EXPECT-- -Objects list: - - Class B, handle 2, refCount 1 -Total object buckets: 2. Current objects: 1. diff --git a/extension/php5/tests/objects_list-no_objects.phpt b/extension/php5/tests/objects_list-no_objects.phpt deleted file mode 100644 index 176aaea..0000000 --- a/extension/php5/tests/objects_list-no_objects.phpt +++ /dev/null @@ -1,9 +0,0 @@ ---TEST-- -meminfo_objects_list with no objects ---FILE-- - ---EXPECT-- -Objects list: -Total object buckets: 0. Current objects: 0. diff --git a/extension/php5/tests/objects_list.phpt b/extension/php5/tests/objects_list.phpt deleted file mode 100644 index 8692964..0000000 --- a/extension/php5/tests/objects_list.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -meminfo_objects_list with some objects ---FILE-- - ---EXPECT-- -Objects list: - - Class A, handle 1, refCount 1 - - Class B, handle 2, refCount 1 -Total object buckets: 2. Current objects: 2. diff --git a/extension/php5/tests/objects_list_refcount.phpt b/extension/php5/tests/objects_list_refcount.phpt deleted file mode 100644 index 110b53d..0000000 --- a/extension/php5/tests/objects_list_refcount.phpt +++ /dev/null @@ -1,27 +0,0 @@ ---TEST-- -meminfo_objects_list with several refs ---FILE-- - ---EXPECT-- -Objects list: - - Class A, handle 1, refCount 1 - - Class B, handle 2, refCount 1 - - Class C, handle 3, refCount 2 -Total object buckets: 3. Current objects: 3. diff --git a/extension/php5/tests/objects_summary-no-objects.phpt b/extension/php5/tests/objects_summary-no-objects.phpt deleted file mode 100644 index c11f10f..0000000 --- a/extension/php5/tests/objects_summary-no-objects.phpt +++ /dev/null @@ -1,10 +0,0 @@ ---TEST-- -meminfo_objects_summary with no objects ---FILE-- - ---EXPECT-- -Instances count by class: -rank #instances class ------------------------------------------------------------------ diff --git a/extension/php5/tests/objects_summary.phpt b/extension/php5/tests/objects_summary.phpt deleted file mode 100644 index 5eacf78..0000000 --- a/extension/php5/tests/objects_summary.phpt +++ /dev/null @@ -1,29 +0,0 @@ ---TEST-- -meminfo_objects_summary with no objects ---FILE-- -name = "A1"; - $a2 = new A(); - $a2->name = "A2"; - - $b1 = new B(); - $b2 = new B(); - $b3 = new B(); - $b4 = new B(); - - $c1 = new C(); - - meminfo_objects_summary(fopen('php://stdout', 'w')); -?> ---EXPECT-- -Instances count by class: -rank #instances class ------------------------------------------------------------------ -1 4 B -2 2 A -3 1 C diff --git a/extension/php7/meminfo.c b/extension/php7/meminfo.c index 07d8fcd..dfb9ede 100644 --- a/extension/php7/meminfo.c +++ b/extension/php7/meminfo.c @@ -3,7 +3,6 @@ #endif #include "php.h" -#include "php_ini.h" #include "php_meminfo.h" #include "ext/standard/info.h" @@ -16,13 +15,9 @@ #include "zend.h" #include "SAPI.h" #include "zend_API.h" -#include "zend_types.h" const zend_function_entry meminfo_functions[] = { - PHP_FE(meminfo_structs_size, NULL) - PHP_FE(meminfo_objects_list, NULL) - PHP_FE(meminfo_objects_summary, NULL) PHP_FE(meminfo_info_dump, NULL) PHP_FE_END }; @@ -40,144 +35,6 @@ zend_module_entry meminfo_module_entry = { STANDARD_MODULE_PROPERTIES }; -PHP_FUNCTION(meminfo_structs_size) -{ - zval *zval_stream; - php_stream *stream; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zval_stream) == FAILURE) { - return; - } - - php_stream_from_zval(stream, zval_stream); - - php_stream_printf(stream, "Simple Zend Type size on this platform\n"); - php_stream_printf(stream, " Zend Signed Integer (zend_long): %ld bytes.\n", sizeof(zend_long)); - php_stream_printf(stream, " Zend Unsigned Integer (zend_ulong): %ld bytes.\n", sizeof(zend_ulong)); - php_stream_printf(stream, " Zend Unsigned Char (zend_uchar): %ld bytes.\n", sizeof(zend_uchar)); - - php_stream_printf(stream, "Structs size on this platform:\n"); - php_stream_printf(stream, " Variable (zval): %ld bytes.\n", sizeof(zval)); - php_stream_printf(stream, " Class (zend_class_entry): %ld bytes.\n", sizeof(zend_class_entry)); - php_stream_printf(stream, " Object (zend_object): %ld bytes.\n", sizeof(zend_object)); -} - -PHP_FUNCTION(meminfo_objects_list) -{ - zval *zval_stream; - php_stream *stream; - uint32_t total_objects_buckets; - uint32_t current_objects = 0; - zend_string *class_name; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zval_stream) == FAILURE) { - return; - } - - php_stream_from_zval(stream, zval_stream); - - php_stream_printf(stream, "Objects list:\n"); - - zend_objects_store *objects = &EG(objects_store); - total_objects_buckets = objects->top - 1; - - if (objects->top > 1) { - zend_object **obj_ptr = objects->object_buckets + 1; - zend_object **end = objects->object_buckets + objects->top; - - do { - zend_object *obj = *obj_ptr; - - if (IS_OBJ_VALID(obj)) { - if (!(GC_FLAGS(obj) & IS_OBJ_DESTRUCTOR_CALLED)) { - php_stream_printf(stream TSRMLS_CC, " - Class %s, handle %d, refCount %d\n", ZSTR_VAL(obj->ce->name), obj->handle, GC_REFCOUNT(obj)); - current_objects++; - } - } - obj_ptr++; - } while (obj_ptr != end); - } - - php_stream_printf(stream, "Total object buckets: %d. Current objects: %d.\n", total_objects_buckets, current_objects); -} - -PHP_FUNCTION(meminfo_objects_summary) -{ - zval *zval_stream = NULL; - php_stream *stream = NULL; - - HashTable *classes = NULL; - - zval *p_instances_count; - zend_string *class_name; - - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zval_stream) == FAILURE) { - return; - } - php_stream_from_zval(stream, zval_stream); - - ALLOC_HASHTABLE(classes); - zend_hash_init(classes, 1000, NULL, NULL, 0); - - zend_objects_store *objects = &EG(objects_store); - - if (objects->top > 1) { - zend_object **obj_ptr = objects->object_buckets + 1; - zend_object **end = objects->object_buckets + objects->top; - - do { - zend_object *obj = *obj_ptr; - - if (IS_OBJ_VALID(obj)) { - if (!(GC_FLAGS(obj) & IS_OBJ_DESTRUCTOR_CALLED)) { - - class_name = obj->ce->name; - - p_instances_count = zend_hash_find(classes, class_name); - - if (p_instances_count != NULL) { - p_instances_count->value.lval++; - } else { - zval instances_count; - - p_instances_count = &instances_count; - p_instances_count->value.lval = 1; - zend_hash_add(classes, class_name, p_instances_count); - } - } - } - obj_ptr++; - } while (obj_ptr != end); - } - - zend_hash_sort(classes, meminfo_instances_count_compare, 0 TSRMLS_CC); - - - uint32_t rank = 1; - ulong index; - - zend_hash_internal_pointer_reset(classes); - - php_stream_printf(stream TSRMLS_CC, "Instances count by class:\n"); - - php_stream_printf(stream TSRMLS_CC, "%-12s %-12s %s\n", "rank", "#instances", "class"); - php_stream_printf(stream TSRMLS_CC, "-----------------------------------------------------------------\n"); - - while (zend_hash_has_more_elements(classes) == SUCCESS) { - p_instances_count = zend_hash_get_current_data(classes); - zend_hash_get_current_key(classes, &class_name, &index); - - php_stream_printf(stream TSRMLS_CC, "%-12d %-12d %s\n", rank, p_instances_count->value.lval, class_name->val); - - zend_hash_move_forward(classes); - rank++; - } - - zend_hash_destroy(classes); - zend_hash_clean(classes); - FREE_HASHTABLE(classes); -} - /** * Generate a JSON output of the list of items in memory (objects, arrays, string, etc...) * with their sizes and other information @@ -248,33 +105,6 @@ PHP_FUNCTION(meminfo_info_dump) FREE_HASHTABLE(visited_items); } -/** - Compare two hashtable buckets by extracting their - int value and return the comparision result. -*/ -static int meminfo_instances_count_compare(const void *a, const void *b TSRMLS_DC) -{ - const Bucket *bucket_a; - const Bucket *bucket_b; - - bucket_a = *((Bucket **) a); - bucket_b = *((Bucket **) b); - - zend_long instances_count_a; - zend_long instances_count_b; - - instances_count_a = (zend_long) &(bucket_a)->val.value.lval; - instances_count_b = (zend_long) &(bucket_b)->val.value.lval; - - if (instances_count_a > instances_count_b) { - return -1; - } else if (instances_count_a == instances_count_b) { - return 0; - } else { - return 1; - } -} - void meminfo_browse_zvals_from_symbol_table(php_stream *stream, char* frame_label, HashTable *p_symbol_table, HashTable * visited_items, int *first_element) { zval *zval_to_dump; @@ -308,10 +138,10 @@ int meminfo_visit_item(char * item_label, HashTable *visited_items) if (zend_hash_exists(visited_items, zstr_label)) { found = 1; - zend_string_release(zstr_label); } else { zend_hash_add_new(visited_items, zstr_label, &isset); } + zend_string_release(zstr_label); return found; } @@ -343,11 +173,23 @@ void meminfo_hash_dump(php_stream *stream, HashTable *ht, zend_bool is_object, H if (is_object) { const char *property_name, *class_name; - int mangled = zend_unmangle_property_name(key, &class_name, &property_name); + zend_string * escaped_property_name; + + zend_unmangle_property_name(key, &class_name, &property_name); + + escaped_property_name = meminfo_escape_for_json(property_name); + + php_stream_printf(stream TSRMLS_CC, " \"%s\":\"%p\"", ZSTR_VAL(escaped_property_name), zval); - php_stream_printf(stream TSRMLS_CC, " \"%s\":\"%p\"", meminfo_escape_for_json(property_name), zval); + zend_string_release(escaped_property_name); } else { - php_stream_printf(stream TSRMLS_CC, " \"%s\":\"%p\"", meminfo_escape_for_json(ZSTR_VAL(key)), zval); + zend_string * escaped_key; + + escaped_key = meminfo_escape_for_json(ZSTR_VAL(key)); + + php_stream_printf(stream TSRMLS_CC, " \"%s\":\"%p\"", ZSTR_VAL(escaped_key), zval); + + zend_string_release(escaped_key); } break; @@ -387,30 +229,46 @@ void meminfo_zval_dump(php_stream * stream, char * frame_label, zend_string * sy } php_stream_printf(stream TSRMLS_CC, " \"%s\" : {\n", zval_id); - php_stream_printf(stream TSRMLS_CC, " \"type\" : \"%s\",\n", zend_get_type_by_const(Z_TYPE_P(zv))); - php_stream_printf(stream TSRMLS_CC, " \"size\" : \"%ld\",\n", meminfo_get_element_size(zv)); if (frame_label) { + zend_string * escaped_frame_label; + if (symbol_name) { - php_stream_printf(stream TSRMLS_CC, " \"symbol_name\" : \"%s\",\n", meminfo_escape_for_json(ZSTR_VAL(symbol_name))); + zend_string * escaped_symbol_name; + + escaped_symbol_name = meminfo_escape_for_json(ZSTR_VAL(symbol_name)); + + php_stream_printf(stream TSRMLS_CC, " \"symbol_name\" : \"%s\",\n", ZSTR_VAL(escaped_symbol_name)); + + zend_string_release(escaped_symbol_name); } + + escaped_frame_label = meminfo_escape_for_json(frame_label); + php_stream_printf(stream TSRMLS_CC, " \"is_root\" : true,\n"); - php_stream_printf(stream TSRMLS_CC, " \"frame\" : \"%s\"\n", meminfo_escape_for_json(frame_label)); + php_stream_printf(stream TSRMLS_CC, " \"frame\" : \"%s\"\n", ZSTR_VAL(escaped_frame_label)); + + zend_string_release(escaped_frame_label); } else { php_stream_printf(stream TSRMLS_CC, " \"is_root\" : false\n"); } if (Z_TYPE_P(zv) == IS_OBJECT) { HashTable *properties; + int is_temp; + zend_string * escaped_class_name; properties = NULL; - int is_temp; + escaped_class_name = meminfo_escape_for_json(ZSTR_VAL(zv->value.obj->ce->name)); php_stream_printf(stream TSRMLS_CC, ",\n"); - php_stream_printf(stream TSRMLS_CC, " \"class\" : \"%s\",\n", meminfo_escape_for_json(ZSTR_VAL(zv->value.obj->ce->name))); + php_stream_printf(stream TSRMLS_CC, " \"class\" : \"%s\",\n", ZSTR_VAL(escaped_class_name)); + + zend_string_release(escaped_class_name); + php_stream_printf(stream TSRMLS_CC, " \"object_handle\" : \"%d\",\n", zv->value.obj->handle); properties = Z_OBJDEBUG_P(zv, is_temp); @@ -449,10 +307,12 @@ zend_ulong meminfo_get_element_size(zval *zv) size += zv->value.str->len; break; + // TODO: add size of the indexes case IS_ARRAY: size += sizeof(HashTable); break; + // TODO: add size of the properties table, but without property content case IS_OBJECT: size += sizeof(zend_object); break; @@ -547,10 +407,11 @@ void meminfo_build_frame_label(char* frame_label, int frame_label_len, zend_exec /** * Escape the \ and " characters for JSON encoding */ -char * meminfo_escape_for_json(const char *s) +zend_string * meminfo_escape_for_json(const char *s) { int new_str_len; zend_string *s1, *s2; + s1 = php_str_to_str((char *) s, strlen(s), "\\", 1, "\\\\", 2); s2 = php_str_to_str(ZSTR_VAL(s1), ZSTR_LEN(s1), "\"", 1, "\\\"", 2); @@ -558,7 +419,7 @@ char * meminfo_escape_for_json(const char *s) zend_string_release(s1); } - return ZSTR_VAL(s2); + return s2; } /** diff --git a/extension/php7/php_meminfo.h b/extension/php7/php_meminfo.h index 8cff65b..c4b50d8 100644 --- a/extension/php7/php_meminfo.h +++ b/extension/php7/php_meminfo.h @@ -4,22 +4,13 @@ #define phpext_meminfo_ptr &meminfo_module_entry #define MEMINFO_NAME "PHP Meminfo" -#define MEMINFO_VERSION "0.3" +#define MEMINFO_VERSION "1.0" #define MEMINFO_AUTHOR "Benoit Jacquemont" #define MEMINFO_COPYRIGHT "Copyright (c) 2010-2017 by Benoit Jacquemont" #define MEMINFO_COPYRIGHT_SHORT "Copyright (c) 2011-2017" -PHP_FUNCTION(meminfo_structs_size); - -PHP_FUNCTION(meminfo_objects_list); -PHP_FUNCTION(meminfo_objects_summary); - -static int meminfo_instances_count_compare(const void *a, const void *b TSRMLS_DC); - -PHP_FUNCTION(meminfo_symbol_table); PHP_FUNCTION(meminfo_info_dump); - zend_ulong meminfo_get_element_size(zval* z); void meminfo_zval_dump(php_stream * stream, char * frame_label, zend_string * symbol_name, zval * zv, HashTable *visited_items, int *first_element); @@ -30,7 +21,7 @@ int meminfo_visit_item(char *item_label, HashTable *visited_items); void meminfo_build_frame_label(char * frame_label, int frame_label_len, zend_execute_data* frame); -char * meminfo_escape_for_json(const char *s); +zend_string * meminfo_escape_for_json(const char *s); char * meminfo_info_dump_header(char * header, int header_len); extern zend_module_entry meminfo_entry; diff --git a/extension/php7/tests/fixtures/books.php b/extension/php7/tests/fixtures/books.php new file mode 100644 index 0000000..9cc8959 --- /dev/null +++ b/extension/php7/tests/fixtures/books.php @@ -0,0 +1,40 @@ +title = "My lists"; +$books->chapter = [ + "title" => "My books", + "para" => [ + "informaltable" => [ + "tgroup" => [ + "thead" => [ + "Title", + "Author", + "Language", + "ISBN" + ], + "tbody" => [ + [ + "The Grapes of Wrath", + "John Steinbeck", + "en", + "0140186409" + ], + [ + "The Pearl", + "John Steinbeck", + "en", + "014017737X" + ], + [ + "Samarcande", + "Amine Maalouf", + "fr", + "2253051209" + ] + ] + ] + ] + ] +]; diff --git a/extension/php7/tests/info_dump.phpt b/extension/php7/tests/info_dump.phpt new file mode 100644 index 0000000..e3dbdbe --- /dev/null +++ b/extension/php7/tests/info_dump.phpt @@ -0,0 +1,115 @@ +--TEST-- +meminfo_objects_list with some objects +--SKIPIF-- + +--FILE-- + true, + 'itemInteger' => 23, + 'itemDoubles' => 1.2e3, + 'itemNull' => null, + 'itemString' => 'hello', + 'itemObject' => $books, + 'itemArray' => (array) $books, + 'itemResource' => $rFilePointer, + ]; + + meminfo_info_dump($rFilePointer); + + rewind($rFilePointer); + $jsonO = json_decode(stream_get_contents($rFilePointer), true); + fclose($rFilePointer); + if (is_array($jsonO)) { + $items = $jsonO['items']; + printf( + "meminfo_info_dump JSON decode ok\nz_val_count:%d\n", + count($items) + ); + uasort($items, function ($itemA, $itemB) { + return $itemA['type'] > $itemB['type'] + || ($itemA['type'] == $itemB['type'] && $itemA['size'] > $itemB['size']); + }); + + foreach($items as $item){ + printf( + "item type:%s size:%d\n", + $item['type'], + $item['size'] + ); + } + } else { + echo 'meminfo_info_dump JSON decode fail'; + } +?> +--EXPECT-- +meminfo_info_dump JSON decode ok +z_val_count:62 +item type:array size:96 +item type:array size:96 +item type:array size:96 +item type:array size:96 +item type:array size:96 +item type:array size:96 +item type:array size:96 +item type:array size:96 +item type:boolean size:24 +item type:boolean size:24 +item type:boolean size:24 +item type:boolean size:24 +item type:boolean size:24 +item type:boolean size:24 +item type:boolean size:24 +item type:boolean size:24 +item type:boolean size:24 +item type:boolean size:24 +item type:double size:24 +item type:double size:24 +item type:integer size:24 +item type:integer size:24 +item type:integer size:24 +item type:integer size:24 +item type:null size:24 +item type:null size:24 +item type:null size:24 +item type:null size:24 +item type:null size:24 +item type:null size:24 +item type:null size:24 +item type:null size:24 +item type:null size:24 +item type:object size:56 +item type:resource size:24 +item type:string size:24 +item type:string size:24 +item type:string size:25 +item type:string size:25 +item type:string size:25 +item type:string size:25 +item type:string size:27 +item type:string size:27 +item type:string size:27 +item type:string size:29 +item type:string size:29 +item type:string size:29 +item type:string size:29 +item type:string size:33 +item type:string size:37 +item type:string size:46 +item type:string size:64 +item type:string size:64 +item type:string size:64 +item type:string size:84 +item type:string size:84 +item type:string size:84 +item type:string size:84 +item type:string size:84 +item type:string size:87 +item type:string size:87 +item type:string size:1054 diff --git a/extension/php7/tests/info_dump_memory-leak.phpt b/extension/php7/tests/info_dump_memory-leak.phpt new file mode 100644 index 0000000..345a9fc --- /dev/null +++ b/extension/php7/tests/info_dump_memory-leak.phpt @@ -0,0 +1,36 @@ +--TEST-- +meminfo_objects_list with some objects +--FILE-- + true, + 'itemInteger' => 23, + 'itemDoubles' => 1.2e3, + 'itemNull' => null, + 'itemString' => 'hello', + 'itemObject' => $books, + 'itemArray' => (array) $books, + 'itemResource' => $rFilePointer, + ]; + $attemptCount = 1000; + gc_collect_cycles(); + $startM = memory_get_usage(true); + while ($attemptCount-- > 0) { + meminfo_info_dump($rFilePointer); + } + fclose($rFilePointer); + gc_collect_cycles(); + $endM = memory_get_usage(true); + if ($endM - $startM === 0) { + echo 'Memory leak test was successful'; + } else { + echo "Memory leak test was failed\n"; + printf("Memory leak:%s bytes",$endM - $startM); + } +?> +--EXPECT-- +Memory leak test was successful diff --git a/extension/php7/tests/objects_list-deleted-object.phpt b/extension/php7/tests/objects_list-deleted-object.phpt deleted file mode 100644 index fcfc572..0000000 --- a/extension/php7/tests/objects_list-deleted-object.phpt +++ /dev/null @@ -1,18 +0,0 @@ ---TEST-- -meminfo_objects_list with a deleted object ---FILE-- - ---EXPECT-- -Objects list: - - Class B, handle 2, refCount 1 -Total object buckets: 2. Current objects: 1. diff --git a/extension/php7/tests/objects_list-no_objects.phpt b/extension/php7/tests/objects_list-no_objects.phpt deleted file mode 100644 index 176aaea..0000000 --- a/extension/php7/tests/objects_list-no_objects.phpt +++ /dev/null @@ -1,9 +0,0 @@ ---TEST-- -meminfo_objects_list with no objects ---FILE-- - ---EXPECT-- -Objects list: -Total object buckets: 0. Current objects: 0. diff --git a/extension/php7/tests/objects_list.phpt b/extension/php7/tests/objects_list.phpt deleted file mode 100644 index 8692964..0000000 --- a/extension/php7/tests/objects_list.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -meminfo_objects_list with some objects ---FILE-- - ---EXPECT-- -Objects list: - - Class A, handle 1, refCount 1 - - Class B, handle 2, refCount 1 -Total object buckets: 2. Current objects: 2. diff --git a/extension/php7/tests/objects_list_refcount.phpt b/extension/php7/tests/objects_list_refcount.phpt deleted file mode 100644 index 2a5d3a9..0000000 --- a/extension/php7/tests/objects_list_refcount.phpt +++ /dev/null @@ -1,25 +0,0 @@ ---TEST-- -meminfo_objects_list with several refs ---FILE-- - ---EXPECT-- -Objects list: - - Class A, handle 1, refCount 1 - - Class B, handle 2, refCount 2 - - Class C, handle 3, refCount 3 -Total object buckets: 3. Current objects: 3. diff --git a/extension/php7/tests/objects_summary-no-objects.phpt b/extension/php7/tests/objects_summary-no-objects.phpt deleted file mode 100644 index c11f10f..0000000 --- a/extension/php7/tests/objects_summary-no-objects.phpt +++ /dev/null @@ -1,10 +0,0 @@ ---TEST-- -meminfo_objects_summary with no objects ---FILE-- - ---EXPECT-- -Instances count by class: -rank #instances class ------------------------------------------------------------------ diff --git a/extension/php7/tests/objects_summary.phpt b/extension/php7/tests/objects_summary.phpt deleted file mode 100644 index 5eacf78..0000000 --- a/extension/php7/tests/objects_summary.phpt +++ /dev/null @@ -1,29 +0,0 @@ ---TEST-- -meminfo_objects_summary with no objects ---FILE-- -name = "A1"; - $a2 = new A(); - $a2->name = "A2"; - - $b1 = new B(); - $b2 = new B(); - $b3 = new B(); - $b4 = new B(); - - $c1 = new C(); - - meminfo_objects_summary(fopen('php://stdout', 'w')); -?> ---EXPECT-- -Instances count by class: -rank #instances class ------------------------------------------------------------------ -1 4 B -2 2 A -3 1 C