Passed
Pull Request — master (#9)
by Shinji
01:28
created

DaemonCommand::execute()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 72
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 1
Metric Value
cc 6
eloc 44
c 4
b 0
f 1
nc 3
nop 2
dl 0
loc 72
rs 8.5937

How to fix   Long Method   

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
/**
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 Amp\Loop;
0 ignored issues
show
Bug introduced by
The type Amp\Loop was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use Amp\Promise;
0 ignored issues
show
Bug introduced by
The type Amp\Promise was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
18
use PhpProfiler\Inspector\Daemon\Dispatcher\WorkerPool;
19
use PhpProfiler\Inspector\Daemon\Reader\Context\PhpReaderContext;
20
use PhpProfiler\Inspector\Daemon\Reader\Context\PhpReaderContextCreator;
21
use PhpProfiler\Inspector\Daemon\Searcher\Context\PhpSearcherContextCreator;
22
use PhpProfiler\Inspector\Settings\DaemonSettings;
23
use PhpProfiler\Inspector\Settings\GetTraceSettings;
24
use PhpProfiler\Inspector\Settings\TargetPhpSettings;
25
use PhpProfiler\Inspector\Settings\TraceLoopSettings;
26
use Symfony\Component\Console\Command\Command;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Command\Command was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
27
use Symfony\Component\Console\Input\InputInterface;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Input\InputInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
28
use Symfony\Component\Console\Input\InputOption;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Input\InputOption was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
29
use Symfony\Component\Console\Output\OutputInterface;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Output\OutputInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
30
31
final class DaemonCommand extends Command
32
{
33
    private PhpSearcherContextCreator $php_searcher_context_creator;
34
    private PhpReaderContextCreator $php_reader_context_creator;
35
36
    public function __construct(
37
        PhpSearcherContextCreator $php_searcher_context_creator,
38
        PhpReaderContextCreator $php_reader_context_creator
39
    ) {
40
        parent::__construct();
41
        $this->php_reader_context_creator = $php_reader_context_creator;
42
        $this->php_searcher_context_creator = $php_searcher_context_creator;
43
    }
44
45
    public function configure(): void
46
    {
47
        $this->setName('inspector:daemon')
48
            ->setDescription('periodically get running function name from an outer process or thread')
49
            ->addOption(
50
                'target-regex',
51
                'P',
52
                InputOption::VALUE_OPTIONAL,
53
                'regex to find the php binary loaded in the target process'
54
            )
55
            ->addOption('depth', 'd', InputOption::VALUE_OPTIONAL, 'max depth')
56
            ->addOption(
57
                'sleep-ns',
58
                's',
59
                InputOption::VALUE_OPTIONAL,
60
                'nanoseconds between traces (default: 1000 * 1000 * 10)'
61
            )
62
            ->addOption(
63
                'max-retries',
64
                'r',
65
                InputOption::VALUE_OPTIONAL,
66
                'max retries on contiguous errors of read (default: 10)'
67
            )
68
            ->addOption(
69
                'threads',
70
                'T',
71
                InputOption::VALUE_OPTIONAL,
72
                'number of workers (default: 8)'
73
            )
74
            ->addOption(
75
                'php-regex',
76
                null,
77
                InputOption::VALUE_OPTIONAL,
78
                'regex to find the php binary loaded in the target process'
79
            )
80
            ->addOption(
81
                'libpthread-regex',
82
                null,
83
                InputOption::VALUE_OPTIONAL,
84
                'regex to find the libpthread.so loaded in the target process'
85
            )
86
            ->addOption(
87
                'php-version',
88
                null,
89
                InputOption::VALUE_OPTIONAL,
90
                'php version of the target'
91
            )
92
            ->addOption(
93
                'php-path',
94
                null,
95
                InputOption::VALUE_OPTIONAL,
96
                'path to the php binary (only needed in tracing chrooted ZTS target)'
97
            )
98
            ->addOption(
99
                'libpthread-path',
100
                null,
101
                InputOption::VALUE_OPTIONAL,
102
                'path to the libpthread.so (only needed in tracing chrooted ZTS target)'
103
            )
104
        ;
105
    }
106
107
    /**
108
     * @param InputInterface $input
109
     * @param OutputInterface $output
110
     * @return int
111
     */
112
    public function execute(InputInterface $input, OutputInterface $output): int
113
    {
114
        $target_php_settings = TargetPhpSettings::fromConsoleInput($input);
115
        $loop_settings = TraceLoopSettings::fromConsoleInput($input);
116
        $get_trace_settings = GetTraceSettings::fromConsoleInput($input);
117
        $daemon_settings = DaemonSettings::fromConsoleInput($input);
118
119
        $searcher_context = $this->php_searcher_context_creator->create();
120
        Promise\wait($searcher_context->start());
0 ignored issues
show
Bug introduced by
The function wait was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

120
        /** @scrutinizer ignore-call */ 
121
        Promise\wait($searcher_context->start());
Loading history...
121
        Promise\wait($searcher_context->sendTargetRegex($daemon_settings->target_regex));
122
        $pid_list = Promise\wait($searcher_context->receivePidList());
123
124
        $worker_pool = WorkerPool::create($this->php_reader_context_creator, $daemon_settings->threads);
125
126
        $readers = [];
127
        foreach ($pid_list as $target_pid) {
128
            $reader_context = $worker_pool->getFreeWorker();
129
            if (is_null($reader_context)) {
130
                continue;
131
            }
132
            Promise\wait(
133
                $reader_context->sendSettings(
134
                    [
135
                        $target_pid,
136
                        $target_php_settings,
137
                        $loop_settings,
138
                        $get_trace_settings
139
                    ]
140
                )
141
            );
142
            $readers[$target_pid] = $reader_context;
143
        }
144
        exec('stty -icanon -echo');
145
146
        Loop::run(function () use (&$readers, $output) {
147
            Loop::onReadable(
148
                STDIN,
149
                /** @param resource $stream */
150
                function (string $watcher_id, $stream) {
151
                    $key = fread($stream, 1);
152
                    if ($key === 'q') {
153
                        Loop::cancel($watcher_id);
154
                        Loop::stop();
155
                    }
156
                }
157
            );
158
            Loop::repeat(10, function () use (&$readers, $output) {
159
                /** @var array<int, PhpReaderContext> $readers */
160
161
                $promises = [];
162
                foreach ($readers as $pid => $reader) {
163
                    if (!$reader->isRunning()) {
164
                        /** @psalm-suppress MixedArrayAccess*/
165
                        unset($readers[$pid]);
166
                        continue;
167
                    }
168
                    $promises[] = \Amp\call(
0 ignored issues
show
Bug introduced by
The function call was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

168
                    $promises[] = /** @scrutinizer ignore-call */ \Amp\call(
Loading history...
169
                        function () use ($reader, &$readers, $pid, $output) {
170
                            /** @psalm-suppress MixedArrayAccess*/
171
                            unset($readers[$pid]);
172
                            /** @var string $result */
173
                            $result = yield $reader->receiveTrace();
174
                            $output->write($result);
175
                            $readers[$pid] = $reader;
176
                        }
177
                    );
178
                }
179
                yield $promises;
180
            });
181
        });
182
183
        return 0;
184
    }
185
}
186