PageRenderTime 58ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/scripts/mysql_stats.php

https://bitbucket.org/MelFlynn/observium
PHP | 1242 lines | 1011 code | 50 blank | 181 comment | 117 complexity | 15420bb16d81f586f6c250a74fba5513 MD5 | raw file
Possible License(s): GPL-3.0, MIT
  1. <?php
  2. # ============================================================================
  3. # This is a script to retrieve information from a MySQL server for input to a
  4. # Cacti graphing process. It is hosted at
  5. # http://code.google.com/p/mysql-cacti-templates/.
  6. #
  7. # This program is copyright (c) 2007 Baron Schwartz. Feedback and improvements
  8. # are welcome.
  9. #
  10. # THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
  11. # WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
  12. # MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  13. #
  14. # This program is free software; you can redistribute it and/or modify it under
  15. # the terms of the GNU General Public License as published by the Free Software
  16. # Foundation, version 2.
  17. #
  18. # You should have received a copy of the GNU General Public License along with
  19. # this program; if not, write to the Free Software Foundation, Inc., 59 Temple
  20. # Place, Suite 330, Boston, MA 02111-1307 USA.
  21. # ============================================================================
  22. # ============================================================================
  23. # To make this code testable, we need to prevent code from running when it is
  24. # included from the test script. The test script and this file have different
  25. # filenames, so we can compare them. In some cases $_SERVER['SCRIPT_FILENAME']
  26. # seems not to be defined, so we skip the check -- this check should certainly
  27. # pass in the test environment.
  28. # ============================================================================
  29. if (!array_key_exists('SCRIPT_FILENAME', $_SERVER)
  30. || basename(__FILE__) == basename($_SERVER['SCRIPT_FILENAME']) ) {
  31. # ============================================================================
  32. # CONFIGURATION
  33. # ============================================================================
  34. # Define MySQL connection constants in config.php. Arguments explicitly passed
  35. # in from Cacti will override these. However, if you leave them blank in Cacti
  36. # and set them here, you can make life easier. Instead of defining parameters
  37. # here, you can define them in another file named the same as this file, with a
  38. # .cnf extension.
  39. # ============================================================================
  40. $mysql_user = 'observium';
  41. $mysql_pass = 'flobbleobservium';
  42. $mysql_host = 'localhost';
  43. $mysql_port = 3306;
  44. $mysql_ssl = FALSE; # Whether to use SSL to connect to MySQL.
  45. $heartbeat = ''; # db.tbl in case you use mk-heartbeat from Maatkit.
  46. $cache_dir = '/tmp'; # If set, this uses caching to avoid multiple calls.
  47. $poll_time = 300; # Adjust to match your polling interval.
  48. $chk_options = array (
  49. 'innodb' => true, # Do you want to check InnoDB statistics?
  50. 'master' => true, # Do you want to check binary logging?
  51. 'slave' => true, # Do you want to check slave status?
  52. 'procs' => true, # Do you want to check SHOW PROCESSLIST?
  53. );
  54. $use_ss = FALSE; # Whether to use the script server or not
  55. $debug = FALSE; # Define whether you want debugging behavior.
  56. $debug_log = FALSE; # If $debug_log is a filename, it'll be used.
  57. # ============================================================================
  58. # You should not need to change anything below this line.
  59. # ============================================================================
  60. $version = "1.1.7";
  61. # ============================================================================
  62. # Include settings from an external config file (issue 39).
  63. # ============================================================================
  64. if (file_exists(__FILE__ . '.cnf' ) ) {
  65. require(__FILE__ . '.cnf');
  66. }
  67. # Make this a happy little script even when there are errors.
  68. $no_http_headers = true;
  69. ini_set('implicit_flush', false); # No output, ever.
  70. if ($debug ) {
  71. ini_set('display_errors', true);
  72. ini_set('display_startup_errors', true);
  73. ini_set('error_reporting', 2147483647);
  74. }
  75. else {
  76. ini_set('error_reporting', E_ERROR);
  77. }
  78. ob_start(); # Catch all output such as notices of undefined array indexes.
  79. function error_handler($errno, $errstr, $errfile, $errline) {
  80. print("$errstr at $errfile line $errline\n");
  81. debug("$errstr at $errfile line $errline");
  82. }
  83. # ============================================================================
  84. # Set up the stuff we need to be called by the script server.
  85. # ============================================================================
  86. if ($use_ss ) {
  87. if (file_exists( dirname(__FILE__) . "/../include/global.php") ) {
  88. # See issue 5 for the reasoning behind this.
  89. debug("including " . dirname(__FILE__) . "/../include/global.php");
  90. include_once(dirname(__FILE__) . "/../include/global.php");
  91. }
  92. elseif (file_exists( dirname(__FILE__) . "/../include/config.php" ) ) {
  93. # Some Cacti installations don't have global.php.
  94. debug("including " . dirname(__FILE__) . "/../include/config.php");
  95. include_once(dirname(__FILE__) . "/../include/config.php");
  96. }
  97. }
  98. # ============================================================================
  99. # Make sure we can also be called as a script.
  100. # ============================================================================
  101. if (!isset($called_by_script_server)) {
  102. debug($_SERVER["argv"]);
  103. array_shift($_SERVER["argv"]); # Strip off this script's filename
  104. $options = parse_cmdline($_SERVER["argv"]);
  105. validate_options($options);
  106. $result = ss_get_mysql_stats($options);
  107. debug($result);
  108. if (!$debug ) {
  109. # Throw away the buffer, which ought to contain only errors.
  110. ob_end_clean();
  111. }
  112. else {
  113. ob_end_flush(); # In debugging mode, print out the errors.
  114. }
  115. # Split the result up and extract only the desired parts of it.
  116. $options['items'] = "";
  117. $wanted = explode(',', $options['items']);
  118. $output = array();
  119. foreach ( explode(' ', $result) as $item ) {
  120. if (in_array(substr($item, 0, 2), $wanted) ) {
  121. $output[] = $item;
  122. }
  123. list($short, $val) = explode(":", $item);
  124. echo(strtolower($short).":".strtolower($val)."\n");
  125. }
  126. debug(array("Final result", $output));
  127. print(implode(' ', $output));
  128. }
  129. # ============================================================================
  130. # End "if file was not included" section.
  131. # ============================================================================
  132. }
  133. # ============================================================================
  134. # Work around the lack of array_change_key_case in older PHP.
  135. # ============================================================================
  136. if (!function_exists('array_change_key_case') ) {
  137. function array_change_key_case($arr) {
  138. $res = array();
  139. foreach ( $arr as $key => $val ) {
  140. $res[strtolower($key)] = $val;
  141. }
  142. return $res;
  143. }
  144. }
  145. # ============================================================================
  146. # Validate that the command-line options are here and correct
  147. # ============================================================================
  148. function validate_options($options) {
  149. debug($options);
  150. $opts = array('items', 'user', 'pass', 'heartbeat', 'nocache', 'port');
  151. # Required command-line options
  152. foreach ( array() as $option ) {
  153. if (!isset($options[$option]) || !$options[$option] ) {
  154. usage("Required option --$option is missing");
  155. }
  156. }
  157. foreach ( $options as $key => $val ) {
  158. if (!in_array($key, $opts) ) {
  159. usage("Unknown option --$key");
  160. }
  161. }
  162. }
  163. # ============================================================================
  164. # Print out a brief usage summary
  165. # ============================================================================
  166. function usage($message) {
  167. global $mysql_host, $mysql_user, $mysql_pass, $mysql_port, $heartbeat;
  168. $usage = <<<EOF
  169. $message
  170. Usage: php ss_get_mysql_stats.php --host <host> --items <item,...> [OPTION]
  171. --host Hostname to connect to; use host:port syntax to specify a port
  172. Use :/path/to/socket if you want to connect via a UNIX socket
  173. --items Comma-separated list of the items whose data you want
  174. --user MySQL username; defaults to $mysql_user if not given
  175. --pass MySQL password; defaults to $mysql_pass if not given
  176. --heartbeat MySQL heartbeat table; defaults to '$heartbeat' (see mk-heartbeat)
  177. --nocache Do not cache results in a file
  178. --port MySQL port; defaults to $mysql_port if not given
  179. --mysql_ssl Add the MYSQL_CLIENT_SSL flag to mysql_connect() call
  180. EOF;
  181. die($usage);
  182. }
  183. # ============================================================================
  184. # Parse command-line arguments, in the format --arg value --arg value, and
  185. # return them as an array ( arg => value )
  186. # ============================================================================
  187. function parse_cmdline( $args ) {
  188. $result = array();
  189. $cur_arg = '';
  190. foreach ($args as $val) {
  191. if (strpos($val, '--') === 0 ) {
  192. if (strpos($val, '--no') === 0 ) {
  193. # It's an option without an argument, but it's a --nosomething so
  194. # it's OK.
  195. $result[substr($val, 2)] = 1;
  196. $cur_arg = '';
  197. }
  198. elseif ($cur_arg ) { # Maybe the last --arg was an option with no arg
  199. if ($cur_arg == '--user' || $cur_arg == '--pass' || $cur_arg == '--port' ) {
  200. # Special case because Cacti will pass these without an arg
  201. $cur_arg = '';
  202. }
  203. else {
  204. die("No arg: $cur_arg\n");
  205. }
  206. }
  207. else {
  208. $cur_arg = $val;
  209. }
  210. }
  211. else {
  212. $result[substr($cur_arg, 2)] = $val;
  213. $cur_arg = '';
  214. }
  215. }
  216. if ($cur_arg && ($cur_arg != '--user' && $cur_arg != '--pass' && $cur_arg != '--port') ) {
  217. die("No arg: $cur_arg\n");
  218. }
  219. debug($result);
  220. return $result;
  221. }
  222. # ============================================================================
  223. # This is the main function. Some parameters are filled in from defaults at the
  224. # top of this file.
  225. # ============================================================================
  226. function ss_get_mysql_stats( $options ) {
  227. # Process connection options and connect to MySQL.
  228. global $debug, $mysql_user, $mysql_pass, $heartbeat, $cache_dir, $poll_time,
  229. $chk_options, $mysql_host, $mysql_port, $mysql_ssl;
  230. # Connect to MySQL.
  231. $user = isset($options['user']) ? $options['user'] : $mysql_user;
  232. $pass = isset($options['pass']) ? $options['pass'] : $mysql_pass;
  233. $port = isset($options['port']) ? $options['port'] : $mysql_port;
  234. $host = isset($options['host']) ? $options['host'] : $mysql_host;
  235. $heartbeat = isset($options['heartbeat']) ? $options['heartbeat'] : $heartbeat;
  236. # If there is a port, or if it's a non-standard port, we add ":$port" to the
  237. # hostname.
  238. $host_str = $host
  239. . $port != 3306 ? ":$port" : '';
  240. debug(array('connecting to', $host_str, $user, $pass));
  241. if (!extension_loaded('mysql') ) {
  242. debug("The MySQL extension is not loaded");
  243. die("The MySQL extension is not loaded");
  244. }
  245. if ($mysql_ssl || (isset($options['mysql_ssl']) && $options['mysql_ssl']) ) {
  246. $conn = mysql_connect($host_str, $user, $pass, true, MYSQL_CLIENT_SSL);
  247. }
  248. else {
  249. $conn = mysql_connect($host_str, $user, $pass);
  250. }
  251. if (!$conn ) {
  252. die("MySQL: " . mysql_error());
  253. }
  254. $sanitized_host = str_replace(array(":", "/"), array("", "_"), $host);
  255. $cache_file = "$cache_dir/$sanitized_host-mysql_cacti_stats.txt"
  256. . $port != 3306 ? ":$port" : '';
  257. debug("Cache file is $cache_file");
  258. # First, check the cache.
  259. $fp = null;
  260. if (!isset($options['nocache']) ) {
  261. if ($fp = fopen($cache_file, 'a+') ) {
  262. $locked = flock($fp, 1); # LOCK_SH
  263. if ($locked ) {
  264. if (filesize($cache_file) > 0
  265. && filectime($cache_file) + ($poll_time/2) > time()
  266. && ($arr = file($cache_file))
  267. ) {# The cache file is good to use.
  268. debug("Using the cache file");
  269. fclose($fp);
  270. return $arr[0];
  271. }
  272. else {
  273. debug("The cache file seems too small or stale");
  274. # Escalate the lock to exclusive, so we can write to it.
  275. if (flock($fp, 2) ) { # LOCK_EX
  276. # We might have blocked while waiting for that LOCK_EX, and
  277. # another process ran and updated it. Let's see if we can just
  278. # return the data now:
  279. if (filesize($cache_file) > 0
  280. && filectime($cache_file) + ($poll_time/2) > time()
  281. && ($arr = file($cache_file))
  282. ) {# The cache file is good to use.
  283. debug("Using the cache file");
  284. fclose($fp);
  285. return $arr[0];
  286. }
  287. ftruncate($fp, 0); # Now it's ready for writing later.
  288. }
  289. }
  290. }
  291. else {
  292. debug("Couldn't lock the cache file, ignoring it.");
  293. $fp = null;
  294. }
  295. }
  296. }
  297. else {
  298. $fp = null;
  299. debug("Couldn't open the cache file");
  300. }
  301. # Set up variables.
  302. $status = array( # Holds the result of SHOW STATUS, SHOW INNODB STATUS, etc
  303. # Define some indexes so they don't cause errors with += operations.
  304. 'relay_log_space' => null,
  305. 'binary_log_space' => null,
  306. 'current_transactions' => null,
  307. 'locked_transactions' => null,
  308. 'active_transactions' => null,
  309. 'innodb_locked_tables' => null,
  310. 'innodb_tables_in_use' => null,
  311. 'innodb_lock_structs' => null,
  312. 'innodb_lock_wait_secs' => null,
  313. 'innodb_sem_waits' => null,
  314. 'innodb_sem_wait_time_ms'=> null,
  315. # Values for the 'state' column from SHOW PROCESSLIST (converted to
  316. # lowercase, with spaces replaced by underscores)
  317. 'State_closing_tables' => null,
  318. 'State_copying_to_tmp_table' => null,
  319. 'State_end' => null,
  320. 'State_freeing_items' => null,
  321. 'State_init' => null,
  322. 'State_locked' => null,
  323. 'State_login' => null,
  324. 'State_preparing' => null,
  325. 'State_reading_from_net' => null,
  326. 'State_sending_data' => null,
  327. 'State_sorting_result' => null,
  328. 'State_statistics' => null,
  329. 'State_updating' => null,
  330. 'State_writing_to_net' => null,
  331. 'State_none' => null,
  332. 'State_other' => null, # Everything not listed above
  333. );
  334. # Get SHOW STATUS and convert the name-value array into a simple
  335. # associative array.
  336. $result = run_query("SHOW /*!50002 GLOBAL */ STATUS", $conn);
  337. foreach ( $result as $row ) {
  338. $status[$row[0]] = $row[1];
  339. }
  340. # Get SHOW VARIABLES and do the same thing, adding it to the $status array.
  341. $result = run_query("SHOW VARIABLES", $conn);
  342. foreach ( $result as $row ) {
  343. $status[$row[0]] = $row[1];
  344. }
  345. # Get SHOW SLAVE STATUS, and add it to the $status array.
  346. if ($chk_options['slave'] ) {
  347. $result = run_query("SHOW SLAVE STATUS", $conn);
  348. $slave_status_rows_gotten = 0;
  349. foreach ( $result as $row ) {
  350. $slave_status_rows_gotten++;
  351. # Must lowercase keys because different MySQL versions have different
  352. # lettercase.
  353. $row = array_change_key_case($row, CASE_LOWER);
  354. $status['relay_log_space'] = $row['relay_log_space'];
  355. $status['slave_lag'] = $row['seconds_behind_master'];
  356. # Check replication heartbeat, if present.
  357. if ($heartbeat ) {
  358. $result2 = run_query(
  359. "SELECT GREATEST(0, UNIX_TIMESTAMP() - UNIX_TIMESTAMP(ts) - 1)"
  360. . " AS delay FROM $heartbeat WHERE id = 1", $conn);
  361. $slave_delay_rows_gotten = 0;
  362. foreach ( $result2 as $row2 ) {
  363. $slave_delay_rows_gotten++;
  364. if ($row2 && is_array($row2)
  365. && array_key_exists('delay', $row2) )
  366. {
  367. $status['slave_lag'] = $row2['delay'];
  368. }
  369. else {
  370. debug("Couldn't get slave lag from $heartbeat");
  371. }
  372. }
  373. if ($slave_delay_rows_gotten == 0 ) {
  374. debug("Got nothing from heartbeat query");
  375. }
  376. }
  377. # Scale slave_running and slave_stopped relative to the slave lag.
  378. $status['slave_running'] = ($row['slave_sql_running'] == 'Yes')
  379. ? $status['slave_lag'] : 0;
  380. $status['slave_stopped'] = ($row['slave_sql_running'] == 'Yes')
  381. ? 0 : $status['slave_lag'];
  382. }
  383. if ($slave_status_rows_gotten == 0 ) {
  384. debug("Got nothing from SHOW SLAVE STATUS");
  385. }
  386. }
  387. # Get SHOW MASTER STATUS, and add it to the $status array.
  388. if ($chk_options['master']
  389. && array_key_exists('log_bin', $status)
  390. && $status['log_bin'] == 'ON'
  391. ) { # See issue #8
  392. $binlogs = array(0);
  393. $result = run_query("SHOW MASTER LOGS", $conn);
  394. foreach ( $result as $row ) {
  395. $row = array_change_key_case($row, CASE_LOWER);
  396. # Older versions of MySQL may not have the File_size column in the
  397. # results of the command. Zero-size files indicate the user is
  398. # deleting binlogs manually from disk (bad user! bad!).
  399. if (array_key_exists('file_size', $row) && $row['file_size'] > 0 ) {
  400. $binlogs[] = $row['file_size'];
  401. }
  402. }
  403. if (count($binlogs)) {
  404. $status['binary_log_space'] = to_int(array_sum($binlogs));
  405. }
  406. }
  407. # Get SHOW PROCESSLIST and aggregate it by state, then add it to the array
  408. # too.
  409. if ($chk_options['procs'] ) {
  410. $result = run_query('SHOW PROCESSLIST', $conn);
  411. foreach ( $result as $row ) {
  412. $state = $row['State'];
  413. if (is_null($state) ) {
  414. $state = 'NULL';
  415. }
  416. if ($state == '' ) {
  417. $state = 'none';
  418. }
  419. $state = str_replace(' ', '_', strtolower($state));
  420. if (array_key_exists("State_$state", $status) ) {
  421. increment($status, "State_$state", 1);
  422. }
  423. else {
  424. increment($status, "State_other", 1);
  425. }
  426. }
  427. }
  428. # Get SHOW INNODB STATUS and extract the desired metrics from it, then add
  429. # those to the array too.
  430. if ($chk_options['innodb']
  431. && array_key_exists('have_innodb', $status)
  432. && $status['have_innodb'] == 'YES'
  433. ) {
  434. $result = run_query("SHOW /*!50000 ENGINE*/ INNODB STATUS", $conn);
  435. $istatus_text = $result[0]['Status'];
  436. $istatus_vals = get_innodb_array($istatus_text);
  437. # Override values from InnoDB parsing with values from SHOW STATUS,
  438. # because InnoDB status might not have everything and the SHOW STATUS is
  439. # to be preferred where possible.
  440. $overrides = array(
  441. 'Innodb_buffer_pool_pages_data' => 'database_pages',
  442. 'Innodb_buffer_pool_pages_dirty' => 'modified_pages',
  443. 'Innodb_buffer_pool_pages_free' => 'free_pages',
  444. 'Innodb_buffer_pool_pages_total' => 'pool_size',
  445. 'Innodb_data_fsyncs' => 'file_fsyncs',
  446. 'Innodb_data_pending_reads' => 'pending_normal_aio_reads',
  447. 'Innodb_data_pending_writes' => 'pending_normal_aio_writes',
  448. 'Innodb_os_log_pending_fsyncs' => 'pending_log_flushes',
  449. 'Innodb_pages_created' => 'pages_created',
  450. 'Innodb_pages_read' => 'pages_read',
  451. 'Innodb_pages_written' => 'pages_written',
  452. 'Innodb_rows_deleted' => 'rows_deleted',
  453. 'Innodb_rows_inserted' => 'rows_inserted',
  454. 'Innodb_rows_read' => 'rows_read',
  455. 'Innodb_rows_updated' => 'rows_updated',
  456. );
  457. # If the SHOW STATUS value exists, override...
  458. foreach ( $overrides as $key => $val ) {
  459. if (array_key_exists($key, $status) ) {
  460. debug("Override $key");
  461. $istatus_vals[$val] = $status[$key];
  462. }
  463. }
  464. # Now copy the values into $status.
  465. foreach ( $istatus_vals as $key => $val ) {
  466. $status[$key] = $istatus_vals[$key];
  467. }
  468. }
  469. # Make table_open_cache backwards-compatible (issue 63).
  470. if (array_key_exists('table_open_cache', $status) ) {
  471. $status['table_cache'] = $status['table_open_cache'];
  472. }
  473. # Compute how much of the key buffer is used and unflushed (issue 127).
  474. $status['Key_buf_bytes_used']
  475. = big_sub($status['key_buffer_size'],
  476. big_multiply($status['Key_blocks_unused'],
  477. $status['key_cache_block_size']));
  478. $status['Key_buf_bytes_unflushed']
  479. = big_multiply($status['Key_blocks_not_flushed'],
  480. $status['key_cache_block_size']);
  481. if (array_key_exists('unflushed_log', $status)
  482. && $status['unflushed_log']
  483. ) {
  484. # TODO: I'm not sure what the deal is here; need to debug this. But the
  485. # unflushed log bytes spikes a lot sometimes and it's impossible for it to
  486. # be more than the log buffer.
  487. debug("Unflushed log: $status[unflushed_log]");
  488. $status['unflushed_log']
  489. = max($status['unflushed_log'], $status['innodb_log_buffer_size']);
  490. }
  491. # Define the variables to output. I use shortened variable names so maybe
  492. # it'll all fit in 1024 bytes for Cactid and Spine's benefit. This list must
  493. # come right after the word MAGIC_VARS_DEFINITIONS. The Perl script parses
  494. # it and uses it as a Perl variable.
  495. $keys = array(
  496. 'Key_read_requests' => 'a0',
  497. 'Key_reads' => 'a1',
  498. 'Key_write_requests' => 'a2',
  499. 'Key_writes' => 'a3',
  500. 'history_list' => 'a4',
  501. 'innodb_transactions' => 'a5',
  502. 'read_views' => 'a6',
  503. 'current_transactions' => 'a7',
  504. 'locked_transactions' => 'a8',
  505. 'active_transactions' => 'a9',
  506. 'pool_size' => 'aa',
  507. 'free_pages' => 'ab',
  508. 'database_pages' => 'ac',
  509. 'modified_pages' => 'ad',
  510. 'pages_read' => 'ae',
  511. 'pages_created' => 'af',
  512. 'pages_written' => 'ag',
  513. 'file_fsyncs' => 'ah',
  514. 'file_reads' => 'ai',
  515. 'file_writes' => 'aj',
  516. 'log_writes' => 'ak',
  517. 'pending_aio_log_ios' => 'al',
  518. 'pending_aio_sync_ios' => 'am',
  519. 'pending_buf_pool_flushes' => 'an',
  520. 'pending_chkp_writes' => 'ao',
  521. 'pending_ibuf_aio_reads' => 'ap',
  522. 'pending_log_flushes' => 'aq',
  523. 'pending_log_writes' => 'ar',
  524. 'pending_normal_aio_reads' => 'as',
  525. 'pending_normal_aio_writes' => 'at',
  526. 'ibuf_inserts' => 'au',
  527. 'ibuf_merged' => 'av',
  528. 'ibuf_merges' => 'aw',
  529. 'spin_waits' => 'ax',
  530. 'spin_rounds' => 'ay',
  531. 'os_waits' => 'az',
  532. 'rows_inserted' => 'b0',
  533. 'rows_updated' => 'b1',
  534. 'rows_deleted' => 'b2',
  535. 'rows_read' => 'b3',
  536. 'Table_locks_waited' => 'b4',
  537. 'Table_locks_immediate' => 'b5',
  538. 'Slow_queries' => 'b6',
  539. 'Open_files' => 'b7',
  540. 'Open_tables' => 'b8',
  541. 'Opened_tables' => 'b9',
  542. 'innodb_open_files' => 'ba',
  543. 'open_files_limit' => 'bb',
  544. 'table_cache' => 'bc',
  545. 'Aborted_clients' => 'bd',
  546. 'Aborted_connects' => 'be',
  547. 'Max_used_connections' => 'bf',
  548. 'Slow_launch_threads' => 'bg',
  549. 'Threads_cached' => 'bh',
  550. 'Threads_connected' => 'bi',
  551. 'Threads_created' => 'bj',
  552. 'Threads_running' => 'bk',
  553. 'max_connections' => 'bl',
  554. 'thread_cache_size' => 'bm',
  555. 'Connections' => 'bn',
  556. 'slave_running' => 'bo',
  557. 'slave_stopped' => 'bp',
  558. 'Slave_retried_transactions' => 'bq',
  559. 'slave_lag' => 'br',
  560. 'Slave_open_temp_tables' => 'bs',
  561. 'Qcache_free_blocks' => 'bt',
  562. 'Qcache_free_memory' => 'bu',
  563. 'Qcache_hits' => 'bv',
  564. 'Qcache_inserts' => 'bw',
  565. 'Qcache_lowmem_prunes' => 'bx',
  566. 'Qcache_not_cached' => 'by',
  567. 'Qcache_queries_in_cache' => 'bz',
  568. 'Qcache_total_blocks' => 'c0',
  569. 'query_cache_size' => 'c1',
  570. 'Questions' => 'c2',
  571. 'Com_update' => 'c3',
  572. 'Com_insert' => 'c4',
  573. 'Com_select' => 'c5',
  574. 'Com_delete' => 'c6',
  575. 'Com_replace' => 'c7',
  576. 'Com_load' => 'c8',
  577. 'Com_update_multi' => 'c9',
  578. 'Com_insert_select' => 'ca',
  579. 'Com_delete_multi' => 'cb',
  580. 'Com_replace_select' => 'cc',
  581. 'Select_full_join' => 'cd',
  582. 'Select_full_range_join' => 'ce',
  583. 'Select_range' => 'cf',
  584. 'Select_range_check' => 'cg',
  585. 'Select_scan' => 'ch',
  586. 'Sort_merge_passes' => 'ci',
  587. 'Sort_range' => 'cj',
  588. 'Sort_rows' => 'ck',
  589. 'Sort_scan' => 'cl',
  590. 'Created_tmp_tables' => 'cm',
  591. 'Created_tmp_disk_tables' => 'cn',
  592. 'Created_tmp_files' => 'co',
  593. 'Bytes_sent' => 'cp',
  594. 'Bytes_received' => 'cq',
  595. 'innodb_log_buffer_size' => 'cr',
  596. 'unflushed_log' => 'cs',
  597. 'log_bytes_flushed' => 'ct',
  598. 'log_bytes_written' => 'cu',
  599. 'relay_log_space' => 'cv',
  600. 'binlog_cache_size' => 'cw',
  601. 'Binlog_cache_disk_use' => 'cx',
  602. 'Binlog_cache_use' => 'cy',
  603. 'binary_log_space' => 'cz',
  604. 'innodb_locked_tables' => 'd0',
  605. 'innodb_lock_structs' => 'd1',
  606. 'State_closing_tables' => 'd2',
  607. 'State_copying_to_tmp_table' => 'd3',
  608. 'State_end' => 'd4',
  609. 'State_freeing_items' => 'd5',
  610. 'State_init' => 'd6',
  611. 'State_locked' => 'd7',
  612. 'State_login' => 'd8',
  613. 'State_preparing' => 'd9',
  614. 'State_reading_from_net' => 'da',
  615. 'State_sending_data' => 'db',
  616. 'State_sorting_result' => 'dc',
  617. 'State_statistics' => 'dd',
  618. 'State_updating' => 'de',
  619. 'State_writing_to_net' => 'df',
  620. 'State_none' => 'dg',
  621. 'State_other' => 'dh',
  622. 'Handler_commit' => 'di',
  623. 'Handler_delete' => 'dj',
  624. 'Handler_discover' => 'dk',
  625. 'Handler_prepare' => 'dl',
  626. 'Handler_read_first' => 'dm',
  627. 'Handler_read_key' => 'dn',
  628. 'Handler_read_next' => 'do',
  629. 'Handler_read_prev' => 'dp',
  630. 'Handler_read_rnd' => 'dq',
  631. 'Handler_read_rnd_next' => 'dr',
  632. 'Handler_rollback' => 'ds',
  633. 'Handler_savepoint' => 'dt',
  634. 'Handler_savepoint_rollback' => 'du',
  635. 'Handler_update' => 'dv',
  636. 'Handler_write' => 'dw',
  637. # Some InnoDB stats added later...
  638. 'innodb_tables_in_use' => 'dx',
  639. 'innodb_lock_wait_secs' => 'dy',
  640. 'hash_index_cells_total' => 'dz',
  641. 'hash_index_cells_used' => 'e0',
  642. 'total_mem_alloc' => 'e1',
  643. 'additional_pool_alloc' => 'e2',
  644. 'uncheckpointed_bytes' => 'e3',
  645. 'ibuf_used_cells' => 'e4',
  646. 'ibuf_free_cells' => 'e5',
  647. 'ibuf_cell_count' => 'e6',
  648. 'adaptive_hash_memory' => 'e7',
  649. 'page_hash_memory' => 'e8',
  650. 'dictionary_cache_memory' => 'e9',
  651. 'file_system_memory' => 'ea',
  652. 'lock_system_memory' => 'eb',
  653. 'recovery_system_memory' => 'ec',
  654. 'thread_hash_memory' => 'ed',
  655. 'innodb_sem_waits' => 'ee',
  656. 'innodb_sem_wait_time_ms' => 'ef',
  657. 'Key_buf_bytes_unflushed' => 'eg',
  658. 'Key_buf_bytes_used' => 'eh',
  659. 'key_buffer_size' => 'ei',
  660. 'Innodb_row_lock_time' => 'ej',
  661. 'Innodb_row_lock_waits' => 'ek',
  662. );
  663. # Return the output.
  664. $output = array();
  665. foreach ($keys as $key => $short ) {
  666. # If the value isn't defined, return -1 which is lower than (most graphs')
  667. # minimum value of 0, so it'll be regarded as a missing value.
  668. $val = isset($status[$key]) ? $status[$key] : -1;
  669. $output[] = "$short:$val";
  670. }
  671. $result = implode(' ', $output);
  672. if ($fp ) {
  673. if (fwrite($fp, $result) === FALSE ) {
  674. die("Can't write '$cache_file'");
  675. }
  676. fclose($fp);
  677. }
  678. return $result;
  679. }
  680. # ============================================================================
  681. # Given INNODB STATUS text, returns a key-value array of the parsed text. Each
  682. # line shows a sample of the input for both standard InnoDB as you would find in
  683. # MySQL 5.0, and XtraDB or enhanced InnoDB from Percona if applicable. Note
  684. # that extra leading spaces are ignored due to trim().
  685. # ============================================================================
  686. function get_innodb_array($text) {
  687. $results = array(
  688. 'spin_waits' => array(),
  689. 'spin_rounds' => array(),
  690. 'os_waits' => array(),
  691. 'pending_normal_aio_reads' => null,
  692. 'pending_normal_aio_writes' => null,
  693. 'pending_ibuf_aio_reads' => null,
  694. 'pending_aio_log_ios' => null,
  695. 'pending_aio_sync_ios' => null,
  696. 'pending_log_flushes' => null,
  697. 'pending_buf_pool_flushes' => null,
  698. 'file_reads' => null,
  699. 'file_writes' => null,
  700. 'file_fsyncs' => null,
  701. 'ibuf_inserts' => null,
  702. 'ibuf_merged' => null,
  703. 'ibuf_merges' => null,
  704. 'log_bytes_written' => null,
  705. 'unflushed_log' => null,
  706. 'log_bytes_flushed' => null,
  707. 'pending_log_writes' => null,
  708. 'pending_chkp_writes' => null,
  709. 'log_writes' => null,
  710. 'pool_size' => null,
  711. 'free_pages' => null,
  712. 'database_pages' => null,
  713. 'modified_pages' => null,
  714. 'pages_read' => null,
  715. 'pages_created' => null,
  716. 'pages_written' => null,
  717. 'queries_inside' => null,
  718. 'queries_queued' => null,
  719. 'read_views' => null,
  720. 'rows_inserted' => null,
  721. 'rows_updated' => null,
  722. 'rows_deleted' => null,
  723. 'rows_read' => null,
  724. 'innodb_transactions' => null,
  725. 'unpurged_txns' => null,
  726. 'history_list' => null,
  727. 'current_transactions' => null,
  728. 'hash_index_cells_total' => null,
  729. 'hash_index_cells_used' => null,
  730. 'total_mem_alloc' => null,
  731. 'additional_pool_alloc' => null,
  732. 'last_checkpoint' => null,
  733. 'uncheckpointed_bytes' => null,
  734. 'ibuf_used_cells' => null,
  735. 'ibuf_free_cells' => null,
  736. 'ibuf_cell_count' => null,
  737. 'adaptive_hash_memory' => null,
  738. 'page_hash_memory' => null,
  739. 'dictionary_cache_memory' => null,
  740. 'file_system_memory' => null,
  741. 'lock_system_memory' => null,
  742. 'recovery_system_memory' => null,
  743. 'thread_hash_memory' => null,
  744. 'innodb_sem_waits' => null,
  745. 'innodb_sem_wait_time_ms' => null,
  746. );
  747. $txn_seen = FALSE;
  748. foreach ( explode("\n", $text) as $line ) {
  749. $line = trim($line);
  750. $row = preg_split('/ +/', $line);
  751. # SEMAPHORES
  752. if (strpos($line, 'Mutex spin waits') === 0 ) {
  753. # Mutex spin waits 79626940, rounds 157459864, OS waits 698719
  754. # Mutex spin waits 0, rounds 247280272495, OS waits 316513438
  755. $results['spin_waits'][] = to_int($row[3]);
  756. $results['spin_rounds'][] = to_int($row[5]);
  757. $results['os_waits'][] = to_int($row[8]);
  758. }
  759. elseif (strpos($line, 'RW-shared spins') === 0 ) {
  760. # RW-shared spins 3859028, OS waits 2100750; RW-excl spins 4641946, OS waits 1530310
  761. $results['spin_waits'][] = to_int($row[2]);
  762. $results['spin_waits'][] = to_int($row[8]);
  763. $results['os_waits'][] = to_int($row[5]);
  764. $results['os_waits'][] = to_int($row[11]);
  765. }
  766. elseif (strpos($line, 'seconds the semaphore:') > 0) {
  767. # --Thread 907205 has waited at handler/ha_innodb.cc line 7156 for 1.00 seconds the semaphore:
  768. increment($results, 'innodb_sem_waits', 1);
  769. increment($results,
  770. 'innodb_sem_wait_time_ms', to_int($row[9]) * 1000);
  771. }
  772. # TRANSACTIONS
  773. elseif (strpos($line, 'Trx id counter') === 0 ) {
  774. # The beginning of the TRANSACTIONS section: start counting
  775. # transactions
  776. # Trx id counter 0 1170664159
  777. # Trx id counter 861B144C
  778. $results['innodb_transactions'] = make_bigint($row[3], $row[4]);
  779. $txn_seen = TRUE;
  780. }
  781. elseif (strpos($line, 'Purge done for trx') === 0 ) {
  782. # Purge done for trx's n:o < 0 1170663853 undo n:o < 0 0
  783. # Purge done for trx's n:o < 861B135D undo n:o < 0
  784. $purged_to = make_bigint($row[6], $row[7] == 'undo' ? null : $row[7]);
  785. $results['unpurged_txns']
  786. = big_sub($results['innodb_transactions'], $purged_to);
  787. }
  788. elseif (strpos($line, 'History list length') === 0 ) {
  789. # History list length 132
  790. $results['history_list'] = to_int($row[3]);
  791. }
  792. elseif ($txn_seen && strpos($line, '---TRANSACTION') === 0 ) {
  793. # ---TRANSACTION 0, not started, process no 13510, OS thread id 1170446656
  794. increment($results, 'current_transactions', 1);
  795. if (strpos($line, 'ACTIVE') > 0 ) {
  796. increment($results, 'active_transactions', 1);
  797. }
  798. }
  799. elseif ($txn_seen && strpos($line, '------- TRX HAS BEEN') === 0 ) {
  800. # ------- TRX HAS BEEN WAITING 32 SEC FOR THIS LOCK TO BE GRANTED:
  801. increment($results, 'innodb_lock_wait_secs', to_int($row[5]));
  802. }
  803. elseif (strpos($line, 'read views open inside InnoDB') > 0 ) {
  804. # 1 read views open inside InnoDB
  805. $results['read_views'] = to_int($row[0]);
  806. }
  807. elseif (strpos($line, 'mysql tables in use') === 0 ) {
  808. # mysql tables in use 2, locked 2
  809. increment($results, 'innodb_tables_in_use', to_int($row[4]));
  810. increment($results, 'innodb_locked_tables', to_int($row[6]));
  811. }
  812. elseif ($txn_seen && strpos($line, 'lock struct(s)') > 0 ) {
  813. # 23 lock struct(s), heap size 3024, undo log entries 27
  814. # LOCK WAIT 12 lock struct(s), heap size 3024, undo log entries 5
  815. # LOCK WAIT 2 lock struct(s), heap size 368
  816. if (strpos($line, 'LOCK WAIT') === 0 ) {
  817. increment($results, 'innodb_lock_structs', to_int($row[2]));
  818. increment($results, 'locked_transactions', 1);
  819. }
  820. else {
  821. increment($results, 'innodb_lock_structs', to_int($row[0]));
  822. }
  823. }
  824. # FILE I/O
  825. elseif (strpos($line, ' OS file reads, ') > 0 ) {
  826. # 8782182 OS file reads, 15635445 OS file writes, 947800 OS fsyncs
  827. $results['file_reads'] = to_int($row[0]);
  828. $results['file_writes'] = to_int($row[4]);
  829. $results['file_fsyncs'] = to_int($row[8]);
  830. }
  831. elseif (strpos($line, 'Pending normal aio reads:') === 0 ) {
  832. # Pending normal aio reads: 0, aio writes: 0,
  833. $results['pending_normal_aio_reads'] = to_int($row[4]);
  834. $results['pending_normal_aio_writes'] = to_int($row[7]);
  835. }
  836. elseif (strpos($line, 'ibuf aio reads') === 0 ) {
  837. # ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0
  838. $results['pending_ibuf_aio_reads'] = to_int($row[3]);
  839. $results['pending_aio_log_ios'] = to_int($row[6]);
  840. $results['pending_aio_sync_ios'] = to_int($row[9]);
  841. }
  842. elseif (strpos($line, 'Pending flushes (fsync)') === 0 ) {
  843. # Pending flushes (fsync) log: 0; buffer pool: 0
  844. $results['pending_log_flushes'] = to_int($row[4]);
  845. $results['pending_buf_pool_flushes'] = to_int($row[7]);
  846. }
  847. # INSERT BUFFER AND ADAPTIVE HASH INDEX
  848. elseif (strpos($line, 'Ibuf for space 0: size ') === 0 ) {
  849. # Older InnoDB code seemed to be ready for an ibuf per tablespace. It
  850. # had two lines in the output. Newer has just one line, see below.
  851. # Ibuf for space 0: size 1, free list len 887, seg size 889, is not empty
  852. # Ibuf for space 0: size 1, free list len 887, seg size 889,
  853. $results['ibuf_used_cells'] = to_int($row[5]);
  854. $results['ibuf_free_cells'] = to_int($row[9]);
  855. $results['ibuf_cell_count'] = to_int($row[12]);
  856. }
  857. elseif (strpos($line, 'Ibuf: size ') === 0 ) {
  858. # Ibuf: size 1, free list len 4634, seg size 4636,
  859. $results['ibuf_used_cells'] = to_int($row[2]);
  860. $results['ibuf_free_cells'] = to_int($row[6]);
  861. $results['ibuf_cell_count'] = to_int($row[9]);
  862. }
  863. elseif (strpos($line, ' merged recs, ') > 0 ) {
  864. # 19817685 inserts, 19817684 merged recs, 3552620 merges
  865. $results['ibuf_inserts'] = to_int($row[0]);
  866. $results['ibuf_merged'] = to_int($row[2]);
  867. $results['ibuf_merges'] = to_int($row[5]);
  868. }
  869. elseif (strpos($line, 'Hash table size ') === 0 ) {
  870. # In some versions of InnoDB, the used cells is omitted.
  871. # Hash table size 4425293, used cells 4229064, ....
  872. # Hash table size 57374437, node heap has 72964 buffer(s) <-- no used cells
  873. $results['hash_index_cells_total'] = to_int($row[3]);
  874. $results['hash_index_cells_used']
  875. = strpos($line, 'used cells') > 0 ? to_int($row[6]) : '0';
  876. }
  877. # LOG
  878. elseif (strpos($line, " log i/o's done, ") > 0 ) {
  879. # 3430041 log i/o's done, 17.44 log i/o's/second
  880. # 520835887 log i/o's done, 17.28 log i/o's/second, 518724686 syncs, 2980893 checkpoints
  881. # TODO: graph syncs and checkpoints
  882. $results['log_writes'] = to_int($row[0]);
  883. }
  884. elseif (strpos($line, " pending log writes, ") > 0 ) {
  885. # 0 pending log writes, 0 pending chkp writes
  886. $results['pending_log_writes'] = to_int($row[0]);
  887. $results['pending_chkp_writes'] = to_int($row[4]);
  888. }
  889. elseif (strpos($line, "Log sequence number") === 0 ) {
  890. # This number is NOT printed in hex in InnoDB plugin.
  891. # Log sequence number 13093949495856 //plugin
  892. # Log sequence number 125 3934414864 //normal
  893. $results['log_bytes_written']
  894. = isset($row[4])
  895. ? make_bigint($row[3], $row[4])
  896. : to_int($row[3]);
  897. }
  898. elseif (strpos($line, "Log flushed up to") === 0 ) {
  899. # This number is NOT printed in hex in InnoDB plugin.
  900. # Log flushed up to 13093948219327
  901. # Log flushed up to 125 3934414864
  902. $results['log_bytes_flushed']
  903. = isset($row[5])
  904. ? make_bigint($row[4], $row[5])
  905. : to_int($row[4]);
  906. }
  907. elseif (strpos($line, "Last checkpoint at") === 0 ) {
  908. # Last checkpoint at 125 3934293461
  909. $results['last_checkpoint']
  910. = isset($row[4])
  911. ? make_bigint($row[3], $row[4])
  912. : to_int($row[3]);
  913. }
  914. # BUFFER POOL AND MEMORY
  915. elseif (strpos($line, "Total memory allocated") === 0 ) {
  916. # Total memory allocated 29642194944; in additional pool allocated 0
  917. $results['total_mem_alloc'] = to_int($row[3]);
  918. $results['additional_pool_alloc'] = to_int($row[8]);
  919. }
  920. elseif (strpos($line, 'Adaptive hash index ') === 0 ) {
  921. # Adaptive hash index 1538240664 (186998824 + 1351241840)
  922. $results['adaptive_hash_memory'] = to_int($row[3]);
  923. }
  924. elseif (strpos($line, 'Page hash ') === 0 ) {
  925. # Page hash 11688584
  926. $results['page_hash_memory'] = to_int($row[2]);
  927. }
  928. elseif (strpos($line, 'Dictionary cache ') === 0 ) {
  929. # Dictionary cache 145525560 (140250984 + 5274576)
  930. $results['dictionary_cache_memory'] = to_int($row[2]);
  931. }
  932. elseif (strpos($line, 'File system ') === 0 ) {
  933. # File system 313848 (82672 + 231176)
  934. $results['file_system_memory'] = to_int($row[2]);
  935. }
  936. elseif (strpos($line, 'Lock system ') === 0 ) {
  937. # Lock system 29232616 (29219368 + 13248)
  938. $results['lock_system_memory'] = to_int($row[2]);
  939. }
  940. elseif (strpos($line, 'Recovery system ') === 0 ) {
  941. # Recovery system 0 (0 + 0)
  942. $results['recovery_system_memory'] = to_int($row[2]);
  943. }
  944. elseif (strpos($line, 'Threads ') === 0 ) {
  945. # Threads 409336 (406936 + 2400)
  946. $results['thread_hash_memory'] = to_int($row[1]);
  947. }
  948. elseif (strpos($line, 'innodb_io_pattern ') === 0 ) {
  949. # innodb_io_pattern 0 (0 + 0)
  950. $results['innodb_io_pattern_memory'] = to_int($row[1]);
  951. }
  952. elseif (strpos($line, "Buffer pool size ") === 0 ) {
  953. # The " " after size is necessary to avoid matching the wrong line:
  954. # Buffer pool size 1769471
  955. # Buffer pool size, bytes 28991012864
  956. $results['pool_size'] = to_int($row[3]);
  957. }
  958. elseif (strpos($line, "Free buffers") === 0 ) {
  959. # Free buffers 0
  960. $results['free_pages'] = to_int($row[2]);
  961. }
  962. elseif (strpos($line, "Database pages") === 0 ) {
  963. # Database pages 1696503
  964. $results['database_pages'] = to_int($row[2]);
  965. }
  966. elseif (strpos($line, "Modified db pages") === 0 ) {
  967. # Modified db pages 160602
  968. $results['modified_pages'] = to_int($row[3]);
  969. }
  970. elseif (strpos($line, "Pages read ahead") === 0 ) {
  971. # Must do this BEFORE the next test, otherwise it'll get fooled by this
  972. # line from the new plugin (see samples/innodb-015.txt):
  973. # Pages read ahead 0.00/s, evicted without access 0.06/s
  974. # TODO: No-op for now, see issue 134.
  975. }
  976. elseif (strpos($line, "Pages read") === 0 ) {
  977. # Pages read 15240822, created 1770238, written 21705836
  978. $results['pages_read'] = to_int($row[2]);
  979. $results['pages_created'] = to_int($row[4]);
  980. $results['pages_written'] = to_int($row[6]);
  981. }
  982. # ROW OPERATIONS
  983. elseif (strpos($line, 'Number of rows inserted') === 0 ) {
  984. # Number of rows inserted 50678311, updated 66425915, deleted 20605903, read 454561562
  985. $results['rows_inserted'] = to_int($row[4]);
  986. $results['rows_updated'] = to_int($row[6]);
  987. $results['rows_deleted'] = to_int($row[8]);
  988. $results['rows_read'] = to_int($row[10]);
  989. }
  990. elseif (strpos($line, " queries inside InnoDB, ") > 0 ) {
  991. # 0 queries inside InnoDB, 0 queries in queue
  992. $results['queries_inside'] = to_int($row[0]);
  993. $results['queries_queued'] = to_int($row[4]);
  994. }
  995. }
  996. foreach ( array('spin_waits', 'spin_rounds', 'os_waits') as $key ) {
  997. $results[$key] = to_int(array_sum($results[$key]));
  998. }
  999. $results['unflushed_log']
  1000. = big_sub($results['log_bytes_written'], $results['log_bytes_flushed']);
  1001. $results['uncheckpointed_bytes']
  1002. = big_sub($results['log_bytes_written'], $results['last_checkpoint']);
  1003. # foreach ($results as $key => $value) {
  1004. # echo(strtolower($key).":".strtolower($value)."\n");
  1005. # }
  1006. return $results;
  1007. }
  1008. # ============================================================================
  1009. # Returns a bigint from two ulint or a single hex number. This is tested in
  1010. # t/mysql_stats.php and copied, without tests, to ss_get_by_ssh.php.
  1011. # ============================================================================
  1012. function make_bigint ($hi, $lo = null) {
  1013. debug(array($hi, $lo));
  1014. if (is_null($lo) ) {
  1015. # Assume it is a hex string representation.
  1016. return base_convert($hi, 16, 10);
  1017. }
  1018. else {
  1019. $hi = $hi ? $hi : '0'; # Handle empty-string or whatnot
  1020. $lo = $lo ? $lo : '0';
  1021. return big_add(big_multiply($hi, 4294967296), $lo);
  1022. }
  1023. }
  1024. # ============================================================================
  1025. # Extracts the numbers from a string. You can't reliably do this by casting to
  1026. # an int, because numbers that are bigger than PHP's int (varies by platform)
  1027. # will be truncated. And you can't use sprintf(%u) either, because the maximum
  1028. # value that will return on some platforms is 4022289582. So this just handles
  1029. # them as a string instead. It extracts digits until it finds a non-digit and
  1030. # quits. This is tested in t/mysql_stats.php and copied, without tests, to
  1031. # ss_get_by_ssh.php.
  1032. # ============================================================================
  1033. function to_int ( $str ) {
  1034. debug($str);
  1035. global $debug;
  1036. preg_match('{(\d+)}', $str, $m);
  1037. if (isset($m[1]) ) {
  1038. return $m[1];
  1039. }
  1040. elseif ($debug ) {
  1041. print_r(debug_backtrace());
  1042. }
  1043. else {
  1044. return 0;
  1045. }
  1046. }
  1047. # ============================================================================
  1048. # Wrap mysql_query in error-handling, and instead of returning the result,
  1049. # return an array of arrays in the result.
  1050. # ============================================================================
  1051. function run_query($sql, $conn) {
  1052. global $debug;
  1053. debug($sql);
  1054. $result = @mysql_query($sql, $conn);
  1055. if ($debug ) {
  1056. $error = @mysql_error($conn);
  1057. if ($error ) {
  1058. debug(array($sql, $error));
  1059. die("SQLERR $error in $sql");
  1060. }
  1061. }
  1062. $array = array();
  1063. while ( $row = @mysql_fetch_array($result) ) {
  1064. $array[] = $row;
  1065. }
  1066. debug(array($sql, $array));
  1067. return $array;
  1068. }
  1069. # ============================================================================
  1070. # Safely increments a value that might be null.
  1071. # ============================================================================
  1072. function increment(&$arr, $key, $howmuch) {
  1073. debug(array($key, $howmuch));
  1074. if (array_key_exists($key, $arr) && isset($arr[$key]) ) {
  1075. $arr[$key] = big_add($arr[$key], $howmuch);
  1076. }
  1077. else {
  1078. $arr[$key] = $howmuch;
  1079. }
  1080. }
  1081. # ============================================================================
  1082. # Multiply two big integers together as accurately as possible with reasonable
  1083. # effort. This is tested in t/mysql_stats.php and copied, without tests, to
  1084. # ss_get_by_ssh.php. $force is for testability.
  1085. # ============================================================================
  1086. function big_multiply ($left, $right, $force = null) {
  1087. if (function_exists("gmp_mul") && (is_null($force) || $force == 'gmp') ) {
  1088. debug(array('gmp_mul', $left, $right));
  1089. return gmp_strval( gmp_mul( $left, $right ));
  1090. }
  1091. elseif (function_exists("bcmul") && (is_null($force) || $force == 'bc') ) {
  1092. debug(array('bcmul', $left, $right));
  1093. return bcmul( $left, $right );
  1094. }
  1095. else { # Or $force == 'something else'
  1096. debug(array('sprintf', $left, $right));
  1097. return sprintf("%.0f", $left * $right);
  1098. }
  1099. }
  1100. # ============================================================================
  1101. # Subtract two big integers as accurately as possible with reasonable effort.
  1102. # This is tested in t/mysql_stats.php and copied, without tests, to
  1103. # ss_get_by_ssh.php. $force is for testability.
  1104. # ============================================================================
  1105. function big_sub ($left, $right, $force = null) {
  1106. debug(array($left, $right));
  1107. if (is_null($left) ) { $left = 0; }
  1108. if (is_null($right) ) { $right = 0; }
  1109. if (function_exists("gmp_sub") && (is_null($force) || $force == 'gmp')) {
  1110. debug(array('gmp_sub', $left, $right));
  1111. return gmp_strval( gmp_sub( $left, $right ));
  1112. }
  1113. elseif (function_exists("bcsub") && (is_null($force) || $force == 'bc')) {
  1114. debug(array('bcsub', $left, $right));
  1115. return bcsub( $left, $right );
  1116. }
  1117. else { # Or $force == 'something else'
  1118. debug(array('to_int', $left, $right));
  1119. return to_int($left - $right);
  1120. }
  1121. }
  1122. # ============================================================================
  1123. # Add two big integers together as accurately as possible with reasonable
  1124. # effort. This is tested in t/mysql_stats.php and copied, without tests, to
  1125. # ss_get_by_ssh.php. $force is for testability.
  1126. # ============================================================================
  1127. function big_add ($left, $right, $force = null) {
  1128. if (is_null($left) ) { $left = 0; }
  1129. if (is_null($right) ) { $right = 0; }
  1130. if (function_exists("gmp_add") && (is_null($force) || $force == 'gmp')) {
  1131. debug(array('gmp_add', $left, $right));
  1132. return gmp_strval( gmp_add( $left, $right ));
  1133. }
  1134. elseif (function_exists("bcadd") && (is_null($force) || $force == 'bc')) {
  1135. debug(array('bcadd', $left, $right));
  1136. return bcadd( $left, $right );
  1137. }
  1138. else { # Or $force == 'something else'
  1139. debug(array('to_int', $left, $right));
  1140. return to_int($left + $right);
  1141. }
  1142. }
  1143. # ============================================================================
  1144. # Writes to a debugging log.
  1145. # ============================================================================
  1146. function debug($val) {
  1147. global $debug_log;
  1148. if (!$debug_log ) {
  1149. return;
  1150. }
  1151. if ($fp = fopen($debug_log, 'a+') ) {
  1152. $trace = debug_backtrace();
  1153. $calls = array();
  1154. $i = 0;
  1155. $line = 0;
  1156. $file = '';
  1157. foreach ( debug_backtrace() as $arr ) {
  1158. if ($i++ ) {
  1159. $calls[] = "$arr[function]() at $file:$line";
  1160. }
  1161. $line = array_key_exists('line', $arr) ? $arr['line'] : '?';
  1162. $file = array_key_exists('file', $arr) ? $arr['file'] : '?';
  1163. }
  1164. if (!count($calls) ) {
  1165. $calls[] = "at $file:$line";
  1166. }
  1167. fwrite($fp, date('Y-m-d h:i:s') . ' ' . implode(' <- ', $calls));
  1168. fwrite($fp, "\n" . var_export($val, TRUE) . "\n");
  1169. fclose($fp);
  1170. }
  1171. else { # Disable logging
  1172. print("Warning: disabling debug logging to $debug_log\n");
  1173. $debug_log = FALSE;
  1174. }
  1175. }
  1176. ?>