diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml index 7eb472fd97..def9aea920 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml @@ -34,6 +34,7 @@ + @@ -45,6 +46,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml index e7d1c3c7d4..e4ba4f9792 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml @@ -19,7 +19,6 @@ - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml index a8666606e1..cee86b3b72 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml @@ -29,6 +29,7 @@ %profiler_listener.only_exceptions% %profiler_listener.only_master_requests% + diff --git a/src/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php index cd7f787017..9ed05821df 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php @@ -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} */ diff --git a/src/Symfony/Component/HttpKernel/DataCollector/MemoryDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/MemoryDataCollector.php index 115101f7cc..03d61e5af1 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/MemoryDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/MemoryDataCollector.php @@ -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} */ diff --git a/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php index 74d7616bf9..4f73418f26 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php @@ -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} */ diff --git a/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php b/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php index 85f27850e6..449e39ad47 100644 --- a/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php +++ b/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php @@ -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); diff --git a/src/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php b/src/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php index 7ee267c510..217bbdff11 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php @@ -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); - } - } } diff --git a/src/Symfony/Component/HttpKernel/Profiler/Profiler.php b/src/Symfony/Component/HttpKernel/Profiler/Profiler.php index d5bad876a0..3315eebd21 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/Profiler.php +++ b/src/Symfony/Component/HttpKernel/Profiler/Profiler.php @@ -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;