diff options
author | Julius Knorr <jus@bitgrid.net> | 2025-04-10 17:03:00 +0200 |
---|---|---|
committer | Julius Knorr <jus@bitgrid.net> | 2025-04-22 08:57:02 +0200 |
commit | 3fc9a9952182772119f1bab994b3142fddfad4fd (patch) | |
tree | e9182ea9f94f16131253826b95756feb01e0ab36 /lib | |
parent | cb4fcd0f40439dde8a5db42d74f1c5cdc3b2bab9 (diff) | |
download | nextcloud-server-perf/excimer.tar.gz nextcloud-server-perf/excimer.zip |
perf: Add config options to trigger individual and sample profiling using excimerperf/excimer
Signed-off-by: Julius Knorr <jus@bitgrid.net>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/base.php | 17 | ||||
-rw-r--r-- | lib/composer/composer/autoload_classmap.php | 1 | ||||
-rw-r--r-- | lib/composer/composer/autoload_static.php | 1 | ||||
-rw-r--r-- | lib/private/Profiler/BuiltInProfiler.php | 95 |
4 files changed, 111 insertions, 3 deletions
diff --git a/lib/base.php b/lib/base.php index 0ed282eff00..aa463e206a3 100644 --- a/lib/base.php +++ b/lib/base.php @@ -7,6 +7,7 @@ declare(strict_types=1); * SPDX-License-Identifier: AGPL-3.0-only */ use OC\Encryption\HookManager; +use OC\Profiler\BuiltInProfiler; use OC\Share20\GroupDeletedListener; use OC\Share20\Hooks; use OC\Share20\UserDeletedListener; @@ -14,6 +15,7 @@ use OC\Share20\UserRemovedListener; use OCP\EventDispatcher\IEventDispatcher; use OCP\Group\Events\GroupDeletedEvent; use OCP\Group\Events\UserRemovedEvent; +use OCP\IConfig; use OCP\ILogger; use OCP\IRequest; use OCP\IURLGenerator; @@ -126,7 +128,6 @@ class OC { } } - if (OC::$CLI) { OC::$WEBROOT = self::$config->getValue('overwritewebroot', ''); } else { @@ -522,7 +523,7 @@ class OC { * We use an additional cookie since we want to protect logout CSRF and * also we can't directly interfere with PHP's session mechanism. */ - private static function performSameSiteCookieProtection(\OCP\IConfig $config): void { + private static function performSameSiteCookieProtection(IConfig $config): void { $request = Server::get(IRequest::class); // Some user agents are notorious and don't really properly follow HTTP @@ -635,6 +636,16 @@ class OC { self::$server = new \OC\Server(\OC::$WEBROOT, self::$config); self::$server->boot(); + try { + $profiler = new BuiltInProfiler( + Server::get(IConfig::class), + Server::get(IRequest::class), + ); + $profiler->start(); + } catch (\Throwable $e) { + logger('core')->error('Failed to start profiler: ' . $e->getMessage(), ['app' => 'base']); + } + if (self::$CLI && in_array('--' . \OCP\Console\ReservedOptions::DEBUG_LOG, $_SERVER['argv'])) { \OC\Core\Listener\BeforeMessageLoggedEventListener::setup(); } @@ -654,7 +665,7 @@ class OC { // initialize intl fallback if necessary OC_Util::isSetLocaleWorking(); - $config = Server::get(\OCP\IConfig::class); + $config = Server::get(IConfig::class); if (!defined('PHPUNIT_RUN')) { $errorHandler = new OC\Log\ErrorHandler( \OCP\Server::get(\Psr\Log\LoggerInterface::class), diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 670a719c9fb..1f3d9d3813b 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1852,6 +1852,7 @@ 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\\BuiltInProfiler' => $baseDir . '/lib/private/Profiler/BuiltInProfiler.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', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index c15d9d5b53c..8c87d90156d 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1893,6 +1893,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 '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\\BuiltInProfiler' => __DIR__ . '/../../..' . '/lib/private/Profiler/BuiltInProfiler.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', diff --git a/lib/private/Profiler/BuiltInProfiler.php b/lib/private/Profiler/BuiltInProfiler.php new file mode 100644 index 00000000000..0a62365e901 --- /dev/null +++ b/lib/private/Profiler/BuiltInProfiler.php @@ -0,0 +1,95 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace OC\Profiler; + +use DateTime; +use OCP\IConfig; +use OCP\IRequest; + +class BuiltInProfiler { + private \ExcimerProfiler $excimer; + + public function __construct( + private IConfig $config, + private IRequest $request, + ) { + } + + public function start(): void { + if (!extension_loaded('excimer')) { + return; + } + + $shouldProfileSingleRequest = $this->shouldProfileSingleRequest(); + $shouldSample = $this->config->getSystemValueBool('profiling.sample') && !$shouldProfileSingleRequest; + + + if (!$shouldProfileSingleRequest && !$shouldSample) { + return; + } + + $requestRate = $this->config->getSystemValue('profiling.request.rate', 0.001); + $sampleRate = $this->config->getSystemValue('profiling.sample.rate', 1.0); + $eventType = $this->config->getSystemValue('profiling.event_type', EXCIMER_REAL); + + + $this->excimer = new \ExcimerProfiler(); + $this->excimer->setPeriod($shouldProfileSingleRequest ? $requestRate : $sampleRate); + $this->excimer->setEventType($eventType); + $this->excimer->setMaxDepth(250); + + if ($shouldSample) { + $this->excimer->setFlushCallback([$this, 'handleSampleFlush'], 1); + } + + $this->excimer->start(); + register_shutdown_function([$this, 'handleShutdown']); + } + + public function handleSampleFlush(\ExcimerLog $log): void { + file_put_contents($this->getSampleFilename(), $log->formatCollapsed(), FILE_APPEND); + } + + public function handleShutdown(): void { + $this->excimer->stop(); + + if (!$this->shouldProfileSingleRequest()) { + $this->excimer->flush(); + return; + } + + $request = \OCP\Server::get(IRequest::class); + $data = $this->excimer->getLog()->getSpeedscopeData(); + + $data['profiles'][0]['name'] = $request->getMethod() . ' ' . $request->getRequestUri() . ' ' . $request->getId(); + + file_put_contents($this->getProfileFilename(), json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + } + + private function shouldProfileSingleRequest(): bool { + $shouldProfileSingleRequest = $this->config->getSystemValueBool('profiling.request', false); + $profileSecret = $this->config->getSystemValueString('profiling.secret', ''); + $secretParam = $this->request->getParam('profile_secret') ?? null; + return $shouldProfileSingleRequest || (!empty($profileSecret) && $profileSecret === $secretParam); + } + + private function getSampleFilename(): string { + $profilePath = $this->config->getSystemValueString('profiling.path', '/tmp'); + $sampleRotation = $this->config->getSystemValueInt('profiling.sample.rotation', 60); + $timestamp = floor(time() / ($sampleRotation * 60)) * ($sampleRotation * 60); + $sampleName = date('Y-m-d_Hi', (int)$timestamp); + return $profilePath . '/sample-' . $sampleName . '.log'; + } + + private function getProfileFilename(): string { + $profilePath = $this->config->getSystemValueString('profiling.path', '/tmp'); + $requestId = $this->request->getId(); + return $profilePath . '/profile-' . (new DateTime)->format('Y-m-d_His_v') . '-' . $requestId . '.json'; + } +} |