Skip to content

Commit 16b6513

Browse files
committed
Merge pull request #11 from BitOne/objects_summary
Objects summary
2 parents 6547398 + f9ff0f1 commit 16b6513

File tree

4 files changed

+205
-24
lines changed

4 files changed

+205
-24
lines changed

README.md

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ For now, PHP Meminfo provides a list of live objects in memory with their class
1111

1212
Compatibility
1313
-------------
14-
1514
Compiled and tested on:
1615

1716
- PHP 5.4.4 (Debian 7)
@@ -43,31 +42,69 @@ Restart your webserver.
4342

4443
Check the PHP Info output and look for the MemInfo data. If you can find it, installation has been successful.
4544

46-
To see the PHP Info output, just create a page calling the phpinfo(); function, a load it from your browser, or call php -i from command line.
45+
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.
4746

4847
Usage
4948
-----
50-
All meminfo functions take a stream handle as a parameter. It allows you to specify a file (if needed with a variable name identified to your context), as well as uses standard output with the `php://stdout` stream.
49+
All meminfo functions take a stream handle as a parameter. It allows you to specify a file (if needed with a variable name identifying your context), as well as to use standard output with the `php://stdout` stream.
50+
51+
## Object instances count per class
52+
Display the number of instances per class, ordered descending. Very useful to identify the content of a memory leak.
53+
54+
```php
55+
meminfo_objects_summary(fopen('php://stdout','w'));
56+
```
57+
58+
The result will provide something similar to the following example (generated at the end of the Symfony2 console launch)
59+
60+
```
61+
Instances count by class:
62+
num #instances class
63+
-----------------------------------------------------------------
64+
1 181 Symfony\Component\Console\Input\InputOption
65+
2 88 Symfony\Component\Console\Input\InputDefinition
66+
3 77 ReflectionObject
67+
4 46 Symfony\Component\Console\Input\InputArgument
68+
5 2 Symfony\Bridge\Monolog\Logger
69+
6 1 Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher
70+
7 1 Doctrine\Bundle\MigrationsBundle\Command\MigrationsDiffDoctrineCommand
71+
...
72+
```
73+
74+
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.
75+
76+
### Examples
77+
The `examples/` directory at the root of the repository contains more detailed examples.
78+
```bash
79+
$ php examples/objects_summary.php
80+
```
5181

5282
## Information on structs size
5383
Display size in byte of main data structs size in PHP. Will mainly differ between 32bits et 64bits environments.
5484

85+
```php
86+
5587
meminfo_structs_size(fopen('php://stdout','w'));
88+
```
5689

5790
It can be useful to understand difference in memory usage between two platforms.
5891

5992
Example Output on 64bits environment:
6093

94+
```
6195
Structs size on this platform:
6296
Class (zend_class_entry): 568 bytes.
6397
Object (zend_object): 32 bytes.
6498
Variable (zval): 24 bytes.
6599
Variable value (zvalue_value): 16 bytes.
100+
```
66101

67102
##List of currently active objects
68103
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.
69104

105+
```php
70106
meminfo_objects_list(fopen('php://stdout','w'));
107+
```
71108

72109
For example:
73110

@@ -78,24 +115,13 @@ For example:
78115
- Class MyClassC, handle 7, refcount 1
79116
Total object buckets: 7. Current objects: 4.
80117

81-
Note: It's a good idea to call the `gc_collect_cycles()` function before executing `meminfo_objects_list()`, 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.
118+
Note: The same remark about `gc_collect_cycle()` before `meminfo_objects_summary()` applies as well for this function.
82119

83120
### Examples
84121
The `examples/` directory at the root of the repository contains more detailed examples.
85122

86123
php examples/objects_list.php
87124

88-
### Analysis
89-
Some simple shell one-liners can help you aggregate data from the dumped objects list to get a better grasp at what's happening:
90-
91-
- Ordered list of number of objects per class
92-
93-
cat objects_list.meminfo | grep - | cut -d "," -f 1 | sort | uniq -c | sort -n
94-
95-
- Top 5 of the most present class by objects number
96-
97-
cat objects_list.meminfo | grep - | cut -d "," -f 1 | sort | uniq -c | sort -nr | head -n 5
98-
99125
Usage in production
100126
-------------------
101127
PHP Meminfo can be used in production, as it does not have any impact on performances outside of the call to the `meminfo` functions.

