Lucas Werkmeister (WMDE) has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/367401 )

Change subject: Log details of long-running constraint checks
......................................................................

Log details of long-running constraint checks

If a constraint check takes a long time, we might be interested in its
details to investigate why it took so long, so log some information
about it. The message is logged as “info” or “warning”, depending on the
duration; both limits (whether to log at all, and whether to bump the
level to “warning”) are configurable, and can be set to null to disable
the logging completely.

Bug: T171281
Change-Id: Icec9925453c1bc75a5be9ee033b86e44cf6790a9
---
M extension.json
M includes/ConstraintCheck/DelegatingConstraintChecker.php
M includes/ConstraintReportFactory.php
M tests/phpunit/Api/CheckConstraintsTest.php
M tests/phpunit/DelegatingConstraintCheckerTest.php
5 files changed, 193 insertions(+), 4 deletions(-)


  git pull 
ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/WikibaseQualityConstraints
 refs/changes/01/367401/1

diff --git a/extension.json b/extension.json
index 8ec7705..021bf14 100644
--- a/extension.json
+++ b/extension.json
@@ -98,6 +98,16 @@
                        "description": "The maximum number of entities that are 
accessed when checking type or subtype relations.",
                        "public": true
                },
+               "WBQualityConstraintsCheckDurationInfoSeconds": {
+                       "value": 1.0,
+                       "description": "The maximum time that a constraint 
check should usually take, in seconds. Any constraint check that takes longer 
than this time is logged at ‘info’ level. Specifying `null` disables this 
logging.",
+                       "public": true
+               },
+               "WBQualityConstraintsCheckDurationWarningSeconds": {
+                       "value": 10.0,
+                       "description": "The maximum time that a constraint 
check should ever take, in seconds. Any constraint check that takes longer than 
this time is logged at ‘warning’ level. Specifying `null` disables this 
logging.",
+                       "public": true
+               },
                "WBQualityConstraintsEnableConstraintsImportFromStatements": {
                        "value": false,
                        "description": "Whether to import property constraint 
statements into the constraint database or not.",
diff --git a/includes/ConstraintCheck/DelegatingConstraintChecker.php 
b/includes/ConstraintCheck/DelegatingConstraintChecker.php
index 5098895..b78d9a6 100644
--- a/includes/ConstraintCheck/DelegatingConstraintChecker.php
+++ b/includes/ConstraintCheck/DelegatingConstraintChecker.php
@@ -2,9 +2,12 @@
 
 namespace WikibaseQuality\ConstraintReport\ConstraintCheck;
 
+use Config;
 use InvalidArgumentException;
 use LogicException;
+use MediaWiki\Logger\LoggerFactory;
 use MediaWiki\MediaWikiServices;
+use Psr\Log\LoggerInterface;
 use Wikibase\DataModel\Entity\EntityDocument;
 use Wikibase\DataModel\Entity\PropertyId;
 use Wikibase\DataModel\Services\Lookup\EntityLookup;
@@ -66,24 +69,32 @@
        private $statementGuidParser;
 
        /**
+        * @var Config
+        */
+       private $config;
+
+       /**
         * @param EntityLookup $lookup
         * @param ConstraintChecker[] $checkerMap
         * @param ConstraintLookup $constraintRepository
         * @param ConstraintParameterParser $constraintParameterParser
         * @param StatementGuidParser $statementGuidParser
+        * @param Config $config
         */
        public function __construct(
                EntityLookup $lookup,
                array $checkerMap,
                ConstraintLookup $constraintRepository,
                ConstraintParameterParser $constraintParameterParser,
-               StatementGuidParser $statementGuidParser
+               StatementGuidParser $statementGuidParser,
+               Config $config
        ) {
                $this->entityLookup = $lookup;
                $this->checkerMap = $checkerMap;
                $this->constraintLookup = $constraintRepository;
                $this->constraintParameterParser = $constraintParameterParser;
                $this->statementGuidParser = $statementGuidParser;
+               $this->config = $config;
        }
 
        /**
@@ -336,9 +347,10 @@
                                        wfMessage( 
'wbqc-violation-message-sparql-error' )->escaped()
                                );
                        }
+                       $endTime = microtime( true );
                        $statsd->timing(
                                'wikibase.quality.constraints.check.timing.' . 
$constraint->getConstraintTypeItemId(),
-                               ( microtime( true ) - $startTime ) * 1000
+                               ( $endTime - $startTime ) * 1000
                        );
 
                        try {
@@ -370,6 +382,18 @@
                                $result->getParameters['constraint_status'] = 
$constraintStatus;
                        }
 
+                       $this->logLongConstraintCheck(
+                               [
+                                       'info' => $this->config->get( 
'WBQualityConstraintsCheckDurationInfoSeconds' ),
+                                       'warning' => $this->config->get( 
'WBQualityConstraintsCheckDurationWarningSeconds' ),
+                               ],
+                               $statement, $constraint, $entity,
+                               $result,
+                               get_class( $checker ), $endTime - $startTime,
+                               LoggerFactory::getInstance( 
'WikibaseQualityConstraints' ),
+                               __METHOD__
+                       );
+
                        return $result;
                } else {
                        return new CheckResult( $entity->getId(), $statement, 
$constraint, [], CheckResult::STATUS_TODO, null );
@@ -377,6 +401,66 @@
        }
 
        /**
+        * Log a constraint check that took longer than a certain time.
+        * Multiple limits corresponding to different log levels can be 
specified;
+        * checks that exceed a higher limit are logged at a more severe level.
+        *
+        * This method is only public for unit tests and should not be called 
externally.
+        *
+        * @param float[] $limits Associative array from log level to limit in 
seconds.
+        * @param Statement $statement
+        * @param Constraint $constraint
+        * @param EntityDocument $entity
+        * @param CheckResult $result
+        * @param string $constraintCheckerClass
+        * @param float $durationSeconds
+        * @param LoggerInterface $logger
+        * @param string $method Use __METHOD__.
+        */
+       public function logLongConstraintCheck(
+               array $limits,
+               Statement $statement, Constraint $constraint, EntityDocument 
$entity,
+               CheckResult $result,
+               $constraintCheckerClass, $durationSeconds,
+               LoggerInterface $logger,
+               $method
+       ) {
+               foreach ( $limits as $level => $limit ) {
+                       if ( isset( $limit ) && $durationSeconds > $limit ) {
+                               if ( !isset( $limitSeconds ) || $limit > 
$limitSeconds ) {
+                                       $limitSeconds = $limit;
+                                       $logLevel = $level;
+                               }
+                       }
+               }
+
+               if ( isset( $limitSeconds ) ) {
+                       $constraintCheckerShortClassName = substr( strrchr( 
$constraintCheckerClass, '\\' ), 1 );
+
+                       $logger->log(
+                               $logLevel,
+                               'Constraint check with 
{constraintCheckerShortClassName} ' .
+                               'took longer than {limitSeconds} second(s) ' .
+                               '(duration: {durationSeconds} seconds).',
+                               [
+                                       'method' => $method,
+                                       'durationSeconds' => $durationSeconds,
+                                       'limitSeconds' => $limitSeconds,
+                                       'constraintId' => 
$constraint->getConstraintId(),
+                                       'constraintTypeItemId' => 
$constraint->getConstraintTypeItemId(),
+                                       'constraintCheckerClass' => 
$constraintCheckerClass,
+                                       'constraintCheckerShortClassName' => 
$constraintCheckerShortClassName,
+                                       'entityId' => 
$entity->getId()->getSerialization(),
+                                       'statementGuid' => 
$statement->getGuid(),
+                                       'resultStatus' => $result->getStatus(),
+                                       'resultParameters' => 
$result->getParameters(),
+                                       'resultMessage' => 
$result->getMessage(),
+                               ]
+                       );
+               }
+       }
+
+       /**
         * @param CheckResult[] $result
         *
         * @return CheckResult[]
diff --git a/includes/ConstraintReportFactory.php 
b/includes/ConstraintReportFactory.php
index be61867..961a1ed 100644
--- a/includes/ConstraintReportFactory.php
+++ b/includes/ConstraintReportFactory.php
@@ -183,7 +183,8 @@
                                $this->getConstraintCheckerMap(),
                                new CachingConstraintLookup( 
$this->getConstraintRepository() ),
                                $this->constraintParameterParser,
-                               $this->statementGuidParser
+                               $this->statementGuidParser,
+                               $this->config
                        );
                }
 
diff --git a/tests/phpunit/Api/CheckConstraintsTest.php 
b/tests/phpunit/Api/CheckConstraintsTest.php
index 5d1e399..2fe1d83 100644
--- a/tests/phpunit/Api/CheckConstraintsTest.php
+++ b/tests/phpunit/Api/CheckConstraintsTest.php
@@ -103,6 +103,8 @@
                                'WBQualityConstraintsPropertyConstraintId' => 
'P1',
                                'WBQualityConstraintsExceptionToConstraintId' 
=> 'P2',
                                'WBQualityConstraintsConstraintStatusId' => 
'P3',
+                               'WBQualityConstraintsCheckDurationInfoSeconds' 
=> 1.0,
+                               
'WBQualityConstraintsCheckDurationWarningSeconds' => 10.0,
                        ] );
                        $entityIdParser = new ItemIdParser();
                        $constraintParameterRenderer = new 
ConstraintParameterRenderer( $entityIdFormatter, $valueFormatter );
@@ -116,7 +118,8 @@
                                self::$checkerMap,
                                new InMemoryConstraintLookup( 
self::$constraintLookupContents ),
                                $constraintParameterParser,
-                               $repo->getStatementGuidParser()
+                               $repo->getStatementGuidParser(),
+                               $config
                        );
 
                        return new CheckConstraints(
diff --git a/tests/phpunit/DelegatingConstraintCheckerTest.php 
b/tests/phpunit/DelegatingConstraintCheckerTest.php
index fa38054..3743f11 100644
--- a/tests/phpunit/DelegatingConstraintCheckerTest.php
+++ b/tests/phpunit/DelegatingConstraintCheckerTest.php
@@ -2,6 +2,7 @@
 
 namespace WikibaseQuality\ConstraintReport\Test\ConstraintChecker;
 
+use Psr\Log\LoggerInterface;
 use Wikibase\DataModel\Entity\DispatchingEntityIdParser;
 use Wikibase\DataModel\Entity\ItemId;
 use Wikibase\DataModel\Entity\PropertyId;
@@ -10,7 +11,9 @@
 use Wikibase\Rdf\RdfVocabulary;
 use Wikibase\Repo\Tests\NewItem;
 use Wikibase\Repo\Tests\NewStatement;
+use WikibaseQuality\ConstraintReport\Constraint;
 use 
WikibaseQuality\ConstraintReport\ConstraintCheck\DelegatingConstraintChecker;
+use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult;
 use WikibaseQuality\ConstraintReport\ConstraintReportFactory;
 use WikibaseQuality\ConstraintReport\Tests\ConstraintParameters;
 use WikibaseQuality\ConstraintReport\Tests\TitleParserMock;
@@ -396,6 +399,94 @@
        }
 
        /**
+        * @dataProvider logLongConstraintProvider
+        */
+       public function testLogLongConstraintCheck( $durationSeconds, 
$expectedLevel, $expectedLimit ) {
+               $statement = NewStatement::noValueFor( 'P1' )->build();
+               $constraint = new Constraint( 'test constraint id', new 
PropertyId( 'P1' ), 'Q100', [] );
+               $entity = NewItem::withId( 'Q1' )->build();
+               $checkResult = new CheckResult(
+                       $entity->getId(),
+                       $statement,
+                       $constraint,
+                       [ 'test' => 'params' ],
+                       CheckResult::STATUS_VIOLATION,
+                       'test message'
+               );
+
+               $logger = $this->getMock( LoggerInterface::class );
+               $logger->expects( $expectedLevel !== null ? $this->once() : 
$this->never() )
+                       ->method( 'log' )
+                       ->with(
+                               $this->identicalTo( $expectedLevel ),
+                               $this->identicalTo(
+                                       'Constraint check with 
{constraintCheckerShortClassName} ' .
+                                       'took longer than {limitSeconds} 
second(s) ' .
+                                       '(duration: {durationSeconds} seconds).'
+                               ),
+                               $this->equalTo(
+                                       [
+                                               'method' => __METHOD__,
+                                               'durationSeconds' => 
$durationSeconds,
+                                               'limitSeconds' => 
$expectedLimit,
+                                               'constraintId' => 'test 
constraint id',
+                                               'constraintTypeItemId' => 
'Q100',
+                                               'constraintCheckerClass' => 
'\Test\Namespace\TestChecker',
+                                               
'constraintCheckerShortClassName' => 'TestChecker',
+                                               'entityId' => 'Q1',
+                                               'statementGuid' => 
$statement->getGuid(),
+                                               'resultStatus' => 
CheckResult::STATUS_VIOLATION,
+                                               'resultParameters' => [ 'test' 
=> 'params' ],
+                                               'resultMessage' => 'test 
message',
+                                       ]
+                               )
+                       );
+
+               $this->constraintChecker->logLongConstraintCheck(
+                       [ 'info' => 1.0, 'warning' => 10.0 ],
+                       $statement, $constraint, $entity,
+                       $checkResult,
+                       '\Test\Namespace\TestChecker', $durationSeconds,
+                       $logger,
+                       __METHOD__
+               );
+       }
+
+       public function logLongConstraintProvider() {
+               return [
+                       'short constraint check, nothing to log' => [ 0.5, 
null, null ],
+                       'long but not extremely long constraint check, log as 
info' => [ 5.0, 'info', 1.0 ],
+                       'extremely long constraint check, log as warning' => [ 
50.0, 'warning', 10.0 ],
+               ];
+       }
+
+       public function testLogLongConstraintCheckDisabled() {
+               $statement = NewStatement::noValueFor( 'P1' )->build();
+               $constraint = new Constraint( 'test constraint id', new 
PropertyId( 'P1' ), 'Q100', [] );
+               $entity = NewItem::withId( 'Q1' )->build();
+               $checkResult = new CheckResult(
+                       $entity->getId(),
+                       $statement,
+                       $constraint,
+                       [ 'test' => 'params' ],
+                       CheckResult::STATUS_VIOLATION,
+                       'test message'
+               );
+
+               $logger = $this->getMock( LoggerInterface::class );
+               $logger->expects( $this->never() )->method( 'log' );
+
+               $this->constraintChecker->logLongConstraintCheck(
+                       [ 'info' => null, 'warning' => null ],
+                       $statement, $constraint, $entity,
+                       $checkResult,
+                       '\Test\Namespace\TestChecker', 5.0,
+                       $logger,
+                       'testMethod'
+               );
+       }
+
+       /**
         * @return JsonFileEntityLookup
         */
        private function createEntityLookup() {

-- 
To view, visit https://gerrit.wikimedia.org/r/367401
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Icec9925453c1bc75a5be9ee033b86e44cf6790a9
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/WikibaseQualityConstraints
Gerrit-Branch: master
Gerrit-Owner: Lucas Werkmeister (WMDE) <lucas.werkmeis...@wikimedia.de>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to