Completed
Push — master ( a54744...1a2e45 )
by Shinji
15s queued 12s
created

GetTraceCommand   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 118
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 2
Metric Value
eloc 65
c 4
b 0
f 2
dl 0
loc 118
rs 10
wmc 17

4 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 11 1
A __construct() 0 8 1
B execute() 0 52 11
A runPeriodically() 0 17 4
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\Lib\Elf\Parser\ElfParserException;
17
use PhpProfiler\Lib\Elf\Process\ProcessSymbolReaderException;
18
use PhpProfiler\Lib\Elf\Tls\TlsFinderException;
19
use PhpProfiler\Lib\Process\MemoryReader\MemoryReaderException;
20
use PhpProfiler\ProcessReader\PhpGlobalsFinder;
21
use PhpProfiler\ProcessReader\PhpMemoryReader\ExecutorGlobalsReader;
22
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...
23
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...
24
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...
25
use Symfony\Component\Console\Output\ConsoleOutputInterface;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Consol...\ConsoleOutputInterface 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\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...
27
28
final class GetTraceCommand extends Command
29
{
30
    private const SLEEP_NANO_SECONDS_DEFAULT = 1000 * 1000 * 10;
31
32
    private PhpGlobalsFinder $php_globals_finder;
33
    private ExecutorGlobalsReader $executor_globals_reader;
34
35
    /**
36
     * GetTraceCommand constructor.
37
     *
38
     * @param PhpGlobalsFinder $php_globals_finder
39
     * @param ExecutorGlobalsReader $executor_globals_reader
40
     * @param string|null $name
41
     */
42
    public function __construct(
43
        PhpGlobalsFinder $php_globals_finder,
44
        ExecutorGlobalsReader $executor_globals_reader,
45
        string $name = null
46
    ) {
47
        parent::__construct($name);
48
        $this->php_globals_finder = $php_globals_finder;
49
        $this->executor_globals_reader = $executor_globals_reader;
50
    }
51
52
    public function configure(): void
53
    {
54
        $this->setName('inspector:trace')
55
            ->setDescription('periodically get call trace from an outer process or thread')
56
            ->addOption('pid', 'p', InputOption::VALUE_REQUIRED, 'process id')
57
            ->addOption('depth', 'd', InputOption::VALUE_OPTIONAL, 'max depth')
58
            ->addOption(
59
                'sleep-ns',
60
                's',
61
                InputOption::VALUE_OPTIONAL,
62
                'nanoseconds between traces (default: 1000 * 1000 * 10)'
63
            );
64
    }
65
66
    /**
67
     * @param InputInterface $input
68
     * @param OutputInterface $output
69
     * @return int
70
     * @throws MemoryReaderException
71
     * @throws ProcessSymbolReaderException
72
     * @throws ElfParserException
73
     * @throws TlsFinderException
74
     */
75
    public function execute(InputInterface $input, OutputInterface $output): int
76
    {
77
        $pid = $input->getOption('pid');
78
        if (is_null($pid)) {
79
            $error_output = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
80
            $error_output->writeln('pid is not specified');
81
            return 1;
82
        }
83
        $pid = filter_var($pid, FILTER_VALIDATE_INT);
84
        if ($pid === false) {
85
            $error_output = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
86
            $error_output->writeln('pid is not integer');
87
            return 2;
88
        }
89
90
        $depth = $input->getOption('depth');
91
        if (is_null($depth)) {
92
            $depth = PHP_INT_MAX;
93
        }
94
        $depth = filter_var($depth, FILTER_VALIDATE_INT);
95
        if ($depth === false) {
96
            $error_output = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
97
            $error_output->writeln('depth is not integer');
98
            return 2;
99
        }
100
101
        $sleep_nano_seconds = $input->getOption('sleep-ns');
102
        if (is_null($sleep_nano_seconds)) {
103
            $sleep_nano_seconds = self::SLEEP_NANO_SECONDS_DEFAULT;
104
        }
105
        $sleep_nano_seconds = filter_var($sleep_nano_seconds, FILTER_VALIDATE_INT);
106
        if ($sleep_nano_seconds === false) {
107
            $error_output = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
108
            $error_output->writeln('sleep-ns is not integer');
109
            return 2;
110
        }
111
112
        $eg_address = $this->php_globals_finder->findExecutorGlobals($pid);
113
114
        $this->runPeriodically(
115
            $sleep_nano_seconds,
116
            function () use ($pid, $eg_address, $depth, $output) {
117
                $call_trace = $this->executor_globals_reader->readCallTrace(
118
                    $pid,
119
                    $eg_address,
120
                    $depth
121
                );
122
                $output->writeln(join(PHP_EOL, $call_trace) . PHP_EOL);
123
            }
124
        );
125
126
        return 0;
127
    }
128
129
    private function runPeriodically(int $sleep_nano_seconds, callable $func): void
130
    {
131
        exec('stty -icanon -echo');
132
        $keyboard_input = fopen('php://stdin', 'r');
133
        stream_set_blocking($keyboard_input, false);
0 ignored issues
show
Bug introduced by
It seems like $keyboard_input can also be of type false; however, parameter $stream of stream_set_blocking() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

133
        stream_set_blocking(/** @scrutinizer ignore-type */ $keyboard_input, false);
Loading history...
134
135
        $key = '';
136
        $count_retry = 0;
137
        while ($key !== 'q' and $count_retry < 10) {
138
            try {
139
                $func();
140
                $count_retry = 0;
141
                time_nanosleep(0, $sleep_nano_seconds);
142
            } catch (MemoryReaderException $e) {
143
                $count_retry++;
144
            }
145
            $key = fread($keyboard_input, 1);
0 ignored issues
show
Bug introduced by
It seems like $keyboard_input can also be of type false; however, parameter $handle of fread() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

145
            $key = fread(/** @scrutinizer ignore-type */ $keyboard_input, 1);
Loading history...
146
        }
147
    }
148
}
149