[StopWatch] Provide a cleaner API

This commit is contained in:
Victor Berchet 2012-02-06 15:51:54 +01:00
parent acd1287d02
commit acdb325067
5 changed files with 195 additions and 146 deletions

View File

@ -51,7 +51,7 @@ class TraceableEventDispatcher extends ContainerAwareEventDispatcher implements
{
switch ($eventName) {
case 'kernel.request':
$this->stopwatch->startSection();
$this->stopwatch->openSection();
break;
case 'kernel.view':
case 'kernel.response':
@ -62,7 +62,8 @@ class TraceableEventDispatcher extends ContainerAwareEventDispatcher implements
}
break;
case 'kernel.terminate':
$this->stopwatch->startSection();
$token = $event->getResponse()->headers->get('X-Debug-Token');
$this->stopwatch->openSection($token);
break;
}
@ -82,8 +83,7 @@ class TraceableEventDispatcher extends ContainerAwareEventDispatcher implements
$this->updateProfile($token);
break;
case 'kernel.terminate':
$token = $event->getResponse()->headers->get('X-Debug-Token');
$this->stopwatch->stopSection($token.'.terminate');
$this->stopwatch->stopSection($token);
$this->updateProfile($token);
break;
}
@ -265,18 +265,7 @@ class TraceableEventDispatcher extends ContainerAwareEventDispatcher implements
return;
}
$events = $this->stopwatch->getSectionEvents($token);
$origin = $events['__section__']->getOrigin();
foreach ($this->stopwatch->getSectionEvents($token.'.terminate') as $name => $event) {
if (isset($events[$name])) {
$events[$name]->merge($event);
} else {
$events[$name] = $event->setOrigin($origin);
}
}
$profile->getCollector('time')->setEvents($events);
$profile->getCollector('time')->setEvents($this->stopwatch->getSectionEvents($token));
$profiler->saveProfile($profile);
// children

View File

