Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Count and limit expensive #ask/#show functions, refs 2469 #2476

Merged
merged 1 commit into from
Jun 10, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions DefaultSettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,31 @@
'smwgQMaxDepth' => 4, // Maximal property depth of queries, e.g. [[rel::<q>[[rel2::Test]]</q>]] has depth 2
##

###
# Expensive threshold
#
# The threshold defined in seconds denotes the ceiling as to when a #ask or
# #show call is classified as expensive and will count towards the
# $smwgQExpensiveExecutionLimit setting.
#
# @since 3.0
# @default 10
##
'smwgQExpensiveThreshold' => 10,
##

###
# Limit of expensive #ask/#show functions
#
# The limit will count all classified #ask/#show parser functions and restricts
# further use on pages that exceed that limit.
#
# @since 3.0
# @default false (== no limit)
##
'smwgQExpensiveExecutionLimit' => false,
##

###
# The below setting defines which query features should be available by
# default.
Expand Down
3 changes: 2 additions & 1 deletion i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -549,5 +549,6 @@
"smw-format-datatable-previous": "Previous",
"smw-format-datatable-sortascending": ": activate to sort column ascending",
"smw-format-datatable-sortdescending": ": activate to sort column descending",
"smw-category-invalid-redirect-target": "Category \"$1\" contains an invalid redirect target to a non-category namespace."
"smw-category-invalid-redirect-target": "Category \"$1\" contains an invalid redirect target to a non-category namespace.",
"smw-parser-function-expensive-execution-limit": "The parser function has reached the limit for expensive executions (see [https://www.semantic-mediawiki.org/wiki/Help:$smwgQExpensiveExecutionLimit $smwgQExpensiveExecutionLimit])."
}
2 changes: 2 additions & 0 deletions includes/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ public static function newFromGlobals() {
'smwgQConceptMaxDepth' => $GLOBALS['smwgQConceptMaxDepth'],
'smwgQConceptFeatures' => $GLOBALS['smwgQConceptFeatures'],
'smwgQConceptCacheLifetime' => $GLOBALS['smwgQConceptCacheLifetime'],
'smwgQExpensiveThreshold' => $GLOBALS['smwgQExpensiveThreshold'],
'smwgQExpensiveExecutionLimit' => $GLOBALS['smwgQExpensiveExecutionLimit'],
'smwgQuerySources' => $GLOBALS['smwgQuerySources'],
'smwgQTemporaryTablesAutoCommitMode' => $GLOBALS['smwgQTemporaryTablesAutoCommitMode'],
'smwgResultFormats' => $GLOBALS['smwgResultFormats'],
Expand Down
43 changes: 19 additions & 24 deletions src/ParserFunctionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use SMW\ParserFunctions\SetParserFunction;
use SMW\ParserFunctions\ConceptParserFunction;
use SMW\ParserFunctions\DeclareParserFunction;
use SMW\ParserFunctions\ExpensiveFuncExecutionWatcher;
use SMW\Utils\CircularReferenceGuard;
use Parser;

Expand Down Expand Up @@ -73,10 +74,12 @@ public function getRecurringEventsParser() {
*/
public function newAskParserFunction( Parser $parser ) {

$applicationFactory = ApplicationFactory::getInstance();

$circularReferenceGuard = new CircularReferenceGuard( 'ask-parser' );
$circularReferenceGuard->setMaxRecursionDepth( 2 );

$parserData = ApplicationFactory::getInstance()->newParserData(
$parserData = $applicationFactory->newParserData(
$parser->getTitle(),
$parser->getOutput()
);
Expand All @@ -92,10 +95,23 @@ public function newAskParserFunction( Parser $parser ) {
$parser->getTargetLanguage()
);

$expensiveFuncExecutionWatcher = new ExpensiveFuncExecutionWatcher(
$parserData
);

$expensiveFuncExecutionWatcher->setExpensiveThreshold(
$applicationFactory->getSettings()->get( 'smwgQExpensiveThreshold' )
);

$expensiveFuncExecutionWatcher->setExpensiveExecutionLimit(
$applicationFactory->getSettings()->get( 'smwgQExpensiveExecutionLimit' )
);

$askParserFunction = new AskParserFunction(
$parserData,
$messageFormatter,
$circularReferenceGuard
$circularReferenceGuard,
$expensiveFuncExecutionWatcher
);

return $askParserFunction;
Expand All @@ -110,29 +126,8 @@ public function newAskParserFunction( Parser $parser ) {
*/
public function newShowParserFunction( Parser $parser ) {

$circularReferenceGuard = new CircularReferenceGuard( 'show-parser' );
$circularReferenceGuard->setMaxRecursionDepth( 2 );

$parserData = ApplicationFactory::getInstance()->newParserData(
$parser->getTitle(),
$parser->getOutput()
);

if ( isset( $parser->getOptions()->smwAskNoDependencyTracking ) ) {
$parserData->setOption( $parserData::NO_QUERY_DEPENDENCY_TRACE, $parser->getOptions()->smwAskNoDependencyTracking );
}

// Avoid possible actions during for example stashedit etc.
$parserData->setOption( 'request.action', $GLOBALS['wgRequest']->getVal( 'action' ) );

$messageFormatter = new MessageFormatter(
$parser->getTargetLanguage()
);

$showParserFunction = new ShowParserFunction(
$parserData,
$messageFormatter,
$circularReferenceGuard
$this->newAskParserFunction( $parser )
);

return $showParserFunction;
Expand Down
30 changes: 27 additions & 3 deletions src/ParserFunctions/AskParserFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ class AskParserFunction {
*/
private $circularReferenceGuard;

/**
* @var ExpensiveFuncExecutionWatcher
*/
private $expensiveFuncExecutionWatcher;

/**
* @var boolean
*/
Expand All @@ -78,11 +83,13 @@ class AskParserFunction {
* @param ParserData $parserData
* @param MessageFormatter $messageFormatter
* @param CircularReferenceGuard $circularReferenceGuard
* @param ExpensiveFuncExecutionWatcher $expensiveFuncExecutionWatcher
*/
public function __construct( ParserData $parserData, MessageFormatter $messageFormatter, CircularReferenceGuard $circularReferenceGuard ) {
public function __construct( ParserData $parserData, MessageFormatter $messageFormatter, CircularReferenceGuard $circularReferenceGuard, ExpensiveFuncExecutionWatcher $expensiveFuncExecutionWatcher ) {
$this->parserData = $parserData;
$this->messageFormatter = $messageFormatter;
$this->circularReferenceGuard = $circularReferenceGuard;
$this->expensiveFuncExecutionWatcher = $expensiveFuncExecutionWatcher;
}

/**
Expand Down Expand Up @@ -129,8 +136,8 @@ public function parse( array $functionParams ) {

// Do we still need this?
// Reference found in SRF_Exhibit.php, SRF_Ploticus.php, SRF_Timeline.php, SRF_JitGraph.php
global $smwgIQRunningNumber;
$smwgIQRunningNumber++;
$GLOBALS['smwgIQRunningNumber']++;
$result = '';

$this->applicationFactory = ApplicationFactory::getInstance();

Expand Down Expand Up @@ -221,6 +228,10 @@ private function doFetchResultsFromFunctionParameters( array $functionParams ) {
$contextPage
);

if ( ( $result = $this->hasReachedExpensiveExecutionLimit( $query ) ) !== false ) {
return $result;
}

$query->setOption( Query::PROC_CONTEXT, 'AskParserFunction' );
$query->setOption( Query::NO_DEPENDENCY_TRACE, $this->noTrace );
$query->setOption( 'request.action', $this->parserData->getOption( 'request.action' ) );
Expand Down Expand Up @@ -254,6 +265,7 @@ private function doFetchResultsFromFunctionParameters( array $functionParams ) {
}

$this->circularReferenceGuard->unmark( $queryHash );
$this->expensiveFuncExecutionWatcher->incrementExpensiveCount( $query );

// In case of an query error add a marker to the subject for discoverability
// of a failed query, don't bail-out as we can have results and errors
Expand All @@ -268,6 +280,18 @@ private function doFetchResultsFromFunctionParameters( array $functionParams ) {
return $result;
}

private function hasReachedExpensiveExecutionLimit( $query ) {

if ( $this->expensiveFuncExecutionWatcher->hasReachedExpensiveLimit( $query ) === false ) {
return false;
}

// Adding to error in order to be discoverable
$this->addProcessingError( array( 'smw-parser-function-expensive-execution-limit' ) );

return $this->messageFormatter->addFromKey( 'smw-parser-function-expensive-execution-limit' )->getHtml();
}

private function addQueryProfile( $query, $format ) {

$settings = $this->applicationFactory->getSettings();
Expand Down
113 changes: 113 additions & 0 deletions src/ParserFunctions/ExpensiveFuncExecutionWatcher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php

namespace SMW\ParserFunctions;

use SMW\ParserData;
use SMWQuery as Query;

/**
* @private
*
* @license GNU GPL v2+
* @since 3.0
*
* @author mwjames
*/
class ExpensiveFuncExecutionWatcher {

/**
* Idenitifer
*/
const EXPENSIVE_COUNTER = 'smw-expensiveparsercount';

/**
* @var ParserData
*/
private $parserData;

/**
* @var integer
*/
private $expensiveThreshold = 10;

/**
* @var integer|boolean
*/
private $expensiveExecutionLimit = false;

/**
* @since 3.0
*
* @param ParserData $parserData
*/
public function __construct( ParserData $parserData ) {
$this->parserData = $parserData;
}

/**
* @since 3.0
*
* @param integer $expensiveThreshold
*/
public function setExpensiveThreshold( $expensiveThreshold ) {
$this->expensiveThreshold = $expensiveThreshold;
}

/**
* @since 3.0
*
* @param integer|boolean $expensiveExecutionLimit
*/
public function setExpensiveExecutionLimit( $expensiveExecutionLimit ) {
$this->expensiveExecutionLimit = $expensiveExecutionLimit;
}

/**
* @since 3.0
*
* @param Query $query
*
* @return boolean
*/
public function hasReachedExpensiveLimit( Query $query ) {

if ( $this->expensiveExecutionLimit === false ) {
return false;
}

if ( $query->getLimit() == 0 ) {
return false;
}

if ( $this->parserData->getOutput()->getExtensionData( self::EXPENSIVE_COUNTER ) < $this->expensiveExecutionLimit ) {
return false;
}

return true;
}

/**
* @since 3.0
*
* @param Query $query
*
* @return boolean
*/
public function incrementExpensiveCount( Query $query ) {

if ( $this->expensiveExecutionLimit === false || $query->getLimit() == 0 || $query->getOption( Query::PROC_QUERY_TIME ) < $this->expensiveThreshold ) {
return;
}

$output = $this->parserData->getOutput();
$expensiveCount = $output->getExtensionData( self::EXPENSIVE_COUNTER );

if ( !is_int( $expensiveCount ) ) {
$expensiveCount = 0;
}

$expensiveCount++;
$output->setExtensionData( self::EXPENSIVE_COUNTER, $expensiveCount );
}

}
42 changes: 8 additions & 34 deletions src/ParserFunctions/ShowParserFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

namespace SMW\ParserFunctions;

use SMW\ParserData;
use SMW\MessageFormatter;
use SMW\Utils\CircularReferenceGuard;

/**
* Class that provides the {{#show}} parser function
*
Expand All @@ -17,31 +13,17 @@
class ShowParserFunction {

/**
* @var ParserData
*/
private $parserData;

/**
* @var MessageFormatter
*/
private $messageFormatter;

/**
* @var CircularReferenceGuard
* @var AskParserFunction
*/
private $circularReferenceGuard;
private $askParserFunction;

/**
* @since 1.9
*
* @param ParserData $parserData
* @param MessageFormatter $messageFormatter
* @param CircularReferenceGuard $circularReferenceGuard
* @param AskParserFunction $askParserFunction
*/
public function __construct( ParserData $parserData, MessageFormatter $messageFormatter, CircularReferenceGuard $circularReferenceGuard ) {
$this->parserData = $parserData;
$this->messageFormatter = $messageFormatter;
$this->circularReferenceGuard = $circularReferenceGuard;
public function __construct( AskParserFunction $askParserFunction ) {
$this->askParserFunction = $askParserFunction;
}

/**
Expand All @@ -59,16 +41,8 @@ public function __construct( ParserData $parserData, MessageFormatter $messageFo
* @return string|null
*/
public function parse( array $rawParams ) {

$instance = new AskParserFunction(
$this->parserData,
$this->messageFormatter,
$this->circularReferenceGuard
);

$instance->setShowMode( true );

return $instance->parse( $rawParams );
$this->askParserFunction->setShowMode( true );
return $this->askParserFunction->parse( $rawParams );
}

/**
Expand All @@ -80,7 +54,7 @@ public function parse( array $rawParams ) {
* @return string|null
*/
public function isQueryDisabled() {
return $this->messageFormatter->addFromKey( 'smw_iq_disabled' )->getHtml();
return $this->askParserFunction->isQueryDisabled();
}

}
Loading