Skip to content

Commit

Permalink
proof of concept: profiler
Browse files Browse the repository at this point in the history
  • Loading branch information
goetas committed Jul 24, 2021
1 parent 4a403d8 commit 4fa8046
Show file tree
Hide file tree
Showing 17 changed files with 977 additions and 29 deletions.
91 changes: 91 additions & 0 deletions Debug/DataCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

declare(strict_types=1);

namespace JMS\SerializerBundle\Debug;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector as BaseDataCollector;
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;

final class DataCollector extends BaseDataCollector implements LateDataCollectorInterface
{
private $eventDispatcher;
private $handler;
private $metadataDriver;

public function __construct(
TraceableEventDispatcher $eventDispatcher,
TraceableHandlerRegistry $handler,
TraceableDriver $metadataDriver
) {
$this->eventDispatcher = $eventDispatcher;
$this->handler = $handler;
$this->metadataDriver = $metadataDriver;

$this->reset();
}

public function collect(Request $request, Response $response, \Throwable $exception = null)
{
}

public function reset(): void
{
$this->data['handlers'] = [];
$this->data['metadata'] = [];
$this->data['listeners'] = [];
}

public function getName(): string
{
return 'jms_serializer';
}

public function addTriggeredEvent(array $call): void
{
$this->data['listeners'][] = $call;
}

public function getTriggeredListeners(): array
{
return $this->data['listeners']['called'];
}

public function getNotTriggeredListeners(): array
{
return $this->data['listeners']['not_called'];
}

public function getTriggeredHandlers(): array
{
return $this->data['handlers']['called'];
}

public function getNotTriggeredHandlers(): array
{
return $this->data['handlers']['not_called'];
}

public function getLoadedMetadata(): array
{
return $this->data['metadata'];
}

public function lateCollect(): void
{
$this->data['listeners'] = [
'called' => $this->eventDispatcher->getTriggeredListeners(),
'not_called' => $this->eventDispatcher->getNotTriggeredListeners(),
];


$this->data['handlers'] = [
'called' => $this->handler->getTriggeredHandlers(),
'not_called' => $this->handler->getNotTriggeredHandlers(),
];

$this->data['metadata'] = $this->metadataDriver->getLoadedMetadata();
}
}
60 changes: 60 additions & 0 deletions Debug/TraceableDriver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php declare(strict_types=1);

namespace JMS\SerializerBundle\Debug;

use JMS\Serializer\Metadata\Driver\XmlDriver;
use Metadata\Cache\CacheInterface;
use Metadata\ClassMetadata;
use Metadata\Driver\DriverInterface;

class TraceableDriver implements CacheInterface
{
/**
* @var CacheInterface
*/
private $driver;
private $storage = [];

public function __construct(CacheInterface $driver)
{

$this->driver = $driver;
}

public function getLoadedMetadata()
{
return $this->storage;
}

public function load($class): ?ClassMetadata
{
try{
return $metadata = $this->driver->load($class);
} finally {
if ($metadata){
$this->trackMetadata($metadata);
}
}

}

private function trackMetadata(ClassMetadata $metadata): void
{
$class = $metadata->name;
$this->storage[$class] = array_merge(
$this->storage[$class] ?? [], $metadata->fileResources
);
$this->storage[$class] = array_unique($this->storage[$class]);
}

public function put(ClassMetadata $metadata): void
{
$this->driver->put($metadata);
$this->trackMetadata($metadata);
}

public function evict(string $class): void
{
$this->driver->evict($class);
}
}
107 changes: 107 additions & 0 deletions Debug/TraceableEventDispatcher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php

declare(strict_types=1);

namespace JMS\SerializerBundle\Debug;

use JMS\Serializer\EventDispatcher\Event;
use JMS\Serializer\EventDispatcher\EventDispatcher;
use JMS\Serializer\EventDispatcher\EventDispatcherInterface;
use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
use JMS\Serializer\EventDispatcher\LazyEventDispatcher;
use Psr\Container\ContainerInterface;

