Skip to content
This repository has been archived by the owner on Oct 6, 2023. It is now read-only.

Convert XHProf to Cachegrind for PHPStorm usage #92

Open
wants to merge 3 commits 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
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,31 @@ file_put_contents(
);
```

## Rendering with PHPStorm

PHPStorm can render XHProf data when you convert it to Cachegrind first. We
provide a simple converter class that can do this in one step for you:

```php
<?php

tideways_xhprof_enable(TIDEWAYS_XHPROF_FLAGS_MEMORY | TIDEWAYS_XHPROF_FLAGS_CPU);

my_application();

require_once 'support/src/CachegrindConverter.php'; // copy file to your project
file_put_contents(
sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'cachegrind.' . uniqid() . '.out',
(new \Tideways\Xhprof\CachegrindConverter)->convertToCachegrind(tideways_xhprof_disable())
);
```

Then open up PHPStorm "Tools" => "Analyze XDebug Profiler Snapshot..." and open
the generated file.

Notice: The calls counter will not show the real call count and shows 1 for
every call in this view instead.

## Data-Format

The XHProf data format records performance data for each parent => child
Expand Down
2 changes: 2 additions & 0 deletions support/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/phpunit
/vendor
7 changes: 7 additions & 0 deletions support/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"autoload": {
"psr-4": {
"Tideways\\Xhprof\\": "src/"
}
}
}
8 changes: 8 additions & 0 deletions support/phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" ?>
<phpunit bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="Tideways XHProf Support Code">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>
108 changes: 108 additions & 0 deletions support/src/CachegrindConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

namespace Tideways\Xhprof;

class CachegrindConverter
{
private $aggregations = [];
private $children = [];
private $data = [];
private $output = '';
private $renderedCalls = [];
private $num = 0;

public function convertToCachegrind(array $data)
{
$this->aggregations = [];
$this->children = [];
$this->renderedCalls = [];
$this->data = $data;
$this->num = 0;

$this->aggregations['main()'] = ['ct' => 1, 'wt' => $data['main()']['wt'], 'pmu' => $data['main()']['pmu'] ?? 0];

foreach ($data as $callPair => $callInfo) {
if ($callPair === "main()") {
continue;
}

[$parent, $child] = explode('==>', $callPair);

if (!isset($this->children[$parent])) {
$this->children[$parent] = [];
}
if (!isset($this->children[$child])) {
$this->children[$child] = [];
}
$this->children[$parent][] = $child;

if (isset($this->aggregations[$child])) {
$this->aggregations[$child]['wt'] += $callInfo['wt'] ?? 0;
$this->aggregations[$child]['pmu'] += $callInfo['pmu'] ?? 0;
} else {
$this->aggregations[$child]['wt'] = $callInfo['wt'] ?? 0;
$this->aggregations[$child]['pmu'] = $callInfo['pmu'] ?? 0;
}

if (isset($this->aggregations[$parent])) {
$this->aggregations[$parent]['wt'] -= $callInfo['wt'] ?? 0;
$this->aggregations[$parent]['pmu'] -= $callInfo['pmu'] ?? 0;
} else {
$this->aggregations[$parent]['wt'] = -1 * $callInfo['wt'] ?? 0;
$this->aggregations[$parent]['pmu'] = -1 * $callInfo['pmu'] ?? 0;
}
}

$this->output = <<<CGD
version: 1
creator: tideways_xhprof
cmd: php
part: 1
positions: line


CGD;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes the code php 7.3 minimum version


$this->output .= "events: Time Memory\n\n";

$this->renderCall('main()');

$this->data["main()"]["wt"] = $this->data["main()"]["wt"] ?? 0;
$this->data["main()"]["pmu"] = $this->data["main()"]["pmu"] ?? 0;

$this->output .= "summary: {$this->data["main()"]["wt"]} {$this->data["main()"]["pmu"]}\n";

return $this->output;
}

private function renderCall($call)
{
if (isset($this->renderedCalls[$call])) {
return;
}

$callInfo = $this->aggregations[$call];
$this->renderedCalls[$call] = true;

foreach ($this->children[$call] as $child) {
$this->renderCall($child);
}

$this->num++;
$this->aggregations[$call]['num'] = $this->num;

$this->output .= "fl=(1) php:internal\n";
$this->output .= "fn=({$this->num}) {$call}\n";
$this->output .= "1 {$callInfo['wt']} {$callInfo['pmu']}\n";

foreach ($this->children[$call] as $child) {
$callPair = $call . "==>" . $child;
$this->output .= "cfl=(1)\n";
$this->output .= "cfn=({$this->aggregations[$child]['num']}) $child\n";
$this->output .= "calls=1 0 0\n";
$this->output .= "1 {$this->data[$callPair]['wt']} {$this->data[$callPair]['pmu']}\n";
}

$this->output .= "\n";
}
}
63 changes: 63 additions & 0 deletions support/tests/phpunit/CachegrindConvertTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace Tideways\Xhprof\Tests;

use PHPUnit\Framework\TestCase;
use Tideways\Xhprof\CachegrindConverter;

class CachegrindConverterTest extends TestCase
{
public function testConvert()
{
$data = [
'main()' => ['wt' => 100, 'ct' => 1, 'pmu' => 50],
'main()==>foo' => ['wt' => 30, 'ct' => 1, 'pmu' => 50],
'main()==>bar' => ['wt' => 30, 'ct' => 1, 'pmu' => 40],
'foo==>baz' => ['wt' => 25, 'ct' => 10, 'pmu' => 10],
];

$converter = new CachegrindConverter();
$output = $converter->convertToCachegrind($data);

$this->assertEquals(<<<CGT
version: 1
creator: tideways_xhprof
cmd: php
part: 1
positions: line

events: Time Memory

fl=(1) php:internal
fn=(1) baz
1 25 10

fl=(1) php:internal
fn=(2) foo
1 5 40
cfl=(1)
cfn=(1) baz
calls=1 0 0
1 25 10

fl=(1) php:internal
fn=(3) bar
1 30 40

fl=(1) php:internal
fn=(4) main()
1 40 -40
cfl=(1)
cfn=(2) foo
calls=1 0 0
1 30 50
cfl=(1)
cfn=(3) bar
calls=1 0 0
1 30 40

summary: 100 50

CGT, $output);
}
}