# This program is copyright 2011-2012 Percona Inc. # Feedback and improvements are welcome. # # THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED # WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF # MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, version 2; OR the Perl Artistic License. On UNIX and similar # systems, you can issue `man perlgpl' or `man perlartistic' to read these # licenses. # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., 59 Temple # Place, Suite 330, Boston, MA 02111-1307 USA. # ########################################################################### # collect_mysql_info package # ########################################################################### # Package: collect_mysql_info # collect collects mysql information. # XXX # THIS LIB REQUIRES log_warn_die.sh, summary_common.sh, and alt_cmds.sh! # XXX CMD_MYSQL="${CMD_MYSQL:-""}" CMD_MYSQLDUMP="${CMD_MYSQLDUMP:-""}" # Simply looks for instances of mysqld in the outof of ps. collect_mysqld_instances () { local variables_file="$1" local pids="$(_pidof mysqld)" if [ -n "$pids" ]; then for pid in $pids; do local nice="$( get_nice_of_pid $pid )" local oom="$( get_oom_of_pid $pid )" echo "internal::nice_of_$pid $nice" >> "$variables_file" echo "internal::oom_of_$pid $oom" >> "$variables_file" done pids="$(echo $pids | sed -e 's/ /,/g')" ps ww -p "$pids" 2>/dev/null else echo "mysqld doesn't appear to be running" fi } # Tries to find the my.cnf file by examining 'ps' output. # You have to specify the port for the instance you are # interested in, in case there are multiple instances. find_my_cnf_file() { local file="$1" local port="${2:-""}" local cnf_file="" if [ "$port" ]; then # Find the cnf file for the specific port. cnf_file="$(grep --max-count 1 "/mysqld.*--port=$port" "$file" \ | awk 'BEGIN{RS=" "; FS="=";} $1 ~ /--defaults-file/ { print $2; }')" else # Find the cnf file for the first mysqld instance. cnf_file="$(grep --max-count 1 '/mysqld' "$file" \ | awk 'BEGIN{RS=" "; FS="=";} $1 ~ /--defaults-file/ { print $2; }')" fi if [ -z "$cnf_file" ]; then # Cannot autodetect config file, try common locations. if [ -e "/etc/my.cnf" ]; then cnf_file="/etc/my.cnf" elif [ -e "/etc/mysql/my.cnf" ]; then cnf_file="/etc/mysql/my.cnf" elif [ -e "/var/db/mysql/my.cnf" ]; then cnf_file="/var/db/mysql/my.cnf"; fi fi echo "$cnf_file" } collect_mysql_variables () { $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW /*!40100 GLOBAL*/ VARIABLES' } collect_mysql_status () { $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW /*!50000 GLOBAL*/ STATUS' } collect_mysql_databases () { $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW DATABASES' 2>/dev/null } collect_mysql_plugins () { $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW PLUGINS' 2>/dev/null } collect_mysql_slave_status () { $CMD_MYSQL $EXT_ARGV -ssE -e 'SHOW SLAVE STATUS' 2>/dev/null } collect_mysql_innodb_status () { $CMD_MYSQL $EXT_ARGV -ssE -e 'SHOW /*!50000 ENGINE*/ INNODB STATUS' 2>/dev/null } collect_mysql_ndb_status () { $CMD_MYSQL $EXT_ARGV -ssE -e 'show /*!50000 ENGINE*/ NDB STATUS' 2>/dev/null } collect_mysql_processlist () { $CMD_MYSQL $EXT_ARGV -ssE -e 'SHOW FULL PROCESSLIST' 2>/dev/null } collect_mysql_users () { # The where clause has been added to skip listing MySQL 8+ roles as users. # ROLES are locked accounts, without passwords and expired. $CMD_MYSQL $EXT_ARGV -ss -e 'SELECT COUNT(*), SUM(user=""), SUM(password=""), SUM(password NOT LIKE "*%") FROM mysql.user' 2>/dev/null if [ "$?" -ne 0 ]; then $CMD_MYSQL $EXT_ARGV -ss -e 'SELECT COUNT(*), SUM(user=""), SUM(authentication_string=""), SUM(authentication_string NOT LIKE "*%") FROM mysql.user WHERE account_locked <> "Y" AND password_expired <> "Y" AND authentication_string <> ""' 2>/dev/null fi } collect_mysql_roles () { QUERY="SELECT DISTINCT User 'Role Name', if(from_user is NULL,0, 1) Active FROM mysql.user LEFT JOIN mysql.role_edges ON from_user=user WHERE account_locked='Y' AND password_expired='Y' AND authentication_string=''\G" $CMD_MYSQL $EXT_ARGV -ss -e "$QUERY" 2>/dev/null } collect_mysql_show_slave_hosts () { $CMD_MYSQL $EXT_ARGV -ssE -e 'SHOW SLAVE HOSTS' 2>/dev/null } collect_master_logs_status () { local master_logs_file="$1" local master_status_file="$2" $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW MASTER LOGS' > "$master_logs_file" 2>/dev/null $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW MASTER STATUS' > "$master_status_file" 2>/dev/null } # Somewhat different from the others, this one joins the status we got earlier collect_mysql_deferred_status () { local status_file="$1" collect_mysql_status > "$PT_TMPDIR/defer_gatherer" join "$status_file" "$PT_TMPDIR/defer_gatherer" } collect_internal_vars () { local mysqld_executables="${1:-""}" local FNV_64="" if $CMD_MYSQL $EXT_ARGV -e 'SELECT FNV_64("a")' >/dev/null 2>&1; then FNV_64="Enabled"; else FNV_64="Unknown"; fi local now="$($CMD_MYSQL $EXT_ARGV -ss -e 'SELECT NOW()')" local user="$($CMD_MYSQL $EXT_ARGV -ss -e 'SELECT CURRENT_USER()')" local trigger_count=$($CMD_MYSQL $EXT_ARGV -ss -e "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TRIGGERS" 2>/dev/null) echo "pt-summary-internal-mysql_executable $CMD_MYSQL" echo "pt-summary-internal-now $now" echo "pt-summary-internal-user $user" echo "pt-summary-internal-FNV_64 $FNV_64" echo "pt-summary-internal-trigger_count $trigger_count" if [ -e "$mysqld_executables" ]; then local i=1 while read executable; do echo "pt-summary-internal-mysqld_executable_${i} $(has_symbols "$executable")" i=$(($i + 1)) done < "$mysqld_executables" fi } # Uses mysqldump and dumps the results to FILE. # args and dbtodump are passed to mysqldump. get_mysqldump_for () { local args="$1" local dbtodump="${2:-"--all-databases"}" $CMD_MYSQLDUMP $EXT_ARGV --no-data --skip-comments \ --skip-add-locks --skip-add-drop-table --compact \ --skip-lock-all-tables --skip-lock-tables --skip-set-charset \ ${args} --databases $(local IFS=,; echo ${dbtodump}) } # Returns a string with arguments to pass to mysqldump. # Takes one argument, which should be a get_mysqldump_args () { local file="$1" local trg_arg="" # If mysqldump supports triggers, then add options for routines. if $CMD_MYSQLDUMP --help --verbose 2>&1 | grep triggers >/dev/null; then # "mysqldump supports triggers" trg_arg="--routines" fi if [ "${trg_arg}" ]; then # Find out if there are any triggers. If there are none, we will skip # that option to mysqldump, because when mysqldump checks for them, it # can take a long time, one table at a time. local triggers="--skip-triggers" local trg=$(get_var "pt-summary-internal-trigger_count" "$file" ) if [ -n "${trg}" ] && [ "${trg}" -gt 0 ]; then triggers="--triggers" fi trg_arg="${trg_arg} ${triggers}"; fi echo "${trg_arg}" } collect_mysqld_executables () { local mysqld_instances="$1" local ps_opt="cmd=" if [ "$(uname -s)" = "Darwin" ]; then ps_opt="command=" fi for pid in $( grep '/mysqld' "$mysqld_instances" | awk '/^.*[0-9]/{print $1}' ); do ps -o $ps_opt -p $pid | sed -e 's/^\(.*mysqld\) .*/\1/' done | sort -u } collect_mysql_info () { local dir="$1" collect_mysql_variables > "$dir/mysql-variables" collect_mysql_status > "$dir/mysql-status" collect_mysql_databases > "$dir/mysql-databases" collect_mysql_plugins > "$dir/mysql-plugins" collect_mysql_slave_status > "$dir/mysql-slave" collect_mysql_innodb_status > "$dir/innodb-status" collect_mysql_ndb_status > "$dir/ndb-status" collect_mysql_processlist > "$dir/mysql-processlist" collect_mysql_users > "$dir/mysql-users" collect_mysql_roles > "$dir/mysql-roles" collect_mysqld_instances "$dir/mysql-variables" > "$dir/mysqld-instances" collect_mysqld_executables "$dir/mysqld-instances" > "$dir/mysqld-executables" collect_mysql_show_slave_hosts "$dir/mysql-slave-hosts" > "$dir/mysql-slave-hosts" local binlog="$(get_var log_bin "$dir/mysql-variables")" if [ "${binlog}" ]; then # "Got a binlog, going to get MASTER LOGS and MASTER STATUS" collect_master_logs_status "$dir/mysql-master-logs" "$dir/mysql-master-status" fi local uptime="$(get_var Uptime "$dir/mysql-status")" local current_time="$($CMD_MYSQL $EXT_ARGV -ss -e \ "SELECT LEFT(NOW() - INTERVAL ${uptime} SECOND, 16)")" local port="$(get_var port "$dir/mysql-variables")" local cnf_file="$(find_my_cnf_file "$dir/mysqld-instances" ${port})" [ -e "$cnf_file" ] && cat "$cnf_file" > "$dir/mysql-config-file" local pid_file="$(get_var "pid_file" "$dir/mysql-variables")" local pid_file_exists="" [ -e "${pid_file}" ] && pid_file_exists=1 echo "pt-summary-internal-pid_file_exists $pid_file_exists" >> "$dir/mysql-variables" # TODO: Do these require a file of their own? echo "pt-summary-internal-current_time $current_time" >> "$dir/mysql-variables" echo "pt-summary-internal-Config_File_path $cnf_file" >> "$dir/mysql-variables" collect_internal_vars "$dir/mysqld-executables" >> "$dir/mysql-variables" # mysqldump schemas if [ "$OPT_DATABASES" -o "$OPT_ALL_DATABASES" ]; then local trg_arg="$(get_mysqldump_args "$dir/mysql-variables")" local dbs="${OPT_DATABASES:-""}" get_mysqldump_for "${trg_arg}" "$dbs" > "$dir/mysqldump" fi # TODO: gather this data in the same format as normal: TS line, stats ( sleep $OPT_SLEEP collect_mysql_deferred_status "$dir/mysql-status" > "$dir/mysql-status-defer" ) & _d "Forked child is $!" } # ########################################################################### # End collect_mysql_info package # ###########################################################################