Skip to content

Commit

Permalink
Add contexts for profiler (#568)
Browse files Browse the repository at this point in the history
* Add contexts for profiler

* styleci

* psalm fix

* Profiler tests

* fix

* add more test

* styleci
  • Loading branch information
darkdef authored Mar 8, 2023
1 parent 422fa5c commit 01ddeaa
Show file tree
Hide file tree
Showing 10 changed files with 195 additions and 14 deletions.
11 changes: 7 additions & 4 deletions src/Command/AbstractCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Yiisoft\Db\Exception\Exception;
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Profiler\ProfilerAwareTrait;
use Yiisoft\Db\Profiler\Context\CommandContext;
use Yiisoft\Db\Query\Data\DataReaderInterface;
use Yiisoft\Db\Query\QueryInterface;

Expand Down Expand Up @@ -540,24 +541,26 @@ protected function queryInternal(int $queryMode): mixed
$isReadMode = $this->isReadMode($queryMode);

$logCategory = self::class . '::' . ($isReadMode ? 'query' : 'execute');
$queryContext = new CommandContext(__METHOD__, $logCategory, $this->getSql(), $this->getParams());

$this->logQuery($rawSql, $logCategory);
$this->prepare($isReadMode);

try {
$this->profiler?->begin($rawSql, [$logCategory]);
$this->profiler?->begin($rawSql, $queryContext);

$this->internalExecute($rawSql);

/** @psalm-var mixed $result */
$result = $this->internalGetQueryResult($queryMode);
$this->profiler?->end($rawSql, [$logCategory]);

$this->profiler?->end($rawSql, $queryContext);

if (!$isReadMode) {
$this->refreshTableSchema();
}
} catch (Exception $e) {
$this->profiler?->end($rawSql, [$logCategory]);

$this->profiler?->end($rawSql, $queryContext->setException($e));
throw $e;
}

Expand Down
8 changes: 5 additions & 3 deletions src/Driver/PDO/AbstractConnectionPDO.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Yiisoft\Db\Exception\Exception;
use Yiisoft\Db\Exception\InvalidCallException;
use Yiisoft\Db\Exception\InvalidConfigException;
use Yiisoft\Db\Profiler\Context\ConnectionContext;
use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;
use Yiisoft\Db\Schema\QuoterInterface;
use Yiisoft\Db\Schema\SchemaInterface;
Expand Down Expand Up @@ -78,14 +79,15 @@ public function open(): void
}

$token = 'Opening DB connection: ' . $this->driver->getDsn();
$connectionContext = new ConnectionContext(__METHOD__);

try {
$this->logger?->log(LogLevel::INFO, $token);
$this->profiler?->begin($token, [__METHOD__]);
$this->profiler?->begin($token, $connectionContext);
$this->initConnection();
$this->profiler?->end($token, [__METHOD__]);
$this->profiler?->end($token, $connectionContext);
} catch (PDOException $e) {
$this->profiler?->end($token, [__METHOD__]);
$this->profiler?->end($token, $connectionContext->setException($e));
$this->logger?->log(LogLevel::ERROR, $token);

throw new Exception($e->getMessage(), (array) $e->errorInfo, $e);
Expand Down
34 changes: 34 additions & 0 deletions src/Profiler/Context/AbstractContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Profiler\Context;

use Throwable;
use Yiisoft\Db\Profiler\ContextInterface;

abstract class AbstractContext implements ContextInterface
{
protected const METHOD = 'method';
protected const EXCEPTION = 'exception';

private Throwable|null $exception = null;

public function __construct(private string $method)
{
}

public function setException(Throwable $e): static
{
$this->exception = $e;
return $this;
}

public function asArray(): array
{
return [
self::METHOD => $this->method,
self::EXCEPTION => $this->exception,
];
}
}
35 changes: 35 additions & 0 deletions src/Profiler/Context/CommandContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Profiler\Context;

final class CommandContext extends AbstractContext
{
private const LOG_CONTEXT = 'logContext';
private const SQL = 'sql';
private const PARAMS = 'params';

public function __construct(
private string $method,
private string $logContext,
private string $sql,
private array $params,
) {
parent::__construct($this->method);
}

public function getType(): string
{
return 'command';
}

public function asArray(): array
{
return parent::asArray() + [
self::LOG_CONTEXT => $this->logContext,
self::SQL => $this->sql,
self::PARAMS => $this->params,
];
}
}
13 changes: 13 additions & 0 deletions src/Profiler/Context/ConnectionContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Profiler\Context;

final class ConnectionContext extends AbstractContext
{
public function getType(): string
{
return 'connection';
}
}
15 changes: 15 additions & 0 deletions src/Profiler/ContextInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Profiler;

interface ContextInterface
{
/**
* @return string Type of the context
*/
public function getType(): string;

public function asArray(): array;
}
8 changes: 4 additions & 4 deletions src/Profiler/ProfilerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,19 @@ interface ProfilerInterface
* The begin and end calls must also be nested.
*
* @param string $token Token for the code block.
* @param array $context The context data of this profile block.
* @param array|ContextInterface $context The context data of this profile block.
*/
public function begin(string $token, array $context = []): void;
public function begin(string $token, array|ContextInterface $context = []): void;

/**
* Marks the end of a code block for profiling.
*
* This has to be matched with an earlier call to {@see begin()} with the same category name.
*
* @param string $token Token for the code block.
* @param array $context The context data of this profile block.
* @param array|ContextInterface $context The context data of this profile block.
*
* {@see begin()}
*/
public function end(string $token, array $context = []): void;
public function end(string $token, array|ContextInterface $context = []): void;
}
46 changes: 44 additions & 2 deletions tests/AbstractCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
use Yiisoft\Db\Exception\Exception;
use Yiisoft\Db\Exception\InvalidConfigException;
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Profiler\Context\CommandContext;
use Yiisoft\Db\Profiler\ContextInterface;
use Yiisoft\Db\Profiler\ProfilerInterface;
use Yiisoft\Db\Tests\Support\DbHelper;
use Yiisoft\Db\Tests\Support\TestTrait;
Expand Down Expand Up @@ -217,9 +219,9 @@ public function testSetSql(): void
* @throws Throwable
* @throws \PHPUnit\Framework\MockObject\Exception
*/
public function testProfiler(): void
public function testProfiler(string $sql = null): void
{
$sql = 'SELECT 123';
$sql = $sql ?? 'SELECT 123';

$db = $this->getConnection();
$db->open();
Expand All @@ -237,4 +239,44 @@ public function testProfiler(): void

$db->createCommand($sql)->execute();
}

