Skip to content

Added a diff command to compare 2 memory dumps #56

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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] [--] <first-file> <second-file>

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
-----------------------------------------------------------------

Expand Down
2 changes: 2 additions & 0 deletions analyzer/src/BitOne/PhpMemInfo/Console/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -21,5 +22,6 @@ public function __construct()
$this->add(new QueryCommand());
$this->add(new ReferencePathCommand());
$this->add(new SummaryCommand());
$this->add(new DiffCommand());
}
}
172 changes: 172 additions & 0 deletions analyzer/src/BitOne/PhpMemInfo/Console/Command/DiffCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
<?php

namespace BitOne\PhpMemInfo\Console\Command;

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;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;


/**
* Class DiffCommand
*
* @author oliver de Cramer <[email protected]>
* @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;
}
}
}