From e8a63a82185b52b6c49f0aa5aa9b1d773d6ba9e2 Mon Sep 17 00:00:00 2001 From: oliverde8 Date: Sat, 6 Jan 2018 14:17:41 +0100 Subject: [PATCH 1/5] Added a diff command to compare 2 memory dumps --- README.md | 36 ++++ .../BitOne/PhpMemInfo/Console/Application.php | 3 +- .../Console/Command/DiffCommand.php | 172 ++++++++++++++++++ 3 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 analyzer/src/BitOne/PhpMemInfo/Console/Command/DiffCommand.php diff --git a/README.md b/README.md index b15942e..5c26e5c 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,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 6c9bcb4..ae3021f 100644 --- a/analyzer/src/BitOne/PhpMemInfo/Console/Application.php +++ b/analyzer/src/BitOne/PhpMemInfo/Console/Application.php @@ -5,8 +5,8 @@ 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 BitOne\PhpMemInfo\Console\Command\TopChildrenCommand; -use BitOne\PhpMemInfo\Console\Command\TopSizeCommand; use Symfony\Component\Console\Application as BaseApplication; /** @@ -23,6 +23,7 @@ public function __construct() $this->add(new QueryCommand()); $this->add(new ReferencePathCommand()); $this->add(new SummaryCommand()); + $this->add(new DiffCommand()); $this->add(new TopChildrenCommand()); } } 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 From f8bd22587d832e73beb28d77afff3357c3aa8966 Mon Sep 17 00:00:00 2001 From: Axel Ducret Date: Sun, 7 Jul 2019 11:02:56 +0200 Subject: [PATCH 2/5] Splitting the command and various tweaks --- .../PhpMemInfo/Analyzer/SummaryDiffer.php | 104 ++++++++++++++++++ .../Console/Command/DiffCommand.php | 68 ++++-------- 2 files changed, 126 insertions(+), 46 deletions(-) create mode 100644 analyzer/src/BitOne/PhpMemInfo/Analyzer/SummaryDiffer.php diff --git a/analyzer/src/BitOne/PhpMemInfo/Analyzer/SummaryDiffer.php b/analyzer/src/BitOne/PhpMemInfo/Analyzer/SummaryDiffer.php new file mode 100644 index 0000000..cb2f43a --- /dev/null +++ b/analyzer/src/BitOne/PhpMemInfo/Analyzer/SummaryDiffer.php @@ -0,0 +1,104 @@ + + * @author Axel Ducret + * @license http://opensource.org/licenses/MIT MIT + */ +class SummaryDiffer +{ + /** @var array */ + protected $firstItems; + + /** @var array */ + protected $secondItems; + + /** + * @param array $firstItems + * @param array $secondItems + */ + public function __construct(array $firstItems, array $secondItems) + { + $this->firstItems = $firstItems; + $this->secondItems = $secondItems; + } + + /** + * @return array + */ + public function generateDiff() + { + $firstSummary = $this->getSummaryForItems($this->firstItems); + $secondSummary = $this->getSummaryForItems($this->secondItems); + $rows = []; + + foreach($secondSummary as $type => $stats) { + $firstSummaryCount = 0; + $firstSummarySize = 0; + + if (isset($firstSummary[$type])) { + $firstSummaryCount = $firstSummary[$type]['count']; + $firstSummarySize = $firstSummary[$type]['self_size']; + unset($firstSummary[$type]); + } + + $countDiff = $stats['count'] - $firstSummaryCount; + $sizeDiff = $stats['self_size'] - $firstSummarySize; + + $rows[] = [ + $type, + $firstSummaryCount, + $firstSummarySize, + $this->formatDiffValue($countDiff), + $this->formatDiffValue($sizeDiff) + ]; + } + + // Let's not forget all elements completely removed from memory. + foreach ($firstSummary as $type => $stats) { + $countDiff = -$stats['count']; + $sizeDiff = -$stats['self_size']; + + $rows[] = [ + $type, + $stats['count'], + $stats['self_size'], + $this->formatDiffValue($countDiff), + $this->formatDiffValue($sizeDiff) + ]; + } + + return $rows; + } + + /** + * @param array $items + * @return array + */ + protected function getSummaryForItems(array $items) + { + $summaryCreator = new SummaryCreator($items); + + return $summaryCreator->createSummary(); + } + + /** + * Format diff value for display + * + * @param int $value + * + * @return string + */ + protected function formatDiffValue($value) + { + if ($value > 0) { + return '+' . $value; + } + + return strval($value); + } +} diff --git a/analyzer/src/BitOne/PhpMemInfo/Console/Command/DiffCommand.php b/analyzer/src/BitOne/PhpMemInfo/Console/Command/DiffCommand.php index 26979bf..c610828 100644 --- a/analyzer/src/BitOne/PhpMemInfo/Console/Command/DiffCommand.php +++ b/analyzer/src/BitOne/PhpMemInfo/Console/Command/DiffCommand.php @@ -2,8 +2,8 @@ namespace BitOne\PhpMemInfo\Console\Command; +use BitOne\PhpMemInfo\Analyzer\SummaryDiffer; use BitOne\PhpMemInfo\Loader; -use BitOne\PhpMemInfo\Analyzer\SummaryCreator; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Helper\Table; @@ -14,9 +14,10 @@ /** - * Class DiffCommand + * Command to list the differences between two memory dumps. * * @author oliver de Cramer + * @author Axel Ducret * @license http://opensource.org/licenses/MIT MIT */ class DiffCommand extends Command @@ -64,6 +65,12 @@ protected function configure() InputOption::VALUE_OPTIONAL, $sortDescription, self::SORT_DIFF_COUNT + ) + ->addOption( + 'only-diff', + 'd', + InputOption::VALUE_NONE, + 'If set, only the rows with actual differences will be showed' ); } @@ -75,20 +82,18 @@ protected function execute(InputInterface $input, OutputInterface $output) $firstFilename = $input->getArgument('first-file'); $secondFilename = $input->getArgument('second-file'); $sort = $input->getOption('sort'); + $onlyDiff = $input->getOption('only-diff'); $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(); + $summaryDiffer = new SummaryDiffer($firstItems, $secondItems); + $rows = $summaryDiffer->generateDiff(); $table = new Table($output); - $this->formatTable($firstSummary, $secondSummary, $table, $sort); + $this->formatTable($rows, $table, $sort, $onlyDiff); $table->render(); @@ -98,38 +103,23 @@ protected function execute(InputInterface $input, OutputInterface $output) /** * Format data into a detailed table. * - * @param array $firstSummary - * @param array $secondSummary + * @param array $rows * @param Table $table * @param string $sort + * @param bool $onlyDiff */ - protected function formatTable(array $firstSummary, array $secondSummary, Table $table, $sort) + protected function formatTable(array $rows, Table $table, $sort, $onlyDiff) { $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)]; - } + if ($onlyDiff) { + $rows = array_filter($rows, function ($row) { + return $row[3] !== '0'; + }); } $this->sortTable($rows, $sort); + $table->setRows($rows); } @@ -152,21 +142,7 @@ protected function sortTable(array &$rows, $sort) $sortIndex = $this->sorts[$sort]; usort($rows, function($item1, $item2) use ($sortIndex) { - return abs(str_replace('+', '', $item1[$sortIndex])) <= abs(str_replace('+', '', $item2[$sortIndex])); + return str_replace('+', '', $item1[$sortIndex]) <= 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 From 2a7649d2a2dfa7dd8c8f129bf2936eb71ee9d21f Mon Sep 17 00:00:00 2001 From: Axel Ducret Date: Sun, 7 Jul 2019 11:03:09 +0200 Subject: [PATCH 3/5] Adding spec --- .../PhpMemInfo/Analyzer/SummaryDifferSpec.php | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 analyzer/spec/BitOne/PhpMemInfo/Analyzer/SummaryDifferSpec.php diff --git a/analyzer/spec/BitOne/PhpMemInfo/Analyzer/SummaryDifferSpec.php b/analyzer/spec/BitOne/PhpMemInfo/Analyzer/SummaryDifferSpec.php new file mode 100644 index 0000000..6eb7c33 --- /dev/null +++ b/analyzer/spec/BitOne/PhpMemInfo/Analyzer/SummaryDifferSpec.php @@ -0,0 +1,110 @@ +beConstructedWith([], []); + $this->shouldHaveType('BitOne\PhpMemInfo\Analyzer\SummaryDiffer'); + } + + public function it_generates_diff() + { + $firstItems = [ + "0x7fb321a94050" => [ + "type" => "string", + "size" => "29" + ], + "0x7fb321a94080" => [ + "type" => "string", + "size" => "29" + ], + "0x7fb321a94108" => [ + "type" => "string", + "size" => "37" + ], + "0x7fe321a94108" => [ + "type" => "string", + "size" => "45" + ], + "0x7fb321a941e0" => [ + "type" => "integer", + "size" => "24" + ], + "0x7fb321a94268" => [ + "type" => "integer", + "size" => "24" + ], + "0x7fc321a94050" => [ + "type" => "object", + "class" => "MyClassA", + "size" => "56" + ], + "0x7fc321a94080" => [ + "type" => "object", + "class" => "MyClassA", + "size" => "56" + ], + "0x7fc321a94378" => [ + "type" => "object", + "class" => "MyClassB", + "size" => "56" + ] + ]; + + $secondItems = [ + "0x7fd04f5d4bb8" => [ + "type" => "string", + "size" => "24" + ], + "0x7fc321a94050" => [ + "type" => "object", + "class" => "MyClassA", + "size" => "56" + ], + "0x7fc321a94080" => [ + "type" => "object", + "class" => "MyClassA", + "size" => "56" + ], + "0x7fd04f5d4bc8" => [ + "type" => "object", + "class" => "MyClassA", + "size" => "26", + ], + "0x7fd04f5d4bd8" => [ + "type" => "object", + "class" => "MyClassA", + "size" => "16", + ], + "0x7fb321a941e0" => [ + "type" => "integer", + "size" => "24" + ], + "0x7fb321a94268" => [ + "type" => "integer", + "size" => "24" + ], + "0x7fd04f5d4bf8" => [ + "type" => "object", + "class" => "MyClassC", + "size" => "56" + ] + ]; + + $this->beConstructedWith($firstItems, $secondItems); + + $this->generateDiff()->shouldReturn([ + ['MyClassA', 2, 112, '+2', '+42'], + ['integer', 2, 48, '0', '0'], + ['MyClassC', 0, 0, '+1', '+56'], + ['string', 4, 140, '-3', '-116'], + ['MyClassB', 1, 56, '-1', '-56'], + ]); + } +} From 6128f6cea0c44946405ba263dc8ee25262eba97a Mon Sep 17 00:00:00 2001 From: Axel Ducret Date: Sun, 7 Jul 2019 11:12:21 +0200 Subject: [PATCH 4/5] Updating readme --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5c26e5c..fe090b0 100644 --- a/README.md +++ b/README.md @@ -235,11 +235,12 @@ Options: - s : Sort by size - dc : Sort by the count differene - ds : Sort by the size difference + -d, --only-diff If set, objects for which count is the same in both dumps will not be showed ``` -### Exemple +### Example -Fallowing exemple displays the diff sorting them by difference in size. +Following example 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 From 51087ce76b4408d2704f2e4b1526573ee365bc6d Mon Sep 17 00:00:00 2001 From: Axel Ducret Date: Sun, 7 Jul 2019 11:57:41 +0200 Subject: [PATCH 5/5] Correcting spec for php < 7 --- .../BitOne/PhpMemInfo/Analyzer/SummaryDifferSpec.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/analyzer/spec/BitOne/PhpMemInfo/Analyzer/SummaryDifferSpec.php b/analyzer/spec/BitOne/PhpMemInfo/Analyzer/SummaryDifferSpec.php index 6eb7c33..4f0a456 100644 --- a/analyzer/spec/BitOne/PhpMemInfo/Analyzer/SummaryDifferSpec.php +++ b/analyzer/spec/BitOne/PhpMemInfo/Analyzer/SummaryDifferSpec.php @@ -94,6 +94,16 @@ public function it_generates_diff() "type" => "object", "class" => "MyClassC", "size" => "56" + ], + "0x7fd04f5d4bf9" => [ + "type" => "object", + "class" => "MyClassC", + "size" => "56" + ], + "0x7fd04f5d4bfa" => [ + "type" => "object", + "class" => "MyClassC", + "size" => "56" ] ]; @@ -101,8 +111,8 @@ public function it_generates_diff() $this->generateDiff()->shouldReturn([ ['MyClassA', 2, 112, '+2', '+42'], + ['MyClassC', 0, 0, '+3', '+168'], ['integer', 2, 48, '0', '0'], - ['MyClassC', 0, 0, '+1', '+56'], ['string', 4, 140, '-3', '-116'], ['MyClassB', 1, 56, '-1', '-56'], ]);