-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
20e6f66
commit ad13995
Showing
6 changed files
with
468 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
{ | ||
"name": "sy-records/xhprof2flamegraph", | ||
"description": "将xhprof产生的数据转为可以生成flamegraph火焰图的格式并生成火焰图", | ||
"type": "library", | ||
"keywords": [ | ||
"flamegraph", | ||
"xhprof", | ||
"tideways" | ||
], | ||
"license": "Apache-2.0", | ||
"authors": [ | ||
{ | ||
"name": "Shen Yan", | ||
"email": "52o@qq52o.cn" | ||
} | ||
], | ||
"support": { | ||
"email": "52o@qq52o.cn", | ||
"issues": "https://github.com/sy-records/xhprof2flamegraph/issues" | ||
}, | ||
"require": { | ||
"php": ">=5.4" | ||
}, | ||
"bin": [ | ||
"xhprof2flamegraph" | ||
], | ||
"autoload": { | ||
"psr-4": { | ||
"Xhprof2Flamegraph\\": "src/" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
<?php | ||
|
||
namespace Xhprof2Flamegraph\Command; | ||
|
||
use Xhprof2Flamegraph\Profile\Analyzer; | ||
use Xhprof2Flamegraph\Profile\Parser; | ||
use Xhprof2Flamegraph\Xhprof2Flamegraph; | ||
|
||
class Command | ||
{ | ||
protected $shortopts = "hf:"; | ||
protected $longopts = [ | ||
"profile:", | ||
"metrics:", | ||
"help", | ||
]; | ||
|
||
protected $opt = [ | ||
"f", | ||
"h", | ||
"help", | ||
"profile", | ||
"metrics", | ||
]; | ||
|
||
protected $metrics = ['ect', 'ewt', 'ecpu', 'emu', 'epmu']; | ||
|
||
public static function main() | ||
{ | ||
$command = new static(); | ||
|
||
$options = $command->parseOptions(); | ||
|
||
if (isset($options['profile'])) { | ||
$data = file_get_contents($options['profile']); | ||
} else { | ||
$data = trim(fgets(STDIN)); | ||
} | ||
|
||
// $data = unserialize($data); | ||
$data = json_decode($data, true); | ||
// decode error | ||
if (!$data) { | ||
throw new \Exception('xhprof profile data error, json_decode error code ' . json_last_error()); | ||
} | ||
|
||
if (!isset($options["metrics"])) { | ||
$options["metrics"] = 'ewt'; | ||
} | ||
$parser = new Parser(); | ||
$analyzer = new Analyzer(); | ||
$Xhprof2Flamegraph = new Xhprof2Flamegraph($data, $parser, $analyzer, $options["metrics"]); | ||
$Xhprof2Flamegraph->show(); | ||
} | ||
|
||
/** | ||
* @return array | ||
* @throws \Exception | ||
*/ | ||
public function parseOptions() | ||
{ | ||
$options = getopt($this->shortopts, $this->longopts); | ||
|
||
// options is empty | ||
if (!$options) { | ||
$this->showHelp(); | ||
exit(); | ||
} | ||
|
||
$result = []; | ||
foreach ($options as $option => $value) { | ||
switch ($option){ | ||
case "help": | ||
case "h": | ||
$this->showHelp(); | ||
exit(); | ||
break; | ||
case "profile": | ||
case "f": | ||
if (!file_exists($value)) { | ||
throw new \Exception("profile is not found."); | ||
} | ||
$result["profile"] = $value; | ||
break; | ||
case "metrics": | ||
if (in_array($value, $this->metrics) === false) { | ||
throw new \Exception("metrics option is given invalid value. ".$value." is given."); | ||
} | ||
$result["metrics"] = $value; | ||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
return $result; | ||
} | ||
|
||
protected function showHelp() | ||
{ | ||
echo <<<HELP | ||
usage: xhprof2flamegraph [-h, --help] [--f, --profile] [--metrics] | ||
options: | ||
-h, --help show help | ||
-f, --profile file path of xhprof profile data | ||
--metrics select target metrics (ect/ewt/ecpu/emu/epmu) | ||
HELP; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
<?php | ||
|
||
namespace Xhprof2Flamegraph\Profile; | ||
|
||
class Analyzer | ||
{ | ||
/** | ||
* @param $records | ||
* @param $indices | ||
* @return array | ||
*/ | ||
public function analyze($records, $indices) | ||
{ | ||
foreach ($records as $record) { | ||
$record->epmu = $record->pmu; | ||
$record->ecpu = $record->cpu; | ||
$record->ewt = $record->wt; | ||
$record->ect = $record->ct; | ||
$record->emu = $record->mu; | ||
} | ||
|
||
$result = []; | ||
foreach ($records as $record) { | ||
$children = $this->getChildren($record, $indices); | ||
foreach ($children as $child) { | ||
$record->ect -= $child["ct"]; | ||
$record->ewt -= $child["wt"]; | ||
$record->ecpu -= isset($child["cpu"]) ? $child["cpu"] : 0; | ||
$record->emu -= isset($child["mu"]) ? $child["mu"] : 0; | ||
$record->epmu -= isset($child["pmu"]) ? $child["pmu"] : 0; | ||
} | ||
$result[] = $record; | ||
} | ||
return $result; | ||
} | ||
|
||
|
||
/** | ||
* @param Record $record | ||
* @param $indices | ||
* @return Record[] | ||
*/ | ||
public function getChildren($record, $indices) | ||
{ | ||
$children = array(); | ||
if (!isset($indices[$record->current_function])) { | ||
return $children; | ||
} | ||
|
||
foreach ($indices[$record->current_function] as $name => $values) { | ||
$children[] = $values; | ||
} | ||
return $children; | ||
} | ||
|
||
/** | ||
* @param Record $record | ||
* @param Record[] $records | ||
* @return Record[] | ||
*/ | ||
public function getParents($record, $records) | ||
{ | ||
$parents = []; | ||
if ($record->hasParent() === false) { | ||
return $parents; | ||
} | ||
|
||
$parent = $records[$record->parent_function]; | ||
|
||
if (isset($records[$record->parent_function])) { | ||
$parents = array_merge($this->getParents($parent, $records), [$parent]); | ||
} | ||
return $parents; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
<?php | ||
|
||
namespace Xhprof2Flamegraph\Profile; | ||
|
||
class Parser | ||
{ | ||
public $indices = []; | ||
public $records = []; | ||
|
||
static $xhprof_profile_keys = ['ct', 'wt', 'cpu', 'mu', 'pmu']; | ||
|
||
const XHPROF_FUNCTION_ARROW = "==>"; | ||
const TOP_LEVEL = "__TOP_LEVEL__"; | ||
|
||
/** | ||
* @param $data | ||
*/ | ||
public function parse($data) | ||
{ | ||
foreach ($data as $name => $values) { | ||
$record = static::parseProfileRecord($name, $values); | ||
|
||
if (!isset($this->records[$record->current_function])) { | ||
$this->records[$record->current_function] = $record; | ||
} else { | ||
$this->records[$record->current_function] = $this->sum($this->records[$record->current_function], $record); | ||
} | ||
|
||
if ($record->hasParent() === false) { | ||
$parent_function = self::TOP_LEVEL; | ||
} else { | ||
$parent_function = $record->parent_function; | ||
} | ||
|
||
if (!isset($this->indices[$parent_function])) { | ||
$this->indices[$parent_function] = []; | ||
} | ||
$this->indices[$parent_function][$record->current_function] = $values; | ||
|
||
} | ||
} | ||
|
||
protected static function parseFunctionName($name) | ||
{ | ||
$parsedName = explode(self::XHPROF_FUNCTION_ARROW, $name); | ||
if (count($parsedName) === 2) { | ||
return $parsedName; | ||
} elseif (count($parsedName) === 1) { | ||
return array(null, $parsedName[0]); | ||
} | ||
throw new \Exception(); | ||
} | ||
|
||
/** | ||
* @param $name | ||
* @param $values | ||
* @return Record | ||
*/ | ||
protected static function parseProfileRecord($name, $values) | ||
{ | ||
$record = new Record(); | ||
foreach (self::$xhprof_profile_keys as $key) { | ||
$record->{$key} = isset($values[$key]) ? $values[$key] : 0 ; | ||
} | ||
list($parent_function, $current_function) = static::parseFunctionName($name); | ||
$record->parent_function = $parent_function; | ||
$record->current_function = $current_function; | ||
return $record; | ||
} | ||
|
||
/** | ||
* @param $record | ||
* @param Record $new_record | ||
* @return mixed | ||
*/ | ||
protected function sum(Record $record, Record $new_record) | ||
{ | ||
foreach (self::$xhprof_profile_keys as $key) { | ||
$record->{$key} += $new_record->{$key}; | ||
} | ||
return $record; | ||
} | ||
|
||
public function getIndices() | ||
{ | ||
return $this->indices; | ||
} | ||
|
||
/** | ||
* @return Record[] | ||
*/ | ||
public function getRecords() | ||
{ | ||
return $this->records; | ||
} | ||
} |
Oops, something went wrong.