mirror of
https://github.com/nextcloud/server.git
synced 2026-05-28 04:32:30 -04:00
Merge pull request #30823 from nextcloud/work/profiler
Built-in profiler This adds the required API for collecting information about requests. This information can then be displayed with the new 'profiler' app.
This commit is contained in:
commit
135bdb3d58
43 changed files with 1998 additions and 187 deletions
|
|
@ -272,6 +272,7 @@ return array(
|
|||
'OCA\\DAV\\Migration\\Version1016Date20201109085907' => $baseDir . '/../lib/Migration/Version1016Date20201109085907.php',
|
||||
'OCA\\DAV\\Migration\\Version1017Date20210216083742' => $baseDir . '/../lib/Migration/Version1017Date20210216083742.php',
|
||||
'OCA\\DAV\\Migration\\Version1018Date20210312100735' => $baseDir . '/../lib/Migration/Version1018Date20210312100735.php',
|
||||
'OCA\\DAV\\Profiler\\ProfilerPlugin' => $baseDir . '/../lib/Profiler/ProfilerPlugin.php',
|
||||
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningNode.php',
|
||||
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php',
|
||||
'OCA\\DAV\\RootCollection' => $baseDir . '/../lib/RootCollection.php',
|
||||
|
|
|
|||
|
|
@ -287,6 +287,7 @@ class ComposerStaticInitDAV
|
|||
'OCA\\DAV\\Migration\\Version1016Date20201109085907' => __DIR__ . '/..' . '/../lib/Migration/Version1016Date20201109085907.php',
|
||||
'OCA\\DAV\\Migration\\Version1017Date20210216083742' => __DIR__ . '/..' . '/../lib/Migration/Version1017Date20210216083742.php',
|
||||
'OCA\\DAV\\Migration\\Version1018Date20210312100735' => __DIR__ . '/..' . '/../lib/Migration/Version1018Date20210312100735.php',
|
||||
'OCA\\DAV\\Profiler\\ProfilerPlugin' => __DIR__ . '/..' . '/../lib/Profiler/ProfilerPlugin.php',
|
||||
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningNode.php',
|
||||
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php',
|
||||
'OCA\\DAV\\RootCollection' => __DIR__ . '/..' . '/../lib/RootCollection.php',
|
||||
|
|
|
|||
47
apps/dav/lib/Profiler/ProfilerPlugin.php
Normal file
47
apps/dav/lib/Profiler/ProfilerPlugin.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* @copyright 2021 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @author Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\Profiler;
|
||||
|
||||
use OCP\IRequest;
|
||||
use Sabre\DAV\Server;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
|
||||
class ProfilerPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
private IRequest $request;
|
||||
|
||||
public function __construct(IRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/** @return void */
|
||||
public function initialize(Server $server) {
|
||||
$server->on('afterMethod:*', [$this, 'afterMethod']);
|
||||
}
|
||||
|
||||
/** @return void */
|
||||
public function afterMethod(RequestInterface $request, ResponseInterface $response) {
|
||||
$response->addHeader('X-Debug-Token', $this->request->getId());
|
||||
}
|
||||
}
|
||||
|
|
@ -36,6 +36,9 @@ namespace OCA\DAV;
|
|||
|
||||
use OCA\DAV\Connector\Sabre\RequestIdHeaderPlugin;
|
||||
use OCP\Diagnostics\IEventLogger;
|
||||
use OCP\Profiler\IProfiler;
|
||||
use OCA\DAV\Profiler\ProfilerPlugin;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use OCA\DAV\AppInfo\PluginManager;
|
||||
use OCA\DAV\CalDAV\BirthdayService;
|
||||
|
|
@ -78,17 +81,19 @@ use Sabre\DAV\UUIDUtil;
|
|||
use SearchDAV\DAV\SearchPlugin;
|
||||
|
||||
class Server {
|
||||
private IRequest $request;
|
||||
private string $baseUri;
|
||||
public Connector\Sabre\Server $server;
|
||||
private IProfiler $profiler;
|
||||
|
||||
/** @var IRequest */
|
||||
private $request;
|
||||
public function __construct(IRequest $request, string $baseUri) {
|
||||
$this->profiler = \OC::$server->get(IProfiler::class);
|
||||
if ($this->profiler->isEnabled()) {
|
||||
/** @var IEventLogger $eventLogger */
|
||||
$eventLogger = \OC::$server->get(IEventLogger::class);
|
||||
$eventLogger->start('runtime', 'DAV Runtime');
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
private $baseUri;
|
||||
|
||||
/** @var Connector\Sabre\Server */
|
||||
public $server;
|
||||
|
||||
public function __construct(IRequest $request, $baseUri) {
|
||||
$this->request = $request;
|
||||
$this->baseUri = $baseUri;
|
||||
$logger = \OC::$server->getLogger();
|
||||
|
|
@ -115,6 +120,7 @@ class Server {
|
|||
$this->server->httpRequest->setUrl($this->request->getRequestUri());
|
||||
$this->server->setBaseUri($this->baseUri);
|
||||
|
||||
$this->server->addPlugin(new ProfilerPlugin($this->request));
|
||||
$this->server->addPlugin(new BlockLegacyClientPlugin(\OC::$server->getConfig()));
|
||||
$this->server->addPlugin(new AnonymousOptionsPlugin());
|
||||
$authPlugin = new Plugin();
|
||||
|
|
@ -343,6 +349,11 @@ class Server {
|
|||
$eventLogger->start('dav_server_exec', '');
|
||||
$this->server->exec();
|
||||
$eventLogger->end('dav_server_exec');
|
||||
if ($this->profiler->isEnabled()) {
|
||||
$eventLogger->end('runtime');
|
||||
$profile = $this->profiler->collect(\OC::$server->get(IRequest::class), new Response());
|
||||
$this->profiler->saveProfile($profile);
|
||||
}
|
||||
}
|
||||
|
||||
private function requestIsForSubtree(array $subTrees): bool {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ return array(
|
|||
'OCA\\User_LDAP\\ConnectionFactory' => $baseDir . '/../lib/ConnectionFactory.php',
|
||||
'OCA\\User_LDAP\\Controller\\ConfigAPIController' => $baseDir . '/../lib/Controller/ConfigAPIController.php',
|
||||
'OCA\\User_LDAP\\Controller\\RenewPasswordController' => $baseDir . '/../lib/Controller/RenewPasswordController.php',
|
||||
'OCA\\User_LDAP\\DataCollector\\LdapDataCollector' => $baseDir . '/../lib/DataCollector/LdapDataCollector.php',
|
||||
'OCA\\User_LDAP\\Events\\GroupBackendRegistered' => $baseDir . '/../lib/Events/GroupBackendRegistered.php',
|
||||
'OCA\\User_LDAP\\Events\\UserBackendRegistered' => $baseDir . '/../lib/Events/UserBackendRegistered.php',
|
||||
'OCA\\User_LDAP\\Exceptions\\AttributeNotSet' => $baseDir . '/../lib/Exceptions/AttributeNotSet.php',
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ class ComposerStaticInitUser_LDAP
|
|||
'OCA\\User_LDAP\\ConnectionFactory' => __DIR__ . '/..' . '/../lib/ConnectionFactory.php',
|
||||
'OCA\\User_LDAP\\Controller\\ConfigAPIController' => __DIR__ . '/..' . '/../lib/Controller/ConfigAPIController.php',
|
||||
'OCA\\User_LDAP\\Controller\\RenewPasswordController' => __DIR__ . '/..' . '/../lib/Controller/RenewPasswordController.php',
|
||||
'OCA\\User_LDAP\\DataCollector\\LdapDataCollector' => __DIR__ . '/..' . '/../lib/DataCollector/LdapDataCollector.php',
|
||||
'OCA\\User_LDAP\\Events\\GroupBackendRegistered' => __DIR__ . '/..' . '/../lib/Events/GroupBackendRegistered.php',
|
||||
'OCA\\User_LDAP\\Events\\UserBackendRegistered' => __DIR__ . '/..' . '/../lib/Events/UserBackendRegistered.php',
|
||||
'OCA\\User_LDAP\\Exceptions\\AttributeNotSet' => __DIR__ . '/..' . '/../lib/Exceptions/AttributeNotSet.php',
|
||||
|
|
|
|||
50
apps/user_ldap/lib/DataCollector/LdapDataCollector.php
Normal file
50
apps/user_ldap/lib/DataCollector/LdapDataCollector.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* @copyright 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @author Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\User_LDAP\DataCollector;
|
||||
|
||||
use OC\AppFramework\Http\Request;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\DataCollector\AbstractDataCollector;
|
||||
|
||||
class LdapDataCollector extends AbstractDataCollector {
|
||||
public function startLdapRequest(string $query, array $args): void {
|
||||
$this->data[] = [
|
||||
'start' => microtime(true),
|
||||
'query' => $query,
|
||||
'args' => $args,
|
||||
'end' => microtime(true),
|
||||
];
|
||||
}
|
||||
|
||||
public function stopLastLdapRequest(): void {
|
||||
$this->data[count($this->data) - 1]['end'] = microtime(true);
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return 'ldap';
|
||||
}
|
||||
|
||||
public function collect(Request $request, Response $response, \Throwable $exception = null): void {
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
* @author Robin McCorkell <robin@mccorkell.me.uk>
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
* @author Roger Szabo <roger.szabo@web.de>
|
||||
* @author Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
|
|
@ -32,7 +33,9 @@
|
|||
*/
|
||||
namespace OCA\User_LDAP;
|
||||
|
||||
use OCP\Profiler\IProfiler;
|
||||
use OC\ServerNotAvailableException;
|
||||
use OCA\User_LDAP\DataCollector\LdapDataCollector;
|
||||
use OCA\User_LDAP\Exceptions\ConstraintViolationException;
|
||||
use OCA\User_LDAP\PagedResults\IAdapter;
|
||||
use OCA\User_LDAP\PagedResults\Php73;
|
||||
|
|
@ -45,9 +48,18 @@ class LDAP implements ILDAPWrapper {
|
|||
/** @var IAdapter */
|
||||
protected $pagedResultsAdapter;
|
||||
|
||||
private ?LdapDataCollector $dataCollector = null;
|
||||
|
||||
public function __construct(string $logFile = '') {
|
||||
$this->pagedResultsAdapter = new Php73();
|
||||
$this->logFile = $logFile;
|
||||
|
||||
/** @var IProfiler $profiler */
|
||||
$profiler = \OC::$server->get(IProfiler::class);
|
||||
if ($profiler->isEnabled()) {
|
||||
$this->dataCollector = new LdapDataCollector();
|
||||
$profiler->add($this->dataCollector);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -295,24 +307,26 @@ class LDAP implements ILDAPWrapper {
|
|||
if ($this->isResultFalse($result)) {
|
||||
$this->postFunctionCall();
|
||||
}
|
||||
if ($this->dataCollector !== null) {
|
||||
$this->dataCollector->stopLastLdapRequest();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $functionName
|
||||
* @param array $args
|
||||
*/
|
||||
private function preFunctionCall($functionName, $args) {
|
||||
private function preFunctionCall(string $functionName, array $args): void {
|
||||
$this->curFunc = $functionName;
|
||||
$this->curArgs = $args;
|
||||
|
||||
if ($this->dataCollector !== null) {
|
||||
$args = array_map(fn ($item) => (!$this->isResource($item) ? $item : '(resource)'), $this->curArgs);
|
||||
|
||||
$this->dataCollector->startLdapRequest($this->curFunc, $args);
|
||||
}
|
||||
|
||||
if ($this->logFile !== '' && is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile))) {
|
||||
$args = array_reduce($this->curArgs, static function (array $carry, $item): array {
|
||||
$carry[] = !is_resource($item) ? $item : '(resource)';
|
||||
return $carry;
|
||||
}, []);
|
||||
$args = array_map(fn ($item) => (!$this->isResource($item) ? $item : '(resource)'), $this->curArgs);
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->curFunc . '::' . json_encode($args) . "\n",
|
||||
|
|
|
|||
|
|
@ -1023,7 +1023,6 @@
|
|||
</RedundantCondition>
|
||||
<TypeDoesNotContainType occurrences="2">
|
||||
<code>get_class($res) === 'OpenSSLAsymmetricKey'</code>
|
||||
<code>is_object($res)</code>
|
||||
</TypeDoesNotContainType>
|
||||
</file>
|
||||
<file src="apps/encryption/lib/Crypto/EncryptAll.php">
|
||||
|
|
@ -2638,11 +2637,6 @@
|
|||
<code>$default</code>
|
||||
</MoreSpecificImplementedParamType>
|
||||
</file>
|
||||
<file src="lib/private/AppFramework/Utility/SimpleContainer.php">
|
||||
<UndefinedMethod occurrences="1">
|
||||
<code>getName</code>
|
||||
</UndefinedMethod>
|
||||
</file>
|
||||
<file src="lib/private/Archive/TAR.php">
|
||||
<UndefinedDocblockClass occurrences="1">
|
||||
<code>$this->tar->extractInString($path)</code>
|
||||
|
|
|
|||
|
|
@ -962,6 +962,14 @@ $CONFIG = [
|
|||
*/
|
||||
'log_rotate_size' => 100 * 1024 * 1024,
|
||||
|
||||
/**
|
||||
* Enable built-in profiler. Helpful when trying to debug performance
|
||||
* issues.
|
||||
*
|
||||
* Note that this has a performance impact and shouldn't be enabled
|
||||
* on production.
|
||||
*/
|
||||
'profiler' => false,
|
||||
|
||||
/**
|
||||
* Alternate Code Locations
|
||||
|
|
|
|||
|
|
@ -202,6 +202,6 @@ $getUserAvatar = static function (int $size) use ($_): string {
|
|||
<div id="content" class="app-<?php p($_['appid']) ?>" role="main">
|
||||
<?php print_unescaped($_['content']); ?>
|
||||
</div>
|
||||
|
||||
<div id="profiler-toolbar"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ namespace OC;
|
|||
|
||||
use \OCP\AutoloadNotAllowedException;
|
||||
use OCP\ILogger;
|
||||
use OCP\ICache;
|
||||
|
||||
class Autoloader {
|
||||
/** @var bool */
|
||||
|
|
@ -182,9 +183,9 @@ class Autoloader {
|
|||
/**
|
||||
* Sets the optional low-latency cache for class to path mapping.
|
||||
*
|
||||
* @param \OC\Memcache\Cache $memoryCache Instance of memory cache.
|
||||
* @param ICache $memoryCache Instance of memory cache.
|
||||
*/
|
||||
public function setMemoryCache(\OC\Memcache\Cache $memoryCache = null): void {
|
||||
public function setMemoryCache(ICache $memoryCache = null): void {
|
||||
$this->memoryCache = $memoryCache;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -608,6 +608,7 @@ class OC {
|
|||
$eventLogger->end('request');
|
||||
});
|
||||
$eventLogger->start('boot', 'Initialize');
|
||||
$eventLogger->start('runtime', 'Runtime (total - autoloader)');
|
||||
|
||||
// Override php.ini and log everything if we're troubleshooting
|
||||
if (self::$config->getValue('loglevel') === ILogger::DEBUG) {
|
||||
|
|
|
|||
|
|
@ -196,6 +196,8 @@ return array(
|
|||
'OCP\\Dashboard\\RegisterWidgetEvent' => $baseDir . '/lib/public/Dashboard/RegisterWidgetEvent.php',
|
||||
'OCP\\Dashboard\\Service\\IEventsService' => $baseDir . '/lib/public/Dashboard/Service/IEventsService.php',
|
||||
'OCP\\Dashboard\\Service\\IWidgetsService' => $baseDir . '/lib/public/Dashboard/Service/IWidgetsService.php',
|
||||
'OCP\\DataCollector\\AbstractDataCollector' => $baseDir . '/lib/public/DataCollector/AbstractDataCollector.php',
|
||||
'OCP\\DataCollector\\IDataCollector' => $baseDir . '/lib/public/DataCollector/IDataCollector.php',
|
||||
'OCP\\Defaults' => $baseDir . '/lib/public/Defaults.php',
|
||||
'OCP\\Diagnostics\\IEvent' => $baseDir . '/lib/public/Diagnostics/IEvent.php',
|
||||
'OCP\\Diagnostics\\IEventLogger' => $baseDir . '/lib/public/Diagnostics/IEventLogger.php',
|
||||
|
|
@ -467,6 +469,8 @@ return array(
|
|||
'OCP\\Preview\\IVersionedPreviewFile' => $baseDir . '/lib/public/Preview/IVersionedPreviewFile.php',
|
||||
'OCP\\Profile\\ILinkAction' => $baseDir . '/lib/public/Profile/ILinkAction.php',
|
||||
'OCP\\Profile\\ParameterDoesNotExistException' => $baseDir . '/lib/public/Profile/ParameterDoesNotExistException.php',
|
||||
'OCP\\Profiler\\IProfile' => $baseDir . '/lib/public/Profiler/IProfile.php',
|
||||
'OCP\\Profiler\\IProfiler' => $baseDir . '/lib/public/Profiler/IProfiler.php',
|
||||
'OCP\\Remote\\Api\\IApiCollection' => $baseDir . '/lib/public/Remote/Api/IApiCollection.php',
|
||||
'OCP\\Remote\\Api\\IApiFactory' => $baseDir . '/lib/public/Remote/Api/IApiFactory.php',
|
||||
'OCP\\Remote\\Api\\ICapabilitiesApi' => $baseDir . '/lib/public/Remote/Api/ICapabilitiesApi.php',
|
||||
|
|
@ -1025,6 +1029,7 @@ return array(
|
|||
'OC\\DB\\Connection' => $baseDir . '/lib/private/DB/Connection.php',
|
||||
'OC\\DB\\ConnectionAdapter' => $baseDir . '/lib/private/DB/ConnectionAdapter.php',
|
||||
'OC\\DB\\ConnectionFactory' => $baseDir . '/lib/private/DB/ConnectionFactory.php',
|
||||
'OC\\DB\\DbDataCollector' => $baseDir . '/lib/private/DB/DbDataCollector.php',
|
||||
'OC\\DB\\Exceptions\\DbalException' => $baseDir . '/lib/private/DB/Exceptions/DbalException.php',
|
||||
'OC\\DB\\MigrationException' => $baseDir . '/lib/private/DB/MigrationException.php',
|
||||
'OC\\DB\\MigrationService' => $baseDir . '/lib/private/DB/MigrationService.php',
|
||||
|
|
@ -1035,6 +1040,7 @@ return array(
|
|||
'OC\\DB\\MySQLMigrator' => $baseDir . '/lib/private/DB/MySQLMigrator.php',
|
||||
'OC\\DB\\MySqlTools' => $baseDir . '/lib/private/DB/MySqlTools.php',
|
||||
'OC\\DB\\OCSqlitePlatform' => $baseDir . '/lib/private/DB/OCSqlitePlatform.php',
|
||||
'OC\\DB\\ObjectParameter' => $baseDir . '/lib/private/DB/ObjectParameter.php',
|
||||
'OC\\DB\\OracleConnection' => $baseDir . '/lib/private/DB/OracleConnection.php',
|
||||
'OC\\DB\\OracleMigrator' => $baseDir . '/lib/private/DB/OracleMigrator.php',
|
||||
'OC\\DB\\PgSqlTools' => $baseDir . '/lib/private/DB/PgSqlTools.php',
|
||||
|
|
@ -1279,8 +1285,10 @@ return array(
|
|||
'OC\\Memcache\\CASTrait' => $baseDir . '/lib/private/Memcache/CASTrait.php',
|
||||
'OC\\Memcache\\Cache' => $baseDir . '/lib/private/Memcache/Cache.php',
|
||||
'OC\\Memcache\\Factory' => $baseDir . '/lib/private/Memcache/Factory.php',
|
||||
'OC\\Memcache\\LoggerWrapperCache' => $baseDir . '/lib/private/Memcache/LoggerWrapperCache.php',
|
||||
'OC\\Memcache\\Memcached' => $baseDir . '/lib/private/Memcache/Memcached.php',
|
||||
'OC\\Memcache\\NullCache' => $baseDir . '/lib/private/Memcache/NullCache.php',
|
||||
'OC\\Memcache\\ProfilerWrapperCache' => $baseDir . '/lib/private/Memcache/ProfilerWrapperCache.php',
|
||||
'OC\\Memcache\\Redis' => $baseDir . '/lib/private/Memcache/Redis.php',
|
||||
'OC\\MemoryInfo' => $baseDir . '/lib/private/MemoryInfo.php',
|
||||
'OC\\Migration\\BackgroundRepair' => $baseDir . '/lib/private/Migration/BackgroundRepair.php',
|
||||
|
|
@ -1347,6 +1355,10 @@ return array(
|
|||
'OC\\Profile\\Actions\\WebsiteAction' => $baseDir . '/lib/private/Profile/Actions/WebsiteAction.php',
|
||||
'OC\\Profile\\ProfileManager' => $baseDir . '/lib/private/Profile/ProfileManager.php',
|
||||
'OC\\Profile\\TProfileHelper' => $baseDir . '/lib/private/Profile/TProfileHelper.php',
|
||||
'OC\\Profiler\\FileProfilerStorage' => $baseDir . '/lib/private/Profiler/FileProfilerStorage.php',
|
||||
'OC\\Profiler\\Profile' => $baseDir . '/lib/private/Profiler/Profile.php',
|
||||
'OC\\Profiler\\Profiler' => $baseDir . '/lib/private/Profiler/Profiler.php',
|
||||
'OC\\Profiler\\RoutingDataCollector' => $baseDir . '/lib/private/Profiler/RoutingDataCollector.php',
|
||||
'OC\\RedisFactory' => $baseDir . '/lib/private/RedisFactory.php',
|
||||
'OC\\Remote\\Api\\ApiBase' => $baseDir . '/lib/private/Remote/Api/ApiBase.php',
|
||||
'OC\\Remote\\Api\\ApiCollection' => $baseDir . '/lib/private/Remote/Api/ApiCollection.php',
|
||||
|
|
|
|||
|
|
@ -225,6 +225,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OCP\\Dashboard\\RegisterWidgetEvent' => __DIR__ . '/../../..' . '/lib/public/Dashboard/RegisterWidgetEvent.php',
|
||||
'OCP\\Dashboard\\Service\\IEventsService' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Service/IEventsService.php',
|
||||
'OCP\\Dashboard\\Service\\IWidgetsService' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Service/IWidgetsService.php',
|
||||
'OCP\\DataCollector\\AbstractDataCollector' => __DIR__ . '/../../..' . '/lib/public/DataCollector/AbstractDataCollector.php',
|
||||
'OCP\\DataCollector\\IDataCollector' => __DIR__ . '/../../..' . '/lib/public/DataCollector/IDataCollector.php',
|
||||
'OCP\\Defaults' => __DIR__ . '/../../..' . '/lib/public/Defaults.php',
|
||||
'OCP\\Diagnostics\\IEvent' => __DIR__ . '/../../..' . '/lib/public/Diagnostics/IEvent.php',
|
||||
'OCP\\Diagnostics\\IEventLogger' => __DIR__ . '/../../..' . '/lib/public/Diagnostics/IEventLogger.php',
|
||||
|
|
@ -496,6 +498,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OCP\\Preview\\IVersionedPreviewFile' => __DIR__ . '/../../..' . '/lib/public/Preview/IVersionedPreviewFile.php',
|
||||
'OCP\\Profile\\ILinkAction' => __DIR__ . '/../../..' . '/lib/public/Profile/ILinkAction.php',
|
||||
'OCP\\Profile\\ParameterDoesNotExistException' => __DIR__ . '/../../..' . '/lib/public/Profile/ParameterDoesNotExistException.php',
|
||||
'OCP\\Profiler\\IProfile' => __DIR__ . '/../../..' . '/lib/public/Profiler/IProfile.php',
|
||||
'OCP\\Profiler\\IProfiler' => __DIR__ . '/../../..' . '/lib/public/Profiler/IProfiler.php',
|
||||
'OCP\\Remote\\Api\\IApiCollection' => __DIR__ . '/../../..' . '/lib/public/Remote/Api/IApiCollection.php',
|
||||
'OCP\\Remote\\Api\\IApiFactory' => __DIR__ . '/../../..' . '/lib/public/Remote/Api/IApiFactory.php',
|
||||
'OCP\\Remote\\Api\\ICapabilitiesApi' => __DIR__ . '/../../..' . '/lib/public/Remote/Api/ICapabilitiesApi.php',
|
||||
|
|
@ -1054,6 +1058,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OC\\DB\\Connection' => __DIR__ . '/../../..' . '/lib/private/DB/Connection.php',
|
||||
'OC\\DB\\ConnectionAdapter' => __DIR__ . '/../../..' . '/lib/private/DB/ConnectionAdapter.php',
|
||||
'OC\\DB\\ConnectionFactory' => __DIR__ . '/../../..' . '/lib/private/DB/ConnectionFactory.php',
|
||||
'OC\\DB\\DbDataCollector' => __DIR__ . '/../../..' . '/lib/private/DB/DbDataCollector.php',
|
||||
'OC\\DB\\Exceptions\\DbalException' => __DIR__ . '/../../..' . '/lib/private/DB/Exceptions/DbalException.php',
|
||||
'OC\\DB\\MigrationException' => __DIR__ . '/../../..' . '/lib/private/DB/MigrationException.php',
|
||||
'OC\\DB\\MigrationService' => __DIR__ . '/../../..' . '/lib/private/DB/MigrationService.php',
|
||||
|
|
@ -1064,6 +1069,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OC\\DB\\MySQLMigrator' => __DIR__ . '/../../..' . '/lib/private/DB/MySQLMigrator.php',
|
||||
'OC\\DB\\MySqlTools' => __DIR__ . '/../../..' . '/lib/private/DB/MySqlTools.php',
|
||||
'OC\\DB\\OCSqlitePlatform' => __DIR__ . '/../../..' . '/lib/private/DB/OCSqlitePlatform.php',
|
||||
'OC\\DB\\ObjectParameter' => __DIR__ . '/../../..' . '/lib/private/DB/ObjectParameter.php',
|
||||
'OC\\DB\\OracleConnection' => __DIR__ . '/../../..' . '/lib/private/DB/OracleConnection.php',
|
||||
'OC\\DB\\OracleMigrator' => __DIR__ . '/../../..' . '/lib/private/DB/OracleMigrator.php',
|
||||
'OC\\DB\\PgSqlTools' => __DIR__ . '/../../..' . '/lib/private/DB/PgSqlTools.php',
|
||||
|
|
@ -1308,8 +1314,10 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OC\\Memcache\\CASTrait' => __DIR__ . '/../../..' . '/lib/private/Memcache/CASTrait.php',
|
||||
'OC\\Memcache\\Cache' => __DIR__ . '/../../..' . '/lib/private/Memcache/Cache.php',
|
||||
'OC\\Memcache\\Factory' => __DIR__ . '/../../..' . '/lib/private/Memcache/Factory.php',
|
||||
'OC\\Memcache\\LoggerWrapperCache' => __DIR__ . '/../../..' . '/lib/private/Memcache/LoggerWrapperCache.php',
|
||||
'OC\\Memcache\\Memcached' => __DIR__ . '/../../..' . '/lib/private/Memcache/Memcached.php',
|
||||
'OC\\Memcache\\NullCache' => __DIR__ . '/../../..' . '/lib/private/Memcache/NullCache.php',
|
||||
'OC\\Memcache\\ProfilerWrapperCache' => __DIR__ . '/../../..' . '/lib/private/Memcache/ProfilerWrapperCache.php',
|
||||
'OC\\Memcache\\Redis' => __DIR__ . '/../../..' . '/lib/private/Memcache/Redis.php',
|
||||
'OC\\MemoryInfo' => __DIR__ . '/../../..' . '/lib/private/MemoryInfo.php',
|
||||
'OC\\Migration\\BackgroundRepair' => __DIR__ . '/../../..' . '/lib/private/Migration/BackgroundRepair.php',
|
||||
|
|
@ -1376,6 +1384,10 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OC\\Profile\\Actions\\WebsiteAction' => __DIR__ . '/../../..' . '/lib/private/Profile/Actions/WebsiteAction.php',
|
||||
'OC\\Profile\\ProfileManager' => __DIR__ . '/../../..' . '/lib/private/Profile/ProfileManager.php',
|
||||
'OC\\Profile\\TProfileHelper' => __DIR__ . '/../../..' . '/lib/private/Profile/TProfileHelper.php',
|
||||
'OC\\Profiler\\FileProfilerStorage' => __DIR__ . '/../../..' . '/lib/private/Profiler/FileProfilerStorage.php',
|
||||
'OC\\Profiler\\Profile' => __DIR__ . '/../../..' . '/lib/private/Profiler/Profile.php',
|
||||
'OC\\Profiler\\Profiler' => __DIR__ . '/../../..' . '/lib/private/Profiler/Profiler.php',
|
||||
'OC\\Profiler\\RoutingDataCollector' => __DIR__ . '/../../..' . '/lib/private/Profiler/RoutingDataCollector.php',
|
||||
'OC\\RedisFactory' => __DIR__ . '/../../..' . '/lib/private/RedisFactory.php',
|
||||
'OC\\Remote\\Api\\ApiBase' => __DIR__ . '/../../..' . '/lib/private/Remote/Api/ApiBase.php',
|
||||
'OC\\Remote\\Api\\ApiCollection' => __DIR__ . '/../../..' . '/lib/private/Remote/Api/ApiCollection.php',
|
||||
|
|
|
|||
|
|
@ -34,11 +34,16 @@ namespace OC\AppFramework;
|
|||
use OC\AppFramework\DependencyInjection\DIContainer;
|
||||
use OC\AppFramework\Http\Dispatcher;
|
||||
use OC\AppFramework\Http\Request;
|
||||
use OC\Diagnostics\EventLogger;
|
||||
use OCP\Profiler\IProfiler;
|
||||
use OC\Profiler\RoutingDataCollector;
|
||||
use OCP\AppFramework\QueryException;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\ICallbackResponse;
|
||||
use OCP\AppFramework\Http\IOutput;
|
||||
use OCP\AppFramework\QueryException;
|
||||
use OCP\Diagnostics\IEventLogger;
|
||||
use OCP\HintException;
|
||||
use OCP\IConfig;
|
||||
use OCP\IRequest;
|
||||
|
||||
/**
|
||||
|
|
@ -114,20 +119,30 @@ class App {
|
|||
* @throws HintException
|
||||
*/
|
||||
public static function main(string $controllerName, string $methodName, DIContainer $container, array $urlParams = null) {
|
||||
/** @var IProfiler $profiler */
|
||||
$profiler = $container->get(IProfiler::class);
|
||||
$config = $container->get(IConfig::class);
|
||||
// Disable profiler on the profiler UI
|
||||
$profiler->setEnabled($profiler->isEnabled() && !is_null($urlParams) && isset($urlParams['_route']) && !str_starts_with($urlParams['_route'], 'profiler.'));
|
||||
if ($profiler->isEnabled()) {
|
||||
\OC::$server->get(IEventLogger::class)->activate();
|
||||
$profiler->add(new RoutingDataCollector($container['AppName'], $controllerName, $methodName));
|
||||
}
|
||||
|
||||
if (!is_null($urlParams)) {
|
||||
/** @var Request $request */
|
||||
$request = $container->query(IRequest::class);
|
||||
$request = $container->get(IRequest::class);
|
||||
$request->setUrlParameters($urlParams);
|
||||
} elseif (isset($container['urlParams']) && !is_null($container['urlParams'])) {
|
||||
/** @var Request $request */
|
||||
$request = $container->query(IRequest::class);
|
||||
$request = $container->get(IRequest::class);
|
||||
$request->setUrlParameters($container['urlParams']);
|
||||
}
|
||||
$appName = $container['AppName'];
|
||||
|
||||
// first try $controllerName then go for \OCA\AppName\Controller\$controllerName
|
||||
try {
|
||||
$controller = $container->query($controllerName);
|
||||
$controller = $container->get($controllerName);
|
||||
} catch (QueryException $e) {
|
||||
if (strpos($controllerName, '\\Controller\\') !== false) {
|
||||
// This is from a global registered app route that is not enabled.
|
||||
|
|
@ -158,6 +173,16 @@ class App {
|
|||
|
||||
$io = $container[IOutput::class];
|
||||
|
||||
if ($profiler->isEnabled()) {
|
||||
/** @var EventLogger $eventLogger */
|
||||
$eventLogger = $container->get(IEventLogger::class);
|
||||
$eventLogger->end('runtime');
|
||||
$profile = $profiler->collect($container->get(IRequest::class), $response);
|
||||
$profiler->saveProfile($profile);
|
||||
$io->setHeader('X-Debug-Token:' . $profile->getToken());
|
||||
$io->setHeader('Server-Timing: token;desc="' . $profile->getToken() . '"');
|
||||
}
|
||||
|
||||
if (!is_null($httpHeaders)) {
|
||||
$io->setHeader($httpHeaders);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ use Psr\Container\ContainerInterface;
|
|||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use ReflectionParameter;
|
||||
use ReflectionNamedType;
|
||||
use function class_exists;
|
||||
|
||||
/**
|
||||
|
|
@ -78,12 +79,13 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
|
|||
$resolveName = $parameter->getName();
|
||||
|
||||
// try to find out if it is a class or a simple parameter
|
||||
if ($parameterType !== null && !$parameterType->isBuiltin()) {
|
||||
if ($parameterType !== null && ($parameterType instanceof ReflectionNamedType) && !$parameterType->isBuiltin()) {
|
||||
$resolveName = $parameterType->getName();
|
||||
}
|
||||
|
||||
try {
|
||||
$builtIn = $parameter->hasType() && $parameter->getType()->isBuiltin();
|
||||
$builtIn = $parameter->hasType() && ($parameter->getType() instanceof ReflectionNamedType)
|
||||
&& $parameter->getType()->isBuiltin();
|
||||
return $this->query($resolveName, !$builtIn);
|
||||
} catch (QueryException $e) {
|
||||
// Service not found, use the default value when available
|
||||
|
|
@ -91,7 +93,7 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
|
|||
return $parameter->getDefaultValue();
|
||||
}
|
||||
|
||||
if ($parameterType !== null && !$parameterType->isBuiltin()) {
|
||||
if ($parameterType !== null && ($parameterType instanceof ReflectionNamedType) && !$parameterType->isBuiltin()) {
|
||||
$resolveName = $parameter->getName();
|
||||
try {
|
||||
return $this->query($resolveName);
|
||||
|
|
|
|||
|
|
@ -115,4 +115,8 @@ class CappedMemoryCache implements ICache, \ArrayAccess {
|
|||
$this->remove($key);
|
||||
}
|
||||
}
|
||||
|
||||
public static function isAvailable(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -203,4 +203,8 @@ class File implements ICache {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function isAvailable(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ use Doctrine\DBAL\Driver;
|
|||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Exception\ConstraintViolationException;
|
||||
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
|
||||
use Doctrine\DBAL\Logging\DebugStack;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\OraclePlatform;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
|
||||
|
|
@ -55,6 +56,7 @@ use OCP\PreConditionNotMetException;
|
|||
use OC\DB\QueryBuilder\QueryBuilder;
|
||||
use OC\SystemConfig;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use OCP\Profiler\IProfiler;
|
||||
|
||||
class Connection extends \Doctrine\DBAL\Connection {
|
||||
/** @var string */
|
||||
|
|
@ -76,6 +78,9 @@ class Connection extends \Doctrine\DBAL\Connection {
|
|||
/** @var int */
|
||||
protected $queriesExecuted = 0;
|
||||
|
||||
/** @var DbDataCollector|null */
|
||||
protected $dbDataCollector = null;
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the Connection class.
|
||||
*
|
||||
|
|
@ -102,6 +107,16 @@ class Connection extends \Doctrine\DBAL\Connection {
|
|||
|
||||
$this->systemConfig = \OC::$server->getSystemConfig();
|
||||
$this->logger = \OC::$server->get(LoggerInterface::class);
|
||||
|
||||
/** @var \OCP\Profiler\IProfiler */
|
||||
$profiler = \OC::$server->get(IProfiler::class);
|
||||
if ($profiler->isEnabled()) {
|
||||
$this->dbDataCollector = new DbDataCollector($this);
|
||||
$profiler->add($this->dbDataCollector);
|
||||
$debugStack = new DebugStack();
|
||||
$this->dbDataCollector->setDebugStack($debugStack);
|
||||
$this->_config->setSQLLogger($debugStack);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
154
lib/private/DB/DbDataCollector.php
Normal file
154
lib/private/DB/DbDataCollector.php
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
/**
|
||||
* @copyright 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @author Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\DB;
|
||||
|
||||
use Doctrine\DBAL\Logging\DebugStack;
|
||||
use Doctrine\DBAL\Types\ConversionException;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use OC\AppFramework\Http\Request;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
|
||||
class DbDataCollector extends \OCP\DataCollector\AbstractDataCollector {
|
||||
protected ?DebugStack $debugStack = null;
|
||||
private Connection $connection;
|
||||
|
||||
/**
|
||||
* DbDataCollector constructor.
|
||||
*/
|
||||
public function __construct(Connection $connection) {
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
public function setDebugStack(DebugStack $debugStack, $name = 'default'): void {
|
||||
$this->debugStack = $debugStack;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function collect(Request $request, Response $response, \Throwable $exception = null): void {
|
||||
$queries = $this->sanitizeQueries($this->debugStack->queries);
|
||||
|
||||
$this->data = [
|
||||
'queries' => $queries,
|
||||
];
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return 'db';
|
||||
}
|
||||
|
||||
public function getQueries(): array {
|
||||
return $this->data['queries'];
|
||||
}
|
||||
|
||||
private function sanitizeQueries(array $queries): array {
|
||||
foreach ($queries as $i => $query) {
|
||||
$queries[$i] = $this->sanitizeQuery($query);
|
||||
}
|
||||
|
||||
return $queries;
|
||||
}
|
||||
|
||||
private function sanitizeQuery(array $query): array {
|
||||
$query['explainable'] = true;
|
||||
$query['runnable'] = true;
|
||||
if (null === $query['params']) {
|
||||
$query['params'] = [];
|
||||
}
|
||||
if (!\is_array($query['params'])) {
|
||||
$query['params'] = [$query['params']];
|
||||
}
|
||||
if (!\is_array($query['types'])) {
|
||||
$query['types'] = [];
|
||||
}
|
||||
foreach ($query['params'] as $j => $param) {
|
||||
$e = null;
|
||||
if (isset($query['types'][$j])) {
|
||||
// Transform the param according to the type
|
||||
$type = $query['types'][$j];
|
||||
if (\is_string($type)) {
|
||||
$type = Type::getType($type);
|
||||
}
|
||||
if ($type instanceof Type) {
|
||||
$query['types'][$j] = $type->getBindingType();
|
||||
try {
|
||||
$param = $type->convertToDatabaseValue($param, $this->connection->getDatabasePlatform());
|
||||
} catch (\TypeError $e) {
|
||||
} catch (ConversionException $e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[$query['params'][$j], $explainable, $runnable] = $this->sanitizeParam($param, $e);
|
||||
if (!$explainable) {
|
||||
$query['explainable'] = false;
|
||||
}
|
||||
|
||||
if (!$runnable) {
|
||||
$query['runnable'] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes a param.
|
||||
*
|
||||
* The return value is an array with the sanitized value and a boolean
|
||||
* indicating if the original value was kept (allowing to use the sanitized
|
||||
* value to explain the query).
|
||||
*/
|
||||
private function sanitizeParam($var, ?\Throwable $error): array {
|
||||
if (\is_object($var)) {
|
||||
return [$o = new ObjectParameter($var, $error), false, $o->isStringable() && !$error];
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
return ['⚠ '.$error->getMessage(), false, false];
|
||||
}
|
||||
|
||||
if (\is_array($var)) {
|
||||
$a = [];
|
||||
$explainable = $runnable = true;
|
||||
foreach ($var as $k => $v) {
|
||||
[$value, $e, $r] = $this->sanitizeParam($v, null);
|
||||
$explainable = $explainable && $e;
|
||||
$runnable = $runnable && $r;
|
||||
$a[$k] = $value;
|
||||
}
|
||||
|
||||
return [$a, $explainable, $runnable];
|
||||
}
|
||||
|
||||
if (\is_resource($var)) {
|
||||
return [sprintf('/* Resource(%s) */', get_resource_type($var)), false, false];
|
||||
}
|
||||
|
||||
return [$var, true, true];
|
||||
}
|
||||
}
|
||||
71
lib/private/DB/ObjectParameter.php
Normal file
71
lib/private/DB/ObjectParameter.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
/**
|
||||
* @copyright 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @author Carl Schwan <carl@carlschwan.eu>
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later AND MIT
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\DB;
|
||||
|
||||
final class ObjectParameter {
|
||||
private $object;
|
||||
private $error;
|
||||
private $stringable;
|
||||
private $class;
|
||||
|
||||
/**
|
||||
* @param object $object
|
||||
*/
|
||||
public function __construct($object, ?\Throwable $error) {
|
||||
$this->object = $object;
|
||||
$this->error = $error;
|
||||
$this->stringable = \is_callable([$object, '__toString']);
|
||||
$this->class = \get_class($object);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object
|
||||
*/
|
||||
public function getObject() {
|
||||
return $this->object;
|
||||
}
|
||||
|
||||
public function getError(): ?\Throwable {
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
public function isStringable(): bool {
|
||||
return $this->stringable;
|
||||
}
|
||||
|
||||
public function getClass(): string {
|
||||
return $this->class;
|
||||
}
|
||||
}
|
||||
|
|
@ -60,7 +60,8 @@ class EventLogger implements IEventLogger {
|
|||
}
|
||||
|
||||
public function isLoggingActivated(): bool {
|
||||
$systemValue = (bool)$this->config->getValue('diagnostics.logging', false);
|
||||
$systemValue = (bool)$this->config->getValue('diagnostics.logging', false)
|
||||
|| (bool)$this->config->getValue('profiler', false);
|
||||
|
||||
if ($systemValue && $this->config->getValue('debug', false)) {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -148,10 +148,7 @@ class APCu extends Cache implements IMemcache {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function isAvailable() {
|
||||
public static function isAvailable(): bool {
|
||||
if (!extension_loaded('apcu')) {
|
||||
return false;
|
||||
} elseif (!\OC::$server->get(IniGetWrapper::class)->getBool('apc.enabled')) {
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ class ArrayCache extends Cache implements IMemcache {
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function isAvailable() {
|
||||
public static function isAvailable(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
*/
|
||||
namespace OC\Memcache;
|
||||
|
||||
use OCP\Profiler\IProfiler;
|
||||
use OCP\ICache;
|
||||
use OCP\ICacheFactory;
|
||||
use OCP\IMemcache;
|
||||
|
|
@ -39,39 +40,39 @@ use Psr\Log\LoggerInterface;
|
|||
class Factory implements ICacheFactory {
|
||||
public const NULL_CACHE = NullCache::class;
|
||||
|
||||
/**
|
||||
* @var string $globalPrefix
|
||||
*/
|
||||
private $globalPrefix;
|
||||
private string $globalPrefix;
|
||||
|
||||
private LoggerInterface $logger;
|
||||
|
||||
/**
|
||||
* @var string $localCacheClass
|
||||
* @var ?class-string<ICache> $localCacheClass
|
||||
*/
|
||||
private $localCacheClass;
|
||||
private ?string $localCacheClass;
|
||||
|
||||
/**
|
||||
* @var string $distributedCacheClass
|
||||
* @var ?class-string<ICache> $distributedCacheClass
|
||||
*/
|
||||
private $distributedCacheClass;
|
||||
private ?string $distributedCacheClass;
|
||||
|
||||
/**
|
||||
* @var string $lockingCacheClass
|
||||
* @var ?class-string<IMemcache> $lockingCacheClass
|
||||
*/
|
||||
private $lockingCacheClass;
|
||||
private ?string $lockingCacheClass;
|
||||
|
||||
/** @var string */
|
||||
private $logFile;
|
||||
private string $logFile;
|
||||
|
||||
public function __construct(
|
||||
string $globalPrefix,
|
||||
LoggerInterface $logger,
|
||||
?string $localCacheClass = null,
|
||||
?string $distributedCacheClass = null,
|
||||
?string $lockingCacheClass = null,
|
||||
string $logFile = ''
|
||||
) {
|
||||
private IProfiler $profiler;
|
||||
|
||||
/**
|
||||
* @param string $globalPrefix
|
||||
* @param LoggerInterface $logger
|
||||
* @param ?class-string<ICache> $localCacheClass
|
||||
* @param ?class-string<ICache> $distributedCacheClass
|
||||
* @param ?class-string<IMemcache> $lockingCacheClass
|
||||
* @param string $logFile
|
||||
*/
|
||||
public function __construct(string $globalPrefix, LoggerInterface $logger, IProfiler $profiler,
|
||||
?string $localCacheClass = null, ?string $distributedCacheClass = null, ?string $lockingCacheClass = null, string $logFile = '') {
|
||||
$this->logger = $logger;
|
||||
$this->logFile = $logFile;
|
||||
$this->globalPrefix = $globalPrefix;
|
||||
|
|
@ -103,6 +104,7 @@ class Factory implements ICacheFactory {
|
|||
$this->localCacheClass = $localCacheClass;
|
||||
$this->distributedCacheClass = $distributedCacheClass;
|
||||
$this->lockingCacheClass = $lockingCacheClass;
|
||||
$this->profiler = $profiler;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -112,7 +114,19 @@ class Factory implements ICacheFactory {
|
|||
* @return IMemcache
|
||||
*/
|
||||
public function createLocking(string $prefix = ''): IMemcache {
|
||||
return new $this->lockingCacheClass($this->globalPrefix . '/' . $prefix, $this->logFile);
|
||||
assert($this->lockingCacheClass !== null);
|
||||
$cache = new $this->lockingCacheClass($this->globalPrefix . '/' . $prefix);
|
||||
if ($this->profiler->isEnabled() && $this->lockingCacheClass === '\OC\Memcache\Redis') {
|
||||
// We only support the profiler with Redis
|
||||
$cache = new ProfilerWrapperCache($cache, 'Locking');
|
||||
$this->profiler->add($cache);
|
||||
}
|
||||
|
||||
if ($this->lockingCacheClass === Redis::class &&
|
||||
$this->logFile !== '' && is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile))) {
|
||||
$cache = new LoggerWrapperCache($cache, $this->logFile);
|
||||
}
|
||||
return $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -122,7 +136,19 @@ class Factory implements ICacheFactory {
|
|||
* @return ICache
|
||||
*/
|
||||
public function createDistributed(string $prefix = ''): ICache {
|
||||
return new $this->distributedCacheClass($this->globalPrefix . '/' . $prefix, $this->logFile);
|
||||
assert($this->distributedCacheClass !== null);
|
||||
$cache = new $this->distributedCacheClass($this->globalPrefix . '/' . $prefix);
|
||||
if ($this->profiler->isEnabled() && $this->distributedCacheClass === '\OC\Memcache\Redis') {
|
||||
// We only support the profiler with Redis
|
||||
$cache = new ProfilerWrapperCache($cache, 'Distributed');
|
||||
$this->profiler->add($cache);
|
||||
}
|
||||
|
||||
if ($this->distributedCacheClass === Redis::class && $this->logFile !== ''
|
||||
&& is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile))) {
|
||||
$cache = new LoggerWrapperCache($cache, $this->logFile);
|
||||
}
|
||||
return $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -132,7 +158,19 @@ class Factory implements ICacheFactory {
|
|||
* @return ICache
|
||||
*/
|
||||
public function createLocal(string $prefix = ''): ICache {
|
||||
return new $this->localCacheClass($this->globalPrefix . '/' . $prefix, $this->logFile);
|
||||
assert($this->localCacheClass !== null);
|
||||
$cache = new $this->localCacheClass($this->globalPrefix . '/' . $prefix);
|
||||
if ($this->profiler->isEnabled() && $this->localCacheClass === '\OC\Memcache\Redis') {
|
||||
// We only support the profiler with Redis
|
||||
$cache = new ProfilerWrapperCache($cache, 'Local');
|
||||
$this->profiler->add($cache);
|
||||
}
|
||||
|
||||
if ($this->localCacheClass === Redis::class && $this->logFile !== ''
|
||||
&& is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile))) {
|
||||
$cache = new LoggerWrapperCache($cache, $this->logFile);
|
||||
}
|
||||
return $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
177
lib/private/Memcache/LoggerWrapperCache.php
Normal file
177
lib/private/Memcache/LoggerWrapperCache.php
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
/**
|
||||
* @copyright 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @author Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Memcache;
|
||||
|
||||
use OCP\IMemcacheTTL;
|
||||
|
||||
/**
|
||||
* Cache wrapper that logs the cache operation in a log file
|
||||
*/
|
||||
class LoggerWrapperCache extends Cache implements IMemcacheTTL {
|
||||
/** @var Redis */
|
||||
protected $wrappedCache;
|
||||
|
||||
/** @var string $logFile */
|
||||
private $logFile;
|
||||
|
||||
/** @var string $prefix */
|
||||
protected $prefix;
|
||||
|
||||
public function __construct(Redis $wrappedCache, string $logFile) {
|
||||
parent::__construct($wrappedCache->getPrefix());
|
||||
$this->wrappedCache = $wrappedCache;
|
||||
$this->logFile = $logFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Prefix used for caching purposes
|
||||
*/
|
||||
public function getPrefix() {
|
||||
return $this->prefix;
|
||||
}
|
||||
|
||||
protected function getNameSpace() {
|
||||
return $this->prefix;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function get($key) {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::get::' . $key . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
return $this->wrappedCache->get($key);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function set($key, $value, $ttl = 0) {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::set::' . $key . '::' . $ttl . '::' . json_encode($value) . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
return $this->wrappedCache->set($key, $value, $$ttl);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function hasKey($key) {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::hasKey::' . $key . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
return $this->wrappedCache->hasKey($key);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function remove($key) {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::remove::' . $key . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
return $this->wrappedCache->remove($key);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function clear($prefix = '') {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::clear::' . $prefix . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
return $this->wrappedCache->clear($prefix);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function add($key, $value, $ttl = 0) {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::add::' . $key . '::' . $value . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
return $this->wrappedCache->add($key, $value, $ttl);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function inc($key, $step = 1) {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::inc::' . $key . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
return $this->wrappedCache->inc($key, $step);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function dec($key, $step = 1) {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::dec::' . $key . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
return $this->wrappedCache->dec($key, $step);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function cas($key, $old, $new) {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::cas::' . $key . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
return $this->wrappedCache->cas($key, $old, $new);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function cad($key, $old) {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::cad::' . $key . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
return $this->wrappedCache->cad($key, $old);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function setTTL($key, $ttl) {
|
||||
$this->wrappedCache->setTTL($key, $ttl);
|
||||
}
|
||||
|
||||
public static function isAvailable(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -196,7 +196,7 @@ class Memcached extends Cache implements IMemcache {
|
|||
return $result;
|
||||
}
|
||||
|
||||
public static function isAvailable() {
|
||||
public static function isAvailable(): bool {
|
||||
return extension_loaded('memcached');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class NullCache extends Cache implements \OCP\IMemcache {
|
|||
return true;
|
||||
}
|
||||
|
||||
public static function isAvailable() {
|
||||
public static function isAvailable(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
220
lib/private/Memcache/ProfilerWrapperCache.php
Normal file
220
lib/private/Memcache/ProfilerWrapperCache.php
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
/**
|
||||
* @copyright 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @author Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Memcache;
|
||||
|
||||
use OC\AppFramework\Http\Request;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\DataCollector\AbstractDataCollector;
|
||||
use OCP\IMemcacheTTL;
|
||||
|
||||
/**
|
||||
* Cache wrapper that logs profiling information
|
||||
*/
|
||||
class ProfilerWrapperCache extends AbstractDataCollector implements IMemcacheTTL, \ArrayAccess {
|
||||
/** @var Redis $wrappedCache*/
|
||||
protected $wrappedCache;
|
||||
|
||||
/** @var string $prefix */
|
||||
protected $prefix;
|
||||
|
||||
/** @var string $type */
|
||||
private $type;
|
||||
|
||||
public function __construct(Redis $wrappedCache, string $type) {
|
||||
$this->prefix = $wrappedCache->getPrefix();
|
||||
$this->wrappedCache = $wrappedCache;
|
||||
$this->type = $type;
|
||||
$this->data['queries'] = [];
|
||||
$this->data['cacheHit'] = 0;
|
||||
$this->data['cacheMiss'] = 0;
|
||||
}
|
||||
|
||||
public function getPrefix(): string {
|
||||
return $this->prefix;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function get($key) {
|
||||
$start = microtime(true);
|
||||
$ret = $this->wrappedCache->get($key);
|
||||
if ($ret === null) {
|
||||
$this->data['cacheMiss']++;
|
||||
} else {
|
||||
$this->data['cacheHit']++;
|
||||
}
|
||||
$this->data['queries'][] = [
|
||||
'start' => $start,
|
||||
'end' => microtime(true),
|
||||
'op' => $this->getPrefix() . '::get::' . $key,
|
||||
];
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function set($key, $value, $ttl = 0) {
|
||||
$start = microtime(true);
|
||||
$ret = $this->wrappedCache->set($key, $value, $ttl);
|
||||
$this->data['queries'][] = [
|
||||
'start' => $start,
|
||||
'end' => microtime(true),
|
||||
'op' => $this->getPrefix() . '::set::' . $key,
|
||||
];
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function hasKey($key) {
|
||||
$start = microtime(true);
|
||||
$ret = $this->wrappedCache->hasKey($key);
|
||||
$this->data['queries'][] = [
|
||||
'start' => $start,
|
||||
'end' => microtime(true),
|
||||
'op' => $this->getPrefix() . '::hasKey::' . $key,
|
||||
];
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function remove($key) {
|
||||
$start = microtime(true);
|
||||
$ret = $this->wrappedCache->remove($key);
|
||||
$this->data['queries'][] = [
|
||||
'start' => $start,
|
||||
'end' => microtime(true),
|
||||
'op' => $this->getPrefix() . '::remove::' . $key,
|
||||
];
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function clear($prefix = '') {
|
||||
$start = microtime(true);
|
||||
$ret = $this->wrappedCache->clear($prefix);
|
||||
$this->data['queries'][] = [
|
||||
'start' => $start,
|
||||
'end' => microtime(true),
|
||||
'op' => $this->getPrefix() . '::clear::' . $prefix,
|
||||
];
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function add($key, $value, $ttl = 0) {
|
||||
$start = microtime(true);
|
||||
$ret = $this->wrappedCache->add($key, $value, $ttl);
|
||||
$this->data['queries'][] = [
|
||||
'start' => $start,
|
||||
'end' => microtime(true),
|
||||
'op' => $this->getPrefix() . '::add::' . $key,
|
||||
];
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function inc($key, $step = 1) {
|
||||
$start = microtime(true);
|
||||
$ret = $this->wrappedCache->inc($key, $step);
|
||||
$this->data['queries'][] = [
|
||||
'start' => $start,
|
||||
'end' => microtime(true),
|
||||
'op' => $this->getPrefix() . '::inc::' . $key,
|
||||
];
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function dec($key, $step = 1) {
|
||||
$start = microtime(true);
|
||||
$ret = $this->wrappedCache->dec($key, $step);
|
||||
$this->data['queries'][] = [
|
||||
'start' => $start,
|
||||
'end' => microtime(true),
|
||||
'op' => $this->getPrefix() . '::dev::' . $key,
|
||||
];
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function cas($key, $old, $new) {
|
||||
$start = microtime(true);
|
||||
$ret = $this->wrappedCache->cas($key, $old, $new);
|
||||
$this->data['queries'][] = [
|
||||
'start' => $start,
|
||||
'end' => microtime(true),
|
||||
'op' => $this->getPrefix() . '::cas::' . $key,
|
||||
];
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function cad($key, $old) {
|
||||
$start = microtime(true);
|
||||
$ret = $this->wrappedCache->cad($key, $old);
|
||||
$this->data['queries'][] = [
|
||||
'start' => $start,
|
||||
'end' => microtime(true),
|
||||
'op' => $this->getPrefix() . '::cad::' . $key,
|
||||
];
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function setTTL($key, $ttl) {
|
||||
$this->wrappedCache->setTTL($key, $ttl);
|
||||
}
|
||||
|
||||
public function offsetExists($offset): bool {
|
||||
return $this->hasKey($offset);
|
||||
}
|
||||
|
||||
public function offsetSet($offset, $value): void {
|
||||
$this->set($offset, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($offset) {
|
||||
return $this->get($offset);
|
||||
}
|
||||
|
||||
public function offsetUnset($offset): void {
|
||||
$this->remove($offset);
|
||||
}
|
||||
|
||||
public function collect(Request $request, Response $response, \Throwable $exception = null): void {
|
||||
// Nothing to do here $data is already ready
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return 'cache/' . $this->type . '/' . $this->prefix;
|
||||
}
|
||||
|
||||
public static function isAvailable(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -37,38 +37,16 @@ class Redis extends Cache implements IMemcacheTTL {
|
|||
*/
|
||||
private static $cache = null;
|
||||
|
||||
private $logFile;
|
||||
|
||||
public function __construct($prefix = '', string $logFile = '') {
|
||||
parent::__construct($prefix);
|
||||
$this->logFile = $logFile;
|
||||
if (is_null(self::$cache)) {
|
||||
self::$cache = \OC::$server->getGetRedisFactory()->getInstance();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* entries in redis get namespaced to prevent collisions between ownCloud instances and users
|
||||
*/
|
||||
protected function getNameSpace() {
|
||||
return $this->prefix;
|
||||
}
|
||||
|
||||
private function logEnabled(): bool {
|
||||
return $this->logFile !== '' && is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile));
|
||||
}
|
||||
|
||||
public function get($key) {
|
||||
if ($this->logEnabled()) {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::get::' . $key . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
|
||||
$result = self::$cache->get($this->getNameSpace() . $key);
|
||||
if ($result === false && !self::$cache->exists($this->getNameSpace() . $key)) {
|
||||
$result = self::$cache->get($this->getPrefix() . $key);
|
||||
if ($result === false && !self::$cache->exists($this->getPrefix() . $key)) {
|
||||
return null;
|
||||
} else {
|
||||
return json_decode($result, true);
|
||||
|
|
@ -76,43 +54,19 @@ class Redis extends Cache implements IMemcacheTTL {
|
|||
}
|
||||
|
||||
public function set($key, $value, $ttl = 0) {
|
||||
if ($this->logEnabled()) {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::set::' . $key . '::' . $ttl . '::' . json_encode($value) . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
|
||||
if ($ttl > 0) {
|
||||
return self::$cache->setex($this->getNameSpace() . $key, $ttl, json_encode($value));
|
||||
return self::$cache->setex($this->getPrefix() . $key, $ttl, json_encode($value));
|
||||
} else {
|
||||
return self::$cache->set($this->getNameSpace() . $key, json_encode($value));
|
||||
return self::$cache->set($this->getPrefix() . $key, json_encode($value));
|
||||
}
|
||||
}
|
||||
|
||||
public function hasKey($key) {
|
||||
if ($this->logEnabled()) {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::hasKey::' . $key . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
|
||||
return (bool)self::$cache->exists($this->getNameSpace() . $key);
|
||||
return (bool)self::$cache->exists($this->getPrefix() . $key);
|
||||
}
|
||||
|
||||
public function remove($key) {
|
||||
if ($this->logEnabled()) {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::remove::' . $key . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
|
||||
if (self::$cache->del($this->getNameSpace() . $key)) {
|
||||
if (self::$cache->del($this->getPrefix() . $key)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
|
@ -120,15 +74,7 @@ class Redis extends Cache implements IMemcacheTTL {
|
|||
}
|
||||
|
||||
public function clear($prefix = '') {
|
||||
if ($this->logEnabled()) {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::clear::' . $prefix . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
|
||||
$prefix = $this->getNameSpace() . $prefix . '*';
|
||||
$prefix = $this->getPrefix() . $prefix . '*';
|
||||
$keys = self::$cache->keys($prefix);
|
||||
$deleted = self::$cache->del($keys);
|
||||
|
||||
|
|
@ -153,14 +99,6 @@ class Redis extends Cache implements IMemcacheTTL {
|
|||
if ($ttl !== 0 && is_int($ttl)) {
|
||||
$args['ex'] = $ttl;
|
||||
}
|
||||
if ($this->logEnabled()) {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::add::' . $key . '::' . $value . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return self::$cache->set($this->getPrefix() . $key, $value, $args);
|
||||
}
|
||||
|
|
@ -173,15 +111,7 @@ class Redis extends Cache implements IMemcacheTTL {
|
|||
* @return int | bool
|
||||
*/
|
||||
public function inc($key, $step = 1) {
|
||||
if ($this->logEnabled()) {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::inc::' . $key . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
|
||||
return self::$cache->incrBy($this->getNameSpace() . $key, $step);
|
||||
return self::$cache->incrBy($this->getPrefix() . $key, $step);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -192,18 +122,10 @@ class Redis extends Cache implements IMemcacheTTL {
|
|||
* @return int | bool
|
||||
*/
|
||||
public function dec($key, $step = 1) {
|
||||
if ($this->logEnabled()) {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::dec::' . $key . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
|
||||
if (!$this->hasKey($key)) {
|
||||
return false;
|
||||
}
|
||||
return self::$cache->decrBy($this->getNameSpace() . $key, $step);
|
||||
return self::$cache->decrBy($this->getPrefix() . $key, $step);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -215,21 +137,13 @@ class Redis extends Cache implements IMemcacheTTL {
|
|||
* @return bool
|
||||
*/
|
||||
public function cas($key, $old, $new) {
|
||||
if ($this->logEnabled()) {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::cas::' . $key . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
|
||||
if (!is_int($new)) {
|
||||
$new = json_encode($new);
|
||||
}
|
||||
self::$cache->watch($this->getNameSpace() . $key);
|
||||
self::$cache->watch($this->getPrefix() . $key);
|
||||
if ($this->get($key) === $old) {
|
||||
$result = self::$cache->multi()
|
||||
->set($this->getNameSpace() . $key, $new)
|
||||
->set($this->getPrefix() . $key, $new)
|
||||
->exec();
|
||||
return $result !== false;
|
||||
}
|
||||
|
|
@ -245,18 +159,10 @@ class Redis extends Cache implements IMemcacheTTL {
|
|||
* @return bool
|
||||
*/
|
||||
public function cad($key, $old) {
|
||||
if ($this->logEnabled()) {
|
||||
file_put_contents(
|
||||
$this->logFile,
|
||||
$this->getNameSpace() . '::cad::' . $key . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
|
||||
self::$cache->watch($this->getNameSpace() . $key);
|
||||
self::$cache->watch($this->getPrefix() . $key);
|
||||
if ($this->get($key) === $old) {
|
||||
$result = self::$cache->multi()
|
||||
->del($this->getNameSpace() . $key)
|
||||
->del($this->getPrefix() . $key)
|
||||
->exec();
|
||||
return $result !== false;
|
||||
}
|
||||
|
|
@ -265,10 +171,10 @@ class Redis extends Cache implements IMemcacheTTL {
|
|||
}
|
||||
|
||||
public function setTTL($key, $ttl) {
|
||||
self::$cache->expire($this->getNameSpace() . $key, $ttl);
|
||||
self::$cache->expire($this->getPrefix() . $key, $ttl);
|
||||
}
|
||||
|
||||
public static function isAvailable() {
|
||||
public static function isAvailable(): bool {
|
||||
return \OC::$server->getGetRedisFactory()->isAvailable();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
286
lib/private/Profiler/FileProfilerStorage.php
Normal file
286
lib/private/Profiler/FileProfilerStorage.php
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
/**
|
||||
* @copyright 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @author Carl Schwan <carl@carlschwan.eu>
|
||||
* @author Alexandre Salomé <alexandre.salome@gmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later AND MIT
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Profiler;
|
||||
|
||||
use OCP\Profiler\IProfile;
|
||||
|
||||
/**
|
||||
* Storage for profiler using files.
|
||||
*/
|
||||
class FileProfilerStorage {
|
||||
// Folder where profiler data are stored.
|
||||
private string $folder;
|
||||
|
||||
/**
|
||||
* Constructs the file storage using a "dsn-like" path.
|
||||
*
|
||||
* Example : "file:/path/to/the/storage/folder"
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function __construct(string $folder) {
|
||||
$this->folder = $folder;
|
||||
|
||||
if (!is_dir($this->folder) && false === @mkdir($this->folder, 0777, true) && !is_dir($this->folder)) {
|
||||
throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $this->folder));
|
||||
}
|
||||
}
|
||||
|
||||
public function find(?string $url, ?int $limit, ?string $method, int $start = null, int $end = null, string $statusCode = null): array {
|
||||
$file = $this->getIndexFilename();
|
||||
|
||||
if (!file_exists($file)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$file = fopen($file, 'r');
|
||||
fseek($file, 0, \SEEK_END);
|
||||
|
||||
$result = [];
|
||||
while (\count($result) < $limit && $line = $this->readLineFromFile($file)) {
|
||||
$values = str_getcsv($line);
|
||||
[$csvToken, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode] = $values;
|
||||
$csvTime = (int) $csvTime;
|
||||
|
||||
if ($url && false === strpos($csvUrl, $url) || $method && false === strpos($csvMethod, $method) || $statusCode && false === strpos($csvStatusCode, $statusCode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($start) && $csvTime < $start) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($end) && $csvTime > $end) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[$csvToken] = [
|
||||
'token' => $csvToken,
|
||||
'method' => $csvMethod,
|
||||
'url' => $csvUrl,
|
||||
'time' => $csvTime,
|
||||
'parent' => $csvParent,
|
||||
'status_code' => $csvStatusCode,
|
||||
];
|
||||
}
|
||||
|
||||
fclose($file);
|
||||
|
||||
return array_values($result);
|
||||
}
|
||||
|
||||
public function purge(): void {
|
||||
$flags = \FilesystemIterator::SKIP_DOTS;
|
||||
$iterator = new \RecursiveDirectoryIterator($this->folder, $flags);
|
||||
$iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if (is_file($file)) {
|
||||
unlink($file);
|
||||
} else {
|
||||
rmdir($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function read(string $token): ?IProfile {
|
||||
if (!$token || !file_exists($file = $this->getFilename($token))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (\function_exists('gzcompress')) {
|
||||
$file = 'compress.zlib://'.$file;
|
||||
}
|
||||
|
||||
return $this->createProfileFromData($token, unserialize(file_get_contents($file)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function write(IProfile $profile): bool {
|
||||
$file = $this->getFilename($profile->getToken());
|
||||
|
||||
$profileIndexed = is_file($file);
|
||||
if (!$profileIndexed) {
|
||||
// Create directory
|
||||
$dir = \dirname($file);
|
||||
if (!is_dir($dir) && false === @mkdir($dir, 0777, true) && !is_dir($dir)) {
|
||||
throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $dir));
|
||||
}
|
||||
}
|
||||
|
||||
$profileToken = $profile->getToken();
|
||||
// when there are errors in sub-requests, the parent and/or children tokens
|
||||
// may equal the profile token, resulting in infinite loops
|
||||
$parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null;
|
||||
$childrenToken = array_filter(array_map(function (IProfile $p) use ($profileToken) {
|
||||
return $profileToken !== $p->getToken() ? $p->getToken() : null;
|
||||
}, $profile->getChildren()));
|
||||
|
||||
// Store profile
|
||||
$data = [
|
||||
'token' => $profileToken,
|
||||
'parent' => $parentToken,
|
||||
'children' => $childrenToken,
|
||||
'data' => $profile->getCollectors(),
|
||||
'method' => $profile->getMethod(),
|
||||
'url' => $profile->getUrl(),
|
||||
'time' => $profile->getTime(),
|
||||
'status_code' => $profile->getStatusCode(),
|
||||
];
|
||||
|
||||
$context = stream_context_create();
|
||||
|
||||
if (\function_exists('gzcompress')) {
|
||||
$file = 'compress.zlib://'.$file;
|
||||
stream_context_set_option($context, 'zlib', 'level', 3);
|
||||
}
|
||||
|
||||
if (false === file_put_contents($file, serialize($data), 0, $context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$profileIndexed) {
|
||||
// Add to index
|
||||
if (false === $file = fopen($this->getIndexFilename(), 'a')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fputcsv($file, [
|
||||
$profile->getToken(),
|
||||
$profile->getMethod(),
|
||||
$profile->getUrl(),
|
||||
$profile->getTime(),
|
||||
$profile->getParentToken(),
|
||||
$profile->getStatusCode(),
|
||||
]);
|
||||
fclose($file);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets filename to store data, associated to the token.
|
||||
*
|
||||
* @return string The profile filename
|
||||
*/
|
||||
protected function getFilename(string $token): string {
|
||||
// Uses 4 last characters, because first are mostly the same.
|
||||
$folderA = substr($token, -2, 2);
|
||||
$folderB = substr($token, -4, 2);
|
||||
|
||||
return $this->folder.'/'.$folderA.'/'.$folderB.'/'.$token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index filename.
|
||||
*
|
||||
* @return string The index filename
|
||||
*/
|
||||
protected function getIndexFilename(): string {
|
||||
return $this->folder.'/index.csv';
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a line in the file, backward.
|
||||
*
|
||||
* This function automatically skips the empty lines and do not include the line return in result value.
|
||||
*
|
||||
* @param resource $file The file resource, with the pointer placed at the end of the line to read
|
||||
*
|
||||
* @return ?string A string representing the line or null if beginning of file is reached
|
||||
*/
|
||||
protected function readLineFromFile($file): ?string {
|
||||
$line = '';
|
||||
$position = ftell($file);
|
||||
|
||||
if (0 === $position) {
|
||||
return null;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
$chunkSize = min($position, 1024);
|
||||
$position -= $chunkSize;
|
||||
fseek($file, $position);
|
||||
|
||||
if (0 === $chunkSize) {
|
||||
// bof reached
|
||||
break;
|
||||
}
|
||||
|
||||
$buffer = fread($file, $chunkSize);
|
||||
|
||||
if (false === ($upTo = strrpos($buffer, "\n"))) {
|
||||
$line = $buffer.$line;
|
||||
continue;
|
||||
}
|
||||
|
||||
$position += $upTo;
|
||||
$line = substr($buffer, $upTo + 1).$line;
|
||||
fseek($file, max(0, $position), \SEEK_SET);
|
||||
|
||||
if ('' !== $line) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return '' === $line ? null : $line;
|
||||
}
|
||||
|
||||
protected function createProfileFromData(string $token, array $data, IProfile $parent = null): IProfile {
|
||||
$profile = new Profile($token);
|
||||
$profile->setMethod($data['method']);
|
||||
$profile->setUrl($data['url']);
|
||||
$profile->setTime($data['time']);
|
||||
$profile->setStatusCode($data['status_code']);
|
||||
$profile->setCollectors($data['data']);
|
||||
|
||||
if (!$parent && $data['parent']) {
|
||||
$parent = $this->read($data['parent']);
|
||||
}
|
||||
|
||||
if ($parent) {
|
||||
$profile->setParent($parent);
|
||||
}
|
||||
|
||||
foreach ($data['children'] as $token) {
|
||||
if (!$token || !file_exists($file = $this->getFilename($token))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (\function_exists('gzcompress')) {
|
||||
$file = 'compress.zlib://'.$file;
|
||||
}
|
||||
|
||||
$profile->addChild($this->createProfileFromData($token, unserialize(file_get_contents($file)), $profile));
|
||||
}
|
||||
|
||||
return $profile;
|
||||
}
|
||||
}
|
||||
168
lib/private/Profiler/Profile.php
Normal file
168
lib/private/Profiler/Profile.php
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
/**
|
||||
* @copyright 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @author Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Profiler;
|
||||
|
||||
use OCP\DataCollector\IDataCollector;
|
||||
use OCP\Profiler\IProfile;
|
||||
|
||||
class Profile implements \JsonSerializable, IProfile {
|
||||
private string $token;
|
||||
|
||||
private ?int $time = null;
|
||||
|
||||
private ?string $url = null;
|
||||
|
||||
private ?string $method = null;
|
||||
|
||||
private ?int $statusCode = null;
|
||||
|
||||
/** @var array<string, IDataCollector> */
|
||||
private array $collectors = [];
|
||||
|
||||
private ?IProfile $parent = null;
|
||||
|
||||
/** @var IProfile[] */
|
||||
private array $children = [];
|
||||
|
||||
public function __construct(string $token) {
|
||||
$this->token = $token;
|
||||
}
|
||||
|
||||
public function getToken(): string {
|
||||
return $this->token;
|
||||
}
|
||||
|
||||
public function setToken(string $token): void {
|
||||
$this->token = $token;
|
||||
}
|
||||
|
||||
public function getTime(): ?int {
|
||||
return $this->time;
|
||||
}
|
||||
|
||||
public function setTime(int $time): void {
|
||||
$this->time = $time;
|
||||
}
|
||||
|
||||
public function getUrl(): ?string {
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
public function setUrl(string $url): void {
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
public function getMethod(): ?string {
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
public function setMethod(string $method): void {
|
||||
$this->method = $method;
|
||||
}
|
||||
|
||||
public function getStatusCode(): ?int {
|
||||
return $this->statusCode;
|
||||
}
|
||||
|
||||
public function setStatusCode(int $statusCode): void {
|
||||
$this->statusCode = $statusCode;
|
||||
}
|
||||
|
||||
public function addCollector(IDataCollector $collector) {
|
||||
$this->collectors[$collector->getName()] = $collector;
|
||||
}
|
||||
|
||||
public function getParent(): ?IProfile {
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
public function setParent(?IProfile $parent): void {
|
||||
$this->parent = $parent;
|
||||
}
|
||||
|
||||
public function getParentToken(): ?string {
|
||||
return $this->parent ? $this->parent->getToken() : null;
|
||||
}
|
||||
|
||||
/** @return IProfile[] */
|
||||
public function getChildren(): array {
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IProfile[] $children
|
||||
*/
|
||||
public function setChildren(array $children): void {
|
||||
$this->children = [];
|
||||
foreach ($children as $child) {
|
||||
$this->addChild($child);
|
||||
}
|
||||
}
|
||||
|
||||
public function addChild(IProfile $profile): void {
|
||||
$this->children[] = $profile;
|
||||
$profile->setParent($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IDataCollector[]
|
||||
*/
|
||||
public function getCollectors(): array {
|
||||
return $this->collectors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IDataCollector[] $collectors
|
||||
*/
|
||||
public function setCollectors(array $collectors): void {
|
||||
$this->collectors = $collectors;
|
||||
}
|
||||
|
||||
public function __sleep(): array {
|
||||
return ['token', 'parent', 'children', 'collectors', 'method', 'url', 'time', 'statusCode'];
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
// Everything but parent
|
||||
return [
|
||||
'token' => $this->token,
|
||||
'method' => $this->method,
|
||||
'children' => $this->children,
|
||||
'url' => $this->url,
|
||||
'statusCode' => $this->statusCode,
|
||||
'time' => $this->time,
|
||||
'collectors' => $this->collectors,
|
||||
];
|
||||
}
|
||||
|
||||
public function getCollector(string $collectorName): ?IDataCollector {
|
||||
if (!array_key_exists($collectorName, $this->collectors)) {
|
||||
return null;
|
||||
}
|
||||
return $this->collectors[$collectorName];
|
||||
}
|
||||
}
|
||||
105
lib/private/Profiler/Profiler.php
Normal file
105
lib/private/Profiler/Profiler.php
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
/**
|
||||
* @copyright 2021 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @author Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Profiler;
|
||||
|
||||
use OC\AppFramework\Http\Request;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\DataCollector\IDataCollector;
|
||||
use OCP\Profiler\IProfiler;
|
||||
use OCP\Profiler\IProfile;
|
||||
use OC\SystemConfig;
|
||||
|
||||
class Profiler implements IProfiler {
|
||||
/** @var array<string, IDataCollector> */
|
||||
private array $dataCollectors = [];
|
||||
|
||||
private ?FileProfilerStorage $storage = null;
|
||||
|
||||
private bool $enabled = false;
|
||||
|
||||
public function __construct(SystemConfig $config) {
|
||||
$this->enabled = $config->getValue('profiler', false);
|
||||
if ($this->enabled) {
|
||||
$this->storage = new FileProfilerStorage($config->getValue('datadirectory', \OC::$SERVERROOT . '/data') . '/profiler');
|
||||
}
|
||||
}
|
||||
|
||||
public function add(IDataCollector $dataCollector): void {
|
||||
$this->dataCollectors[$dataCollector->getName()] = $dataCollector;
|
||||
}
|
||||
|
||||
public function loadProfileFromResponse(Response $response): ?IProfile {
|
||||
if (!$token = $response->getHeaders()['X-Debug-Token']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->loadProfile($token);
|
||||
}
|
||||
|
||||
public function loadProfile(string $token): ?IProfile {
|
||||
return $this->storage->read($token);
|
||||
}
|
||||
|
||||
public function saveProfile(IProfile $profile): bool {
|
||||
return $this->storage->write($profile);
|
||||
}
|
||||
|
||||
public function collect(Request $request, Response $response): IProfile {
|
||||
$profile = new Profile($request->getId());
|
||||
$profile->setTime(time());
|
||||
$profile->setUrl($request->getRequestUri());
|
||||
$profile->setMethod($request->getMethod());
|
||||
$profile->setStatusCode($response->getStatus());
|
||||
foreach ($this->dataCollectors as $dataCollector) {
|
||||
$dataCollector->collect($request, $response, null);
|
||||
|
||||
// We clone for subrequests
|
||||
$profile->addCollector(clone $dataCollector);
|
||||
}
|
||||
return $profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
*/
|
||||
public function find(?string $url, ?int $limit, ?string $method, ?int $start, ?int $end,
|
||||
string $statusCode = null): array {
|
||||
return $this->storage->find($url, $limit, $method, $start, $end, $statusCode);
|
||||
}
|
||||
|
||||
public function dataProviders(): array {
|
||||
return array_keys($this->dataCollectors);
|
||||
}
|
||||
|
||||
public function isEnabled(): bool {
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
public function setEnabled(bool $enabled): void {
|
||||
$this->enabled = $enabled;
|
||||
}
|
||||
}
|
||||
55
lib/private/Profiler/RoutingDataCollector.php
Normal file
55
lib/private/Profiler/RoutingDataCollector.php
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @author Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Profiler;
|
||||
|
||||
use OC\AppFramework\Http\Request;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\DataCollector\AbstractDataCollector;
|
||||
|
||||
class RoutingDataCollector extends AbstractDataCollector {
|
||||
private string $appName;
|
||||
private string $controllerName;
|
||||
private string $actionName;
|
||||
|
||||
public function __construct(string $appName, string $controllerName, string $actionName) {
|
||||
$this->appName = $appName;
|
||||
$this->controllerName = $controllerName;
|
||||
$this->actionName = $actionName;
|
||||
}
|
||||
|
||||
public function collect(Request $request, Response $response, \Throwable $exception = null): void {
|
||||
$this->data = [
|
||||
'appName' => $this->appName,
|
||||
'controllerName' => $this->controllerName,
|
||||
'actionName' => $this->actionName,
|
||||
];
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return 'router';
|
||||
}
|
||||
}
|
||||
|
|
@ -260,6 +260,8 @@ use OCA\Files_External\Service\UserStoragesService;
|
|||
use OCA\Files_External\Service\UserGlobalStoragesService;
|
||||
use OCA\Files_External\Service\GlobalStoragesService;
|
||||
use OCA\Files_External\Service\BackendService;
|
||||
use OCP\Profiler\IProfiler;
|
||||
use OC\Profiler\Profiler;
|
||||
|
||||
/**
|
||||
* Class Server
|
||||
|
|
@ -344,6 +346,10 @@ class Server extends ServerContainer implements IServerContainer {
|
|||
);
|
||||
});
|
||||
|
||||
$this->registerService(IProfiler::class, function (Server $c) {
|
||||
return new Profiler($c->get(SystemConfig::class));
|
||||
});
|
||||
|
||||
$this->registerService(\OCP\Encryption\IManager::class, function (Server $c) {
|
||||
$view = new View();
|
||||
$util = new Encryption\Util(
|
||||
|
|
@ -691,9 +697,9 @@ class Server extends ServerContainer implements IServerContainer {
|
|||
$this->registerDeprecatedAlias('UserCache', ICache::class);
|
||||
|
||||
$this->registerService(Factory::class, function (Server $c) {
|
||||
$arrayCacheFactory = new \OC\Memcache\Factory(
|
||||
'',
|
||||
$c->get(LoggerInterface::class),
|
||||
$profiler = $c->get(IProfiler::class);
|
||||
$arrayCacheFactory = new \OC\Memcache\Factory('', $c->get(LoggerInterface::class),
|
||||
$profiler,
|
||||
ArrayCache::class,
|
||||
ArrayCache::class,
|
||||
ArrayCache::class
|
||||
|
|
@ -717,9 +723,9 @@ class Server extends ServerContainer implements IServerContainer {
|
|||
$instanceId = \OC_Util::getInstanceId();
|
||||
$path = \OC::$SERVERROOT;
|
||||
$prefix = md5($instanceId . '-' . $version . '-' . $path);
|
||||
return new \OC\Memcache\Factory(
|
||||
$prefix,
|
||||
return new \OC\Memcache\Factory($prefix,
|
||||
$c->get(LoggerInterface::class),
|
||||
$profiler,
|
||||
$config->getSystemValue('memcache.local', null),
|
||||
$config->getSystemValue('memcache.distributed', null),
|
||||
$config->getSystemValue('memcache.locking', null),
|
||||
|
|
@ -769,6 +775,7 @@ class Server extends ServerContainer implements IServerContainer {
|
|||
$c->get(KnownUserService::class)
|
||||
);
|
||||
});
|
||||
|
||||
$this->registerAlias(IAvatarManager::class, AvatarManager::class);
|
||||
/** @deprecated 19.0.0 */
|
||||
$this->registerDeprecatedAlias('AvatarManager', AvatarManager::class);
|
||||
|
|
@ -861,7 +868,6 @@ class Server extends ServerContainer implements IServerContainer {
|
|||
}
|
||||
$connectionParams = $factory->createConnectionParams();
|
||||
$connection = $factory->getConnection($type, $connectionParams);
|
||||
$connection->getConfiguration()->setSQLLogger($c->getQueryLogger());
|
||||
return $connection;
|
||||
});
|
||||
/** @deprecated 19.0.0 */
|
||||
|
|
|
|||
87
lib/public/DataCollector/AbstractDataCollector.php
Normal file
87
lib/public/DataCollector/AbstractDataCollector.php
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @author Carl Schwan <carl@carlschwan.eu>
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later AND MIT
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCP\DataCollector;
|
||||
|
||||
/**
|
||||
* Children of this class must store the collected data in
|
||||
* the data property.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Bernhard Schussek <bschussek@symfony.com>
|
||||
* @author Carl Schwan <carl@carlschwan.eu>
|
||||
* @since 24.0.0
|
||||
*/
|
||||
abstract class AbstractDataCollector implements IDataCollector, \JsonSerializable {
|
||||
/** @var array */
|
||||
protected $data = [];
|
||||
|
||||
/**
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function getName(): string {
|
||||
return static::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the state of the profiler. By default it only empties the
|
||||
* $this->data contents, but you can override this method to do
|
||||
* additional cleaning.
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function reset(): void {
|
||||
$this->data = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function __sleep(): array {
|
||||
return ['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal to prevent implementing \Serializable
|
||||
* @since 24.0.0
|
||||
*/
|
||||
final protected function serialize() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal to prevent implementing \Serializable
|
||||
* @since 24.0.0
|
||||
*/
|
||||
final protected function unserialize(string $data) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 24.0.0
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
55
lib/public/DataCollector/IDataCollector.php
Normal file
55
lib/public/DataCollector/IDataCollector.php
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @author Carl Schwan <carl@carlschwan.eu>
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later AND MIT
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCP\DataCollector;
|
||||
|
||||
use OC\AppFramework\Http\Request;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
|
||||
/**
|
||||
* DataCollectorInterface.
|
||||
*
|
||||
* @since 24.0.0
|
||||
*/
|
||||
interface IDataCollector {
|
||||
/**
|
||||
* Collects data for the given Request and Response.
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function collect(Request $request, Response $response, \Throwable $exception = null): void;
|
||||
|
||||
/**
|
||||
* Reset the state of the profiler.
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function reset(): void;
|
||||
|
||||
/**
|
||||
* Returns the name of the collector.
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function getName(): string;
|
||||
}
|
||||
|
|
@ -76,4 +76,10 @@ interface ICache {
|
|||
* @since 6.0.0
|
||||
*/
|
||||
public function clear($prefix = '');
|
||||
|
||||
/**
|
||||
* Check if the cache implementation is available
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public static function isAvailable(): bool;
|
||||
}
|
||||
|
|
|
|||
168
lib/public/Profiler/IProfile.php
Normal file
168
lib/public/Profiler/IProfile.php
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @author Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCP\Profiler;
|
||||
|
||||
use OCP\DataCollector\IDataCollector;
|
||||
|
||||
/**
|
||||
* This interface store the results of the profiling of one
|
||||
* request. You can get the saved profiles from the @see IProfiler.
|
||||
*
|
||||
* ```php
|
||||
* <?php
|
||||
* $profiler = \OC::$server->get(IProfiler::class);
|
||||
* $profiles = $profiler->find('/settings/users', 10);
|
||||
* ```
|
||||
*
|
||||
* This interface is meant to be used directly and not extended.
|
||||
* @since 24.0.0
|
||||
*/
|
||||
interface IProfile {
|
||||
/**
|
||||
* Get the token of the profile
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function getToken(): string;
|
||||
|
||||
/**
|
||||
* Set the token of the profile
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function setToken(string $token): void;
|
||||
|
||||
/**
|
||||
* Get the time of the profile
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function getTime(): ?int;
|
||||
|
||||
/**
|
||||
* Set the time of the profile
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function setTime(int $time): void;
|
||||
|
||||
/**
|
||||
* Get the url of the profile
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function getUrl(): ?string;
|
||||
|
||||
/**
|
||||
* Set the url of the profile
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function setUrl(string $url): void;
|
||||
|
||||
/**
|
||||
* Get the method of the profile
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function getMethod(): ?string;
|
||||
|
||||
/**
|
||||
* Set the method of the profile
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function setMethod(string $method): void;
|
||||
|
||||
/**
|
||||
* Get the status code of the profile
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function getStatusCode(): ?int;
|
||||
|
||||
/**
|
||||
* Set the status code of the profile
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function setStatusCode(int $statusCode): void;
|
||||
|
||||
/**
|
||||
* Add a data collector to the profile
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function addCollector(IDataCollector $collector);
|
||||
|
||||
/**
|
||||
* Get the parent profile to this profile
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function getParent(): ?IProfile;
|
||||
|
||||
/**
|
||||
* Set the parent profile to this profile
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function setParent(?IProfile $parent): void;
|
||||
|
||||
/**
|
||||
* Get the parent token to this profile
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function getParentToken(): ?string;
|
||||
|
||||
/**
|
||||
* Get the profile's children
|
||||
* @return IProfile[]
|
||||
* @since 24.0.0
|
||||
**/
|
||||
public function getChildren(): array;
|
||||
|
||||
/**
|
||||
* Set the profile's children
|
||||
* @param IProfile[] $children
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function setChildren(array $children): void;
|
||||
|
||||
/**
|
||||
* Add the child profile
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function addChild(IProfile $profile): void;
|
||||
|
||||
/**
|
||||
* Get all the data collectors
|
||||
* @return IDataCollector[]
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function getCollectors(): array;
|
||||
|
||||
/**
|
||||
* Set all the data collectors
|
||||
* @param IDataCollector[] $collectors
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function setCollectors(array $collectors): void;
|
||||
|
||||
/**
|
||||
* Get a data collector by name
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function getCollector(string $collectorName): ?IDataCollector;
|
||||
}
|
||||
101
lib/public/Profiler/IProfiler.php
Normal file
101
lib/public/Profiler/IProfiler.php
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @author Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCP\Profiler;
|
||||
|
||||
use OC\AppFramework\Http\Request;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\DataCollector\IDataCollector;
|
||||
|
||||
/**
|
||||
* This interface allows to interact with the built-in Nextcloud profiler.
|
||||
* @since 24.0.0
|
||||
*/
|
||||
interface IProfiler {
|
||||
/**
|
||||
* Add a new data collector to the profiler. This allows to later on
|
||||
* collect all the data from every registered collector.
|
||||
*
|
||||
* @see IDataCollector
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function add(IDataCollector $dataCollector): void;
|
||||
|
||||
/**
|
||||
* Load a profile from a response object
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function loadProfileFromResponse(Response $response): ?IProfile;
|
||||
|
||||
/**
|
||||
* Load a profile from the response token
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function loadProfile(string $token): ?IProfile;
|
||||
|
||||
/**
|
||||
* Save a profile on the disk. This allows to later load it again in the
|
||||
* profiler user interface.
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function saveProfile(IProfile $profile): bool;
|
||||
|
||||
/**
|
||||
* Find a profile from various search parameters
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function find(?string $url, ?int $limit, ?string $method, ?int $start, ?int $end, string $statusCode = null): array;
|
||||
|
||||
/**
|
||||
* Get the list of data providers by identifier
|
||||
* @return string[]
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function dataProviders(): array;
|
||||
|
||||
/**
|
||||
* Check if the profiler is enabled.
|
||||
*
|
||||
* If it is not enabled, data provider shouldn't be created and
|
||||
* shouldn't collect any data.
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function isEnabled(): bool;
|
||||
|
||||
/**
|
||||
* Set if the profiler is enabled.
|
||||
* @see isEnabled
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function setEnabled(bool $enabled): void;
|
||||
|
||||
/**
|
||||
* Collect all the information from the current request and construct
|
||||
* a IProfile from it.
|
||||
* @since 24.0.0
|
||||
*/
|
||||
public function collect(Request $request, Response $response): IProfile;
|
||||
}
|
||||
|
|
@ -71,7 +71,7 @@ class AppTest extends \Test\TestCase {
|
|||
$this->container[$this->controllerName] = $this->controller;
|
||||
$this->container['Dispatcher'] = $this->dispatcher;
|
||||
$this->container['OCP\\AppFramework\\Http\\IOutput'] = $this->io;
|
||||
$this->container['urlParams'] = [];
|
||||
$this->container['urlParams'] = ['_route' => 'not-profiler'];
|
||||
|
||||
$this->appPath = __DIR__ . '/../../../apps/namespacetestapp';
|
||||
$infoXmlPath = $this->appPath . '/appinfo/info.xml';
|
||||
|
|
@ -183,6 +183,7 @@ class AppTest extends \Test\TestCase {
|
|||
public function testCoreApp() {
|
||||
$this->container['AppName'] = 'core';
|
||||
$this->container['OC\Core\Controller\Foo'] = $this->controller;
|
||||
$this->container['urlParams'] = ['_route' => 'not-profiler'];
|
||||
|
||||
$return = ['HTTP/2.0 200 OK', [], [], null, new Response()];
|
||||
$this->dispatcher->expects($this->once())
|
||||
|
|
@ -200,6 +201,7 @@ class AppTest extends \Test\TestCase {
|
|||
public function testSettingsApp() {
|
||||
$this->container['AppName'] = 'settings';
|
||||
$this->container['OCA\Settings\Controller\Foo'] = $this->controller;
|
||||
$this->container['urlParams'] = ['_route' => 'not-profiler'];
|
||||
|
||||
$return = ['HTTP/2.0 200 OK', [], [], null, new Response()];
|
||||
$this->dispatcher->expects($this->once())
|
||||
|
|
@ -217,6 +219,7 @@ class AppTest extends \Test\TestCase {
|
|||
public function testApp() {
|
||||
$this->container['AppName'] = 'bar';
|
||||
$this->container['OCA\Bar\Controller\Foo'] = $this->controller;
|
||||
$this->container['urlParams'] = ['_route' => 'not-profiler'];
|
||||
|
||||
$return = ['HTTP/2.0 200 OK', [], [], null, new Response()];
|
||||
$this->dispatcher->expects($this->once())
|
||||
|
|
|
|||
|
|
@ -23,12 +23,13 @@ namespace Test\Memcache;
|
|||
|
||||
use OC\Memcache\NullCache;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use OCP\Profiler\IProfiler;
|
||||
|
||||
class Test_Factory_Available_Cache1 extends NullCache {
|
||||
public function __construct($prefix = '') {
|
||||
}
|
||||
|
||||
public static function isAvailable() {
|
||||
public static function isAvailable(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -37,7 +38,7 @@ class Test_Factory_Available_Cache2 extends NullCache {
|
|||
public function __construct($prefix = '') {
|
||||
}
|
||||
|
||||
public static function isAvailable() {
|
||||
public static function isAvailable(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -46,7 +47,7 @@ class Test_Factory_Unavailable_Cache1 extends NullCache {
|
|||
public function __construct($prefix = '') {
|
||||
}
|
||||
|
||||
public static function isAvailable() {
|
||||
public static function isAvailable(): bool {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -55,7 +56,7 @@ class Test_Factory_Unavailable_Cache2 extends NullCache {
|
|||
public function __construct($prefix = '') {
|
||||
}
|
||||
|
||||
public static function isAvailable() {
|
||||
public static function isAvailable(): bool {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -119,7 +120,8 @@ class FactoryTest extends \Test\TestCase {
|
|||
public function testCacheAvailability($localCache, $distributedCache, $lockingCache,
|
||||
$expectedLocalCache, $expectedDistributedCache, $expectedLockingCache) {
|
||||
$logger = $this->getMockBuilder(LoggerInterface::class)->getMock();
|
||||
$factory = new \OC\Memcache\Factory('abc', $logger, $localCache, $distributedCache, $lockingCache);
|
||||
$profiler = $this->getMockBuilder(IProfiler::class)->getMock();
|
||||
$factory = new \OC\Memcache\Factory('abc', $logger, $profiler, $localCache, $distributedCache, $lockingCache);
|
||||
$this->assertTrue(is_a($factory->createLocal(), $expectedLocalCache));
|
||||
$this->assertTrue(is_a($factory->createDistributed(), $expectedDistributedCache));
|
||||
$this->assertTrue(is_a($factory->createLocking(), $expectedLockingCache));
|
||||
|
|
@ -132,6 +134,7 @@ class FactoryTest extends \Test\TestCase {
|
|||
$this->expectException(\OCP\HintException::class);
|
||||
|
||||
$logger = $this->getMockBuilder(LoggerInterface::class)->getMock();
|
||||
new \OC\Memcache\Factory('abc', $logger, $localCache, $distributedCache);
|
||||
$profiler = $this->getMockBuilder(IProfiler::class)->getMock();
|
||||
new \OC\Memcache\Factory('abc', $logger, $profiler, $localCache, $distributedCache);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue