Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 124
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialTranscodeStatistics
0.00% covered (danger)
0.00%
0 / 124
0.00% covered (danger)
0.00%
0 / 7
462
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 renderState
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
42
 getTranscodes
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
12
 getTranscodesTable
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
6
 getStates
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 1
42
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Special:TranscodeStatistics
4 *
5 * Show some information about unprocessed jobs
6 *
7 * @file
8 * @ingroup SpecialPage
9 */
10
11namespace MediaWiki\TimedMediaHandler;
12
13use MediaWiki\Output\OutputPage;
14use MediaWiki\SpecialPage\SpecialPage;
15use MediaWiki\TimedMediaHandler\WebVideoTranscode\WebVideoTranscode;
16use MediaWiki\Title\Title;
17use WANObjectCache;
18use Wikimedia\Rdbms\Database;
19use Wikimedia\Rdbms\IConnectionProvider;
20use Wikimedia\Rdbms\SelectQueryBuilder;
21
22class SpecialTranscodeStatistics extends SpecialPage {
23    /** @var string[] */
24    private $transcodeStates = [
25        // Note: these queries should check prefixes of the index transcode_time_inx
26        // phpcs:ignore Generic.Files.LineLength.TooLong
27        'active'  => 'transcode_time_addjob IS NOT NULL AND transcode_time_startwork IS NOT NULL AND transcode_time_success IS     NULL AND transcode_time_error IS     NULL',
28        // phpcs:ignore Generic.Files.LineLength.TooLong
29        'failed'  => 'transcode_time_addjob IS NOT NULL AND transcode_time_startwork IS NOT NULL AND transcode_time_success IS     NULL AND transcode_time_error IS NOT NULL',
30        // phpcs:ignore Generic.Files.LineLength.TooLong
31        'queued'  => 'transcode_time_addjob IS NOT NULL AND transcode_time_startwork IS     NULL AND transcode_time_success IS     NULL AND transcode_time_error IS     NULL',
32        // phpcs:ignore Generic.Files.LineLength.TooLong
33        'missing' => 'transcode_time_addjob IS     NULL AND transcode_time_startwork IS     NULL AND transcode_time_success IS     NULL AND transcode_time_error IS     NULL',
34    ];
35    // index on transcode_time_addjob,transcode_time_startwork,transcode_time_error,transcode_key,transcode_image_name
36
37    /** @var IConnectionProvider */
38    private $dbProvider;
39
40    /** @var WANObjectCache */
41    private $cache;
42
43    /**
44     * @param IConnectionProvider $dbProvider
45     * @param WANObjectCache $cache
46     */
47    public function __construct(
48        IConnectionProvider $dbProvider,
49        WANObjectCache $cache
50    ) {
51        parent::__construct( 'TranscodeStatistics', 'transcode-status' );
52        $this->dbProvider = $dbProvider;
53        $this->cache = $cache;
54    }
55
56    /** @inheritDoc */
57    public function execute( $par ) {
58        $this->setHeaders();
59        $this->checkPermissions();
60        $out = $this->getOutput();
61
62        $out->addModuleStyles( 'mediawiki.special' );
63
64        $states = $this->getStates();
65        $this->renderState( $out, 'transcodes', $states, false );
66        foreach ( $this->transcodeStates as $state => $condition ) {
67            $this->renderState( $out, $state, $states, $state !== 'missing' );
68        }
69    }
70
71    /**
72     * @param OutputPage $out
73     * @param string $state
74     * @param array $states
75     * @param bool $showTable
76     */
77    private function renderState( $out, $state, $states, $showTable = true ) {
78        $allTranscodes = WebVideoTranscode::enabledTranscodes();
79        if ( $states[ $state ][ 'total' ] ) {
80            // Give grep a chance to find the usages:
81            // timedmedia-derivative-state-transcodes, timedmedia-derivative-state-active,
82            // timedmedia-derivative-state-queued, timedmedia-derivative-state-failed,
83            // timedmedia-derivative-state-missing
84            $out->addHTML(
85                "<h2>"
86                . $this->msg(
87                    'timedmedia-derivative-state-' . $state
88                )->numParams( $states[ $state ]['total'] )->escaped()
89                . "</h2>"
90            );
91            foreach ( $allTranscodes as $key ) {
92                if ( isset( $states[$state][$key] ) && $states[$state][$key] ) {
93                    $out->addHTML(
94                        htmlspecialchars( $this->getLanguage()->formatNum( $states[ $state ][ $key ] ) )
95                        . ' '
96                        . $this->msg( 'timedmedia-derivative-desc-' . $key )->escaped()
97                        . "<br>" );
98                }
99            }
100            if ( $showTable ) {
101                $out->addHTML( $this->getTranscodesTable( $state ) );
102            }
103        }
104    }
105
106    /**
107     * @param string $state
108     * @param int $limit
109     *
110     * @return false|array
111     */
112    private function getTranscodes( $state, $limit = 50 ) {
113        $fname = __METHOD__;
114
115        return $this->cache->getWithSetCallback(
116            $this->cache->makeKey( 'TimedMediaHandler-files', $state ),
117            $this->cache::TTL_MINUTE,
118            function ( $oldValue, &$ttl, array &$setOpts ) use ( $state, $limit, $fname ) {
119                $dbr = $this->dbProvider->getReplicaDatabase();
120                $setOpts += Database::getCacheSetOptions( $dbr );
121
122                $files = [];
123                $res = $dbr->newSelectQueryBuilder()
124                    ->select( [ 'transcode_image_name', 'transcode_key' ] )
125                    ->from( 'transcode' )
126                    ->where( $this->transcodeStates[ $state ] )
127                    ->limit( $limit )
128                    ->orderBy( [
129                        'transcode_time_addjob',
130                        'transcode_time_startwork',
131                        'transcode_time_success',
132                        'transcode_time_error',
133                    ], SelectQueryBuilder::SORT_DESC )
134                    ->caller( $fname )
135                    ->fetchResultSet();
136
137                foreach ( $res as $row ) {
138                    $transcode = [];
139                    foreach ( $row as $k => $v ) {
140                        $transcode[ str_replace( 'transcode_', '', $k ) ] = $v;
141                    }
142                    $files[] = $transcode;
143                }
144
145                return $files;
146            }
147        );
148    }
149
150    /**
151     * @param string $state
152     * @param int $limit
153     *
154     * @return string
155     */
156    private function getTranscodesTable( $state, $limit = 50 ) {
157        $linkRenderer = $this->getLinkRenderer();
158        $table = '<table class="wikitable">' . "\n"
159            . '<tr>'
160            . '<th>' . $this->msg( 'timedmedia-transcodeinfo' )->escaped() . '</th>'
161            . '<th>' . $this->msg( 'timedmedia-file' )->escaped() . '</th>'
162            . '</tr>'
163            . "\n";
164
165        foreach ( $this->getTranscodes( $state, $limit ) as $transcode ) {
166            $title = Title::newFromText( $transcode[ 'image_name' ], NS_FILE );
167            $table .= '<tr>'
168                . '<td>' . $this->msg(
169                    'timedmedia-derivative-desc-' . $transcode[ 'key' ]
170                )->escaped() . '</td>'
171                . '<td>' . $linkRenderer->makeLink( $title, $transcode[ 'image_name' ] ) . '</td>'
172                . '</tr>'
173                . "\n";
174        }
175        $table .= '</table>';
176        return $table;
177    }
178
179    /**
180     * @return array
181     */
182    private function getStates() {
183        $fname = __METHOD__;
184
185        return $this->cache->getWithSetCallback(
186            $this->cache->makeKey( 'TimedMediaHandler-states' ),
187            $this->cache::TTL_MINUTE,
188            function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
189                $dbr = $this->dbProvider->getReplicaDatabase();
190                $setOpts += Database::getCacheSetOptions( $dbr );
191
192                $allTranscodes = WebVideoTranscode::enabledTranscodes();
193
194                $states = [];
195                $states[ 'transcodes' ] = [ 'total' => 0 ];
196                foreach ( $this->transcodeStates as $state => $condition ) {
197                    $states[ $state ] = [ 'total' => 0 ];
198                    foreach ( $allTranscodes as $type ) {
199                        // Important to pre-initialize, as can give
200                        // warnings if you don't have a lot of things in transcode table.
201                        $states[ $state ][ $type ] = 0;
202                    }
203                }
204                foreach ( $this->transcodeStates as $state => $condition ) {
205                    $cond = [ 'transcode_key' => $allTranscodes ];
206                    $cond[] = $condition;
207                    $res = $dbr->newSelectQueryBuilder()
208                        ->select( [ 'COUNT(*) as count', 'transcode_key' ] )
209                        ->from( 'transcode' )
210                        ->where( $cond )
211                        ->groupBy( 'transcode_key' )
212                        ->caller( $fname )
213                        ->fetchResultSet();
214                    foreach ( $res as $row ) {
215                        $key = $row->transcode_key;
216                        $states[ $state ][ $key ] = $row->count;
217                        $states[ $state ][ 'total' ] += $states[ $state ][ $key ];
218                    }
219                }
220                $res = $dbr->newSelectQueryBuilder()
221                    ->select( [ 'COUNT(*) as count', 'transcode_key' ] )
222                    ->from( 'transcode' )
223                    ->where( [ 'transcode_key' => $allTranscodes ] )
224                    ->groupBy( 'transcode_key' )
225                    ->caller( $fname )
226                    ->fetchResultSet();
227                foreach ( $res as $row ) {
228                    $key = $row->transcode_key;
229                    $states[ 'transcodes' ][ $key ] = $row->count;
230                    $states[ 'transcodes' ][ $key ] -= $states[ 'queued' ][ $key ];
231                    $states[ 'transcodes' ][ $key ] -= $states[ 'missing' ][ $key ];
232                    $states[ 'transcodes' ][ $key ] -= $states[ 'active' ][ $key ];
233                    $states[ 'transcodes' ][ $key ] -= $states[ 'failed' ][ $key ];
234                    $states[ 'transcodes' ][ 'total' ] += $states[ 'transcodes' ][ $key ];
235                }
236
237                return $states;
238            },
239            [ 'lockTSE' => 30 ]
240        );
241    }
242
243    /** @inheritDoc */
244    protected function getGroupName() {
245        return 'media';
246    }
247}