[HttpKernel] decoupled TraceableEventDispatcher and Profiler

This commit is contained in:
Fabien Potencier 2013-09-29 17:17:25 +02:00
parent 59409b47c8
commit 9c4bc9a0ed
9 changed files with 95 additions and 116 deletions

View File

@ -34,6 +34,7 @@
<service id="data_collector.events" class="%data_collector.events.class%" public="false">
<tag name="data_collector" template="@WebProfiler/Collector/events.html.twig" id="events" priority="255" />
<argument type="service" id="event_dispatcher" on-invalid="ignore" />
</service>
<service id="data_collector.logger" class="%data_collector.logger.class%" public="false">
@ -45,6 +46,7 @@
<service id="data_collector.time" class="%data_collector.time.class%" public="false">
<tag name="data_collector" template="@WebProfiler/Collector/time.html.twig" id="time" priority="255" />
<argument type="service" id="kernel" on-invalid="ignore" />
<argument type="service" id="debug.stopwatch" on-invalid="ignore" />
</service>
<service id="data_collector.memory" class="%data_collector.memory.class%" public="false">

View File

@ -19,7 +19,6 @@
<argument type="service" id="event_dispatcher" />
<argument type="service" id="debug.stopwatch" />
<argument type="service" id="logger" on-invalid="null" />
<call method="setProfiler"><argument type="service" id="profiler" on-invalid="null" /></call>
</service>
<service id="debug.controller_resolver" class="%debug.controller_resolver.class%">

View File

@ -29,6 +29,7 @@
<argument type="service" id="profiler.request_matcher" on-invalid="null" />
<argument>%profiler_listener.only_exceptions%</argument>
<argument>%profiler_listener.only_master_requests%</argument>
<argument type="service" id="request_stack" />
</service>
</services>
</container>

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface;
/**
@ -22,6 +23,13 @@ use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface;
*/
class EventDataCollector extends DataCollector
{
protected $dispatcher;
public function __construct(EventDispatcherInterface $dispatcher = null)
{
$this->dispatcher = $dispatcher;
}
/**
* {@inheritdoc}
*/
@ -81,6 +89,16 @@ class EventDataCollector extends DataCollector
return $this->data['not_called_listeners'];
}
public function serialize()
{
if ($this->dispatcher instanceof TraceableEventDispatcherInterface) {
$this->setCalledListeners($this->dispatcher->getCalledListeners());
$this->setNotCalledListeners($this->dispatcher->getNotCalledListeners());
}
return parent::serialize();
}
/**
* {@inheritdoc}
*/

View File

@ -65,6 +65,13 @@ class MemoryDataCollector extends DataCollector
$this->data['memory'] = memory_get_peak_usage(true);
}
public function serialize()
{
$this->updateMemoryUsage();
return parent::serialize();
}
/**
* {@inheritdoc}
*/

View File