examples/objects_summary.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
class MyClassA {
4+
}
5+
6+
class MyClassB {
7+
}
8+
9+
class MyClassC {
10+
}
11+
12+
$objectA = new MyClassA();
13+
$objectB = new MyClassB();
14+
15+
echo "* Objects summary after instanciating objects A and B\n";
16+
meminfo_objects_summary(fopen('php://stdout', 'w'));
17+
18+
$objectsC = array();
19+
20+
for ($i = 0; $i < 5; $i++) {
21+
$objectsC[] = new MyClassC();
22+
}
23+
24+
echo "\n* Objects summary after instanciating 10 objects C\n";
25+
meminfo_objects_summary(fopen('php://stdout', 'w'));
26+
27+
unset($objectA);
28+
unset($objectsC[0]);
29+
unset($objectsC[1]);
30+
31+
echo "\n* Objects summary after unset on some objects\n";
32+
meminfo_objects_summary(fopen('php://stdout', 'w'));
33+
34+
$myClosure = function() {
35+
$a = 1;
36+
};
37+
38+
echo "\n* Objects summary after instantiating an anonymous function\n";
39+
meminfo_objects_summary(fopen('php://stdout', 'w'));
40+
41+
$objectBClone = clone $objectB;
42+
43+
echo "\n* Objects summary after cloning objectB\n";
44+
meminfo_objects_summary(fopen('php://stdout', 'w'));
45+
46+
function myFunction() {
47+
$myDate = new \DateTime();
48+
49+
echo "\n* Objects summary in function call with inside DateTime object\n";
50+
meminfo_objects_summary(fopen('php://stdout', 'w'));
51+
}
52+
53+
myFunction();
54+
55+
echo "\n* Objects summary after function call\n";
56+
meminfo_objects_summary(fopen('php://stdout', 'w'));
57+

meminfo.c

Lines changed: 101 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
const zend_function_entry meminfo_functions[] = {
1818
PHP_FE(meminfo_structs_size, NULL)
1919
PHP_FE(meminfo_objects_list, NULL)
20+
PHP_FE(meminfo_objects_summary, NULL)
2021
PHP_FE(meminfo_gc_roots_list, NULL)
2122
PHP_FE(meminfo_symbol_table, NULL)
2223
{NULL, NULL, NULL}
@@ -61,11 +62,11 @@ char* get_type_label(zval* z) {
6162
case IS_DOUBLE:
6263
return "double";
6364
break;
64-
65+
6566
case IS_STRING:
6667
return "string";
6768
break;
68-
69+
6970
case IS_ARRAY:
7071
return "array";
7172
break;
@@ -161,7 +162,104 @@ PHP_FUNCTION(meminfo_objects_list)
161162
}
162163

163164
php_stream_printf(stream, "Total object buckets: %d. Current objects: %d.\n", total_objects_buckets, current_objects);
165+
}
166+
167+
PHP_FUNCTION(meminfo_objects_summary)
168+
{
169+
zval *zval_stream = NULL;
170+
php_stream *stream = NULL;
171+
HashTable *classes = NULL;
172+
173+
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zval_stream) == FAILURE) {
174+
return;
175+
}
176+
177+
ALLOC_HASHTABLE(classes);
178+
179+
zend_hash_init(classes, 1000, NULL, NULL, 0);
180+
181+
zend_objects_store *objects = &EG(objects_store);
182+
zend_uint i;
183+
zend_object *object;
184+
185+
for (i = 1; i < objects->top ; i++) {
186+
if (objects->object_buckets[i].valid && !objects->object_buckets[i].destructor_called) {
187+
const char *class_name;
188+
zval **zv_dest;
189+
190+
class_name = get_classname(i);
191+
192+
if (zend_hash_find(classes, class_name, strlen(class_name)+1, (void **) &zv_dest) == SUCCESS) {
193+
Z_LVAL_PP(zv_dest) = Z_LVAL_PP(zv_dest) ++;
194+
} else {
195+
zval *zv_instances_count;
196+
MAKE_STD_ZVAL(zv_instances_count);
197+
ZVAL_LONG(zv_instances_count, 1);
198+
199+
zend_hash_update(classes, class_name, strlen(class_name)+1, &zv_instances_count, sizeof(zval *), NULL);
200+
}
201+
}
202+
}
203+
204+
zend_hash_sort(classes, zend_qsort, instances_count_compare, 0 TSRMLS_CC);
164205

