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

DaemonCommand   A

Complexity

Total Complexity 7

Size/Duplication

Total Lines 141
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 1
Metric Value
eloc 91
dl 0
loc 141
c 4
b 0
f 1
rs 10
wmc 7

3 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 58 1
A execute() 0 60 5
A __construct() 0 7 1
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 Amp\Parallel\Context;
0 ignored issues
show
Bug introduced by
The type Amp\Parallel\Context 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...
19
use PhpProfiler\Inspector\Daemon\Reader\Context\PhpReaderContextCreator;
20
use PhpProfiler\Inspector\Daemon\Searcher\Context\PhpSearcherContextCreator;
21
use PhpProfiler\Inspector\Settings\DaemonSettings;
22
use PhpProfiler\Inspector\Settings\GetTraceSettings;
23
use PhpProfiler\Inspector\Settings\TargetPhpSettings;
24
use PhpProfiler\Inspector\Settings\TraceLoopSettings;
25
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...
26
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...
27
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...
28
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...
29
30
final class DaemonCommand extends Command
31
{
32
    private PhpSearcherContextCreator $php_searcher_context_creator;
33
    private PhpReaderContextCreator $php_reader_context_creator;
34
35
    public function __construct(
36
        PhpSearcherContextCreator $php_searcher_context_creator,
37
        PhpReaderContextCreator $php_reader_context_creator
38
    ) {
39
        parent::__construct();
40
        $this->php_reader_context_creator = $php_reader_context_creator;
41
        $this->php_searcher_context_creator = $php_searcher_context_creator;
42
    }
43
44
    public function configure(): void
45
    {
46
        $this->setName('inspector:daemon')
47
            ->setDescription('periodically get running function name from an outer process or thread')
48
            ->addOption(
49
                'target-regex',
50
                'P',
51
                InputOption::VALUE_OPTIONAL,
52
                'regex to find the php binary loaded in the target process'
53
            )
54
            ->addOption('depth', 'd', InputOption::VALUE_OPTIONAL, 'max depth')
55
            ->addOption(
56
                'sleep-ns',
57
                's',
58
                InputOption::VALUE_OPTIONAL,
59
                'nanoseconds between traces (default: 1000 * 1000 * 10)'
60
            )
61
            ->addOption(
62
                'max-retries',
63
                'r',
64
                InputOption::VALUE_OPTIONAL,
65
                'max retries on contiguous errors of read (default: 10)'
66
            )
67
            ->addOption(
68
                'threads',
69
                'T',
70
                InputOption::VALUE_OPTIONAL,
71
                'number of workers (default: 8)'
72
            )
73
            ->addOption(
74
                'php-regex',
75
                null,
76
                InputOption::VALUE_OPTIONAL,
77
                'regex to find the php binary loaded in the target process'
78
            )
79
            ->addOption(
80
                'libpthread-regex',
81
                null,
82
                InputOption::VALUE_OPTIONAL,
83
                'regex to find the libpthread.so loaded in the target process'
84
            )
85
            ->addOption(
86
                'php-version',
87
                null,
88
                InputOption::VALUE_OPTIONAL,
89
                'php version of the target'
90
            )
91
            ->addOption(
92
                'php-path',
93
                null,
94
                InputOption::VALUE_OPTIONAL,
95
                'path to the php binary (only needed in tracing chrooted ZTS target)'
96
            )
97
            ->addOption(
98
                'libpthread-path',
99
                null,
100
                InputOption::VALUE_OPTIONAL,
101
                'path to the libpthread.so (only needed in tracing chrooted ZTS target)'
102
            )
103
        ;
104
    }
105
106
    /**
107
     * @param InputInterface $input
108
     * @param OutputInterface $output
109
     * @return int
110
     */
111
    public function execute(InputInterface $input, OutputInterface $output): int
112
    {
113
        $target_php_settings = TargetPhpSettings::fromConsoleInput($input);
114
        $loop_settings = TraceLoopSettings::fromConsoleInput($input);
115
        $get_trace_settings = GetTraceSettings::fromConsoleInput($input);
116
        $daemon_settings = DaemonSettings::fromConsoleInput($input);
117
118
        $context = $this->php_searcher_context_creator->create();
119
        /** @var int $searcher_pid */
120
        $searcher_pid = Promise\wait($context->start());
0 ignored issues
show
Unused Code introduced by
The assignment to $searcher_pid is dead and can be removed.
Loading history...
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
        $searcher_pid = /** @scrutinizer ignore-call */ Promise\wait($context->start());
Loading history...
121
        Promise\wait($context->send($daemon_settings->target_regex));
122
        /** @var int[] $pid_list */
123
        $pid_list = Promise\wait($context->receive());
124
        $readers = [];
125
        foreach ($pid_list as $target_pid) {
126
            $context = $this->php_reader_context_creator->create();
127
            Promise\wait($context->start());
128
            Promise\wait($context->send([$target_pid, $target_php_settings, $loop_settings, $get_trace_settings]));
129
            $readers[$target_pid] = $context;
130
        }
131
        exec('stty -icanon -echo');
132
133
        Loop::run(function () use (&$readers, $output) {
134
            Loop::onReadable(
135
                STDIN,
136
                /** @param resource $stream */
137
                function (string $watcher_id, $stream) {
138
                    $key = fread($stream, 1);
139
                    if ($key === 'q') {
140
                        Loop::cancel($watcher_id);
141
                        Loop::stop();
142
                    }
143
                }
144
            );
145
            Loop::repeat(10, function () use (&$readers, $output) {
146
                /** @var array<int, Context\Context> $readers */
147
148
                $promises = [];
149
                foreach ($readers as $pid => $reader) {
150
                    if (!$reader->isRunning()) {
151
                        /** @psalm-suppress MixedArrayAccess*/
152
                        unset($readers[$pid]);
153
                        continue;
154
                    }
155
                    $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

155
                    $promises[] = /** @scrutinizer ignore-call */ \Amp\call(
Loading history...
156
                        function () use ($reader, &$readers, $pid, $output) {
157
                            /** @psalm-suppress MixedArrayAccess*/
158
                            unset($readers[$pid]);
159
                            /** @var string $result */
160
                            $result = yield $reader->receive();
161
                            $output->write($result);
162
                            $readers[$pid] = $reader;
163
                        }
164
                    );
165
                }
166
                yield $promises;
167
            });
168
        });
169
170
        return 0;
171
    }
172
}
173