Passed
Push — 0.6.x ( ecdcc4...abcf7f )
by Shinji
02:26 queued 47s
created

GetTraceCommand   A

Complexity

Total Complexity 6

Size/Duplication

Total Lines 97
Duplicated Lines 0 %

Importance

Changes 7
Bugs 1 Features 2
Metric Value
wmc 6
eloc 49
c 7
b 1
f 2
dl 0
loc 97
rs 10
1
<?php
2
3
/**
4
 * This file is part of the sj-i/php-profiler package.
5
 *
6
 * (c) sji <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace PhpProfiler\Command\Inspector;
15
16
use PhpProfiler\Inspector\Output\TraceOutput\TraceOutputFactory;
17
use PhpProfiler\Inspector\RetryingLoopProvider;
18
use PhpProfiler\Inspector\Settings\GetTraceSettings\GetTraceSettingsFromConsoleInput;
19
use PhpProfiler\Inspector\Settings\InspectorSettingsException;
20
use PhpProfiler\Inspector\Settings\OutputSettings\OutputSettingsFromConsoleInput;
21
use PhpProfiler\Inspector\Settings\TargetPhpSettings\TargetPhpSettingsFromConsoleInput;
22
use PhpProfiler\Inspector\Settings\TargetProcessSettings\TargetProcessSettingsFromConsoleInput;
23
use PhpProfiler\Inspector\Settings\TraceLoopSettings\TraceLoopSettingsFromConsoleInput;
24
use PhpProfiler\Inspector\TargetProcess\TargetProcessResolver;
25
use PhpProfiler\Inspector\TraceLoopProvider;
26
use PhpProfiler\Lib\Elf\Parser\ElfParserException;
27
use PhpProfiler\Lib\Elf\Process\ProcessSymbolReaderException;
28
use PhpProfiler\Lib\Elf\Tls\TlsFinderException;
29
use PhpProfiler\Lib\PhpProcessReader\PhpGlobalsFinder;
30
use PhpProfiler\Lib\PhpProcessReader\PhpVersionDetector;
31
use PhpProfiler\Lib\PhpProcessReader\TraceCache;
32
use PhpProfiler\Lib\Process\MemoryReader\MemoryReaderException;
33
use PhpProfiler\Lib\PhpProcessReader\PhpMemoryReader\CallTraceReader;
34
use PhpProfiler\Lib\Process\ProcessStopper\ProcessStopper;
35
use Symfony\Component\Console\Command\Command;
36
use Symfony\Component\Console\Input\InputInterface;
37
use Symfony\Component\Console\Output\OutputInterface;
38
39
use function PhpProfiler\Lib\Defer\defer;
40
41
final class GetTraceCommand extends Command
42
{
43
    public function __construct(
44
        private PhpGlobalsFinder $php_globals_finder,
45
        private PhpVersionDetector $php_version_detector,
46
        private CallTraceReader $executor_globals_reader,
47
        private TraceLoopProvider $loop_provider,
48
        private GetTraceSettingsFromConsoleInput $get_trace_settings_from_console_input,
49
        private TargetPhpSettingsFromConsoleInput $target_php_settings_from_console_input,
50
        private TargetProcessSettingsFromConsoleInput $target_process_settings_from_console_input,
51
        private TraceLoopSettingsFromConsoleInput $trace_loop_settings_from_console_input,
52
        private OutputSettingsFromConsoleInput $output_settings_from_console_input,
53
        private TraceOutputFactory $trace_output_factory,
54
        private ProcessStopper $process_stopper,
55
        private TargetProcessResolver $target_process_resolver,
56
        private RetryingLoopProvider $retrying_loop_provider,
57
    ) {
58
        parent::__construct();
59
    }
60
61
    public function configure(): void
62
    {
63
        $this->setName('inspector:trace')
64
            ->setDescription('periodically get call trace from an outer process or thread')
65
        ;
66
        $this->target_process_settings_from_console_input->setOptions($this);
67
        $this->get_trace_settings_from_console_input->setOptions($this);
68
        $this->trace_loop_settings_from_console_input->setOptions($this);
69
        $this->target_php_settings_from_console_input->setOptions($this);
70
        $this->output_settings_from_console_input->setOptions($this);
71
    }
72
73
    /**
74
     * @throws MemoryReaderException
75
     * @throws ProcessSymbolReaderException
76
     * @throws ElfParserException
77
     * @throws TlsFinderException
78
     * @throws InspectorSettingsException
79
     */
80
    public function execute(InputInterface $input, OutputInterface $output): int
81
    {
82
        $get_trace_settings = $this->get_trace_settings_from_console_input->createSettings($input);
83
        $target_php_settings = $this->target_php_settings_from_console_input->createSettings($input);
84
        $target_process_settings = $this->target_process_settings_from_console_input->createSettings($input);
85
        $loop_settings = $this->trace_loop_settings_from_console_input->createSettings($input);
86
        $trace_output = $this->trace_output_factory->fromSettingsAndConsoleOutput(
87
            $output,
88
            $this->output_settings_from_console_input->createSettings($input),
89
        );
90
91
        $process_specifier = $this->target_process_resolver->resolve($target_process_settings);
92
93
        $target_php_settings = $this->php_version_detector->decidePhpVersion(
94
            $process_specifier,
95
            $target_php_settings
96
        );
97
98
        // On targeting ZTS, it's possible that libpthread.so of the target process isn't yet loaded
99
        // at this point. In that case the TLS block can't be located, then the address of EG can't
100
        // be found also. So simply retrying the whole process of finding EG here.
101
        $eg_address = $this->retrying_loop_provider->do(
102
            try: fn () => $this->php_globals_finder->findExecutorGlobals(
103
                $process_specifier,
104
                $target_php_settings
105
            ),
106
            retry_on: [\Throwable::class],
107
            max_retry: 10,
108
            interval_on_retry_ns: 1000 * 1000 * 10,
109
        );
110
111
        $sg_address = $this->php_globals_finder->findSAPIGlobals(
112
            $process_specifier,
113
            $target_php_settings
114
        );
115
116
        $trace_cache = new TraceCache();
117
        $this->loop_provider->getMainLoop(
118
            function () use (
119
                $get_trace_settings,
120
                $process_specifier,
121
                $target_php_settings,
122
                $loop_settings,
123
                $eg_address,
124
                $sg_address,
125
                $trace_output,
126
                $trace_cache,
127
            ): bool {
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected ')', expecting T_VARIABLE on line 127 at column 12
Loading history...
128
                assert($target_php_settings->isDecided());
129
                if ($loop_settings->stop_process and $this->process_stopper->stop($process_specifier->pid)) {
130
                    defer($_, fn () => $this->process_stopper->resume($process_specifier->pid));
131
                }
132
                $call_trace = $this->executor_globals_reader->readCallTrace(
133
                    $process_specifier->pid,
134
                    $target_php_settings->php_version,
135
                    $eg_address,
136
                    $sg_address,
137
                    $get_trace_settings->depth,
138
                    $trace_cache
139
                );
140
                if (!is_null($call_trace)) {
141
                    $trace_output->output($call_trace);
142
                }
143
                return true;
144
            },
145
            $loop_settings
146
        )->invoke();
147
148
        return 0;
149
    }
150
}
151