Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow using the profiler to selectively profile production instances #39399

Closed
wants to merge 13 commits into from
2 changes: 2 additions & 0 deletions apps/dav/appinfo/v1/caldav.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
use OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin;
use OCA\DAV\Connector\Sabre\MaintenancePlugin;
use OCA\DAV\Connector\Sabre\Principal;
use OCA\DAV\Profiler\ProfilerPlugin;
use OCP\Accounts\IAccountManager;
use Psr\Log\LoggerInterface;

Expand Down Expand Up @@ -116,6 +117,7 @@
$server->addPlugin(\OC::$server->query(\OCA\DAV\CalDAV\Schedule\IMipPlugin::class));
}
$server->addPlugin(new ExceptionLoggerPlugin('caldav', $logger));
$server->addPlugin(\OC::$server->get(ProfilerPlugin::class));

// And off we go!
$server->exec();
2 changes: 2 additions & 0 deletions apps/dav/appinfo/v1/carddav.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
use OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin;
use OCA\DAV\Connector\Sabre\MaintenancePlugin;
use OCA\DAV\Connector\Sabre\Principal;
use OCA\DAV\Profiler\ProfilerPlugin;
use OCP\Accounts\IAccountManager;
use OCP\App\IAppManager;
use Psr\Log\LoggerInterface;
Expand Down Expand Up @@ -103,6 +104,7 @@
\OC::$server->get(LoggerInterface::class)
)));
$server->addPlugin(new ExceptionLoggerPlugin('carddav', \OC::$server->get(LoggerInterface::class)));
$server->addPlugin(\OC::$server->get(ProfilerPlugin::class));

// And off we go!
$server->exec();
2 changes: 2 additions & 0 deletions apps/dav/appinfo/v2/direct.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*
*/
use \OCA\DAV\Direct\ServerFactory;
use OCA\DAV\Profiler\ProfilerPlugin;

// no php execution timeout for webdav
if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
Expand All @@ -48,5 +49,6 @@
\OC::$server->getBruteForceThrottler(),
\OC::$server->getRequest()
);
$server->addPlugin(\OC::$server->get(ProfilerPlugin::class));

$server->exec();
2 changes: 2 additions & 0 deletions apps/dav/lib/Connector/Sabre/ServerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
*/
namespace OCA\DAV\Connector\Sabre;

