27 public function __construct() {
28 parent::__construct();
29 $this->addDescription(
'Script to bootstrap TTMServer.' );
32 '(optional) Number of threads',
38 '(optional) Server configuration identifier',
45 'Update the index mapping. Warning: Clears all existing data in the index.'
49 'Do not make any changes to the index.'
53 'Output more status information.'
55 $this->setBatchSize( 500 );
56 $this->requireExtension(
'Translate' );
57 $this->start = microtime(
true );
60 public function statusLine( $text, $channel =
null ) {
61 $pid = sprintf(
'%5s', getmypid() );
62 $prefix = sprintf(
'%6.2f', microtime(
true ) - $this->start );
63 $mem = sprintf(
'%5.1fM', memory_get_usage(
true ) / ( 1024 * 1024 ) );
64 $this->output(
"$pid $prefix $mem $text", $channel );
67 public function execute() {
68 global $wgTranslateTranslationServices,
69 $wgTranslateTranslationDefaultService;
71 $configKey = $this->getOption(
'ttmserver', $wgTranslateTranslationDefaultService );
72 if ( !isset( $wgTranslateTranslationServices[$configKey] ) ) {
73 $this->fatalError(
'Translation memory is not configured properly' );
76 $dryRun = $this->getOption(
'dry-run' );
78 $config = [
'class' => FakeTTMServer::class ];
80 $config = $wgTranslateTranslationServices[$configKey];
83 $server = $this->getServer( $config );
84 $this->logInfo(
"Implementation: " . get_class( $server ) .
"\n" );
90 $this->resetStateForFork();
91 $server = $this->getServer( $config );
92 $this->beginBootstrap( $server );
94 } elseif ( $pid === -1 ) {
96 $this->beginBootstrap( $server );
99 $this->statusLine(
"Forked thread $pid to handle bootstrapping\n" );
101 pcntl_waitpid( $pid, $status );
103 if ( !$this->verifyChildStatus( $pid, $status ) ) {
104 $this->fatalError(
'Bootstrap failed.' );
109 $threads = $this->getOption(
'threads', 1 );
112 $groups = MessageGroups::singleton()->getGroups();
113 foreach ( $groups as $id => $group ) {
115 if ( $group->isMeta() ) {
123 $this->resetStateForFork();
124 $server = $this->getServer( $config );
125 $this->exportGroup( $group, $server );
127 } elseif ( $pid === -1 ) {
129 $this->exportGroup( $group, $server );
132 $this->statusLine(
"Forked thread $pid to handle $id\n" );
136 if ( count( $pids ) >= $threads ) {
138 $pid = pcntl_wait( $status );
139 $hasErrors = $hasErrors || !$this->verifyChildStatus( $pid, $status );
140 unset( $pids[$pid] );
146 foreach ( array_keys( $pids ) as $pid ) {
148 pcntl_waitpid( $pid, $status );
149 $hasErrors = $hasErrors || !$this->verifyChildStatus( $pid, $status );
153 $this->endBootstrap( $server );
156 $this->fatalError(
'!!! Some threads failed. Review the script output !!!' );
161 $server = TTMServer::factory( $config );
163 $this->fatalError(
"Service must implement WritableTTMServer" );
166 if ( is_callable( [ $server,
'setLogger' ] ) ) {
169 $server->setLogger( $this );
172 if ( $this->getOption(
'reindex',
false ) ) {
174 $server->setDoReIndex();
181 $this->statusLine(
"Cleaning up old entries...\n" );
186 $this->statusLine(
"Optimizing...\n" );
192 'total' => -microtime(
true ),
199 $id = $group->
getId();
202 $times[
'stats' ] -= microtime(
true );
203 $stats = MessageGroupStats::forGroup( $id );
204 $times[
'stats' ] += microtime(
true );
206 $times[
'init' ] -= microtime(
true );
208 $collection->filter(
'ignored' );
209 $collection->initMessages();
213 foreach ( $collection->keys() as $mkey => $titleValue ) {
214 $title = Title::newFromLinkTarget( $titleValue );
216 $inserts[] = [ $handle, $sourceLanguage, $collection[$mkey]->definition() ];
220 while ( $inserts !== [] ) {
221 $batch = array_splice( $inserts, 0, $this->mBatchSize );
225 $times[
'init' ] += microtime(
true );
227 $times[
'trans' ] -= microtime(
true );
228 foreach ( $stats as $targetLanguage => $numbers ) {
229 if ( $targetLanguage === $sourceLanguage ) {
232 if ( $numbers[MessageGroupStats::TRANSLATED] === 0 ) {
236 $collection->resetForNewLanguage( $targetLanguage );
237 $collection->filter(
'ignored' );
238 $collection->filter(
'translated',
false );
239 $collection->loadTranslations();
241 foreach ( $collection->keys() as $mkey => $titleValue ) {
242 $title = Title::newFromLinkTarget( $titleValue );
244 $inserts[] = [ $handle, $sourceLanguage, $collection[$mkey]->translation() ];
248 while ( count( $inserts ) >= $this->mBatchSize ) {
249 $batch = array_splice( $inserts, 0, $this->mBatchSize );
254 while ( $inserts !== [] ) {
255 $batch = array_splice( $inserts, 0, $this->mBatchSize );
260 $times[
'trans' ] += microtime(
true );
261 $times[
'total' ] += microtime(
true );
263 if ( $countItems !== 0 ) {
265 "Total %.1f s for %d items >> stats/init/trans %%: %d/%d/%d >> %.1f ms/item",
268 $times[
'stats'] / $times[
'total' ] * 100,
269 $times[
'init'] / $times[
'total' ] * 100,
270 $times[
'trans'] / $times[
'total' ] * 100,
271 $times[
'total' ] / $countItems * 1000
273 $this->logInfo(
"Finished exporting $id. $debug\n" );
277 private function logInfo(
string $text ) {
278 if ( $this->getOption(
'verbose',
false ) ) {
279 $this->statusLine( $text );
283 protected function resetStateForFork() {
286 MediaWiki\MediaWikiServices::resetChildProcessServices();
291 MediaWiki\MediaWikiServices::getInstance()->getMessageCache()->disable();
294 private function verifyChildStatus(
int $pid,
int $status ):
bool {
295 if ( pcntl_wifexited( $status ) ) {
296 $code = pcntl_wexitstatus( $status );
298 $this->output(
"Pid $pid exited with status $code !!\n" );
301 } elseif ( pcntl_wifsignaled( $status ) ) {
302 $signum = pcntl_wtermsig( $status );
303 $this->output(
"Pid $pid terminated by signal $signum !!\n" );