PageRenderTime 69ms CodeModel.GetById 20ms app.highlight 38ms RepoModel.GetById 1ms 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

Large files files are truncated, but you can click here to view the full file

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

Large files files are truncated, but you can click here to view the full file