Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 159 |
|
0.00% |
0 / 12 |
CRAP | |
0.00% |
0 / 1 |
MathPerformance | |
0.00% |
0 / 154 |
|
0.00% |
0 / 12 |
930 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
12 | |||
actionExport | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
getFormulae | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
12 | |||
actionBenchmark | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
42 | |||
time | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
12 | |||
resetTimer | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
vPrint | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
runTest | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
actionPng | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
processImage | |
0.00% |
0 / 40 |
|
0.00% |
0 / 1 |
20 | |||
makePath | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 |
1 | #!/usr/bin/env php |
2 | <?php |
3 | /** |
4 | * This program is free software; you can redistribute it and/or modify |
5 | * it under the terms of the GNU General Public License as published by |
6 | * the Free Software Foundation; either version 2 of the License, or |
7 | * (at your option) any later version. |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU General Public License along |
15 | * with this program; if not, write to the Free Software Foundation, Inc., |
16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | * http://www.gnu.org/copyleft/gpl.html |
18 | * |
19 | * @ingroup Maintenance |
20 | */ |
21 | |
22 | use MediaWiki\Extension\Math\Hooks as MathHooks; |
23 | use MediaWiki\Extension\Math\MathLaTeXML; |
24 | use MediaWiki\Extension\Math\MathMathML; |
25 | use MediaWiki\Extension\Math\MathRestbaseInterface; |
26 | use MediaWiki\Logger\LoggerFactory; |
27 | use MediaWiki\MediaWikiServices; |
28 | |
29 | require_once __DIR__ . '/../../../maintenance/Maintenance.php'; |
30 | |
31 | class MathPerformance extends Maintenance { |
32 | |
33 | /** @var bool */ |
34 | private $verbose; |
35 | /** @var \Wikimedia\Rdbms\IDatabase */ |
36 | private $db; |
37 | /** @var string|null */ |
38 | private $currentHash; |
39 | /** @var float */ |
40 | private $time = 0.0; // microtime( true ); |
41 | /** @var float[] */ |
42 | private $performance = []; |
43 | /** @var string */ |
44 | private $renderingMode = 'mathml'; |
45 | |
46 | public function __construct() { |
47 | parent::__construct(); |
48 | $this->addDescription( 'Run math performance tests.' ); |
49 | $this->addArg( 'action', 'Selects what should be done.', false ); |
50 | $this->addArg( 'shares', 'How many pieces should be used.', false ); |
51 | $this->addArg( 'share', 'Which piece should be used. Starting from 0.', false ); |
52 | $this->addOption( 'table', 'table to load the formulae from', false ); |
53 | $this->addOption( 'input', 'field that contains the input', false ); |
54 | $this->addOption( 'hash', 'field that contains the hash', false ); |
55 | $this->addOption( 'min', 'If set, processing is started at formula>min', false ); |
56 | $this->addOption( 'max', 'If set, processing is stopped at formula<=max', false ); |
57 | $this->addOption( 'output', 'The destination of the output defaults to stdout.', false ); |
58 | $this->addOption( 'verbose', 'If set, output for successful rendering will produced', false, |
59 | false, 'v' ); |
60 | $this->requireExtension( 'MathSearch' ); |
61 | } |
62 | |
63 | public function execute() { |
64 | global $wgMathValidModes; |
65 | $this->db = MediaWikiServices::getInstance() |
66 | ->getConnectionProvider() |
67 | ->getPrimaryDatabase(); |
68 | $wgMathValidModes[] = $this->renderingMode; |
69 | $this->verbose = $this->getOption( 'verbose', false ); |
70 | $this->vPrint( "Loaded." ); |
71 | $action = trim( $this->getArg( 0, 'export' ) ); |
72 | switch ( $action ) { |
73 | case 'export': |
74 | $this->actionExport(); |
75 | break; |
76 | case "benchmark": |
77 | $this->actionBenchmark(); |
78 | break; |
79 | } |
80 | $shareString = $this->getArg( 2, '' ); |
81 | $this->vPrint( "{$shareString}Done." ); |
82 | } |
83 | |
84 | private function actionExport() { |
85 | $tex = $this->getOption( 'input', 'math_input' ); |
86 | $hash = $this->getOption( 'hash', 'math_inputhash' ); |
87 | $formulae = $this->getFormulae( $hash, $tex ); |
88 | $out = []; |
89 | foreach ( $formulae as $formula ) { |
90 | $out[] = [ $hash => base64_encode( $formula->$hash ), $tex => $formula->$tex ]; |
91 | } |
92 | $output = $this->getOption( 'output', 'php://stdout' ); |
93 | file_put_contents( $output, json_encode( $out, JSON_PRETTY_PRINT ) ); |
94 | } |
95 | |
96 | /** |
97 | * @param string $hash |
98 | * @param string $tex |
99 | * @return bool|\Wikimedia\Rdbms\IResultWrapper |
100 | */ |
101 | private function getFormulae( $hash, $tex ) { |
102 | $min = $this->getOption( 'min', 0 ); |
103 | $max = $this->getOption( 'max', 0 ); |
104 | $options = []; |
105 | if ( $max ) { |
106 | $options['LIMIT'] = $max - $min; |
107 | $options['OFFSET'] = $min; |
108 | } |
109 | $shares = $this->getArg( 1, false ); // 'shares' |
110 | $share = $this->getArg( 2, 0 ); // 'share' |
111 | if ( $shares ) { |
112 | $this->vPrint( "Processing share $share of $shares." ); |
113 | $counts = $this->db->selectField( 'mathlog', 'count(*)' ); |
114 | $bucket = ceil( $counts / $shares ); |
115 | $min = $share * $bucket; |
116 | $max = $min + $bucket; |
117 | $options['LIMIT'] = $max - $min; |
118 | $options['OFFSET'] = $min; |
119 | } |
120 | $formulae = $this->db->select( |
121 | 'mathlog', |
122 | [ $hash, $tex ], |
123 | '', |
124 | __METHOD__, |
125 | $options |
126 | ); |
127 | return $formulae; |
128 | } |
129 | |
130 | private function actionBenchmark() { |
131 | $tex = $this->getOption( 'input', 'math_input' ); |
132 | $hash = $this->getOption( 'hash', 'math_inputhash' ); |
133 | $formulae = $this->getFormulae( $hash, $tex ); |
134 | foreach ( $formulae as $formula ) { |
135 | $this->currentHash = $formula->$hash; |
136 | $rbi = new MathRestbaseInterface( $formula->$tex, false ); |
137 | if ( $this->runTest( $rbi ) ) { |
138 | if ( round( rand( 0, 1 ) ) ) { |
139 | $this->runTest( $rbi, 'getSvg', '1-' ) && |
140 | $this->runTest( $rbi, 'getMathML', '2-' ); |
141 | } else { |
142 | $this->runTest( $rbi, 'getMathML', '1-' ) && |
143 | $this->runTest( $rbi, 'getSvg', '2-' ); |
144 | } |
145 | } |
146 | } |
147 | } |
148 | |
149 | /** |
150 | * Measures time in ms. |
151 | * In order to have a formula centric evaluation, we can not just the build in profiler |
152 | * @param string $category |
153 | * |
154 | * @return int |
155 | */ |
156 | private function time( $category = 'default' ) { |
157 | global $wgMathDebug; |
158 | $delta = ( microtime( true ) - $this->time ) * 1000; |
159 | if ( isset( $this->performance[$category] ) ) { |
160 | $this->performance[$category] += $delta; |
161 | } else { |
162 | $this->performance[$category] = $delta; |
163 | } |
164 | $logData = [ |
165 | 'math_inputhash' => $this->currentHash, |
166 | 'mathperformance_name' => substr( $category, 0, 10 ), |
167 | 'mathperformance_time' => $delta, |
168 | 'mathperformance_mode' => MathHooks::mathModeToHashKey( $this->renderingMode ) |
169 | ]; |
170 | if ( $wgMathDebug ) { |
171 | $this->db->insert( 'mathperformance', $logData ); |
172 | } else { |
173 | $logData['math_inputhash'] = base64_encode( $logData['math_inputhash'] ); |
174 | echo json_encode( $logData ) . "\n"; |
175 | } |
176 | $this->resetTimer(); |
177 | return (int)$delta; |
178 | } |
179 | |
180 | private function resetTimer() { |
181 | $this->time = microtime( true ); |
182 | } |
183 | |
184 | private function vPrint( $string ) { |
185 | if ( $this->verbose ) { |
186 | $this->output( $string . "\n" ); |
187 | } |
188 | } |
189 | |
190 | private function runTest( MathRestbaseInterface $rbi, $method = 'checkTeX', $prefix = '' ) { |
191 | try { |
192 | $this->resetTimer(); |
193 | call_user_func( [ $rbi, $method ] ); |
194 | $this->time( $prefix . $method ); |
195 | return true; |
196 | } catch ( Exception $e ) { |
197 | $this->vPrint( "Tex:{$rbi->getTex()}" ); |
198 | $this->vPrint( $e->getMessage() ); |
199 | $this->vPrint( $e->getTraceAsString() ); |
200 | return false; |
201 | } |
202 | } |
203 | |
204 | private function actionPng() { |
205 | $tex = $this->getOption( 'input', 'math_input' ); |
206 | $hash = $this->getOption( 'hash', 'math_inputhash' ); |
207 | $folder = $this->getOption( 'output', '/tmp/math' ); |
208 | $formulae = $this->getFormulae( $hash, $tex ); |
209 | foreach ( $formulae as $formula ) { |
210 | $this->currentHash = $formula->$hash; |
211 | self::processImage( $folder, $formula->$tex ); |
212 | } |
213 | } |
214 | |
215 | /** |
216 | * @param string $folder |
217 | * @param string $input |
218 | */ |
219 | private static function processImage( $folder, $input ) { |
220 | $log = LoggerFactory::getInstance( 'MathSearch-maint' ); |
221 | $mathML = new MathMathML( $input ); |
222 | $md5 = $mathML->getInputHash(); |
223 | $path = self::makePath( $folder, $md5 ); |
224 | $log->debug( 'process image', [ |
225 | 'tex' => $input, |
226 | 'path' => $path |
227 | ] ); |
228 | |
229 | // Mathoid |
230 | if ( !$mathML->render() ) { |
231 | $log->error( 'MathML rendering returned false', [ |
232 | 'mathml' => $mathML, |
233 | 'tex' => $input, |
234 | 'path' => $path |
235 | ] ); |
236 | return; |
237 | } |
238 | $o = MathObject::cloneFromRenderer( $mathML ); |
239 | file_put_contents( "$path/new.mml", $o->getMathml() ); |
240 | file_put_contents( "$path/new.svg", $o->getSvg() ); |
241 | file_put_contents( "$path/tex.tex", $o->getUserInputTex() ); |
242 | |
243 | // Mathoid eating it's MathML |
244 | $mathML = new MathMathML( $o->getMathml(), [ 'type' => 'pmml' ] ); |
245 | if ( !$mathML->render() ) { |
246 | $log->error( 'MathML rendering returned false', [ |
247 | 'mathml' => $mathML, |
248 | 'tex' => $input, |
249 | 'path' => $path |
250 | ] ); |
251 | return; |
252 | } |
253 | |
254 | $o = MathObject::cloneFromRenderer( $mathML ); |
255 | file_put_contents( "$path/new-mathml.svg", $o->getSvg( 'force' ) ); |
256 | |
257 | // LaTeXML */ |
258 | $mathML = new MathLaTeXML( $input ); |
259 | if ( !$mathML->readFromCache() ) { |
260 | $log->error( 'LaTeXML rendering returned false', [ |
261 | 'mathml' => $mathML, |
262 | 'tex' => $input, |
263 | 'path' => $path |
264 | ] ); |
265 | return; |
266 | } |
267 | |
268 | $o = MathObject::cloneFromRenderer( $mathML ); |
269 | file_put_contents( "$path/new-latexml.svg", $o->getSvg( 'force' ) ); |
270 | file_put_contents( "$path/new-latexml.mml", $mathML->getMathml() ); |
271 | } |
272 | |
273 | /** |
274 | * @param string $folder |
275 | * @param string $md5 |
276 | * @return string |
277 | */ |
278 | private static function makePath( $folder, $md5 ) { |
279 | $subPath = implode( '/', str_split( substr( $md5, 0, 3 ) ) ); |
280 | $path = $folder . '/' . $subPath . '/' . $md5; |
281 | mkdir( $path, '0755', true ); |
282 | return $path; |
283 | } |
284 | |
285 | } |
286 | |
287 | $maintClass = MathPerformance::class; |
288 | /** @noinspection PhpIncludeInspection */ |
289 | require_once RUN_MAINTENANCE_IF_MAIN; |