summaryrefslogtreecommitdiffstats
path: root/lib/private/DB
diff options
context:
space:
mode:
authorCarl Schwan <carl@carlschwan.eu>2022-04-04 12:56:37 +0200
committerGitHub <noreply@github.com>2022-04-04 12:56:37 +0200
commit135bdb3d5830672214149fa0b75fadd29b20b844 (patch)
treef2c1bc68c733829c90090f71d712f78efbc77083 /lib/private/DB
parent498d3aea060ed496e2b5351108718e198b021d00 (diff)
parent7d272c54d013538746d6731097ec37f360effb5d (diff)
downloadnextcloud-server-135bdb3d5830672214149fa0b75fadd29b20b844.tar.gz
nextcloud-server-135bdb3d5830672214149fa0b75fadd29b20b844.zip
Merge pull request #30823 from nextcloud/work/profiler
Built-in profiler This adds the required API for collecting information about requests. This information can then be displayed with the new 'profiler' app.
Diffstat (limited to 'lib/private/DB')
-rw-r--r--lib/private/DB/Connection.php15
-rw-r--r--lib/private/DB/DbDataCollector.php154
-rw-r--r--lib/private/DB/ObjectParameter.php71
3 files changed, 240 insertions, 0 deletions
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;
+ }
+}