-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 53cac8c
Showing
14 changed files
with
508 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Kanti\ServerTiming\Dto; | ||
|
||
final class Time | ||
{ | ||
/** @var string */ | ||
public $key; | ||
/** @var float[] */ | ||
public $startTime = []; | ||
/** @var float[] */ | ||
public $stopTime = []; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Kanti\ServerTiming\Middleware; | ||
|
||
use Doctrine\DBAL\Logging\LoggerChain; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
use Psr\Http\Server\MiddlewareInterface; | ||
use Psr\Http\Server\RequestHandlerInterface; | ||
use TYPO3\CMS\Adminpanel\Log\DoctrineSqlLogger; | ||
use TYPO3\CMS\Adminpanel\Utility\StateUtility; | ||
use TYPO3\CMS\Core\Database\ConnectionPool; | ||
use TYPO3\CMS\Core\Utility\GeneralUtility; | ||
|
||
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 | ||
{ | ||
if (StateUtility::isActivatedForUser() && StateUtility::isOpen()) { | ||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); | ||
$connection = $connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME); | ||
$connection->getConfiguration()->setSQLLogger( | ||
new LoggerChain( | ||
array_filter( | ||
[ | ||
GeneralUtility::makeInstance(DoctrineSqlLogger::class), | ||
$connection->getConfiguration()->getSQLLogger(), | ||
] | ||
) | ||
) | ||
); | ||
} | ||
return $handler->handle($request); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Kanti\ServerTiming\Middleware; | ||
|
||
use Doctrine\DBAL\Logging\SQLLogger; | ||
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\Utility\GeneralUtility; | ||
use TYPO3\CMS\Extbase\Utility\DebuggerUtility; | ||
|
||
class FirstMiddleware implements MiddlewareInterface | ||
{ | ||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface | ||
{ | ||
TimingUtility::start('middleware Inward'); | ||
$this->registerSqlLogger(); | ||
$response = $handler->handle($request); | ||
TimingUtility::end('middleware Outward'); | ||
return $response; | ||
} | ||
|
||
protected function registerSqlLogger(): void | ||
{ | ||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); | ||
$connection = $connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME); | ||
$connection->getConfiguration()->setSQLLogger( | ||
new class implements SQLLogger { | ||
public function startQuery($sql, ?array $params = null, ?array $types = null) | ||
{ | ||
TimingUtility::start('sql', true); | ||
} | ||
|
||
public function stopQuery() | ||
{ | ||
TimingUtility::end('sql'); | ||
} | ||
} | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Kanti\ServerTiming\Middleware; | ||
|
||
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; | ||
|
||
class LastMiddleware implements MiddlewareInterface | ||
{ | ||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface | ||
{ | ||
TimingUtility::end('middleware Inward'); | ||
TimingUtility::start('requestHandler'); | ||
$response = $handler->handle($request); | ||
TimingUtility::end('requestHandler'); | ||
TimingUtility::start('middleware Outward'); | ||
return $response; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Kanti\ServerTiming\Utility; | ||
|
||
use Closure; | ||
use Psr\Http\Message\RequestInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
|
||
class GuzzleUtility | ||
{ | ||
public static function getHandler(): Closure | ||
{ | ||
return static function (callable $handler): Closure { | ||
return static function (RequestInterface $request, array $options) use ($handler): ResponseInterface { | ||
$str = 'guzzle ' . $request->getMethod(); | ||
if ($request->getUri()) { | ||
$str .= ' ' . $request->getUri()->getHost(); | ||
} | ||
$stop = TimingUtility::stopWatch($str); | ||
$response = $handler($request, $options); | ||
$stop(); | ||
return $response; | ||
}; | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Kanti\ServerTiming\Utility; | ||
|
||
use Closure; | ||
use Kanti\ServerTiming\Dto\Time; | ||
use TYPO3\CMS\Extbase\Utility\DebuggerUtility; | ||
|
||
final class TimingUtility | ||
{ | ||
/** @var Time[] */ | ||
private static $order = []; | ||
/** @var array<string, Time> */ | ||
private static $keyRef = []; | ||
/** @var array<string, \Closure> */ | ||
private static $stopWatchStack = []; | ||
/** @var bool */ | ||
private static $registered = false; | ||
|
||
public static function start(string $key, bool $isTotal = false): void | ||
{ | ||
$s = self::stopWatch($key, $isTotal); | ||
if (isset(self::$stopWatchStack[$key])) { | ||
if (!$isTotal) { | ||
throw new \Exception('only one measurement at a time, use TimingUtility::stopWatch() for parallel measurements'); | ||
} | ||
} | ||
self::$stopWatchStack[$key] = $s; | ||
} | ||
|
||
public static function end(string $key): void | ||
{ | ||
if (!isset(self::$stopWatchStack[$key])) { | ||
throw new \Exception('where is no measurement with this key'); | ||
} | ||
$stop = self::$stopWatchStack[$key]; | ||
$stop(); | ||
} | ||
|
||
public static function stopWatch(string $key, bool $isTotal = false): \Closure | ||
{ | ||
if ($isTotal) { | ||
if (isset(self::$keyRef[$key])) { | ||
$time = self::$keyRef[$key]; | ||
} else { | ||
$time = new Time(); | ||
$time->key = $key; | ||
self::$keyRef[$key] = $time; | ||
self::$order[] = $time; | ||
} | ||
} else { | ||
$time = new Time(); | ||
$time->key = $key; | ||
self::$order[] = $time; | ||
} | ||
$time->startTime[] = microtime(true); | ||
|
||
|
||
if (!self::$registered) { | ||
register_shutdown_function(static function () { | ||
self::shutdown(); | ||
}); | ||
self::$registered = true; | ||
} | ||
|
||
return static function () use ($time) { | ||
$time->stopTime[] = microtime(true); | ||
}; | ||
} | ||
|
||
private static function shutdown(): void | ||
{ | ||
if (PHP_SAPI === 'cli') { | ||
return; | ||
} | ||
self::end('php'); | ||
$timings = []; | ||
$keyCount = []; | ||
foreach (self::$order as $index => $time) { | ||
$keyCount[$time->key] = $keyCount[$time->key] ?? -1; | ||
$keyCount[$time->key]++; | ||
$singleTimes = array_filter( | ||
array_map(static function (float $startTime, ?float $endTime) { | ||
if ($endTime === null) { | ||
$endTime = microtime(true); | ||
} | ||
return ($endTime - $startTime) * 1000; | ||
}, $time->startTime, $time->stopTime) | ||
); | ||
$totalTime = array_sum($singleTimes); | ||
$count = count($time->startTime); | ||
$key = $time->key; | ||
if ($keyCount[$time->key]) { | ||
$key .= $keyCount[$time->key]; | ||
} | ||
if ($count > 1) { | ||
$key .= ' count:' . $count; | ||
} | ||
$timings[] = self::timingString($index, $key, $totalTime); | ||
rsort($singleTimes); | ||
if (count($singleTimes) > 1) { | ||
foreach (array_slice($singleTimes, 0, 3) as $subIndex => $subTime) { | ||
$timings[] = self::timingString($index, $time->key . ' top: ' . ($subIndex + 1), $subTime, $subIndex); | ||
} | ||
} | ||
} | ||
if (count($timings) > 70) { | ||
$timings = [self::timingString(0, 'To Many measurements ' . count($timings), 1.0)]; | ||
} | ||
if ($timings) { | ||
header(sprintf('Server-Timing: %s', implode(',', $timings)), false); | ||
} | ||
} | ||
|
||
private static function timingString(int $index, string $key, float $duration, ?int $subIndex = null): string | ||
{ | ||
$subIndexString = ''; | ||
if ($subIndex !== null) { | ||
$subIndexString = '_' . str_pad((string)$subIndex, 3, '0'); | ||
} | ||
return sprintf('%02d%s;desc="%s";dur=%0.2f', $index, $subIndexString, $key, $duration); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Kanti\ServerTiming\XClass; | ||
|
||
use GuzzleHttp\Client; | ||
use GuzzleHttp\ClientInterface; | ||
use GuzzleHttp\HandlerStack; | ||
use TYPO3\CMS\Core\Http\RequestFactory; | ||
use TYPO3\CMS\Core\Utility\GeneralUtility; | ||
|
||
/** | ||
* only used for TYPO3 v9 | ||
* makes the option $GLOBALS['TYPO3_CONF_VARS']['HTTP']['handler'] availalble in v9 | ||
* is a default option in >=v10 | ||
*/ | ||
class CoreRequestFactory extends RequestFactory | ||
{ | ||
protected function getClient(): ClientInterface | ||
{ | ||
$httpOptions = $GLOBALS['TYPO3_CONF_VARS']['HTTP']; | ||
$httpOptions['verify'] = filter_var($httpOptions['verify'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? $httpOptions['verify']; | ||
|
||
if (isset($GLOBALS['TYPO3_CONF_VARS']['HTTP']['handler']) && is_array($GLOBALS['TYPO3_CONF_VARS']['HTTP']['handler'])) { | ||
$stack = HandlerStack::create(); | ||
foreach ($GLOBALS['TYPO3_CONF_VARS']['HTTP']['handler'] ?? [] as $name => $handler) { | ||
$stack->push($handler, (string)$name); | ||
} | ||
$httpOptions['handler'] = $stack; | ||
} | ||
|
||
return GeneralUtility::makeInstance(Client::class, $httpOptions); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Kanti\ServerTiming\XClass; | ||
|
||
use Kanti\ServerTiming\Utility\TimingUtility; | ||
use TYPO3\CMS\Extbase\Mvc\Dispatcher; | ||
use TYPO3\CMS\Extbase\Mvc\RequestInterface; | ||
use TYPO3\CMS\Extbase\Mvc\ResponseInterface; | ||
use TYPO3\CMS\Extbase\Mvc\Web\Request; | ||
|
||
class ExtbaseDispatcher extends Dispatcher | ||
{ | ||
public function dispatch(RequestInterface $request, ResponseInterface $response) | ||
{ | ||
$str = 'extbase ' . str_replace('\\', '_', $request->getControllerObjectName()); | ||
if ($request instanceof Request) { | ||
$str .= '->' . $request->getControllerActionName(); | ||
} | ||
$stop = TimingUtility::stopWatch($str); | ||
parent::dispatch($request, $response); | ||
$stop(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<?php | ||
|
||
return [ | ||
'frontend' => [ | ||
'server-timing/first' => [ | ||
'target' => \Kanti\ServerTiming\Middleware\FirstMiddleware::class, | ||
'before' => [ | ||
'staticfilecache/fallback', | ||
'typo3/cms-frontend/timetracker', | ||
], | ||
], | ||
'server-timing/last' => [ | ||
'target' => \Kanti\ServerTiming\Middleware\LastMiddleware::class, | ||
'after' => [ | ||
'solr/service/pageexporter', | ||
'typo3/cms-frontend/output-compression', | ||
], | ||
], | ||
], | ||
'backend' => [ | ||
'server-timing/first' => [ | ||
'target' => \Kanti\ServerTiming\Middleware\FirstMiddleware::class, | ||
'before' => [ | ||
'typo3/cms-core/normalized-params-attribute', | ||
'typo3/cms-backend/locked-backend', | ||
], | ||
], | ||
'server-timing/last' => [ | ||
'target' => \Kanti\ServerTiming\Middleware\LastMiddleware::class, | ||
'after' => [ | ||
'typo3/cms-frontend/output-compression', | ||
'typo3/cms-backend/response-headers', | ||
'typo3/cms-backend/site-resolver', | ||
'typo3/cms-backend/legacy-document-template', | ||
], | ||
], | ||
], | ||
]; |
Oops, something went wrong.