/**
* @throws Exception
* @throws InvalidConfigException
* @throws Throwable
* @throws \PHPUnit\Framework\MockObject\Exception
*/
public function testProfilerData(string $sql = null): void
{
$sql = $sql ?? 'SELECT 123';

$db = $this->getConnection();
$db->open();

$profiler = new class ($this, $sql) implements ProfilerInterface {
public function __construct(private TestCase $test, private string $sql)
{
}

public function begin(string $token, ContextInterface|array $context = []): void
{
$this->test->assertSame($this->sql, $token);
$this->test->assertInstanceOf(CommandContext::class, $context);
$this->test->assertSame('command', $context->getType());
$this->test->assertIsArray($context->asArray());
}

public function end(string $token, ContextInterface|array $context = []): void
{
$this->test->assertSame($this->sql, $token);
$this->test->assertInstanceOf(CommandContext::class, $context);
$this->test->assertSame('command', $context->getType());
$this->test->assertIsArray($context->asArray());
}
};

$db->setProfiler($profiler);

$db->createCommand($sql)->execute();
}
}
29 changes: 29 additions & 0 deletions tests/AbstractConnectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
use Yiisoft\Db\Driver\PDO\ConnectionPDOInterface;
use Yiisoft\Db\Exception\InvalidConfigException;
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Profiler\Context\ConnectionContext;
use Yiisoft\Db\Profiler\ContextInterface;
use Yiisoft\Db\Query\BatchQueryResult;
use Yiisoft\Db\Query\Query;
use Yiisoft\Db\Tests\Support\Assert;
Expand Down Expand Up @@ -110,6 +112,33 @@ public function testNotProfiler(): void
$this->assertNull(Assert::getInaccessibleProperty($db, 'profiler'));
}

public function testProfiler(): void
{
$db = $this->getConnection();

$profiler = new class ($this) implements ProfilerInterface {
public function __construct(private TestCase $test)
{
}

public function begin(string $token, ContextInterface|array $context = []): void
{
$this->test->assertInstanceOf(ConnectionContext::class, $context);
$this->test->assertSame('connection', $context->getType());
$this->test->assertIsArray($context->asArray());
}

public function end(string $token, ContextInterface|array $context = []): void
{
$this->test->assertInstanceOf(ConnectionContext::class, $context);
$this->test->assertSame('connection', $context->getType());
$this->test->assertIsArray($context->asArray());
}
};
$db->setProfiler($profiler);
$db->open();
}

public function testSetTablePrefix(): void
{
$db = $this->getConnection();
Expand Down
10 changes: 9 additions & 1 deletion tests/Db/Command/CommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -712,11 +712,19 @@ public function testUpsert(): void
$command->upsert('{{table}}', []);
}

public function testProfiler(): void
public function testProfiler(string $sql = null): void
{
$this->expectExceptionMessage(
'Yiisoft\Db\Tests\Support\Stub\Command::internalExecute is not supported by this DBMS.'
);
parent::testProfiler();
}

public function testProfilerData(string $sql = null): void
{
$this->expectExceptionMessage(
'Yiisoft\Db\Tests\Support\Stub\Command::internalExecute is not supported by this DBMS.'
);
parent::testProfilerData();
}
}

0 comments on commit 01ddeaa

Please sign in to comment.