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 Aug 8, 2021
1 parent b1c2063 commit 56a42ba
Show file tree
Hide file tree
Showing 16 changed files with 1,151 additions and 0 deletions.
142 changes: 142 additions & 0 deletions Debug/DataCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<?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;

/**
* @internal
*/
final class DataCollector extends BaseDataCollector implements LateDataCollectorInterface
{
private $eventDispatcher;
private $handler;
private $metadataDriver;
private $locator;
private $loadedDirs;
private $runsListener;

public function __construct(
array $loadedDirs,
TraceableEventDispatcher $eventDispatcher,
TraceableHandlerRegistry $handler,
TraceableDriver $metadataDriver,
TraceableFileLocator $locator,
RunsListener $runsListener
) {
$this->eventDispatcher = $eventDispatcher;
$this->handler = $handler;
$this->metadataDriver = $metadataDriver;
$this->locator = $locator;
$this->loadedDirs = $loadedDirs;
$this->runsListener = $runsListener;

$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'] = [];
$this->data['metadata_files'] = [];
$this->data['loaded_dirs'] = [];
$this->data['runs'] = [];
}

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

public function getNumListeners($type): int
{
return array_sum(array_map(function ($l){
return count($l);
}, $this->data['listeners'][$type]));
}

public function getNumHandlers($type): int
{
return array_sum(array_map(function ($l){
return count($l);
}, $this->data['handlers'][$type]));
}

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

public function getRuns($direction): array
{
return $this->data['runs'][$direction] ?? [];
}

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

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 getMetadataFiles(): array
{
return $this->data['metadata_files'];
}

public function getTriggeredEvents()
{
return $this->data['triggered_events'];
}

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();
$this->data['metadata_files'] = $this->locator->getAttemptedFiles();
$this->data['loaded_dirs'] = $this->loadedDirs;
$this->data['runs'] = $this->runsListener->getRuns();
$this->data['triggered_events'] = $this->eventDispatcher->getTriggeredEvents();
ksort($this->data['loaded_dirs']);
ksort($this->data['metadata_files']);
ksort($this->data['metadata']);
}
}
24 changes: 24 additions & 0 deletions Debug/RunsListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php declare(strict_types=1);

namespace JMS\SerializerBundle\Debug;

use JMS\Serializer\EventDispatcher\Event;

/**
* @internal
*/
class RunsListener
{
private $runs = [];

public function saveRunInfo(Event $event)
{
$context = $event->getContext();
$this->runs[$context->getDirection()][spl_object_hash($context)] = true;
}

public function getRuns(): array
{
return $this->runs;
}
}
61 changes: 61 additions & 0 deletions Debug/TraceableDriver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php declare(strict_types=1);

namespace JMS\SerializerBundle\Debug;

use Metadata\Cache\CacheInterface;
use Metadata\ClassMetadata;

/**
* @internal
*/
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);
}
}
121 changes: 121 additions & 0 deletions Debug/TraceableEventDispatcher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

declare(strict_types=1);

namespace JMS\SerializerBundle\Debug;

use JMS\Serializer\EventDispatcher\LazyEventDispatcher;

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

public function getTriggeredEvents(): array
{
$data = [
'count' => 0,
'duration' => 0
];

foreach ($this->storage as $calledOnTypes) {
foreach ($calledOnTypes as $calls) {
$data['count'] += count($calls);
$data['duration'] += $this->calculateTotalDuration($calls);
}
}
return $data;
}

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')) * 1000;
}
}
33 changes: 33 additions & 0 deletions Debug/TraceableFileLocator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php declare(strict_types=1);

namespace JMS\SerializerBundle\Debug;

use Metadata\Driver\FileLocator;

/**
* @internal
*/
class TraceableFileLocator extends FileLocator
{
private $files = [];

public function __construct(array $dirs)
{
parent::__construct($dirs);
}

public function getAttemptedFiles()
{
return $this->files;
}

protected function loadFileIfFound($prefix, $dir, \ReflectionClass $class, $extension)
{
$pathData = parent::loadFileIfFound($prefix, $dir, $class, $extension);

if ($pathData[0] !== null) {
$this->files[$class->getName()][$pathData[0]] = $pathData[1];
}
return $pathData;
}
}

0 comments on commit 56a42ba

Please sign in to comment.