Passed
Pull Request — master (#101)
by Shinji
01:38
created

SpeedscopeCommand::collectFrames()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 38
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 4
eloc 31
c 1
b 0
f 1
nc 4
nop 1
dl 0
loc 38
rs 9.424
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
Bug introduced by
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
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...
22
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...
23
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...
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
            [$file, $line] = explode(':', $file_line);
72
            $frames[] = new CallFrame(
73
                '',
74
                $name,
75
                $file,
76
                new Opline(0, 0, 0, 0, Cast::toInt($line), new OpcodeV80(0), 0, 0, 0),
77
            );
78
        }
79
        return new CallTrace(...$frames);
80
    }
81
82
    /** @param iterable<CallTrace> $call_frames */
83
    private function collectFrames(iterable $call_frames): array
84
    {
85
        $mapper = fn (array $value): string => \json_encode($value);
86
        $trace_map = [];
87
        $result_frames = [];
88
        $sampled_stacks = [];
89
        $counter = 0;
90
        foreach ($call_frames as $frames) {
91
            $sampled_stack = [];
92
            foreach ($frames->call_frames as $call_frame) {
93
                    $frame = [
94
                    'name' => $call_frame->getFullyQualifiedFunctionName(),
95
                    'file' => $call_frame->file_name,
96
                    'line' => $call_frame->getLineno(),
97
                    ];
98
                    $mapper_key = $mapper($frame);
99
                    if (!isset($trace_map[$mapper_key])) {
100
                        $result_frames[] = $frame;
101
                        $trace_map[$mapper_key] = array_key_last($result_frames);
102
                    }
103
                    $sampled_stack[] = $trace_map[$mapper_key];
104
            }
105
            $sampled_stacks[] = $sampled_stack;
106
            $counter++;
107
        }
108
        return [
109
            "\$schema" => "https://www.speedscope.app/file-format-schema.json",
110
            'shared' => [
111
                'frames' => $result_frames,
112
            ],
113
            'profiles' => [[
114
                'type' => 'sampled',
115
                'name' => 'test',
116
                'unit' => 'none',
117
                'startValue' => 0,
118
                'endValue' => $counter,
119
                'samples' => $sampled_stacks,
120
                'weights' => array_fill(0, count($sampled_stacks), 1),
121
            ]]
122
        ];
123
    }
124
}
125