Skip to content

Commit 629a140

Browse files
staabmphpstan-bot
authored andcommitted
List most-often-analysed trait files in -vvv diagnose output
- Added ProcessedFilesCollector service to accumulate processed files during analysis - Added TraitAnalysisDiagnoseExtension to print top 5 most-analysed files in -vvv output - Modified WorkerCommand to send processedFiles in parallel worker JSON responses - Modified ParallelAnalyser to pass processedFiles through postFileCallback - Modified AnalyseApplication to collect processedFiles in both debug and non-debug modes - Added unit tests for ProcessedFilesCollector and TraitAnalysisDiagnoseExtension
1 parent 4e614b3 commit 629a140

File tree

8 files changed

+228
-4
lines changed

8 files changed

+228
-4
lines changed

conf/services.neon

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,16 @@ services:
107107
configPhpVersion: %phpVersion%
108108
autowired: false
109109

110+
-
111+
class: PHPStan\Diagnose\ProcessedFilesCollector
112+
113+
-
114+
class: PHPStan\Diagnose\TraitAnalysisDiagnoseExtension
115+
arguments:
116+
simpleRelativePathHelper: @simpleRelativePathHelper
117+
tags:
118+
- phpstan.diagnoseExtension
119+
110120
# not registered using attributes because there is 2+ instances
111121
-
112122
class: PHPStan\Type\Php\ReflectionGetAttributesMethodReturnTypeExtension

