Merge pull request #23047 from nextcloud/techdebt/noid/warn-on-database-abuse
Log the number of queries built and executed
This commit is contained in:
commit
f4707d178e
|
@ -61,6 +61,7 @@ use OCP\Files\Folder;
|
||||||
use OCP\Files\IAppData;
|
use OCP\Files\IAppData;
|
||||||
use OCP\Group\ISubAdmin;
|
use OCP\Group\ISubAdmin;
|
||||||
use OCP\IConfig;
|
use OCP\IConfig;
|
||||||
|
use OCP\IDBConnection;
|
||||||
use OCP\IInitialStateService;
|
use OCP\IInitialStateService;
|
||||||
use OCP\IL10N;
|
use OCP\IL10N;
|
||||||
use OCP\ILogger;
|
use OCP\ILogger;
|
||||||
|
@ -180,7 +181,10 @@ class DIContainer extends SimpleContainer implements IAppContainer {
|
||||||
$c->get('Protocol'),
|
$c->get('Protocol'),
|
||||||
$c->get(MiddlewareDispatcher::class),
|
$c->get(MiddlewareDispatcher::class),
|
||||||
$c->get(IControllerMethodReflector::class),
|
$c->get(IControllerMethodReflector::class),
|
||||||
$c->get(IRequest::class)
|
$c->get(IRequest::class),
|
||||||
|
$c->get(IConfig::class),
|
||||||
|
$c->get(IDBConnection::class),
|
||||||
|
$c->get(LoggerInterface::class)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -35,11 +35,14 @@ namespace OC\AppFramework\Http;
|
||||||
use OC\AppFramework\Http;
|
use OC\AppFramework\Http;
|
||||||
use OC\AppFramework\Middleware\MiddlewareDispatcher;
|
use OC\AppFramework\Middleware\MiddlewareDispatcher;
|
||||||
use OC\AppFramework\Utility\ControllerMethodReflector;
|
use OC\AppFramework\Utility\ControllerMethodReflector;
|
||||||
|
use OC\DB\Connection;
|
||||||
use OCP\AppFramework\Controller;
|
use OCP\AppFramework\Controller;
|
||||||
use OCP\AppFramework\Http\DataResponse;
|
use OCP\AppFramework\Http\DataResponse;
|
||||||
use OCP\AppFramework\Http\Response;
|
use OCP\AppFramework\Http\Response;
|
||||||
|
use OCP\IConfig;
|
||||||
|
use OCP\IDBConnection;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to dispatch the request to the middleware dispatcher
|
* Class to dispatch the request to the middleware dispatcher
|
||||||
|
@ -58,6 +61,15 @@ class Dispatcher {
|
||||||
/** @var IRequest */
|
/** @var IRequest */
|
||||||
private $request;
|
private $request;
|
||||||
|
|
||||||
|
/** @var IConfig */
|
||||||
|
private $config;
|
||||||
|
|
||||||
|
/** @var IDBConnection|Connection */
|
||||||
|
private $connection;
|
||||||
|
|
||||||
|
/** @var LoggerInterface */
|
||||||
|
private $logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Http $protocol the http protocol with contains all status headers
|
* @param Http $protocol the http protocol with contains all status headers
|
||||||
* @param MiddlewareDispatcher $middlewareDispatcher the dispatcher which
|
* @param MiddlewareDispatcher $middlewareDispatcher the dispatcher which
|
||||||
|
@ -65,15 +77,24 @@ class Dispatcher {
|
||||||
* @param ControllerMethodReflector $reflector the reflector that is used to inject
|
* @param ControllerMethodReflector $reflector the reflector that is used to inject
|
||||||
* the arguments for the controller
|
* the arguments for the controller
|
||||||
* @param IRequest $request the incoming request
|
* @param IRequest $request the incoming request
|
||||||
|
* @param IConfig $config
|
||||||
|
* @param IDBConnection $connection
|
||||||
|
* @param LoggerInterface $logger
|
||||||
*/
|
*/
|
||||||
public function __construct(Http $protocol,
|
public function __construct(Http $protocol,
|
||||||
MiddlewareDispatcher $middlewareDispatcher,
|
MiddlewareDispatcher $middlewareDispatcher,
|
||||||
ControllerMethodReflector $reflector,
|
ControllerMethodReflector $reflector,
|
||||||
IRequest $request) {
|
IRequest $request,
|
||||||
|
IConfig $config,
|
||||||
|
IDBConnection $connection,
|
||||||
|
LoggerInterface $logger) {
|
||||||
$this->protocol = $protocol;
|
$this->protocol = $protocol;
|
||||||
$this->middlewareDispatcher = $middlewareDispatcher;
|
$this->middlewareDispatcher = $middlewareDispatcher;
|
||||||
$this->reflector = $reflector;
|
$this->reflector = $reflector;
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
|
$this->config = $config;
|
||||||
|
$this->connection = $connection;
|
||||||
|
$this->logger = $logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,8 +118,36 @@ class Dispatcher {
|
||||||
|
|
||||||
$this->middlewareDispatcher->beforeController($controller,
|
$this->middlewareDispatcher->beforeController($controller,
|
||||||
$methodName);
|
$methodName);
|
||||||
|
|
||||||
|
$databaseStatsBefore = [];
|
||||||
|
if ($this->config->getSystemValueBool('debug', false)) {
|
||||||
|
$databaseStatsBefore = $this->connection->getStats();
|
||||||
|
}
|
||||||
|
|
||||||
$response = $this->executeController($controller, $methodName);
|
$response = $this->executeController($controller, $methodName);
|
||||||
|
|
||||||
|
if (!empty($databaseStatsBefore)) {
|
||||||
|
$databaseStatsAfter = $this->connection->getStats();
|
||||||
|
$numBuilt = $databaseStatsAfter['built'] - $databaseStatsBefore['built'];
|
||||||
|
$numExecuted = $databaseStatsAfter['executed'] - $databaseStatsBefore['executed'];
|
||||||
|
|
||||||
|
if ($numBuilt > 50) {
|
||||||
|
$this->logger->debug('Controller {class}::{method} created {count} QueryBuilder objects, please check if they are created inside a loop by accident.' , [
|
||||||
|
'class' => (string) get_class($controller),
|
||||||
|
'method' => $methodName,
|
||||||
|
'count' => $numBuilt,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($numExecuted > 100) {
|
||||||
|
$this->logger->warning('Controller {class}::{method} executed {count} queries.' , [
|
||||||
|
'class' => (string) get_class($controller),
|
||||||
|
'method' => $methodName,
|
||||||
|
'count' => $numExecuted,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if an exception appears, the middleware checks if it can handle the
|
// if an exception appears, the middleware checks if it can handle the
|
||||||
// exception and creates a response. If no response is created, it is
|
// exception and creates a response. If no response is created, it is
|
||||||
// assumed that theres no middleware who can handle it and the error is
|
// assumed that theres no middleware who can handle it and the error is
|
||||||
|
|
|
@ -59,6 +59,12 @@ class Connection extends ReconnectWrapper implements IDBConnection {
|
||||||
|
|
||||||
protected $lockedTable = null;
|
protected $lockedTable = null;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
protected $queriesBuilt = 0;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
protected $queriesExecuted = 0;
|
||||||
|
|
||||||
public function connect() {
|
public function connect() {
|
||||||
try {
|
try {
|
||||||
return parent::connect();
|
return parent::connect();
|
||||||
|
@ -68,12 +74,20 @@ class Connection extends ReconnectWrapper implements IDBConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getStats(): array {
|
||||||
|
return [
|
||||||
|
'built' => $this->queriesBuilt,
|
||||||
|
'executed' => $this->queriesExecuted,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a QueryBuilder for the connection.
|
* Returns a QueryBuilder for the connection.
|
||||||
*
|
*
|
||||||
* @return \OCP\DB\QueryBuilder\IQueryBuilder
|
* @return \OCP\DB\QueryBuilder\IQueryBuilder
|
||||||
*/
|
*/
|
||||||
public function getQueryBuilder() {
|
public function getQueryBuilder() {
|
||||||
|
$this->queriesBuilt++;
|
||||||
return new QueryBuilder(
|
return new QueryBuilder(
|
||||||
$this,
|
$this,
|
||||||
\OC::$server->getSystemConfig(),
|
\OC::$server->getSystemConfig(),
|
||||||
|
@ -90,6 +104,7 @@ class Connection extends ReconnectWrapper implements IDBConnection {
|
||||||
public function createQueryBuilder() {
|
public function createQueryBuilder() {
|
||||||
$backtrace = $this->getCallerBacktrace();
|
$backtrace = $this->getCallerBacktrace();
|
||||||
\OC::$server->getLogger()->debug('Doctrine QueryBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
|
\OC::$server->getLogger()->debug('Doctrine QueryBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
|
||||||
|
$this->queriesBuilt++;
|
||||||
return parent::createQueryBuilder();
|
return parent::createQueryBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +117,7 @@ class Connection extends ReconnectWrapper implements IDBConnection {
|
||||||
public function getExpressionBuilder() {
|
public function getExpressionBuilder() {
|
||||||
$backtrace = $this->getCallerBacktrace();
|
$backtrace = $this->getCallerBacktrace();
|
||||||
\OC::$server->getLogger()->debug('Doctrine ExpressionBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
|
\OC::$server->getLogger()->debug('Doctrine ExpressionBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
|
||||||
|
$this->queriesBuilt++;
|
||||||
return parent::getExpressionBuilder();
|
return parent::getExpressionBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,6 +207,7 @@ class Connection extends ReconnectWrapper implements IDBConnection {
|
||||||
public function executeQuery($query, array $params = [], $types = [], QueryCacheProfile $qcp = null) {
|
public function executeQuery($query, array $params = [], $types = [], QueryCacheProfile $qcp = null) {
|
||||||
$query = $this->replaceTablePrefix($query);
|
$query = $this->replaceTablePrefix($query);
|
||||||
$query = $this->adapter->fixupStatement($query);
|
$query = $this->adapter->fixupStatement($query);
|
||||||
|
$this->queriesExecuted++;
|
||||||
return parent::executeQuery($query, $params, $types, $qcp);
|
return parent::executeQuery($query, $params, $types, $qcp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,6 +228,7 @@ class Connection extends ReconnectWrapper implements IDBConnection {
|
||||||
public function executeUpdate($query, array $params = [], array $types = []) {
|
public function executeUpdate($query, array $params = [], array $types = []) {
|
||||||
$query = $this->replaceTablePrefix($query);
|
$query = $this->replaceTablePrefix($query);
|
||||||
$query = $this->adapter->fixupStatement($query);
|
$query = $this->adapter->fixupStatement($query);
|
||||||
|
$this->queriesExecuted++;
|
||||||
return parent::executeUpdate($query, $params, $types);
|
return parent::executeUpdate($query, $params, $types);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,9 @@ use OCP\AppFramework\Http\DataResponse;
|
||||||
use OCP\AppFramework\Http\JSONResponse;
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
use OCP\AppFramework\Http\Response;
|
use OCP\AppFramework\Http\Response;
|
||||||
use OCP\IConfig;
|
use OCP\IConfig;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
class TestController extends Controller {
|
class TestController extends Controller {
|
||||||
/**
|
/**
|
||||||
|
@ -72,7 +75,12 @@ class TestController extends Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class DispatcherTest
|
||||||
|
*
|
||||||
|
* @package Test\AppFramework\Http
|
||||||
|
* @group DB
|
||||||
|
*/
|
||||||
class DispatcherTest extends \Test\TestCase {
|
class DispatcherTest extends \Test\TestCase {
|
||||||
/** @var MiddlewareDispatcher */
|
/** @var MiddlewareDispatcher */
|
||||||
private $middlewareDispatcher;
|
private $middlewareDispatcher;
|
||||||
|
@ -80,16 +88,24 @@ class DispatcherTest extends \Test\TestCase {
|
||||||
private $dispatcher;
|
private $dispatcher;
|
||||||
private $controllerMethod;
|
private $controllerMethod;
|
||||||
private $response;
|
private $response;
|
||||||
|
/** @var IRequest|MockObject */
|
||||||
private $request;
|
private $request;
|
||||||
private $lastModified;
|
private $lastModified;
|
||||||
private $etag;
|
private $etag;
|
||||||
|
/** @var Http|MockObject */
|
||||||
private $http;
|
private $http;
|
||||||
private $reflector;
|
private $reflector;
|
||||||
|
/** @var IConfig|MockObject */
|
||||||
|
private $config;
|
||||||
|
/** @var LoggerInterface|MockObject */
|
||||||
|
private $logger;
|
||||||
|
|
||||||
protected function setUp(): void {
|
protected function setUp(): void {
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
$this->controllerMethod = 'test';
|
$this->controllerMethod = 'test';
|
||||||
|
|
||||||
|
$this->config = $this->createMock(IConfig::class);
|
||||||
|
$this->logger = $this->createMock(LoggerInterface::class);
|
||||||
$app = $this->getMockBuilder(
|
$app = $this->getMockBuilder(
|
||||||
'OC\AppFramework\DependencyInjection\DIContainer')
|
'OC\AppFramework\DependencyInjection\DIContainer')
|
||||||
->disableOriginalConstructor()
|
->disableOriginalConstructor()
|
||||||
|
@ -99,7 +115,7 @@ class DispatcherTest extends \Test\TestCase {
|
||||||
->disableOriginalConstructor()
|
->disableOriginalConstructor()
|
||||||
->getMock();
|
->getMock();
|
||||||
$this->http = $this->getMockBuilder(
|
$this->http = $this->getMockBuilder(
|
||||||
'\OC\AppFramework\Http')
|
\OC\AppFramework\Http::class)
|
||||||
->disableOriginalConstructor()
|
->disableOriginalConstructor()
|
||||||
->getMock();
|
->getMock();
|
||||||
|
|
||||||
|
@ -124,7 +140,10 @@ class DispatcherTest extends \Test\TestCase {
|
||||||
$this->http,
|
$this->http,
|
||||||
$this->middlewareDispatcher,
|
$this->middlewareDispatcher,
|
||||||
$this->reflector,
|
$this->reflector,
|
||||||
$this->request
|
$this->request,
|
||||||
|
$this->config,
|
||||||
|
\OC::$server->getDatabaseConnection(),
|
||||||
|
$this->logger
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->response = $this->createMock(Response::class);
|
$this->response = $this->createMock(Response::class);
|
||||||
|
@ -299,7 +318,10 @@ class DispatcherTest extends \Test\TestCase {
|
||||||
);
|
);
|
||||||
$this->dispatcher = new Dispatcher(
|
$this->dispatcher = new Dispatcher(
|
||||||
$this->http, $this->middlewareDispatcher, $this->reflector,
|
$this->http, $this->middlewareDispatcher, $this->reflector,
|
||||||
$this->request
|
$this->request,
|
||||||
|
$this->config,
|
||||||
|
\OC::$server->getDatabaseConnection(),
|
||||||
|
$this->logger
|
||||||
);
|
);
|
||||||
$controller = new TestController('app', $this->request);
|
$controller = new TestController('app', $this->request);
|
||||||
|
|
||||||
|
@ -330,7 +352,10 @@ class DispatcherTest extends \Test\TestCase {
|
||||||
);
|
);
|
||||||
$this->dispatcher = new Dispatcher(
|
$this->dispatcher = new Dispatcher(
|
||||||
$this->http, $this->middlewareDispatcher, $this->reflector,
|
$this->http, $this->middlewareDispatcher, $this->reflector,
|
||||||
$this->request
|
$this->request,
|
||||||
|
$this->config,
|
||||||
|
\OC::$server->getDatabaseConnection(),
|
||||||
|
$this->logger
|
||||||
);
|
);
|
||||||
$controller = new TestController('app', $this->request);
|
$controller = new TestController('app', $this->request);
|
||||||
|
|
||||||
|
@ -364,7 +389,10 @@ class DispatcherTest extends \Test\TestCase {
|
||||||
);
|
);
|
||||||
$this->dispatcher = new Dispatcher(
|
$this->dispatcher = new Dispatcher(
|
||||||
$this->http, $this->middlewareDispatcher, $this->reflector,
|
$this->http, $this->middlewareDispatcher, $this->reflector,
|
||||||
$this->request
|
$this->request,
|
||||||
|
$this->config,
|
||||||
|
\OC::$server->getDatabaseConnection(),
|
||||||
|
$this->logger
|
||||||
);
|
);
|
||||||
$controller = new TestController('app', $this->request);
|
$controller = new TestController('app', $this->request);
|
||||||
|
|
||||||
|
@ -397,7 +425,10 @@ class DispatcherTest extends \Test\TestCase {
|
||||||
);
|
);
|
||||||
$this->dispatcher = new Dispatcher(
|
$this->dispatcher = new Dispatcher(
|
||||||
$this->http, $this->middlewareDispatcher, $this->reflector,
|
$this->http, $this->middlewareDispatcher, $this->reflector,
|
||||||
$this->request
|
$this->request,
|
||||||
|
$this->config,
|
||||||
|
\OC::$server->getDatabaseConnection(),
|
||||||
|
$this->logger
|
||||||
);
|
);
|
||||||
$controller = new TestController('app', $this->request);
|
$controller = new TestController('app', $this->request);
|
||||||
|
|
||||||
|
@ -431,7 +462,10 @@ class DispatcherTest extends \Test\TestCase {
|
||||||
);
|
);
|
||||||
$this->dispatcher = new Dispatcher(
|
$this->dispatcher = new Dispatcher(
|
||||||
$this->http, $this->middlewareDispatcher, $this->reflector,
|
$this->http, $this->middlewareDispatcher, $this->reflector,
|
||||||
$this->request
|
$this->request,
|
||||||
|
$this->config,
|
||||||
|
\OC::$server->getDatabaseConnection(),
|
||||||
|
$this->logger
|
||||||
);
|
);
|
||||||
$controller = new TestController('app', $this->request);
|
$controller = new TestController('app', $this->request);
|
||||||
|
|
||||||
|
@ -467,7 +501,10 @@ class DispatcherTest extends \Test\TestCase {
|
||||||
);
|
);
|
||||||
$this->dispatcher = new Dispatcher(
|
$this->dispatcher = new Dispatcher(
|
||||||
$this->http, $this->middlewareDispatcher, $this->reflector,
|
$this->http, $this->middlewareDispatcher, $this->reflector,
|
||||||
$this->request
|
$this->request,
|
||||||
|
$this->config,
|
||||||
|
\OC::$server->getDatabaseConnection(),
|
||||||
|
$this->logger
|
||||||
);
|
);
|
||||||
$controller = new TestController('app', $this->request);
|
$controller = new TestController('app', $this->request);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue