Skip to content

Commit

Permalink
core
Browse files Browse the repository at this point in the history
  • Loading branch information
sy-records committed May 4, 2019
1 parent 20e6f66 commit ad13995
Show file tree
Hide file tree
Showing 6 changed files with 468 additions and 0 deletions.
32 changes: 32 additions & 0 deletions composer.json
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/"
}
}
}
109 changes: 109 additions & 0 deletions src/Command/Command.php
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;
}
}
75 changes: 75 additions & 0 deletions src/Profile/Analyzer.php
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;
}
}
96 changes: 96 additions & 0 deletions src/Profile/Parser.php
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;
}
}
Loading

0 comments on commit ad13995

Please sign in to comment.