Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions conf/services.neon
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ services:
composerAutoloaderProjectPaths: %composerAutoloaderProjectPaths%
allConfigFiles: %allConfigFiles%
configPhpVersion: %phpVersion%
simpleRelativePathHelper: @simpleRelativePathHelper
autowired: false

# not registered using attributes because there is 2+ instances
Expand Down
46 changes: 46 additions & 0 deletions src/Analyser/ProcessedFilesCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use PHPStan\DependencyInjection\AutowiredService;
use function array_count_values;
use function array_slice;
use function arsort;

#[AutowiredService]
final class ProcessedFilesCollector
{

/** @var list<string> */
private array $processedFiles = [];

/**
* @param list<string> $files
*/
public function addProcessedFiles(array $files): void
{
foreach ($files as $file) {
$this->processedFiles[] = $file;
}
}

/**
* @return array<string, int<2, max>>
*/
public function getTopMostAnalysedFiles(int $limit): array
{
$counts = array_count_values($this->processedFiles);
arsort($counts);

$result = [];
foreach (array_slice($counts, 0, $limit, true) as $file => $count) {
if ($count <= 1) {
continue;
}
$result[$file] = $count;
}

return $result;
}

}
10 changes: 8 additions & 2 deletions src/Command/AnalyseApplication.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPStan\Analyser\Error;
use PHPStan\Analyser\FileAnalyserResult;
use PHPStan\Analyser\Ignore\IgnoredErrorHelper;
use PHPStan\Analyser\ProcessedFilesCollector;
use PHPStan\Analyser\ResultCache\ResultCacheManagerFactory;
use PHPStan\Collectors\CollectedData;
use PHPStan\DependencyInjection\AutowiredService;
Expand All @@ -17,6 +18,7 @@
use Symfony\Component\Console\Input\InputInterface;
use function array_merge;
use function array_unique;
use function array_values;
use function count;
use function fclose;
use function feof;
Expand All @@ -43,6 +45,7 @@ public function __construct(
private ResultCacheManagerFactory $resultCacheManagerFactory,
private IgnoredErrorHelper $ignoredErrorHelper,
private StubFilesProvider $stubFilesProvider,
private ProcessedFilesCollector $processedFilesCollector,
)
{
}
Expand Down Expand Up @@ -244,8 +247,9 @@ private function runAnalyser(

if (!$debug) {
$preFileCallback = null;
$postFileCallback = static function (int $step) use ($errorOutput): void {
$postFileCallback = function (int $step, array $processedFiles) use ($errorOutput): void {
$errorOutput->getStyle()->progressAdvance($step);
$this->processedFilesCollector->addProcessedFiles(array_values($processedFiles));
};

$errorOutput->getStyle()->progressStart($allAnalysedFilesCount);
Expand All @@ -259,7 +263,7 @@ private function runAnalyser(
$postFileCallback = null;
if ($stdOutput->isDebug()) {
$previousMemory = memory_get_peak_usage(true);
$postFileCallback = static function (int $step, array $processedFiles = []) use ($stdOutput, &$previousMemory, &$startTime, &$linesOfCode): void {
$postFileCallback = function (int $step, array $processedFiles) use ($stdOutput, &$previousMemory, &$startTime, &$linesOfCode): void {
if ($startTime === null) {
throw new ShouldNotHappenException();
}
Expand All @@ -280,6 +284,8 @@ private function runAnalyser(
fclose($handle);
}

$this->processedFilesCollector->addProcessedFiles(array_values($processedFiles));

$stdOutput->writeLineFormatted(sprintf('--- consumed %s, total %s, took %.2f s, %.3f LoC/s', BytesHelper::bytes($currentTotalMemory - $previousMemory), BytesHelper::bytes($currentTotalMemory), $elapsedTime, $linesOfCode / $elapsedTime));
$previousMemory = $currentTotalMemory;
};
Expand Down
2 changes: 1 addition & 1 deletion src/Command/AnalyserRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public function __construct(
* @param string[] $files
* @param string[] $allAnalysedFiles
* @param Closure(string $file): void|null $preFileCallback
* @param Closure(int, list<string>=): void|null $postFileCallback
* @param Closure(int, list<string>): void|null $postFileCallback
*/
public function runAnalyser(
array $files,
Expand Down
3 changes: 3 additions & 0 deletions src/Command/WorkerCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ private function runWorker(
$dependencies = [];
$usedTraitDependencies = [];
$exportedNodes = [];
$processedFiles = [];
foreach ($files as $file) {
try {
if ($file === $insteadOfFile) {
Expand All @@ -242,6 +243,7 @@ private function runWorker(
$dependencies[$file] = $fileAnalyserResult->getDependencies();
$usedTraitDependencies[$file] = $fileAnalyserResult->getUsedTraitDependencies();
$exportedNodes[$file] = $fileAnalyserResult->getExportedNodes();
$processedFiles = array_merge($processedFiles, $fileAnalyserResult->getProcessedFiles());
foreach ($fileErrors as $fileError) {
$errors[] = $fileError;
}
Expand Down Expand Up @@ -283,6 +285,7 @@ private function runWorker(
'usedTraitDependencies' => $usedTraitDependencies,
'exportedNodes' => $exportedNodes,
'files' => $files,
'processedFiles' => $processedFiles,
'internalErrorsCount' => $internalErrorsCount,
]]);
});
Expand Down
19 changes: 19 additions & 0 deletions src/Diagnose/PHPStanDiagnoseExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
namespace PHPStan\Diagnose;

use Phar;
use PHPStan\Analyser\ProcessedFilesCollector;
use PHPStan\Command\Output;
use PHPStan\ExtensionInstaller\GeneratedConfig;
use PHPStan\File\FileHelper;
use PHPStan\File\RelativePathHelper;
use PHPStan\Internal\ComposerHelper;
use PHPStan\Php\ComposerPhpVersionFactory;
use PHPStan\Php\PhpVersion;
Expand Down Expand Up @@ -42,6 +44,8 @@ public function __construct(
private array $composerAutoloaderProjectPaths,
private array $allConfigFiles,
private ComposerPhpVersionFactory $composerPhpVersionFactory,
private ProcessedFilesCollector $processedFilesCollector,
private RelativePathHelper $simpleRelativePathHelper,
)
{
}
Expand Down Expand Up @@ -203,6 +207,21 @@ public function print(Output $output): void
$output->writeLineFormatted($composerAutoloaderProjectPath);
}
$output->writeLineFormatted('');

$topFiles = $this->processedFilesCollector->getTopMostAnalysedFiles(5);
if (count($topFiles) <= 0) {
return;
}

$output->writeLineFormatted('<info>Most often analysed files:</info>');
foreach ($topFiles as $file => $count) {
$output->writeLineFormatted(sprintf(
' %s: %d times',
$this->simpleRelativePathHelper->getRelativePath($file),
$count,
));
}
$output->writeLineFormatted('');
}

}
4 changes: 2 additions & 2 deletions src/Parallel/ParallelAnalyser.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public function __construct(
}

/**
* @param Closure(int ): void|null $postFileCallback
* @param Closure(int, list<string>): void|null $postFileCallback
* @param (callable(list<Error>, list<Error>, string[]): void)|null $onFileAnalysisHandler
* @return PromiseInterface<AnalyserResult>
*/
Expand Down Expand Up @@ -282,7 +282,7 @@ public function analyse(
}

if ($postFileCallback !== null) {
$postFileCallback(count($json['files']));
$postFileCallback(count($json['files']), $json['processedFiles']);
}

if (!isset($peakMemoryUsages[$processIdentifier]) || $peakMemoryUsages[$processIdentifier] < $json['memoryUsage']) {
Expand Down
62 changes: 62 additions & 0 deletions tests/PHPStan/Analyser/ProcessedFilesCollectorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use PHPUnit\Framework\TestCase;
use function array_keys;

class ProcessedFilesCollectorTest extends TestCase
{

public function testEmpty(): void
{
$collector = new ProcessedFilesCollector();
$this->assertSame([], $collector->getTopMostAnalysedFiles(5));
}

public function testSingleFileNotReported(): void
{
$collector = new ProcessedFilesCollector();
$collector->addProcessedFiles(['/path/to/file.php']);
$this->assertSame([], $collector->getTopMostAnalysedFiles(5));
}

public function testTopMostAnalysedFiles(): void
{
$collector = new ProcessedFilesCollector();

// Simulate: file A uses trait T1 and T2, file B uses trait T1
$collector->addProcessedFiles(['/path/to/A.php', '/path/to/T1.php', '/path/to/T2.php']);
$collector->addProcessedFiles(['/path/to/B.php', '/path/to/T1.php']);

$top = $collector->getTopMostAnalysedFiles(5);
$this->assertSame(['/path/to/T1.php' => 2], $top);
}

public function testLimit(): void
{
$collector = new ProcessedFilesCollector();

// Create 7 trait files with varying usage counts
for ($i = 0; $i < 7; $i++) {
$files = ['/path/to/main' . $i . '.php'];
for ($j = 0; $j <= $i; $j++) {
$files[] = '/path/to/trait' . $j . '.php';
}
$collector->addProcessedFiles($files);
}

$top = $collector->getTopMostAnalysedFiles(3);
$this->assertCount(3, $top);

// trait0.php used 7 times, trait1.php 6 times, trait2.php 5 times
$files = array_keys($top);
$this->assertSame('/path/to/trait0.php', $files[0]);
$this->assertSame(7, $top['/path/to/trait0.php']);
$this->assertSame('/path/to/trait1.php', $files[1]);
$this->assertSame(6, $top['/path/to/trait1.php']);
$this->assertSame('/path/to/trait2.php', $files[2]);
$this->assertSame(5, $top['/path/to/trait2.php']);
}

}
Loading