feature#9170 Decoupled TraceableEventDispatcher from the Profiler (fabpot)
This PR was merged into the master branch. Discussion ---------- Decoupled TraceableEventDispatcher from the Profiler | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | n/a | License | MIT | Doc PR | n/a This PR removes the Profiler dependency on the TraceableEventDispatcher. That makes things more decoupled and cleaner. This PR also cleans up how profiles are stored; a Profile is now always stored only once. I've created a `LateDataCollectorInterface` that is implemented for data collector that needs to get information from data that are available very late in the request process (when the request and the response are not even available anymore). The `lateCollect()` method is called just before the profile is stored. We have 3 data collectors that implement that interface: * Time: As the traceable event dipsatcher gets inject timing information via the stopwatch about all events (including the `terminate` one), we need to get events from the stopwatch as late as possible. * Event: The traceable event dispatcher gathers all called listeners to determine non-called ones. To be able to accurately do that for all events (including the `terminate` one), we need to get the data as late as possible. * Memory: We want to get the memory as late as possible to get the most accurate number as possible I'm not very happy with the name and as always, better suggestions would be much appreciated. This is an extract from #9168 Commits -------5cedea2
[HttpKernel] added LateDataCollectorInterface9c4bc9a
[HttpKernel] decoupled TraceableEventDispatcher and Profiler
This commit is contained in:
commit
bdcdc6eddb
@ -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">
|
||||
|
@ -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%">
|
||||
|
@ -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>
|
||||
|
@ -11,8 +11,10 @@
|
||||
|
||||
namespace Symfony\Component\HttpKernel\DataCollector;
|
||||
|
||||
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface;
|
||||
|
||||
/**
|
||||
@ -20,8 +22,15 @@ use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class EventDataCollector extends DataCollector
|
||||
class EventDataCollector extends DataCollector implements LateDataCollectorInterface
|
||||
{
|
||||
protected $dispatcher;
|
||||
|
||||
public function __construct(EventDispatcherInterface $dispatcher = null)
|
||||
{
|
||||
$this->dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -33,6 +42,14 @@ class EventDataCollector extends DataCollector
|
||||
);
|
||||
}
|
||||
|
||||
public function lateCollect()
|
||||
{
|
||||
if ($this->dispatcher instanceof TraceableEventDispatcherInterface) {
|
||||
$this->setCalledListeners($this->dispatcher->getCalledListeners());
|
||||
$this->setNotCalledListeners($this->dispatcher->getNotCalledListeners());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the called listeners.
|
||||
*
|
||||
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpKernel\DataCollector;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* LateDataCollectorInterface.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface LateDataCollectorInterface
|
||||
{
|
||||
/**
|
||||
* Collects data as late as possible.
|
||||
*/
|
||||
public function lateCollect();
|
||||
}
|
@ -32,6 +32,14 @@ class LoggerDataCollector extends DataCollector
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function collect(Request $request, Response $response, \Exception $exception = null)
|
||||
{
|
||||
// everything is done as late as possible
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\HttpKernel\DataCollector;
|
||||
|
||||
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
@ -19,7 +20,7 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class MemoryDataCollector extends DataCollector
|
||||
class MemoryDataCollector extends DataCollector implements LateDataCollectorInterface
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
@ -37,6 +38,14 @@ class MemoryDataCollector extends DataCollector
|
||||
$this->updateMemoryUsage();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lateCollect()
|
||||
{
|
||||
$this->updateMemoryUsage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the memory.
|
||||
*
|
||||
|
@ -12,22 +12,26 @@
|
||||
namespace Symfony\Component\HttpKernel\DataCollector;
|
||||
|
||||
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
|
||||
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Stopwatch\Stopwatch;
|
||||
|
||||
/**
|
||||
* TimeDataCollector.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class TimeDataCollector extends DataCollector
|
||||
class TimeDataCollector extends DataCollector implements LateDataCollectorInterface
|
||||
{
|
||||
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,11 +46,23 @@ class TimeDataCollector extends DataCollector
|
||||
}
|
||||
|
||||
$this->data = array(
|
||||
'token' => $response->headers->get('X-Debug-Token'),
|
||||
'start_time' => $startTime * 1000,
|
||||
'events' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lateCollect()
|
||||
{
|
||||
if (null !== $this->stopwatch && isset($this->data['token'])) {
|
||||
$this->setEvents($this->stopwatch->getSectionEvents($this->data['token']));
|
||||
}
|
||||
unset($this->data['token']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the request events.
|
||||
*
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface;
|
||||
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
|
||||
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
@ -109,6 +110,13 @@ class Profiler
|
||||
*/
|
||||
public function saveProfile(Profile $profile)
|
||||
{
|
||||
// late collect
|
||||
foreach ($profile->getCollectors() as $collector) {
|
||||
if ($collector instanceof LateDataCollectorInterface) {
|
||||
$collector->lateCollect();
|
||||
}
|
||||
}
|
||||
|
||||
if (!($ret = $this->storage->write($profile)) && null !== $this->logger) {
|
||||
$this->logger->warning('Unable to store the profiler information.');
|
||||
}
|
||||
@ -215,8 +223,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;
|
||||
|
Reference in New Issue
Block a user