src/Command/AnalyseApplication.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@
1010
use PHPStan\Analyser\ResultCache\ResultCacheManagerFactory;
1111
use PHPStan\Collectors\CollectedData;
1212
use PHPStan\DependencyInjection\AutowiredService;
13+
use PHPStan\Diagnose\ProcessedFilesCollector;
1314
use PHPStan\Internal\BytesHelper;
1415
use PHPStan\PhpDoc\StubFilesProvider;
1516
use PHPStan\PhpDoc\StubValidator;
1617
use PHPStan\ShouldNotHappenException;
1718
use Symfony\Component\Console\Input\InputInterface;
1819
use function array_merge;
1920
use function array_unique;
21+
use function array_values;
2022
use function count;
2123
use function fclose;
2224
use function feof;
@@ -43,6 +45,7 @@ public function __construct(
4345
private ResultCacheManagerFactory $resultCacheManagerFactory,
4446
private IgnoredErrorHelper $ignoredErrorHelper,
4547
private StubFilesProvider $stubFilesProvider,
48+
private ProcessedFilesCollector $processedFilesCollector,
4649
)
4750
{
4851
}
@@ -244,8 +247,9 @@ private function runAnalyser(
244247

245248
if (!$debug) {
246249
$preFileCallback = null;
247-
$postFileCallback = static function (int $step) use ($errorOutput): void {
250+
$postFileCallback = function (int $step, array $processedFiles = []) use ($errorOutput): void {
248251
$errorOutput->getStyle()->progressAdvance($step);
252+
$this->processedFilesCollector->addProcessedFiles(array_values($processedFiles));
249253
};
250254

251255
$errorOutput->getStyle()->progressStart($allAnalysedFilesCount);
@@ -259,7 +263,7 @@ private function runAnalyser(
259263
$postFileCallback = null;
260264
if ($stdOutput->isDebug()) {
261265
$previousMemory = memory_get_peak_usage(true);
262-
$postFileCallback = static function (int $step, array $processedFiles = []) use ($stdOutput, &$previousMemory, &$startTime, &$linesOfCode): void {
266+
$postFileCallback = function (int $step, array $processedFiles = []) use ($stdOutput, &$previousMemory, &$startTime, &$linesOfCode): void {
263267
if ($startTime === null) {
264268
throw new ShouldNotHappenException();
265269
}
@@ -280,6 +284,8 @@ private function runAnalyser(
280284
fclose($handle);
281285
}
282286

287+
$this->processedFilesCollector->addProcessedFiles(array_values($processedFiles));
288+
283289
$stdOutput->writeLineFormatted(sprintf('--- consumed %s, total %s, took %.2f s, %.3f LoC/s', BytesHelper::bytes($currentTotalMemory - $previousMemory), BytesHelper::bytes($currentTotalMemory), $elapsedTime, $linesOfCode / $elapsedTime));
284290
$previousMemory = $currentTotalMemory;
285291
};

src/Command/WorkerCommand.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ private function runWorker(
228228
$dependencies = [];
229229
$usedTraitDependencies = [];
230230
$exportedNodes = [];
231+
$processedFiles = [];
231232
foreach ($files as $file) {
232233
try {
233234
if ($file === $insteadOfFile) {
@@ -242,6 +243,7 @@ private function runWorker(
242243
$dependencies[$file] = $fileAnalyserResult->getDependencies();
243244
$usedTraitDependencies[$file] = $fileAnalyserResult->getUsedTraitDependencies();
244245
$exportedNodes[$file] = $fileAnalyserResult->getExportedNodes();
246+
$processedFiles = array_merge($processedFiles, $fileAnalyserResult->getProcessedFiles());
245247
foreach ($fileErrors as $fileError) {
246248
$errors[] = $fileError;
247249
}
@@ -283,6 +285,7 @@ private function runWorker(
283285
'usedTraitDependencies' => $usedTraitDependencies,
284286
'exportedNodes' => $exportedNodes,
285287
'files' => $files,
288+
'processedFiles' => $processedFiles,
286289
'internalErrorsCount' => $internalErrorsCount,
287290
]]);
288291
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Diagnose;
4+
5+
use function array_count_values;
6+
use function array_slice;
7+
use function arsort;
8+
9+
final class ProcessedFilesCollector
10+
{
11+
12+
/** @var list<string> */
13+
private array $processedFiles = [];
14+
15+
/**
16+
* @param list<string> $files
17+
*/
18+
public function addProcessedFiles(array $files): void
19+
{
20+
foreach ($files as $file) {
21+
$this->processedFiles[] = $file;
22+
}
23+
}
24+
25+
/**
26+
* @return array<string, int>
27+
*/
28+
public function getTopMostAnalysedFiles(int $limit): array
29+
{
30+
$counts = array_count_values($this->processedFiles);
31+
arsort($counts);
32+
33+
$result = [];
34+
foreach (array_slice($counts, 0, $limit, true) as $file => $count) {
35+
if ($count <= 1) {
36+
continue;
37+
}
38+
$result[$file] = $count;
39+
}
40+
41+
return $result;
42+
}
43+
44+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Diagnose;
4+
5+
use PHPStan\Command\Output;
6+
use PHPStan\File\RelativePathHelper;
7+
use function count;
8+
use function sprintf;
9+
10+
final class TraitAnalysisDiagnoseExtension implements DiagnoseExtension
11+
{
12+
13+
public function __construct(
14+
private ProcessedFilesCollector $processedFilesCollector,
15+
private RelativePathHelper $simpleRelativePathHelper,
16+
)
17+
{
18+
}
19+
20+
public function print(Output $output): void
21+
{
22+
$topFiles = $this->processedFilesCollector->getTopMostAnalysedFiles(5);
23+
if (count($topFiles) === 0) {
24+
return;
25+
}
26+
27+
$output->writeLineFormatted('<info>Most often analysed files (likely trait files):</info>');
28+
foreach ($topFiles as $file => $count) {
29+
$output->writeLineFormatted(sprintf(
30+
' %s: %d %s',
31+
$this->simpleRelativePathHelper->getRelativePath($file),
32+
$count,
33+
$count === 1 ? 'time' : 'times',
34+
));
35+
}
36+
$output->writeLineFormatted('');
37+
}
38+
39+
}

src/Parallel/ParallelAnalyser.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public function __construct(
5858
}
5959

6060
/**
61-
* @param Closure(int ): void|null $postFileCallback
61+
* @param Closure(int, list<string>): void|null $postFileCallback
6262
* @param (callable(list<Error>, list<Error>, string[]): void)|null $onFileAnalysisHandler
6363
* @return PromiseInterface<AnalyserResult>
6464
*/
@@ -282,7 +282,7 @@ public function analyse(
282282
}
283283

284284
if ($postFileCallback !== null) {
285-
$postFileCallback(count($json['files']));
285+
$postFileCallback(count($json['files']), $json['processedFiles'] ?? []);
286286
}
287287

288288
if (!isset($peakMemoryUsages[$processIdentifier]) || $peakMemoryUsages[$processIdentifier] < $json['memoryUsage']) {
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Diagnose;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use function array_keys;
7+
8+
class ProcessedFilesCollectorTest extends TestCase
9+
{
10+
11+
public function testEmpty(): void
12+
{
13+
$collector = new ProcessedFilesCollector();
14+
$this->assertSame([], $collector->getTopMostAnalysedFiles(5));
15+
}
16+
17+
public function testSingleFileNotReported(): void
18+
{
19+
$collector = new ProcessedFilesCollector();
20+
$collector->addProcessedFiles(['/path/to/file.php']);
21+
$this->assertSame([], $collector->getTopMostAnalysedFiles(5));
22+
}
23+
24+
public function testTopMostAnalysedFiles(): void
25+
{
26+
$collector = new ProcessedFilesCollector();
27+
28+
// Simulate: file A uses trait T1 and T2, file B uses trait T1
29+
$collector->addProcessedFiles(['/path/to/A.php', '/path/to/T1.php', '/path/to/T2.php']);
30+
$collector->addProcessedFiles(['/path/to/B.php', '/path/to/T1.php']);
31+
32+
$top = $collector->getTopMostAnalysedFiles(5);
33+
$this->assertSame(['/path/to/T1.php' => 2], $top);
34+
}
35+
36+
public function testLimit(): void
37+
{
38+
$collector = new ProcessedFilesCollector();
39+
40+
// Create 7 trait files with varying usage counts
41+
for ($i = 0; $i < 7; $i++) {
42+
$files = ['/path/to/main' . $i . '.php'];
43+
for ($j = 0; $j <= $i; $j++) {
44+
$files[] = '/path/to/trait' . $j . '.php';
45+
}
46+
$collector->addProcessedFiles($files);
47+
}
48+
49+
$top = $collector->getTopMostAnalysedFiles(3);
50+
$this->assertCount(3, $top);
51+
52+
// trait0.php used 7 times, trait1.php 6 times, trait2.php 5 times
53+
$files = array_keys($top);
54+
$this->assertSame('/path/to/trait0.php', $files[0]);
55+
$this->assertSame(7, $top['/path/to/trait0.php']);
56+
$this->assertSame('/path/to/trait1.php', $files[1]);
57+
$this->assertSame(6, $top['/path/to/trait1.php']);
58+
$this->assertSame('/path/to/trait2.php', $files[2]);
59+
$this->assertSame(5, $top['/path/to/trait2.php']);
60+
}
61+
62+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Diagnose;
4+
5+
use PHPStan\Command\Output;
6+
use PHPStan\File\NullRelativePathHelper;
7+
use PHPUnit\Framework\TestCase;
8+
9+
class TraitAnalysisDiagnoseExtensionTest extends TestCase
10+
{
11+
12+
public function testNoOutput(): void
13+
{
14+
$collector = new ProcessedFilesCollector();
15+
$extension = new TraitAnalysisDiagnoseExtension($collector, new NullRelativePathHelper());
16+
17+
$lines = [];
18+
$output = $this->createOutput($lines);
19+
20+
$extension->print($output);
21+
$this->assertSame([], $lines);
22+
}
23+
24+
public function testPrintsTopFiles(): void
25+
{
26+
$collector = new ProcessedFilesCollector();
27+
$collector->addProcessedFiles(['/src/A.php', '/src/Trait1.php', '/src/Trait2.php']);
28+
$collector->addProcessedFiles(['/src/B.php', '/src/Trait1.php', '/src/Trait2.php']);
29+
$collector->addProcessedFiles(['/src/C.php', '/src/Trait1.php']);
30+
31+
$extension = new TraitAnalysisDiagnoseExtension($collector, new NullRelativePathHelper());
32+
33+
$lines = [];
34+
$output = $this->createOutput($lines);
35+
36+
$extension->print($output);
37+
38+
$this->assertCount(4, $lines);
39+
$this->assertStringContainsString('Most often analysed files', $lines[0]);
40+
$this->assertStringContainsString('/src/Trait1.php', $lines[1]);
41+
$this->assertStringContainsString('3 times', $lines[1]);
42+
$this->assertStringContainsString('/src/Trait2.php', $lines[2]);
43+
$this->assertStringContainsString('2 times', $lines[2]);
44+
$this->assertSame('', $lines[3]);
45+
}
46+
47+
/**
48+
* @param list<string> $lines
49+
*/
50+
private function createOutput(array &$lines): Output
51+
{
52+
$output = $this->createMock(Output::class);
53+
$output->method('writeLineFormatted')->willReturnCallback(static function (string $message) use (&$lines): void {
54+
$lines[] = $message;
55+
});
56+
57+
return $output;
58+
}
59+
60+
}

0 commit comments

Comments
 (0)