@ -15,6 +15,7 @@ use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* TimeDataCollector.
@ -24,10 +25,12 @@ use Symfony\Component\HttpFoundation\Response;
class TimeDataCollector extends DataCollector
{
protected $kernel;
protected $stopwatch;
public function __construct(KernelInterface $kernel = null)
public function __construct(KernelInterface $kernel = null, $stopwatch = null)
{
$this->kernel = $kernel;
$this->stopwatch = $stopwatch;
}
/**
@ -42,6 +45,7 @@ class TimeDataCollector extends DataCollector
}
$this->data = array(
'token' => $response->headers->get('X-Debug-Token'),
'start_time' => $startTime * 1000,
'events' => array(),
);
@ -113,6 +117,16 @@ class TimeDataCollector extends DataCollector
return $this->data['start_time'];
}
public function serialize()
{
if (null !== $this->stopwatch && isset($this->data['token'])) {
$this->setEvents($this->stopwatch->getSectionEvents($this->data['token']));
}
unset($this->data['token']);
return parent::serialize();
}
/**
* {@inheritdoc}
*/

View File

@ -14,8 +14,6 @@ namespace Symfony\Component\HttpKernel\Debug;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\HttpKernel\KernelEvents;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Profiler\Profile;
use Symfony\Component\HttpKernel\Profiler\Profiler;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@ -33,7 +31,6 @@ class TraceableEventDispatcher implements EventDispatcherInterface, TraceableEve
private $logger;
private $called;
private $stopwatch;
private $profiler;
private $dispatcher;
private $wrappedListeners;
private $firstCalledEvent;
@ -59,11 +56,16 @@ class TraceableEventDispatcher implements EventDispatcherInterface, TraceableEve
/**
* Sets the profiler.
*
* The traceable event dispatcher does not use the profiler anymore.
* The job is now done directly by the Profiler listener and the
* data collectors themselves.
*
* @param Profiler|null $profiler A Profiler instance
*
* @deprecated Deprecated since version 2.4, to be removed in 3.0.
*/
public function setProfiler(Profiler $profiler = null)
{
$this->profiler = $profiler;
}
/**
@ -142,7 +144,9 @@ class TraceableEventDispatcher implements EventDispatcherInterface, TraceableEve
unset($this->firstCalledEvent[$eventName]);
$e->stop();
if ($e->isStarted()) {
$e->stop();
}
$this->postDispatch($eventName, $event);
@ -312,57 +316,6 @@ class TraceableEventDispatcher implements EventDispatcherInterface, TraceableEve
return $info;
}
/**
* Updates the stopwatch data in the profile hierarchy.
*
* @param string $token Profile token
* @param Boolean $updateChildren Whether to update the children altogether
*/
private function updateProfiles($token, $updateChildren)
{
if (!$this->profiler || !$profile = $this->profiler->loadProfile($token)) {
return;
}
$this->saveInfoInProfile($profile, $updateChildren);
}
/**
* Update the profiles with the timing and events information and saves them.
*
* @param Profile $profile The root profile
* @param Boolean $updateChildren Whether to update the children altogether
*/
private function saveInfoInProfile(Profile $profile, $updateChildren)
{
try {
$collector = $profile->getCollector('memory');
$collector->updateMemoryUsage();
} catch (\InvalidArgumentException $e) {
}
try {
$collector = $profile->getCollector('time');
$collector->setEvents($this->stopwatch->getSectionEvents($profile->getToken()));
} catch (\InvalidArgumentException $e) {
}
try {
$collector = $profile->getCollector('events');
$collector->setCalledListeners($this->getCalledListeners());
$collector->setNotCalledListeners($this->getNotCalledListeners());
} catch (\InvalidArgumentException $e) {
}
$this->profiler->saveProfile($profile);
if ($updateChildren) {
foreach ($profile->getChildren() as $child) {
$this->saveInfoInProfile($child, true);
}
}
}
private function preDispatch($eventName, Event $event)
{
// wrap all listeners before they are called
@ -411,23 +364,14 @@ class TraceableEventDispatcher implements EventDispatcherInterface, TraceableEve
case KernelEvents::RESPONSE:
$token = $event->getResponse()->headers->get('X-Debug-Token');
$this->stopwatch->stopSection($token);
if ($event->isMasterRequest()) {
// The profiles can only be updated once they have been created
// that is after the 'kernel.response' event of the main request
$this->updateProfiles($token, true);
}
break;
case KernelEvents::TERMINATE:
$token = $event->getResponse()->headers->get('X-Debug-Token');
// In the special case described in the `preDispatch` method above, the `$token` section
// does not exist, then closing it throws an exception which must be caught.
$token = $event->getResponse()->headers->get('X-Debug-Token');
try {
$this->stopwatch->stopSection($token);
} catch (\LogicException $e) {}
// The children profiles have been updated by the previous 'kernel.response'
// event. Only the root profile need to be updated with the 'kernel.terminate'
// timing informations.
$this->updateProfiles($token, false);
break;
}
@ -448,7 +392,9 @@ class TraceableEventDispatcher implements EventDispatcherInterface, TraceableEve
call_user_func($listener, $event, $eventName, $self);
$e->stop();
if ($e->isStarted()) {
$e->stop();
}
if ($event->isPropagationStopped()) {
$self->logSkippedListeners($eventName, $event, $listener);

View File

@ -11,13 +11,16 @@
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Profiler\Profile;
use Symfony\Component\HttpKernel\Profiler\Profiler;
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
@ -32,9 +35,10 @@ class ProfilerListener implements EventSubscriberInterface
protected $onlyException;
protected $onlyMasterRequests;
protected $exception;
protected $children;
protected $requests;
protected $profiles;
protected $requestStack;
protected $parents;
/**
* Constructor.
@ -44,14 +48,16 @@ class ProfilerListener implements EventSubscriberInterface
* @param Boolean $onlyException true if the profiler only collects data when an exception occurs, false otherwise
* @param Boolean $onlyMasterRequests true if the profiler only collects data when the request is a master request, false otherwise
*/
public function __construct(Profiler $profiler, RequestMatcherInterface $matcher = null, $onlyException = false, $onlyMasterRequests = false)
public function __construct(Profiler $profiler, RequestMatcherInterface $matcher = null, $onlyException = false, $onlyMasterRequests = false, RequestStack $requestStack = null)
{
$this->profiler = $profiler;
$this->matcher = $matcher;
$this->onlyException = (Boolean) $onlyException;
$this->onlyMasterRequests = (Boolean) $onlyMasterRequests;
$this->children = new \SplObjectStorage();
$this->profiles = array();
$this->profiles = new \SplObjectStorage();
$this->parents = new \SplObjectStorage();
$this->requests = array();
$this->requestStack = $requestStack;
}
/**
@ -68,9 +74,14 @@ class ProfilerListener implements EventSubscriberInterface
$this->exception = $event->getException();
}
/**
* @deprecated Deprecated since version 2.4, to be removed in 3.0.
*/
public function onKernelRequest(GetResponseEvent $event)
{
$this->requests[] = $event->getRequest();
if (null === $this->requestStack) {
$this->requests[] = $event->getRequest();
}
}
/**
@ -101,42 +112,35 @@ class ProfilerListener implements EventSubscriberInterface
return;
}
$this->profiles[] = $profile;
$this->profiles[$request] = $profile;
if (null !== $exception) {
foreach ($this->profiles as $profile) {
$this->profiler->saveProfile($profile);
}
return;
}
// keep the profile as the child of its parent
if (!$master) {
if (null !== $this->requestStack) {
$this->parents[$request] = $this->requestStack->getParentRequest();
} elseif (!$master) {
// to be removed when requestStack is required
array_pop($this->requests);
$parent = end($this->requests);
$this->parents[$request] = end($this->requests);
}
}
// when simulating requests, we might not have the parent
if ($parent) {
$profiles = isset($this->children[$parent]) ? $this->children[$parent] : array();
$profiles[] = $profile;
$this->children[$parent] = $profiles;
public function onKernelTerminate(PostResponseEvent $event)
{
// attach children to parents
foreach ($this->profiles as $request) {
if ($parentRequest = $this->parents[$request]) {
$this->profiles[$parentRequest]->addChild($this->profiles[$request]);
}
}
if (isset($this->children[$request])) {
foreach ($this->children[$request] as $child) {
$profile->addChild($child);
}
$this->children[$request] = array();
// save profiles
foreach ($this->profiles as $request) {
$this->profiler->saveProfile($this->profiles[$request]);
}
if ($master) {
$this->saveProfiles($profile);
$this->children = new \SplObjectStorage();
}
$this->profiles = new \SplObjectStorage();
$this->parents = new \SplObjectStorage();
$this->requests = array();
}
public static function getSubscribedEvents()
@ -147,19 +151,7 @@ class ProfilerListener implements EventSubscriberInterface
KernelEvents::REQUEST => array('onKernelRequest', 1024),
KernelEvents::RESPONSE => array('onKernelResponse', -100),
KernelEvents::EXCEPTION => 'onKernelException',
KernelEvents::TERMINATE => array('onKernelTerminate', -1024),
);
}
/**
* Saves the profile hierarchy.
*
* @param Profile $profile The root profile
*/
private function saveProfiles(Profile $profile)
{
$this->profiler->saveProfile($profile);
foreach ($profile->getChildren() as $profile) {
$this->saveProfiles($profile);
}
}
}

View File

@ -215,8 +215,8 @@ class Profiler
foreach ($this->collectors as $collector) {
$collector->collect($request, $response, $exception);
// forces collectors to become "read/only" (they loose their object dependencies)
$profile->addCollector(unserialize(serialize($collector)));
// we need to clone for sub-requests
$profile->addCollector(clone $collector);
}
return $profile;