Skip to content

Commit 9ce1900

Browse files
committed
initial working commit
0 parents  commit 9ce1900

File tree

4 files changed

+284
-0
lines changed

4 files changed

+284
-0
lines changed

XDebugParser.php

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
3+
class XDebugParser
4+
{
5+
6+
protected $handle;
7+
8+
protected $functions = array();
9+
10+
public function __construct($fileName)
11+
{
12+
$this->handle = fopen($fileName, 'r');
13+
if (!$this->handle) {
14+
throw new Exception("Can't open '$fileName'");
15+
}
16+
$header1 = fgets($this->handle);
17+
$header2 = fgets($this->handle);
18+
if (!preg_match('@Version: [23].*@', $header1) || !preg_match('@File format: [2-4]@', $header2)) {
19+
throw new Exception("This file is not an Xdebug trace file made with format option '1' and version 2 to 4.");
20+
}
21+
}
22+
23+
public function parse()
24+
{
25+
$c = 0;
26+
$size = fstat($this->handle);
27+
$size = $size['size'];
28+
$read = 0;
29+
30+
while (!feof($this->handle)) {
31+
$buffer = fgets($this->handle, 4096);
32+
$read += strlen($buffer);
33+
$this->parseLine($buffer);
34+
$c++;
35+
}
36+
}
37+
38+
function parseLine($line)
39+
{
40+
$parts = explode("\t", $line);
41+
if (count($parts) < 5) {
42+
return;
43+
}
44+
45+
$funcNr = (int) $parts[1];
46+
$type = $parts[2];
47+
48+
switch ($type) {
49+
case '0': // Function enter
50+
$this->functions[$funcNr] = array();
51+
$this->functions[$funcNr]['depth'] = (int) $parts[0];
52+
$this->functions[$funcNr]['time.enter'] = $parts[3];
53+
$this->functions[$funcNr]['memory.enter'] = $parts[4];
54+
$this->functions[$funcNr]['name'] = $parts[5];
55+
$this->functions[$funcNr]['internal'] = !(bool) $parts[6];
56+
$this->functions[$funcNr]['file'] = $parts[8];
57+
$this->functions[$funcNr]['line'] = $parts[9];
58+
if($parts[7]) {
59+
$this->functions[$funcNr]['params'] = array($parts[7]);
60+
} else {
61+
$this->functions[$funcNr]['params'] = array_slice($parts, 11);
62+
}
63+
64+
// these are set later
65+
$this->functions[$funcNr]['time.exit'] = '';
66+
$this->functions[$funcNr]['memory.exit'] = '';
67+
$this->functions[$funcNr]['time.diff'] = '';
68+
$this->functions[$funcNr]['memory.diff'] = '';
69+
$this->functions[$funcNr]['return'] = '';
70+
break;
71+
case '1': // Function exit
72+
$this->functions[$funcNr]['time.exit'] = $parts[3];
73+
$this->functions[$funcNr]['memory.exit'] = $parts[4];
74+
$this->functions[$funcNr]['time.diff'] = $this->functions[$funcNr]['time.exit'] - $this->functions[$funcNr]['time.enter'];
75+
$this->functions[$funcNr]['memory.diff'] = $this->functions[$funcNr]['memory.exit'] - $this->functions[$funcNr]['memory.enter'];
76+
break;
77+
case 'R'; // Function return
78+
$this->functions[$funcNr]['return'] = $parts[5];
79+
break;
80+
}
81+
}
82+
83+
function getTrace()
84+
{
85+
return $this->functions;
86+
}
87+
88+
function getTraceHTML()
89+
{
90+
ob_start();
91+
92+
echo '<div class="f header">';
93+
echo '<div class="func">Function Call</div>';
94+
echo '<div class="data">';
95+
echo '<span class="timediff">ΔTime</span>';
96+
echo '<span class="memorydiff">ΔMemory</span>';
97+
echo '<span class="time">Time</span>';
98+
echo '</div>';
99+
echo '</div>';
100+
101+
102+
$level = 0;
103+
foreach ($this->functions as $func) {
104+
// depth wrapper
105+
if ($func['depth'] > $level) {
106+
for ($i = $level; $i < $func['depth']; $i++) {
107+
echo '<div class="d">';
108+
}
109+
} elseif ($func['depth'] < $level) {
110+
for ($i = $func['depth']; $i < $level; $i++) {
111+
echo '</div>';
112+
}
113+
}
114+
$level = $func['depth'];
115+
116+
$class = 'f';
117+
if ($func['internal']) {
118+
$class .= ' i';
119+
}
120+
121+
echo '<div class="' . $class . '">';
122+
123+
echo '<div class="func">';
124+
echo '<span class="name">' . htmlspecialchars($func['name']) . '</span>';
125+
echo '(<span class="params short">' . htmlspecialchars(join(",", $func['params'])) . '</span>) ';
126+
if ($func['return'] !== '') {
127+
echo '→ <span class="return short">' . htmlspecialchars($func['return']) . '</span>';
128+
}
129+
echo '</div>';
130+
131+
echo '<div class="data">';
132+
echo '<span class="timediff">' . sprintf('%f', $func['time.diff']) . '</span>';
133+
echo '<span class="memorydiff">' . sprintf('%d', $func['memory.diff']) . '</span>';
134+
echo '<span class="time">' . sprintf('%f', $func['time.enter']) . '</span>';
135+
echo '</div>';
136+
137+
echo '</div>';
138+
}
139+
140+
$html = ob_get_contents();
141+
ob_end_clean();
142+
return $html;
143+
}
144+
145+
}

index.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<html>
2+
<head>
3+
<link rel="stylesheet" href="style.css">
4+
<script src="https://code.jquery.com/jquery-2.2.1.min.js"></script>
5+
<script src="script.js"></script>
6+
</head>
7+
<body>
8+
9+
<form method="post">
10+
<label for="file">File:</label>
11+
<select name="file" id="file">
12+
<?php
13+
$dir = ini_get('xdebug.trace_output_dir');
14+
if(!$dir) $dir = '/tmp/';
15+
$files = glob("$dir/*.xt");
16+
foreach($files as $file) {
17+
echo '<option value="'.htmlspecialchars($file).'">'.htmlspecialchars(basename($file)).'</option>';
18+
}
19+
?>
20+
</select>
21+
<button type="submit">Load</button>
22+
</form>
23+
24+
25+
<form>
26+
<input type="checkbox" value="1" checked="checked" id="internal"><label for="internal">Show internal functions</label>
27+
</form>
28+
29+
30+
<?php
31+
32+
33+
if(!empty($_REQUEST['file'])) {
34+
require_once 'XDebugParser.php';
35+
$parser = new XDebugParser($_REQUEST['file']);
36+
$parser->parse();
37+
echo $parser->getTraceHTML();
38+
}
39+
?>
40+
41+
</body>
42+
</html>

script.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
$(function(){
2+
3+
4+
$('div.d').click(function (e) {
5+
if (e.target !== this) return;
6+
$(this).toggleClass('hide');
7+
e.preventDefault();
8+
e.stopPropagation();
9+
});
10+
11+
$('span.params, span.return').click(function (e) {
12+
if (e.target !== this) return;
13+
$(this).toggleClass('short');
14+
e.preventDefault();
15+
e.stopPropagation();
16+
});
17+
18+
$('#internal').change(function(){
19+
$('div.i').toggle();
20+
});
21+
});

style.css

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
div.d {
2+
padding-left: 2em;
3+
cursor: pointer;
4+
}
5+
6+
div.d.hide {
7+
height: 5px;
8+
background-color: #ccc;
9+
}
10+
11+
div.d.hide * {
12+
display: none;
13+
}
14+
15+
16+
div.f {
17+
cursor: auto;
18+
display: flex;
19+
flex-wrap: nowrap;
20+
width: 100%;
21+
}
22+
23+
div.f div {
24+
padding: 5px 0;
25+
26+
}
27+
28+
div.f div.func {
29+
flex-grow: 1;
30+
}
31+
32+
div.f div.data {
33+
flex-grow: 0;
34+
width: 450px;
35+
min-width: 450px;
36+
max-width: 450px;
37+
}
38+
39+
span {
40+
display: inline-block;
41+
line-height: 1.1em;
42+
vertical-align: bottom;
43+
}
44+
45+
span.short {
46+
max-height: 1.1em;
47+
overflow: hidden;
48+
}
49+
50+
span.name {
51+
font-weight: bold;
52+
}
53+
54+
span.params {
55+
color: #468b5e;
56+
cursor: pointer;
57+
}
58+
59+
span.return {
60+
color: #8a2200;
61+
cursor: pointer;
62+
}
63+
64+
span.time,
65+
span.memorydiff,
66+
span.timediff {
67+
width: 150px;
68+
text-align: right;
69+
white-space: nowrap;
70+
}
71+
72+
div.header {
73+
width: 100%;
74+
font-weight: bold;
75+
background-color: antiquewhite;
76+
}

0 commit comments

Comments
 (0)