206+
php_stream_from_zval(stream, &zval_stream);
207+
php_stream_printf(stream, "Instances count by class:\n");
208+
209+
php_stream_printf(stream, "%-12s %-12s %s\n", "num", "#instances", "class");
210+
php_stream_printf(stream, "-----------------------------------------------------------------\n");
211+
212+
zend_uint num = 1;
213+
214+
HashPosition position;
215+
zval **entry = NULL;
216+
217+
for (zend_hash_internal_pointer_reset_ex(classes, &position);
218+
zend_hash_get_current_data_ex(classes, (void **) &entry, &position) == SUCCESS;
219+
zend_hash_move_forward_ex(classes, &position)) {
220+
221+
char *class_name = NULL;
222+
uint class_name_len;
223+
ulong index;
224+
225+
zend_hash_get_current_key_ex(classes, &class_name, &class_name_len, &index, 0, &position);
226+
php_stream_printf(stream, "%-12d %-12d %s\n", num, Z_LVAL_PP(entry), class_name);
227+
228+
num++;
229+
}
230+
231+
zend_hash_destroy(classes);
232+
FREE_HASHTABLE(classes);
233+
}
234+
235+
static int instances_count_compare(const void *a, const void *b TSRMLS_DC)
236+
{
237+
const Bucket *first_bucket;
238+
const Bucket *second_bucket;
239+
240+
first_bucket = *((const Bucket **) a);
241+
second_bucket = *((const Bucket **) b);
242+
243+
zval *zv_first;
244+
zval *zv_second;
245+
246+
zv_first = (zval *) first_bucket->pDataPtr;
247+
zv_second = (zval *) second_bucket->pDataPtr;
248+
249+
250+
zend_uint first;
251+
zend_uint second;
252+
253+
first = Z_LVAL_P(zv_first);
254+
second = Z_LVAL_P(zv_second);
255+
256+
if (first > second) {
257+
return -1;
258+
} else if (first == second) {
259+
return 0;
260+
} else {
261+
return 1;
262+
}
165263
}
166264

167265
PHP_FUNCTION(meminfo_gc_roots_list)
@@ -181,7 +279,7 @@ PHP_FUNCTION(meminfo_gc_roots_list)
181279

182280
while (current != &GC_G(roots)) {
183281
pz = current->u.pz;
184-
php_stream_printf( stream, " zval pointer: %p ", (void *) pz);
282+
php_stream_printf( stream, " zval pointer: %p ", (void *) pz);
185283
if (current->handle) {
186284
php_stream_printf(
187285
stream,
@@ -214,7 +312,4 @@ PHP_FUNCTION(meminfo_symbol_table)
214312

215313
php_stream_printf(stream, "Nb elements in Symbol Table: %d\n",main_symbol_table.nNumOfElements);
216314

217-
218315
}
219-
220-

php_meminfo.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@
22
#define PHP_MEMINFO_H 1
33

44
#define MEMINFO_NAME "PHP Meminfo"
5-
#define MEMINFO_VERSION "0.1-dev"
5+
#define MEMINFO_VERSION "0.2"
66
#define MEMINFO_AUTHOR "Benoit Jacquemont"
7-
#define MEMINFO_COPYRIGHT "Copyright (c) 2010-2014 by Benoit Jacquemont"
8-
#define MEMINFO_COPYRIGHT_SHORT "Copyright (c) 2011-2014"
7+
#define MEMINFO_COPYRIGHT "Copyright (c) 2010-2015 by Benoit Jacquemont"
8+
#define MEMINFO_COPYRIGHT_SHORT "Copyright (c) 2011-2015"
99

1010
PHP_FUNCTION(meminfo_structs_size);
1111
PHP_FUNCTION(meminfo_objects_list);
12+
PHP_FUNCTION(meminfo_objects_summary);
1213
PHP_FUNCTION(meminfo_gc_roots_list);
1314
PHP_FUNCTION(meminfo_symbol_table);
1415

16+
static int instances_count_compare(const void *a, const void *b TSRMLS_DC);
17+
1518
extern zend_module_entry meminfo_entry;
1619
#define phpext_meminfo_ptr &meminfo_module_entry
1720

0 commit comments

Comments
 (0)