merged branch vicb/profiler (PR #3190)

Commits
-------

b879397 [Profiler] Optimize time panel IS
d4300b9 [WebProfilerBundle] Tweak the time view
416a2a4 [Stopwatch] Fix some logic
8c3505e [Profiler] Tweak PHPDoc
3bcd154 [HttpKernel] Tweak the Profile class - DRY

Discussion
----------

[Profiler] Stopwatch related tweaks

* Some fixes in the stopwatch logic,
* Some JS fixes,
* Make use of modern JS.
This commit is contained in:
Fabien Potencier 2012-01-28 15:16:32 +01:00
commit a72bf897d3
7 changed files with 148 additions and 146 deletions

View File

@ -48,7 +48,7 @@
</tr> </tr>
<tr> <tr>
<th>Threshold</th> <th>Threshold</th>
<td><input type="number" size="3" name="threshold" value="{{ threshold }}" /> ms</td> <td><input type="number" size="3" name="threshold" value="{{ threshold }}" min="0" /> ms</td>
</tr> </tr>
<tr> <tr>
<th>Width</th> <th>Width</th>
@ -87,174 +87,153 @@
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<script type="text/javascript"> <script type="text/javascript">//<![CDATA[
function drawCanvas(request, max, threshold, width) { function drawCanvas(request, max, threshold, width) {
var colors = { "use strict";
{% for name, color in colors %} var text,
"{{ name }}": "{{ color }}"{{ loop.last ? '' : ', ' }} ms,
{% endfor %} xc,
}; mainEvents,
var space = 10.5; colors = {{ colors|json_encode|raw }},
var ratio = (width - space * 2) / max; space = 10.5,
var height = 0; ratio = (width - space * 2) / max,
for (i = 0; i < request.events.length; i++) { height = 0,
if (request.events[i].totaltime < threshold) { h = space,
continue; x = request.left * ratio + space,
} canvas = document.getElementById('timeline_' + request.id),
ctx = canvas.getContext("2d");
height = height + 38; request.events = request.events.filter(function(event) {
} return event.totaltime >= threshold;
var h = space; });
var x = request.left * ratio + space;
var canvas = document.getElementById('timeline_' + request.id); height += 38 * request.events.length;
canvas.width = width; canvas.width = width;
ctx.textBaseline = "middle";
ctx.lineWidth = 0;
var context = canvas.getContext("2d"); request.events.forEach(function(event) {
context.textBaseline = "middle"; event.periods.forEach(function(period) {
context.lineWidth = 0; if ('section.child' === event.name) {
ctx.fillStyle = colors.child_sections;
for (i = 0; i < request.events.length; i++) { ctx.fillRect(x + period.begin * ratio, 0, (period.end - period.begin) * ratio, height);
var event = request.events[i]; } else if ('section' === event.category) {
ctx.beginPath();
if (event.totaltime < threshold) { ctx.strokeStyle = "#dfdfdf";
continue; ctx.moveTo(x + period.begin * ratio, 0);
} ctx.lineTo(x + period.begin * ratio, height);
ctx.moveTo(x + period.end * ratio, 0);
for (j = 0; j < event.periods.length; j++) { ctx.lineTo(x + period.end * ratio, height);
var period = event.periods[j]; ctx.fill();
ctx.closePath();
if ('section.child' == event.name) { ctx.stroke();
context.fillStyle = colors.child_sections;
context.fillRect(x + period.begin * ratio, 0, (period.end - period.begin) * ratio, height);
} else if ('section' == event.category) {
context.beginPath();
context.strokeStyle = "#dfdfdf";
context.moveTo(x + period.begin * ratio, 0);
context.lineTo(x + period.begin * ratio, height);
context.moveTo(x + period.end * ratio, 0);
context.lineTo(x + period.end * ratio, height);
context.fill();
context.closePath();
context.stroke();
} }
} });
} });
for (i = 0; i < request.events.length; i++) { mainEvents = request.events.filter(function(event) {
var event = request.events[i]; return 'section.child' !== event.name;
});
if (event.totaltime < threshold || 'section.child' == event.name) { mainEvents.forEach(function(event) {
continue;
}
h += 8; h += 8;
for (j = 0; j < event.periods.length; j++) { event.periods.forEach(function(period) {
var period = event.periods[j];
if (colors[event.name]) { if (colors[event.name]) {
context.fillStyle = colors[event.name]; ctx.fillStyle = colors[event.name];
context.strokeStyle = colors[event.name]; ctx.strokeStyle = colors[event.name];
} else if (colors[event.category]) { } else if (colors[event.category]) {
context.fillStyle = colors[event.category]; ctx.fillStyle = colors[event.category];
context.strokeStyle = colors[event.category]; ctx.strokeStyle = colors[event.category];
} else { } else {
context.fillStyle = colors.default; ctx.fillStyle = colors['default'];
context.strokeStyle = colors.default; ctx.strokeStyle = colors['default'];
} }
if ('section' != event.category) { if ('section' !== event.category) {
context.fillRect(x + period.begin * ratio, h + 3, 2, 6); ctx.fillRect(x + period.begin * ratio, h + 3, 2, 6);
context.fillRect(x + period.begin * ratio, h, (period.end - period.begin) * ratio ? (period.end - period.begin) * ratio : 2, 6); ctx.fillRect(x + period.begin * ratio, h, (period.end - period.begin) * ratio || 2, 6);
} else { } else {
context.beginPath(); ctx.beginPath();
context.moveTo(x + period.begin * ratio, h); ctx.moveTo(x + period.begin * ratio, h);
context.lineTo(x + period.begin * ratio, h + 11); ctx.lineTo(x + period.begin * ratio, h + 11);
context.lineTo(x + period.begin * ratio + 8, h); ctx.lineTo(x + period.begin * ratio + 8, h);
context.lineTo(x + period.begin * ratio, h); ctx.lineTo(x + period.begin * ratio, h);
context.fill(); ctx.fill();
context.closePath(); ctx.closePath();
context.stroke(); ctx.stroke();
context.beginPath(); ctx.beginPath();
context.moveTo(x + period.end * ratio, h); ctx.moveTo(x + period.end * ratio, h);
context.lineTo(x + period.end * ratio, h + 11); ctx.lineTo(x + period.end * ratio, h + 11);
context.lineTo(x + period.end * ratio - 8, h); ctx.lineTo(x + period.end * ratio - 8, h);
context.lineTo(x + period.end * ratio, h); ctx.lineTo(x + period.end * ratio, h);
context.fill(); ctx.fill();
context.closePath(); ctx.closePath();
context.stroke(); ctx.stroke();
context.beginPath(); ctx.beginPath();
context.moveTo(x + period.begin * ratio, h); ctx.moveTo(x + period.begin * ratio, h);
context.lineTo(x + period.end * ratio, h); ctx.lineTo(x + period.end * ratio, h);
context.lineTo(x + period.end * ratio, h + 2); ctx.lineTo(x + period.end * ratio, h + 2);
context.lineTo(x + period.begin * ratio, h + 2); ctx.lineTo(x + period.begin * ratio, h + 2);
context.lineTo(x + period.begin * ratio, h); ctx.lineTo(x + period.begin * ratio, h);
context.fill(); ctx.fill();
context.closePath(); ctx.closePath();
context.stroke(); ctx.stroke();
} }
} });
h += 30; h += 30;
context.beginPath(); ctx.beginPath();
context.strokeStyle = "#dfdfdf"; ctx.strokeStyle = "#dfdfdf";
context.moveTo(0, h - 10); ctx.moveTo(0, h - 10);
context.lineTo(width, h - 10); ctx.lineTo(width, h - 10);
context.closePath(); ctx.closePath();
context.stroke(); ctx.stroke();
} });
h = space; h = space;
for (i = 0; i < request.events.length; i++) { mainEvents.forEach(function(event) {
var event = request.events[i];
if (event.totaltime < threshold || 'section.child' == event.name) { ctx.fillStyle = "#444";
continue; ctx.font = "12px sans-serif";
} text = event.name;
ms = " ~ " + (event.totaltime < 1 ? event.totaltime : parseInt(event.totaltime, 10)) + " ms";
context.fillStyle = "#444"; if (x + event.starttime * ratio + ctx.measureText(text + ms).width > width) {
context.font = "12px sans-serif"; ctx.textAlign = "end";
var text = event.name; ctx.font = "10px sans-serif";
var ms;
if (event.totaltime < 1) {
ms = " ~ " + event.totaltime + " ms";
} else {
ms = " ~ " + parseInt(event.totaltime) + " ms";
}
if (x + event.starttime * ratio + context.measureText(text + ms).width > width) {
context.textAlign = "end";
context.font = "10px sans-serif";
xc = x + event.endtime * ratio - 1; xc = x + event.endtime * ratio - 1;
context.fillText(ms, xc, h); ctx.fillText(ms, xc, h);
xc = xc - context.measureText(ms).width xc -= ctx.measureText(ms).width;
context.font = "12px sans-serif"; ctx.font = "12px sans-serif";
context.fillText(text, xc, h); ctx.fillText(text, xc, h);
} else { } else {
context.textAlign = "start"; ctx.textAlign = "start";
context.font = "12px sans-serif"; ctx.font = "12px sans-serif";
xc = x + event.starttime * ratio + 1; xc = x + event.starttime * ratio + 1;
context.fillText(text, xc, h); ctx.fillText(text, xc, h);
xc = xc + context.measureText(text).width; xc += ctx.measureText(text).width;
context.font = "10px sans-serif"; ctx.font = "10px sans-serif";
context.fillText(ms, xc, h); ctx.fillText(ms, xc, h);
} }
h += 38; h += 38;
} });
} }
function drawCanvases(width) function drawCanvases(width)
{ {
for (k = 0; k < requests_data.requests.length; k++) { "use strict";
drawCanvas(requests_data.requests[k], requests_data.max, {{ threshold }}, width); requests_data.requests.forEach(function(request) {
} drawCanvas(request, requests_data.max, {{ threshold }}, width);
});
} }
var requests_data = { var requests_data = {
@ -272,7 +251,7 @@
}; };
drawCanvases({{ width }}); drawCanvases({{ width }});
</script> //]]></script>
{% endblock %} {% endblock %}
{% macro dump_request_data(token, profile, events, origin) %} {% macro dump_request_data(token, profile, events, origin) %}

View File

@ -76,7 +76,7 @@ class Stopwatch
public function start($name, $category = null) public function start($name, $category = null)
{ {
if (!isset($this->events[$name])) { if (!isset($this->events[$name])) {
$this->events[$name] = new StopwatchEvent($this->origin, $category); $this->events[$name] = new StopwatchEvent($this->origin ?: microtime(true) * 1000, $category);
} }
return $this->events[$name]->start(); return $this->events[$name]->start();

View File

@ -28,10 +28,12 @@ class StopwatchEvent
* *
* @param integer $origin The origin time in milliseconds * @param integer $origin The origin time in milliseconds
* @param string $category The event category * @param string $category The event category
*
* @throws \InvalidArgumentException When the raw time is not valid
*/ */
public function __construct($origin, $category = null) public function __construct($origin, $category = null)
{ {
$this->origin = $origin; $this->origin = $this->formatTime($origin);
$this->category = is_string($category) ? $category : 'default'; $this->category = is_string($category) ? $category : 'default';
$this->started = array(); $this->started = array();
$this->periods = array(); $this->periods = array();
@ -132,7 +134,7 @@ class StopwatchEvent
*/ */
public function getEndTime() public function getEndTime()
{ {
return count($this->periods) ? $this->periods[count($this->periods) - 1][1] : 0; return ($count = count($this->periods)) ? $this->periods[$count - 1][1] : 0;
} }
/** /**
@ -147,11 +149,29 @@ class StopwatchEvent
$total += $period[1] - $period[0]; $total += $period[1] - $period[0];
} }
return sprintf('%.1f', $total); return $this->formatTime($total);
} }
private function getNow() private function getNow()
{ {
return sprintf('%.1f', microtime(true) * 1000 - $this->origin); return $this->formatTime(microtime(true) * 1000 - $this->origin);
}
/**
* Formats a time.
*
* @param numerical $time A raw time
*
* @return float The formatted time
*
* @throws \InvalidArgumentException When the raw time is not valid
*/
private function formatTime($time)
{
if (!is_numeric($time)) {
throw new \InvalidArgumentException('The time must be a numerical value');
}
return round($time, 1);
} }
} }

View File

@ -82,7 +82,7 @@ class MongoDbProfilerStorage implements ProfilerStorageInterface
} }
/** /**
* Write data associated with the given token. * Saves a Profile.
* *
* @param Profile $profile A Profile instance * @param Profile $profile A Profile instance
* *

View File

@ -18,7 +18,7 @@ use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
* *
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
*/ */
class Profile implements \Serializable class Profile
{ {
private $token; private $token;
private $collectors; private $collectors;
@ -212,13 +212,8 @@ class Profile implements \Serializable
return isset($this->collectors[$name]); return isset($this->collectors[$name]);
} }
public function serialize() public function __sleep()
{ {
return serialize(array($this->token, $this->parent, $this->children, $this->collectors, $this->ip, $this->method, $this->url, $this->time)); return array('token', 'parent', 'children', 'collectors', 'ip', 'method', 'url', 'time');
}
public function unserialize($data)
{
list($this->token, $this->parent, $this->children, $this->collectors, $this->ip, $this->method, $this->url, $this->time) = unserialize($data);
} }
} }

