Passed
Pull Request — 0.9.x (#294)
by Shinji
01:35
created

MemoryCommand::execute()   F

Complexity

Conditions 31
Paths 6

Size

Total Lines 277
Code Lines 203

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 31
eloc 203
nc 6
nop 2
dl 0
loc 277
rs 3.3333
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Reli\Command\Inspector;
4
5
use FFI\CData;
6
use Reli\Inspector\Settings\TargetPhpSettings\TargetPhpSettingsFromConsoleInput;
7
use Reli\Inspector\Settings\TargetProcessSettings\TargetProcessSettingsFromConsoleInput;
8
use Reli\Inspector\TargetProcess\TargetProcessResolver;
9
use Reli\Lib\FFI\CastedTypeProvider;
10
use Reli\Lib\PhpInternals\Types\Zend\ZendArray;
11
use Reli\Lib\PhpInternals\Types\Zend\ZendCastedTypeProvider;
12
use Reli\Lib\PhpInternals\Types\Zend\ZendClassConstant;
13
use Reli\Lib\PhpInternals\Types\Zend\ZendClassEntry;
14
use Reli\Lib\PhpInternals\Types\Zend\ZendCompilerGlobals;
15
use Reli\Lib\PhpInternals\Types\Zend\ZendExecutorGlobals;
16
use Reli\Lib\PhpInternals\Types\Zend\Zval;
17
use Reli\Lib\PhpInternals\ZendTypeReaderCreator;
18
use Reli\Lib\PhpProcessReader\PhpGlobalsFinder;
19
use Reli\Lib\PhpProcessReader\PhpVersionDetector;
20
use Reli\Lib\PhpProcessReader\PhpZendMemoryManagerChunkFinder;
21
use Reli\Lib\Process\MemoryReader\MemoryReaderInterface;
22
use Reli\Lib\Process\Pointer\Dereferencer;
23
use Reli\Lib\Process\Pointer\Pointer;
24
use Reli\Lib\Process\Pointer\RemoteProcessDereferencer;
25
use Symfony\Component\Console\Command\Command;
26
use Symfony\Component\Console\Input\InputInterface;
27
use Symfony\Component\Console\Output\OutputInterface;
28
29
final class MemoryCommand extends Command
30
{
31
    public function __construct(
32
        private PhpGlobalsFinder $php_globals_finder,
33
        private TargetPhpSettingsFromConsoleInput $target_php_settings_from_console_input,
34
        private TargetProcessSettingsFromConsoleInput $target_process_settings_from_console_input,
35
        private TargetProcessResolver $target_process_resolver,
36
        private PhpZendMemoryManagerChunkFinder $chunk_finder,
37
        private MemoryReaderInterface $memory_reader,
38
        private PhpVersionDetector $php_version_detector,
39
        private ZendTypeReaderCreator $zend_type_reader_creator,
40
    ) {
41
        parent::__construct();
42
    }
43
44
    public function configure(): void
45
    {
46
        $this->setName('inspector:memory')
47
            ->setDescription('get memory usage from an outer process or thread')
48
        ;
49
        $this->target_process_settings_from_console_input->setOptions($this);
50
        $this->target_php_settings_from_console_input->setOptions($this);
51
    }
52
53
    public function execute(InputInterface $input, OutputInterface $output): int
54
    {
55
        $target_php_settings = $this->target_php_settings_from_console_input->createSettings($input);
56
        $target_process_settings = $this->target_process_settings_from_console_input->createSettings($input);
57
58
        $process_specifier = $this->target_process_resolver->resolve($target_process_settings);
59
60
        $target_php_settings = $this->php_version_detector->decidePhpVersion(
61
            $process_specifier,
62
            $target_php_settings
63
        );
64
65
        $result = $this->memory_reader->read(
66
            $process_specifier->pid,
67
            $main_chunk_address = $this->getChunkAddress(
68
                $process_specifier->pid,
69
                $target_php_settings->php_version,
70
                $this->memory_reader
71
            ),
72
            0x200000
73
        );
74
        $zend_type_reader = $this->zend_type_reader_creator->create($target_php_settings->php_version);
75
        $zend_mm_main_chunk = $zend_type_reader->readAs('zend_mm_chunk', $result);
76
        $size = $zend_mm_main_chunk->casted->heap_slot->size;
77
        $real_size = $zend_mm_main_chunk->casted->heap_slot->real_size;
78
79
        $eg_address = $this->php_globals_finder->findExecutorGlobals($process_specifier, $target_php_settings);
80
        $eg_pointer = new Pointer(
81
            ZendExecutorGlobals::class,
82
            $eg_address,
83
            $zend_type_reader->sizeOf('zend_executor_globals')
84
        );
85
        $cg_address = $this->php_globals_finder->findCompilerGlobals($process_specifier, $target_php_settings);
86
        $cg_pointer = new Pointer(
87
            ZendCompilerGlobals::class,
88
            $cg_address,
89
            $zend_type_reader->sizeOf('zend_compiler_globals')
90
        );
91
        $remote_process_dereferencer = new RemoteProcessDereferencer(
92
            $this->memory_reader,
93
            $process_specifier,
94
            $casted_type_provider = new ZendCastedTypeProvider(
95
                $zend_type_reader
96
            ),
97
        );
98
        /** @var ZendExecutorGlobals $eg */
99
        $eg = $remote_process_dereferencer->deref($eg_pointer);
100
        $function_table = $remote_process_dereferencer->deref($eg->function_table);
101
        $class_table = $remote_process_dereferencer->deref($eg->class_table);
102
        $zend_constants = $remote_process_dereferencer->deref($eg->zend_constants);
103
104
        $vm_stack_curent = $remote_process_dereferencer->deref($eg->vm_stack);
105
        $vm_stack_size = 0;
106
        foreach ($vm_stack_curent->iterateStackChain($remote_process_dereferencer) as $vm_stack) {
107
            $vm_stack_size += $vm_stack->getSize();
108
        }
109
        /** @var ZendCompilerGlobals $cg */
110
        $cg = $remote_process_dereferencer->deref($cg_pointer);
111
112
        $dump_symbol_table = function (ZendArray $array) use (&$dump_symbol_table, $remote_process_dereferencer, &$dump_zval) {
113
            $result = [];
114
            $pos = 0;
115
            foreach ($array->getItemIterator($remote_process_dereferencer) as $key => $zval) {
116
                $key_name = $key ?? $pos++;
117
                $result[$key_name] = $dump_zval($zval);
118
            }
119
            return $result;
120
        };
121
        $dump_function_table = function (ZendArray $array) use ($dump_symbol_table, $remote_process_dereferencer) {
122
            $result = [];
123
            $pos = 0;
124
            foreach ($array->getItemIterator($remote_process_dereferencer) as $key => $zval) {
125
                $function_name = $key ?? $pos++;
126
                $func = $remote_process_dereferencer->deref($zval->value->func);
127
                if ($func->type === 2) {
128
                    $static_variables = [];
129
                    if (!is_null($func->op_array->static_variables)) {
130
                        $static_variables = $dump_symbol_table(
131
                            $remote_process_dereferencer->deref($func->op_array->static_variables)
132
                        );
133
                    }
134
                    $result[$function_name] = [
135
                        'op_array_size' => $func->op_array->last * 48,
136
                        'static_variables' => $static_variables,
137
                        'overhead' => 128,
138
                    ];
139
                }
140
            }
141
            return $result;
142
        };
143
        $dump_class_constants_table = function (ZendClassEntry $ce) use ($remote_process_dereferencer, $dump_symbol_table, $zend_type_reader, &$dump_zval) {
144
            $result = [];
145
            $pos = 0;
146
            foreach ($ce->constants_table->getItemIterator($remote_process_dereferencer) as $key => $zval_ptr) {
147
                $constant_name = $key ?? $pos++;
148
                $class_constant_ptr = $zval_ptr->value->getAsPointer(
149
                    ZendClassConstant::class,
150
                    $zend_type_reader->sizeOf(ZendClassConstant::getCTypeName()),
151
                );
152
                $class_constant = $remote_process_dereferencer->deref(
153
                    $class_constant_ptr
154
                );
155
156
                $zval = $class_constant->value;
157
                $result[$constant_name] = $dump_zval($zval);
158
            }
159
            return $result;
160
        };
161
        $dump_class_table = function (ZendArray $array) use ($dump_symbol_table, $dump_function_table, $dump_class_constants_table, $remote_process_dereferencer, $zend_type_reader, &$dump_zval) {
162
            $result = [];
163
            $pos = 0;
164
            foreach ($array->getItemIterator($remote_process_dereferencer) as $key => $zval) {
165
                $class_name = $key ?? $pos++;
166
                /** @var ZendClassEntry $class_entry */
167
                $class_entry = $remote_process_dereferencer->deref($zval->value->ce);
168
                if ($class_entry->isInternal()) {
169
                    continue;
170
                }
171
                $analyzed_static_member_table = [];
172
                $static_property_iterator = $class_entry->getStaticPropertyIterator(
173
                    $remote_process_dereferencer,
174
                    $zend_type_reader
175
                );
176
                foreach ($static_property_iterator as $name => $value) {
177
                    if (!is_null($value)) {
178
                        $analyzed_static_member_table[$name] = $dump_zval($value);
179
                    } else {
180
                        $analyzed_static_member_table[$name] = 16;
181
                    }
182
                }
183
                $result[$class_name] = [
184
                    'static_variables' => $analyzed_static_member_table,
185
                    'methods' => $dump_function_table($class_entry->function_table),
186
                    'constants' => $dump_class_constants_table($class_entry),
187
                    'overhead' => 128,
188
                ];
189
            }
190
            return $result;
191
        };
192
        $recursion_memo = [];
193
        $dump_zval = function (Zval $zval) use (&$dump_zval, &$recursion_memo, $remote_process_dereferencer, &$dump_symbol_table, $zend_type_reader): int|array {
194
            if ($zval->isArray()) {
195
                $pos = 0;
196
                $result = [];
197
                if (isset($recursion_memo[$zval->value->arr->address])) {
198
                    return [$zval->value->arr->address => 16];
199
                }
200
                $recursion_memo[$zval->value->arr->address] = true;
201
                $array = $remote_process_dereferencer->deref($zval->value->arr);
202
                foreach ($array->getItemIterator($remote_process_dereferencer) as $key => $zval) {
203
                    $key_name = $key ?? $pos++;
204
                    $result[$key_name] = $dump_zval($zval);
205
                }
206
207
                return [
208
                    'address' => $zval->value->arr->address,
209
                    'overhead' => 64,
210
                    'items' => $result,
211
                ];
212
            } elseif ($zval->isObject()) {
213
                if (isset($recursion_memo[$zval->value->obj->address])) {
214
                    return [$zval->value->obj->address => 16];
215
                }
216
                $recursion_memo[$zval->value->obj->address] = true;
217
                $obj = $remote_process_dereferencer->deref($zval->value->obj);
218
                $dynamic_properties = [];
219
                if (!is_null($obj->properties) and !is_null($obj->ce) and !$obj->isEnum($remote_process_dereferencer)) {
220
                    $dynamic_properties = $dump_symbol_table(
221
                        $remote_process_dereferencer->deref($obj->properties),
222
                    );
223
                }
224
                $properties = [];
225
                $properties_iterator = $obj->getPropertiesIterator(
226
                    $remote_process_dereferencer,
227
                    $zend_type_reader,
228
                    $zval->value->obj,
229
                );
230
                foreach ($properties_iterator as $name => $property) {
231
                    $properties[$name] = $dump_zval($property);
232
                }
233
234
                return [
235
                    'address' => $zval->value->obj->address,
236
                    'overhead' => 128,
237
                    'dynamic_properties' => $dynamic_properties,
238
                    'properties' => $properties,
239
                ];
240
            } elseif ($zval->isString()) {
241
                $str = $remote_process_dereferencer->deref($zval->value->str);
242
                return [
243
                    'address' => $zval->value->str->address,
244
                    'overhead' => 128,
245
                    'len' => $str->len,
246
                ];
247
            } else {
248
                return 16;
249
            }
250
        };
251
252
        $call_frames = [];
253
        $execute_data = $remote_process_dereferencer->deref($eg->current_execute_data);
254
        foreach ($execute_data->iterateStackChain($remote_process_dereferencer) as $execute_data) {
255
            $current_function_name = $execute_data->getFunctionName($remote_process_dereferencer);
256
            $current_call_frame = [];
257
            $local_variables_iterator = $execute_data->getVariables($remote_process_dereferencer, $zend_type_reader);
258
            foreach ($local_variables_iterator as $name => $value) {
259
                $current_call_frame[$name] = $dump_zval($value);
260
            }
261
            $call_frames[] = [
262
                'function_name' => $current_function_name,
263
                'local_variables' => $current_call_frame,
264
            ];
265
        }
266
        $pick_heap_allocated = function (array $call_frames) {
267
            $result = [];
268
            foreach ($call_frames as $key => $frame) {
269
                $local_variables = [];
270
                foreach ($frame['local_variables'] as $name => $variable) {
271
                    if (isset($variable['address'])) {
272
                        $local_variables[$name] = $variable;
273
                    }
274
                    if (is_int($variable)) {
275
                        $local_variables[$name] = 'stack allocated';
276
                    }
277
                }
278
                $result[$key] = [
279
                    'function_name' => $frame['function_name'],
280
                    'local_variables' => $local_variables,
281
                ];
282
            }
283
            return $result;
284
        };
285
286
        $sum = function (array $result) use (&$sum) {
287
            $total = 0;
288
            foreach ($result as $key => $item) {
289
                if (is_array($item)) {
290
                    $total += $sum($item);
291
                } elseif ($key !== 'address' and is_int($item)) {
292
                    $total += $item;
293
                }
294
            }
295
            return $total;
296
        };
297
        $symbol_table_result = $dump_symbol_table($eg->symbol_table);
298
        $function_table_result = $dump_function_table($function_table);
299
        $class_table_result = $dump_class_table($class_table);
300
        $compiler_arena = $cg->getSizeOfArena($remote_process_dereferencer);
301
        $compiler_ast_arena = $cg->getSizeOfAstArena($remote_process_dereferencer);
302
        $data_traced = [
303
            'call_frames' => $call_frames,
304
            'global_variables' => $symbol_table_result,
305
            'functions' => $function_table_result,
306
            'classes' => $class_table_result,
307
        ];
308
//        var_dump($data_traced);
309
//        var_dump($call_frames);
310
//        die;
311
        $analyzed_result = [
312
            'call_frames' => $sum($pick_heap_allocated($call_frames)),
313
            'global_variables' => $sum($symbol_table_result),
314
            'functions' => $sum($function_table_result),
315
            'classes' => $sum($class_table_result),
316
            'vm_stack' => $vm_stack_size,
317
            'compiler_arena' => $compiler_arena,
318
            'compiler_ast_arena' => $compiler_ast_arena,
319
        ];
320
        file_put_contents('traced_data.json', json_encode($data_traced, JSON_PRETTY_PRINT));
321
        var_dump($analyzed_result);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($analyzed_result) looks like debug code. Are you sure you do not want to remove it?
Loading history...
322
323
        var_dump([
324
            'heap_size' => $size,
325
            'heap_real_size' => $real_size,
326
            'analyzed_sum' => $sum($analyzed_result),
327
            'analyzed_percentage' => $sum($analyzed_result) / $size * 100,
328
        ]);
329
        return 0;
330
    }
331
332
    private function getChunkAddress(int $pid, string $php_version, MemoryReaderInterface $memory_reader): int
333
    {
334
        $chunk_address = $this->chunk_finder->findAddress(
335
            $pid,
336
            $php_version,
337
            $memory_reader
338
        );
339
        if (is_null($chunk_address)) {
340
            throw new \RuntimeException('chunk address not found');
341
        }
342
        return $chunk_address;
343
    }
344
}
345