Skip to content

Commit

Permalink
Merge pull request #83 from neo4j-php/feat/profiling
Browse files Browse the repository at this point in the history
feat: Profiler integration
  • Loading branch information
transistive authored Sep 16, 2024
2 parents 5dd76bb + cf78648 commit 7919235
Show file tree
Hide file tree
Showing 27 changed files with 642 additions and 84 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ on:
branches:
- master
pull_request:
branches:
- master

jobs:
php-cs-fixer:
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ on:
branches:
- master
pull_request:
branches:
- master

jobs:
build:
Expand Down
6 changes: 4 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ RUN apt-get update \
&& docker-php-ext-enable xdebug \
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

WORKDIR /opt/project

RUN echo "xdebug.client_host=host.docker.internal" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
RUN echo "xdebug.mode=debug,develop" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini

WORKDIR /opt/project

CMD ["php", "-S", "0.0.0.0:80", "-t", "/opt/project/tests/App"]
2 changes: 1 addition & 1 deletion bin/console.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@

require __DIR__ . '/../vendor/autoload.php';

$console = new Application(new TestKernel('test', true));
$console = new Application(new TestKernel($_ENV['APP_ENV'] ?? 'dev', true));

$console->run();
14 changes: 9 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,22 @@
"symfony/config": "^5.4 || ^6.0 || ^7.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.30",
"kubawerlos/php-cs-fixer-custom-fixers": "^3.0",
"matthiasnoback/symfony-dependency-injection-test": "^4.3 || ^5.0",
"phpunit/phpunit": "^9.5",
"psalm/plugin-phpunit": "^0.18",
"psalm/plugin-symfony": "^5.0",
"symfony/console": "^5.4 || ^6.0 || ^7.0",
"symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0",
"symfony/http-kernel": "^5.4 || ^6.0 || ^7.0",
"symfony/routing": "^5.4 || ^6.0 || ^7.0",
"symfony/stopwatch": "^6.4",
"symfony/test-pack": "^1.1",
"symfony/twig-bundle": "^5.4 || ^6.0 || ^7.0",
"symfony/web-profiler-bundle": "^5.4 || ^6.0 || ^7.0",
"symfony/yaml": "^5.4 || ^6.0 || ^7.0",
"vimeo/psalm": "^5.15.0",
"kubawerlos/php-cs-fixer-custom-fixers": "^3.0",
"friendsofphp/php-cs-fixer": "^3.30",
"psalm/plugin-phpunit": "^0.18"
"vimeo/psalm": "^5.15.0"
},
"autoload": {
"psr-4": {
Expand All @@ -49,7 +53,7 @@
}
},
"scripts": {
"psalm": "php bin/console.php cache:warmup && vendor/bin/psalm --show-info=true",
"psalm": "APP_ENV=dev php bin/console.php cache:warmup && vendor/bin/psalm --show-info=true",
"fix-cs": "vendor/bin/php-cs-fixer fix",
"check-cs": "vendor/bin/php-cs-fixer fix --dry-run",
"ci-symfony-install-version": "./.github/scripts/setup-symfony-env.bash"
Expand Down
9 changes: 4 additions & 5 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use Laudis\Neo4j\Contracts\SessionInterface;
use Laudis\Neo4j\Contracts\TransactionInterface;
use Neo4j\Neo4jBundle\ClientFactory;
use Neo4j\Neo4jBundle\EventHandler;
use Neo4j\Neo4jBundle\EventListener\Neo4jProfileListener;
use Neo4j\Neo4jBundle\SymfonyClient;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

Expand All @@ -16,10 +16,6 @@
return static function (ContainerConfigurator $configurator) {
$services = $configurator->services();

$services->set('neo4j.event_handler', EventHandler::class)
->autowire()
->autoconfigure();

$services->set('neo4j.client_factory', ClientFactory::class)
->args([
service('neo4j.event_handler'),
Expand Down Expand Up @@ -47,4 +43,7 @@
$services->alias(DriverInterface::class, 'neo4j.driver');
$services->alias(SessionInterface::class, 'neo4j.session');
$services->alias(TransactionInterface::class, 'neo4j.transaction');

$services->set('neo4j.subscriber', Neo4jProfileListener::class)
->tag('kernel.event_subscriber');
};
3 changes: 3 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ services:
- NEO4J_PORT=7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=testtest
- XDEBUG_CONFIG="client_host=host.docker.internal log=/tmp/xdebug.log"
working_dir: /opt/project
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- neo4j-symfony

Expand Down
2 changes: 1 addition & 1 deletion psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
</projectFiles>
<plugins>
<pluginClass class="Psalm\SymfonyPsalmPlugin\Plugin">
<containerXml>var/cache/test/Neo4j_Neo4jBundle_Tests_App_TestKernelTestDebugContainer.xml</containerXml>
<containerXml>var/cache/dev/Neo4j_Neo4jBundle_Tests_App_TestKernelDevDebugContainer.xml</containerXml>
</pluginClass>
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
</plugins>
Expand Down
8 changes: 4 additions & 4 deletions src/ClientFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@ public function create(): SymfonyClient
/** @var ClientBuilder<SummarizedResult<CypherMap>> $builder */
$builder = ClientBuilder::create();

if ($this->driverConfig) {
if (null !== $this->driverConfig) {
$builder = $builder->withDefaultDriverConfiguration($this->makeDriverConfig());
}

if ($this->sessionConfiguration) {
if (null !== $this->sessionConfiguration) {
$builder = $builder->withDefaultSessionConfiguration($this->makeSessionConfig());
}

if ($this->transactionConfiguration) {
if (null !== $this->transactionConfiguration) {
$builder = $builder->withDefaultTransactionConfiguration($this->makeTransactionConfig());
}

Expand All @@ -77,7 +77,7 @@ public function create(): SymfonyClient
);
}

if ($this->defaultDriver) {
if (null !== $this->defaultDriver) {
$builder = $builder->withDefaultDriver($this->defaultDriver);
}

Expand Down
148 changes: 148 additions & 0 deletions src/Collector/Neo4jDataCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?php

declare(strict_types=1);

namespace Neo4j\Neo4jBundle\Collector;

use Neo4j\Neo4jBundle\EventListener\Neo4jProfileListener;
use Symfony\Bundle\FrameworkBundle\DataCollector\AbstractDataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
* @var array{
* successful_statements_count: int,
* failed_statements_count: int,
* statements: array<array-key, array<string, mixed>> | list<array{
* statement: mixed,
* exception: mixed,
* alias: string|null
* }>,
* } $data
*/
final class Neo4jDataCollector extends AbstractDataCollector
{
public function __construct(
private readonly Neo4jProfileListener $subscriber,
) {
}

public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
{
$t = $this;
$profiledSummaries = $this->subscriber->getProfiledSummaries();
$successfulStatements = [];
foreach ($profiledSummaries as $summary) {
$statement = ['status' => 'success'];
foreach ($summary as $key => $value) {
if (!is_array($value) && !is_object($value)) {
$statement[$key] = $value;
continue;
}

$statement[$key] = $t->recursiveToArray($value);
}
$successfulStatements[] = $statement;
}

$failedStatements = array_map(
static fn (array $x) => [
'status' => 'failure',
'time' => $x['time'],
'timestamp' => $x['timestamp'],
'result' => [
'statement' => $x['statement']->toArray(),
],
'exception' => [
'code' => $x['exception']->getErrors()[0]->getCode(),
'message' => $x['exception']->getErrors()[0]->getMessage(),
'classification' => $x['exception']->getErrors()[0]->getClassification(),
'category' => $x['exception']->getErrors()[0]->getCategory(),
'title' => $x['exception']->getErrors()[0]->getTitle(),
],
'alias' => $x['alias'],
],
$this->subscriber->getProfiledFailures()
);

$this->data['successful_statements_count'] = count($successfulStatements);
$this->data['failed_statements_count'] = count($failedStatements);
$mergedArray = array_merge($successfulStatements, $failedStatements);
uasort(
$mergedArray,
static fn (array $a, array $b) => $a['start_time'] <=> $b['timestamp']
);
$this->data['statements'] = $mergedArray;
}

public function reset(): void
{
parent::reset();
$this->subscriber->reset();
}

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

/** @api */
public function getStatements(): array
{
return $this->data['statements'];
}

public function getSuccessfulStatements(): array
{
return array_filter(
$this->data['statements'],
static fn (array $x) => 'success' === $x['status']
);
}

public function getFailedStatements(): array
{
return array_filter(
$this->data['statements'],
static fn (array $x) => 'failure' === $x['status']
);
}

/** @api */
public function getFailedStatementsCount(): array
{
return $this->data['failed_statements_count'];
}

/** @api */
public function getSuccessfulStatementsCount(): array
{
return $this->data['successful_statements_count'];
}

public function getQueryCount(): int
{
return count($this->data['statements']);
}

public static function getTemplate(): ?string
{
return '@Neo4j/web_profiler.html.twig';
}

private function recursiveToArray(mixed $obj): mixed
{
if (is_array($obj)) {
return array_map(
fn (mixed $x): mixed => $this->recursiveToArray($x),
$obj
);
}

if (is_object($obj) && method_exists($obj, 'toArray')) {
return $obj->toArray();
}

return $obj;
}
}
51 changes: 45 additions & 6 deletions src/DependencyInjection/Neo4jExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@

namespace Neo4j\Neo4jBundle\DependencyInjection;

use Neo4j\Neo4jBundle\Collector\Neo4jDataCollector;
use Neo4j\Neo4jBundle\EventHandler;
use Neo4j\Neo4jBundle\EventListener\Neo4jProfileListener;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Reference;
Expand All @@ -27,28 +31,63 @@ public function load(array $configs, ContainerBuilder $container): ContainerBuil
$loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../../config'));
$loader->load('services.php');

$defaultAlias = $mergedConfig['default_driver'] ?? $mergedConfig['drivers'][0]['alias'] ?? 'default';

$container->setDefinition('neo4j.event_handler', new Definition(EventHandler::class))
->setAutowired(true)
->addTag('neo4j.event_handler')
->setArgument(1, $defaultAlias);

$container->getDefinition('neo4j.client_factory')
->setArgument(1, $mergedConfig['default_driver_config'] ?? null)
->setArgument(2, $mergedConfig['default_session_config'] ?? null)
->setArgument(3, $mergedConfig['default_transaction_config'] ?? null)
->setArgument(4, $mergedConfig['drivers'] ?? [])
->setArgument(5, $mergedConfig['default_driver'] ?? null)
->setArgument(6, new Reference(ClientInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE))
->setArgument(7, new Reference(StreamFactoryInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE))
->setArgument(8, new Reference(RequestFactoryInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE))
->setAbstract(false)
;
->setArgument(
7,
new Reference(StreamFactoryInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE)
)
->setArgument(
8,
new Reference(RequestFactoryInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE)
)
->setAbstract(false);

$container->getDefinition('neo4j.driver')
->setArgument(0, $mergedConfig['drivers']['alias'] ?? 'default');
->setArgument(0, $defaultAlias);

$enabledProfiles = [];
foreach ($mergedConfig['drivers'] as $driver) {
if (true === $driver['profiling'] || (null === $driver['profiling'] && $container->getParameter('kernel.debug'))) {
if (true === $driver['profiling'] || (null === $driver['profiling'] && $container->getParameter(
'kernel.debug'
))) {
$enabledProfiles[] = $driver['alias'];
}
}

if (0 !== count($enabledProfiles)) {
$container->setDefinition(
'neo4j.data_collector',
(new Definition(Neo4jDataCollector::class))
->setAutowired(true)
->addTag('data_collector', [
'id' => 'neo4j',
'priority' => 500,
])
);

$container->setAlias(Neo4jProfileListener::class, 'neo4j.subscriber');

$container->setDefinition(
'neo4j.subscriber',
(new Definition(Neo4jProfileListener::class))
->setArgument(0, $enabledProfiles)
->addTag('kernel.event_subscriber')
);
}

return $container;
}

Expand Down
Loading

0 comments on commit 7919235

Please sign in to comment.