Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/server.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/dav/composer/composer/autoload_classmap.php1
-rw-r--r--apps/dav/composer/composer/autoload_static.php1
-rw-r--r--apps/dav/lib/Profiler/ProfilerPlugin.php47
-rw-r--r--apps/dav/lib/Server.php31
-rw-r--r--apps/user_ldap/composer/composer/autoload_classmap.php1
-rw-r--r--apps/user_ldap/composer/composer/autoload_static.php1
-rw-r--r--apps/user_ldap/lib/DataCollector/LdapDataCollector.php50
-rw-r--r--apps/user_ldap/lib/LDAP.php32
-rw-r--r--build/psalm-baseline.xml6
-rw-r--r--config/config.sample.php8
-rw-r--r--core/templates/layout.user.php2
-rw-r--r--lib/autoloader.php5
-rw-r--r--lib/base.php1
-rw-r--r--lib/composer/composer/autoload_classmap.php12
-rw-r--r--lib/composer/composer/autoload_static.php12
-rw-r--r--lib/private/AppFramework/App.php33
-rw-r--r--lib/private/AppFramework/Utility/SimpleContainer.php8
-rw-r--r--lib/private/Cache/CappedMemoryCache.php4
-rw-r--r--lib/private/Cache/File.php4
-rw-r--r--lib/private/DB/Connection.php15
-rw-r--r--lib/private/DB/DbDataCollector.php154
-rw-r--r--lib/private/DB/ObjectParameter.php71
-rw-r--r--lib/private/Diagnostics/EventLogger.php3
-rw-r--r--lib/private/Memcache/APCu.php5
-rw-r--r--lib/private/Memcache/ArrayCache.php2
-rw-r--r--lib/private/Memcache/Factory.php88
-rw-r--r--lib/private/Memcache/LoggerWrapperCache.php177
-rw-r--r--lib/private/Memcache/Memcached.php2
-rw-r--r--lib/private/Memcache/NullCache.php2
-rw-r--r--lib/private/Memcache/ProfilerWrapperCache.php220
-rw-r--r--lib/private/Memcache/Redis.php124
-rw-r--r--lib/private/Profiler/FileProfilerStorage.php286
-rw-r--r--lib/private/Profiler/Profile.php168
-rw-r--r--lib/private/Profiler/Profiler.php105
-rw-r--r--lib/private/Profiler/RoutingDataCollector.php55
-rw-r--r--lib/private/Server.php18
-rw-r--r--lib/public/DataCollector/AbstractDataCollector.php87
-rw-r--r--lib/public/DataCollector/IDataCollector.php55
-rw-r--r--lib/public/ICache.php6
-rw-r--r--lib/public/Profiler/IProfile.php168
-rw-r--r--lib/public/Profiler/IProfiler.php101
-rw-r--r--tests/lib/AppFramework/AppTest.php5
-rw-r--r--tests/lib/Memcache/FactoryTest.php15
43 files changed, 2001 insertions, 190 deletions
diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php
index f4b17814817..aa89da0e37a 100644
--- a/apps/dav/composer/composer/autoload_classmap.php
+++ b/apps/dav/composer/composer/autoload_classmap.php
@@ -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',
diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php
index d164ab2b1ce..b94e383cb0e 100644
--- a/apps/dav/composer/composer/autoload_static.php
+++ b/apps/dav/composer/composer/autoload_static.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',
diff --git a/apps/dav/lib/Profiler/ProfilerPlugin.php b/apps/dav/lib/Profiler/ProfilerPlugin.php
new file mode 100644
index 00000000000..672ca4010b7
--- /dev/null
+++ b/apps/dav/lib/Profiler/ProfilerPlugin.php
@@ -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());
+ }
+}
diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php
index 759d39c0233..ff64da2a238 100644
--- a/apps/dav/lib/Server.php
+++ b/apps/dav/lib/Server.php
@@ -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;
+
+ 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 IRequest */
- private $request;
-
- /** @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 {
diff --git a/apps/user_ldap/composer/composer/autoload_classmap.php b/apps/user_ldap/composer/composer/autoload_classmap.php
index cffb2aaa9fe..7ba4fadef0a 100644
--- a/apps/user_ldap/composer/composer/autoload_classmap.php
+++ b/apps/user_ldap/composer/composer/autoload_classmap.php
@@ -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',
diff --git a/apps/user_ldap/composer/composer/autoload_static.php b/apps/user_ldap/composer/composer/autoload_static.php
index 5928ff78ef0..20c5d58eb6f 100644
--- a/apps/user_ldap/composer/composer/autoload_static.php
+++ b/apps/user_ldap/composer/composer/autoload_static.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',
diff --git a/apps/user_ldap/lib/DataCollector/LdapDataCollector.php b/apps/user_ldap/lib/DataCollector/LdapDataCollector.php
new file mode 100644
index 00000000000..cb61de96e37
--- /dev/null
+++ b/apps/user_ldap/lib/DataCollector/LdapDataCollector.php
@@ -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 {
+ }
+}
diff --git a/apps/user_ldap/lib/LDAP.php b/apps/user_ldap/lib/LDAP.php
index 18a9476128d..3c579596941 100644
--- a/apps/user_ldap/lib/LDAP.php
+++ b/apps/user_ldap/lib/LDAP.php
@@ -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",
diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml
index e721a490e6d..8884bc18612 100644
--- a/build/psalm-baseline.xml
+++ b/build/psalm-baseline.xml
@@ -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-&gt;tar-&gt;extractInString($path)</code>
diff --git a/config/config.sample.php b/config/config.sample.php
index 357321f6d29..7a7dfa356b8 100644
--- a/config/config.sample.php
+++ b/config/config.sample.php
@@ -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
diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php
index aa6ff416ba1..ce316b882e6 100644
--- a/core/templates/layout.user.php
+++ b/core/templates/layout.user.php
@@ -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>
diff --git a/lib/autoloader.php b/lib/autoloader.php
index c8eebee3e0c..a29b9aece79 100644
--- a/lib/autoloader.php
+++ b/lib/autoloader.php
@@ -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;
}
}
diff --git a/lib/base.php b/lib/base.php
index f3c3e4f31cb..21889272dd7 100644
--- a/lib/base.php
+++ b/lib/base.php
@@ -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) {
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 69cd3b1116e..e12719110bd 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -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',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index fbf1281cdf8..8fb0d16ea36 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.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',
diff --git a/lib/private/AppFramework/App.php b/lib/private/AppFramework/App.php
index 6c2f905afa5..feebb32d5bc 100644
--- a/lib/private/AppFramework/App.php
+++ b/lib/private/AppFramework/App.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);
}
diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php
index 598c66b6aba..429382aa223 100644
--- a/lib/private/AppFramework/Utility/SimpleContainer.php
+++ b/lib/private/AppFramework/Utility/SimpleContainer.php
@@ -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);
diff --git a/lib/private/Cache/CappedMemoryCache.php b/lib/private/Cache/CappedMemoryCache.php
index 9260bf1f6b3..0a3300435eb 100644
--- a/lib/private/Cache/CappedMemoryCache.php
+++ b/lib/private/Cache/CappedMemoryCache.php
@@ -115,4 +115,8 @@ class CappedMemoryCache implements ICache, \ArrayAccess {
$this->remove($key);
}
}
+
+ public static function isAvailable(): bool {
+ return true;
+ }
}
diff --git a/lib/private/Cache/File.php b/lib/private/Cache/File.php
index 0ecd894d2d2..a96a7cd9c0b 100644
--- a/lib/private/Cache/File.php
+++ b/lib/private/Cache/File.php
@@ -203,4 +203,8 @@ class File implements ICache {
}
}
}
+
+ public static function isAvailable(): bool {
+ return true;
+ }
}
diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php
index 0cd310550b6..2e38b1ddf5e 100644
--- a/lib/private/DB/Connection.php
+++ b/lib/private/DB/Connection.php
@@ -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);
+ }
}
/**
diff --git a/lib/private/DB/DbDataCollector.php b/lib/private/DB/DbDataCollector.php
new file mode 100644
index 00000000000..d708955b10e
--- /dev/null
+++ b/lib/private/DB/DbDataCollector.php
@@ -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];
+ }
+}
diff --git a/lib/private/DB/ObjectParameter.php b/lib/private/DB/ObjectParameter.php
new file mode 100644
index 00000000000..61ac16018d8
--- /dev/null
+++ b/lib/private/DB/ObjectParameter.php
@@ -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;
+ }
+}
diff --git a/lib/private/Diagnostics/EventLogger.php b/lib/private/Diagnostics/EventLogger.php
index 35cef0be3f5..c7b89002ea9 100644
--- a/lib/private/Diagnostics/EventLogger.php
+++ b/lib/private/Diagnostics/EventLogger.php
@@ -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;
diff --git a/lib/private/Memcache/APCu.php b/lib/private/Memcache/APCu.php
index 56345890bf2..f0eb98b9db2 100644
--- a/lib/private/Memcache/APCu.php
+++ b/lib/private/Memcache/APCu.php
@@ -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')) {
diff --git a/lib/private/Memcache/ArrayCache.php b/lib/private/Memcache/ArrayCache.php
index b89aff0b7ed..13597a068b3 100644
--- a/lib/private/Memcache/ArrayCache.php
+++ b/lib/private/Memcache/ArrayCache.php
@@ -153,7 +153,7 @@ class ArrayCache extends Cache implements IMemcache {
/**
* {@inheritDoc}
*/
- public static function isAvailable() {
+ public static function isAvailable(): bool {
return true;
}
}
diff --git a/lib/private/Memcache/Factory.php b/lib/private/Memcache/Factory.php
index 73206aac011..604f764c03c 100644
--- a/lib/private/Memcache/Factory.php
+++ b/lib/private/Memcache/Factory.php
@@ -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 ?string $localCacheClass;
+
+ /**
+ * @var ?class-string<ICache> $distributedCacheClass
*/
- private $localCacheClass;
+ private ?string $distributedCacheClass;
/**
- * @var string $distributedCacheClass
+ * @var ?class-string<IMemcache> $lockingCacheClass
*/
- private $distributedCacheClass;
+ private ?string $lockingCacheClass;
+
+ private string $logFile;
+
+ private IProfiler $profiler;
/**
- * @var string $lockingCacheClass
+ * @param string $globalPrefix
+ * @param LoggerInterface $logger
+ * @param ?class-string<ICache> $localCacheClass
+ * @param ?class-string<ICache> $distributedCacheClass
+ * @param ?class-string<IMemcache> $lockingCacheClass
+ * @param string $logFile
*/
- private $lockingCacheClass;
-
- /** @var string */
- private $logFile;
-
- public function __construct(
- string $globalPrefix,
- LoggerInterface $logger,
- ?string $localCacheClass = null,
- ?string $distributedCacheClass = null,
- ?string $lockingCacheClass = null,
- 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;
}
/**
diff --git a/lib/private/Memcache/LoggerWrapperCache.php b/lib/private/Memcache/LoggerWrapperCache.php
new file mode 100644
index 00000000000..55c0e76db79
--- /dev/null
+++ b/lib/private/Memcache/LoggerWrapperCache.php
@@ -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;
+ }
+}
diff --git a/lib/private/Memcache/Memcached.php b/lib/private/Memcache/Memcached.php
index f78be581d63..db4aa7ba9cc 100644
--- a/lib/private/Memcache/Memcached.php
+++ b/lib/private/Memcache/Memcached.php
@@ -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');
}
diff --git a/lib/private/Memcache/NullCache.php b/lib/private/Memcache/NullCache.php
index 7b56ec932f4..fc41595dfe1 100644
--- a/lib/private/Memcache/NullCache.php
+++ b/lib/private/Memcache/NullCache.php
@@ -67,7 +67,7 @@ class NullCache extends Cache implements \OCP\IMemcache {
return true;
}
- public static function isAvailable() {
+ public static function isAvailable(): bool {
return true;
}
}
diff --git a/lib/private/Memcache/ProfilerWrapperCache.php b/lib/private/Memcache/ProfilerWrapperCache.php
new file mode 100644
index 00000000000..8e9b160ba0e
--- /dev/null
+++ b/lib/private/Memcache/ProfilerWrapperCache.php
@@ -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;
+ }
+}
diff --git a/lib/private/Memcache/Redis.php b/lib/private/Memcache/Redis.php
index 63180dd8066..9b07da2d99c 100644
--- a/lib/private/Memcache/Redis.php
+++ b/lib/private/Memcache/Redis.php
@@ -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();
}
}
diff --git a/lib/private/Profiler/FileProfilerStorage.php b/lib/private/Profiler/FileProfilerStorage.php
new file mode 100644
index 00000000000..ce09ed51ed9
--- /dev/null
+++ b/lib/private/Profiler/FileProfilerStorage.php
@@ -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;
+ }
+}
diff --git a/lib/private/Profiler/Profile.php b/lib/private/Profiler/Profile.php
new file mode 100644
index 00000000000..648c49c0330
--- /dev/null
+++ b/lib/private/Profiler/Profile.php
@@ -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];
+ }
+}
diff --git a/lib/private/Profiler/Profiler.php b/lib/private/Profiler/Profiler.php
new file mode 100644
index 00000000000..8aa800fbc6d
--- /dev/null
+++ b/lib/private/Profiler/Profiler.php
@@ -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;
+ }
+}
diff --git a/lib/private/Profiler/RoutingDataCollector.php b/lib/private/Profiler/RoutingDataCollector.php
new file mode 100644
index 00000000000..e6659230879
--- /dev/null
+++ b/lib/private/Profiler/RoutingDataCollector.php
@@ -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';
+ }
+}
diff --git a/lib/private/Server.php b/lib/private/Server.php
index 682e6fa06ce..b214ba3ce54 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -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 */
diff --git a/lib/public/DataCollector/AbstractDataCollector.php b/lib/public/DataCollector/AbstractDataCollector.php
new file mode 100644
index 00000000000..68298671b7b
--- /dev/null
+++ b/lib/public/DataCollector/AbstractDataCollector.php
@@ -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;
+ }
+}
diff --git a/lib/public/DataCollector/IDataCollector.php b/lib/public/DataCollector/IDataCollector.php
new file mode 100644
index 00000000000..0fb914727df
--- /dev/null
+++ b/lib/public/DataCollector/IDataCollector.php
@@ -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;
+}
diff --git a/lib/public/ICache.php b/lib/public/ICache.php
index 47b0e2f4c3d..0e818277f60 100644
--- a/lib/public/ICache.php
+++ b/lib/public/ICache.php
@@ -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;
}
diff --git a/lib/public/Profiler/IProfile.php b/lib/public/Profiler/IProfile.php
new file mode 100644
index 00000000000..1831496a5a7
--- /dev/null
+++ b/lib/public/Profiler/IProfile.php
@@ -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;
+}
diff --git a/lib/public/Profiler/IProfiler.php b/lib/public/Profiler/IProfiler.php
new file mode 100644
index 00000000000..78325089523
--- /dev/null
+++ b/lib/public/Profiler/IProfiler.php
@@ -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;
+}
diff --git a/tests/lib/AppFramework/AppTest.php b/tests/lib/AppFramework/AppTest.php
index 595a556a9a8..3dc62246e97 100644
--- a/tests/lib/AppFramework/AppTest.php
+++ b/tests/lib/AppFramework/AppTest.php
@@ -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())
diff --git a/tests/lib/Memcache/FactoryTest.php b/tests/lib/Memcache/FactoryTest.php
index 0e995865b5d..f16f70eddc2 100644
--- a/tests/lib/Memcache/FactoryTest.php
+++ b/tests/lib/Memcache/FactoryTest.php
@@ -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);
}
}