View File

@ -42,7 +42,7 @@ interface ProfilerStorageInterface
function read($token); function read($token);
/** /**
* Write data associated with the given token. * Saves a Profile.
* *
* @param Profile $profile A Profile instance * @param Profile $profile A Profile instance
* *

View File

@ -31,7 +31,7 @@ class StopwatchEventTest extends \PHPUnit_Framework_TestCase
$event = new StopwatchEvent(microtime(true) * 1000); $event = new StopwatchEvent(microtime(true) * 1000);
$this->assertEquals('default', $event->getCategory()); $this->assertEquals('default', $event->getCategory());
$event = new StopwatchEvent(time(), 'cat'); $event = new StopwatchEvent(microtime(true) * 1000, 'cat');
$this->assertEquals('cat', $event->getCategory()); $this->assertEquals('cat', $event->getCategory());
} }
@ -141,4 +141,12 @@ class StopwatchEventTest extends \PHPUnit_Framework_TestCase
$end = $event->getEndTime(); $end = $event->getEndTime();
$this->assertTrue($end >= 18 && $end <= 30); $this->assertTrue($end >= 18 && $end <= 30);
} }
/**
* @expectedException \InvalidArgumentException
*/
public function testInvalidOriginThrowsAnException()
{
new StopwatchEvent("abc");
}
} }