1<?php
2/* vim: set expandtab sw=4 ts=4 sts=4: */
3/**
4 * PhpMyAdmin\Server\Status\Data class
5 * Used by server_status_*.php pages
6 *
7 * @package PhpMyAdmin
8 */
9namespace PhpMyAdmin\Server\Status;
10
11use PhpMyAdmin\Url;
12
13/**
14 * This class provides data about the server status
15 *
16 * All properties of the class are read-only
17 *
18 * TODO: Use lazy initialisation for some of the properties
19 *       since not all of the server_status_*.php pages need
20 *       all the data that this class provides.
21 *
22 * @package PhpMyAdmin
23 */
24class Data
25{
26    public $status;
27    public $sections;
28    public $variables;
29    public $used_queries;
30    public $allocationMap;
31    public $links;
32    public $db_isLocal;
33    public $section;
34    public $sectionUsed;
35    public $selfUrl;
36    public $dataLoaded;
37
38    /**
39     * An empty setter makes the above properties read-only
40     *
41     * @param string $a key
42     * @param mixed  $b value
43     *
44     * @return void
45     */
46    public function __set($a, $b)
47    {
48        // Discard everything
49    }
50
51    /**
52     * Gets the allocations for constructor
53     *
54     * @return array
55     */
56    private function _getAllocations()
57    {
58        return array(
59            // variable name => section
60            // variable names match when they begin with the given string
61
62            'Com_'              => 'com',
63            'Innodb_'           => 'innodb',
64            'Ndb_'              => 'ndb',
65            'Handler_'          => 'handler',
66            'Qcache_'           => 'qcache',
67            'Threads_'          => 'threads',
68            'Slow_launch_threads' => 'threads',
69
70            'Binlog_cache_'     => 'binlog_cache',
71            'Created_tmp_'      => 'created_tmp',
72            'Key_'              => 'key',
73
74            'Delayed_'          => 'delayed',
75            'Not_flushed_delayed_rows' => 'delayed',
76
77            'Flush_commands'    => 'query',
78            'Last_query_cost'   => 'query',
79            'Slow_queries'      => 'query',
80            'Queries'           => 'query',
81            'Prepared_stmt_count' => 'query',
82
83            'Select_'           => 'select',
84            'Sort_'             => 'sort',
85
86            'Open_tables'       => 'table',
87            'Opened_tables'     => 'table',
88            'Open_table_definitions' => 'table',
89            'Opened_table_definitions' => 'table',
90            'Table_locks_'      => 'table',
91
92            'Rpl_status'        => 'repl',
93            'Slave_'            => 'repl',
94
95            'Tc_'               => 'tc',
96
97            'Ssl_'              => 'ssl',
98
99            'Open_files'        => 'files',
100            'Open_streams'      => 'files',
101            'Opened_files'      => 'files',
102        );
103    }
104
105    /**
106     * Gets the sections for constructor
107     *
108     * @return array
109     */
110    private function _getSections()
111    {
112        return array(
113            // section => section name (description)
114            'com'           => 'Com',
115            'query'         => __('SQL query'),
116            'innodb'        => 'InnoDB',
117            'ndb'           => 'NDB',
118            'handler'       => __('Handler'),
119            'qcache'        => __('Query cache'),
120            'threads'       => __('Threads'),
121            'binlog_cache'  => __('Binary log'),
122            'created_tmp'   => __('Temporary data'),
123            'delayed'       => __('Delayed inserts'),
124            'key'           => __('Key cache'),
125            'select'        => __('Joins'),
126            'repl'          => __('Replication'),
127            'sort'          => __('Sorting'),
128            'table'         => __('Tables'),
129            'tc'            => __('Transaction coordinator'),
130            'files'         => __('Files'),
131            'ssl'           => 'SSL',
132            'other'         => __('Other')
133        );
134    }
135
136    /**
137     * Gets the links for constructor
138     *
139     * @return array
140     */
141    private function _getLinks()
142    {
143        $links = array();
144        // variable or section name => (name => url)
145
146        $links['table'][__('Flush (close) all tables')] = [
147            'url' => $this->selfUrl,
148            'params' => Url::getCommon(['flush' => 'TABLES'], ''),
149        ];
150        $links['table'][__('Show open tables')] = [
151            'url' => 'sql.php',
152            'params' => Url::getCommon([
153                'sql_query' => 'SHOW OPEN TABLES',
154                'goto' => $this->selfUrl,
155            ], ''),
156        ];
157
158        if ($GLOBALS['replication_info']['master']['status']) {
159            $links['repl'][__('Show slave hosts')] = [
160                'url' => 'sql.php',
161                'params' => Url::getCommon([
162                    'sql_query' => 'SHOW SLAVE HOSTS',
163                    'goto' => $this->selfUrl,
164                ], ''),
165            ];
166            $links['repl'][__('Show master status')] = [
167                'url' => '#replication_master',
168                'params' => '',
169            ];
170        }
171        if ($GLOBALS['replication_info']['slave']['status']) {
172            $links['repl'][__('Show slave status')] = [
173                'url' => '#replication_slave',
174                'params' => '',
175            ];
176        }
177
178        $links['repl']['doc'] = 'replication';
179
180        $links['qcache'][__('Flush query cache')] = [
181            'url' => $this->selfUrl,
182            'params' => Url::getCommon(['flush' => 'QUERY CACHE'], ''),
183        ];
184        $links['qcache']['doc'] = 'query_cache';
185
186        $links['threads']['doc'] = 'mysql_threads';
187
188        $links['key']['doc'] = 'myisam_key_cache';
189
190        $links['binlog_cache']['doc'] = 'binary_log';
191
192        $links['Slow_queries']['doc'] = 'slow_query_log';
193
194        $links['innodb'][__('Variables')] = [
195            'url' => 'server_engines.php',
196            'params' => Url::getCommon(['engine' => 'InnoDB'], ''),
197        ];
198        $links['innodb'][__('InnoDB Status')] = [
199            'url' => 'server_engines.php',
200            'params' => Url::getCommon([
201                'engine' => 'InnoDB',
202                'page' => 'Status',
203            ], ''),
204        ];
205        $links['innodb']['doc'] = 'innodb';
206
207        return($links);
208    }
209
210    /**
211     * Calculate some values
212     *
213     * @param array $server_status    contains results of SHOW GLOBAL STATUS
214     * @param array $server_variables contains results of SHOW GLOBAL VARIABLES
215     *
216     * @return array $server_status
217     */
218    private function _calculateValues(array $server_status, array $server_variables)
219    {
220        // Key_buffer_fraction
221        if (isset($server_status['Key_blocks_unused'])
222            && isset($server_variables['key_cache_block_size'])
223            && isset($server_variables['key_buffer_size'])
224            && $server_variables['key_buffer_size'] != 0
225        ) {
226            $server_status['Key_buffer_fraction_%']
227                = 100
228                - $server_status['Key_blocks_unused']
229                * $server_variables['key_cache_block_size']
230                / $server_variables['key_buffer_size']
231                * 100;
232        } elseif (isset($server_status['Key_blocks_used'])
233            && isset($server_variables['key_buffer_size'])
234            && $server_variables['key_buffer_size'] != 0
235        ) {
236            $server_status['Key_buffer_fraction_%']
237                = $server_status['Key_blocks_used']
238                * 1024
239                / $server_variables['key_buffer_size'];
240        }
241
242        // Ratio for key read/write
243        if (isset($server_status['Key_writes'])
244            && isset($server_status['Key_write_requests'])
245            && $server_status['Key_write_requests'] > 0
246        ) {
247            $key_writes = $server_status['Key_writes'];
248            $key_write_requests = $server_status['Key_write_requests'];
249            $server_status['Key_write_ratio_%']
250                = 100 * $key_writes / $key_write_requests;
251        }
252
253        if (isset($server_status['Key_reads'])
254            && isset($server_status['Key_read_requests'])
255            && $server_status['Key_read_requests'] > 0
256        ) {
257            $key_reads = $server_status['Key_reads'];
258            $key_read_requests = $server_status['Key_read_requests'];
259            $server_status['Key_read_ratio_%']
260                = 100 * $key_reads / $key_read_requests;
261        }
262
263        // Threads_cache_hitrate
264        if (isset($server_status['Threads_created'])
265            && isset($server_status['Connections'])
266            && $server_status['Connections'] > 0
267        ) {
268
269            $server_status['Threads_cache_hitrate_%']
270                = 100 - $server_status['Threads_created']
271                / $server_status['Connections'] * 100;
272        }
273        return $server_status;
274    }
275
276    /**
277     * Sort variables into arrays
278     *
279     * @param array $server_status contains results of SHOW GLOBAL STATUS
280     * @param array $allocations   allocations for sections
281     * @param array $allocationMap map variables to their section
282     * @param array $sectionUsed   is a section used?
283     * @param array $used_queries  used queries
284     *
285     * @return array ($allocationMap, $sectionUsed, $used_queries)
286     */
287    private function _sortVariables(
288        array $server_status, array $allocations, array $allocationMap, array $sectionUsed,
289        array $used_queries
290    ) {
291        foreach ($server_status as $name => $value) {
292            $section_found = false;
293            foreach ($allocations as $filter => $section) {
294                if (mb_strpos($name, $filter) !== false) {
295                    $allocationMap[$name] = $section;
296                    $sectionUsed[$section] = true;
297                    $section_found = true;
298                    if ($section == 'com' && $value > 0) {
299                        $used_queries[$name] = $value;
300                    }
301                    break; // Only exits inner loop
302                }
303            }
304            if (! $section_found) {
305                $allocationMap[$name] = 'other';
306                $sectionUsed['other'] = true;
307            }
308        }
309        return array($allocationMap, $sectionUsed, $used_queries);
310    }
311
312    /**
313     * Constructor
314     */
315    public function __construct()
316    {
317        $this->selfUrl = basename($GLOBALS['PMA_PHP_SELF']);
318
319        // get status from server
320        $server_status_result = $GLOBALS['dbi']->tryQuery('SHOW GLOBAL STATUS');
321        $server_status = array();
322        if ($server_status_result === false) {
323            $this->dataLoaded = false;
324        } else {
325            $this->dataLoaded = true;
326            while ($arr = $GLOBALS['dbi']->fetchRow($server_status_result)) {
327                $server_status[$arr[0]] = $arr[1];
328            }
329            $GLOBALS['dbi']->freeResult($server_status_result);
330        }
331
332        // for some calculations we require also some server settings
333        $server_variables = $GLOBALS['dbi']->fetchResult(
334            'SHOW GLOBAL VARIABLES', 0, 1
335        );
336
337        // cleanup of some deprecated values
338        $server_status = self::cleanDeprecated($server_status);
339
340        // calculate some values
341        $server_status = $this->_calculateValues(
342            $server_status, $server_variables
343        );
344
345        // split variables in sections
346        $allocations = $this->_getAllocations();
347
348        $sections = $this->_getSections();
349
350        // define some needful links/commands
351        $links = $this->_getLinks();
352
353        // Variable to contain all com_ variables (query statistics)
354        $used_queries = array();
355
356        // Variable to map variable names to their respective section name
357        // (used for js category filtering)
358        $allocationMap = array();
359
360        // Variable to mark used sections
361        $sectionUsed = array();
362
363        // sort vars into arrays
364        list(
365            $allocationMap, $sectionUsed, $used_queries
366        ) = $this->_sortVariables(
367            $server_status, $allocations, $allocationMap, $sectionUsed,
368            $used_queries
369        );
370
371        // admin commands are not queries (e.g. they include COM_PING,
372        // which is excluded from $server_status['Questions'])
373        unset($used_queries['Com_admin_commands']);
374
375        // Set all class properties
376        $this->db_isLocal = false;
377        $serverHostToLower = mb_strtolower(
378            $GLOBALS['cfg']['Server']['host']
379        );
380        if ($serverHostToLower === 'localhost'
381            || $GLOBALS['cfg']['Server']['host'] === '127.0.0.1'
382            || $GLOBALS['cfg']['Server']['host'] === '::1'
383        ) {
384            $this->db_isLocal = true;
385        }
386        $this->status = $server_status;
387        $this->sections = $sections;
388        $this->variables = $server_variables;
389        $this->used_queries = $used_queries;
390        $this->allocationMap = $allocationMap;
391        $this->links = $links;
392        $this->sectionUsed = $sectionUsed;
393    }
394
395    /**
396     * cleanup of some deprecated values
397     *
398     * @param array $server_status status array to process
399     *
400     * @return array
401     */
402    public static function cleanDeprecated(array $server_status)
403    {
404        $deprecated = array(
405            'Com_prepare_sql' => 'Com_stmt_prepare',
406            'Com_execute_sql' => 'Com_stmt_execute',
407            'Com_dealloc_sql' => 'Com_stmt_close',
408        );
409        foreach ($deprecated as $old => $new) {
410            if (isset($server_status[$old]) && isset($server_status[$new])) {
411                unset($server_status[$old]);
412            }
413        }
414        return $server_status;
415    }
416
417    /**
418     * Generates menu HTML
419     *
420     * @return string
421     */
422    public function getMenuHtml()
423    {
424        $url_params = Url::getCommon();
425        $items = array(
426            array(
427                'name' => __('Server'),
428                'url' => 'server_status.php'
429            ),
430            array(
431                'name' => __('Processes'),
432                'url' => 'server_status_processes.php'
433            ),
434            array(
435                'name' => __('Query statistics'),
436                'url' => 'server_status_queries.php'
437            ),
438            array(
439                'name' => __('All status variables'),
440                'url' => 'server_status_variables.php'
441            ),
442            array(
443                'name' => __('Monitor'),
444                'url' => 'server_status_monitor.php'
445            ),
446            array(
447                'name' => __('Advisor'),
448                'url' => 'server_status_advisor.php'
449            )
450        );
451
452        $retval  = '<ul id="topmenu2">';
453        foreach ($items as $item) {
454            $class = '';
455            if ($item['url'] === $this->selfUrl) {
456                $class = ' class="tabactive"';
457            }
458            $retval .= '<li>';
459            $retval .= '<a' . $class;
460            $retval .= ' href="' . $item['url'] . $url_params . '">';
461            $retval .= $item['name'];
462            $retval .= '</a>';
463            $retval .= '</li>';
464        }
465        $retval .= '</ul>';
466        $retval .= '<div class="clearfloat"></div>';
467
468        return $retval;
469    }
470
471    /**
472     * Builds a <select> list for refresh rates
473     *
474     * @param string $name         Name of select
475     * @param int    $defaultRate  Currently chosen rate
476     * @param array  $refreshRates List of refresh rates
477     *
478     * @return string
479     */
480    public static function getHtmlForRefreshList($name,
481        $defaultRate = 5,
482        array $refreshRates = array(1, 2, 5, 10, 20, 40, 60, 120, 300, 600)
483    ) {
484        $return = '<select name="' . $name . '" id="id_' . $name
485            . '" class="refreshRate">';
486        foreach ($refreshRates as $rate) {
487            $selected = ($rate == $defaultRate)?' selected="selected"':'';
488            $return .= '<option value="' . $rate . '"' . $selected . '>';
489            if ($rate < 60) {
490                $return .= sprintf(
491                    _ngettext('%d second', '%d seconds', $rate), $rate
492                );
493            } else {
494                $rate = $rate / 60;
495                $return .= sprintf(
496                    _ngettext('%d minute', '%d minutes', $rate), $rate
497                );
498            }
499            $return .=  '</option>';
500        }
501        $return .= '</select>';
502        return $return;
503    }
504}
505