ss_get_mysql_stats.php 59 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406
  1. <?php
  2. # ============================================================================
  3. # This program is part of Percona Monitoring Plugins
  4. # License: GPL License (see COPYING)
  5. # Copyright 2008-2016 Baron Schwartz, 2012-2016 Percona
  6. # Authors:
  7. # Baron Schwartz, Roman Vynar
  8. # ============================================================================
  9. # ============================================================================
  10. # To make this code testable, we need to prevent code from running when it is
  11. # included from the test script. The test script and this file have different
  12. # filenames, so we can compare them. In some cases $_SERVER['SCRIPT_FILENAME']
  13. # seems not to be defined, so we skip the check -- this check should certainly
  14. # pass in the test environment.
  15. # ============================================================================
  16. if ( !array_key_exists('SCRIPT_FILENAME', $_SERVER)
  17. || basename(__FILE__) == basename($_SERVER['SCRIPT_FILENAME']) ) {
  18. # ============================================================================
  19. # CONFIGURATION
  20. # ============================================================================
  21. # Define MySQL connection constants in config.php. Arguments explicitly passed
  22. # in from Cacti will override these. However, if you leave them blank in Cacti
  23. # and set them here, you can make life easier. Instead of defining parameters
  24. # here, you can define them in another file named the same as this file, with a
  25. # .cnf extension.
  26. # ============================================================================
  27. $mysql_user = 'cactiuser';
  28. $mysql_pass = 'cactiuser';
  29. $mysql_port = 3306;
  30. $mysql_socket = NULL;
  31. $mysql_flags = 0;
  32. $mysql_ssl = FALSE; # Whether to use SSL to connect to MySQL.
  33. $mysql_ssl_key = '/etc/pki/tls/certs/mysql/client-key.pem';
  34. $mysql_ssl_cert = '/etc/pki/tls/certs/mysql/client-cert.pem';
  35. $mysql_ssl_ca = '/etc/pki/tls/certs/mysql/ca-cert.pem';
  36. $mysql_connection_timeout = 5;
  37. $heartbeat = FALSE; # Whether to use pt-heartbeat table for repl. delay calculation.
  38. $heartbeat_utc = FALSE; # Whether pt-heartbeat is run with --utc option.
  39. $heartbeat_server_id = 0; # Server id to associate with a heartbeat. Leave 0 if no preference.
  40. $heartbeat_table = 'percona.heartbeat'; # db.tbl.
  41. $cache_dir = '/tmp'; # If set, this uses caching to avoid multiple calls.
  42. $poll_time = 300; # Adjust to match your polling interval.
  43. $timezone = null; # If not set, uses the system default. Example: "UTC"
  44. $chk_options = array (
  45. 'innodb' => true, # Do you want to check InnoDB statistics?
  46. 'master' => true, # Do you want to check binary logging?
  47. 'slave' => true, # Do you want to check slave status?
  48. 'procs' => true, # Do you want to check SHOW PROCESSLIST?
  49. 'get_qrt' => true, # Get query response times from Percona Server or MariaDB?
  50. );
  51. $use_ss = FALSE; # Whether to use the script server or not
  52. $debug = FALSE; # Define whether you want debugging behavior.
  53. $debug_log = FALSE; # If $debug_log is a filename, it'll be used.
  54. # ============================================================================
  55. # You should not need to change anything below this line.
  56. # ============================================================================
  57. $version = '1.1.7';
  58. # ============================================================================
  59. # Include settings from an external config file.
  60. # ============================================================================
  61. if ( file_exists('/etc/cacti/' . basename(__FILE__) . '.cnf' ) ) {
  62. require('/etc/cacti/' . basename(__FILE__) . '.cnf');
  63. debug('Found configuration file /etc/cacti/' . basename(__FILE__) . '.cnf');
  64. }
  65. elseif ( file_exists(__FILE__ . '.cnf' ) ) {
  66. require(__FILE__ . '.cnf');
  67. debug('Found configuration file ' . __FILE__ . '.cnf');
  68. }
  69. # Make this a happy little script even when there are errors.
  70. $no_http_headers = true;
  71. ini_set('implicit_flush', false); # No output, ever.
  72. if ( $debug ) {
  73. ini_set('display_errors', true);
  74. ini_set('display_startup_errors', true);
  75. ini_set('error_reporting', 2147483647);
  76. }
  77. else {
  78. ini_set('error_reporting', E_ERROR);
  79. }
  80. ob_start(); # Catch all output such as notices of undefined array indexes.
  81. function error_handler($errno, $errstr, $errfile, $errline) {
  82. print("$errstr at $errfile line $errline\n");
  83. debug("$errstr at $errfile line $errline");
  84. }
  85. # ============================================================================
  86. # Set up the stuff we need to be called by the script server.
  87. # ============================================================================
  88. if ( $use_ss ) {
  89. if ( file_exists( dirname(__FILE__) . "/../include/global.php") ) {
  90. # See issue 5 for the reasoning behind this.
  91. debug("including " . dirname(__FILE__) . "/../include/global.php");
  92. include_once(dirname(__FILE__) . "/../include/global.php");
  93. }
  94. elseif ( file_exists( dirname(__FILE__) . "/../include/config.php" ) ) {
  95. # Some Cacti installations don't have global.php.
  96. debug("including " . dirname(__FILE__) . "/../include/config.php");
  97. include_once(dirname(__FILE__) . "/../include/config.php");
  98. }
  99. }
  100. # ============================================================================
  101. # Set the default timezone either to the configured, system timezone, or the
  102. # default set above in the script.
  103. # ============================================================================
  104. if ( function_exists("date_default_timezone_set")
  105. && function_exists("date_default_timezone_get") ) {
  106. $tz = ($timezone ? $timezone : @date_default_timezone_get());
  107. if ( $tz ) {
  108. @date_default_timezone_set($tz);
  109. }
  110. }
  111. # ============================================================================
  112. # Make sure we can also be called as a script.
  113. # ============================================================================
  114. if (!isset($called_by_script_server)) {
  115. debug($_SERVER["argv"]);
  116. array_shift($_SERVER["argv"]); # Strip off this script's filename
  117. $options = parse_cmdline($_SERVER["argv"]);
  118. validate_options($options);
  119. $result = ss_get_mysql_stats($options);
  120. debug($result);
  121. if ( !$debug ) {
  122. # Throw away the buffer, which ought to contain only errors.
  123. ob_end_clean();
  124. }
  125. else {
  126. ob_end_flush(); # In debugging mode, print out the errors.
  127. }
  128. # Split the result up and extract only the desired parts of it.
  129. $wanted = explode(',', $options['items']);
  130. $output = array();
  131. foreach ( explode(' ', $result) as $item ) {
  132. if ( in_array(substr($item, 0, 2), $wanted) ) {
  133. $output[] = $item;
  134. }
  135. }
  136. debug(array("Final result", $output));
  137. print(implode(' ', $output));
  138. }
  139. # ============================================================================
  140. # End "if file was not included" section.
  141. # ============================================================================
  142. }
  143. # ============================================================================
  144. # Work around the lack of array_change_key_case in older PHP.
  145. # ============================================================================
  146. if ( !function_exists('array_change_key_case') ) {
  147. function array_change_key_case($arr) {
  148. $res = array();
  149. foreach ( $arr as $key => $val ) {
  150. $res[strtolower($key)] = $val;
  151. }
  152. return $res;
  153. }
  154. }
  155. # ============================================================================
  156. # Validate that the command-line options are here and correct
  157. # ============================================================================
  158. function validate_options($options) {
  159. $opts = array('host', 'items', 'user', 'pass', 'nocache', 'port', 'server-id');
  160. # Show help
  161. if ( array_key_exists('help', $options) ) {
  162. usage('');
  163. }
  164. # Required command-line options
  165. foreach ( array('host', 'items') as $option ) {
  166. if ( !isset($options[$option]) || !$options[$option] ) {
  167. usage("Required option --$option is missing");
  168. }
  169. }
  170. foreach ( $options as $key => $val ) {
  171. if ( !in_array($key, $opts) ) {
  172. usage("Unknown option --$key");
  173. }
  174. }
  175. }
  176. # ============================================================================
  177. # Print out a brief usage summary
  178. # ============================================================================
  179. function usage($message) {
  180. global $mysql_user, $mysql_pass, $mysql_port;
  181. $usage = <<<EOF
  182. $message
  183. Usage: php ss_get_mysql_stats.php --host <host> --items <item,...> [OPTION]
  184. --host MySQL host
  185. --items Comma-separated list of the items whose data you want
  186. --user MySQL username
  187. --pass MySQL password
  188. --port MySQL port
  189. --socket MySQL socket
  190. --flags MySQL flags
  191. --connection-timeout MySQL connection timeout
  192. --server-id Server id to associate with a heartbeat if heartbeat usage is enabled
  193. --nocache Do not cache results in a file
  194. --help Show usage
  195. EOF;
  196. die($usage);
  197. }
  198. # ============================================================================
  199. # Parse command-line arguments, in the format --arg value --arg value, and
  200. # return them as an array ( arg => value )
  201. # ============================================================================
  202. function parse_cmdline( $args ) {
  203. $options = array();
  204. while (list($tmp, $p) = each($args)) {
  205. if (strpos($p, '--') === 0) {
  206. $param = substr($p, 2);
  207. $value = null;
  208. $nextparam = current($args);
  209. if ($nextparam !== false && strpos($nextparam, '--') !==0) {
  210. list($tmp, $value) = each($args);
  211. }
  212. $options[$param] = $value;
  213. }
  214. }
  215. if ( array_key_exists('host', $options) ) {
  216. $options['host'] = substr($options['host'], 0, 4) == 'tcp:' ? substr($options['host'], 4) : $options['host'];
  217. }
  218. debug($options);
  219. return $options;
  220. }
  221. # ============================================================================
  222. # This is the main function. Some parameters are filled in from defaults at the
  223. # top of this file.
  224. # ============================================================================
  225. function ss_get_mysql_stats( $options ) {
  226. # Process connection options.
  227. global $debug, $mysql_user, $mysql_pass, $cache_dir, $poll_time, $chk_options,
  228. $mysql_port, $mysql_socket, $mysql_flags,
  229. $mysql_ssl, $mysql_ssl_key, $mysql_ssl_cert, $mysql_ssl_ca,
  230. $mysql_connection_timeout,
  231. $heartbeat, $heartbeat_table, $heartbeat_server_id, $heartbeat_utc;
  232. $user = isset($options['user']) ? $options['user'] : $mysql_user;
  233. $pass = isset($options['pass']) ? $options['pass'] : $mysql_pass;
  234. $host = $options['host'];
  235. $port = isset($options['port']) ? $options['port'] : $mysql_port;
  236. $socket = isset($options['socket']) ? $options['socket'] : $mysql_socket;
  237. $flags = isset($options['flags']) ? $options['flags'] : $mysql_flags;
  238. $connection_timeout = isset($options['connection-timeout']) ? $options['connection-timeout'] : $mysql_connection_timeout;
  239. $heartbeat_server_id = isset($options['server-id']) ? $options['server-id'] : $heartbeat_server_id;
  240. $sanitized_host = str_replace(array(":", "/"), array("", "_"), $host);
  241. $cache_file = "$cache_dir/$sanitized_host-mysql_cacti_stats.txt" . ($port != 3306 ? ":$port" : '');
  242. debug("Cache file is $cache_file");
  243. # First, check the cache.
  244. $fp = null;
  245. if ( $cache_dir && !array_key_exists('nocache', $options) ) {
  246. if ( $fp = fopen($cache_file, 'a+') ) {
  247. $locked = flock($fp, 1); # LOCK_SH
  248. if ( $locked ) {
  249. if ( filesize($cache_file) > 0
  250. && filectime($cache_file) + ($poll_time/2) > time()
  251. && ($arr = file($cache_file))
  252. ) {# The cache file is good to use.
  253. debug("Using the cache file");
  254. fclose($fp);
  255. return $arr[0];
  256. }
  257. else {
  258. debug("The cache file seems too small or stale");
  259. # Escalate the lock to exclusive, so we can write to it.
  260. if ( flock($fp, 2) ) { # LOCK_EX
  261. # We might have blocked while waiting for that LOCK_EX, and
  262. # another process ran and updated it. Let's see if we can just
  263. # return the data now:
  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. ftruncate($fp, 0); # Now it's ready for writing later.
  273. }
  274. }
  275. }
  276. else {
  277. $fp = null;
  278. debug("Couldn't lock the cache file, ignoring it");
  279. }
  280. }
  281. else {
  282. $fp = null;
  283. debug("Couldn't open the cache file");
  284. }
  285. }
  286. else {
  287. debug("Caching is disabled.");
  288. }
  289. # Connect to MySQL.
  290. debug(array('Connecting to', $host, $port, $user, $pass));
  291. if ( !extension_loaded('mysqli') ) {
  292. debug("PHP MySQLi extension is not loaded");
  293. die("PHP MySQLi extension is not loaded");
  294. }
  295. if ( $mysql_ssl ) {
  296. $conn = mysqli_init();
  297. $conn->options(MYSQLI_OPT_CONNECT_TIMEOUT, $connection_timeout);
  298. mysqli_ssl_set($conn, $mysql_ssl_key, $mysql_ssl_cert, $mysql_ssl_ca, NULL, NULL);
  299. mysqli_real_connect($conn, $host, $user, $pass, NULL, $port, $socket, $flags);
  300. }
  301. else {
  302. $conn = mysqli_init();
  303. $conn->options(MYSQLI_OPT_CONNECT_TIMEOUT, $connection_timeout);
  304. mysqli_real_connect($conn, $host, $user, $pass, NULL, $port, $socket, $flags);
  305. }
  306. if ( mysqli_connect_errno() ) {
  307. debug("MySQL connection failed: " . mysqli_connect_error());
  308. die("ERROR: " . mysqli_connect_error());
  309. }
  310. # MySQL server version.
  311. # The form of this version number is main_version * 10000 + minor_version * 100 + sub_version
  312. # i.e. version 5.5.44 is 50544.
  313. $mysql_version = mysqli_get_server_version($conn);
  314. debug("MySQL server version is " . $mysql_version);
  315. # Set up variables.
  316. $status = array( # Holds the result of SHOW STATUS, SHOW INNODB STATUS, etc
  317. # Define some indexes so they don't cause errors with += operations.
  318. 'relay_log_space' => null,
  319. 'binary_log_space' => null,
  320. 'current_transactions' => 0,
  321. 'locked_transactions' => 0,
  322. 'active_transactions' => 0,
  323. 'innodb_locked_tables' => 0,
  324. 'innodb_tables_in_use' => 0,
  325. 'innodb_lock_structs' => 0,
  326. 'innodb_lock_wait_secs' => 0,
  327. 'innodb_sem_waits' => 0,
  328. 'innodb_sem_wait_time_ms' => 0,
  329. # Values for the 'state' column from SHOW PROCESSLIST (converted to
  330. # lowercase, with spaces replaced by underscores)
  331. 'State_closing_tables' => 0,
  332. 'State_copying_to_tmp_table' => 0,
  333. 'State_end' => 0,
  334. 'State_freeing_items' => 0,
  335. 'State_init' => 0,
  336. 'State_locked' => 0,
  337. 'State_login' => 0,
  338. 'State_preparing' => 0,
  339. 'State_reading_from_net' => 0,
  340. 'State_sending_data' => 0,
  341. 'State_sorting_result' => 0,
  342. 'State_statistics' => 0,
  343. 'State_updating' => 0,
  344. 'State_writing_to_net' => 0,
  345. 'State_none' => 0,
  346. 'State_other' => 0, # Everything not listed above
  347. );
  348. # Get SHOW STATUS and convert the name-value array into a simple
  349. # associative array.
  350. $result = run_query("SHOW /*!50002 GLOBAL */ STATUS", $conn);
  351. foreach ( $result as $row ) {
  352. $status[$row[0]] = $row[1];
  353. }
  354. # Get SHOW VARIABLES and do the same thing, adding it to the $status array.
  355. $result = run_query("SHOW VARIABLES", $conn);
  356. foreach ( $result as $row ) {
  357. $status[$row[0]] = $row[1];
  358. }
  359. # Get SHOW SLAVE STATUS, and add it to the $status array.
  360. if ( $chk_options['slave'] ) {
  361. # Leverage lock-free SHOW SLAVE STATUS if available
  362. $result = run_query("SHOW SLAVE STATUS NONBLOCKING", $conn);
  363. if ( !$result ) {
  364. $result = run_query("SHOW SLAVE STATUS NOLOCK", $conn);
  365. if ( !$result ) {
  366. $result = run_query("SHOW SLAVE STATUS", $conn);
  367. }
  368. }
  369. $slave_status_rows_gotten = 0;
  370. foreach ( $result as $row ) {
  371. $slave_status_rows_gotten++;
  372. # Must lowercase keys because different MySQL versions have different
  373. # lettercase.
  374. $row = array_change_key_case($row, CASE_LOWER);
  375. $status['relay_log_space'] = $row['relay_log_space'];
  376. $status['slave_lag'] = $row['seconds_behind_master'];
  377. # Check replication heartbeat, if present.
  378. if ( $heartbeat ) {
  379. if ( $heartbeat_utc ) {
  380. $now_func = 'UNIX_TIMESTAMP(UTC_TIMESTAMP)';
  381. }
  382. else {
  383. $now_func = 'UNIX_TIMESTAMP()';
  384. }
  385. $result2 = run_query(
  386. "SELECT MAX($now_func - ROUND(UNIX_TIMESTAMP(ts)))"
  387. . " AS delay FROM $heartbeat_table"
  388. . " WHERE $heartbeat_server_id = 0 OR server_id = $heartbeat_server_id", $conn);
  389. $slave_delay_rows_gotten = 0;
  390. foreach ( $result2 as $row2 ) {
  391. $slave_delay_rows_gotten++;
  392. if ( $row2 && is_array($row2)
  393. && array_key_exists('delay', $row2) )
  394. {
  395. $status['slave_lag'] = $row2['delay'];
  396. }
  397. else {
  398. debug("Couldn't get slave lag from $heartbeat_table");
  399. }
  400. }
  401. if ( $slave_delay_rows_gotten == 0 ) {
  402. debug("Got nothing from heartbeat query");
  403. }
  404. }
  405. # Scale slave_running and slave_stopped relative to the slave lag.
  406. $status['slave_running'] = ($row['slave_sql_running'] == 'Yes')
  407. ? $status['slave_lag'] : 0;
  408. $status['slave_stopped'] = ($row['slave_sql_running'] == 'Yes')
  409. ? 0 : $status['slave_lag'];
  410. }
  411. if ( $slave_status_rows_gotten == 0 ) {
  412. debug("Got nothing from SHOW SLAVE STATUS");
  413. }
  414. }
  415. # Get SHOW MASTER STATUS, and add it to the $status array.
  416. if ( $chk_options['master']
  417. && array_key_exists('log_bin', $status)
  418. && $status['log_bin'] == 'ON'
  419. ) { # See issue #8
  420. $binlogs = array(0);
  421. $result = run_query("SHOW MASTER LOGS", $conn);
  422. foreach ( $result as $row ) {
  423. $row = array_change_key_case($row, CASE_LOWER);
  424. # Older versions of MySQL may not have the File_size column in the
  425. # results of the command. Zero-size files indicate the user is
  426. # deleting binlogs manually from disk (bad user! bad!).
  427. if ( array_key_exists('file_size', $row) && $row['file_size'] > 0 ) {
  428. $binlogs[] = $row['file_size'];
  429. }
  430. }
  431. if (count($binlogs)) {
  432. $status['binary_log_space'] = to_int(array_sum($binlogs));
  433. }
  434. }
  435. # Get SHOW PROCESSLIST and aggregate it by state, then add it to the array
  436. # too.
  437. if ( $chk_options['procs'] ) {
  438. $result = run_query('SHOW PROCESSLIST', $conn);
  439. foreach ( $result as $row ) {
  440. $state = $row['State'];
  441. if ( is_null($state) ) {
  442. $state = 'NULL';
  443. }
  444. if ( $state == '' ) {
  445. $state = 'none';
  446. }
  447. # MySQL 5.5 replaces the 'Locked' state with a variety of "Waiting for
  448. # X lock" types of statuses. Wrap these all back into "Locked" because
  449. # we don't really care about the type of locking it is.
  450. $state = preg_replace('/^(Table lock|Waiting for .*lock)$/', 'Locked', $state);
  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. # Get SHOW ENGINES to be able to determine whether InnoDB is present.
  461. $engines = array();
  462. $result = run_query("SHOW ENGINES", $conn);
  463. foreach ( $result as $row ) {
  464. $engines[$row[0]] = $row[1];
  465. }
  466. # Get SHOW INNODB STATUS and extract the desired metrics from it, then add
  467. # those to the array too.
  468. if ( $chk_options['innodb']
  469. && array_key_exists('InnoDB', $engines)
  470. && $engines['InnoDB'] == 'YES'
  471. || $engines['InnoDB'] == 'DEFAULT'
  472. ) {
  473. $result = run_query("SHOW /*!50000 ENGINE*/ INNODB STATUS", $conn);
  474. $istatus_text = $result[0]['Status'];
  475. $istatus_vals = get_innodb_array($istatus_text, $mysql_version);
  476. # Get response time histogram from Percona Server or MariaDB if enabled.
  477. if ( $chk_options['get_qrt']
  478. && (( isset($status['have_response_time_distribution'])
  479. && $status['have_response_time_distribution'] == 'YES')
  480. || (isset($status['query_response_time_stats'])
  481. && $status['query_response_time_stats'] == 'ON')) )
  482. {
  483. debug('Getting query time histogram');
  484. $i = 0;
  485. $result = run_query(
  486. "SELECT `count`, ROUND(total * 1000000) AS total "
  487. . "FROM INFORMATION_SCHEMA.QUERY_RESPONSE_TIME "
  488. . "WHERE `time` <> 'TOO LONG'",
  489. $conn);
  490. foreach ( $result as $row ) {
  491. if ( $i > 13 ) {
  492. # It's possible that the number of rows returned isn't 14.
  493. # Don't add extra status counters.
  494. break;
  495. }
  496. $count_key = sprintf("Query_time_count_%02d", $i);
  497. $total_key = sprintf("Query_time_total_%02d", $i);
  498. $status[$count_key] = $row['count'];
  499. $status[$total_key] = $row['total'];
  500. $i++;
  501. }
  502. # It's also possible that the number of rows returned is too few.
  503. # Don't leave any status counters unassigned; it will break graphs.
  504. while ( $i <= 13 ) {
  505. $count_key = sprintf("Query_time_count_%02d", $i);
  506. $total_key = sprintf("Query_time_total_%02d", $i);
  507. $status[$count_key] = 0;
  508. $status[$total_key] = 0;
  509. $i++;
  510. }
  511. }
  512. else {
  513. debug('Not getting time histogram because it is not enabled');
  514. }
  515. # Override values from InnoDB parsing with values from SHOW STATUS,
  516. # because InnoDB status might not have everything and the SHOW STATUS is
  517. # to be preferred where possible.
  518. $overrides = array(
  519. 'Innodb_buffer_pool_pages_data' => 'database_pages',
  520. 'Innodb_buffer_pool_pages_dirty' => 'modified_pages',
  521. 'Innodb_buffer_pool_pages_free' => 'free_pages',
  522. 'Innodb_buffer_pool_pages_total' => 'pool_size',
  523. 'Innodb_data_fsyncs' => 'file_fsyncs',
  524. 'Innodb_data_pending_reads' => 'pending_normal_aio_reads',
  525. 'Innodb_data_pending_writes' => 'pending_normal_aio_writes',
  526. 'Innodb_os_log_pending_fsyncs' => 'pending_log_flushes',
  527. 'Innodb_pages_created' => 'pages_created',
  528. 'Innodb_pages_read' => 'pages_read',
  529. 'Innodb_pages_written' => 'pages_written',
  530. 'Innodb_rows_deleted' => 'rows_deleted',
  531. 'Innodb_rows_inserted' => 'rows_inserted',
  532. 'Innodb_rows_read' => 'rows_read',
  533. 'Innodb_rows_updated' => 'rows_updated',
  534. 'Innodb_buffer_pool_reads' => 'pool_reads',
  535. 'Innodb_buffer_pool_read_requests' => 'pool_read_requests',
  536. );
  537. # If the SHOW STATUS value exists, override...
  538. foreach ( $overrides as $key => $val ) {
  539. if ( array_key_exists($key, $status) ) {
  540. debug("Override $key");
  541. $istatus_vals[$val] = $status[$key];
  542. }
  543. }
  544. # Now copy the values into $status.
  545. foreach ( $istatus_vals as $key => $val ) {
  546. $status[$key] = $istatus_vals[$key];
  547. }
  548. }
  549. # Make table_open_cache backwards-compatible (issue 63).
  550. if ( array_key_exists('table_open_cache', $status) ) {
  551. $status['table_cache'] = $status['table_open_cache'];
  552. }
  553. # Compute how much of the key buffer is used and unflushed (issue 127).
  554. $status['Key_buf_bytes_used']
  555. = big_sub($status['key_buffer_size'],
  556. big_multiply($status['Key_blocks_unused'],
  557. $status['key_cache_block_size']));
  558. $status['Key_buf_bytes_unflushed']
  559. = big_multiply($status['Key_blocks_not_flushed'],
  560. $status['key_cache_block_size']);
  561. if ( array_key_exists('unflushed_log', $status)
  562. && $status['unflushed_log']
  563. ) {
  564. # TODO: I'm not sure what the deal is here; need to debug this. But the
  565. # unflushed log bytes spikes a lot sometimes and it's impossible for it to
  566. # be more than the log buffer.
  567. debug("Unflushed log: $status[unflushed_log]");
  568. $status['unflushed_log']
  569. = max($status['unflushed_log'], $status['innodb_log_buffer_size']);
  570. }
  571. # Define the variables to output. I use shortened variable names so maybe
  572. # it'll all fit in 1024 bytes for Cactid and Spine's benefit. Strings must
  573. # have some non-hex characters (non a-f0-9) to avoid a Cacti bug. This list
  574. # must come right after the word MAGIC_VARS_DEFINITIONS. The Perl script
  575. # parses it and uses it as a Perl variable.
  576. $keys = array(
  577. 'Key_read_requests' => 'gg',
  578. 'Key_reads' => 'gh',
  579. 'Key_write_requests' => 'gi',
  580. 'Key_writes' => 'gj',
  581. 'history_list' => 'gk',
  582. 'innodb_transactions' => 'gl',
  583. 'read_views' => 'gm',
  584. 'current_transactions' => 'gn',
  585. 'locked_transactions' => 'go',
  586. 'active_transactions' => 'gp',
  587. 'pool_size' => 'gq',
  588. 'free_pages' => 'gr',
  589. 'database_pages' => 'gs',
  590. 'modified_pages' => 'gt',
  591. 'pages_read' => 'gu',
  592. 'pages_created' => 'gv',
  593. 'pages_written' => 'gw',
  594. 'file_fsyncs' => 'gx',
  595. 'file_reads' => 'gy',
  596. 'file_writes' => 'gz',
  597. 'log_writes' => 'hg',
  598. 'pending_aio_log_ios' => 'hh',
  599. 'pending_aio_sync_ios' => 'hi',
  600. 'pending_buf_pool_flushes' => 'hj',
  601. 'pending_chkp_writes' => 'hk',
  602. 'pending_ibuf_aio_reads' => 'hl',
  603. 'pending_log_flushes' => 'hm',
  604. 'pending_log_writes' => 'hn',
  605. 'pending_normal_aio_reads' => 'ho',
  606. 'pending_normal_aio_writes' => 'hp',
  607. 'ibuf_inserts' => 'hq',
  608. 'ibuf_merged' => 'hr',
  609. 'ibuf_merges' => 'hs',
  610. 'spin_waits' => 'ht',
  611. 'spin_rounds' => 'hu',
  612. 'os_waits' => 'hv',
  613. 'rows_inserted' => 'hw',
  614. 'rows_updated' => 'hx',
  615. 'rows_deleted' => 'hy',
  616. 'rows_read' => 'hz',
  617. 'Table_locks_waited' => 'ig',
  618. 'Table_locks_immediate' => 'ih',
  619. 'Slow_queries' => 'ii',
  620. 'Open_files' => 'ij',
  621. 'Open_tables' => 'ik',
  622. 'Opened_tables' => 'il',
  623. 'innodb_open_files' => 'im',
  624. 'open_files_limit' => 'in',
  625. 'table_cache' => 'io',
  626. 'Aborted_clients' => 'ip',
  627. 'Aborted_connects' => 'iq',
  628. 'Max_used_connections' => 'ir',
  629. 'Slow_launch_threads' => 'is',
  630. 'Threads_cached' => 'it',
  631. 'Threads_connected' => 'iu',
  632. 'Threads_created' => 'iv',
  633. 'Threads_running' => 'iw',
  634. 'max_connections' => 'ix',
  635. 'thread_cache_size' => 'iy',
  636. 'Connections' => 'iz',
  637. 'slave_running' => 'jg',
  638. 'slave_stopped' => 'jh',
  639. 'Slave_retried_transactions' => 'ji',
  640. 'slave_lag' => 'jj',
  641. 'Slave_open_temp_tables' => 'jk',
  642. 'Qcache_free_blocks' => 'jl',
  643. 'Qcache_free_memory' => 'jm',
  644. 'Qcache_hits' => 'jn',
  645. 'Qcache_inserts' => 'jo',
  646. 'Qcache_lowmem_prunes' => 'jp',
  647. 'Qcache_not_cached' => 'jq',
  648. 'Qcache_queries_in_cache' => 'jr',
  649. 'Qcache_total_blocks' => 'js',
  650. 'query_cache_size' => 'jt',
  651. 'Questions' => 'ju',
  652. 'Com_update' => 'jv',
  653. 'Com_insert' => 'jw',
  654. 'Com_select' => 'jx',
  655. 'Com_delete' => 'jy',
  656. 'Com_replace' => 'jz',
  657. 'Com_load' => 'kg',
  658. 'Com_update_multi' => 'kh',
  659. 'Com_insert_select' => 'ki',
  660. 'Com_delete_multi' => 'kj',
  661. 'Com_replace_select' => 'kk',
  662. 'Select_full_join' => 'kl',
  663. 'Select_full_range_join' => 'km',
  664. 'Select_range' => 'kn',
  665. 'Select_range_check' => 'ko',
  666. 'Select_scan' => 'kp',
  667. 'Sort_merge_passes' => 'kq',
  668. 'Sort_range' => 'kr',
  669. 'Sort_rows' => 'ks',
  670. 'Sort_scan' => 'kt',
  671. 'Created_tmp_tables' => 'ku',
  672. 'Created_tmp_disk_tables' => 'kv',
  673. 'Created_tmp_files' => 'kw',
  674. 'Bytes_sent' => 'kx',
  675. 'Bytes_received' => 'ky',
  676. 'innodb_log_buffer_size' => 'kz',
  677. 'unflushed_log' => 'lg',
  678. 'log_bytes_flushed' => 'lh',
  679. 'log_bytes_written' => 'li',
  680. 'relay_log_space' => 'lj',
  681. 'binlog_cache_size' => 'lk',
  682. 'Binlog_cache_disk_use' => 'll',
  683. 'Binlog_cache_use' => 'lm',
  684. 'binary_log_space' => 'ln',
  685. 'innodb_locked_tables' => 'lo',
  686. 'innodb_lock_structs' => 'lp',
  687. 'State_closing_tables' => 'lq',
  688. 'State_copying_to_tmp_table' => 'lr',
  689. 'State_end' => 'ls',
  690. 'State_freeing_items' => 'lt',
  691. 'State_init' => 'lu',
  692. 'State_locked' => 'lv',
  693. 'State_login' => 'lw',
  694. 'State_preparing' => 'lx',
  695. 'State_reading_from_net' => 'ly',
  696. 'State_sending_data' => 'lz',
  697. 'State_sorting_result' => 'mg',
  698. 'State_statistics' => 'mh',
  699. 'State_updating' => 'mi',
  700. 'State_writing_to_net' => 'mj',
  701. 'State_none' => 'mk',
  702. 'State_other' => 'ml',
  703. 'Handler_commit' => 'mm',
  704. 'Handler_delete' => 'mn',
  705. 'Handler_discover' => 'mo',
  706. 'Handler_prepare' => 'mp',
  707. 'Handler_read_first' => 'mq',
  708. 'Handler_read_key' => 'mr',
  709. 'Handler_read_next' => 'ms',
  710. 'Handler_read_prev' => 'mt',
  711. 'Handler_read_rnd' => 'mu',
  712. 'Handler_read_rnd_next' => 'mv',
  713. 'Handler_rollback' => 'mw',
  714. 'Handler_savepoint' => 'mx',
  715. 'Handler_savepoint_rollback' => 'my',
  716. 'Handler_update' => 'mz',
  717. 'Handler_write' => 'ng',
  718. 'innodb_tables_in_use' => 'nh',
  719. 'innodb_lock_wait_secs' => 'ni',
  720. 'hash_index_cells_total' => 'nj',
  721. 'hash_index_cells_used' => 'nk',
  722. 'total_mem_alloc' => 'nl',
  723. 'additional_pool_alloc' => 'nm',
  724. 'uncheckpointed_bytes' => 'nn',
  725. 'ibuf_used_cells' => 'no',
  726. 'ibuf_free_cells' => 'np',
  727. 'ibuf_cell_count' => 'nq',
  728. 'adaptive_hash_memory' => 'nr',
  729. 'page_hash_memory' => 'ns',
  730. 'dictionary_cache_memory' => 'nt',
  731. 'file_system_memory' => 'nu',
  732. 'lock_system_memory' => 'nv',
  733. 'recovery_system_memory' => 'nw',
  734. 'thread_hash_memory' => 'nx',
  735. 'innodb_sem_waits' => 'ny',
  736. 'innodb_sem_wait_time_ms' => 'nz',
  737. 'Key_buf_bytes_unflushed' => 'og',
  738. 'Key_buf_bytes_used' => 'oh',
  739. 'key_buffer_size' => 'oi',
  740. 'Innodb_row_lock_time' => 'oj',
  741. 'Innodb_row_lock_waits' => 'ok',
  742. 'Query_time_count_00' => 'ol',
  743. 'Query_time_count_01' => 'om',
  744. 'Query_time_count_02' => 'on',
  745. 'Query_time_count_03' => 'oo',
  746. 'Query_time_count_04' => 'op',
  747. 'Query_time_count_05' => 'oq',
  748. 'Query_time_count_06' => 'or',
  749. 'Query_time_count_07' => 'os',
  750. 'Query_time_count_08' => 'ot',
  751. 'Query_time_count_09' => 'ou',
  752. 'Query_time_count_10' => 'ov',
  753. 'Query_time_count_11' => 'ow',
  754. 'Query_time_count_12' => 'ox',
  755. 'Query_time_count_13' => 'oy',
  756. 'Query_time_total_00' => 'oz',
  757. 'Query_time_total_01' => 'pg',
  758. 'Query_time_total_02' => 'ph',
  759. 'Query_time_total_03' => 'pi',
  760. 'Query_time_total_04' => 'pj',
  761. 'Query_time_total_05' => 'pk',
  762. 'Query_time_total_06' => 'pl',
  763. 'Query_time_total_07' => 'pm',
  764. 'Query_time_total_08' => 'pn',
  765. 'Query_time_total_09' => 'po',
  766. 'Query_time_total_10' => 'pp',
  767. 'Query_time_total_11' => 'pq',
  768. 'Query_time_total_12' => 'pr',
  769. 'Query_time_total_13' => 'ps',
  770. 'wsrep_replicated_bytes' => 'pt',
  771. 'wsrep_received_bytes' => 'pu',
  772. 'wsrep_replicated' => 'pv',
  773. 'wsrep_received' => 'pw',
  774. 'wsrep_local_cert_failures' => 'px',
  775. 'wsrep_local_bf_aborts' => 'py',
  776. 'wsrep_local_send_queue' => 'pz',
  777. 'wsrep_local_recv_queue' => 'qg',
  778. 'wsrep_cluster_size' => 'qh',
  779. 'wsrep_cert_deps_distance' => 'qi',
  780. 'wsrep_apply_window' => 'qj',
  781. 'wsrep_commit_window' => 'qk',
  782. 'wsrep_flow_control_paused' => 'ql',
  783. 'wsrep_flow_control_sent' => 'qm',
  784. 'wsrep_flow_control_recv' => 'qn',
  785. 'pool_reads' => 'qo',
  786. 'pool_read_requests' => 'qp',
  787. );
  788. # Return the output.
  789. $output = array();
  790. foreach ($keys as $key => $short ) {
  791. # If the value isn't defined, return -1 which is lower than (most graphs')
  792. # minimum value of 0, so it'll be regarded as a missing value.
  793. $val = isset($status[$key]) ? $status[$key] : -1;
  794. $output[] = "$short:$val";
  795. }
  796. $result = implode(' ', $output);
  797. if ( $fp ) {
  798. if ( fwrite($fp, $result) === FALSE ) {
  799. die("Can't write '$cache_file'");
  800. }
  801. fclose($fp);
  802. }
  803. return $result;
  804. }
  805. # ============================================================================
  806. # Given INNODB STATUS text, returns a key-value array of the parsed text. Each
  807. # line shows a sample of the input for both standard InnoDB as you would find in
  808. # MySQL 5.0, and XtraDB or enhanced InnoDB from Percona if applicable. Note
  809. # that extra leading spaces are ignored due to trim().
  810. # ============================================================================
  811. function get_innodb_array($text, $mysql_version) {
  812. $results = array(
  813. 'spin_waits' => array(),
  814. 'spin_rounds' => array(),
  815. 'os_waits' => array(),
  816. 'pending_normal_aio_reads' => null,
  817. 'pending_normal_aio_writes' => null,
  818. 'pending_ibuf_aio_reads' => null,
  819. 'pending_aio_log_ios' => null,
  820. 'pending_aio_sync_ios' => null,
  821. 'pending_log_flushes' => null,
  822. 'pending_buf_pool_flushes' => null,
  823. 'file_reads' => null,
  824. 'file_writes' => null,
  825. 'file_fsyncs' => null,
  826. 'ibuf_inserts' => null,
  827. 'ibuf_merged' => null,
  828. 'ibuf_merges' => null,
  829. 'log_bytes_written' => null,
  830. 'unflushed_log' => null,
  831. 'log_bytes_flushed' => null,
  832. 'pending_log_writes' => null,
  833. 'pending_chkp_writes' => null,
  834. 'log_writes' => null,
  835. 'pool_size' => null,
  836. 'free_pages' => null,
  837. 'database_pages' => null,
  838. 'modified_pages' => null,
  839. 'pages_read' => null,
  840. 'pages_created' => null,
  841. 'pages_written' => null,
  842. 'queries_inside' => null,
  843. 'queries_queued' => null,
  844. 'read_views' => null,
  845. 'rows_inserted' => null,
  846. 'rows_updated' => null,
  847. 'rows_deleted' => null,
  848. 'rows_read' => null,
  849. 'innodb_transactions' => null,
  850. 'unpurged_txns' => null,
  851. 'history_list' => null,
  852. 'current_transactions' => null,
  853. 'hash_index_cells_total' => null,
  854. 'hash_index_cells_used' => null,
  855. 'total_mem_alloc' => null,
  856. 'additional_pool_alloc' => null,
  857. 'last_checkpoint' => null,
  858. 'uncheckpointed_bytes' => null,
  859. 'ibuf_used_cells' => null,
  860. 'ibuf_free_cells' => null,
  861. 'ibuf_cell_count' => null,
  862. 'adaptive_hash_memory' => null,
  863. 'page_hash_memory' => null,
  864. 'dictionary_cache_memory' => null,
  865. 'file_system_memory' => null,
  866. 'lock_system_memory' => null,
  867. 'recovery_system_memory' => null,
  868. 'thread_hash_memory' => null,
  869. 'innodb_sem_waits' => null,
  870. 'innodb_sem_wait_time_ms' => null,
  871. );
  872. $txn_seen = FALSE;
  873. foreach ( explode("\n", $text) as $line ) {
  874. $line = trim($line);
  875. $row = preg_split('/ +/', $line);
  876. # SEMAPHORES
  877. if (strpos($line, 'Mutex spin waits') === 0 ) {
  878. # Mutex spin waits 79626940, rounds 157459864, OS waits 698719
  879. # Mutex spin waits 0, rounds 247280272495, OS waits 316513438
  880. $results['spin_waits'][] = to_int($row[3]);
  881. $results['spin_rounds'][] = to_int($row[5]);
  882. $results['os_waits'][] = to_int($row[8]);
  883. }
  884. elseif (strpos($line, 'RW-shared spins') === 0
  885. && strpos($line, ';') > 0 ) {
  886. # RW-shared spins 3859028, OS waits 2100750; RW-excl spins 4641946, OS waits 1530310
  887. $results['spin_waits'][] = to_int($row[2]);
  888. $results['spin_waits'][] = to_int($row[8]);
  889. $results['os_waits'][] = to_int($row[5]);
  890. $results['os_waits'][] = to_int($row[11]);
  891. }
  892. elseif (strpos($line, 'RW-shared spins') === 0 && strpos($line, '; RW-excl spins') === FALSE) {
  893. # Post 5.5.17 SHOW ENGINE INNODB STATUS syntax
  894. # RW-shared spins 604733, rounds 8107431, OS waits 241268
  895. $results['spin_waits'][] = to_int($row[2]);
  896. $results['os_waits'][] = to_int($row[7]);
  897. }
  898. elseif (strpos($line, 'RW-excl spins') === 0) {
  899. # Post 5.5.17 SHOW ENGINE INNODB STATUS syntax
  900. # RW-excl spins 604733, rounds 8107431, OS waits 241268
  901. $results['spin_waits'][] = to_int($row[2]);
  902. $results['os_waits'][] = to_int($row[7]);
  903. }
  904. elseif (strpos($line, 'seconds the semaphore:') > 0) {
  905. # --Thread 907205 has waited at handler/ha_innodb.cc line 7156 for 1.00 seconds the semaphore:
  906. increment($results, 'innodb_sem_waits', 1);
  907. increment($results,
  908. 'innodb_sem_wait_time_ms', to_int($row[9]) * 1000);
  909. }
  910. # TRANSACTIONS
  911. elseif ( strpos($line, 'Trx id counter') === 0 ) {
  912. # The beginning of the TRANSACTIONS section: start counting
  913. # transactions
  914. if ( $mysql_version < 50600 ) {
  915. # For versions prior 5.6: two decimals or one hex
  916. # Trx id counter 0 1170664159
  917. # Trx id counter 861B144C
  918. $results['innodb_transactions'] = isset($row[4]) ? make_bigint(
  919. $row[3], $row[4]) : base_convert($row[3], 16, 10);
  920. }
  921. else {
  922. # For versions 5.6+ and MariaDB 10.x: one decimal
  923. # Trx id counter 2903813
  924. $results['innodb_transactions'] = $row[3];
  925. }
  926. $txn_seen = TRUE;
  927. }
  928. elseif ( strpos($line, 'Purge done for trx') === 0 ) {
  929. if ( $mysql_version < 50600 ) {
  930. # For versions prior 5.6: two decimals or one hex
  931. # Purge done for trx's n:o < 0 1170663853 undo n:o < 0 0
  932. # Purge done for trx's n:o < 861B135D undo n:o < 0
  933. $purged_to = $row[7] == 'undo' ? base_convert($row[6], 16, 10) : make_bigint($row[6], $row[7]);
  934. }
  935. else {
  936. # For versions 5.6+ and MariaDB 10.x: one decimal
  937. # Purge done for trx's n:o < 2903354 undo n:o < 0 state: running but idle
  938. $purged_to = $row[6];
  939. }
  940. $results['unpurged_txns']
  941. = big_sub($results['innodb_transactions'], $purged_to);
  942. }
  943. elseif (strpos($line, 'History list length') === 0 ) {
  944. # History list length 132
  945. $results['history_list'] = to_int($row[3]);
  946. }
  947. elseif ( $txn_seen && strpos($line, '---TRANSACTION') === 0 ) {
  948. # ---TRANSACTION 0, not started, process no 13510, OS thread id 1170446656
  949. increment($results, 'current_transactions', 1);
  950. if ( strpos($line, 'ACTIVE') > 0 ) {
  951. increment($results, 'active_transactions', 1);
  952. }
  953. }
  954. elseif ( $txn_seen && strpos($line, '------- TRX HAS BEEN') === 0 ) {
  955. # ------- TRX HAS BEEN WAITING 32 SEC FOR THIS LOCK TO BE GRANTED:
  956. increment($results, 'innodb_lock_wait_secs', to_int($row[5]));
  957. }
  958. elseif ( strpos($line, 'read views open inside InnoDB') > 0 ) {
  959. # 1 read views open inside InnoDB
  960. $results['read_views'] = to_int($row[0]);
  961. }
  962. elseif ( strpos($line, 'mysql tables in use') === 0 ) {
  963. # mysql tables in use 2, locked 2
  964. increment($results, 'innodb_tables_in_use', to_int($row[4]));
  965. increment($results, 'innodb_locked_tables', to_int($row[6]));
  966. }
  967. elseif ( $txn_seen && strpos($line, 'lock struct(s)') > 0 ) {
  968. # 23 lock struct(s), heap size 3024, undo log entries 27
  969. # LOCK WAIT 12 lock struct(s), heap size 3024, undo log entries 5
  970. # LOCK WAIT 2 lock struct(s), heap size 368
  971. if ( strpos($line, 'LOCK WAIT') === 0 ) {
  972. increment($results, 'innodb_lock_structs', to_int($row[2]));
  973. increment($results, 'locked_transactions', 1);
  974. }
  975. else {
  976. increment($results, 'innodb_lock_structs', to_int($row[0]));
  977. }
  978. }
  979. # FILE I/O
  980. elseif (strpos($line, ' OS file reads, ') > 0 ) {
  981. # 8782182 OS file reads, 15635445 OS file writes, 947800 OS fsyncs
  982. $results['file_reads'] = to_int($row[0]);
  983. $results['file_writes'] = to_int($row[4]);
  984. $results['file_fsyncs'] = to_int($row[8]);
  985. }
  986. elseif (strpos($line, 'Pending normal aio reads:') === 0 ) {
  987. # Pending normal aio reads: 0, aio writes: 0,
  988. $results['pending_normal_aio_reads'] = to_int($row[4]);
  989. $results['pending_normal_aio_writes'] = to_int($row[7]);
  990. }
  991. elseif (strpos($line, 'ibuf aio reads') === 0 ) {
  992. # ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0
  993. $results['pending_ibuf_aio_reads'] = to_int($row[3]);
  994. $results['pending_aio_log_ios'] = to_int($row[6]);
  995. $results['pending_aio_sync_ios'] = to_int($row[9]);
  996. }
  997. elseif ( strpos($line, 'Pending flushes (fsync)') === 0 ) {
  998. # Pending flushes (fsync) log: 0; buffer pool: 0
  999. $results['pending_log_flushes'] = to_int($row[4]);
  1000. $results['pending_buf_pool_flushes'] = to_int($row[7]);
  1001. }
  1002. # INSERT BUFFER AND ADAPTIVE HASH INDEX
  1003. elseif (strpos($line, 'Ibuf for space 0: size ') === 0 ) {
  1004. # Older InnoDB code seemed to be ready for an ibuf per tablespace. It
  1005. # had two lines in the output. Newer has just one line, see below.
  1006. # Ibuf for space 0: size 1, free list len 887, seg size 889, is not empty
  1007. # Ibuf for space 0: size 1, free list len 887, seg size 889,
  1008. $results['ibuf_used_cells'] = to_int($row[5]);
  1009. $results['ibuf_free_cells'] = to_int($row[9]);
  1010. $results['ibuf_cell_count'] = to_int($row[12]);
  1011. }
  1012. elseif (strpos($line, 'Ibuf: size ') === 0 ) {
  1013. # Ibuf: size 1, free list len 4634, seg size 4636,
  1014. $results['ibuf_used_cells'] = to_int($row[2]);
  1015. $results['ibuf_free_cells'] = to_int($row[6]);
  1016. $results['ibuf_cell_count'] = to_int($row[9]);
  1017. if (strpos($line, 'merges')) {
  1018. $results['ibuf_merges'] = to_int($row[10]);
  1019. }
  1020. }
  1021. elseif (strpos($line, ', delete mark ') > 0 && strpos($prev_line, 'merged operations:') === 0 ) {
  1022. # Output of show engine innodb status has changed in 5.5
  1023. # merged operations:
  1024. # insert 593983, delete mark 387006, delete 73092
  1025. $results['ibuf_inserts'] = to_int($row[1]);
  1026. $results['ibuf_merged'] = to_int($row[1]) + to_int($row[4]) + to_int($row[6]);
  1027. }
  1028. elseif (strpos($line, ' merged recs, ') > 0 ) {
  1029. # 19817685 inserts, 19817684 merged recs, 3552620 merges
  1030. $results['ibuf_inserts'] = to_int($row[0]);
  1031. $results['ibuf_merged'] = to_int($row[2]);
  1032. $results['ibuf_merges'] = to_int($row[5]);
  1033. }
  1034. elseif (strpos($line, 'Hash table size ') === 0 ) {
  1035. # In some versions of InnoDB, the used cells is omitted.
  1036. # Hash table size 4425293, used cells 4229064, ....
  1037. # Hash table size 57374437, node heap has 72964 buffer(s) <-- no used cells
  1038. $results['hash_index_cells_total'] = to_int($row[3]);
  1039. $results['hash_index_cells_used']
  1040. = strpos($line, 'used cells') > 0 ? to_int($row[6]) : '0';
  1041. }
  1042. # LOG
  1043. elseif (strpos($line, " log i/o's done, ") > 0 ) {
  1044. # 3430041 log i/o's done, 17.44 log i/o's/second
  1045. # 520835887 log i/o's done, 17.28 log i/o's/second, 518724686 syncs, 2980893 checkpoints
  1046. # TODO: graph syncs and checkpoints
  1047. $results['log_writes'] = to_int($row[0]);
  1048. }
  1049. elseif (strpos($line, " pending log writes, ") > 0 ) {
  1050. # 0 pending log writes, 0 pending chkp writes
  1051. $results['pending_log_writes'] = to_int($row[0]);
  1052. $results['pending_chkp_writes'] = to_int($row[4]);
  1053. }
  1054. elseif (strpos($line, "Log sequence number") === 0 ) {
  1055. # This number is NOT printed in hex in InnoDB plugin.
  1056. # Log sequence number 13093949495856 //plugin
  1057. # Log sequence number 125 3934414864 //normal
  1058. $results['log_bytes_written']
  1059. = isset($row[4])
  1060. ? make_bigint($row[3], $row[4])
  1061. : to_int($row[3]);
  1062. }
  1063. elseif (strpos($line, "Log flushed up to") === 0 ) {
  1064. # This number is NOT printed in hex in InnoDB plugin.
  1065. # Log flushed up to 13093948219327
  1066. # Log flushed up to 125 3934414864
  1067. $results['log_bytes_flushed']
  1068. = isset($row[5])
  1069. ? make_bigint($row[4], $row[5])
  1070. : to_int($row[4]);
  1071. }
  1072. elseif (strpos($line, "Last checkpoint at") === 0 ) {
  1073. # Last checkpoint at 125 3934293461
  1074. $results['last_checkpoint']
  1075. = isset($row[4])
  1076. ? make_bigint($row[3], $row[4])
  1077. : to_int($row[3]);
  1078. }
  1079. # BUFFER POOL AND MEMORY
  1080. elseif (strpos($line, "Total memory allocated") === 0 && strpos($line, "in additional pool allocated") > 0 ) {
  1081. # Total memory allocated 29642194944; in additional pool allocated 0
  1082. # Total memory allocated by read views 96
  1083. $results['total_mem_alloc'] = to_int($row[3]);
  1084. $results['additional_pool_alloc'] = to_int($row[8]);
  1085. }
  1086. elseif(strpos($line, 'Adaptive hash index ') === 0 ) {
  1087. # Adaptive hash index 1538240664 (186998824 + 1351241840)
  1088. $results['adaptive_hash_memory'] = to_int($row[3]);
  1089. }
  1090. elseif(strpos($line, 'Page hash ') === 0 ) {
  1091. # Page hash 11688584
  1092. $results['page_hash_memory'] = to_int($row[2]);
  1093. }
  1094. elseif(strpos($line, 'Dictionary cache ') === 0 ) {
  1095. # Dictionary cache 145525560 (140250984 + 5274576)
  1096. $results['dictionary_cache_memory'] = to_int($row[2]);
  1097. }
  1098. elseif(strpos($line, 'File system ') === 0 ) {
  1099. # File system 313848 (82672 + 231176)
  1100. $results['file_system_memory'] = to_int($row[2]);
  1101. }
  1102. elseif(strpos($line, 'Lock system ') === 0 ) {
  1103. # Lock system 29232616 (29219368 + 13248)
  1104. $results['lock_system_memory'] = to_int($row[2]);
  1105. }
  1106. elseif(strpos($line, 'Recovery system ') === 0 ) {
  1107. # Recovery system 0 (0 + 0)
  1108. $results['recovery_system_memory'] = to_int($row[2]);
  1109. }
  1110. elseif(strpos($line, 'Threads ') === 0 ) {
  1111. # Threads 409336 (406936 + 2400)
  1112. $results['thread_hash_memory'] = to_int($row[1]);
  1113. }
  1114. elseif(strpos($line, 'innodb_io_pattern ') === 0 ) {
  1115. # innodb_io_pattern 0 (0 + 0)
  1116. $results['innodb_io_pattern_memory'] = to_int($row[1]);
  1117. }
  1118. elseif (strpos($line, "Buffer pool size ") === 0 ) {
  1119. # The " " after size is necessary to avoid matching the wrong line:
  1120. # Buffer pool size 1769471
  1121. # Buffer pool size, bytes 28991012864
  1122. $results['pool_size'] = to_int($row[3]);
  1123. }
  1124. elseif (strpos($line, "Free buffers") === 0 ) {
  1125. # Free buffers 0
  1126. $results['free_pages'] = to_int($row[2]);
  1127. }
  1128. elseif (strpos($line, "Database pages") === 0 ) {
  1129. # Database pages 1696503
  1130. $results['database_pages'] = to_int($row[2]);
  1131. }
  1132. elseif (strpos($line, "Modified db pages") === 0 ) {
  1133. # Modified db pages 160602
  1134. $results['modified_pages'] = to_int($row[3]);
  1135. }
  1136. elseif (strpos($line, "Pages read ahead") === 0 ) {
  1137. # Must do this BEFORE the next test, otherwise it'll get fooled by this
  1138. # line from the new plugin (see samples/innodb-015.txt):
  1139. # Pages read ahead 0.00/s, evicted without access 0.06/s
  1140. # TODO: No-op for now, see issue 134.
  1141. }
  1142. elseif (strpos($line, "Pages read") === 0 ) {
  1143. # Pages read 15240822, created 1770238, written 21705836
  1144. $results['pages_read'] = to_int($row[2]);
  1145. $results['pages_created'] = to_int($row[4]);
  1146. $results['pages_written'] = to_int($row[6]);
  1147. }
  1148. # ROW OPERATIONS
  1149. elseif (strpos($line, 'Number of rows inserted') === 0 ) {
  1150. # Number of rows inserted 50678311, updated 66425915, deleted 20605903, read 454561562
  1151. $results['rows_inserted'] = to_int($row[4]);
  1152. $results['rows_updated'] = to_int($row[6]);
  1153. $results['rows_deleted'] = to_int($row[8]);
  1154. $results['rows_read'] = to_int($row[10]);
  1155. }
  1156. elseif (strpos($line, " queries inside InnoDB, ") > 0 ) {
  1157. # 0 queries inside InnoDB, 0 queries in queue
  1158. $results['queries_inside'] = to_int($row[0]);
  1159. $results['queries_queued'] = to_int($row[4]);
  1160. }
  1161. $prev_line = $line;
  1162. }
  1163. foreach ( array('spin_waits', 'spin_rounds', 'os_waits') as $key ) {
  1164. $results[$key] = to_int(array_sum($results[$key]));
  1165. }
  1166. $results['unflushed_log']
  1167. = big_sub($results['log_bytes_written'], $results['log_bytes_flushed']);
  1168. $results['uncheckpointed_bytes']
  1169. = big_sub($results['log_bytes_written'], $results['last_checkpoint']);
  1170. return $results;
  1171. }
  1172. # ============================================================================
  1173. # Returns a bigint from two ulint. This is tested in
  1174. # t/mysql_stats.php and copied, without tests, to ss_get_by_ssh.php.
  1175. # ============================================================================
  1176. function make_bigint ($hi, $lo) {
  1177. debug(array($hi, $lo));
  1178. $hi = $hi ? $hi : '0'; # Handle empty-string or whatnot
  1179. $lo = $lo ? $lo : '0';
  1180. return big_add(big_multiply($hi, 4294967296), $lo);
  1181. }
  1182. # ============================================================================
  1183. # Extracts the numbers from a string. You can't reliably do this by casting to
  1184. # an int, because numbers that are bigger than PHP's int (varies by platform)
  1185. # will be truncated. And you can't use sprintf(%u) either, because the maximum
  1186. # value that will return on some platforms is 4022289582. So this just handles
  1187. # them as a string instead. It extracts digits until it finds a non-digit and
  1188. # quits. This is tested in t/mysql_stats.php and copied, without tests, to
  1189. # ss_get_by_ssh.php.
  1190. # ============================================================================
  1191. function to_int ( $str ) {
  1192. debug($str);
  1193. global $debug;
  1194. preg_match('{(\d+)}', $str, $m);
  1195. if ( isset($m[1]) ) {
  1196. return $m[1];
  1197. }
  1198. elseif ( $debug ) {
  1199. print_r(debug_backtrace());
  1200. }
  1201. else {
  1202. return 0;
  1203. }
  1204. }
  1205. # ============================================================================
  1206. # Wrap mysqli_query in error-handling, and instead of returning the result,
  1207. # return an array of arrays in the result.
  1208. # ============================================================================
  1209. function run_query($sql, $conn) {
  1210. global $debug;
  1211. debug($sql);
  1212. $result = @mysqli_query($conn, $sql);
  1213. if ( $debug && strpos($sql, 'SHOW SLAVE STATUS ') === false ) {
  1214. $error = @mysqli_error($conn);
  1215. if ( $error ) {
  1216. debug(array($sql, $error));
  1217. die("SQLERR $error in $sql");
  1218. }
  1219. }
  1220. $array = array();
  1221. $count = @mysqli_num_rows($result);
  1222. if ( $count > 10000 ) {
  1223. debug('Abnormal number of rows returned: ' . $count);
  1224. }
  1225. else {
  1226. while ( $row = @mysqli_fetch_array($result) ) {
  1227. $array[] = $row;
  1228. }
  1229. }
  1230. debug(array($sql, $array));
  1231. return $array;
  1232. }
  1233. # ============================================================================
  1234. # Safely increments a value that might be null.
  1235. # ============================================================================
  1236. function increment(&$arr, $key, $howmuch) {
  1237. debug(array($key, $howmuch));
  1238. if ( array_key_exists($key, $arr) && isset($arr[$key]) ) {
  1239. $arr[$key] = big_add($arr[$key], $howmuch);
  1240. }
  1241. else {
  1242. $arr[$key] = $howmuch;
  1243. }
  1244. }
  1245. # ============================================================================
  1246. # Multiply two big integers together as accurately as possible with reasonable
  1247. # effort. This is tested in t/mysql_stats.php and copied, without tests, to
  1248. # ss_get_by_ssh.php. $force is for testability.
  1249. # ============================================================================
  1250. function big_multiply ($left, $right, $force = null) {
  1251. if ( function_exists("gmp_mul") && (is_null($force) || $force == 'gmp') ) {
  1252. debug(array('gmp_mul', $left, $right));
  1253. return gmp_strval( gmp_mul( $left, $right ));
  1254. }
  1255. elseif ( function_exists("bcmul") && (is_null($force) || $force == 'bc') ) {
  1256. debug(array('bcmul', $left, $right));
  1257. return bcmul( $left, $right );
  1258. }
  1259. else { # Or $force == 'something else'
  1260. debug(array('sprintf', $left, $right));
  1261. return sprintf("%.0f", $left * $right);
  1262. }
  1263. }
  1264. # ============================================================================
  1265. # Subtract two big integers as accurately as possible with reasonable effort.
  1266. # This is tested in t/mysql_stats.php and copied, without tests, to
  1267. # ss_get_by_ssh.php. $force is for testability.
  1268. # ============================================================================
  1269. function big_sub ($left, $right, $force = null) {
  1270. debug(array($left, $right));
  1271. if ( is_null($left) ) { $left = 0; }
  1272. if ( is_null($right) ) { $right = 0; }
  1273. if ( function_exists("gmp_sub") && (is_null($force) || $force == 'gmp')) {
  1274. debug(array('gmp_sub', $left, $right));
  1275. return gmp_strval( gmp_sub( $left, $right ));
  1276. }
  1277. elseif ( function_exists("bcsub") && (is_null($force) || $force == 'bc')) {
  1278. debug(array('bcsub', $left, $right));
  1279. return bcsub( $left, $right );
  1280. }
  1281. else { # Or $force == 'something else'
  1282. debug(array('to_int', $left, $right));
  1283. return to_int($left - $right);
  1284. }
  1285. }
  1286. # ============================================================================
  1287. # Add two big integers together as accurately as possible with reasonable
  1288. # effort. This is tested in t/mysql_stats.php and copied, without tests, to
  1289. # ss_get_by_ssh.php. $force is for testability.
  1290. # ============================================================================
  1291. function big_add ($left, $right, $force = null) {
  1292. if ( is_null($left) ) { $left = 0; }
  1293. if ( is_null($right) ) { $right = 0; }
  1294. if ( function_exists("gmp_add") && (is_null($force) || $force == 'gmp')) {
  1295. debug(array('gmp_add', $left, $right));
  1296. return gmp_strval( gmp_add( $left, $right ));
  1297. }
  1298. elseif ( function_exists("bcadd") && (is_null($force) || $force == 'bc')) {
  1299. debug(array('bcadd', $left, $right));
  1300. return bcadd( $left, $right );
  1301. }
  1302. else { # Or $force == 'something else'
  1303. debug(array('to_int', $left, $right));
  1304. return to_int($left + $right);
  1305. }
  1306. }
  1307. # ============================================================================
  1308. # Writes to a debugging log.
  1309. # ============================================================================
  1310. function debug($val) {
  1311. global $debug_log;
  1312. if ( !$debug_log ) {
  1313. return;
  1314. }
  1315. if ( $fp = fopen($debug_log, 'a+') ) {
  1316. $trace = debug_backtrace();
  1317. $calls = array();
  1318. $i = 0;
  1319. $line = 0;
  1320. $file = '';
  1321. foreach ( debug_backtrace() as $arr ) {
  1322. if ( $i++ ) {
  1323. $calls[] = "$arr[function]() at $file:$line";
  1324. }
  1325. $line = array_key_exists('line', $arr) ? $arr['line'] : '?';
  1326. $file = array_key_exists('file', $arr) ? $arr['file'] : '?';
  1327. }
  1328. if ( !count($calls) ) {
  1329. $calls[] = "at $file:$line";
  1330. }
  1331. fwrite($fp, date('Y-m-d H:i:s') . ' ' . implode(' <- ', $calls));
  1332. fwrite($fp, "\n" . var_export($val, TRUE) . "\n");
  1333. fclose($fp);
  1334. }
  1335. else { # Disable logging
  1336. print("Warning: disabling debug logging to $debug_log\n");
  1337. $debug_log = FALSE;
  1338. }
  1339. }