use OCA\DAV\Profiler\ProfilerPlugin;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Folder;
use OCA\DAV\AppInfo\PluginManager;
Expand Down Expand Up @@ -99,6 +100,7 @@ public function createServer(string $baseUri,
$server->setBaseUri($baseUri);

// Load plugins
$server->addPlugin(\OC::$server->get(ProfilerPlugin::class));
$server->addPlugin(new \OCA\DAV\Connector\Sabre\MaintenancePlugin($this->config, $this->l10n));
$server->addPlugin(new \OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin($this->config));
$server->addPlugin(new \OCA\DAV\Connector\Sabre\AnonymousOptionsPlugin());
Expand Down
63 changes: 53 additions & 10 deletions apps/dav/lib/Profiler/ProfilerPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,68 @@

namespace OCA\DAV\Profiler;

use OCP\AppFramework\Http\Response;
use OCP\Diagnostics\IEventLogger;
use OCP\IRequest;
use OCP\Profiler\IProfiler;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

class ProfilerPlugin extends \Sabre\DAV\ServerPlugin {
private IRequest $request;
class ProfilerPlugin extends ServerPlugin {
private bool $finalized = false;
public function __construct(
private IRequest $request,
private IProfiler $profiler,
private IEventLogger $eventLogger,
) {
$a = 1;
}

public function initialize(Server $server): void {
$server->on('beforeMethod:*', [$this, 'beforeMethod'], 1);
$server->on('afterMethod:*', [$this, 'afterMethod'], 9999);
$server->on('afterResponse', [$this, 'afterResponse'], 9999);
$server->on('exception', [$this, 'exception']);
}

public function beforeMethod(): void {
$this->eventLogger->start('dav:server:method', 'Processing dav request');
}

public function __construct(IRequest $request) {
$this->request = $request;
public function afterMethod(RequestInterface $request, ResponseInterface $response): void {
$this->eventLogger->end('dav:server:method');
$this->eventLogger->start('dav:server:response', 'Sending dav response');
if ($this->profiler->isEnabled()) {
$response->addHeader('X-Debug-Token', $this->request->getId());
}
}

/** @return void */
public function initialize(Server $server) {
$server->on('afterMethod:*', [$this, 'afterMethod']);
public function afterResponse(RequestInterface $request, ResponseInterface $response): void {
$this->eventLogger->end('dav:server:response');
$this->finalize($response->getStatus());
}

/** @return void */
public function afterMethod(RequestInterface $request, ResponseInterface $response) {
$response->addHeader('X-Debug-Token', $this->request->getId());
public function exception(): void {
$this->finalize();
}

public function __destruct() {
// in error cases, the "afterResponse" isn't called, so we do the finalization now
$this->finalize();
}

public function finalize(int $status = null): void {
if ($this->finalized) {
return;
}
$this->finalized = true;

$this->eventLogger->end('runtime');
if ($this->profiler->isEnabled()) {
$profile = $this->profiler->collect($this->request, new Response($status));
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
$this->profiler->saveProfile($profile);
}
}
}
11 changes: 1 addition & 10 deletions apps/dav/lib/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public function __construct(IRequest $request, string $baseUri) {
$this->server->httpRequest->setUrl($this->request->getRequestUri());
$this->server->setBaseUri($this->baseUri);

$this->server->addPlugin(new ProfilerPlugin($this->request));
$this->server->addPlugin(\OC::$server->get(ProfilerPlugin::class));
$this->server->addPlugin(new BlockLegacyClientPlugin(\OC::$server->getConfig()));
$this->server->addPlugin(new AnonymousOptionsPlugin());
$authPlugin = new Plugin();
Expand Down Expand Up @@ -361,16 +361,7 @@ function () {
}

public function exec() {
/** @var IEventLogger $eventLogger */
$eventLogger = \OC::$server->get(IEventLogger::class);
$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 {
Expand Down
34 changes: 34 additions & 0 deletions core/Command/Profiler/Clear.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

// SPDX-FileCopyrightText: 2022 Robin Appelman <[email protected]>
// SPDX-License-Identifier: AGPL-3.0-or-later

namespace OC\Core\Command\Profiler;

use OC\Core\Command\Base;
use OCP\Profiler\IProfiler;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class Clear extends Base {
private IProfiler $profiler;

public function __construct(IProfiler $profiler) {
parent::__construct();
$this->profiler = $profiler;
}

protected function configure() {
Fixed Show fixed Hide fixed
$this
->setName('profiler:clear')
->setDescription('Remove all saved profiles');
}

protected function execute(InputInterface $input, OutputInterface $output): int {
$this->profiler->clear();

return 0;
}
}
33 changes: 33 additions & 0 deletions core/Command/Profiler/Disable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

// SPDX-FileCopyrightText: 2022 Robin Appelman <[email protected]>
// SPDX-License-Identifier: AGPL-3.0-or-later

namespace OC\Core\Command\Profiler;

use OCP\IConfig;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class Disable extends Command {
private IConfig $config;

public function __construct(IConfig $config) {
parent::__construct();
$this->config = $config;
}

protected function configure() {
Fixed Show fixed Hide fixed
$this
->setName('profiler:disable')
->setDescription('Disable profiling');
}

protected function execute(InputInterface $input, OutputInterface $output): int {
$this->config->setSystemValue('profiler', false);
return 0;
}
}
33 changes: 33 additions & 0 deletions core/Command/Profiler/Enable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

// SPDX-FileCopyrightText: 2022 Robin Appelman <[email protected]>
// SPDX-License-Identifier: AGPL-3.0-or-later

namespace OC\Core\Command\Profiler;

use OCP\IConfig;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class Enable extends Command {
private IConfig $config;

public function __construct(IConfig $config) {
parent::__construct();
$this->config = $config;
}

protected function configure() {
Fixed Show fixed Hide fixed
$this
->setName('profiler:enable')
->setDescription('Enable profiling');
}

protected function execute(InputInterface $input, OutputInterface $output): int {
$this->config->setSystemValue('profiler', true);
return 0;
}
}
58 changes: 58 additions & 0 deletions core/Command/Profiler/Export.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

// SPDX-FileCopyrightText: 2022 Robin Appelman <[email protected]>
// SPDX-License-Identifier: AGPL-3.0-or-later

namespace OC\Core\Command\Profiler;

use _HumbugBox1cb33d1f20f1\LanguageServerProtocol\PackageDescriptor;
use OC\Core\Command\Base;
use OCP\Profiler\IProfiler;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class Export extends Base {
private IProfiler $profiler;

public function __construct(IProfiler $profiler) {
parent::__construct();
$this->profiler = $profiler;
}

protected function configure() {
Fixed Show fixed Hide fixed
$this
->setName('profiler:export')
->setDescription('Export captured profiles as json')
->addOption('limit', null, InputOption::VALUE_REQUIRED, 'Maximum number of profiles to export')
->addOption('url', null, InputOption::VALUE_REQUIRED, 'Url to export profiles for')
->addOption('since', null, InputOption::VALUE_REQUIRED, 'Minimum date for exported profiles, as unix timestamp')
->addOption('before', null, InputOption::VALUE_REQUIRED, 'Maximum date for exported profiles, as unix timestamp')
->addOption('token', null, InputOption::VALUE_REQUIRED, 'Export only profile for a single request token');
}

protected function execute(InputInterface $input, OutputInterface $output): int {
$since = $input->getOption('since') ? (int)$input->getOption('since') : null;
$before = $input->getOption('before') ? (int)$input->getOption('before') : null;
$limit = $input->getOption('limit') ? (int)$input->getOption('limit') : 1000;
$token = $input->getOption('token') ? $input->getOption('token') : null;
$url = $input->getOption('url');

if ($token) {
$profiles = [$this->profiler->loadProfile($token)];
$profiles = array_filter($profiles);
} else {
$profiles = $this->profiler->find($url, $limit, null, $since, $before);
$profiles = array_reverse($profiles);
$profiles = array_map(function (array $profile) {
return $this->profiler->loadProfile($profile['token']);
}, $profiles);
}

$output->writeln(json_encode($profiles));

return 0;
}
}
Loading