aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAaron Schulz <aschulz@wikimedia.org>2023-03-01 12:16:39 -0800
committerKrinkle <krinkle@fastmail.com>2023-03-23 00:08:49 +0000
commit29bab859fc8ef3680143a792acf1cf0acad22968 (patch)
tree2e28cf81b650d8b4b11d0e10516c788533edb7db
parent4de04152407da76f53291128ed0ac723782efb98 (diff)
downloadmediawikicore-29bab859fc8ef3680143a792acf1cf0acad22968.tar.gz
mediawikicore-29bab859fc8ef3680143a792acf1cf0acad22968.zip
profiler: Add ProfilingContext class
Use this class to track the entry point and handler used for requests, making it available for use in profiling, stats, and logging code. This makes it possible for periodic and/or shutdown profiling callbacks to know the basic action handler that applies to the request (if any). Metric names can easily include this string along with MW_ENTRY_POINT to create per-action profiling dashboards. This info cannot otherwise be acquired from things like excimer stack traces since the router and handler classes do not appear in the stack during PRESEND deferred updates and variations like ApiMain/SpecialPage "inclusion mode" would have to be detected somehow. Bug: T330810 Change-Id: Icca5a7a343faeeb18652994c96752acb61a61fd1
-rw-r--r--autoload.php1
-rw-r--r--includes/MediaWiki.php5
-rw-r--r--includes/ResourceLoader/ResourceLoader.php2
-rw-r--r--includes/Rest/Router.php2
-rw-r--r--includes/api/ApiMain.php2
-rw-r--r--includes/profiler/ProfilingContext.php79
-rw-r--r--includes/specialpage/SpecialPageFactory.php2
-rw-r--r--tests/phpunit/MediaWikiIntegrationTestCase.php2
-rw-r--r--tests/phpunit/includes/profiler/ProfilingContextTest.php54
-rw-r--r--thumb.php3
10 files changed, 151 insertions, 1 deletions
diff --git a/autoload.php b/autoload.php
index f346a7426fe8..e9b654e9e191 100644
--- a/autoload.php
+++ b/autoload.php
@@ -1653,6 +1653,7 @@ $wgAutoloadLocalClasses = [
'MediaWiki\\Preferences\\SignatureValidatorFactory' => __DIR__ . '/includes/preferences/SignatureValidatorFactory.php',
'MediaWiki\\Preferences\\TimezoneFilter' => __DIR__ . '/includes/preferences/TimezoneFilter.php',
'MediaWiki\\ProcOpenError' => __DIR__ . '/includes/exception/ProcOpenError.php',
+ 'MediaWiki\\Profiler\\ProfilingContext' => __DIR__ . '/includes/profiler/ProfilingContext.php',
'MediaWiki\\RenameUser\\Hook\\RenameUserAbortHook' => __DIR__ . '/includes/RenameUser/Hook/RenameUserAbortHook.php',
'MediaWiki\\RenameUser\\Hook\\RenameUserCompleteHook' => __DIR__ . '/includes/RenameUser/Hook/RenameUserCompleteHook.php',
'MediaWiki\\RenameUser\\Hook\\RenameUserPreRenameHook' => __DIR__ . '/includes/RenameUser/Hook/RenameUserPreRenameHook.php',
diff --git a/includes/MediaWiki.php b/includes/MediaWiki.php
index a8d47e2c3a12..cb5d99c190e1 100644
--- a/includes/MediaWiki.php
+++ b/includes/MediaWiki.php
@@ -26,6 +26,7 @@ use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MainConfigNames;
use MediaWiki\MediaWikiServices;
use MediaWiki\Permissions\PermissionStatus;
+use MediaWiki\Profiler\ProfilingContext;
use MediaWiki\Request\DerivativeRequest;
use MediaWiki\Request\WebResponse;
use MediaWiki\Title\Title;
@@ -526,9 +527,11 @@ class MediaWiki {
$t = microtime( true );
$actionName = $this->getAction();
$services = MediaWikiServices::getInstance();
- $action = $services->getActionFactory()->getAction( $actionName, $article, $this->context );
+ $action = $services->getActionFactory()->getAction( $actionName, $article, $this->context );
if ( $action instanceof Action ) {
+ ProfilingContext::singleton()->init( MW_ENTRY_POINT, $actionName );
+
// Check read permissions
if ( $action->needsReadRights() && !$user->isAllowed( 'read' ) ) {
throw new PermissionsError( 'read' );
diff --git a/includes/ResourceLoader/ResourceLoader.php b/includes/ResourceLoader/ResourceLoader.php
index ad46ddd35c86..492fff773303 100644
--- a/includes/ResourceLoader/ResourceLoader.php
+++ b/includes/ResourceLoader/ResourceLoader.php
@@ -37,6 +37,7 @@ use MediaWiki\HookContainer\HookContainer;
use MediaWiki\Html\Html;
use MediaWiki\MainConfigNames;
use MediaWiki\MediaWikiServices;
+use MediaWiki\Profiler\ProfilingContext;
use MediaWiki\Request\HeaderCallback;
use MediaWiki\Title\Title;
use MediaWiki\User\UserOptionsLookup;
@@ -750,6 +751,7 @@ class ResourceLoader implements LoggerAwareInterface {
ob_start();
$responseTime = $this->measureResponseTime();
+ ProfilingContext::singleton()->init( MW_ENTRY_POINT, 'respond' );
// Find out which modules are missing and instantiate the others
$modules = [];
diff --git a/includes/Rest/Router.php b/includes/Rest/Router.php
index 054f2f53bc80..6f6e58480fd8 100644
--- a/includes/Rest/Router.php
+++ b/includes/Rest/Router.php
@@ -9,6 +9,7 @@ use MediaWiki\Config\ServiceOptions;
use MediaWiki\HookContainer\HookContainer;
use MediaWiki\MainConfigNames;
use MediaWiki\Permissions\Authority;
+use MediaWiki\Profiler\ProfilingContext;
use MediaWiki\Rest\BasicAccess\BasicAuthorizerInterface;
use MediaWiki\Rest\PathTemplateMatcher\PathMatcher;
use MediaWiki\Rest\Reporter\ErrorReporter;
@@ -493,6 +494,7 @@ class Router {
* @return ResponseInterface
*/
private function executeHandler( $handler ): ResponseInterface {
+ ProfilingContext::singleton()->init( MW_ENTRY_POINT, $handler->getPath() );
// Check for basic authorization, to avoid leaking data from private wikis
$authResult = $this->basicAuth->authorize( $handler->getRequest(), $handler );
if ( $authResult ) {
diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php
index f4162cfdb8f8..b06f5cfed8a1 100644
--- a/includes/api/ApiMain.php
+++ b/includes/api/ApiMain.php
@@ -28,6 +28,7 @@ use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MainConfigNames;
use MediaWiki\MediaWikiServices;
use MediaWiki\ParamValidator\TypeDef\UserDef;
+use MediaWiki\Profiler\ProfilingContext;
use MediaWiki\Request\FauxRequest;
use MediaWiki\Request\WebRequestUpload;
use MediaWiki\Rest\HeaderParser\Origin;
@@ -1886,6 +1887,7 @@ class ApiMain extends ApiBase {
$this->mModule = $module;
if ( !$this->mInternalMode ) {
+ ProfilingContext::singleton()->init( MW_ENTRY_POINT, $module->getModuleName() );
$this->setRequestExpectations( $module );
}
diff --git a/includes/profiler/ProfilingContext.php b/includes/profiler/ProfilingContext.php
new file mode 100644
index 000000000000..9859a7100ed7
--- /dev/null
+++ b/includes/profiler/ProfilingContext.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace MediaWiki\Profiler;
+
+/**
+ * Class for tracking request-level classification information for profiling/stats/logging
+ *
+ * @note: this avoids the use of MediaWikiServices so that shutdown functions can use it
+ *
+ * @since 1.40
+ */
+class ProfilingContext {
+ private string $entryPoint = 'unknown';
+ private string $handler = 'unknown';
+
+ private bool $initialized = false;
+
+ private static ?ProfilingContext $instance = null;
+
+ public static function singleton(): ProfilingContext {
+ self::$instance ??= new self();
+
+ return self::$instance;
+ }
+
+ /**
+ * Set entry point name and principle handler name for this request
+ *
+ * @param string $entryPoint Entry point script name (alphanumeric characters)
+ * @param string $handler Handler name (printable ASCII characters)
+ */
+ public function init( string $entryPoint, string $handler ) {
+ // Ignore nested nesting (e.g. rest.php handlers that use ApiMain)
+ if ( !$this->initialized ) {
+ $this->initialized = true;
+ $this->entryPoint = $entryPoint;
+ $this->handler = $handler;
+ }
+ }
+
+ /**
+ * @return bool Whether the context was initialized yet
+ */
+ public function isInitialized() {
+ return $this->initialized;
+ }
+
+ /**
+ * @return string Entry point name for this request (e.g. "index", "api", "load")
+ */
+ public function getEntryPoint(): string {
+ return $this->entryPoint;
+ }
+
+ /**
+ * @return string Handler name for this request (e.g. "edit", "recentchanges")
+ */
+ public function getHandler(): string {
+ return $this->handler;
+ }
+
+ /**
+ * @return string Statsd metric name prefix for this entry point and handler
+ */
+ public function getHandlerMetricPrefix(): string {
+ // Replace any characters that may have a special meaning in statsd/graphite
+ return $this->entryPoint . '_' . strtr(
+ $this->handler,
+ [ '{' => '', '}' => '', ':' => '_', '/' => '_', '.' => '_' ]
+ );
+ }
+
+ /**
+ * @internal For testing only
+ */
+ public static function destroySingleton() {
+ self::$instance = null;
+ }
+}
diff --git a/includes/specialpage/SpecialPageFactory.php b/includes/specialpage/SpecialPageFactory.php
index e993d36d9d3d..3b639150a121 100644
--- a/includes/specialpage/SpecialPageFactory.php
+++ b/includes/specialpage/SpecialPageFactory.php
@@ -33,6 +33,7 @@ use MediaWiki\Linker\LinkRenderer;
use MediaWiki\MainConfigNames;
use MediaWiki\MediaWikiServices;
use MediaWiki\Page\PageReference;
+use MediaWiki\Profiler\ProfilingContext;
use MediaWiki\Specials\SpecialActiveUsers;
use MediaWiki\Specials\SpecialAllMessages;
use MediaWiki\Specials\SpecialAllPages;
@@ -1445,6 +1446,7 @@ class SpecialPageFactory {
}
if ( !$including ) {
+ ProfilingContext::singleton()->init( MW_ENTRY_POINT, $page->getName() );
// Narrow DB query expectations for this HTTP request
$trxLimits = $context->getConfig()->get( MainConfigNames::TrxProfilerLimits );
$trxProfiler = Profiler::instance()->getTransactionProfiler();
diff --git a/tests/phpunit/MediaWikiIntegrationTestCase.php b/tests/phpunit/MediaWikiIntegrationTestCase.php
index efaecd4bbb59..646de2a64e81 100644
--- a/tests/phpunit/MediaWikiIntegrationTestCase.php
+++ b/tests/phpunit/MediaWikiIntegrationTestCase.php
@@ -12,6 +12,7 @@ use MediaWiki\Page\PageIdentity;
use MediaWiki\Page\ProperPageIdentity;
use MediaWiki\Permissions\Authority;
use MediaWiki\Permissions\UltimateAuthority;
+use MediaWiki\Profiler\ProfilingContext;
use MediaWiki\Request\FauxRequest;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Storage\PageUpdateStatus;
@@ -720,6 +721,7 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
}
$wgRequest = RequestContext::getMain()->getRequest();
MediaWiki\Session\SessionManager::resetCache();
+ ProfilingContext::destroySingleton();
// If anything changed the content language, we need to
// reset the SpecialPageFactory.
diff --git a/tests/phpunit/includes/profiler/ProfilingContextTest.php b/tests/phpunit/includes/profiler/ProfilingContextTest.php
new file mode 100644
index 000000000000..cf2631f297bb
--- /dev/null
+++ b/tests/phpunit/includes/profiler/ProfilingContextTest.php
@@ -0,0 +1,54 @@
+<?php
+
+use MediaWiki\Profiler\ProfilingContext;
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * @covers \MediaWiki\Profiler\ProfilingContext
+ */
+class ProfilingContextTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
+ public static function provideEntryPointNames() {
+ return [
+ [ 'index', 'edit', 'index_edit' ],
+ [ 'index', 'Recentchanges', 'index_Recentchanges' ],
+ [ 'api', 'upload', 'api_upload' ],
+ [ 'rest', '/wikibase/v1/something/{complex}/id', 'rest__wikibase_v1_something_complex_id' ]
+ ];
+ }
+
+ /**
+ * @dataProvider provideEntryPointNames
+ */
+ public function testSetEntryPointHandler( $entryPoint, $handler, $metricName ) {
+ $profilerContext = new ProfilingContext();
+
+ $this->assertFalse( $profilerContext->isInitialized() );
+ $profilerContext->init( $entryPoint, $handler );
+
+ $this->assertTrue( $profilerContext->isInitialized() );
+ $this->assertSame( $entryPoint, $profilerContext->getEntryPoint() );
+ $this->assertSame( $handler, $profilerContext->getHandler() );
+ $this->assertSame( $metricName, $profilerContext->getHandlerMetricPrefix() );
+ }
+}
diff --git a/thumb.php b/thumb.php
index 888945117c08..6dc43ec3571d 100644
--- a/thumb.php
+++ b/thumb.php
@@ -28,6 +28,7 @@
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
+use MediaWiki\Profiler\ProfilingContext;
use MediaWiki\Title\Title;
use Wikimedia\AtEase\AtEase;
@@ -44,6 +45,8 @@ wfThumbMain();
function wfThumbMain() {
global $wgTrivialMimeDetection, $wgRequest;
+ ProfilingContext::singleton()->init( MW_ENTRY_POINT, 'stream' );
+
// Don't use fancy MIME detection, just check the file extension for jpg/gif/png
$wgTrivialMimeDetection = true;