Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 129 |
|
0.00% |
0 / 9 |
CRAP | |
0.00% |
0 / 1 |
LuaSandboxEngine | |
0.00% |
0 / 129 |
|
0.00% |
0 / 9 |
1406 | |
0.00% |
0 / 1 |
getPerformanceCharacteristics | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getSoftwareInfo | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
20 | |||
getResourceUsage | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
getLimitReportData | |
0.00% |
0 / 44 |
|
0.00% |
0 / 1 |
110 | |||
fixTruncation | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
reportLimitData | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
formatLimitData | |
0.00% |
0 / 54 |
|
0.00% |
0 / 1 |
132 | |||
getMwLuaLine | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
newInterpreter | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\Scribunto\Engines\LuaSandbox; |
4 | |
5 | use LuaSandbox; |
6 | use MediaWiki\Extension\Scribunto\Engines\LuaCommon\LuaEngine; |
7 | use MediaWiki\Extension\Scribunto\Engines\LuaCommon\LuaInterpreterBadVersionError; |
8 | use MediaWiki\Extension\Scribunto\Engines\LuaCommon\LuaInterpreterNotFoundError; |
9 | use MediaWiki\Html\Html; |
10 | use MediaWiki\MediaWikiServices; |
11 | use MediaWiki\Parser\ParserOutput; |
12 | use MediaWiki\Title\Title; |
13 | |
14 | class LuaSandboxEngine extends LuaEngine { |
15 | /** @var array */ |
16 | public $options; |
17 | /** @var bool */ |
18 | public $loaded = false; |
19 | /** @var array */ |
20 | protected $lineCache = []; |
21 | |
22 | /** |
23 | * @var LuaSandboxInterpreter |
24 | */ |
25 | protected $interpreter; |
26 | |
27 | /** @inheritDoc */ |
28 | public function getPerformanceCharacteristics() { |
29 | return [ |
30 | 'phpCallsRequireSerialization' => false, |
31 | ]; |
32 | } |
33 | |
34 | /** @inheritDoc */ |
35 | public function getSoftwareInfo( array &$software ) { |
36 | try { |
37 | LuaSandboxInterpreter::checkLuaSandboxVersion(); |
38 | } catch ( LuaInterpreterNotFoundError $e ) { |
39 | // They shouldn't be using this engine if the extension isn't |
40 | // loaded. But in case they do for some reason, let's not have |
41 | // Special:Version fatal. |
42 | return; |
43 | } catch ( LuaInterpreterBadVersionError $e ) { |
44 | // @phan-suppress-previous-line PhanPluginDuplicateCatchStatementBody |
45 | // Same for if the extension is too old. |
46 | return; |
47 | } |
48 | |
49 | $versions = LuaSandbox::getVersionInfo(); |
50 | $software['[https://www.mediawiki.org/wiki/LuaSandbox LuaSandbox]'] = |
51 | $versions['LuaSandbox']; |
52 | $software['[http://www.lua.org/ Lua]'] = str_replace( 'Lua ', '', $versions['Lua'] ); |
53 | if ( isset( $versions['LuaJIT'] ) ) { |
54 | $software['[http://luajit.org/ LuaJIT]'] = str_replace( 'LuaJIT ', '', $versions['LuaJIT'] ); |
55 | } |
56 | } |
57 | |
58 | /** @inheritDoc */ |
59 | public function getResourceUsage( $resource ) { |
60 | $this->load(); |
61 | switch ( $resource ) { |
62 | case self::MEM_PEAK_BYTES: |
63 | return $this->interpreter->getPeakMemoryUsage(); |
64 | case self::CPU_SECONDS: |
65 | return $this->interpreter->getCPUUsage(); |
66 | default: |
67 | return false; |
68 | } |
69 | } |
70 | |
71 | /** |
72 | * @return array |
73 | */ |
74 | private function getLimitReportData() { |
75 | $ret = []; |
76 | $this->load(); |
77 | |
78 | $t = $this->interpreter->getCPUUsage(); |
79 | $ret['scribunto-limitreport-timeusage'] = [ |
80 | sprintf( "%.3f", $t ), |
81 | sprintf( "%.3f", $this->options['cpuLimit'] ) |
82 | ]; |
83 | $ret['scribunto-limitreport-memusage'] = [ |
84 | $this->interpreter->getPeakMemoryUsage(), |
85 | $this->options['memoryLimit'], |
86 | ]; |
87 | |
88 | $logs = $this->getLogBuffer(); |
89 | if ( $logs !== '' ) { |
90 | $ret['scribunto-limitreport-logs'] = $logs; |
91 | } |
92 | |
93 | if ( $t < 1.0 ) { |
94 | return $ret; |
95 | } |
96 | |
97 | $percentProfile = $this->interpreter->getProfilerFunctionReport( |
98 | LuaSandboxInterpreter::PERCENT |
99 | ); |
100 | if ( !count( $percentProfile ) ) { |
101 | return $ret; |
102 | } |
103 | $timeProfile = $this->interpreter->getProfilerFunctionReport( |
104 | LuaSandboxInterpreter::SECONDS |
105 | ); |
106 | |
107 | $lines = []; |
108 | $cumulativePercent = 0; |
109 | $num = $otherTime = $otherPercent = 0; |
110 | foreach ( $percentProfile as $name => $percent ) { |
111 | $time = $timeProfile[$name] * 1000; |
112 | $num++; |
113 | if ( $cumulativePercent <= 99 && $num <= 10 ) { |
114 | // Map some regularly appearing internal names |
115 | if ( preg_match( '/^<mw.lua:(\d+)>$/', $name, $m ) ) { |
116 | $line = $this->getMwLuaLine( (int)$m[1] ); |
117 | if ( preg_match( '/^\s*(local\s+)?function ([a-zA-Z0-9_.]*)/', $line, $m ) ) { |
118 | $name = $m[2] . ' ' . $name; |
119 | } |
120 | } |
121 | $utf8Name = $this->fixTruncation( $name ); |
122 | $lines[] = [ $utf8Name, sprintf( '%.0f', $time ), sprintf( '%.1f', $percent ) ]; |
123 | } else { |
124 | $otherTime += $time; |
125 | $otherPercent += $percent; |
126 | } |
127 | $cumulativePercent += $percent; |
128 | } |
129 | if ( $otherTime ) { |
130 | $lines[] = [ '[others]', sprintf( '%.0f', $otherTime ), sprintf( '%.1f', $otherPercent ) ]; |
131 | } |
132 | $ret['scribunto-limitreport-profile'] = $lines; |
133 | return $ret; |
134 | } |
135 | |
136 | /** |
137 | * Lua truncates symbols at 60 bytes, but this may create invalid UTF-8. |
138 | * |
139 | * MediaWiki has Language::normalize() but that's complex and seems like |
140 | * overkill. A no-op iconv() with errors ignored does the job. |
141 | * |
142 | * @param string $s |
143 | * @return string |
144 | */ |
145 | private function fixTruncation( $s ) { |
146 | $lang = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( 'en' ); |
147 | return $lang->iconv( 'UTF-8', 'UTF-8', $s ); |
148 | } |
149 | |
150 | /** @inheritDoc */ |
151 | public function reportLimitData( ParserOutput $parserOutput ) { |
152 | $data = $this->getLimitReportData(); |
153 | foreach ( $data as $k => $v ) { |
154 | $parserOutput->setLimitReportData( $k, $v ); |
155 | } |
156 | if ( isset( $data['scribunto-limitreport-logs'] ) ) { |
157 | $parserOutput->addModules( [ 'ext.scribunto.logs' ] ); |
158 | } |
159 | } |
160 | |
161 | /** |
162 | * @inheritDoc |
163 | * @suppress SecurityCheck-DoubleEscaped phan false positive |
164 | */ |
165 | public function formatLimitData( $key, &$value, &$report, $isHTML, $localize ) { |
166 | switch ( $key ) { |
167 | case 'scribunto-limitreport-logs': |
168 | if ( $isHTML ) { |
169 | $report .= $this->formatHtmlLogs( $value, $localize ); |
170 | } |
171 | return false; |
172 | } |
173 | |
174 | if ( $key !== 'scribunto-limitreport-profile' ) { |
175 | return true; |
176 | } |
177 | '@phan-var string[] $value'; |
178 | |
179 | $keyMsg = wfMessage( 'scribunto-limitreport-profile' ); |
180 | $msMsg = wfMessage( 'scribunto-limitreport-profile-ms' ); |
181 | $percentMsg = wfMessage( 'scribunto-limitreport-profile-percent' ); |
182 | if ( !$localize ) { |
183 | $keyMsg->inLanguage( 'en' )->useDatabase( false ); |
184 | $msMsg->inLanguage( 'en' )->useDatabase( false ); |
185 | $percentMsg->inLanguage( 'en' )->useDatabase( false ); |
186 | } |
187 | |
188 | // To avoid having to do actual work in Message::fetchMessage for each |
189 | // line in the loops below, call ->exists() here to populate ->message. |
190 | $msMsg->exists(); |
191 | $percentMsg->exists(); |
192 | |
193 | if ( $isHTML ) { |
194 | $report .= Html::openElement( 'tr' ) . |
195 | Html::rawElement( 'th', [ 'colspan' => 2 ], $keyMsg->parse() ) . |
196 | Html::closeElement( 'tr' ) . |
197 | Html::openElement( 'tr' ) . |
198 | Html::openElement( 'td', [ 'colspan' => 2 ] ) . |
199 | Html::openElement( 'table' ); |
200 | |
201 | $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer(); |
202 | |
203 | foreach ( $value as $line ) { |
204 | $name = $line[0]; |
205 | $location = ''; |
206 | if ( preg_match( '/^(.*?) *<([^<>]+):(\d+)>$/', $name, $m ) ) { |
207 | $name = $m[1]; |
208 | $title = Title::newFromText( $m[2] ); |
209 | if ( $title && $title->hasContentModel( CONTENT_MODEL_SCRIBUNTO ) ) { |
210 | $location = '<' . |
211 | $linkRenderer->makeLink( $title ) . ":{$m[3]}>"; |
212 | } else { |
213 | $location = htmlspecialchars( "<{$m[2]}:{$m[3]}>" ); |
214 | } |
215 | } |
216 | $ms = clone $msMsg; |
217 | $ms->params( $line[1] ); |
218 | $pct = clone $percentMsg; |
219 | $pct->params( $line[2] ); |
220 | $report .= Html::openElement( 'tr' ) . |
221 | Html::element( 'td', [], $name ) . |
222 | Html::rawElement( 'td', [], $location ) . |
223 | Html::rawElement( 'td', [ 'align' => 'right' ], $ms->parse() ) . |
224 | Html::rawElement( 'td', [ 'align' => 'right' ], $pct->parse() ) . |
225 | Html::closeElement( 'tr' ); |
226 | } |
227 | $report .= Html::closeElement( 'table' ) . |
228 | Html::closeElement( 'td' ) . |
229 | Html::closeElement( 'tr' ); |
230 | } else { |
231 | $report .= $keyMsg->text() . ":\n"; |
232 | foreach ( $value as $line ) { |
233 | $ms = clone $msMsg; |
234 | $ms->params( $line[1] ); |
235 | $pct = clone $percentMsg; |
236 | $pct->params( $line[2] ); |
237 | $report .= sprintf( " %-59s %11s %11s\n", $line[0], $ms->text(), $pct->text() ); |
238 | } |
239 | } |
240 | |
241 | return false; |
242 | } |
243 | |
244 | /** |
245 | * Fetch a line from mw.lua |
246 | * @param int $lineNum |
247 | * @return string |
248 | */ |
249 | protected function getMwLuaLine( $lineNum ) { |
250 | if ( !isset( $this->lineCache['mw.lua'] ) ) { |
251 | $this->lineCache['mw.lua'] = file( $this->getLuaLibDir() . '/mw.lua' ); |
252 | } |
253 | return $this->lineCache['mw.lua'][$lineNum - 1]; |
254 | } |
255 | |
256 | protected function newInterpreter() { |
257 | return new LuaSandboxInterpreter( $this, $this->options ); |
258 | } |
259 | } |