final class TraceableEventDispatcher extends LazyEventDispatcher
{
/**
* @var array
*/
private $storage = [];

public function getTriggeredListeners(): array
{
$resultsByListener = [];

foreach ($this->storage as $eventName => $calledOnTypes) {
foreach ($calledOnTypes as $type => $calls) {
foreach ($calls as $call) {
$listener = $this->findNameForListener($call['listener']);
$resultsByListener[$eventName][$listener][$type][] = $call;
}
}
}

foreach ($resultsByListener as $eventName => $calledOnListeners) {
foreach ($calledOnListeners as $listener => $calledOnTypes) {
foreach ($calledOnTypes as $type => $calls) {
$resultsByListener[$eventName][$listener][$type] = [
'calls' => count($calls),
'duration' => $this->calculateTotalDuration($calls)
];
}
}
}

return $resultsByListener;
}

private function findNameForListener($listener): string
{
if (is_array($listener)) {
return (is_string($listener[0]) ? $listener[0] : get_class($listener[0]) ).'::'.$listener[1];
}
return 'unknown';
}

public function getNotTriggeredListeners(): array
{
$result = [];

foreach ($this->getListeners() as $event => $listeners) {
foreach ($listeners as $listener) {
foreach ($this->storage[$event] ?? [] as $calls) {
foreach ($calls as $call) {
if ($call['listener'] == $listener[0]) {
continue 3;
}
}
}
$listenerName = $this->findNameForListener($listener[0]);
$result[$event][$listenerName] = $listenerName;
}
}

return $result;
}

/**
* {@inheritdoc}
*/
protected function initializeListeners(string $eventName, string $loweredClass, string $format): array
{
$listeners = parent::initializeListeners($eventName, $loweredClass, $format);
$this->storage = [];
foreach ($listeners as &$listener) {
$listener[0] = $f = function (...$args) use ($listener, &$f) {
$t = microtime(true);
call_user_func_array($listener[0], $args);

// $args = [$event, $eventName, $class, $format, $dispatcher]
// $listener = [$callable, $class, $format, $interface]
$this->storage[$args[1]][$args[2]][] = [
'listener' => $listener[0],
'format' => $args[3],
'type' => $args[0]->getType(),
'duration' => microtime(true) - $t
];
};
}

return $listeners;
}

private function calculateTotalDuration(array $calls): float
{
return array_sum(array_column($calls, 'duration'));
}
}
100 changes: 100 additions & 0 deletions Debug/TraceableHandlerRegistry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php declare(strict_types=1);

namespace JMS\SerializerBundle\Debug;

use JMS\Serializer\Handler\HandlerRegistryInterface;
use JMS\Serializer\Handler\SubscribingHandlerInterface;

final class TraceableHandlerRegistry implements HandlerRegistryInterface
{
/**
* @var array
*/
private $storage = [];
private $registry;

public function __construct(HandlerRegistryInterface $registry)
{
$this->registry = $registry;
}

public function registerSubscribingHandler(SubscribingHandlerInterface $handler): void
{
$this->registerSubscribingHandler($handler);
}

public function registerHandler(int $direction, string $typeName, string $format, $handler): void
{
$this->registry->registerHandler($direction, $typeName, $format, $handler);
}

public function getHandler(int $direction, string $typeName, string $format)
{
$handler = $this->registry->getHandler($direction, $typeName, $format);
if ($handler=== null) {
return null;
}
return function (...$args) use($handler, $direction, $typeName, $format) {
try{
$t = microtime(true);
return call_user_func_array($handler, $args);
} finally {
$this->storage[$direction][$typeName][]= [
'handler' => $handler,
'format' => $format,
'duration' => microtime(true) - $t
];
}

};
}

private function findNameForListener($listener): string
{
if (is_array($listener)) {
return (is_string($listener[0]) ? $listener[0] : get_class($listener[0]) ).'::'.$listener[1];
}
return 'unknown';
}

public function getTriggeredHandlers(): array
{
$result = [];

foreach ($this->storage as $direction => $handlersByType) {
foreach ($handlersByType as $type => $calls) {
foreach ($calls as $call) {
$handlerName = $this->findNameForListener($call['handler']);
if (!isset($result[$direction][$type][$handlerName])){
$result[$direction][$type][$handlerName] = [
'calls' => 0,
'duration' => 0,
];
}
$result[$direction][$type][$handlerName] = [
'handler' => $handlerName,
'calls' => $result[$direction][$type][$handlerName]['calls']+1,
'duration' => $result[$direction][$type][$handlerName]['duration']+$call['duration'],
];
}
}
}

return $result;
}

public function getNotTriggeredHandlers(): array
{
return [];
$result = [];

foreach ($this->getTraceableHandlers() as $info) {
if (!$info['calls']) {
$result[$info['direction']][] = $info;
}
}

return $result;
}

}
16 changes: 12 additions & 4 deletions DependencyInjection/Compiler/CustomHandlersPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use JMS\Serializer\GraphNavigatorInterface;
use JMS\Serializer\Handler\HandlerRegistry;
use JMS\SerializerBundle\Debug\Handler\TraceableHandler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
Expand Down Expand Up @@ -80,14 +81,21 @@ public function process(ContainerBuilder $container)
}
}

$handlers = $this->sortAndFlattenHandlersList($handlers);
$handlersByDirection = $this->sortAndFlattenHandlersList($handlers);

$container->findDefinition('jms_serializer.handler_registry')
->addArgument($handlers);
$hdef = $container->findDefinition('jms_serializer.handler_registry');

foreach ($handlersByDirection as $direction => $handlersByType) {
foreach ($handlersByType as $type => $handlersByFormat) {
foreach ($handlersByFormat as $format => $handlerCallable) {
$hdef->addMethodCall('registerHandler', [$direction, $type, $format, $handlerCallable]);
}
}
}

if (class_exists(ServiceLocatorTagPass::class)) {
$serviceLocator = ServiceLocatorTagPass::register($container, $handlerServices);
$container->findDefinition('jms_serializer.handler_registry')->replaceArgument(0, $serviceLocator);
$hdef->replaceArgument(0, $serviceLocator);
}
}

Expand Down
Loading

0 comments on commit 4fa8046

Please sign in to comment.