REST: collect metrics on endpoint access

This collects metrics on how often each endpoint is hit, and with which
code it responds.

Change-Id: Ie282bc5b5f5df0bbd6a40c8362ba73fcbbf36c2e
This commit is contained in:
daniel 2022-12-05 20:32:20 +01:00
parent 812befc34a
commit d32c260ed0
5 changed files with 60 additions and 5 deletions

View file

@ -63,6 +63,8 @@ class EntryPoint {
$authority
);
$metrics = $services->getStatsdDataFactory();
return ( new Router(
self::getRouteFiles( $conf ),
ExtensionRegistry::getInstance()->getAttribute( 'RestRoutes' ),
@ -76,7 +78,9 @@ class EntryPoint {
new MWErrorReporter(),
$services->getHookContainer(),
$context->getRequest()->getSession()
) )->setCors( $cors );
) )
->setCors( $cors )
->setMetrics( $metrics );
}
/**

View file

@ -87,6 +87,15 @@ abstract class Handler {
$this->postInitSetup();
}
/**
* Returns the path this handler is bound to, including path variables.
*
* @return string
*/
public function getPath(): string {
return $this->getConfig()['path'];
}
/**
* Get the Router. The return type declaration causes it to raise
* a fatal error if init() has not yet been called.

View file

@ -4,6 +4,7 @@ namespace MediaWiki\Rest;
use AppendIterator;
use BagOStuff;
use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\HookContainer\HookContainer;
use MediaWiki\MainConfigNames;
@ -13,6 +14,7 @@ use MediaWiki\Rest\PathTemplateMatcher\PathMatcher;
use MediaWiki\Rest\Reporter\ErrorReporter;
use MediaWiki\Rest\Validator\Validator;
use MediaWiki\Session\Session;
use NullStatsdDataFactory;
use Throwable;
use Wikimedia\Message\MessageValue;
use Wikimedia\ObjectFactory\ObjectFactory;
@ -80,6 +82,9 @@ class Router {
/** @var Session */
private $session;
/** @var StatsdDataFactoryInterface */
private $metrics;
/**
* @internal
* @var array
@ -135,6 +140,8 @@ class Router {
$this->errorReporter = $errorReporter;
$this->hookContainer = $hookContainer;
$this->session = $session;
$this->metrics = new NullStatsdDataFactory();
}
/**
@ -402,14 +409,25 @@ class Router {
$request->setPathParams( array_map( 'rawurldecode', $match['params'] ) );
$handler = $this->createHandler( $request, $match['userData'] );
// Replace any characters that may have a special meaning in the metrics DB.
$metricsKey = 'REST/endpoint/' . strtr( $handler->getPath(), [
'{' => '_',
'}' => '_',
] );
$this->metrics->increment( "$metricsKey.req.$requestMethod" );
try {
return $this->executeHandler( $handler );
$response = $this->executeHandler( $handler );
} catch ( HttpException $e ) {
return $this->responseFactory->createFromException( $e );
$response = $this->responseFactory->createFromException( $e );
} catch ( Throwable $e ) {
$this->errorReporter->reportError( $e, $handler, $request );
return $this->responseFactory->createFromException( $e );
$response = $this->responseFactory->createFromException( $e );
}
$this->metrics->increment( "$metricsKey.response.status" . $response->getStatusCode() );
return $response;
}
/**
@ -507,4 +525,14 @@ class Router {
return $this;
}
/**
* @param StatsdDataFactoryInterface $metrics
* @return self
*/
public function setMetrics( StatsdDataFactoryInterface $metrics ): self {
$this->metrics = $metrics;
return $this;
}
}

View file

@ -47,12 +47,19 @@ class RestStructureTest extends MediaWikiIntegrationTestCase {
$services = $this->createNoOpMock(
MediaWikiServices::class,
[ 'getMainConfig', 'getHookContainer', 'getObjectFactory', 'getLocalServerObjectCache' ]
[
'getMainConfig',
'getHookContainer',
'getObjectFactory',
'getLocalServerObjectCache',
'getStatsdDataFactory',
]
);
$services->method( 'getMainConfig' )->willReturn( $config );
$services->method( 'getHookContainer' )->willReturn( $hookContainer );
$services->method( 'getObjectFactory' )->willReturn( $objectFactory );
$services->method( 'getLocalServerObjectCache' )->willReturn( new EmptyBagOStuff() );
$services->method( 'getStatsdDataFactory' )->willReturn( new NullStatsdDataFactory() );
return $services;
}

View file

@ -113,6 +113,13 @@ class HandlerTest extends \MediaWikiUnitTestCase {
$this->assertStringEndsWith( $expected, $url );
}
public function testGetPath() {
$handler = $this->newHandler();
$request = new RequestData();
$this->initHandler( $handler, $request, [ 'path' => 'just/some/path' ] );
$this->assertSame( 'just/some/path', $handler->getPath() );
}
public function testGetResponseFactory() {
$handler = $this->newHandler();
$this->initHandler( $handler, new RequestData() );