Skip to content

Commit ff8eec3

Browse files
committed
Added configuration, updated readme, added CI tools
1 parent 78bbcfe commit ff8eec3

File tree

10 files changed

+187
-33
lines changed

10 files changed

+187
-33
lines changed

.scrutinizer.yml

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
filter:
2+
excluded_paths: [tests/*]
3+
4+
tools:
5+
external_code_coverage:
6+
timeout: 600
7+
runs: 2

.travis.yml

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
language: php
2+
3+
php:
4+
- 7.3
5+
- 7.4
6+
7+
script:
8+
- mkdir -p build/logs
9+
10+
- composer outdated -D --strict
11+
12+
- composer require squizlabs/php_codesniffer --dev
13+
- vendor/bin/phpcs src --standard=PSR2 -n
14+
- composer remove squizlabs/php_codesniffer --dev
15+
16+
- composer require phpstan/phpstan --dev
17+
- vendor/bin/phpstan analyze src --level 7 --no-progress
18+
- composer remove phpstan/phpstan --dev
19+
20+
- composer require efabrica/php-extensions-finder --dev
21+
- vendor/bin/php-extensions-finder check src tests
22+
- composer remove efabrica/php-extensions-finder --dev
23+
24+
- vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover
25+
26+
after_script:
27+
- wget https://scrutinizer-ci.com/ocular.phar
28+
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover;

README.md

+40-7
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,54 @@
11
# Sphpera
22

3-
Sphpera is **S**tatic **ph**p **per**formance **a**nalysis tool designed for finding slowest methods and classes based on functions / methods called in them.
3+
Sphpera is **S**tatic **ph**p **per**formance **a**nalysis tool designed for finding potentially the slowest methods and classes based on functions / methods called in them.
44

5-
## Installation
6-
This project is not meant to be run as a dependency, but as separate project.
5+
## Features
6+
7+
### Implemented
8+
- detection of global function calls
9+
- multiplication for calls in cycles
10+
- custom configuration
711

8-
### Composer
9-
The recommended way is to install this library via composer.
12+
### Planned
13+
- detection of class methods calls
14+
- HTML output similar to PHPUnit
15+
- detection of multiple implementations - when some interface or class has multiple implementations and there is only this interface injected, we have to decide which implementation will be used for analysis (default the slowest, can be overridden via configuration)
16+
- multiplication for calls in array_map and similar cycle-style functions
17+
18+
## Installation
19+
This project should not be run as a dependency, but as separate project. Create some directory for it:
1020

1121
```shell script
1222
mkdir sphpera
1323
cd sphpera
24+
```
25+
and follow one of next steps:
26+
27+
### use composer
28+
The recommended way is to install this project via composer.
29+
30+
```shell script
1431
composer require lulco/sphpera
1532
```
1633

17-
### Usage
18-
Run command
34+
### git clone
35+
You can also clone this project directly. Use this for contribution.
36+
```shell script
37+
git clone [email protected]:lulco/sphpera.git .
38+
composer install
1939
```
40+
41+
## Usage
42+
Note: Following examples describe how to use sphpera when it is installed via composer.
43+
44+
Run command:
45+
```shell script
2046
vendor/bin/sphpera analyse dir1 dir2
2147
```
48+
49+
## Configuration
50+
Create your own configuration file where you set the score for functions / methods and default score. Then use this configuration file as option in `analyse` command.
51+
52+
```shell script
53+
vendor/bin/sphpera analyse dir1 dir2 --config=path_to_custom_config_file
54+
```

example/config/config.php

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
use GuzzleHttp\Client;
4+
5+
return [
6+
'default' => 0.0001,
7+
'functions' => [
8+
'curl_exec' => 10,
9+
'file_*' => 1,
10+
],
11+
'methods' => [
12+
Client::class => [
13+
'request' => 10,
14+
],
15+
],
16+
];

src/Command/AnalyseCommand.php

+22-5
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
namespace Sphpera\Command;
44

5+
use InvalidArgumentException;
6+
use Sphpera\Config\Config;
57
use Sphpera\ScoreResolver;
68
use Symfony\Component\Console\Command\Command;
79
use Symfony\Component\Console\Input\InputArgument;
810
use Symfony\Component\Console\Input\InputInterface;
11+
use Symfony\Component\Console\Input\InputOption;
912
use Symfony\Component\Console\Output\OutputInterface;
1013
use Symfony\Component\Finder\Finder;
1114

@@ -15,25 +18,39 @@ protected function configure(): void
1518
{
1619
$this->setName('analyse')
1720
->addArgument('dirs', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'List of dirs to analyse')
21+
->addOption('config', null, InputOption::VALUE_REQUIRED, 'Path to custom config file')
1822
;
1923
}
2024

2125
protected function execute(InputInterface $input, OutputInterface $output): int
2226
{
23-
$config = [
24-
'default' => 0.0001,
25-
'functions' => [],
26-
'methods' => [],
27-
];
27+
$configuration = [];
28+
/** @var string|null $configPath */
29+
$configPath = $input->getOption('config');
30+
if ($configPath) {
31+
if (!file_exists($configPath)) {
32+
throw new InvalidArgumentException('File "' . $configPath . '" not found');
33+
}
34+
$configuration = require $configPath;
35+
}
36+
37+
if (!is_array($configuration)) {
38+
throw new InvalidArgumentException('Configuration is not array');
39+
}
2840

41+
$config = new Config($configuration);
2942
$scoreResolver = new ScoreResolver($config);
3043

44+
/** @var array $dirs */
3145
$dirs = $input->getArgument('dirs');
3246
$slowest = 0;
3347
$slowestName = '';
3448
foreach (Finder::create()->in($dirs)->name('*.php') as $path) {
3549
$path = (string)$path;
3650
$scores = $scoreResolver->resolve($path);
51+
52+
print_R($scores);
53+
3754
foreach ($scores as $class => $methods) {
3855
foreach ($methods as $method => $score) {
3956
if ($slowest < $score) {

src/Config/Config.php

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Sphpera\Config;
4+
5+
class Config
6+
{
7+
private $configuration = [
8+
'default' => 0.0001,
9+
'functions' => [],
10+
'methods' => [],
11+
];
12+
13+
public function __construct(array $configuration)
14+
{
15+
$this->configuration = array_merge($this->configuration, $configuration);
16+
}
17+
18+
public function getDefault(): float
19+
{
20+
return $this->configuration['default'];
21+
}
22+
23+
public function getFunctions(): array
24+
{
25+
return $this->configuration['functions'];
26+
}
27+
28+
public function getMethods(): array
29+
{
30+
return $this->configuration['methods'];
31+
}
32+
}

src/Parser/Visitor/MyVisitor.php

+27-10
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,32 @@
99
use PhpParser\Node\Name;
1010
use PhpParser\Node\Stmt\Class_;
1111
use PhpParser\Node\Stmt\ClassMethod;
12+
use PhpParser\Node\Stmt\Do_;
1213
use PhpParser\Node\Stmt\For_;
1314
use PhpParser\Node\Stmt\Foreach_;
1415
use PhpParser\Node\Stmt\Namespace_;
1516
use PhpParser\Node\Stmt\While_;
1617
use PhpParser\NodeVisitorAbstract;
18+
use Sphpera\Config\Config;
1719
use Sphpera\Stack;
1820

1921
class MyVisitor extends NodeVisitorAbstract
2022
{
21-
/** @var array */
23+
/** @var Config */
2224
private $config;
2325

2426
/** @var Stack */
2527
private $stack;
2628

27-
public function __construct(array $config, Stack $stack)
29+
public function __construct(Config $config, Stack $stack)
2830
{
2931
$this->config = $config;
3032
$this->stack = $stack;
3133
}
3234

3335
public function enterNode(Node $node)
3436
{
35-
if ($node instanceof Foreach_ || $node instanceof While_ || $node instanceof For_) {
37+
if ($node instanceof Foreach_ || $node instanceof While_ || $node instanceof For_ || $node instanceof Do_) {
3638
// TODO get number of cycle iterations
3739
$this->stack->startCycle($node->getStartLine(), $node->getEndLine());
3840
return null;
@@ -41,25 +43,40 @@ public function enterNode(Node $node)
4143
$this->stack->checkCycle($node->getStartLine());
4244

4345
if ($node instanceof Namespace_) {
44-
$this->stack->actualNamespace(implode('\\', $node->name->parts));
46+
$name = $node->name;
47+
$namespace = null;
48+
if ($name) {
49+
$namespace = implode('\\', $name->parts);
50+
}
51+
$this->stack->actualNamespace($namespace);
4552
return null;
4653
}
4754

4855
if ($node instanceof Class_) {
49-
$this->stack->actualClass($node->name->name);
56+
$name = $node->name;
57+
$className = null;
58+
if ($name) {
59+
$className = $name->name;
60+
}
61+
$this->stack->actualClass($className);
5062
return null;
5163
}
5264

5365
if ($node instanceof ClassMethod) {
54-
$this->stack->actualMethod($node->name->name);
66+
$name = $node->name;
67+
$methodName = null;
68+
if ($name) {
69+
$methodName = $name->name;
70+
}
71+
$this->stack->actualMethod($methodName);
5572
return null;
5673
}
5774

5875
if ($node instanceof MethodCall) {
5976
// TODO resolve class name from variables etc
6077
$className = '';
6178
$methodName = $node->name->name;
62-
foreach ($this->config['methods'] as $class => $methods) {
79+
foreach ($this->config->getMethods() as $class => $methods) {
6380
if (!preg_match('/' . str_replace('*', '(.*?)', $class) . '/', $className)) {
6481
continue;
6582
}
@@ -70,7 +87,7 @@ public function enterNode(Node $node)
7087
}
7188
}
7289
}
73-
$this->stack->add($this->config['default']);
90+
$this->stack->add($this->config->getDefault());
7491
return null;
7592
}
7693

@@ -82,13 +99,13 @@ public function enterNode(Node $node)
8299
} elseif ($name instanceof Variable) {
83100
$functionName = $name->name;
84101
}
85-
foreach ($this->config['functions'] as $function => $score) {
102+
foreach ($this->config->getFunctions() as $function => $score) {
86103
if (preg_match('/' . str_replace('*', '(.*?)', $function) . '/', $functionName)) {
87104
$this->stack->add($score);
88105
return null;
89106
}
90107
}
91-
$this->stack->add($this->config['default']);
108+
$this->stack->add($this->config->getDefault());
92109
return null;
93110
}
94111

src/ScoreResolver.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
use PhpParser\NodeTraverserInterface;
88
use PhpParser\NodeVisitor\NameResolver;
99
use PhpParser\ParserFactory;
10+
use Sphpera\Config\Config;
1011
use Sphpera\Parser\Visitor\MyVisitor;
1112

1213
class ScoreResolver
1314
{
14-
/** @var array */
15+
/** @var Config */
1516
private $config;
1617

17-
public function __construct(array $config)
18+
public function __construct(Config $config)
1819
{
1920
$this->config = $config;
2021
}

src/Stack.php

+9-6
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,17 @@ class Stack
2020

2121
private $scores = [];
2222

23-
public function actualNamespace(string $namespace): void
23+
public function actualNamespace(?string $namespace): void
2424
{
2525
$this->actualNamespace = $namespace;
2626
}
2727

28-
public function actualClass(string $class): void
28+
public function actualClass(?string $class): void
2929
{
3030
$this->actualClass = $class;
3131
}
3232

33-
public function actualMethod(string $method): void
33+
public function actualMethod(?string $method): void
3434
{
3535
$this->actualMethod = $method;
3636
}
@@ -62,13 +62,16 @@ public function checkCycle(int $line): bool
6262

6363
public function add(float $score): void
6464
{
65-
if (!isset($this->scores[$this->actualNamespace . '\\' . $this->actualClass][$this->actualMethod])) {
66-
$this->scores[$this->actualNamespace . '\\' . $this->actualClass][$this->actualMethod] = 0;
65+
$className = implode('\\', array_filter([$this->actualNamespace, $this->actualClass]));
66+
$methodName = $this->actualMethod ?: '';
67+
68+
if (!isset($this->scores[$className][$methodName])) {
69+
$this->scores[$className][$methodName] = 0;
6770
}
6871
if ($this->inCycle && $this->cycleMultiplier) {
6972
$score *= $this->cycleMultiplier;
7073
}
71-
$this->scores[$this->actualNamespace . '\\' . $this->actualClass][$this->actualMethod] += $score;
74+
$this->scores[$className][$methodName] += $score;
7275
}
7376

7477
public function getScores(): array

tests/ScoreTest.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44

55
use GuzzleHttp\Client;
66
use PHPUnit\Framework\TestCase;
7+
use Sphpera\Config\Config;
78
use Sphpera\ScoreResolver;
89
use SphperaTest\Sample\Sample;
910

1011
class ScoreTest extends TestCase
1112
{
1213
public function testSample()
1314
{
14-
$config = [
15-
'default' => 0.0001,
15+
$config = new Config([
1616
'functions' => [
1717
'curl_exec' => 10,
1818
'file_*' => 1,
@@ -22,7 +22,7 @@ public function testSample()
2222
'request' => 10,
2323
],
2424
],
25-
];
25+
]);
2626

2727
$expectedScores = [
2828
Sample::class => [

0 commit comments

Comments
 (0)