@ -18,24 +18,33 @@ namespace Symfony\Component\HttpKernel\Debug;
*/
class Stopwatch
{
private $waiting;
private $sections;
private $events;
private $origin;
private $activeSections;
public function __construct()
{
$this->sections = $this->activeSections = array('__root__' => new Section('__root__'));
}
/**
* Starts a new section.
* Creates a new section or re-opens an existing section.
*
* @param string|null $id The id of the session to re-open, null to create a new one
*
* @throws \LogicException When the section to re-open is not reachable
*/
public function startSection()
public function openSection($id = null)
{
if ($this->events) {
$this->start('__section__.child', 'section');
$this->waiting[] = array($this->events, $this->origin);
$this->events = array();
$current = end($this->activeSections);
if (null !== $id) {
if (false === $current->get($id)) {
throw new \LogicException(sprintf('The section "%s" has been started at an other level and can not be opened.', $id));
}
}
$this->origin = microtime(true) * 1000;
$this->start('__section__.child', 'section');
$this->activeSections[] = $current->open($id);
$this->start('__section__');
}
@ -52,17 +61,12 @@ class Stopwatch
{
$this->stop('__section__');
if (null !== $id) {
$this->sections[$id] = $this->events;
if (1 == count($this->activeSections)) {
throw new \LogicException('There is no started section to stop.');
}
if ($this->waiting) {
list($this->events, $this->origin) = array_pop($this->waiting);
$this->stop('__section__.child');
} else {
$this->origin = null;
$this->events = array();
}
$this->sections[$id] = array_pop($this->activeSections)->setId($id);
$this->stop('__section__.child');
}
/**
@ -74,6 +78,129 @@ class Stopwatch
* @return StopwatchEvent A StopwatchEvent instance
*/
public function start($name, $category = null)
{
return end($this->activeSections)->startEvent($name, $category);
}
/**
* Stops an event.
*
* @param string $name The event name
*
* @return StopwatchEvent A StopwatchEvent instance
*/
public function stop($name)
{
return end($this->activeSections)->stopEvent($name);
}
/**
* Stops then restarts an event.
*
* @param string $name The event name
*
* @return Symfony\Component\HttpKernel\Debug\StopwatchEvent A StopwatchEvent instance
*/
public function lap($name)
{
return end($this->activeSections)->stopEvent($name)->start();
}
/**
* Gets all events for a given section.
*
* @param string $id A section identifier
*
* @return Symfony\Component\HttpKernel\Debug\StopwatchEvent[] An array of StopwatchEvent instances
*/
public function getSectionEvents($id)
{
return isset($this->sections[$id]) ? $this->sections[$id]->getEvents() : array();
}
}
class Section
{
private $events = array();
private $origin;
private $id;
private $children = array();
/**
* Constructor.
*
* @param float|null $origin Set the origin of the events in this section, use null to set their origin to their start time
*/
public function __construct($origin = null)
{
$this->origin = is_numeric($origin) ? $origin : null;
}
/**
* Returns the child section.
*
* @param string $id The child section identifier
*
* @return Section|false The child section or false when none found
*/
public function get($id)
{
foreach ($this->children as $child) {
if ($id === $child->getId()) {
return $child;
}
}
return false;
}
/**
* Creates or re-opens a child section.
*
* @param string|null $id null to create a new section, the identifier to re-open an existing one.
*
* @return Section A child section
*/
public function open($id)
{
if (false === $session = $this->get($id)) {
$session = $this->children[] = new self(microtime(true) * 1000);
}
return $session;
}
/**
* @return string The identifier of the section
*/
public function getId()
{
return $this->id;
}
/**
* Sets the session identifier.
*
* @param string $id The session identifier
*
* @return Section The current section
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* Starts an event.
*
* @param string $name The event name
* @param string $category The event category
*
* @return Symfony\Component\HttpKernel\Debug\StopwatchEvent The event
*/
public function startEvent($name, $category)
{
if (!isset($this->events[$name])) {
$this->events[$name] = new StopwatchEvent($this->origin ?: microtime(true) * 1000, $category);
@ -87,9 +214,11 @@ class Stopwatch
*
* @param string $name The event name
*
* @return StopwatchEvent A StopwatchEvent instance
* @return Symfony\Component\HttpKernel\Debug\StopwatchEvent The event
*
* @throws \LogicException When the event has not been started
*/
public function stop($name)
public function stopEvent($name)
{
if (!isset($this->events[$name])) {
throw new \LogicException(sprintf('Event "%s" is not started.', $name));
@ -99,11 +228,13 @@ class Stopwatch
}
/**
* Stops then restart an event.
* Stops then restarts an event.
*
* @param string $name The event name
*
* @return StopwatchEvent A StopwatchEvent instance
* @return Symfony\Component\HttpKernel\Debug\StopwatchEvent The event
*
* @throws \LogicException When the event has not been started
*/
public function lap($name)
{
@ -111,14 +242,12 @@ class Stopwatch
}
/**
* Gets all events for a given section.
* Returns the events from this section.
*
* @param string $id A section identifier
*
* @return StopwatchEvent[] An array of StopwatchEvent instances
* @return Symfony\Component\HttpKernel\Debug\StopwatchEvent[] An array of StopwatchEvent instances
*/
public function getSectionEvents($id)
public function getEvents()
{
return isset($this->sections[$id]) ? $this->sections[$id] : array();
return $this->events;
}
}
}

View File

@ -59,47 +59,6 @@ class StopwatchEvent
return $this->origin;
}
/**
* Updates the origin.
*
* @param float $origin The origin time in milliseconds
*
* @return StopwatchEvent The event
*
* @throws \InvalidArgumentException When the raw time is not valid
*/
public function setOrigin($origin)
{
$origin = $this->formatTime($origin);
$delta = $this->origin - $origin;
$this->origin = $origin;
foreach ($this->started as $i => $time) {
$this->started[$i] = $this->formatTime($time + $delta);
}
foreach ($this->periods as $i => $period) {
$this->periods[$i] = array(
$this->formatTime($period[0] + $delta),
$this->formatTime($period[1] + $delta)
);
}
return $this;
}
/**
* Merges two events.
*
* @param StopWatchEvent $event The event to merge
*
* @return StopwatchEvent The event
*/
public function merge(StopWatchEvent $event)
{
$this->periods = array_merge($this->periods, $event->setOrigin($this->origin)->getPeriods());
return $this;
}
/**
* Starts a new event period.
*

View File

@ -149,59 +149,4 @@ class StopwatchEventTest extends \PHPUnit_Framework_TestCase
{
new StopwatchEvent("abc");
}
public function testSetOrigin()
{
$event = $this
->getMockBuilder('Symfony\\Component\\HttpKernel\\Debug\\StopwatchEvent')
->setMethods(array('getNow'))
->setConstructorArgs(array(0))
->getMock()
;
$event
->expects($this->exactly(4))
->method('getNow')
->will($this->onConsecutiveCalls(10, 20, 30, 40))
;
$this->assertEquals(
array(array(0, 10), array(20, 40)),
$event->start()->stop()->start()->setOrigin(10)->stop()->getPeriods()
);
}
public function testMerge()
{
$e1 = $this
->getMockBuilder('Symfony\\Component\\HttpKernel\\Debug\\StopwatchEvent')
->setMethods(array('getNow'))
->setConstructorArgs(array(0))
->getMock()
;
$e1
->expects($this->exactly(2))
->method('getNow')
->will($this->onConsecutiveCalls(0, 10))
;
$e2 = $this
->getMockBuilder('Symfony\\Component\\HttpKernel\\Debug\\StopwatchEvent')
->setMethods(array('getNow'))
->setConstructorArgs(array(10))
->getMock()
;
$e2
->expects($this->exactly(2))
->method('getNow')
->will($this->onConsecutiveCalls(50, 60))
;
$this->assertEquals(
array(array(0, 10), array(60, 70)),
$e1->start()->stop()->merge($e2->start()->stop())->getPeriods()
);
}
}

View File

@ -68,19 +68,19 @@ class StopwatchTest extends \PHPUnit_Framework_TestCase
{
$stopwatch = new Stopwatch();
$stopwatch->startSection();
$stopwatch->openSection();
$stopwatch->start('foo', 'cat');
$stopwatch->stop('foo');
$stopwatch->start('bar', 'cat');
$stopwatch->stop('bar');
$stopwatch->stopSection('1');
$stopwatch->startSection();
$stopwatch->openSection();
$stopwatch->start('foobar', 'cat');
$stopwatch->stop('foobar');
$stopwatch->stopSection('2');
$stopwatch->startSection();
$stopwatch->openSection();
$stopwatch->start('foobar', 'cat');
$stopwatch->stop('foobar');
$stopwatch->stopSection('0');
@ -91,4 +91,31 @@ class StopwatchTest extends \PHPUnit_Framework_TestCase
$this->assertCount(2, $stopwatch->getSectionEvents('2'));
$this->assertCount(2, $stopwatch->getSectionEvents('0'));
}
public function testReopenASection()
{
$stopwatch = new Stopwatch();
$stopwatch->openSection();
$stopwatch->start('foo', 'cat');
$stopwatch->stopSection('section');
$stopwatch->openSection('section');
$stopwatch->start('bar', 'cat');
$stopwatch->stopSection('section');
$events = $stopwatch->getSectionEvents('section');
$this->assertCount(3, $events);
$this->assertCount(2, $events['__section__']->getPeriods());
}
/**
* @expectedException \LogicException
*/
public function testReopenANewSectionShouldThrowAnException()
{
$stopwatch = new Stopwatch();
$stopwatch->openSection('section');
}
}