Skip to content

Commit

Permalink
Merge pull request #19 from Kanti/feature/sentry-profiling
Browse files Browse the repository at this point in the history
  • Loading branch information
Kanti authored Aug 14, 2023
2 parents 5bd690b + 6143e55 commit 11915a4
Show file tree
Hide file tree
Showing 37 changed files with 945 additions and 356 deletions.
15 changes: 2 additions & 13 deletions .github/workflows/tasks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,8 @@ jobs:
strategy:
fail-fast: false
matrix:
php: [ '7.4', '8.0', '8.1', '8.2' ]
typo3: [ '10', '11', '12' ]
exclude:
- php: '8.0'
typo3: '10'
- php: '8.1'
typo3: '10'
- php: '8.2'
typo3: '10'
- php: '7.4'
typo3: '12'
- php: '8.0'
typo3: '12'
php: [ '8.1', '8.2' ]
typo3: [ '11', '12' ]
steps:
- name: Setup PHP with PECL extension
uses: shivammathur/setup-php@v2
Expand Down
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
.phpunit.cache
composer.lock
public/
vendor/
.phpunit.result.cache
var
Resources/Public/test-result
12 changes: 12 additions & 0 deletions .phpunit-watcher.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
watch:
directories:
- Classes/
- Tests/
fileMask: '*.php'
notifications:
passingTests: false
failingTests: false
phpunit:
binaryPath: vendor/bin/phpunit
arguments: '--stop-on-failure'
timeout: 180
41 changes: 41 additions & 0 deletions Classes/Dto/ScriptResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Kanti\ServerTiming\Dto;

use Kanti\ServerTiming\Utility\TimingUtility;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Http\ServerRequestFactory;

final class ScriptResult
{
private function __construct(public readonly ?ServerRequestInterface $request, public readonly ?ResponseInterface $response, public readonly ?int $cliExitCode)
{
}

public static function fromRequest(ServerRequestInterface $request, ?ResponseInterface $response = null): ScriptResult
{
return new ScriptResult($request, $response, null);
}

public static function fromCli(?int $cliExitCode = null): ScriptResult
{
return new ScriptResult(null, null, $cliExitCode);
}

public static function fromShutdown(): ScriptResult
{
if (TimingUtility::IS_CLI) {
return self::fromCli();
}

return self::fromRequest($GLOBALS['TYPO3_REQUEST'] ?? ServerRequestFactory::fromGlobals());
}

public function isCli(): bool
{
return !$this->request;
}
}
31 changes: 17 additions & 14 deletions Classes/Dto/StopWatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,33 @@

final class StopWatch
{
/** @var string */
public $key = '';
/** @var string */
public $info = '';
/** @var ?float */
public $startTime;
/** @var ?float */
public $stopTime;

public function __construct(string $key, string $info)
public float $startTime;

public ?float $stopTime = null;

public function __construct(public string $key, public string $info)
{
$this->key = $key;
$this->info = $info;
$this->startTime = microtime(true);
}

public function getDuration(): float
{
$this->stopTime = $this->stopTime ?? microtime(true);
$this->stopTime ??= microtime(true);
return $this->stopTime - $this->startTime;
}

public function __invoke(): void
public function stop(): void
{
$this->stopTime = microtime(true);
}

public function __invoke(): void
{
$this->stop();
}

public function stopIfNot(): void
{
$this->stopTime ??= microtime(true);
}
}
24 changes: 24 additions & 0 deletions Classes/EventListener/ConsoleCommandEventListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Kanti\ServerTiming\EventListener;

use Kanti\ServerTiming\Dto\ScriptResult;
use Kanti\ServerTiming\Utility\TimingUtility;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;

final class ConsoleCommandEventListener
{
public function start(ConsoleCommandEvent $event): void
{
TimingUtility::start('cli', (string)$event->getCommand()?->getName());
}

public function stop(ConsoleTerminateEvent $event): void
{
TimingUtility::end('cli');
TimingUtility::getInstance()->shutdown(ScriptResult::fromCli($event->getExitCode()));
}
}
10 changes: 5 additions & 5 deletions Classes/Middleware/AdminpanelSqlLoggingMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

