MediaWiki  1.34.0
Engine.php
Go to the documentation of this file.
1 <?php
2 
4  public $options, $loaded = false;
5  protected $lineCache = [];
6 
10  protected $interpreter;
11 
12  public function getPerformanceCharacteristics() {
13  return [
14  'phpCallsRequireSerialization' => false,
15  ];
16  }
17 
18  public function getSoftwareInfo( array &$software ) {
19  try {
22  // They shouldn't be using this engine if the extension isn't
23  // loaded. But in case they do for some reason, let's not have
24  // Special:Version fatal.
25  return;
27  // Same for if the extension is too old.
28  return;
29  }
30 
31  $versions = LuaSandbox::getVersionInfo();
32  $software['[https://www.mediawiki.org/wiki/LuaSandbox LuaSandbox]'] =
33  $versions['LuaSandbox'];
34  $software['[http://www.lua.org/ Lua]'] = str_replace( 'Lua ', '', $versions['Lua'] );
35  if ( isset( $versions['LuaJIT'] ) ) {
36  $software['[http://luajit.org/ LuaJIT]'] = str_replace( 'LuaJIT ', '', $versions['LuaJIT'] );
37  }
38  }
39 
40  public function getResourceUsage( $resource ) {
41  $this->load();
42  switch ( $resource ) {
44  return $this->interpreter->getPeakMemoryUsage();
45  case self::CPU_SECONDS:
46  return $this->interpreter->getCPUUsage();
47  default:
48  return false;
49  }
50  }
51 
52  private function getLimitReportData() {
53  $ret = [];
54  $this->load();
55 
56  $t = $this->interpreter->getCPUUsage();
57  $ret['scribunto-limitreport-timeusage'] = [
58  sprintf( "%.3f", $t ),
59  sprintf( "%.3f", $this->options['cpuLimit'] )
60  ];
61  $ret['scribunto-limitreport-memusage'] = [
62  $this->interpreter->getPeakMemoryUsage(),
63  $this->options['memoryLimit'],
64  ];
65 
66  $logs = $this->getLogBuffer();
67  if ( $logs !== '' ) {
68  $ret['scribunto-limitreport-logs'] = $logs;
69  }
70 
71  if ( $t < 1.0 ) {
72  return $ret;
73  }
74 
75  $percentProfile = $this->interpreter->getProfilerFunctionReport(
77  );
78  if ( !count( $percentProfile ) ) {
79  return $ret;
80  }
81  $timeProfile = $this->interpreter->getProfilerFunctionReport(
83  );
84 
85  $lines = [];
86  $cumulativePercent = 0;
87  $num = $otherTime = $otherPercent = 0;
88  foreach ( $percentProfile as $name => $percent ) {
89  $time = $timeProfile[$name] * 1000;
90  $num++;
91  if ( $cumulativePercent <= 99 && $num <= 10 ) {
92  // Map some regularly appearing internal names
93  if ( preg_match( '/^<mw.lua:(\d+)>$/', $name, $m ) ) {
94  $line = $this->getMwLuaLine( $m[1] );
95  if ( preg_match( '/^\s*(local\s+)?function ([a-zA-Z0-9_.]*)/', $line, $m ) ) {
96  $name = $m[2] . ' ' . $name;
97  }
98  }
99  $lines[] = [ $name, sprintf( '%.0f', $time ), sprintf( '%.1f', $percent ) ];
100  } else {
101  $otherTime += $time;
102  $otherPercent += $percent;
103  }
104  $cumulativePercent += $percent;
105  }
106  if ( $otherTime ) {
107  $lines[] = [ '[others]', sprintf( '%.0f', $otherTime ), sprintf( '%.1f', $otherPercent ) ];
108  }
109  $ret['scribunto-limitreport-profile'] = $lines;
110  return $ret;
111  }
112 
113  public function reportLimitData( ParserOutput $output ) {
114  $data = $this->getLimitReportData();
115  foreach ( $data as $k => $v ) {
116  $output->setLimitReportData( $k, $v );
117  }
118  if ( isset( $data['scribunto-limitreport-logs'] ) ) {
119  $output->addModules( 'ext.scribunto.logs' );
120  }
121  }
122 
123  public function formatLimitData( $key, &$value, &$report, $isHTML, $localize ) {
124  global $wgLang;
125  $lang = $localize ? $wgLang : Language::factory( 'en' );
126  switch ( $key ) {
127  case 'scribunto-limitreport-logs':
128  if ( $isHTML ) {
129  $report .= $this->formatHtmlLogs( $value, $localize );
130  }
131  return false;
132 
133  case 'scribunto-limitreport-memusage':
134  $value = array_map( [ $lang, 'formatSize' ], $value );
135  break;
136  }
137 
138  if ( $key !== 'scribunto-limitreport-profile' ) {
139  return true;
140  }
141 
142  $keyMsg = wfMessage( 'scribunto-limitreport-profile' );
143  $msMsg = wfMessage( 'scribunto-limitreport-profile-ms' );
144  $percentMsg = wfMessage( 'scribunto-limitreport-profile-percent' );
145  if ( !$localize ) {
146  $keyMsg->inLanguage( 'en' )->useDatabase( false );
147  $msMsg->inLanguage( 'en' )->useDatabase( false );
148  $percentMsg->inLanguage( 'en' )->useDatabase( false );
149  }
150 
151  // To avoid having to do actual work in Message::fetchMessage for each
152  // line in the loops below, call ->exists() here to populate ->message.
153  $msMsg->exists();
154  $percentMsg->exists();
155 
156  if ( $isHTML ) {
157  $report .= Html::openElement( 'tr' ) .
158  Html::rawElement( 'th', [ 'colspan' => 2 ], $keyMsg->parse() ) .
159  Html::closeElement( 'tr' ) .
160  Html::openElement( 'tr' ) .
161  Html::openElement( 'td', [ 'colspan' => 2 ] ) .
162  Html::openElement( 'table' );
163  foreach ( $value as $line ) {
164  $name = $line[0];
165  $location = '';
166  if ( preg_match( '/^(.*?) *<([^<>]+):(\d+)>$/', $name, $m ) ) {
167  $name = $m[1];
168  $title = Title::newFromText( $m[2] );
169  if ( $title && $title->hasContentModel( CONTENT_MODEL_SCRIBUNTO ) ) {
170  $location = '&lt;' . Linker::link( $title ) . ":{$m[3]}&gt;";
171  } else {
172  $location = htmlspecialchars( "<{$m[2]}:{$m[3]}>" );
173  }
174  }
175  $ms = clone $msMsg;
176  $ms->params( $line[1] );
177  $pct = clone $percentMsg;
178  $pct->params( $line[2] );
179  $report .= Html::openElement( 'tr' ) .
180  Html::element( 'td', null, $name ) .
181  Html::rawElement( 'td', null, $location ) .
182  Html::rawElement( 'td', [ 'align' => 'right' ], $ms->parse() ) .
183  Html::rawElement( 'td', [ 'align' => 'right' ], $pct->parse() ) .
184  Html::closeElement( 'tr' );
185  }
186  $report .= Html::closeElement( 'table' ) .
187  Html::closeElement( 'td' ) .
188  Html::closeElement( 'tr' );
189  } else {
190  $report .= $keyMsg->text() . ":\n";
191  foreach ( $value as $line ) {
192  $ms = clone $msMsg;
193  $ms->params( $line[1] );
194  $pct = clone $percentMsg;
195  $pct->params( $line[2] );
196  $report .= sprintf( " %-59s %11s %11s\n", $line[0], $ms->text(), $pct->text() );
197  }
198  }
199 
200  return false;
201  }
202 
203  protected function getMwLuaLine( $lineNum ) {
204  if ( !isset( $this->lineCache['mw.lua'] ) ) {
205  $this->lineCache['mw.lua'] = file( $this->getLuaLibDir() . '/mw.lua' );
206  }
207  return $this->lineCache['mw.lua'][$lineNum - 1];
208  }
209 
210  protected function newInterpreter() {
211  return new Scribunto_LuaSandboxInterpreter( $this, $this->options );
212  }
213 }
214 
219  public $engine;
220 
224  public $sandbox;
225 
230 
231  const SAMPLES = 0;
232  const SECONDS = 1;
233  const PERCENT = 2;
234 
240  public static function checkLuaSandboxVersion() {
241  if ( !extension_loaded( 'luasandbox' ) ) {
243  'The luasandbox extension is not present, this engine cannot be used.' );
244  }
245 
246  if ( !is_callable( 'LuaSandbox::getVersionInfo' ) ) {
248  'The luasandbox extension is too old (version 1.6+ is required), ' .
249  'this engine cannot be used.'
250  );
251  }
252  }
253 
254  public function __construct( $engine, array $options ) {
256 
257  $this->engine = $engine;
258  $this->sandbox = new LuaSandbox;
259  $this->sandbox->setMemoryLimit( $options['memoryLimit'] );
260  $this->sandbox->setCPULimit( $options['cpuLimit'] );
261  if ( !isset( $options['profilerPeriod'] ) ) {
262  $options['profilerPeriod'] = 0.02;
263  }
264  if ( $options['profilerPeriod'] ) {
265  $this->profilerEnabled = true;
266  $this->sandbox->enableProfiler( $options['profilerPeriod'] );
267  }
268  }
269 
270  protected function convertSandboxError( LuaSandboxError $e ) {
271  $opts = [];
272  if ( isset( $e->luaTrace ) ) {
273  $opts['trace'] = $e->luaTrace;
274  }
275  $message = $e->getMessage();
276  if ( preg_match( '/^(.*?):(\d+): (.*)$/', $message, $m ) ) {
277  $opts['module'] = $m[1];
278  $opts['line'] = $m[2];
279  $message = $m[3];
280  }
281  return $this->engine->newLuaError( $message, $opts );
282  }
283 
290  public function loadString( $text, $chunkName ) {
291  try {
292  return $this->sandbox->loadString( $text, $chunkName );
293  } catch ( LuaSandboxError $e ) {
294  throw $this->convertSandboxError( $e );
295  }
296  }
297 
298  public function registerLibrary( $name, array $functions ) {
299  $realLibrary = [];
300  foreach ( $functions as $funcName => $callback ) {
301  $realLibrary[$funcName] = [
302  new Scribunto_LuaSandboxCallback( $callback ),
303  $funcName ];
304  }
305  $this->sandbox->registerLibrary( $name, $realLibrary );
306 
307  # TODO: replace this with
308  # $this->sandbox->registerVirtualLibrary(
309  # $name, [ $this, 'callback' ], $functions );
310  }
311 
312  public function callFunction( $func, ...$args ) {
313  try {
314  $ret = $func->call( ...$args );
315  if ( $ret === false ) {
316  // Per the documentation on LuaSandboxFunction::call, a return value
317  // of false means that something went wrong and it's PHP's fault,
318  // so throw a "real" exception.
319  throw new MWException(
320  __METHOD__ . ': LuaSandboxFunction::call returned false' );
321  }
322  return $ret;
323  } catch ( LuaSandboxTimeoutError $e ) {
324  throw $this->engine->newException( 'scribunto-common-timeout' );
325  } catch ( LuaSandboxError $e ) {
326  throw $this->convertSandboxError( $e );
327  }
328  }
329 
330  public function wrapPhpFunction( $callable ) {
331  return $this->sandbox->wrapPhpFunction( $callable );
332  }
333 
334  public function isLuaFunction( $object ) {
335  return $object instanceof LuaSandboxFunction;
336  }
337 
338  public function getPeakMemoryUsage() {
339  return $this->sandbox->getPeakMemoryUsage();
340  }
341 
342  public function getCPUUsage() {
343  return $this->sandbox->getCPUUsage();
344  }
345 
346  public function getProfilerFunctionReport( $units ) {
347  if ( $this->profilerEnabled ) {
348  static $unitsMap;
349  if ( !$unitsMap ) {
350  $unitsMap = [
351  self::SAMPLES => LuaSandbox::SAMPLES,
352  self::SECONDS => LuaSandbox::SECONDS,
353  self::PERCENT => LuaSandbox::PERCENT,
354  ];
355  }
356  return $this->sandbox->getProfilerFunctionReport( $unitsMap[$units] );
357  } else {
358  return [];
359  }
360  }
361 
362  public function pauseUsageTimer() {
363  $this->sandbox->pauseUsageTimer();
364  }
365 
366  public function unpauseUsageTimer() {
367  $this->sandbox->unpauseUsageTimer();
368  }
369 }
370 
372 
376  protected $callback;
377 
378  public function __construct( $callback ) {
379  $this->callback = $callback;
380  }
381 
389  public function __call( $funcName, $args ) {
390  try {
391  return ( $this->callback )( ...$args );
392  } catch ( Scribunto_LuaError $e ) {
393  throw new LuaSandboxRuntimeError( $e->getLuaMessage() );
394  }
395  }
396 }
Scribunto_LuaSandboxInterpreter\getCPUUsage
getCPUUsage()
Definition: Engine.php:342
ScribuntoEngineBase\MEM_PEAK_BYTES
const MEM_PEAK_BYTES
Definition: Base.php:33
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:316
Scribunto_LuaSandboxInterpreter\pauseUsageTimer
pauseUsageTimer()
Pause CPU usage and limits.
Definition: Engine.php:362
ParserOutput
Definition: ParserOutput.php:25
Scribunto_LuaSandboxInterpreter\registerLibrary
registerLibrary( $name, array $functions)
Register a library of functions.
Definition: Engine.php:298
Scribunto_LuaSandboxEngine\$loaded
$loaded
Definition: Engine.php:4
Scribunto_LuaError
Definition: LuaCommon.php:992
Scribunto_LuaSandboxInterpreter\checkLuaSandboxVersion
static checkLuaSandboxVersion()
Check that php-luasandbox is available and of a recent-enough version.
Definition: Engine.php:240
ScribuntoEngineBase\CPU_SECONDS
const CPU_SECONDS
Definition: Base.php:32
Scribunto_LuaEngine\load
load()
Initialise the interpreter and the base environment.
Definition: LuaCommon.php:114
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
Scribunto_LuaSandboxInterpreter\getProfilerFunctionReport
getProfilerFunctionReport( $units)
Definition: Engine.php:346
Scribunto_LuaSandboxEngine\getResourceUsage
getResourceUsage( $resource)
Get CPU and memory usage information, if the script engine provides it.
Definition: Engine.php:40
Scribunto_LuaSandboxInterpreter\$sandbox
LuaSandbox $sandbox
Definition: Engine.php:224
Scribunto_LuaSandboxEngine\formatLimitData
formatLimitData( $key, &$value, &$report, $isHTML, $localize)
Format limit report data.
Definition: Engine.php:123
Scribunto_LuaSandboxInterpreter\PERCENT
const PERCENT
Definition: Engine.php:233
Scribunto_LuaSandboxEngine\getMwLuaLine
getMwLuaLine( $lineNum)
Definition: Engine.php:203
Scribunto_LuaSandboxEngine\$options
$options
Definition: Engine.php:4
Scribunto_LuaEngine\getLuaLibDir
getLuaLibDir()
Return the base path for Lua modules.
Definition: LuaCommon.php:189
Scribunto_LuaSandboxInterpreter\$profilerEnabled
bool $profilerEnabled
Definition: Engine.php:229
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1264
Scribunto_LuaSandboxCallback\__construct
__construct( $callback)
Definition: Engine.php:378
Scribunto_LuaSandboxInterpreter\getPeakMemoryUsage
getPeakMemoryUsage()
Definition: Engine.php:338
Scribunto_LuaSandboxEngine\getLimitReportData
getLimitReportData()
Definition: Engine.php:52
Scribunto_LuaSandboxInterpreter\unpauseUsageTimer
unpauseUsageTimer()
Unpause CPU usage and limits.
Definition: Engine.php:366
Scribunto_LuaSandboxEngine\newInterpreter
newInterpreter()
Create a new interpreter object.
Definition: Engine.php:210
Scribunto_LuaSandboxEngine\getPerformanceCharacteristics
getPerformanceCharacteristics()
Get performance characteristics of the Lua engine/interpreter.
Definition: Engine.php:12
Scribunto_LuaSandboxInterpreter\wrapPhpFunction
wrapPhpFunction( $callable)
Wrap a PHP callable as a Lua function, which can be passed back into Lua.
Definition: Engine.php:330
Scribunto_LuaInterpreterNotFoundError
Definition: LuaInterpreter.php:64
MWException
MediaWiki exception.
Definition: MWException.php:26
Scribunto_LuaSandboxEngine\getSoftwareInfo
getSoftwareInfo(array &$software)
Get software information for Special:Version.
Definition: Engine.php:18
Scribunto_LuaSandboxEngine\reportLimitData
reportLimitData(ParserOutput $output)
Add limit report data to a ParserOutput object.
Definition: Engine.php:113
Scribunto_LuaSandboxEngine\$interpreter
Scribunto_LuaSandboxInterpreter $interpreter
Definition: Engine.php:10
Scribunto_LuaEngine
Definition: LuaCommon.php:6
$wgLang
$wgLang
Definition: Setup.php:881
$t
$t
Definition: make-normalization-table.php:143
$lines
$lines
Definition: router.php:61
Scribunto_LuaSandboxEngine
Definition: Engine.php:3
$output
$output
Definition: SyntaxHighlight.php:335
Scribunto_LuaSandboxInterpreter\$engine
Scribunto_LuaEngine $engine
Definition: Engine.php:219
Scribunto_LuaSandboxInterpreter\convertSandboxError
convertSandboxError(LuaSandboxError $e)
Definition: Engine.php:270
Scribunto_LuaSandboxInterpreter\loadString
loadString( $text, $chunkName)
Definition: Engine.php:290
$line
$line
Definition: cdb.php:59
Scribunto_LuaSandboxInterpreter\__construct
__construct( $engine, array $options)
Definition: Engine.php:254
Scribunto_LuaInterpreterBadVersionError
Definition: LuaInterpreter.php:67
Scribunto_LuaError\getLuaMessage
getLuaMessage()
Definition: LuaCommon.php:1007
Linker\link
static link( $target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition: Linker.php:85
Title\hasContentModel
hasContentModel( $id)
Convenience method for checking a title's content model name.
Definition: Title.php:1082
Scribunto_LuaSandboxInterpreter\callFunction
callFunction( $func,... $args)
Call a Lua function.
Definition: Engine.php:312
Scribunto_LuaSandboxInterpreter\SECONDS
const SECONDS
Definition: Engine.php:232
Scribunto_LuaSandboxInterpreter
Definition: Engine.php:215
Scribunto_LuaSandboxCallback\__call
__call( $funcName, $args)
We use __call with a variable function name so that LuaSandbox will be able to return a meaningful fu...
Definition: Engine.php:389
$args
if( $line===false) $args
Definition: cdb.php:64
Scribunto_LuaInterpreter
Definition: LuaInterpreter.php:3
Scribunto_LuaSandboxCallback\$callback
callable $callback
Definition: Engine.php:376
Scribunto_LuaEngine\getLogBuffer
getLogBuffer()
Get data logged by modules.
Definition: LuaCommon.php:303
Scribunto_LuaSandboxEngine\$lineCache
$lineCache
Definition: Engine.php:5
Scribunto_LuaSandboxCallback
Definition: Engine.php:371
Scribunto_LuaSandboxInterpreter\SAMPLES
const SAMPLES
Definition: Engine.php:231
Language\factory
static factory( $code)
Get a cached or new language object for a given language code.
Definition: Language.php:217
Scribunto_LuaSandboxInterpreter\isLuaFunction
isLuaFunction( $object)
Test whether an object is a Lua function.
Definition: Engine.php:334
Scribunto_LuaEngine\formatHtmlLogs
formatHtmlLogs( $logs, $localize)
Format the logged data for HTML output.
Definition: LuaCommon.php:322
ScribuntoEngineBase\$title
Title $title
Definition: Base.php:38