Issues (200)

src/Command/Converter/SpeedscopeCommand.php (4 issues)

Labels
Severity
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\Converter;
15
16
use PhpCast\Cast;
0 ignored issues
show
The type PhpCast\Cast 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 PhpProfiler\Lib\PhpInternals\Opcodes\OpcodeV80;
18
use PhpProfiler\Lib\PhpInternals\Types\Zend\Opline;
19
use PhpProfiler\Lib\PhpProcessReader\CallFrame;
20
use PhpProfiler\Lib\PhpProcessReader\CallTrace;
21
use Symfony\Component\Console\Command\Command;
0 ignored issues
show
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...
22
use Symfony\Component\Console\Input\InputInterface;
0 ignored issues
show
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...
23
use Symfony\Component\Console\Output\OutputInterface;
0 ignored issues
show
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...
24
25
final class SpeedscopeCommand extends Command
26
{
27
    public function configure(): void
28
    {
29
        $this->setName('converter:speedscope')
30
            ->setDescription('convert traces to the speedscope file format')
31
        ;
32
    }
33
34
    public function execute(InputInterface $input, OutputInterface $output): int
35
    {
36
        $output->write(
37
            json_encode(
38
                $this->collectFrames(
39
                    $this->getTraceIterator(STDIN)
40
                )
41
            )
42
        );
43
        return 0;
44
    }
45
46
    /**
47
     * @param resource $fp
48
     * @return iterable<int, CallTrace>
49
     */
50
    private function getTraceIterator($fp): iterable
51
    {
52
        $buffer = [];
53
        while (($line = fgets($fp)) !== false) {
54
            $line = trim($line);
55
            if ($line !== '') {
56
                $buffer[] = $line;
57
                continue;
58
            }
59
            yield $this->parsePhpSpyCompatible($buffer);
60
            $buffer = [];
61
        }
62
    }
63
64
    /** @param string[] $buffer */
65
    private function parsePhpSpyCompatible(array $buffer): CallTrace
66
    {
67
        $frames = [];
68
        foreach ($buffer as $line_buffer) {
69
            $result = explode(' ', $line_buffer);
70
            [$depth, $name, $file_line] = $result;
71
            if ($depth === '#') { // comment
72
                continue;
73
            }
74
            [$file, $line] = explode(':', $file_line);
75
            $frames[] = new CallFrame(
76
                '',
77
                $name,
78
                $file,
79
                new Opline(0, 0, 0, 0, Cast::toInt($line), new OpcodeV80(0), 0, 0, 0),
80
            );
81
        }
82
        return new CallTrace(...$frames);
83
    }
84
85
    /** @param iterable<CallTrace> $call_frames */
86
    private function collectFrames(iterable $call_frames): array
87
    {
88
        $mapper = fn (array $value): string => \json_encode($value);
89
        $trace_map = [];
90
        $result_frames = [];
91
        $sampled_stacks = [];
92
        $counter = 0;
93
        foreach ($call_frames as $frames) {
94
            $sampled_stack = [];
95
            foreach ($frames->call_frames as $call_frame) {
96
                    $frame = [
97
                    'name' => $call_frame->getFullyQualifiedFunctionName(),
98
                    'file' => $call_frame->file_name,
99
                    'line' => $call_frame->getLineno(),
100
                    ];
101
                    $mapper_key = $mapper($frame);
102
                    if (!isset($trace_map[$mapper_key])) {
103
                        $result_frames[] = $frame;
104
                        $trace_map[$mapper_key] = array_key_last($result_frames);
105
                    }
106
                    $sampled_stack[] = $trace_map[$mapper_key];
107
            }
108
            $sampled_stacks[] = \array_reverse($sampled_stack);
109
            $counter++;
110
        }
111
        return [
112
            "\$schema" => "https://www.speedscope.app/file-format-schema.json",
113
            'shared' => [
114
                'frames' => $result_frames,
115
            ],
116
            'profiles' => [[
117
                'type' => 'sampled',
118
                'name' => 'php profile',
119
                'unit' => 'none',
120
                'startValue' => 0,
121
                'endValue' => $counter,
122
                'samples' => $sampled_stacks,
123
                'weights' => array_fill(0, count($sampled_stacks), 1),
124
            ]]
125
        ];
126
    }
127
}
128