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

MemoryCommand::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 8
dl 0
loc 11
rs 10
c 1
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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