aboutsummaryrefslogtreecommitdiffstats
path: root/includes/Rest/Module
diff options
context:
space:
mode:
authordaniel <dkinzler@wikimedia.org>2024-05-21 17:02:16 +0200
committerDaniel Kinzler <dkinzler@wikimedia.org>2024-05-21 17:34:59 +0000
commit68dc4845b5d5bd595630038ced307603c44d7c52 (patch)
tree1bd0c9c911aa0bcc9e01667d38dee446efd47e93 /includes/Rest/Module
parentf5a53b215fb84e48fedb13273a594d9caa6ebab2 (diff)
downloadmediawikicore-68dc4845b5d5bd595630038ced307603c44d7c52.tar.gz
mediawikicore-68dc4845b5d5bd595630038ced307603c44d7c52.zip
REST: fix metrics keys
In Iebcde4645d472d2 I broke the way we generate metrics keys from endpoint paths. Instead of using the declared paths with placeholders, we were recording the actual paths, resulting in an explosion of metrics keys. This moves metrics logging from Router into Module, where the declared path is available. This patch also introduces regression tests for the issue. Bug: T365111 Change-Id: I2c9ddfe6e28aaecd313356894f17033e2db59073
Diffstat (limited to 'includes/Rest/Module')
-rw-r--r--includes/Rest/Module/Module.php52
1 files changed, 51 insertions, 1 deletions
diff --git a/includes/Rest/Module/Module.php b/includes/Rest/Module/Module.php
index b370b3e4d34a..f702f972b91c 100644
--- a/includes/Rest/Module/Module.php
+++ b/includes/Rest/Module/Module.php
@@ -2,6 +2,7 @@
namespace MediaWiki\Rest\Module;
+use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
use LogicException;
use MediaWiki\Profiler\ProfilingContext;
use MediaWiki\Rest\BasicAccess\BasicAuthorizerInterface;
@@ -17,6 +18,7 @@ use MediaWiki\Rest\ResponseFactory;
use MediaWiki\Rest\ResponseInterface;
use MediaWiki\Rest\Router;
use MediaWiki\Rest\Validator\Validator;
+use NullStatsdDataFactory;
use Throwable;
use Wikimedia\Message\MessageValue;
use Wikimedia\ObjectFactory\ObjectFactory;
@@ -42,8 +44,9 @@ abstract class Module {
private ObjectFactory $objectFactory;
private Validator $restValidator;
private ErrorReporter $errorReporter;
-
private Router $router;
+
+ private StatsdDataFactoryInterface $stats;
private ?CorsUtils $cors = null;
/**
@@ -71,6 +74,8 @@ abstract class Module {
$this->objectFactory = $objectFactory;
$this->restValidator = $restValidator;
$this->errorReporter = $errorReporter;
+
+ $this->stats = new NullStatsdDataFactory();
}
public function getPathPrefix(): string {
@@ -252,6 +257,7 @@ abstract class Module {
*/
public function execute( string $path, RequestInterface $request ): ResponseInterface {
$handler = null;
+ $startTime = microtime( true );
try {
$handler = $this->getHandlerForPath( $path, $request, true );
@@ -265,9 +271,40 @@ abstract class Module {
$response = $this->responseFactory->createFromException( $e );
}
+ $this->recordMetrics( $handler, $request, $response, $startTime );
+
return $response;
}
+ private function recordMetrics(
+ ?Handler $handler,
+ RequestInterface $request,
+ ResponseInterface $response,
+ float $startTime
+ ) {
+ $microtime = ( microtime( true ) - $startTime ) * 1000;
+
+ // NOTE: The "/" prefix is for consistency with old logs. It's rather ugly.
+ $pathForMetrics = '/' . $this->getPathPrefix();
+ $pathForMetrics .= $handler ? $handler->getPath() : '/UNKNOWN';
+
+ // Replace any characters that may have a special meaning in the metrics DB.
+ $pathForMetrics = strtr( $pathForMetrics, '{}:/.', '---__' );
+
+ $statusCode = $response->getStatusCode();
+ $requestMethod = $request->getMethod();
+ if ( $statusCode >= 400 ) {
+ // count how often we return which error code
+ $this->stats->increment( "rest_api_errors.$pathForMetrics.$requestMethod.$statusCode" );
+ } else {
+ // measure how long it takes to generate a response
+ $this->stats->timing(
+ "rest_api_latency.$pathForMetrics.$requestMethod.$statusCode",
+ $microtime
+ );
+ }
+ }
+
/**
* @internal for testing
*
@@ -348,6 +385,19 @@ abstract class Module {
}
/**
+ * @internal for use by Router
+ *
+ * @param StatsdDataFactoryInterface $stats
+ *
+ * @return self
+ */
+ public function setStats( StatsdDataFactoryInterface $stats ): self {
+ $this->stats = $stats;
+
+ return $this;
+ }
+
+ /**
* Loads a module specification from a file.
*
* This method does not know or care about the structure of the file