diff --git a/README.md b/README.md index 1cfcba8..91718a9 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,42 @@ Path from 0x7f94a1856260 +---------------------------+ ``` +## Comparing two memory dumps +Will allow you to see which objects counts and size have changed. + +```bash +$ bin/analyzer diff [options] [--] + +Arguments: + first-file PHP Meminfo Dump File in JSON format + second-file PHP Meminfo Dump File in JSON format to compare first file with + +Options: + -s, --sort[=SORT] Define sorting order when displaying diff. Available options are : + - c : Sort by count + - s : Sort by size + - dc : Sort by the count differene + - ds : Sort by the size difference +``` + +### Exemple + +Fallowing exemple displays the diff sorting them by difference in size. + +```bash +$ bin/analyzer diff eXpansion-mem-dump-2018-01-06T11\:37\:38+0000.json eXpansion-mem-dump-2018-01-06T12\:04\:23+0000.json -sds ++-------------------------------------------------------------+-----------------------+-----------------------------------+-----------------------+--------------------------+ +| Type | First Instances Count | First Cumulated Self Size (bytes) | Second Instances Diff | Second Size Diff (bytes) | ++-------------------------------------------------------------+-----------------------+-----------------------------------+-----------------------+--------------------------+ +| string | 7495 | 436324 | +372 | +23447 | +| array | 2097 | 150984 | +28 | +2016 | +| integer | 769 | 12304 | +61 | +976 | +| DateTime | 10 | 720 | +8 | +576 | +| boolean | 795 | 12720 | +15 | +240 | +| eXpansion\Bundle\LocalRecords\Model\Record | 2 | 144 | +2 | +144 | +| eXpansion\Framework\Core\Listener\BaseStorageUpdateListener | 3 | 216 | +1 | +72 | +``` + A worflow to find and understand memory leak by using PHP Meminfo ----------------------------------------------------------------- diff --git a/analyzer/src/BitOne/PhpMemInfo/Console/Application.php b/analyzer/src/BitOne/PhpMemInfo/Console/Application.php index 4e69d1a..646b997 100644 --- a/analyzer/src/BitOne/PhpMemInfo/Console/Application.php +++ b/analyzer/src/BitOne/PhpMemInfo/Console/Application.php @@ -5,6 +5,7 @@ use BitOne\PhpMemInfo\Console\Command\QueryCommand; use BitOne\PhpMemInfo\Console\Command\ReferencePathCommand; use BitOne\PhpMemInfo\Console\Command\SummaryCommand; +use BitOne\PhpMemInfo\Console\Command\DiffCommand; use Symfony\Component\Console\Application as BaseApplication; /** @@ -21,5 +22,6 @@ public function __construct() $this->add(new QueryCommand()); $this->add(new ReferencePathCommand()); $this->add(new SummaryCommand()); + $this->add(new DiffCommand()); } } diff --git a/analyzer/src/BitOne/PhpMemInfo/Console/Command/DiffCommand.php b/analyzer/src/BitOne/PhpMemInfo/Console/Command/DiffCommand.php new file mode 100644 index 0000000..26979bf --- /dev/null +++ b/analyzer/src/BitOne/PhpMemInfo/Console/Command/DiffCommand.php @@ -0,0 +1,172 @@ + + * @license http://opensource.org/licenses/MIT MIT + */ +class DiffCommand extends Command +{ + const SORT_COUNT = 'c'; + const SORT_SIZE = 's'; + const SORT_DIFF_COUNT = 'dc'; + const SORT_DIFF_SIZE = 'ds'; + + /** @var array List of available sorts and the keys to use for the sorting. */ + protected $sorts = [ + self::SORT_COUNT => 1, + self::SORT_SIZE => 2, + self::SORT_DIFF_COUNT => 3, + self::SORT_DIFF_SIZE => 4, + ]; + + /** + * {@inheritedDoc}. + */ + protected function configure() + { + $sortDescription = "Define sorting order when displaying diff. Available options are : \n"; + $sortDescription .= "- c : Sort by count\n"; + $sortDescription .= "- s : Sort by size\n"; + $sortDescription .= "- dc : Sort by the count differene\n"; + $sortDescription .= "- ds : Sort by the size difference\n"; + + $this + ->setName('diff') + ->setDescription('Compare 2 dump files') + ->addArgument( + 'first-file', + InputArgument::REQUIRED, + 'PHP Meminfo Dump File in JSON format' + ) + ->addArgument( + 'second-file', + InputArgument::REQUIRED, + 'PHP Meminfo Dump File in JSON format to compare first file with' + ) + ->addOption( + 'sort', + 's', + InputOption::VALUE_OPTIONAL, + $sortDescription, + self::SORT_DIFF_COUNT + ); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $firstFilename = $input->getArgument('first-file'); + $secondFilename = $input->getArgument('second-file'); + $sort = $input->getOption('sort'); + + $loader = new Loader(); + + $firstItems = $loader->load($firstFilename); + $secondItems = $loader->load($secondFilename); + + $firstSummary = new SummaryCreator($firstItems); + $secondSummary = new SummaryCreator($secondItems); + + $firstSummary = $firstSummary->createSummary(); + $secondSummary = $secondSummary->createSummary(); + + $table = new Table($output); + $this->formatTable($firstSummary, $secondSummary, $table, $sort); + + $table->render(); + + return 0; + } + + /** + * Format data into a detailed table. + * + * @param array $firstSummary + * @param array $secondSummary + * @param Table $table + * @param string $sort + */ + protected function formatTable(array $firstSummary, array $secondSummary, Table $table, $sort) + { + $table->setHeaders(['Type', 'First Instances Count', 'First Cumulated Self Size (bytes)', 'Second Instances Diff', 'Second Size Diff (bytes)']); + + $rows = []; + foreach($secondSummary as $type => $stats) { + $countDiff = $stats['count']; + $sizeDiff = $stats['self_size']; + + if (isset($firstSummary[$type])) { + $countDiff = $stats['count'] - $firstSummary[$type]['count']; + $sizeDiff = $stats['self_size'] - $firstSummary[$type]['self_size']; + } + + $rows[] = [$type, $stats['count'], $stats['self_size'], $this->formatDiffValue($countDiff), $this->formatDiffValue($sizeDiff)]; + } + + // Let's not forget all elements completely removed from memory. + foreach ($firstSummary as $type => $stats) { + if (!isset($secondSummary[$type])) { + $countDiff = -$stats['count']; + $sizeDiff = -$stats['self_size']; + $rows[] = [$type, $stats['count'], $stats['self_size'], $this->formatDiffValue($countDiff), $this->formatDiffValue($sizeDiff)]; + } + } + + $this->sortTable($rows, $sort); + $table->setRows($rows); + } + + /** + * Sort data for display. + * + * @param array $rows + * @param $sort + */ + protected function sortTable(array &$rows, $sort) + { + if (!isset($this->sorts[$sort])) { + throw new InvalidArgumentException("'$sort' is not a know sort parameter, see help for possible sort options"); + } + + if ($this->sorts[$sort] < 0) { + return; + } + + $sortIndex = $this->sorts[$sort]; + + usort($rows, function($item1, $item2) use ($sortIndex) { + return abs(str_replace('+', '', $item1[$sortIndex])) <= abs(str_replace('+', '', $item2[$sortIndex])); + }); + } + + /** + * Format diff value for display + * + * @param int $value + * + * @return string + */ + protected function formatDiffValue($value) + { + if ($value > 0) { + return '+' . $value; + } + } +} \ No newline at end of file