class AdminpanelSqlLoggingMiddleware implements MiddlewareInterface
/**
* @deprecated can be removed if only TYPO3 >=12 is compatible
*/
final class AdminpanelSqlLoggingMiddleware implements MiddlewareInterface
{
/**
* Enable SQL Logging as early as possible to catch all queries if the admin panel is active
*
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
Expand All @@ -39,6 +38,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
)
);
}

return $handler->handle($request);
}
}
59 changes: 35 additions & 24 deletions Classes/Middleware/FirstMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,59 +5,70 @@
namespace Kanti\ServerTiming\Middleware;

use Doctrine\DBAL\Logging\SQLLogger;
use Kanti\ServerTiming\Dto\ScriptResult;
use Kanti\ServerTiming\Dto\StopWatch;
use Kanti\ServerTiming\Utility\TimingUtility;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Http\ImmediateResponseException;
use TYPO3\CMS\Core\Information\Typo3Version;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Utility\DebuggerUtility;

class FirstMiddleware implements MiddlewareInterface
final class FirstMiddleware implements MiddlewareInterface
{
/** @var StopWatch|null */
public static $stopWatchOutward = null;
public static ?StopWatch $stopWatchOutward = null;

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$stop = TimingUtility::stopWatch('middleware', 'Inward');
$request = $request->withAttribute('server-timing:middleware:inward', $stop);
$inward = TimingUtility::stopWatch('middleware', 'Inward');
$request = $request->withAttribute('server-timing:middleware:inward', $inward);

$GLOBALS['TYPO3_REQUEST'] = $request;

$this->registerSqlLogger();
$response = $handler->handle($request);
$stop = self::$stopWatchOutward;
if ($stop instanceof StopWatch) {
$stop();

try {
$response = $handler->handle($request);
} catch (ImmediateResponseException $immediateResponseException) {
$response = $immediateResponseException->getResponse();
}
return $response;

$inward->stopIfNot();
self::$stopWatchOutward?->stopIfNot();
self::$stopWatchOutward = null;

return TimingUtility::getInstance()->shutdown(ScriptResult::fromRequest($request, $response)) ?? $response;
}

protected function registerSqlLogger(): void
/**
* @deprecated can be removed if only TYPO3 >=12 is compatible
*/
private function registerSqlLogger(): void
{
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
$connection = $connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
$connection->getConfiguration()->setSQLLogger(
new class implements SQLLogger {
/** @var StopWatch|null */
private $stopWatch = null;
private ?StopWatch $stopWatch = null;

public function startQuery($sql, ?array $params = null, ?array $types = null)
public function startQuery($sql, ?array $params = null, ?array $types = null): void
{
$stop = $this->stopWatch;
if ($stop instanceof StopWatch) {
$stop();
$this->stopWatch?->stopIfNot();

if ($sql === 'SELECT DATABASE()') {
return;
}

$this->stopWatch = TimingUtility::stopWatch('sql', $sql);
}

public function stopQuery()
public function stopQuery(): void
{
$stop = $this->stopWatch;
if ($stop instanceof StopWatch) {
$stop();
$this->stopWatch = null;
}
$this->stopWatch?->stopIfNot();
$this->stopWatch = null;
}
}
);
Expand Down
22 changes: 12 additions & 10 deletions Classes/Middleware/LastMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,22 @@
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class LastMiddleware implements MiddlewareInterface
final class LastMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
TimingUtility::getInstance()->checkBackendUserStatus();
$stop = $request->getAttribute('server-timing:middleware:inward');
if ($stop instanceof StopWatch) {
$stop();
}
$stopWatch = $request->getAttribute('server-timing:middleware:inward');
$stopWatch?->stop();

$request = $request->withoutAttribute('server-timing:middleware:inward');
$stop = TimingUtility::stopWatch('requestHandler');
$response = $handler->handle($request);
$stop();
\Kanti\ServerTiming\Middleware\FirstMiddleware::$stopWatchOutward = TimingUtility::stopWatch('middleware', 'Outward');
$stopWatch = TimingUtility::stopWatch('requestHandler');
try {
$response = $handler->handle($request);
} finally {
$stopWatch();
FirstMiddleware::$stopWatchOutward = TimingUtility::stopWatch('middleware', 'Outward');
}

return $response;
}
}
35 changes: 35 additions & 0 deletions Classes/Service/ConfigService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Kanti\ServerTiming\Service;

use Kanti\ServerTiming\Utility\TimingUtility;

final class ConfigService
{
/** @var int */
private const DEFAULT_STOP_WATCH_LIMIT = 100_000;

public function stopWatchLimit(): int
{
return (int)($this->getConfig('stop_watch_limit') ?: self::DEFAULT_STOP_WATCH_LIMIT);
}

public function tracesSampleRate(): ?float
{
$tracesSampleRate = $this->getConfig(TimingUtility::IS_CLI ? 'sentry_cli_sample_rate' : 'sentry_sample_rate');
return $tracesSampleRate === '' ? null : (float)$tracesSampleRate;
}

public function enableTracing(): ?bool
{
$tracesSampleRate = $this->tracesSampleRate();
return $tracesSampleRate === null ? null : (bool)$tracesSampleRate;
}

private function getConfig(string $path): string
{
return (string)($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['server_timing'][$path] ?? '');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Kanti\ServerTiming\Service\RegisterShutdownFunction;

final class RegisterShutdownFunction implements RegisterShutdownFunctionInterface
{
public function register(callable $callback): void
{
register_shutdown_function($callback);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Kanti\ServerTiming\Service\RegisterShutdownFunction;

interface RegisterShutdownFunctionInterface
{
public function register(callable $callback): void;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Kanti\ServerTiming\Service\RegisterShutdownFunction;

final class RegisterShutdownFunctionNoop implements RegisterShutdownFunctionInterface
{
public int $callCount = 0;

public function register(callable $callback): void
{
$this->callCount++;
}
}
Loading

0 comments on commit 11915a4

Please sign in to comment.