MediaWiki REL1_34
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
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();
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}
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
$wgLang
Definition Setup.php:880
$line
Definition cdb.php:59
if( $line===false) $args
Definition cdb.php:64
static link( $target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition Linker.php:85
MediaWiki exception.
addModules( $modules)
setLimitReportData( $key, $value)
Sets parser limit report data for a key.
const CPU_SECONDS
Definition Base.php:32
const MEM_PEAK_BYTES
Definition Base.php:33
formatHtmlLogs( $logs, $localize)
Format the logged data for HTML output.
load()
Initialise the interpreter and the base environment.
getLuaLibDir()
Return the base path for Lua modules.
getLogBuffer()
Get data logged by modules.
__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
getPerformanceCharacteristics()
Get performance characteristics of the Lua engine/interpreter.
Definition Engine.php:12
Scribunto_LuaSandboxInterpreter $interpreter
Definition Engine.php:10
newInterpreter()
Create a new interpreter object.
Definition Engine.php:210
getResourceUsage( $resource)
Get CPU and memory usage information, if the script engine provides it.
Definition Engine.php:40
reportLimitData(ParserOutput $output)
Add limit report data to a ParserOutput object.
Definition Engine.php:113
getSoftwareInfo(array &$software)
Get software information for Special:Version.
Definition Engine.php:18
formatLimitData( $key, &$value, &$report, $isHTML, $localize)
Format limit report data.
Definition Engine.php:123
registerLibrary( $name, array $functions)
Register a library of functions.
Definition Engine.php:298
static checkLuaSandboxVersion()
Check that php-luasandbox is available and of a recent-enough version.
Definition Engine.php:240
wrapPhpFunction( $callable)
Wrap a PHP callable as a Lua function, which can be passed back into Lua.
Definition Engine.php:330
isLuaFunction( $object)
Test whether an object is a Lua function.
Definition Engine.php:334
loadString( $text, $chunkName)
Definition Engine.php:290
callFunction( $func,... $args)
Call a Lua function.
Definition Engine.php:312
pauseUsageTimer()
Pause CPU usage and limits.
Definition Engine.php:362
convertSandboxError(LuaSandboxError $e)
Definition Engine.php:270
Scribunto_LuaEngine $engine
Definition Engine.php:219
__construct( $engine, array $options)
Definition Engine.php:254
unpauseUsageTimer()
Unpause CPU usage and limits.
Definition Engine.php:366
hasContentModel( $id)
Convenience method for checking a title's content model name.
Definition Title.php:1082
$lines
Definition router.php:61
if(!isset( $args[0])) $lang