8 mysql_ - Munin plugin to display misc MySQL server status
10 =head1 APPLICABLE SYSTEMS
12 Any MySQL platform, tested by the authors on:
15 * MariaDB-5.5.39(galera).
20 * MariaDB-10 Query Response Time: https://mariadb.com/kb/en/mariadb/query_response_time-plugin/
22 Information Schema tables:
23 * User statistics - MariaDB-5.2+, OurDelta, Percona Server - https://mariadb.com/kb/en/mariadb/user-statistics
27 This script is used to generate data for several graphs. To generate
28 data for one specific graph, you need to create a symbolic link with a
29 name like mysql_<GRAPH> to this script.
31 If you need to run against multiple MySQL instances on the same host,
32 create your symlinks with names like mysql<N>_<GRAPH> where N is any
33 non-negative integer. You must also set the env.cachenamespace variable
34 to a unique value for each group of symlinks.
36 To get a list of symlinks that can be created, run:
40 In addition you might need to specify connection parameters in the
41 plugin configuration to override the defaults. These are the defaults:
44 env.mysqlconnection DBI:mysql:information_schema
50 env.mysqlconnection DBI:mysql:information_schema;host=127.0.0.1;port=3306
52 env.mysqlpassword geheim
53 env.cachenamespace munin_mysql_pri
55 env.mysqlconnection DBI:mysql:information_schema;host=127.0.0.1;port=13306
57 env.mysqlpassword ryuWyawEv
58 env.cachenamespace munin_mysql_alt
62 env.mysqlconnection DBI:mysql:information_schema;mysql_read_default_file=/etc/munin/.my-10.cnf
63 env.cachenamespace munin_mysql_10
64 # here the [client] section of /etc/munin/.my-10.cnf is read. socket= can
67 Creating a munin user:
69 CREATE USER 'munin'@'localhost' IDENTIFIED BY 'ryuWyawEv';
71 or with a unix_socket plugin (INSTALL PLUGIN unix_socket SONAME 'auth_socket')
73 CREATE USER 'munin'@'localhost' IDENTIFIED WITH unix_socket;
75 Note: requires 'user munin' in the configuration.
77 The minimum required priviledges of the munin database user is:
79 GRANT PROCESS, REPLICATION CLIENT ON *.* TO 'munin'@'localhost';
82 Warning and critical values can be set via the environment in the usual way.
86 env.slave_io_running_warning 0.5
87 env.slave_sql_running_warning 0.5
88 env.seconds_behind_master_warning 300
89 env.seconds_behind_master_critical 600
97 The plugin uses shared memory to cache the statistics gathered from
98 MySQL. This ensures minimal inpact on the MySQL server.
104 =head1 INTERPRETATION
108 The statistics from innodb are mainly collected from the command
110 SHOW ENGINE INNODB STATUS
112 A nice walk through is found at
113 L<http://www.mysqlperformanceblog.com/2006/07/17/show-innodb-status-walk-through/>
117 FIX point to relevant sections in the MySQL manual and other www
118 resources for each graph
122 =item mysql_replication
124 slave_io_running and slave_sql_running both translate the "Yes" values to 0 and
125 anything else to 1 for their respective fields in the "SHOW SLAVE STATUS" output.
126 This can be used to warn on slave failure if the warning and critical values
127 are set as seen in a previous section.
133 Copyright (C) 2008,2009 Kjell-Magne Øierud
135 This program is free software; you can redistribute it and/or modify
136 it under the terms of the GNU General Public License as published by
137 the Free Software Foundation; version 2 dated June, 1991.
139 This program is distributed in the hope that it will be useful, but
140 WITHOUT ANY WARRANTY; without even the implied warranty of
141 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
142 General Public License for more details.
144 You should have received a copy of the GNU General Public License along
145 with this program; if not, write to the Free Software Foundation, Inc.,
146 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
150 git-master + a few munin modifications
152 This plugin was downloaded from L<http://github.com/kjellm/munin-mysql/>
154 =head1 MAGICK MARKERS
157 #%# capabilities=suggest autoconf
167 use Math
::BigInt
; # Used to append "=> lib 'GMP'" here, but GMP caused
168 # segfault on some occasions. Removed as I don't
169 # think the tiny performance boost is worth the
171 use Storable
qw(nfreeze thaw);
178 eval 'require Cache::SharedMemoryCache';
179 $has_cache = $@ ? 0 : 1;
183 #---------------------------------------------------------------------
185 #---------------------------------------------------------------------
188 'dsn' => $ENV{'mysqlconnection'} || 'DBI:mysql:information_schema',
189 'user' => $ENV{'mysqluser'} || 'root',
190 'password' => $ENV{'mysqlpassword'} || '',
191 'cache_namespace' => $ENV{'cachenamespace'} || 'munin_mysql',
195 #---------------------------------------------------------------------
197 #---------------------------------------------------------------------
199 my %cache_options = (
200 'namespace' => $config{cache_namespace
},
201 'default_expires_in' => 60,
204 my $shared_memory_cache ;
207 $shared_memory_cache = Cache
::SharedMemoryCache-
>new(\
%cache_options)
208 or die("Couldn't instantiate SharedMemoryCache");
211 #---------------------------------------------------------------------
212 # G R A P H D E F I N I T I O N S
213 #---------------------------------------------------------------------
215 # These are defaults to save typing in the graph definitions
218 args
=> '--base 1000',
220 data_source_attrs
=> {
227 # %graphs contains the graph definitions, it is indexed on the graph
228 # name. The information stored for each graph is used for both showing
229 # data source values and for printing the graph configuration. Each
230 # graph follows the followingformat:
234 # # The global attributes for this graph
236 # # Attributes common to all data sources in this graph
237 # data_source_attrs => {}
240 # # NAME - The name of the data source (e.g. variable names
241 # # from SHOW STATUS)
242 # # DATA_SOURCE_ATTRS - key-value pairs with data source
244 # {name => 'NAME', (DATA_SOURCE_ATTRS)},
249 #---------------------------------------------------------------------
251 $graphs{bin_relay_log
} = {
254 title
=> 'Binary/Relay Logs',
255 vlabel
=> 'Log activity (txn/s)',
257 data_source_attrs
=> {
262 {name
=> 'Binlog_cache_disk_use', label
=> 'Binlog Cache Disk Use', info
=> 'Number of transactions which used a temporary disk cache because they could not fit in the regular binary log cache, being larger than binlog_cache_size'},
263 {name
=> 'Binlog_cache_use', label
=> 'Binlog Cache Use', info
=> 'Number of transaction which used the regular binary log cache, being smaller than binlog_cache_size'},
267 #---------------------------------------------------------------------
268 $graphs{binlog_space
} = {
271 title
=> 'Binary log space',
272 vlabel
=> 'Log space',
273 args
=> '--base 1024',
275 data_source_attrs
=> {
281 {name
=> 'ma_binlog_size', label
=> 'Binary Log Space'},
285 #-------------------------
286 $graphs{binlog_groupcommit
} = {
289 title
=> 'Binary Log Group Commits',
290 vlabel
=> 'Commits/Groups',
292 data_source_attrs
=> {
297 {name
=> 'Binlog_commits', label
=> 'Binlog commits'},
298 {name
=> 'Binlog_group_commits', label
=> 'Binlog Group Commits'},
299 {name
=> 'Binlog_group_commit_trigger_count', label
=> 'Binlog Groups because of binlog_commit_wait_count'},
300 {name
=> 'Binlog_group_commit_trigger_timeout', label
=> 'Binlog Groups because of binlog_commit_wait_usec'},
301 {name
=> 'Binlog_group_commit_trigger_lock_wait', label
=> 'Binlog Groups because of transactions'},
305 #---------------------------------------------------------------------
307 $graphs{commands
} = {
310 title
=> 'Command Counters',
311 vlabel
=> 'Commands per ${graph_period}',
312 total
=> 'Questions',
314 data_source_attrs
=> {},
317 {name
=> 'Com_delete', label
=> 'Delete'},
318 {name
=> 'Com_insert', label
=> 'Insert'},
319 {name
=> 'Com_insert_select', label
=> 'Insert select'},
320 {name
=> 'Com_load', label
=> 'Load Data'},
321 {name
=> 'Com_replace', label
=> 'Replace'},
322 {name
=> 'Com_replace_select', label
=> 'Replace select'},
323 {name
=> 'Com_select', label
=> 'Select'},
324 {name
=> 'Com_update', label
=> 'Update'},
325 {name
=> 'Com_update_multi', label
=> 'Update multi'},
329 #---------------------------------------------------------------------
331 $graphs{connections
} = {
334 title
=> 'Connections',
335 vlabel
=> 'Connections per ${graph_period}',
337 data_source_attrs
=> {
342 {name
=> 'max_connections', label
=> 'Max connections',
346 {name
=> 'Max_used_connections', label
=> 'Max used',
350 {name
=> 'Aborted_clients', label
=> 'Aborted clients'},
351 {name
=> 'Aborted_connects', label
=> 'Aborted connects'},
352 {name
=> 'Threads_connected', label
=> 'Threads connected',
354 {name
=> 'Threads_running', label
=> 'Threads running',
356 {name
=> 'Connections', label
=> 'New connections'},
360 #---------------------------------------------------------------------
362 $graphs{files_tables
} = {
365 title
=> 'Files and tables',
368 data_source_attrs
=> {
374 {name
=> 'table_open_cache', label
=> 'Table cache',
377 {name
=> 'Open_files', label
=> 'Open files'},
378 {name
=> 'Open_tables', label
=> 'Open tables'},
379 {name
=> 'Opened_tables', label
=> 'Opened tables',
385 #---------------------------------------------------------------------
387 $graphs{innodb_bpool
} = {
390 title
=> 'InnoDB Buffer Pool',
392 args
=> '--base 1024',
394 data_source_attrs
=> {
400 {name
=> 'ib_bpool_size', label
=> 'Buffer pool size',
403 {name
=> 'ib_bpool_dbpages', label
=> 'Database pages',
406 {name
=> 'ib_bpool_free', label
=> 'Free pages'},
407 {name
=> 'ib_bpool_modpages', label
=> 'Modified pages'},
411 #---------------------------------------------------------------------
413 $graphs{innodb_bpool_act
} = {
416 title
=> 'InnoDB Buffer Pool Activity',
417 vlabel
=> 'Activity per ${graph_period}',
420 data_source_attrs
=> {
425 {name
=> 'ib_bpool_read', label
=> 'Pages read'},
426 {name
=> 'ib_bpool_created', label
=> 'Pages created'},
427 {name
=> 'ib_bpool_written', label
=> 'Pages written'},
431 #---------------------------------------------------------------------
433 $graphs{innodb_insert_buf
} = {
436 title
=> 'InnoDB Insert Buffer',
437 vlabel
=> 'Activity per ${graph_period}',
439 data_source_attrs
=> {
444 {name
=> 'ib_ibuf_inserts', label
=> 'Inserts'},
445 {name
=> 'ib_ibuf_merged_rec', label
=> 'Merged Records'},
446 {name
=> 'ib_ibuf_merges', label
=> 'Merges'},
450 #---------------------------------------------------------------------
452 $graphs{innodb_io
} = {
455 title
=> 'InnoDB IO',
456 vlabel
=> 'IO operations per ${graph_period}',
458 data_source_attrs
=> {
463 {name
=> 'ib_io_read', label
=> 'File reads'},
464 {name
=> 'ib_io_write', label
=> 'File writes'},
465 {name
=> 'ib_io_log', label
=> 'Log writes'},
466 {name
=> 'ib_io_fsync', label
=> 'File syncs'},
470 #---------------------------------------------------------------------
472 $graphs{innodb_io_pend
} = {
475 title
=> 'InnoDB IO Pending',
476 vlabel
=> 'Pending operations',
478 data_source_attrs
=> {
483 {name
=> 'ib_iop_log', label
=> 'AIO Log'},
484 {name
=> 'ib_iop_sync', label
=> 'AIO Sync'},
485 {name
=> 'ib_iop_flush_bpool', label
=> 'Buf Pool Flush'},
486 {name
=> 'ib_iop_flush_log', label
=> 'Log Flushes'},
487 {name
=> 'ib_iop_ibuf_aio', label
=> 'Insert Buf AIO Read'},
488 {name
=> 'ib_iop_aioread', label
=> 'Normal AIO Reads'},
489 {name
=> 'ib_iop_aiowrite', label
=> 'Normal AIO Writes'},
493 #---------------------------------------------------------------------
495 $graphs{innodb_log
} = {
498 title
=> 'InnoDB Log',
499 vlabel
=> 'Log activity per ${graph_period}',
501 data_source_attrs
=> {
506 {name
=> 'innodb_log_buffer_size', label
=> 'Buffer Size',
510 {name
=> 'ib_log_flush', label
=> 'KB Flushed'},
511 {name
=> 'ib_log_written', label
=> 'KB Written'},
515 #---------------------------------------------------------------------
517 $graphs{innodb_rows
} = {
520 title
=> 'InnoDB Row Operations',
521 vlabel
=> 'Operations per ${graph_period}',
524 data_source_attrs
=> {},
527 {name
=> 'Innodb_rows_deleted', label
=> 'Deletes'},
528 {name
=> 'Innodb_rows_inserted', label
=> 'Inserts'},
529 {name
=> 'Innodb_rows_read', label
=> 'Reads'},
530 {name
=> 'Innodb_rows_updated', label
=> 'Updates'},
534 #---------------------------------------------------------------------
536 $graphs{innodb_semaphores
} = {
539 title
=> 'InnoDB Semaphores',
540 vlabel
=> 'Semaphores per ${graph_period}',
542 data_source_attrs
=> {
547 {name
=> 'ib_spin_rounds', label
=> 'Spin Rounds'},
548 {name
=> 'ib_spin_waits', label
=> 'Spin Waits'},
549 {name
=> 'ib_os_waits', label
=> 'OS Waits'},
553 #---------------------------------------------------------------------
555 $graphs{innodb_tnx
} = {
558 title
=> 'InnoDB Transactions',
559 vlabel
=> 'Transactions per ${graph_period}',
561 data_source_attrs
=> {
566 {name
=> 'ib_tnx', label
=> 'Transactions created'},
570 #---------------------------------------------------------------------
572 $graphs{myisam_indexes
} = {
575 title
=> 'MyISAM Indexes',
576 vlabel
=> 'Requests per ${graph_period}',
578 data_source_attrs
=> {
583 {name
=> 'Key_read_requests', label
=> 'Key read requests'},
584 {name
=> 'Key_reads', label
=> 'Key reads'},
585 {name
=> 'Key_write_requests', label
=> 'Key write requests'},
586 {name
=> 'Key_writes', label
=> 'Key writes'},
590 #---------------------------------------------------------------------
592 $graphs{network_traffic
} = {
595 title
=> 'Network Traffic',
596 args
=> '--base 1024',
597 vlabel
=> 'Bytes received (-) / sent (+) per ${graph_period}',
599 data_source_attrs
=> {
604 {name
=> 'Bytes_received', label
=> 'Bytes transfered',
606 {name
=> 'Bytes_sent', label
=> 'Bytes transfered',
607 negative
=> 'Bytes_received'},
611 #---------------------------------------------------------------------
616 title
=> 'Query Cache',
617 vlabel
=> 'Commands per ${graph_period}',
619 data_source_attrs
=> {
624 {name
=> 'Qcache_queries_in_cache', label
=> 'Queries in cache'},
625 {name
=> 'Qcache_hits', label
=> 'Cache hits'},
626 {name
=> 'Qcache_inserts', label
=> 'Inserts'},
627 {name
=> 'Qcache_not_cached', label
=> 'Not cached'},
628 {name
=> 'Qcache_lowmem_prunes', label
=> 'Low-memory prunes'},
632 #---------------------------------------------------------------------
634 $graphs{qcache_mem
} = {
637 title
=> 'Query Cache Memory',
639 args
=> '--base 1024 --lower-limit 0',
641 data_source_attrs
=> {
647 {name
=> 'query_cache_size', label
=> 'Cache size'},
648 {name
=> 'Qcache_free_memory', label
=> 'Free mem'},
652 #---------------------------------------------------------------------
654 $graphs{replication
} = {
657 title
=> 'Replication',
658 vlabel
=> 'Activity',
660 data_source_attrs
=> {
665 {name
=> 'slave_io_running', label
=> 'Slave IO Running',
668 {name
=> 'slave_sql_running', label
=> 'Slave SQL Running',
671 {name
=> 'Slave_retried_transactions', label
=> 'Retried Transactions'},
672 {name
=> 'Slave_open_temp_tables', label
=> 'Open Temp Tables'},
673 {name
=> 'seconds_behind_master', label
=> 'Secs Behind Master',
678 #---------------------------------------------------------------------
680 $graphs{select_types
} = {
683 title
=> 'Select types',
684 vlabel
=> 'Commands per ${graph_period}',
687 data_source_attrs
=> {},
690 {name
=> 'Select_full_join', label
=> 'Full join'},
691 {name
=> 'Select_full_range_join', label
=> 'Full range'},
692 {name
=> 'Select_range', label
=> 'Range'},
693 {name
=> 'Select_range_check', label
=> 'Range check'},
694 {name
=> 'Select_scan', label
=> 'Scan'},
698 #---------------------------------------------------------------------
703 title
=> 'Slow Queries',
704 vlabel
=> 'Slow queries per ${graph_period}',
707 data_source_attrs
=> {
712 {name
=> 'Slow_queries', label
=> 'Slow queries'},
716 #---------------------------------------------------------------------
722 vlabel
=> 'Sorts / ${graph_period}',
724 data_source_attrs
=> {
729 {name
=> 'Sort_rows', label
=> 'Rows sorted'},
730 {name
=> 'Sort_range', label
=> 'Range'},
731 {name
=> 'Sort_merge_passes', label
=> 'Merge passes'},
732 {name
=> 'Sort_scan', label
=> 'Scan'},
736 #---------------------------------------------------------------------
738 $graphs{table_locks
} = {
741 title
=> 'Table locks',
742 vlabel
=> 'locks per ${graph_period}',
744 data_source_attrs
=> {
749 {name
=> 'Table_locks_immediate', label
=> 'Table locks immed'},
750 {name
=> 'Table_locks_waited', label
=> 'Table locks waited'},
754 #---------------------------------------------------------------------
756 $graphs{tmp_tables
} = {
759 title
=> 'Temporary objects',
760 vlabel
=> 'Objects per ${graph_period}',
762 data_source_attrs
=> {
767 {name
=> 'Created_tmp_disk_tables', label
=> 'Temp disk tables'},
768 {name
=> 'Created_tmp_tables', label
=> 'Temp tables'},
769 {name
=> 'Created_tmp_files', label
=> 'Temp files'},
773 #---------------------------------------------------------------------
775 # These are mysql plugins of type INFORMATION SCHEMA
777 # These will be added to $graphs if available
778 #---------------------------------------------------------------------
780 my %graph_plugins = ();
782 $graph_plugins{query_response_time
} = {
786 title
=> 'Query Response Time Count',
787 vlabel
=> 'queries per ${graph_period}',
789 data_source_attrs
=> {
794 # data_sources are populated by sub plugin_query_response_time
801 title
=> 'Query Response Time Total',
802 vlabel
=> 'query time (microseconds) per ${graph_period}',
804 data_source_attrs
=> {
809 # data_sources are populated by sub plugin_query_response_time
815 $graph_plugins{user_statistics
} = {
819 title
=> 'User Connections',
820 vlabel
=> 'connections per ${graph_period}',
822 data_source_attrs
=> {
827 cols
=> { 'total_connections' => {}, 'concurrent_connections' => {}, 'denied_connections' => {}, 'lost_connections' => {}},
834 title
=> 'User Time',
837 data_source_attrs
=> {
842 cols
=> { 'connected_time' => {}, 'busy_time' => {}, 'cpu_time' => {} },
849 title
=> 'User Bytes',
852 data_source_attrs
=> {
857 cols
=> { 'bytes_received' => {}, 'bytes_sent' => {}, 'binlog_bytes_written' => {} },
864 title
=> 'User Rows',
867 data_source_attrs
=> {
872 cols
=> { 'rows_read' => {}, 'rows_sent' => {}, 'rows_deleted' => {}, 'rows_inserted' => {}, 'rows_updated' => {} },
879 title
=> 'Command breakdown by user',
880 vlabel
=> 'commands',
882 data_source_attrs
=> {
887 cols
=> { 'select_commands' => {}, 'update_commands' => {}, 'other_commands' => {}, 'commit_transactions' => {}, 'rollback_transactions' => {} },
893 #---------------------------------------------------------------------
895 #---------------------------------------------------------------------
899 # Global hash holding the data collected from mysql.
901 our $data; # Was 'my'. Changed to 'our' to facilitate testing.
905 my $graph = basename
($0);
906 $graph =~ s/^mysql[0-9]*_//g; # allow multiple instances
907 my $command = $ARGV[0] || 'show';
910 'autoconf' => \
&autoconf
,
911 'config' => \
&config
,
913 'suggest' => \
&suggest
,
916 die "Unknown command: $command"
917 unless exists $command_map{$command};
919 die "Missing dependency Cache::Cache"
920 unless $has_cache || $command eq 'autoconf';
922 return $command_map{$command}->($graph);
926 #---------------------------------------------------------------------
927 # C O M M A N D H A N D L E R S
928 #---------------------------------------------------------------------
930 # Each command handler should return an appropriate exit code
933 # http://munin-monitoring.org/wiki/ConcisePlugins#autoconf
935 unless ($has_cache) {
936 print "no (Missing dependency Cache::Cache)\n";
945 $err =~ s{\s at \s \S+ \s line .*}{}xms;
954 # http://munin-monitoring.org/wiki/ConcisePlugins#suggest
957 # What is the best way to decide which graphs is applicable to a
960 # Does the database use InnoDB? A zero count from:
964 # WHERE table_type = 'base table'
965 # AND engine = 'innodb'
967 # Does the database use binary logs? 'OFF' as the result from:
969 # SHOW GLOBAL variables LIKE 'log_bin'
971 # Is the database setup as a slave? Empty result from:
975 foreach my $graph (sort keys(%graphs)) {
976 next if $graph =~ /innodb_/ && $data->{_innodb_disabled
};
977 next if $graph =~ /wsrep_/ && $data->{_galera_disabled
};
986 my $graph_name = shift;
988 # In MySQL 5.1 (and probably erlier versions as well) status
989 # variables are unique when looking at the last 19 characters.
991 # SELECT RIGHT(variable_name, 19), COUNT(*)
992 # FROM information_schema.global_status
993 # GROUP BY RIGHT(variable_name, 19)
994 # HAVING COUNT(*) > 1;
996 # Empty set (0.06 sec)
998 # There is one duplicate when looking at server variables
1000 # SELECT RIGHT(variable_name, 19), COUNT(*)
1001 # FROM information_schema.global_variables
1002 # GROUP BY RIGHT(variable_name, 19)
1003 # HAVING COUNT(*) > 1;
1005 # +--------------------------+----------+
1006 # | RIGHT(variable_name, 19) | COUNT(*) |
1007 # +--------------------------+----------+
1008 # | OW_PRIORITY_UPDATES | 2 |
1009 # +--------------------------+----------+
1010 # 1 row in set (0.05 sec)
1012 # show global variables like '%OW_PRIORITY_UPDATES';
1014 # +--------------------------+-------+
1015 # | Variable_name | Value |
1016 # +--------------------------+-------+
1017 # | low_priority_updates | OFF |
1018 # | sql_low_priority_updates | OFF |
1019 # +--------------------------+-------+
1020 # 2 rows in set (0.00 sec)
1022 # Not a problem since we don't graph these
1026 die 'Unknown graph ' . ($graph_name ? $graph_name : '')
1027 unless $graphs{$graph_name};
1029 my $graph = $graphs{$graph_name};
1031 my %conf = (%{$defaults{global_attrs
}}, %{$graph->{config
}{global_attrs
}});
1032 while (my ($k, $v) = each %conf) {
1033 print "graph_$k $v\n";
1035 print "graph_category mysql2\n";
1037 for my $ds (@{$graph->{data_sources
}}) {
1039 %{$defaults{data_source_attrs
}},
1040 %{$graph->{config
}{data_source_attrs
}},
1043 while (my ($k, $v) = each %ds_spec) {
1044 # 'name' is only used internally in this script, not understood by munin.
1045 printf("%s.%s %s\n", clean_fieldname
($ds->{name
}), $k, $v) unless ($k eq 'name');
1047 print_thresholds
(clean_fieldname
($ds->{name
}));
1054 my $graph_name = shift;
1058 die 'Unknown graph ' . ($graph_name ? $graph_name : '')
1059 unless $graphs{$graph_name};
1061 my $graph = $graphs{$graph_name};
1063 die "Can't show data for '$graph_name' because InnoDB is disabled."
1064 if $graph_name =~ /innodb_/ && $data->{_innodb_disabled
};
1066 for my $ds (@{$graph->{data_sources
}}) {
1067 my $value = exists $ds->{value
}
1068 ? $ds->{value
}($data)
1069 : $data->{$ds->{name
}};
1071 printf "%s.value %s\n", clean_fieldname
($ds->{name
}), defined($value) ? $value : 'U';
1079 #---------------------------------------------------------------------
1080 # U T I L I T Y S U B S
1081 #---------------------------------------------------------------------
1085 my $dsn = "$config{dsn};mysql_connect_timeout=5";
1087 return DBI-
>connect($dsn, $config{user
}, $config{password
}, {
1090 FetchHashKeyName
=> 'NAME_lc',
1096 $data = $shared_memory_cache->get('data');
1097 my $graphs_stored = $shared_memory_cache->get('graphs');
1098 %graphs = %{thaw
($graphs_stored)} if $graphs_stored;
1101 #warn "Need to update cache";
1105 my $dbh = db_connect
();
1107 # Set up defaults in case the server is not a slave
1108 $data->{relay_log_space
} = 0;
1109 $data->{slave_running
} = 0;
1110 $data->{slave_stopped
} = 0;
1112 # Set up defaults in case binlog is not enabled
1113 $data->{ma_binlog_size
} = 0;
1115 update_variables
($dbh);
1116 update_plugins
($dbh);
1117 update_innodb
($dbh);
1118 update_master
($dbh);
1121 $shared_memory_cache->set('data', $data);
1122 $shared_memory_cache->set('graphs', nfreeze
(\
%graphs));
1126 sub update_plugins
{
1130 'query_response_time' => \
&plugin_query_response_time
,
1134 my ($f, $sec, $dbh, %g) = @_;
1135 if ($f->($dbh) == 0) {
1136 while (my ($k, $v) = each %g) {
1137 $graphs{$sec . '_' . $k} = $v;
1142 my $sth = $dbh->prepare("SHOW PLUGINS");
1144 while (my $row = $sth->fetchrow_hashref()) {
1145 next if $row->{'type'} ne 'INFORMATION SCHEMA';
1146 my $sec = lc $row->{'name'};
1147 next if not exists $plugin_map{$sec};
1148 add_graphs
($plugin_map{$sec}, $sec, $dbh, %{$graph_plugins{$sec}});
1153 'user_statistics' => \
&is_user_statistics
,
1156 $sth = $dbh->prepare("SHOW TABLES IN INFORMATION_SCHEMA");
1158 while (my $row = $sth->fetchrow_hashref()) {
1159 my $sec = lc $row->{'tables_in_information_schema'};
1160 next if not exists $is_map{$sec};
1161 add_graphs
($is_map{$sec}, $sec, $dbh, %{$graph_plugins{$sec}});
1166 sub update_variables
{
1169 'SHOW GLOBAL STATUS',
1170 'SHOW GLOBAL VARIABLES',
1173 my %variable_name_map = (
1174 table_cache
=> 'table_open_cache', # table_open_cache was
1175 # previously known as
1176 # table_cache in MySQL
1177 # 5.1.2 and earlier.
1180 for my $query (@queries) {
1181 $data->{$query} = {};
1183 my $sth = $dbh->prepare($query);
1185 while (my $row = $sth->fetch) {
1186 my $var = $variable_name_map{$row->[0]} || $row->[0];
1187 $data->{$var} = $row->[1];
1197 my $sth = $dbh->prepare('SHOW /*!50000 ENGINE*/ INNODB STATUS');
1202 if ($@ =~ /Unknown (storage|table) engine 'INNODB'|Cannot call SHOW INNODB STATUS because skip-innodb is defined/i) {
1203 $data->{_innodb_disabled
} = 1;
1208 my $row = $sth->fetchrow_hashref();
1209 my $status = $row->{'status'};
1212 parse_innodb_status
($status);
1219 my $sth = $dbh->prepare('SHOW MASTER LOGS');
1224 # SHOW MASTER LOGS failed becuase binlog is not enabled
1225 return if $@ =~ /You are not using binary logging/;
1229 while (my $row = $sth->fetch) {
1230 $data->{ma_binlog_size
} += $row->[1];
1240 my $sth = $dbh->prepare('SHOW SLAVE STATUS');
1242 my $row = $sth->fetchrow_hashref();
1244 while (my ($k, $v) = each %$row) {
1249 # undef when slave is stopped, or when MySQL fails to calculate
1250 # the lag (which happens depresingly often). (mk-heartbeat fixes
1252 $data->{seconds_behind_master
} ||= 0;
1254 # Track these two fields so we can trigger warnings if the slave stops
1256 $data->{slave_sql_running
} = ($data->{slave_sql_running
} eq 'Yes')
1258 $data->{slave_io_running
} = ($data->{slave_io_running
} eq 'Yes')
1264 #---------------------------------------------------------------------
1265 # Information SCHEMA tables represent data to be processed
1266 #---------------------------------------------------------------------
1269 sub plugin_query_response_time
{
1272 return 1 if not defined $data->{query_response_time_stats
};
1273 return 1 if $data->{query_response_time_stats
} eq 'OFF';
1275 my $sth = $dbh->prepare("SELECT * FROM INFORMATION_SCHEMA.QUERY_RESPONSE_TIME");
1277 while (my $row = $sth->fetchrow_hashref()) {
1278 my $time = $row->{'time'};
1279 $data->{'query_response_time_count_' . $time} = $row->{'count'};
1280 push @{$graph_plugins{query_response_time
}->{count
}->{data_sources
}}, {name
=> 'query_response_time_count_' . $time, label
=> $time };
1281 next if $row->{'total'} eq 'TOO LONG';
1282 $data->{'query_response_time_total_' . $time} = $row->{'total'} * 1e6;
1283 push @{$graph_plugins{query_response_time
}->{total
}->{data_sources
}}, {name
=> 'query_response_time_total_' . $time, label
=> $time };
1290 sub is_user_statistics
{
1293 return 1 if not defined $data->{userstat
};
1294 return 1 if $data->{userstat
} eq 'OFF';
1296 my $sth = $dbh->prepare("SELECT * FROM INFORMATION_SCHEMA.USER_STATISTICS");
1298 while (my $row = $sth->fetchrow_hashref()) {
1299 my $user = $row->{'user'};
1301 while (my ($g, $v) = each %{$graph_plugins{user_statistics
}}) {
1302 while (my ($userstat,$conf) = each %{$v->{cols
}}) {
1303 $var = 'user_stats_' . $user . '_' . $userstat;
1304 $data->{$var} = int $row->{$userstat};
1305 my $ds = { %$conf };
1307 $ds->{label
} = $user . ' ' . $userstat;
1308 push @{$graph_plugins{user_statistics
}->{$g}->{data_sources
}}, $ds;
1317 # In 'SHOW ENGINE INNODB STATUS' 64 bit integers are not formated as
1318 # plain integers. They are either:
1320 # - split in two and needs to be shifted together,
1327 ? Math
::BigInt-
>new($x)->blsft(32) + $y
1328 : Math
::BigInt-
>new("0x$x");
1331 #---------------------------------------------------------------------
1332 # P A R S E 'SHOW ENGINE INNODB STATUS' O U T P U T
1333 #---------------------------------------------------------------------
1336 # A nice walk through
1337 # http://www.mysqlperformanceblog.com/2006/07/17/show-innodb-status-walk-through/
1339 # The parsing is split in one subrutine per section. Each subroutine
1340 # should parse a block with the following structure
1346 sub parse_innodb_status
{
1349 # Add a dummy section to the end in case the innodb status output
1350 # has been truncated (Happens for status > 64K characters)
1351 $_ .= "\n----------\nDUMMY\n";
1355 'BUFFER POOL AND MEMORY' => \
&parse_buffer_pool_and_memory
,
1356 'INDIVIDUAL BUFFER POOL INFO' => \
&skip
,
1357 'FILE I/O' => \
&parse_file_io
,
1358 'INSERT BUFFER AND ADAPTIVE HASH INDEX'
1359 => \
&parse_insert_buffer_and_adaptive_hash_index
,
1360 'LATEST DETECTED DEADLOCK' => \
&skip
,
1361 'LATEST FOREIGN KEY ERROR' => \
&skip
,
1362 'LOG' => \
&parse_log
,
1363 'ROW OPERATIONS' => \
&skip
,
1364 'SEMAPHORES' => \
&parse_semaphores
,
1365 'TRANSACTIONS' => \
&parse_transactions
,
1366 'BACKGROUND THREAD' => \
&skip
,
1374 last if $sec eq 'END OF INNODB MONITOR OUTPUT';
1375 if ($sec eq 'DUMMY') {
1376 handle_incomplete_innodb_status
();
1380 die "Unknown section: $1" unless exists $section_map{$sec};
1381 die "Parse error. Expected a section separator" unless m/\G-+\n/gc;
1383 $section_map{$sec}->();
1388 # This regular expression handles the different formating of 64-bit
1389 # integers in different versions of the innodb engine. Either two
1390 # decimal 32-bit integers seperated by a space, or a single
1391 # hexadecimal 64-bit integer.
1392 my $innodb_bigint_rx = qr{([[a-fA-F\d]+)(?: (\d+))?};
1395 sub match_dashes
{ return m/\G-+\n(?!-)/gc; }
1398 sub skip_line
{ return m/\G.*\n/gc; }
1402 # Heading is 6 lines
1403 for my $foo (1...6) {
1404 skip_line
or die('Parse error');
1412 #warn substr($_, pos(), 10);
1414 return if match_dashes
();
1415 next if $parser->();
1421 sub skip
{ parse_section
(sub {}); }
1424 sub parse_semaphores
{
1427 m/\GMutex spin waits (\d+), rounds (\d+), OS waits (\d+)\n/gc && do {
1428 $data->{ib_spin_waits
} = $1;
1429 $data->{ib_spin_rounds
} = $2;
1430 $data->{ib_os_waits
} = $3;
1438 sub parse_transactions
{
1441 m/\GTrx id counter $innodb_bigint_rx\n/gc && do {
1442 $data->{ib_tnx
} = innodb_bigint
($1, $2);
1445 m/\GPurge done for trx's n:o < $innodb_bigint_rx undo n:o < $innodb_bigint_rx\n/gc && do {
1448 $data->{ib_tnx_prg
} = innodb_bigint
($1, $2);
1449 # FIX add to data? innodb_bigint($3, $4);
1453 $data->{ib_tnx_prg
} = innodb_bigint
($1);
1454 # FIX add to data? innodb_bigint($2);
1458 m/\GHistory list length (\d+)\n/gc && do {
1459 $data->{ib_tnx_hist
} = $1;
1471 m/\GPending normal aio reads: (\d+), aio writes: (\d+),\n\s*ibuf aio reads: (\d+), log i\/o's: (\d+), sync i\/o's
: (\d
+)\n/gc
&& do {
1472 $data->{ib_iop_aioread
} = $1;
1473 $data->{ib_iop_aiowrite
} = $2;
1474 $data->{ib_iop_ibuf_aio
} = $3;
1475 $data->{ib_iop_log
} = $4;
1476 $data->{ib_iop_sync
} = $5;
1479 m/\GPending flushes \(fsync\) log: (\d+); buffer pool: (\d+)\n/gc && do {
1480 $data->{ib_iop_flush_log
} = $1;
1481 $data->{ib_iop_flush_bpool
} = $2;
1484 m/\G(\d+) OS file reads, (\d+) OS file writes, (\d+) OS fsyncs\n/gc && do {
1485 $data->{ib_io_read
} = $1;
1486 $data->{ib_io_write
} = $2;
1487 $data->{ib_io_fsync
} = $3;
1495 sub parse_insert_buffer_and_adaptive_hash_index
{
1499 m/\G(\d+) inserts, (\d+) merged recs, (\d+) merges\n/gc && do {
1500 $data->{ib_ibuf_inserts
} = $1;
1501 $data->{ib_ibuf_merged_rec
} = $2;
1502 $data->{ib_ibuf_merges
} = $3;
1506 m/\Gmerged operations:\n insert (\d+), delete mark \d+, delete \d+\ndiscarded operations:\n insert (\d+), delete mark \d+, delete \d+\n/gc && do {
1507 $data->{ib_ibuf_inserts
} = $1;
1508 $data->{ib_ibuf_merged_rec
} = $1 + $2;
1511 m/\GIbuf: size (\d+), free list len (\d+), seg size (\d+),(?: (\d+) merges)?\n/gc && do {
1512 $data->{ib_ibuf_size
} = $1;
1513 $data->{ib_ibuf_free_len
} = $2;
1514 $data->{ib_ibuf_seg_size
} = $3;
1515 $data->{ib_ibuf_merges
} = $4 if defined $4; # MySQL >= 5.5
1526 m/\GLog sequence number $innodb_bigint_rx\n/gc && do {
1527 $data->{ib_log_written
} = innodb_bigint
($1, $2);
1530 m/\GLog flushed up to\s+$innodb_bigint_rx\n/gc && do {
1531 $data->{ib_log_flush
} = innodb_bigint
($1, $2);
1534 m/\G(\d+) log i\/o's done.*\n/gc && do {
1535 $data->{ib_io_log} = $1;
1543 sub parse_buffer_pool_and_memory {
1546 m/\GBuffer pool size\s+(\d+)\n/gc && do {
1547 $data->{ib_bpool_size} = $1;
1550 m/\GFree buffers\s+(\d+)\n/gc && do {
1551 $data->{ib_bpool_free} = $1;
1554 m/\GDatabase pages\s+(\d+)\n/gc && do {
1555 $data->{ib_bpool_dbpages} = $1;
1558 m/\GModified db pages\s+(\d+)\n/gc && do {
1559 $data->{ib_bpool_modpages} = $1;
1562 m/\GPages read (\d+), created (\d+), written (\d+)\n/gc && do {
1563 $data->{ib_bpool_read} = $1;
1564 $data->{ib_bpool_created} = $2;
1565 $data->{ib_bpool_written} = $3;
1573 sub handle_incomplete_innodb_status {
1575 warn "Output from SHOW ENGINE INNDOB STATUS was truncated. "
1576 . "This happens if the output of SEIS exceeds 64KB. "
1577 . "Several of the InnoDB graphs might be affected by this.";
1579 # FIX Is it possible to find some of the missing values from SHOW
1584 exit main() unless caller;