diff --git a/bin/pt-mysql-summary b/bin/pt-mysql-summary index b360b31b..d336f319 100755 --- a/bin/pt-mysql-summary +++ b/bin/pt-mysql-summary @@ -6,8 +6,6 @@ set -u -PTDEBUG="${PTDEBUG:-""}" - # ########################################################################### # log_warn_die package # This package is a copy without comments from the original. The original @@ -20,6 +18,7 @@ PTDEBUG="${PTDEBUG:-""}" set -u +PTDEBUG="${PTDEBUG:-""}" EXIT_STATUS=0 log() { @@ -37,10 +36,8 @@ die() { exit 1 } -debug_log () { - if [ -n "$PTDEBUG" ]; then - log "$*" >&2 - fi +_d () { + [ "$PTDEBUG" ] && echo "# $(log "$@")" >&2 } # ########################################################################### @@ -56,6 +53,10 @@ debug_log () { # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### + + + + set -u ARGV="" # Non-option args (probably input files) @@ -249,7 +250,7 @@ _eval_po() { fi ;; *) - echo "Invalid attribute in $opt_spec: ${key} ${val:-""}" >&2 + echo "Invalid attribute in $opt_spec: $line" >&2 exit 1 esac done < "$opt_spec" @@ -499,19 +500,17 @@ _which() { # ########################################################################### # ########################################################################### -# summary_common package +# report_formatting package # This package is a copy without comments from the original. The original # with comments and its test file can be found in the Bazaar repository at, -# lib/bash/summary_common.sh -# t/lib/bash/summary_common.sh +# lib/bash/report_formatting.sh +# t/lib/bash/report_formatting.sh # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### set -u -NAME_VAL_LEN=12 - POSIXLY_CORRECT=1 export POSIXLY_CORRECT @@ -537,57 +536,72 @@ fuzzy_formula=' factor = factor * 10; }' -# Does fuzzy rounding: rounds to nearest interval, but the interval gets larger -# as the number gets larger. This is to make things easier to diff. fuzz () { + _d "fuzz: $1" echo $1 | awk "{fuzzy_var=\$1; ${fuzzy_formula} print fuzzy_var;}" } -# Fuzzy computes the percent that $1 is of $2 fuzzy_pct () { local pct="$(echo $1 $2 | awk '{ if ($2 > 0) { printf "%d", $1/$2*100; } else {print 0} }')"; echo "$(fuzz "${pct}")%" } -# Prints a section header. All spaces in the string passed in are replaced -# with #'s and all underscores with spaces. section () { local str="$1" local line="$(printf '#_%-60s' "${str}_" | sed -e 's/[[:space:]]/#/g' -e 's/_/ /g')" printf "%s\n" "${line}" } +NAME_VAL_LEN=12 name_val () { - # We use $NAME_VAL_LEN here because the two summary tools, as well as - # the tests, use diffent widths. printf "%+*s | %s\n" "${NAME_VAL_LEN}" "$1" "$2" } shorten() { - local unit="k" - local size=1024 + local num="$1" + local prec="${2:-2}" + local div="${3:-1024}" - if [ $1 -ge 1099511627776 ] ; then - size=1099511627776 - unit=T - elif [ $1 -ge 1073741824 ] ; then - size=1073741824 - unit=G - elif [ $1 -ge 1048576 ] ; then - size=1048576 - unit=M - fi - local result=$(echo "$1 ${size} ${2:-0}" | awk '{printf "%." $3 "f", $1 / $2}') - echo "${result}${unit}" + echo "$num" | awk -v prec="$prec" -v div="$div" ' + { + size = 4; + val = $1; + + unit = val >= 1099511627776 ? "T" : val >= 1073741824 ? "G" : val >= 1048576 ? "M" : val >= 1024 ? "k" : ""; + + while ( int(val) && !(val % 1024) ) { + val /= 1024; + } + + while ( val > 1000 ) { + val /= div; + } + + printf "%.*f%s", prec, val, unit; + } + ' } group_concat () { sed -e '{H; $!d;}' -e 'x' -e 's/\n[[:space:]]*\([[:digit:]]*\)[[:space:]]*/, \1x/g' -e 's/[[:space:]][[:space:]]*/ /g' -e 's/, //' "${1}" } -# Tries to find the niceness of the passed in PID. First with ps, and -# failing that, with a bit of C, using getpriority(). -# Returns the nice for the pid, or "?" if it can't find any. +# ########################################################################### +# End report_formatting package +# ########################################################################### + +# ########################################################################### +# summary_common package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/bash/summary_common.sh +# t/lib/bash/summary_common.sh +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### + + +set -u + get_nice_of_pid () { local pid="$1" local niceness=$(ps -p $pid -o nice | tail -n+2 | awk '{print $1; exit;}') @@ -595,14 +609,9 @@ get_nice_of_pid () { if [ -n "${niceness}" ]; then echo $niceness else - local tmpfile="$( PERCONA_TMPFILE )" - # TODO overkill? - debug_log "Getting the niceness from ps failed, somehow. We are about to try this:" + local tmpfile="$TMPDIR/nice_through_c.tmp.c" + _d "Getting the niceness from ps failed, somehow. We are about to try this:" cat < "$tmpfile" -#include -#include -#include -#include int main(void) { int priority = getpriority(PRIO_PROCESS, $pid); @@ -620,19 +629,16 @@ EOC if [ -z "${c_comp}" ]; then c_comp=$(_which cc) fi - debug_log "$tmpfile: $( cat "$tmpfile" )" - debug_log "$c_comp -xc \"$tmpfile\" -o \"$tmpfile\" && eval \"$tmpfile\"" + _d "$tmpfile: $( cat "$tmpfile" )" + _d "$c_comp -xc \"$tmpfile\" -o \"$tmpfile\" && eval \"$tmpfile\"" $c_comp -xc "$tmpfile" -o "$tmpfile" 2>/dev/null && eval "$tmpfile" 2>/dev/null if [ $? -ne 0 ]; then echo "?" - debug_log "Failed to get a niceness value for $pid" + _d "Failed to get a niceness value for $pid" fi fi } -# Fetches the oom value for a given pid. -# To avoi deprecation warnings, tries /proc/PID/oom_score_adj first. -# Will only work if /proc/cpuinfo is available. get_oom_of_pid () { local pid="$1" local oom_adj="" @@ -640,10 +646,10 @@ get_oom_of_pid () { if [ -n "${pid}" ] && [ -e /proc/cpuinfo ]; then if [ -s "/proc/$pid/oom_score_adj" ]; then oom_adj=$(cat "/proc/$pid/oom_score_adj" 2>/dev/null) - debug_log "For $pid, the oom value is $oom_adj, retreived from oom_score_adj" + _d "For $pid, the oom value is $oom_adj, retreived from oom_score_adj" else oom_adj=$(cat "/proc/$pid/oom_adj" 2>/dev/null) - debug_log "For $pid, the oom value is $oom_adj, retreived from oom_adj" + _d "For $pid, the oom value is $oom_adj, retreived from oom_adj" fi fi @@ -651,14 +657,240 @@ get_oom_of_pid () { echo "${oom_adj}" else echo "?" - debug_log "Can't find the oom value for $pid" + _d "Can't find the oom value for $pid" fi } +CMD_FILE="$( _which file 2>/dev/null )" +CMD_NM="$( _which nm 2>/dev/null )" +CMD_OBJDUMP="$( _which objdump 2>/dev/null )" + +has_symbols () { + local executable="$(_which "$1")" + local has_symbols="" + + if [ "${CMD_FILE}" ] \ + && [ "$($CMD_FILE "${executable}" | grep 'not stripped' )" ]; then + has_symbols=1 + elif [ "${CMD_NM}" ] \ + || [ "${CMD_OBJDMP}" ]; then + if [ "${CMD_NM}" ] \ + && [ !"$("${CMD_NM}" -- "${executable}" 2>&1 | grep 'File format not recognized' )" ]; then + if [ -z "$( $CMD_NM -- "${executable}" 2>&1 | grep ': no symbols' )" ]; then + has_symbols=1 + fi + elif [ -z "$("${CMD_OBJDUMP}" -t -- "${executable}" | grep '^no symbols$' )" ]; then + has_symbols=1 + fi + fi + + if [ "${has_symbols}" ]; then + echo "Yes" + return 0 + else + echo "No" + return 1 + fi +} + +setup_data_dir () { + local data_dir="" + if [ -z "$OPT_SAVE_DATA" ]; then + mkdir "$TMPDIR/data" || die "Cannot mkdir $TMPDIR/data" + data_dir="$TMPDIR/data" + else + if [ ! -d "$OPT_SAVE_DATA" ]; then + mkdir "$OPT_SAVE_DATA" || die "Cannot mkdir $OPT_SAVE_DATA" + fi + touch "$OPT_SAVE_DATA/test" || die "Cannot write to $OPT_SAVE_DATA" + rm "$OPT_SAVE_DATA/test" || die "Cannot rm $OPT_SAVE_DATA/test" + data_dir="$OPT_SAVE_DATA" + fi + echo "$data_dir" +} + +_GET_VAR_DEFAULT=0 +get_var () { + local varname="$1" + local file="$2" + local v="$(awk "\$1 ~ /^${varname}$/ { print \$2 }" "${file}")" + echo "${v:-$_GET_VAR_DEFAULT}" +} + # ########################################################################### # End summary_common package # ########################################################################### +# ########################################################################### +# collect_mysql_info package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/bash/collect_mysql_info.sh +# t/lib/bash/collect_mysql_info.sh +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### + + + +collect_mysqld_instances () { + local file="$1" + ps auxww 2>/dev/null | grep mysqld > "$file" +} + +find_my_cnf_file() { + local file="$1" + local port=${2:-""} + + local cnf_file="" + if test -n "$port" && grep -- "/mysqld.*--port=$port" "${file}" >/dev/null 2>&1 ; then + cnf_file="$(grep -- "/mysqld.*--port=$port" "${file}" \ + | awk 'BEGIN{RS=" "; FS="=";} $1 ~ /--defaults-file/ { print $2; }' \ + | head -n1)" + else + cnf_file="$(grep '/mysqld' "${file}" \ + | awk 'BEGIN{RS=" "; FS="=";} $1 ~ /--defaults-file/ { print $2; }' \ + | head -n1)" + fi + + if [ ! -n "${cnf_file}" ]; then + _d "Cannot autodetect config file, trying common locations" + cnf_file="/etc/my.cnf"; + if [ ! -e "${cnf_file}" ]; then + cnf_file="/etc/mysql/my.cnf"; + fi + if [ ! -e "${cnf_file}" ]; then + cnf_file="/var/db/mysql/my.cnf"; + fi + fi + + echo "$cnf_file" +} + +collect_mysql_variables () { + local file="$1" + $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW /*!40100 GLOBAL*/ VARIABLES' > "$file" +} + +collect_mysql_status () { + local file="$1" + $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW /*!50000 GLOBAL*/ STATUS' > "$file" +} + +collect_mysql_databases () { + local file="$1" + $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW DATABASES' > "$file" 2>/dev/null +} + +collect_mysql_plugins () { + local file="$1" + $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW PLUGINS' > "$file" 2>/dev/null +} + +collect_mysql_slave_status () { + local file="$1" + $CMD_MYSQL $EXT_ARGV -ssE -e 'SHOW SLAVE STATUS' > "$file" 2>/dev/null +} + +collect_mysql_innodb_status () { + local file="$1" + $CMD_MYSQL $EXT_ARGV -ssE -e 'SHOW /*!50000 ENGINE*/ INNODB STATUS' > "$file" 2>/dev/null +} + +collect_mysql_processlist () { + local file="$1" + $CMD_MYSQL $EXT_ARGV -ssE -e 'SHOW FULL PROCESSLIST' > "$file" 2>/dev/null +} + +collect_mysql_users () { + local file="$1" + $CMD_MYSQL $EXT_ARGV -ssE -e 'SELECT COUNT(*), SUM(user=""), SUM(password=""), SUM(password NOT LIKE "*%") FROM mysql.user' > "$file" 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 +} + +collect_mysql_deferred_status () { + local status_file="$1" + local defer_file="$2" + collect_mysql_status "$TMPDIR/defer_gatherer" + cat "$TMPDIR/defer_gatherer" | join "$status_file" - > "$defer_file" +} + +collect_internal_vars () { + local file="$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) + local has_symbols="$(has_symbols "${CMD_MYSQL}")" + + echo "pt-summary-internal-now $now" >> "$file" + echo "pt-summary-internal-user $user" >> "$file" + echo "pt-summary-internal-FNV_64 $FNV_64" >> "$file" + echo "pt-summary-internal-trigger_count $trigger_count" >> "$file" + echo "pt-summary-internal-symbols $has_symbols" >> "$file" +} + +collect_mysql_info () { + local dir="$1" + local prefix="${2:-percona-toolkit}" + + collect_mysqld_instances "$dir/${prefix}-mysqld-instances" + + collect_mysql_variables "$dir/${prefix}-mysql-variables" + collect_mysql_status "$dir/${prefix}-mysql-status" + collect_mysql_databases "$dir/${prefix}-mysql-databases" + collect_mysql_plugins "$dir/${prefix}-mysql-plugins" + collect_mysql_slave_status "$dir/${prefix}-mysql-slave" + collect_mysql_innodb_status "$dir/${prefix}-innodb-status" + collect_mysql_processlist "$dir/${prefix}-mysql-processlist" + collect_mysql_users "$dir/${prefix}-mysql-users" + + local binlog="$(get_var log_bin "$dir/${prefix}-mysql-variables")" + if [ "${binlog}" ]; then + _d "Got a binlog, going to get MASTER LOGS and MASTER STATUS" + collect_master_logs_status "$dir/${prefix}-mysql-master-logs" "$dir/${prefix}-mysql-master-status" + fi + + local uptime="$(get_var Uptime "$dir/${prefix}-mysql-status")" + local current_time="$($CMD_MYSQL $EXT_ARGV -ss -e \ + "SELECT LEFT(NOW() - INTERVAL ${uptime} SECOND, 16)")" + + local port="$(get_var port "$dir/${prefix}-mysql-variables")" + local cnf_file=$(find_my_cnf_file "$dir/${prefix}-mysqld-instances" ${port}); + + echo "pt-summary-internal-current_time $current_time" >> "$dir/${prefix}-mysql-variables" + echo "pt-summary-internal-Config_File $cnf_file" >> "$dir/${prefix}-mysql-variables" + collect_internal_vars "$dir/${prefix}-mysql-variables" + + if [ -n "${OPT_DUMP_SCHEMAS}" ]; then + _d "--dump-schemas passed in, dumping early" + local trg_arg="$( get_mysqldump_args "$dir/${prefix}-mysql-variables" )" + get_mysqldump_for "$dir/${prefix}-mysqldump" "${trg_arg}" "${OPT_DUMP_SCHEMAS}" + fi + + ( + sleep $OPT_SLEEP + collect_mysql_deferred_status "$dir/${prefix}-mysql-status" "$dir/${prefix}-mysql-status-defer" + ) & + _d "Forked child is $!" +} + +# ########################################################################### +# End collect_mysql_info package +# ########################################################################### + # ######################################################################## # Some global setup is necessary for cross-platform compatibility, even # when sourcing this script for testing purposes. @@ -666,13 +898,8 @@ get_oom_of_pid () { TOOL="pt-mysql-summary" -setup_commands () { - CMD_MYSQL=$(_which mysql) - CMD_MYSQLDUMP=$( _which mysqldump ) - CMD_FILE="$( _which file )" - CMD_NM="$( _which nm )" - CMD_OBJDUMP="$( _which objdump )" -} +CMD_MYSQL="$(_which mysql)" +CMD_MYSQLDUMP="$( _which mysqldump )" # Accepts a number of seconds, and outputs a d+h:m:s formatted string secs_to_time () { @@ -681,15 +908,6 @@ secs_to_time () { }' } -# gets a value from $MYSQL_VARIABLES_FILE. Returns zero if it doesn't -# exist. -get_var () { - local varname="$1" - local file="$2" - local v="$(awk "\$1 ~ /^${varname}$/ { print \$2 }" ${file})" - echo "${v:-0}" -} - # Returns true if a variable exists var_exists () { local varname="$1" @@ -731,15 +949,6 @@ feat_on() { fi } -# gets a value from $MYSQL_STATUS_FILE. Returns zero if it doesn't -# exist. -get_stat () { - local varname="$1" - local file="$2" - local v="$(awk "\$1 ~ /^${varname}$/ { print \$2 }" "${file}")" - echo "${v:-0}" -} - get_table_cache () { local file="$1" local table_cache="" @@ -802,23 +1011,6 @@ parse_mysqld_instances () { done } -# 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:-""} - if test -n "$port" && grep -- "/mysqld.*--port=$port" "${file}" >/dev/null 2>&1 ; then - grep -- "/mysqld.*--port=$port" "${file}" \ - | awk 'BEGIN{RS=" "; FS="=";} $1 ~ /--defaults-file/ { print $2; }' \ - | head -n1 - else - grep '/mysqld' "${file}" \ - | awk 'BEGIN{RS=" "; FS="=";} $1 ~ /--defaults-file/ { print $2; }' \ - | head -n1 - fi -} - # Gets the MySQL system time. Uses input from $MYSQL_VARIABLES_FILE. get_mysql_timezone () { local file="${1:-$MYSQL_VARIABLES_FILE}" @@ -848,6 +1040,7 @@ get_mysql_uptime () { summarize_binlogs () { local file="$1" local size="$(awk '{t += $2} END{printf "%0.f\n", t}' "$file")" +cat "$file" >> ~/bluhbluh.txt name_val "Binlogs" $(wc -l "$file") name_val "Zero-Sized" $(grep -c '\<0$' "$file") name_val "Total Size" $(shorten ${size} 1) @@ -1124,7 +1317,7 @@ find_max_trx_time() { find_transation_states () { local file="$1" - local tmpfile="$( PERCONA_TMPFILE )" + local tmpfile="$TMPDIR/find_transation_states.tmp" awk -F, '/^---TRANSACTION/{print $2}' "${file}" \ | sed -e 's/ [0-9]* sec.*//' \ | sort \ @@ -1135,7 +1328,7 @@ find_transation_states () { # Summarizes various things about InnoDB status that are not easy to see by eye. format_innodb_status () { local file=$1 - name_val "Checkpoint Age" "$(shorten $(find_checkpoint_age "${file}"))" + name_val "Checkpoint Age" "$(shorten $(find_checkpoint_age "${file}") 0)" name_val "InnoDB Queue" "$(awk '/queries inside/{print}' "${file}")" name_val "Oldest Transaction" "$(find_max_trx_time "${file}") Seconds"; name_val "History List Len" "$(awk '/History list length/{print $4}' "${file}")" @@ -1173,7 +1366,7 @@ format_innodb_status () { # then there should be multiple databases. format_overall_db_stats () { local file="$1" - local tmpfile="$( PERCONA_TMPFILE )" + local tmpfile="$TMPDIR/format_overall_db_stats.tmp" echo # We keep counts of everything in an associative array keyed by db name, and # what it is. The num_dbs counter is to ensure sort order is consistent when @@ -1469,8 +1662,8 @@ _myisam () { local buf_size=$(get_var key_buffer_size "$variables_file") local blk_size=$(get_var key_cache_block_size "$variables_file") - local blk_unus=$(get_stat Key_blocks_unused "$status_file") - local blk_unfl=$(get_stat Key_blocks_not_flushed "$variables_file") + local blk_unus=$(get_var Key_blocks_unused "$status_file") + local blk_unfl=$(get_var Key_blocks_not_flushed "$variables_file") local unus=$((${blk_unus} * ${blk_size})) local unfl=$((${blk_unfl} * ${blk_size})) local used=$((${buf_size} - ${unus})) @@ -1521,23 +1714,23 @@ _innodb () { local bp_size="$(get_var innodb_buffer_pool_size "$variables_file")" name_val "Buffer Pool Size" "$(shorten ${bp_size} 1)" - local bp_pags="$(get_stat Innodb_buffer_pool_pages_total "$status_file")" - local bp_free="$(get_stat Innodb_buffer_pool_pages_free "$status_file")" - local bp_dirt="$(get_stat Innodb_buffer_pool_pages_dirty "$status_file")" + local bp_pags="$(get_var Innodb_buffer_pool_pages_total "$status_file")" + local bp_free="$(get_var Innodb_buffer_pool_pages_free "$status_file")" + local bp_dirt="$(get_var Innodb_buffer_pool_pages_dirty "$status_file")" local bp_fill=$((${bp_pags} - ${bp_free})) name_val "Buffer Pool Fill" "$(fuzzy_pct ${bp_fill} ${bp_pags})" name_val "Buffer Pool Dirty" "$(fuzzy_pct ${bp_dirt} ${bp_pags})" name_val "File Per Table" $(get_var innodb_file_per_table "$variables_file") - name_val "Page Size" $(shorten $(get_stat Innodb_page_size "$status_file")) + name_val "Page Size" $(shorten $(get_var Innodb_page_size "$status_file") 0) - local lg_size="$(get_var innodb_log_file_size "$variables_file")" - local lg_fils="$(get_var innodb_log_files_in_group "$variables_file")" - local lg_totl="$((${lg_size} * ${lg_fils}))" + local log_size="$(get_var innodb_log_file_size "$variables_file")" + local log_file="$(get_var innodb_log_files_in_group "$variables_file")" + local log_total=$(echo 1 | awk "{printf \"%.2f\n\", ${log_size}*${log_file}}" ) name_val "Log File Size" \ - "${lg_fils} * $(shorten ${lg_size} 1) = $(shorten ${lg_totl} 1)" + "${log_file} * $(shorten ${log_size} 1) = $(shorten ${log_total} 1 1000)" name_val "Log Buffer Size" \ - "$(shorten $(get_var innodb_log_buffer_size "$variables_file"))" + "$(shorten $(get_var innodb_log_buffer_size "$variables_file") 0)" name_val "Flush Method" \ "$(get_var innodb_flush_method "$variables_file")" name_val "Flush Log At Commit" \ @@ -1580,7 +1773,7 @@ _noteworthy_variables () { bulk_insert_buffer max_heap_table_size tmp_table_size \ max_allowed_packet thread_stack; do - name_val ${v} $(shorten $(get_var ${v} "$file")) + name_val ${v} $(shorten $(get_var ${v} "$file") 0) done for v in log log_error log_warnings log_slow_queries \ log_queries_not_using_indexes log_slave_updates; @@ -1593,7 +1786,7 @@ _semi_sync_stats_for () { local target="$1" local file="$2" - local semisync_status="$(get_stat "Rpl_semi_sync_${target}_status" "${file}" )" + local semisync_status="$(get_var "Rpl_semi_sync_${target}_status" "${file}" )" local semisync_trace="$(get_var "rpl_semi_sync_${target}_trace_level" "${file}")" local trace_extra="" @@ -1620,7 +1813,7 @@ _semi_sync_stats_for () { name_val "${target} waits for slaves" \ "$(get_var "rpl_semi_sync_${target}_wait_no_slave" "${file}")" - debug_log "Prepend Rpl_semi_sync_master_ to the following" + _d "Prepend Rpl_semi_sync_master_ to the following" for v in \ clients net_avg_wait_time net_wait_time net_waits \ no_times no_tx timefunc_failures tx_avg_wait_time \ @@ -1628,7 +1821,7 @@ _semi_sync_stats_for () { wait_sessions yes_tx; do name_val "${target} ${v}" \ - "$( get_stat "Rpl_semi_sync_master_${v}" "${file}" )" + "$( get_var "Rpl_semi_sync_master_${v}" "${file}" )" done fi } @@ -1668,33 +1861,6 @@ noncounters_pattern () { echo $noncounters_pattern } -has_symbols () { - local executable="$(_which "$1")" - local has_symbols="" - - if [ "${CMD_FILE}" ] \ - && [ "$($CMD_FILE "${executable}" | grep 'not stripped' )" ]; then - has_symbols=1 - elif [ "${CMD_NM}" ] \ - || [ "${CMD_OBJDMP}" ]; then - if [ "${CMD_NM}" ] \ - && [ !"$("${CMD_NM}" -- "${executable}" 2>&1 | grep 'File format not recognized' )" ]; then - if [ -z "$( $CMD_NM -- "${executable}" 2>&1 | grep ': no symbols' )" ]; then - has_symbols=1 - fi - elif [ -z "$("${CMD_OBJDUMP}" -t -- "${executable}" | grep '^no symbols$' )" ]; then - has_symbols=1 - fi - fi - - if [ "${has_symbols}" ]; then - echo "Yes" - return 0 - else - echo "No" - return 1 - fi -} # Uses mysqldump and dumps the results to FILE. # args and dbtodump are passed to mysqldump. @@ -1717,7 +1883,7 @@ get_mysqldump_args () { # If mysqldump supports triggers, then add options for routines. if $CMD_MYSQLDUMP --help --verbose 2>&1 | grep triggers >/dev/null; then - debug_log "mysqldump supports triggers" + _d "mysqldump supports triggers" trg_arg="--routines" fi @@ -1728,7 +1894,7 @@ get_mysqldump_args () { local triggers="--skip-triggers" local trg=$(get_var "pt-summary-internal-trigger_count" "$file" ) if [ -n "${trg}" ] && [ "${trg}" -gt 0 ]; then - debug_log "We have triggers to dump" + _d "We have triggers to dump" triggers="--triggers" fi trg_arg="${trg_arg} ${triggers}"; @@ -1736,129 +1902,13 @@ get_mysqldump_args () { echo "${trg_arg}" } -setup_files () { - local dir="${1:-/tmp}" - local prefix="${2:-percona-toolkit}" - - debug_log "Going to use files in $dir/, prefixed with [$prefix]" - - MYSQL_VARIABLES_FILE="$dir/${prefix}-mysql-variables" - MYSQL_STATUS_FILE="$dir/${prefix}-mysql-status" - MYSQL_STATUS_FILE_DEFER="$dir/${prefix}-mysql-status-defer" - MYSQL_DATABASES_FILE="$dir/${prefix}-mysql-databases" - MYSQL_PLUGINS_FILE="$dir/${prefix}-mysql-plugins" - MYSQL_PROCESSLIST_FILE="$dir/${prefix}-mysql-processlist" - MYSQL_SLAVE_FILE="$dir/${prefix}-mysql-slave" - MYSQL_MASTER_LOGS_FILE="$dir/${prefix}-mysql-master-logs" - MYSQL_MASTER_STATUS_FILE="$dir/${prefix}-mysql-master-status" - MYSQL_USERS_FILE="$dir/${prefix}-mysql-users" - MYSQL_INNODB_STATUS_FILE="$dir/${prefix}-innodb-status" - MYSQLD_INSTANCES_FILE="$dir/${prefix}-mysqld-instances" - MYSQLDUMP_FILE="$dir/${prefix}-mysqldump" - PERCONA_TMPFILE="$dir/${prefix}-tempfile" - - for file in {-mysqldump,-innodb-status,-tempfile,-mysqld-instances,-mysql-{variables,status,status-defer,databases,plugins,processlist,slave,master-logs,master-status,users}}; - do - touch "$dir/${prefix}${file}"; - done -} - -PERCONA_TMPFILE () { - echo "${PERCONA_TMPFILE:-"$TMPDIR/percona-toolkit-tempfile"}" -} - -collect_data () { - local dir="$1" - local prefix="$2" - - setup_files "$dir" "$prefix" - - ps auxww 2>/dev/null | grep mysqld > "$MYSQLD_INSTANCES_FILE" - - local user="$($CMD_MYSQL $EXT_ARGV -ss -e 'SELECT CURRENT_USER()' > "$PERCONA_TMPFILE")"; - if [ "$?" != "0" ]; then - if [ -n "${OPT_TEMPDIR}" ]; then - rm_tmpdir - fi - die "Cannot connect to mysql, please specify command-line options." - fi - - $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW /*!40100 GLOBAL*/ VARIABLES' > "$MYSQL_VARIABLES_FILE" - $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW /*!50000 GLOBAL*/ STATUS' > "$MYSQL_STATUS_FILE" - $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW DATABASES' > "$MYSQL_DATABASES_FILE" 2>/dev/null - $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW PLUGINS' > "$MYSQL_PLUGINS_FILE" 2>/dev/null - $CMD_MYSQL $EXT_ARGV -ssE -e 'SHOW SLAVE STATUS' > "$MYSQL_SLAVE_FILE" 2>/dev/null - $CMD_MYSQL $EXT_ARGV -ssE -e 'SHOW /*!50000 ENGINE*/ INNODB STATUS' > "$MYSQL_INNODB_STATUS_FILE" 2>/dev/null - $CMD_MYSQL $EXT_ARGV -ssE -e 'SHOW FULL PROCESSLIST' > "$MYSQL_PROCESSLIST_FILE" 2>/dev/null - local now="$($CMD_MYSQL $EXT_ARGV -ss -e 'SELECT NOW()')" - local port="$(get_var port "$MYSQL_VARIABLES_FILE")" - - local binlog="$(get_var log_bin "$MYSQL_VARIABLES_FILE")" - if [ "${binlog}" ]; then - debug_log "Got a binlog, going to get MASTER LOGS and MASTER STATUS" - $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW MASTER LOGS' > "$MYSQL_MASTER_LOGS_FILE" 2>/dev/null - $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW MASTER STATUS' > "$MYSQL_MASTER_STATUS_FILE" 2>/dev/null - fi - - $CMD_MYSQL $EXT_ARGV -ssE -e 'SELECT COUNT(*), SUM(user=""), SUM(password=""), SUM(password NOT LIKE "*%") FROM mysql.user' > "$MYSQL_USERS_FILE" 2>/dev/null - - local uptime="$(get_stat Uptime "$MYSQL_STATUS_FILE")" - local current_time="$($CMD_MYSQL $EXT_ARGV -ss -e \ - "SELECT LEFT(NOW() - INTERVAL ${uptime} SECOND, 16)")" - - local cnf_file=$(find_my_cnf_file "$MYSQLD_INSTANCES_FILE" ${port}); - if [ ! -e "${cnf_file}" ]; then - debug_log "Cannot autodetect config file, trying common locations" - cnf_file="/etc/my.cnf"; - fi - if [ ! -e "${cnf_file}" ]; then - cnf_file="/etc/mysql/my.cnf"; - fi - if [ ! -e "${cnf_file}" ]; then - cnf_file="/var/db/mysql/my.cnf"; - fi - - 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 trigger_count=$($CMD_MYSQL $EXT_ARGV -ss -e "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TRIGGERS" 2>/dev/null) - local has_symbols="$(has_symbols "${CMD_MYSQL}")" - - # TODO: Do these require a file of their own? - echo "pt-summary-internal-now $now" >> "$MYSQL_VARIABLES_FILE" - echo "pt-summary-internal-current_time $current_time" >> "$MYSQL_VARIABLES_FILE" - echo "pt-summary-internal-user $user" >> "$MYSQL_VARIABLES_FILE" - echo "pt-summary-internal-Config_File $cnf_file" >> "$MYSQL_VARIABLES_FILE" - echo "pt-summary-internal-FNV_64 $FNV_64" >> "$MYSQL_VARIABLES_FILE" - echo "pt-summary-internal-trigger_count $trigger_count" >> "$MYSQL_VARIABLES_FILE" - echo "pt-summary-internal-symbols $has_symbols" >> "$MYSQL_VARIABLES_FILE" - - if [ -n "${OPT_DUMP_SCHEMAS}" ]; then - debug_log "--dump-schemas passed in, dumping early" - local trg_arg="$( get_mysqldump_args "$MYSQL_VARIABLES_FILE" )" - get_mysqldump_for "$MYSQLDUMP_FILE" "${trg_arg}" "${OPT_DUMP_SCHEMAS}" - fi - - # TODO: gather this data in the same format as normal: TS line, stats - ( - sleep $OPT_SLEEP - $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW /*!50000 GLOBAL*/ STATUS' \ - | join "$MYSQL_STATUS_FILE" - > "$MYSQL_STATUS_FILE_DEFER" - ) & - debug_log "Forked child is $!" -} - check_mysql () { # Check that mysql and mysqldump are in PATH. If not, we're # already dead in the water, so don't bother with cmd line opts, # just error and exit. - [ -n "$(mysql --help)" ] \ + [ -n "$(mysql --help 2>/dev/null)" ] \ || die "Cannot execute mysql. Check that it is in PATH." - [ -n "$(mysqldump --help)" ] \ + [ -n "$(mysqldump --help 2>/dev/null)" ] \ || die "Cannot execute mysqldump. Check that it is in PATH." # Now that we have the cmd line opts, check that we can actually @@ -1881,9 +1931,9 @@ main() { # Prepending SIG to these doesn't work with NetBSD's sh trap sigtrap HUP INT TERM - RAN_WITH="--sleep=$OPT_SLEEP --dump-schemas=$OPT_DUMP_SCHEMAS --tempdir=$OPT_TEMPDIR" + local RAN_WITH="--sleep=$OPT_SLEEP --dump-schemas=$OPT_DUMP_SCHEMAS --save-data=$OPT_SAVE_DATA" - debug_log "Starting $0 $RAN_WITH" + _d "Starting $0 $RAN_WITH" # Begin by setting the $PATH to include some common locations that are not # always in the $PATH, including the "sbin" locations. On SunOS systems, @@ -1892,39 +1942,39 @@ main() { export PATH="${PATH}:/usr/mysql/bin/:/usr/local/sbin:/usr/sbin:/sbin" export PATH="/usr/gnu/bin/:/usr/xpg4/bin/:${PATH}" - # Check if mysql and mysqldump are there - check_mysql - # Set up CMD_mysql and friends - setup_commands - debug_log "Going to use: mysql=${CMD_MYSQL} mysqldump=${CMD_MYSQLDUMP}" + _d "Going to use: mysql=${CMD_MYSQL} mysqldump=${CMD_MYSQLDUMP}" - # Create the tempdir for everything to run in - mk_tmpdir "${OPT_TEMPDIR}" - debug_log "Tempdir is $TMPDIR" + # Create the tmpdir for everything to run in + mk_tmpdir + + # Set DATA_DIR where we'll save collected data files. + local data_dir="$(setup_data_dir)" + + _d "Temp dir is [$TMPDIR], saving data in [$data_dir]" # ######################################################################## # Fetch most info, leave a child in the background gathering the rest # ######################################################################## - collect_data "$TMPDIR" "${OPT_PREFIX:-percona-toolkit}" + collect_mysql_info "${data_dir}" # ######################################################################## # Format and pretty-print the data # ######################################################################## - report_summary "$TMPDIR" "${OPT_PREFIX:-percona-toolkit}" + report_summary "${data_dir}" - # Remove the tempdir if it wasn't passed in by the user - if [ -z "${OPT_TEMPDIR}" ]; then - rm_tmpdir - fi + rm_tmpdir } report_summary () { local dir="$1" - local prefix="$2" + local prefix="${2:-percona-toolkit}" # Make sure the MYSQL_etc_FILE variables are set - setup_files "$dir" "$prefix" + setup_files "$dir" + + # Field width for name_val + local NAME_VAL_LEN=25 # ######################################################################## # Header for the whole thing, table of discovered instances @@ -1933,44 +1983,44 @@ report_summary () { section Percona_Toolkit_MySQL_Summary_Report name_val "System time" "`date -u +'%F %T UTC'` (local TZ: `date +'%Z %z'`)" section Instances - parse_mysqld_instances "$MYSQLD_INSTANCES_FILE" + parse_mysqld_instances "$dir/${prefix}-mysqld-instances" section MySQL_Executable - name_val "Has symbols" "$( get_var "pt-summary-internal-symbols" "$MYSQL_VARIABLES_FILE" )" + name_val "Has symbols" "$( get_var "pt-summary-internal-symbols" "$dir/${prefix}-mysql-variables" )" # ######################################################################## # General date, hostname, etc # ######################################################################## - local user="$(get_var "pt-summary-internal-user" "$MYSQL_VARIABLES_FILE")" - local port="$(get_var port "$MYSQL_VARIABLES_FILE")" - local now="$(get_var "pt-summary-internal-NOW" "$MYSQL_VARIABLES_FILE")" + local user="$(get_var "pt-summary-internal-user" "$dir/${prefix}-mysql-variables")" + local port="$(get_var port "$dir/${prefix}-mysql-variables")" + local now="$(get_var "pt-summary-internal-NOW" "$dir/${prefix}-mysql-variables")" section "Report_On_Port_${port}" name_val User "${user}" - name_val Time "${now} ($(get_mysql_timezone "$MYSQL_VARIABLES_FILE"))" - name_val Hostname "$(get_var hostname "$MYSQL_VARIABLES_FILE")" - get_mysql_version "$MYSQL_VARIABLES_FILE" + name_val Time "${now} ($(get_mysql_timezone "$dir/${prefix}-mysql-variables"))" + name_val Hostname "$(get_var hostname "$dir/${prefix}-mysql-variables")" + get_mysql_version "$dir/${prefix}-mysql-variables" - local uptime="$(get_stat Uptime "$MYSQL_STATUS_FILE")" - local current_time="$(get_stat "pt-summary-internal-current_time" "$MYSQL_STATUS_FILE")" + local uptime="$(get_var Uptime "$dir/${prefix}-mysql-status")" + local current_time="$(get_var "pt-summary-internal-current_time" "$dir/${prefix}-mysql-status")" name_val Started "$(get_mysql_uptime "${uptime}" "${current_time}")" - local num_dbs="$(grep -c . "$MYSQL_DATABASES_FILE")" + local num_dbs="$(grep -c . "$dir/${prefix}-mysql-databases")" name_val Databases "${num_dbs}" - name_val Datadir "$(get_var datadir "$MYSQL_VARIABLES_FILE")" + name_val Datadir "$(get_var datadir "$dir/${prefix}-mysql-variables")" - local fuzz_procs=$(fuzz $(get_stat Threads_connected "$MYSQL_STATUS_FILE")) - local fuzz_procr=$(fuzz $(get_stat Threads_running "$MYSQL_STATUS_FILE")) + local fuzz_procs=$(fuzz $(get_var Threads_connected "$dir/${prefix}-mysql-status")) + local fuzz_procr=$(fuzz $(get_var Threads_running "$dir/${prefix}-mysql-status")) name_val Processes "${fuzz_procs} connected, ${fuzz_procr} running" local slave="" - if [ -s $MYSQL_SLAVE_FILE ]; then slave=""; else slave="not "; fi - local slavecount=$(grep -c 'Binlog Dump' "${MYSQL_PROCESSLIST_FILE}") + if [ -s "$dir/${prefix}-mysql-slave" ]; then slave=""; else slave="not "; fi + local slavecount=$(grep -c 'Binlog Dump' "$dir/${prefix}-mysql-processlist") name_val Replication "Is ${slave}a slave, has ${slavecount} slaves connected" # TODO move this into a section with other files: error log, slow log and # show the sizes - local pid_file="$(get_var pid_file "$MYSQL_VARIABLES_FILE")" + local pid_file="$(get_var pid_file "$dir/${prefix}-mysql-variables")" local PID_EXISTS="" if [ -e "${pid_file}" ]; then PID_EXISTS="(exists)" @@ -1983,7 +2033,7 @@ report_summary () { # Processlist, sliced several different ways # ######################################################################## section Processlist - summarize_processlist "$MYSQL_PROCESSLIST_FILE" + summarize_processlist "$dir/${prefix}-mysql-processlist" # ######################################################################## # Queries and query plans @@ -1992,14 +2042,14 @@ report_summary () { # Wait for the child that was forked during collection. wait local noncounters_pattern="$(noncounters_pattern)" - format_status_variables "$MYSQL_STATUS_FILE_DEFER" | grep -v "${noncounters_pattern}" + format_status_variables "$dir/${prefix}-mysql-status-defer" | grep -v "${noncounters_pattern}" # ######################################################################## # Table cache # ######################################################################## section Table_cache - local open_tables=$(get_stat Open_tables "$MYSQL_STATUS_FILE") - local table_cache=$(get_table_cache "$MYSQL_STATUS_FILE") + local open_tables=$(get_var Open_tables "$dir/${prefix}-mysql-status") + local table_cache=$(get_table_cache "$dir/${prefix}-mysql-status") name_val Size $table_cache name_val Usage "$(fuzzy_pct ${open_tables} ${table_cache})" @@ -2007,41 +2057,41 @@ report_summary () { # Percona Server features # ######################################################################## section Key_Percona_Server_features - _percona_server_features "$MYSQL_VARIABLES_FILE" + _percona_server_features "$dir/${prefix}-mysql-variables" # ######################################################################## # Plugins # ######################################################################## section Plugins - name_val "InnoDB compression" "$(get_plugin_status "$MYSQL_PLUGINS_FILE" "INNODB_CMP")" + name_val "InnoDB compression" "$(get_plugin_status "$dir/${prefix}-mysql-plugins" "INNODB_CMP")" # ######################################################################## # Query cache # ######################################################################## - if [ "$(get_var have_query_cache "$MYSQL_VARIABLES_FILE")" ]; then + if [ "$(get_var have_query_cache "$dir/${prefix}-mysql-variables")" ]; then section Query_cache - local query_cache_size=$(get_var query_cache_size "$MYSQL_VARIABLES_FILE") - local used=$(( ${query_cache_size} - $(get_stat Qcache_free_memory "$MYSQL_STATUS_FILE") )) - local hrat=$(fuzzy_pct $(get_stat Qcache_hits "$MYSQL_STATUS_FILE") $(get_stat Qcache_inserts "$MYSQL_STATUS_FILE")) - name_val query_cache_type $(get_var query_cache_type "$MYSQL_VARIABLES_FILE") + local query_cache_size=$(get_var query_cache_size "$dir/${prefix}-mysql-variables") + local used=$(( ${query_cache_size} - $(get_var Qcache_free_memory "$dir/${prefix}-mysql-status") )) + local hrat=$(fuzzy_pct $(get_var Qcache_hits "$dir/${prefix}-mysql-status") $(get_var Qcache_inserts "$dir/${prefix}-mysql-status")) + name_val query_cache_type $(get_var query_cache_type "$dir/${prefix}-mysql-variables") name_val Size "$(shorten ${query_cache_size} 1)" name_val Usage "$(fuzzy_pct ${used} ${query_cache_size})" name_val HitToInsertRatio "${hrat}" fi - local semisync_enabled_master="$(get_var rpl_semi_sync_master_enabled "$MYSQL_VARIABLES_FILE")" + local semisync_enabled_master="$(get_var rpl_semi_sync_master_enabled "$dir/${prefix}-mysql-variables")" if [ -n "${semisync_enabled_master}" ]; then section Semisynchronous_Replication if [ "$semisync_enabled_master" = "OFF" -o "$semisync_enabled_master" = "0" -o -z "$semisync_enabled_master" ]; then name_val "Master" "Disabled" else - _semi_sync_stats_for "master" "$MYSQL_VARIABLES_FILE" + _semi_sync_stats_for "master" "$dir/${prefix}-mysql-variables" fi - local semisync_enabled_slave="$(get_var rpl_semi_sync_slave_enabled "$MYSQL_VARIABLES_FILE")" + local semisync_enabled_slave="$(get_var rpl_semi_sync_slave_enabled "$dir/${prefix}-mysql-variables")" if [ "$semisync_enabled_slave" = "OFF" -o "$semisync_enabled_slave" = "0" -o -z "$semisync_enabled_slave" ]; then name_val "Slave" "Disabled" else - _semi_sync_stats_for "slave" "$MYSQL_VARIABLES_FILE" + _semi_sync_stats_for "slave" "$dir/${prefix}-mysql-variables" fi fi @@ -2069,19 +2119,19 @@ report_summary () { echo -n "Type the name of the database, or press Enter to dump all of them. " local dbtodump="" read dbtodump - local trg_arg="$( get_mysqldump_args "$MYSQL_VARIABLES_FILE" )" - get_mysqldump_for "$MYSQLDUMP_FILE" "${trg_arg}" "${dbtodump}" + local trg_arg="$( get_mysqldump_args "$dir/${prefix}-mysql-variables" )" + get_mysqldump_for "$dir/${prefix}-mysqldump" "${trg_arg}" "${dbtodump}" fi # Test the result by checking the file, not by the exit status, because we # might get partway through and then die, and the info is worth analyzing # anyway. - if [ -s "$MYSQLDUMP_FILE" ] && grep 'CREATE TABLE' "$MYSQLDUMP_FILE" >/dev/null 2>&1; then - format_overall_db_stats "$MYSQLDUMP_FILE" "$PERCONA_TMPFILE" + if [ -e "$dir/${prefix}-mysqldump" -a -s "$dir/${prefix}-mysqldump" ] \ + && grep 'CREATE TABLE' "$dir/${prefix}-mysqldump" >/dev/null 2>&1; then + format_overall_db_stats "$dir/${prefix}-mysqldump" else warn "Skipping schema analysis due to apparent error in dump file" - rm -f "$MYSQLDUMP_FILE" fi else echo "Skipping schema analysis" @@ -2091,60 +2141,60 @@ report_summary () { # Noteworthy Technologies # ######################################################################## section Noteworthy_Technologies - if [ -s "$MYSQLDUMP_FILE" ]; then - if grep FULLTEXT "$MYSQLDUMP_FILE" > /dev/null; then + if [ -s "$dir/${prefix}-mysqldump" ]; then + if grep FULLTEXT "$dir/${prefix}-mysqldump" > /dev/null; then name_val "Full Text Indexing" Yes else name_val "Full Text Indexing" No fi - if grep 'GEOMETRY\|POINT\|LINESTRING\|POLYGON' "$MYSQLDUMP_FILE" > /dev/null; then + if grep 'GEOMETRY\|POINT\|LINESTRING\|POLYGON' "$dir/${prefix}-mysqldump" > /dev/null; then name_val "Geospatial Types" Yes else name_val "Geospatial Types" No fi - if grep 'FOREIGN KEY' "$MYSQLDUMP_FILE" > /dev/null; then + if grep 'FOREIGN KEY' "$dir/${prefix}-mysqldump" > /dev/null; then name_val "Foreign Keys" Yes else name_val "Foreign Keys" No fi - if grep 'PARTITION BY' $MYSQLDUMP_FILE > /dev/null; then + if grep 'PARTITION BY' "$dir/${prefix}-mysqldump" > /dev/null; then name_val "Partitioning" Yes else name_val "Partitioning" No fi fi - if [ "$(get_stat Ssl_accepts "$MYSQL_STATUS_FILE")" -gt 0 ]; then + if [ "$(get_var Ssl_accepts "$dir/${prefix}-mysql-status")" -gt 0 ]; then name_val "SSL" Yes else name_val "SSL" No fi - if [ "$(get_stat Com_lock_tables "$MYSQL_STATUS_FILE")" -gt 0 ]; then + if [ "$(get_var Com_lock_tables "$dir/${prefix}-mysql-status")" -gt 0 ]; then name_val "Explicit LOCK TABLES" Yes else name_val "Explicit LOCK TABLES" No fi - if [ "$(get_stat Delayed_writes "$MYSQL_STATUS_FILE")" -gt 0 ]; then + if [ "$(get_var Delayed_writes "$dir/${prefix}-mysql-status")" -gt 0 ]; then name_val "Delayed Insert" Yes else name_val "Delayed Insert" No fi - if [ "$(get_stat Com_xa_start "$MYSQL_STATUS_FILE")" -gt 0 ]; then + if [ "$(get_var Com_xa_start "$dir/${prefix}-mysql-status")" -gt 0 ]; then name_val "XA Transactions" Yes else name_val "XA Transactions" No fi - if [ "$(get_stat Ndb_cluster_node_id "$MYSQL_STATUS_FILE")" -gt 0 ]; then + if [ "$(get_var Ndb_cluster_node_id "$dir/${prefix}-mysql-status")" -gt 0 ]; then name_val "NDB Cluster" Yes else name_val "NDB Cluster" No fi - local prep=$(( $(get_stat Com_stmt_prepare "$MYSQL_STATUS_FILE") + $(get_stat Com_prepare_sql "$MYSQL_STATUS_FILE") )) + local prep=$(( $(get_var Com_stmt_prepare "$dir/${prefix}-mysql-status") + $(get_var Com_prepare_sql "$dir/${prefix}-mysql-status") )) if [ "${prep}" -gt 0 ]; then name_val "Prepared Statements" Yes else name_val "Prepared Statements" No fi - local prep_count="$(get_stat Prepared_stmt_count "$MYSQL_STATUS_FILE")" + local prep_count="$(get_var Prepared_stmt_count "$dir/${prefix}-mysql-status")" if [ "${prep_count}" ]; then name_val "Prepared statement count" "${prep_count}" fi @@ -2153,12 +2203,12 @@ report_summary () { # InnoDB # ######################################################################## section InnoDB - local have_innodb=$(get_var have_innodb "$MYSQL_VARIABLES_FILE") + local have_innodb=$(get_var have_innodb "$dir/${prefix}-mysql-variables") if [ "${have_innodb}" = "YES" ]; then - _innodb "$MYSQL_VARIABLES_FILE" "$MYSQL_STATUS_FILE" + _innodb "$dir/${prefix}-mysql-variables" "$dir/${prefix}-mysql-status" - if [ -s "$MYSQL_INNODB_STATUS_FILE" ]; then - format_innodb_status "$MYSQL_INNODB_STATUS_FILE" + if [ -s "$dir/${prefix}-innodb-status" ]; then + format_innodb_status "$dir/${prefix}-innodb-status" fi fi @@ -2166,31 +2216,31 @@ report_summary () { # MyISAM # ######################################################################## section MyISAM - _myisam "$MYSQL_VARIABLES_FILE" "$MYSQL_STATUS_FILE" + _myisam "$dir/${prefix}-mysql-variables" "$dir/${prefix}-mysql-status" # ######################################################################## # Users & Security # ######################################################################## section Security - local users="$( format_users "$MYSQL_USERS_FILE")" + local users="$( format_users "$dir/${prefix}-mysql-users" )" # XXX TODO Give it a different formatting? name_val Users "${users}" - name_val "Old Passwords" "$(get_var old_passwords "$MYSQL_VARIABLES_FILE")" + name_val "Old Passwords" "$(get_var old_passwords "$dir/${prefix}-mysql-variables")" # ######################################################################## # Binary Logging # ######################################################################## section Binary_Logging - if [ -s "$MYSQL_MASTER_LOGS_FILE" ] \ - || [ -s "$MYSQL_MASTER_STATUS_FILE" ]; then - summarize_binlogs "$MYSQL_MASTER_LOGS_FILE" - local format="$(get_var binlog_format "$MYSQL_VARIABLES_FILE")" + if [ -s "$dir/${prefix}-mysql-master-logs" ] \ + || [ -s "$dir/${prefix}-mysql-master-status" ]; then + summarize_binlogs "$dir/${prefix}-mysql-master-logs" + local format="$(get_var binlog_format "$dir/${prefix}-mysql-variables")" name_val binlog_format "${format:-STATEMENT}" - name_val expire_logs_days $(get_var expire_logs_days "$MYSQL_VARIABLES_FILE") - name_val sync_binlog $(get_var sync_binlog "$MYSQL_VARIABLES_FILE") - name_val server_id $(get_var server_id "$MYSQL_VARIABLES_FILE") - format_binlog_filters "$MYSQL_MASTER_STATUS_FILE" + name_val expire_logs_days $(get_var expire_logs_days "$dir/${prefix}-mysql-variables") + name_val sync_binlog $(get_var sync_binlog "$dir/${prefix}-mysql-variables") + name_val server_id $(get_var server_id "$dir/${prefix}-mysql-variables") + format_binlog_filters "$dir/${prefix}-mysql-master-status" fi # Replication: seconds behind, running, filters, skip_slave_start, skip_errors, @@ -2200,17 +2250,16 @@ report_summary () { # Interesting things that you just ought to know about. # ######################################################################## section Noteworthy_Variables - _noteworthy_variables "$MYSQL_VARIABLES_FILE" + _noteworthy_variables "$dir/${prefix}-mysql-variables" # ######################################################################## # If there is a my.cnf in a standard location, see if we can pretty-print it. # ######################################################################## section Configuration_File - local cnf_file="$(get_var "pt-summary-internal-Config_File" "$MYSQL_VARIABLES_FILE")" - if [ -e "${cnf_file}" ]; then + local cnf_file="$(get_var "pt-summary-internal-Config_File" "$dir/${prefix}-mysql-variables")" + if [ -n "${cnf_file}" ]; then name_val "Config File" "${cnf_file}" - cp "${cnf_file}" "$PERCONA_TMPFILE" - pretty_print_cnf_file "$PERCONA_TMPFILE" + pretty_print_cnf_file "${cnf_file}" else name_val "Config File" "Cannot autodetect or find, giving up" fi @@ -2219,32 +2268,33 @@ report_summary () { section The_End } -# Run this no matter how we are being executed. -# If being called directly, it'll be run once more inside main(), -# after PATH has been mangled with, which is more likely to yield -# correct results, but if being called indirectly (like when testing) -# this is our best shot at having these available. -setup_commands - # Execute the program if it was not included from another file. # This makes it possible to include without executing, and thus test. if [ "${0##*/}" = "$TOOL" ] \ || [ "${0##*/}" = "bash" -a "$_" = "$0" ]; then - NAME_VAL_LEN=25 - # Set up temporary dir. mk_tmpdir # Parse command line options. - REPORT_UNRECOGNIZED_OPTIONS="" - parse_options $0 "$@" - usage_or_errors $0 + parse_options "$0" "$@" + + # Verify that --sleep, if present, is positive + if [ -n "$OPT_SLEEP" ] && [ "$OPT_SLEEP" -lt 0 ]; then + option_error "Invalid --sleep value: $sleep" + fi + + usage_or_errors "$0" po_status=$? rm_tmpdir if [ $po_status -ne 0 ]; then - exit $po_status + [ $OPT_ERRS -gt 0 ] && exit 1 + exit 0 fi + + # Check if mysql and mysqldump are there, otherwise bail out early. + check_mysql + main "$@" fi @@ -2320,7 +2370,7 @@ then the nearest 10, nearest 25, and then repeats by a factor of 10 larger =head1 OPTIONS -All options not specified here are passed to C. +All options after -- are passed to C. =over @@ -2335,17 +2385,17 @@ first option on the command line. Print help and exit. -=item --tempdir +=item --save-data type: string -Directory to save the collected data in. +Save the data files used to generate the summary in this directory. =item --dump-schemas type: string -TODO, Should this be --databases? Names of databases to dump through myslqdump. If you want all of them, +Names of databases to dump through myslqdump. If you want all of them, you can use --all-databases. If not provided, the program will ask you for manual input. @@ -2367,8 +2417,9 @@ This tool does not use any environment variables. =head1 SYSTEM REQUIREMENTS -This tool requires Bash v3 or newer. On BSD systems, it may require a -mounted procfs. +This tool requires Bash v3 or newer, Perl 5.8 or newer, and binutils. +These are generally already provided by most distributions. +On BSD systems, it may require a mounted procfs. =head1 BUGS diff --git a/bin/pt-summary b/bin/pt-summary index 8adba47d..c0ab7dc4 100755 --- a/bin/pt-summary +++ b/bin/pt-summary @@ -4,18 +4,659 @@ # See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal # notices and disclaimers. +set -u + # ######################################################################## # Globals, settings, helper functions # ######################################################################## POSIXLY_CORRECT=1 export POSIXLY_CORRECT -# The awk code for fuzzy rounding. (It's used in a few places, so makes sense -# not to duplicate). It fuzzy-rounds the variable named fuzzy_var. It goes in -# steps of 5, 10, 25, then repeats by a factor of 10 larger (50, 100, 250), and -# so on, until it finds a number that's large enough. The pattern is slightly -# broken between the initial 1 and 50, because rounding to the nearest 2.5 -# doesn't seem right to me. +PT_SUMMARY_SKIP="${PT_SUMMARY_SKIP:-""}" + +set -u + +PTDEBUG="${PTDEBUG:-""}" + +# ########################################################################### +# log_warn_die package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/bash/log_warn_die.sh +# t/lib/bash/log_warn_die.sh +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### + + +set -u + +PTDEBUG="${PTDEBUG:-""}" +EXIT_STATUS=0 + +log() { + TS=$(date +%F-%T | tr :- _); + echo "$TS $*" +} + +warn() { + log "$*" >&2 + EXIT_STATUS=1 +} + +die() { + warn "$*" + exit 1 +} + +_d () { + [ "$PTDEBUG" ] && log "# $*" >&2 +} + +# ########################################################################### +# End log_warn_die package +# ########################################################################### + +# ########################################################################### +# parse_options package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/bash/parse_options.sh +# t/lib/bash/parse_options.sh +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### + + + + + +set -u + +ARGV="" # Non-option args (probably input files) +EXT_ARGV="" # Everything after -- (args for an external command) +HAVE_EXT_ARGV="" # Got --, everything else is put into EXT_ARGV +OPT_ERRS=0 # How many command line option errors +OPT_VERSION="" # If --version was specified +OPT_HELP="" # If --help was specified +PO_DIR="" # Directory with program option spec files + +usage() { + local file="$1" + + local usage=$(grep '^Usage: ' "$file") + echo $usage + echo + echo "For more information, 'man $TOOL' or 'perldoc $file'." +} + +usage_or_errors() { + local file="$1" + + if [ "$OPT_VERSION" ]; then + local version=$(grep '^pt-[^ ]\+ [0-9]' "$file") + echo "$version" + return 1 + fi + + if [ "$OPT_HELP" ]; then + usage "$file" + echo + echo "Command line options:" + echo + perl -e ' + use strict; + use warnings FATAL => qw(all); + my $lcol = 20; # Allow this much space for option names. + my $rcol = 80 - $lcol; # The terminal is assumed to be 80 chars wide. + my $name; + while ( <> ) { + my $line = $_; + chomp $line; + if ( $line =~ s/^long:/ --/ ) { + $name = $line; + } + elsif ( $line =~ s/^desc:// ) { + $line =~ s/ +$//mg; + my @lines = grep { $_ } + $line =~ m/(.{0,$rcol})(?:\s+|\Z)/g; + if ( length($name) >= $lcol ) { + print $name, "\n", (q{ } x $lcol); + } + else { + printf "%-${lcol}s", $name; + } + print join("\n" . (q{ } x $lcol), @lines); + print "\n"; + } + } + ' "$PO_DIR"/* + echo + echo "Options and values after processing arguments:" + echo + for opt in $(ls "$PO_DIR"); do + local varname="OPT_$(echo "$opt" | tr a-z- A-Z_)" + local varvalue="${!varname}" + printf -- " --%-30s %s" "$opt" "${varvalue:-(No value)}" + echo + done + return 1 + fi + + if [ $OPT_ERRS -gt 0 ]; then + echo + usage "$file" + return 1 + fi + + return 0 +} + +option_error() { + local err="$1" + OPT_ERRS=$(($OPT_ERRS + 1)) + echo "$err" >&2 +} + +parse_options() { + local file="$1" + shift + + ARGV="" + EXT_ARGV="" + HAVE_EXT_ARGV="" + OPT_ERRS=0 + OPT_VERSION="" + OPT_HELP="" + PO_DIR="$TMPDIR/po" + + if [ ! -d "$PO_DIR" ]; then + mkdir "$PO_DIR" + if [ $? -ne 0 ]; then + echo "Cannot mkdir $PO_DIR" >&2 + exit 1 + fi + fi + + rm -rf "$PO_DIR"/* + if [ $? -ne 0 ]; then + echo "Cannot rm -rf $PO_DIR/*" >&2 + exit 1 + fi + + _parse_pod "$file" # Parse POD into program option (po) spec files + _eval_po # Eval po into existence with default values + + if [ $# -ge 2 ] && [ "$1" = "--config" ]; then + shift # --config + local user_config_files="$1" + shift # that ^ + local IFS="," + for user_config_file in $user_config_files; do + _parse_config_files "$user_config_file" + done + else + _parse_config_files "/etc/percona-toolkit/percona-toolkit.conf" "/etc/percona-toolkit/$TOOL.conf" "$HOME/.percona-toolkit.conf" "$HOME/.$TOOL.conf" + fi + + _parse_command_line "$@" +} + +_parse_pod() { + local file="$1" + + cat "$file" | PO_DIR="$PO_DIR" perl -ne ' + BEGIN { $/ = ""; } + next unless $_ =~ m/^=head1 OPTIONS/; + while ( defined(my $para = <>) ) { + last if $para =~ m/^=head1/; + chomp; + if ( $para =~ m/^=item --(\S+)/ ) { + my $opt = $1; + my $file = "$ENV{PO_DIR}/$opt"; + open my $opt_fh, ">", $file or die "Cannot open $file: $!"; + print $opt_fh "long:$opt\n"; + $para = <>; + chomp; + if ( $para =~ m/^[a-z ]+:/ ) { + map { + chomp; + my ($attrib, $val) = split(/: /, $_); + print $opt_fh "$attrib:$val\n"; + } split(/; /, $para); + $para = <>; + chomp; + } + my ($desc) = $para =~ m/^([^?.]+)/; + print $opt_fh "desc:$desc.\n"; + close $opt_fh; + } + } + last; + ' +} + +_eval_po() { + local IFS=":" + for opt_spec in "$PO_DIR"/*; do + local opt="" + local default_val="" + local neg=0 + local size=0 + while read key val; do + case "$key" in + long) + opt=$(echo $val | sed 's/-/_/g' | tr [:lower:] [:upper:]) + ;; + default) + default_val="$val" + ;; + "short form") + ;; + type) + [ "$val" = "size" ] && size=1 + ;; + desc) + ;; + negatable) + if [ "$val" = "yes" ]; then + neg=1 + fi + ;; + *) + echo "Invalid attribute in $opt_spec: $line" >&2 + exit 1 + esac + done < "$opt_spec" + + if [ -z "$opt" ]; then + echo "No long attribute in option spec $opt_spec" >&2 + exit 1 + fi + + if [ $neg -eq 1 ]; then + if [ -z "$default_val" ] || [ "$default_val" != "yes" ]; then + echo "Option $opt_spec is negatable but not default: yes" >&2 + exit 1 + fi + fi + + if [ $size -eq 1 -a -n "$default_val" ]; then + default_val=$(size_to_bytes $default_val) + fi + + eval "OPT_${opt}"="$default_val" + done +} + +_parse_config_files() { + + for config_file in "$@"; do + test -f "$config_file" || continue + + while read config_opt; do + + echo "$config_opt" | grep '^[ ]*[^#]' >/dev/null 2>&1 || continue + + config_opt="$(echo "$config_opt" | sed -e 's/^ *//g' -e 's/ *$//g' -e 's/[ ]*=[ ]*/=/' -e 's/[ ]*#.*$//')" + + [ "$config_opt" = "" ] && continue + + if ! [ "$HAVE_EXT_ARGV" ]; then + config_opt="--$config_opt" + fi + + _parse_command_line "$config_opt" + + done < "$config_file" + + HAVE_EXT_ARGV="" # reset for each file + + done +} + +_parse_command_line() { + local opt="" + local val="" + local next_opt_is_val="" + local opt_is_ok="" + local opt_is_negated="" + local real_opt="" + local required_arg="" + local spec="" + + for opt in "$@"; do + if [ "$opt" = "--" -o "$opt" = "----" ]; then + HAVE_EXT_ARGV=1 + continue + fi + if [ "$HAVE_EXT_ARGV" ]; then + if [ "$EXT_ARGV" ]; then + EXT_ARGV="$EXT_ARGV $opt" + else + EXT_ARGV="$opt" + fi + continue + fi + + if [ "$next_opt_is_val" ]; then + next_opt_is_val="" + if [ $# -eq 0 ] || [ $(expr "$opt" : "-") -eq 1 ]; then + option_error "$real_opt requires a $required_arg argument" + continue + fi + val="$opt" + opt_is_ok=1 + else + if [ $(expr "$opt" : "-") -eq 0 ]; then + if [ -z "$ARGV" ]; then + ARGV="$opt" + else + ARGV="$ARGV $opt" + fi + continue + fi + + real_opt="$opt" + + if $(echo $opt | grep '^--no-' >/dev/null); then + opt_is_negated=1 + opt=$(echo $opt | sed 's/^--no-//') + else + opt_is_negated="" + opt=$(echo $opt | sed 's/^-*//') + fi + + if $(echo $opt | grep '^[a-z-][a-z-]*=' >/dev/null 2>&1); then + val="$(echo $opt | awk -F= '{print $2}')" + opt="$(echo $opt | awk -F= '{print $1}')" + fi + + if [ -f "$TMPDIR/po/$opt" ]; then + spec="$TMPDIR/po/$opt" + else + spec=$(grep "^short form:-$opt\$" "$TMPDIR"/po/* | cut -d ':' -f 1) + if [ -z "$spec" ]; then + option_error "Unknown option: $real_opt" + continue + fi + fi + + required_arg=$(cat "$spec" | awk -F: '/^type:/{print $2}') + if [ "$required_arg" ]; then + if [ "$val" ]; then + opt_is_ok=1 + else + next_opt_is_val=1 + fi + else + if [ "$val" ]; then + option_error "Option $real_opt does not take a value" + continue + fi + if [ "$opt_is_negated" ]; then + val="" + else + val="yes" + fi + opt_is_ok=1 + fi + fi + + if [ "$opt_is_ok" ]; then + opt=$(cat "$spec" | grep '^long:' | cut -d':' -f2 | sed 's/-/_/g' | tr [:lower:] [:upper:]) + + if grep "^type:size" "$spec" >/dev/null; then + val=$(size_to_bytes $val) + fi + + eval "OPT_$opt"="'$val'" + + opt="" + val="" + next_opt_is_val="" + opt_is_ok="" + opt_is_negated="" + real_opt="" + required_arg="" + spec="" + fi + done +} + +size_to_bytes() { + local size="$1" + echo $size | perl -ne '%f=(B=>1, K=>1_024, M=>1_048_576, G=>1_073_741_824, T=>1_099_511_627_776); m/^(\d+)([kMGT])?/i; print $1 * $f{uc($2 || "B")};' +} + +# ########################################################################### +# End parse_options package +# ########################################################################### + +# ########################################################################### +# tmpdir package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/bash/tmpdir.sh +# t/lib/bash/tmpdir.sh +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### + + +set -u + +TMPDIR="" + +mk_tmpdir() { + local dir="${1:-""}" + + if [ -n "$dir" ]; then + if [ ! -d "$dir" ]; then + mkdir "$dir" || die "Cannot make tmpdir $dir" + fi + TMPDIR="$dir" + else + local tool="${0##*/}" + local pid="$$" + TMPDIR=`mktemp -d /tmp/${tool}.${pid}.XXXXX` \ + || die "Cannot make secure tmpdir" + fi +} + +rm_tmpdir() { + if [ -n "$TMPDIR" ] && [ -d "$TMPDIR" ]; then + rm -rf "$TMPDIR" + fi + TMPDIR="" +} + +# ########################################################################### +# End tmpdir package +# ########################################################################### + +# ########################################################################### +# alt_cmds package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/bash/alt_cmds.sh +# t/lib/bash/alt_cmds.sh +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### + + +set -u + +_seq() { + local i="$1" + awk "BEGIN { for(i=1; i<=$i; i++) print i; }" +} + +_pidof() { + local cmd="$1" + if ! pidof "$cmd" 2>/dev/null; then + ps -eo pid,ucomm | awk -v comm="$cmd" '$2 == comm { print $1 }' + fi +} + +_lsof() { + local pid="$1" + if ! lsof -p $pid 2>/dev/null; then + /bin/ls -l /proc/$pid/fd 2>/dev/null + fi +} + +_which() { + [ -x /usr/bin/which ] && /usr/bin/which "$1" 2>/dev/null | awk '{print $1}' +} + +# ########################################################################### +# End alt_cmds package +# ########################################################################### + +# ########################################################################### +# summary_common package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/bash/summary_common.sh +# t/lib/bash/summary_common.sh +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### + + +set -u + +get_nice_of_pid () { + local pid="$1" + local niceness=$(ps -p $pid -o nice | tail -n+2 | awk '{print $1; exit;}') + + if [ -n "${niceness}" ]; then + echo $niceness + else + local tmpfile="$TMPDIR/nice_through_c.tmp.c" + _d "Getting the niceness from ps failed, somehow. We are about to try this:" + cat < "$tmpfile" + +int main(void) { + int priority = getpriority(PRIO_PROCESS, $pid); + if ( priority == -1 && errno == ESRCH ) { + return 1; + } + else { + printf("%d\\n", priority); + return 0; + } +} + +EOC + local c_comp=$(_which gcc) + if [ -z "${c_comp}" ]; then + c_comp=$(_which cc) + fi + _d "$tmpfile: $( cat "$tmpfile" )" + _d "$c_comp -xc \"$tmpfile\" -o \"$tmpfile\" && eval \"$tmpfile\"" + $c_comp -xc "$tmpfile" -o "$tmpfile" 2>/dev/null && eval "$tmpfile" 2>/dev/null + if [ $? -ne 0 ]; then + echo "?" + _d "Failed to get a niceness value for $pid" + fi + fi +} + +get_oom_of_pid () { + local pid="$1" + local oom_adj="" + + if [ -n "${pid}" ] && [ -e /proc/cpuinfo ]; then + if [ -s "/proc/$pid/oom_score_adj" ]; then + oom_adj=$(cat "/proc/$pid/oom_score_adj" 2>/dev/null) + _d "For $pid, the oom value is $oom_adj, retreived from oom_score_adj" + else + oom_adj=$(cat "/proc/$pid/oom_adj" 2>/dev/null) + _d "For $pid, the oom value is $oom_adj, retreived from oom_adj" + fi + fi + + if [ -n "${oom_adj}" ]; then + echo "${oom_adj}" + else + echo "?" + _d "Can't find the oom value for $pid" + fi +} + +CMD_FILE="$( _which file 2>/dev/null )" +CMD_NM="$( _which nm 2>/dev/null )" +CMD_OBJDUMP="$( _which objdump 2>/dev/null )" + +has_symbols () { + local executable="$(_which "$1")" + local has_symbols="" + + if [ "${CMD_FILE}" ] \ + && [ "$($CMD_FILE "${executable}" | grep 'not stripped' )" ]; then + has_symbols=1 + elif [ "${CMD_NM}" ] \ + || [ "${CMD_OBJDMP}" ]; then + if [ "${CMD_NM}" ] \ + && [ !"$("${CMD_NM}" -- "${executable}" 2>&1 | grep 'File format not recognized' )" ]; then + if [ -z "$( $CMD_NM -- "${executable}" 2>&1 | grep ': no symbols' )" ]; then + has_symbols=1 + fi + elif [ -z "$("${CMD_OBJDUMP}" -t -- "${executable}" | grep '^no symbols$' )" ]; then + has_symbols=1 + fi + fi + + if [ "${has_symbols}" ]; then + echo "Yes" + return 0 + else + echo "No" + return 1 + fi +} + +setup_data_dir () { + local data_dir="" + if [ -z "$OPT_SAVE_DATA" ]; then + mkdir "$TMPDIR/data" || die "Cannot mkdir $TMPDIR/data" + data_dir="$TMPDIR/data" + else + if [ ! -d "$OPT_SAVE_DATA" ]; then + mkdir "$OPT_SAVE_DATA" || die "Cannot mkdir $OPT_SAVE_DATA" + fi + touch "$OPT_SAVE_DATA/test" || die "Cannot write to $OPT_SAVE_DATA" + rm "$OPT_SAVE_DATA/test" || die "Cannot rm $OPT_SAVE_DATA/test" + data_dir="$OPT_SAVE_DATA" + fi + echo "$data_dir" +} + +_GET_VAR_DEFAULT=0 +get_var () { + local varname="$1" + local file="$2" + local v="$(awk "\$1 ~ /^${varname}$/ { print \$2 }" "${file}")" + echo "${v:-$_GET_VAR_DEFAULT}" +} + +# ########################################################################### +# End summary_common package +# ########################################################################### + +# ########################################################################### +# report_formatting package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/bash/report_formatting.sh +# t/lib/bash/report_formatting.sh +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### + + +set -u + +POSIXLY_CORRECT=1 +export POSIXLY_CORRECT + fuzzy_formula=' rounded = 0; if (fuzzy_var <= 10 ) { @@ -38,130 +679,479 @@ fuzzy_formula=' factor = factor * 10; }' -# Does fuzzy rounding: rounds to nearest interval, but the interval gets larger -# as the number gets larger. This is to make things easier to diff. fuzz () { - echo $1 | $AP_AWK "{fuzzy_var=\$1; ${fuzzy_formula} print fuzzy_var;}" + _d "fuzz: $1" + echo $1 | awk "{fuzzy_var=\$1; ${fuzzy_formula} print fuzzy_var;}" +} + +fuzzy_pct () { + local pct="$(echo $1 $2 | awk '{ if ($2 > 0) { printf "%d", $1/$2*100; } else {print 0} }')"; + echo "$(fuzz "${pct}")%" +} + +section () { + local str="$1" + local line="$(printf '#_%-60s' "${str}_" | sed -e 's/[[:space:]]/#/g' -e 's/_/ /g')" + printf "%s\n" "${line}" +} + +NAME_VAL_LEN=12 +name_val () { + printf "%+*s | %s\n" "${NAME_VAL_LEN}" "$1" "$2" +} + +shorten() { + local num="$1" + local prec="${2:-2}" + local div="${3:-1024}" + + echo "$num" | awk -v prec="$prec" -v div="$div" ' + { + size = 4; + val = $1; + + unit = val >= 1099511627776 ? "T" : val >= 1073741824 ? "G" : val >= 1048576 ? "M" : val >= 1024 ? "k" : ""; + + while ( int(val) && !(val % 1024) ) { + val /= 1024; + } + + while ( val > 1000 ) { + val /= div; + } + + printf "%.*f%s", prec, val, unit; + } + ' +} + +group_concat () { + sed -e '{H; $!d;}' -e 'x' -e 's/\n[[:space:]]*\([[:digit:]]*\)[[:space:]]*/, \1x/g' -e 's/[[:space:]][[:space:]]*/ /g' -e 's/, //' "${1}" } # ########################################################################### -# tmpdir package +# End report_formatting package +# ########################################################################### + +# ########################################################################### +# collect_system_info package # This package is a copy without comments from the original. The original # with comments and its test file can be found in the Bazaar repository at, -# lib/bash/tmpdir.sh -# t/lib/bash/tmpdir.sh +# lib/bash/collect_system_info.sh +# t/lib/bash/collect_system_info.sh # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### -TMPDIR="" -mk_tmpdir() { - local dir=${1:-""} - if [ -n "$dir" ]; then - if [ ! -d "$dir" ]; then - mkdir $dir || die "Cannot make tmpdir $dir" + +CMD_SYSCTL="$(_which sysctl 2>/dev/null )" +CMD_DMIDECODE="$(_which dmidecode 2>/dev/null )" +CMD_ZONENAME="$(_which zonename 2>/dev/null )" +CMD_DMESG="$(_which dmesg 2>/dev/null )" +CMD_FILE="$(_which file 2>/dev/null )" +CMD_LSPCI="$(_which lspci 2>/dev/null )" +CMD_PRTDIAG="$(_which prtdiag 2>/dev/null )" +CMD_SMBIOS="$(_which smbios 2>/dev/null )" +CMD_GETENFORCE="$(_which getenforce 2>/dev/null )" +CMD_PRTCONF="$(_which prtconf 2>/dev/null )" +CMD_LVS="$(_which lvs 2>/dev/null)" +CMD_VGS="$(_which vgs 2>/dev/null)" +CMD_PRSTAT="$(_which prstat 2>/dev/null)" +CMD_TOP="$(_which top 2>/dev/null)" +CMD_VMSTAT="$(_which vmstat 2>/dev/null)" +CMD_IP="$( _which ip 2>/dev/null )" +CMD_NETSTAT="$( _which netstat 2>/dev/null )" + +collect_system_data () { + local data_dir="$1" + + if [ -r /var/log/dmesg -a -s /var/log/dmesg ]; then + cat "/var/log/dmesg" > "$data_dir/dmesg_file" + fi + + $CMD_SYSCTL -a > "$data_dir/sysctl" 2>/dev/null + + if [ -n "${CMD_LSPCI}" ]; then + $CMD_LSPCI > "$data_dir/lspci_file" 2>/dev/null + fi + + local platform="$(uname -s)" + echo "platform $platform" >> "$data_dir/summary" + echo "hostname $(uname -n)" >> "$data_dir/summary" + echo "uptime $(uptime | awk '{print substr($0, index($0, "up") + 3)}')" >> "$data_dir/summary" + + processor_info "$data_dir" + find_release_and_kernel "$data_dir/summary" "$platform" + cpu_and_os_arch "$data_dir/summary" "$platform" + find_virtualization "$data_dir/summary" "$platform" "$data_dir/dmesg_file" "$data_dir/lspci_file" + dmidecode_system_info "$data_dir/summary" + + if [ "${platform}" = "SunOS" ]; then + if [ -n "${CMD_ZONENAME}" ]; then + echo "zonename $($CMD_ZONENAME)" >> "$data_dir/summary" fi - TMPDIR="$dir" - else - local tool=`basename $0` - local pid="$$" - TMPDIR=`mktemp -d /tmp/${tool}.${pid}.XXXXX` \ - || die "Cannot make secure tmpdir" + fi + + if [ "${platform}" = "Linux" ]; then + echo "threading $(getconf GNU_LIBPTHREAD_VERSION)" >> "$data_dir/summary" + fi + if [ -x /lib/libc.so.6 ]; then + echo "compiler $(/lib/libc.so.6 | grep 'Compiled by' | cut -c13-)" >> "$data_dir/summary" + fi + + if [ "${platform}" = "Linux" ]; then + local getenforce="" + if [ -n "$CMD_GETENFORCE" ]; then + getenforce="$($CMD_GETENFORCE 2>&1)"; + fi + echo "getenforce ${getenforce:-No SELinux detected}" >> "$data_dir/summary" + fi + + local rss=$(ps -eo rss 2>/dev/null | awk '/[0-9]/{total += $1 * 1024} END {print total}') + echo "rss ${rss}" >> "$data_dir/summary" + + if [ "${platform}" = "Linux" ]; then + echo "swappiness $(awk '/vm.swappiness/{print $3}' "$data_dir/sysctl")">> "$data_dir/summary" + echo "dirtypolicy $(awk '/vm.dirty_ratio/{print $3}' "$data_dir/sysctl"), $(awk '/vm.dirty_background_ratio/{print $3}' "$data_dir/sysctl")" >> "$data_dir/summary" + if $(awk '/vm.dirty_bytes/{print $3}' "$data_dir/sysctl") > /dev/null 2>&1; then + echo "dirtystatus $(awk '/vm.dirty_bytes/{print $3}' "$data_dir/sysctl"), $(awk '/vm.dirty_background_bytes/{print $3}' "$data_dir/sysctl")" >> "$data_dir/summary" + fi + fi + + if [ -n "$CMD_DMIDECODE" ]; then + $CMD_DMIDECODE > "$data_dir/dmidecode" 2>/dev/null + fi + + find_memory_stats "$data_dir/memory" "$platform" + mounted_fs_info "$data_dir/mounted_fs" "$platform" "$PT_SUMMARY_SKIP" + raid_controller "$data_dir/summary" "$data_dir/dmesg_file" "$data_dir/lspci_file" + + local controller="$(get_var raid_controller "$data_dir/summary")" + propietary_raid_controller "$data_dir/raid-controller" "$data_dir/summary" "$data_dir" "$controller" + + if [ "${platform}" = "Linux" ]; then + schedulers_and_queue_size "$data_dir/summary" "$data_dir/partitioning" + for file in dentry-state file-nr inode-nr; do + echo "${file} $(cat /proc/sys/fs/${file} 2>&1)" >> "$data_dir/summary" + done + + if [ -n "$CMD_LVS" ] && test -x "$CMD_LVS"; then + $CMD_LVS 1>"$data_dir/lvs" 2>&1 + fi + + if [ -n "$CMD_VGS" ] && test -x "$CMD_VGS"; then + $CMD_VGS -o vg_name,vg_size,vg_free 2>/dev/null > "$data_dir/vgs" + fi + + if [ -n "$CMD_NETSTAT" ] && echo "${PT_SUMMARY_SKIP}" | grep -v NETWORK >/dev/null; then + $CMD_NETSTAT -antp > "$data_dir/netstat" 2>/dev/null + fi + + fi + + if [ -n "$CMD_IP" ] && echo "${PT_SUMMARY_SKIP}" | grep -v NETWORK >/dev/null; then + $CMD_IP -s link > "$data_dir/ip" + fi + + top_processes "$data_dir/processes" "$PT_SUMMARY_SKIP" + notable_processes_info "$data_dir/notable_procs" "$PT_SUMMARY_SKIP" + + if [ -n "$CMD_VMSTAT" ]; then + touch "$data_dir/vmstat" + ( + $CMD_VMSTAT 1 $OPT_SLEEP > "$data_dir/vmstat" + ) & fi } -rm_tmpdir() { - if [ -n "$TMPDIR" ] && [ -d "$TMPDIR" ]; then - rm -rf $TMPDIR - fi - TMPDIR="" -} +find_release_and_kernel () { + local file="$1" + local platform="$2" -# ########################################################################### -# End tmpdir package -# ########################################################################### - -# The temp files are for storing working results so we don't call commands many -# times (gives inconsistent results, maybe adds load on things I don't want to -# such as RAID controllers). They must not exist -- if they did, someone would -# symlink them to /etc/passwd and then run this program as root. Call this -# function with "rm" or "touch" as an argument. -temp_files() { - for file in $TMPDIR/percona-toolkit $TMPDIR/percona-toolkit2; do - case "$1" in - touch) - if ! touch "${file}"; then - echo "I can't make my temp file ${file}"; - exit 1; + local kernel="" + local release="" + if [ "${platform}" = "Linux" ]; then + kernel="$(uname -r)" + if [ -e /etc/fedora-release ]; then + release=$(cat /etc/fedora-release); + elif [ -e /etc/redhat-release ]; then + release=$(cat /etc/redhat-release); + elif [ -e /etc/system-release ]; then + release=$(cat /etc/system-release); + elif _which lsb_release >/dev/null 2>&1; then + release="$(lsb_release -ds) ($(lsb_release -cs))" + elif [ -e /etc/lsb-release ]; then + release=$(grep DISTRIB_DESCRIPTION /etc/lsb-release |awk -F'=' '{print $2}' |sed 's#"##g'); + elif [ -e /etc/debian_version ]; then + release="Debian-based version $(cat /etc/debian_version)"; + if [ -e /etc/apt/sources.list ]; then + local code=` awk '/^deb/ {print $3}' /etc/apt/sources.list \ + | awk -F/ '{print $1}'| awk 'BEGIN {FS="|"}{print $1}' \ + | sort | uniq -c | sort -rn | head -n1 | awk '{print $2}'` + release="${release} (${code})" + fi + elif ls /etc/*release >/dev/null 2>&1; then + if grep -q DISTRIB_DESCRIPTION /etc/*release; then + release=$(grep DISTRIB_DESCRIPTION /etc/*release | head -n1); + else + release=$(cat /etc/*release | head -n1); fi - ;; - rm) - rm -f "${file}" - ;; - esac + fi + elif [ "${platform}" = "FreeBSD" ]; then + release="$(uname -r)" + kernel="$($CMD_SYSCTL -n kern.osrevision)" + elif [ "${platform}" = "SunOS" ]; then + release="$(head -n1 /etc/release)" + if [ -z "${release}" ]; then + release="$(uname -r)" + fi + kernel="$(uname -v)" + fi + echo "kernel $kernel" >> "$file" + echo "release $release" >> "$file" +} + +cpu_and_os_arch () { + local file="$1" + local platform="$2" + + local CPU_ARCH='32-bit' + local OS_ARCH='32-bit' + if [ "${platform}" = "Linux" ]; then + if [ "$(grep -q ' lm ' /proc/cpuinfo)" ]; then + CPU_ARCH='64-bit' + fi + elif [ "${platform}" = "FreeBSD" ]; then + if $CMD_SYSCTL hw.machine_arch | grep -v 'i[36]86' >/dev/null; then + CPU_ARCH='64-bit' + fi + elif [ "${platform}" = "SunOS" ]; then + if isainfo -b | grep 64 >/dev/null ; then + CPU_ARCH="64-bit" + fi + fi + if [ -z "$CMD_FILE" ]; then + OS_ARCH='N/A' + elif $CMD_FILE /bin/sh | grep '64-bit' >/dev/null; then + OS_ARCH='64-bit' + fi + + echo "CPU_ARCH $CPU_ARCH" >> "$file" + echo "OS_ARCH $OS_ARCH" >> "$file" +} + +find_virtualization () { + local vars_file="$1" + local platform="$2" + local dmesg_file="$3" + local lspci_file="$4" + + local tempfile="$TMPDIR/find_virtualziation.tmp" + + local virt="" + if [ -s "$dmesg_file" ]; then + virt="$(parse_virtualization_dmesg "$dmesg_file")" + fi + if [ -z "${virt}" ] && [ -s "$lspci_file" ]; then + if grep -qi virtualbox "$lspci_file" ; then + virt=VirtualBox + elif grep -qi vmware "$lspci_file" ; then + virt=VMWare + fi + elif [ "${platform}" = "FreeBSD" ]; then + if ps -o stat | grep J ; then + virt="FreeBSD Jail" + fi + elif [ "${platform}" = "SunOS" ]; then + if [ -n "$CMD_PRTDIAG" ] && $CMD_PRTDIAG > "$tempfile" 2>/dev/null; then + virt="$(parse_virtualization_generic "$tempfile" )" + elif [ -n "$CMD_SMBIOS" ] && $CMD_SMBIOS > "$tempfile" 2>/dev/null; then + virt="$(parse_virtualization_generic "$tempfile" )" + fi + elif [ -e /proc/user_beancounters ]; then + virt="OpenVZ/Virtuozzo" + fi + echo "virt ${virt:-No virtualization detected}" >> "$vars_file" +} + +dmidecode_system_info () { + local file="$1" + if [ -n "${CMD_DMIDECODE}" ]; then + local vendor="$($CMD_DMIDECODE -s system-manufacturer 2>/dev/null | sed 's/ *$//g')" + echo "vendor ${vendor}" >> "$file" + if [ "${vendor}" ]; then + local product="$($CMD_DMIDECODE -s system-product-name 2>/dev/null | sed 's/ *$//g')" + local version="$($CMD_DMIDECODE -s system-version 2>/dev/null | sed 's/ *$//g')" + local chassis="$($CMD_DMIDECODE -s chassis-type 2>/dev/null | sed 's/ *$//g')" + local servicetag="$($CMD_DMIDECODE -s system-serial-number 2>/dev/null | sed 's/ *$//g')" + local system="${vendor}; ${product}; v${version} (${chassis})" + + echo "system ${system}" >> "$file" + echo "servicetag ${servicetag:-Not found}" >> "$file" + fi + fi +} + +find_memory_stats () { + local file="$1" + local platform="$2" + + if [ "${platform}" = "Linux" ]; then + free -b > "$file" + cat /proc/meminfo >> "$file" + elif [ "${platform}" = "SunOS" ]; then + $CMD_PRTCONF | awk -F: '/Memory/{print $2}' > "$file" + fi +} + +mounted_fs_info () { + local file="$1" + local platform="$2" + local skip="${3:-$PT_SUMMARY_SKIP}" + + if echo "${skip}" | grep -v MOUNT >/dev/null; then + if [ "${platform}" != "SunOS" ]; then + local cmd="df -h" + if [ "${platform}" = "Linux" ]; then + cmd="df -h -P" + fi + $cmd | sort > "$TMPDIR/mounted_fs_info.tmp" + mount | sort | join "$TMPDIR/mounted_fs_info.tmp" - > "$file" + fi + fi +} + +raid_controller () { + local file="$1" + local dmesg_file="$2" + local lspci_file="$3" + + local tempfile="$TMPDIR/raid_controller.tmp" + + local controller="" + if [ -s "$lspci_file" ]; then + controller="$(parse_raid_controller_lspci "$lspci_file")" + fi + if [ -z "${controller}" ] && [ -s "$dmesg_file" ]; then + controller="$(parse_raid_controller_dmesg "$dmesg_file")" + fi + + echo "raid_controller ${controller:-No RAID controller detected}" >> "$file" +} + +schedulers_and_queue_size () { + local file="$1" + local disk_partitioning_file="$2" + + local disks="$(ls /sys/block/ | grep -v -e ram -e loop -e 'fd[0-9]')" + echo "disks $disks" >> "$file" + echo "" > "$disk_partitioning_file" + for disk in $disks; do + if [ -e "/sys/block/${disk}/queue/scheduler" ]; then + echo "internal::${disk} $(cat /sys/block/${disk}/queue/scheduler | grep -o '\[.*\]') $(cat /sys/block/${disk}/queue/nr_requests)" >> "$file" + fdisk -l "/dev/${disk}" >> "$disk_partitioning_file" 2>/dev/null + fi done } -# Print a space-padded string into $line. Then translate spaces to hashes, and -# underscores to spaces. End result is a line of hashes with words at the -# start. -section () { - echo "$1" | awk '{l=sprintf("#_%-60s", $0 "_"); print l}' | sed -e 's/ /#/g' -e 's/_/ /g' +top_processes () { + local top_processes_file="$1" + local skip="${2:-"$PT_SUMMARY_SKIP"}" + + if echo "${skip}" | grep -v PROCESS >/dev/null; then + if [ -n "$CMD_PRSTAT" ]; then + $CMD_PRSTAT | head > "$top_processes_file" + elif [ -n "$CMD_TOP" ]; then + local cmd="$CMD_TOP -bn 1" + if [ "${platform}" = "FreeBSD" ]; then + cmd="$CMD_TOP -b -d 1" + fi + $cmd | sed -e 's# *$##g' -e '/./{H;$!d;}' -e 'x;/PID/!d;' | grep . | head > "$top_processes_file" + fi + fi } -# Print a "name | value" line. -name_val() { - printf "%12s | %s\n" "$1" "$(echo $2)" +notable_processes_info () { + local notable_processes_file="$1" + local skip="${2:-"$PT_SUMMARY_SKIP"}" + + if echo "${skip}" | grep -v PROCESS >/dev/null; then + local sshd_pid=$(_pidof sshd) + + echo " PID OOM COMMAND" > "$notable_processes_file" + + if [ "$sshd_pid" ]; then + echo "$sshd_pid $(get_oom_of_pid $sshd_pid) sshd" >> "$notable_processes_file" + else + _d "sshd doesn't appear to be running" + fi + + ps -eo pid,ucomm | tail -n +2 | while read pid proc; do + [ "$proc" = "sshd" ] && continue + local oom=$(get_oom_of_pid $pid) + if [ "$oom" ] && [ "$oom" != "?" ] && [ "$oom" -eq -17 ]; then + printf "%5s %+2d %s\n" $pid $oom $proc >> "$notable_processes_file" + fi + done + fi } -# Converts a value to units of power of 2. Arg 1: the value. Arg 2: precision (defaults to 2). -shorten() { - echo $@ | awk '{ - unit = "k"; - size = 1024; - val = $1; - prec = 2; - if ( $2 ~ /./ ) { - prec = $2; - } - if ( val >= 1099511627776 ) { - size = 1099511627776; - unit = "T"; - } - else if ( val >= 1073741824 ) { - size = 1073741824; - unit = "G"; - } - else if ( val >= 1048576 ) { - size = 1048576; - unit = "M"; - } - printf "%." prec "f%s", val / size, unit; - }' +processor_info () { + local data_dir="$1" + if [ -f /proc/cpuinfo ]; then + cat /proc/cpuinfo > "$data_dir/proc_cpuinfo_copy" 2>/dev/null + elif [ "${platform}" = "SunOS" ]; then + psrinfo -v > "$data_dir/psrinfo_minus_v" + fi } -# ############################################################################## -# Function to take a file and collapse it into an aggregated list. This -# function works on $1, which it expects to be created with 'sort | -# uniq -c'. Leading whitespace is deleted. The result will look like -# "4xabc, 1xdef" Copy any changes to 'mysql-summary' too. -# ############################################################################## -group_concat () { - sed -e '{H; $!d}' -e 'x' -e 's/\n[[:space:]]*\([[:digit:]]*\)[[:space:]]*/, \1x/g' -e 's/[[:space:]][[:space:]]*/ /g' -e 's/, //' ${1} - # In words: save the whole file into the hold space, - # {H; $!d} - # Swap it back into the pattern space, - # x - # Join lines with a comma, delete leading whitespace, and put an 'x' between - # the number and the text that follows, - # s/\n[[:space:]]*\([[:digit:]]*\)[[:space:]]*/, \1x/g - # Collapse whitespace, - # s/[[:space:]][[:space:]]*/ /g - # And delete the leading comma-space. - # s/, // +propietary_raid_controller () { + local file="$1" + local variable_file="$2" + local data_dir="$3" + local controller="$4" + + rm -f "$file" + touch "$file" + + notfound="" + if [ "${controller}" = "AACRAID" ]; then + if ! _which arcconf >/dev/null 2>&1; then + notfound="e.g. http://www.adaptec.com/en-US/support/raid/scsi_raid/ASR-2120S/" + elif arcconf getconfig 1 > "$file" 2>/dev/null; then + echo "internal::raid_opt 1" >> "$variable_file" + fi + elif [ "${controller}" = "HP Smart Array" ]; then + if ! _which hpacucli >/dev/null 2>&1; then + notfound="your package repository or the manufacturer's website" + elif hpacucli ctrl all show config > "$file" 2>/dev/null; then + echo "internal::raid_opt 2" >> "$variable_file" + fi + elif [ "${controller}" = "LSI Logic MegaRAID SAS" ]; then + if ! _which MegaCli64 >/dev/null 2>&1; then + notfound="your package repository or the manufacturer's website" + else + echo "internal::raid_opt 3" >> "$variable_file" + MegaCli64 -AdpAllInfo -aALL -NoLog > "$data_dir/lsi_megaraid_adapter_info.tmp" 2>/dev/null + MegaCli64 -AdpBbuCmd -GetBbuStatus -aALL -NoLog > "$data_dir/lsi_megaraid_bbu_status.tmp" 2>/dev/null + MegaCli64 -LdPdInfo -aALL -NoLog > "$data_dir/lsi_megaraid_devices.tmp" 2>/dev/null + fi + fi + + if [ "${notfound}" ]; then + echo "internal::raid_opt 0" >> "$variable_file" + echo " RAID controller software not found; try getting it from" > "$file" + echo " ${notfound}" >> "$file" + fi } +# ########################################################################### +# End collect_system_info package +# ########################################################################### + +TOOL="pt-summary" + # ############################################################################## # Functions for parsing specific files and getting desired info from them. # These are called from within main() and are separated so they can be tested @@ -174,14 +1164,14 @@ group_concat () { # Parse Linux's /proc/cpuinfo, which should be stored in $TMPDIR/percona-toolkit. # ############################################################################## parse_proc_cpuinfo () { - local file=$1 + local file="$1" # Physical processors are indicated by distinct 'physical id'. Virtual CPUs # are indicated by paragraphs -- one per paragraph. We assume that all # processors are identical, i.e. that there are not some processors with dual # cores and some with quad cores. - virtual=$(grep -c ^processor $file); - physical=$(grep 'physical id' $file | sort -u | wc -l); - cores=$(grep 'cpu cores' $file | head -n 1 | cut -d: -f2); + virtual=$(grep -c ^processor "${file}"); + physical=$(grep 'physical id' "${file}" | sort -u | wc -l); + cores=$(grep 'cpu cores' "${file}" | head -n 1 | cut -d: -f2); # Older kernel won't have 'physical id' or 'cpu cores'. if [ "${physical}" = "0" ]; then physical=${virtual}; fi @@ -194,15 +1184,15 @@ parse_proc_cpuinfo () { name_val "Processors" "physical = ${physical}, cores = ${cores}, virtual = ${virtual}, hyperthreading = ${htt}" - awk -F: '/cpu MHz/{print $2}' $file \ + awk -F: '/cpu MHz/{print $2}' "${file}" \ | sort | uniq -c > "$file.unq" name_val "Speeds" "$(group_concat "$file.unq")" - awk -F: '/model name/{print $2}' $file \ + awk -F: '/model name/{print $2}' "${file}" \ | sort | uniq -c > "$file.unq" name_val "Models" "$(group_concat "$file.unq")" - awk -F: '/cache size/{print $2}' $file \ + awk -F: '/cache size/{print $2}' "${file}" \ | sort | uniq -c > "$file.unq" name_val "Caches" "$(group_concat "$file.unq")" } @@ -212,17 +1202,29 @@ parse_proc_cpuinfo () { # first argument. # ############################################################################## parse_sysctl_cpu_freebsd() { - virtual="$(awk '/hw.ncpu/{print $2}' "$1")" + local file="$1" + virtual="$(awk '/hw.ncpu/{print $2}' "$file")" name_val "Processors" "virtual = ${virtual}" - name_val "Speeds" "$(awk '/hw.clockrate/{print $2}' "$1")" - name_val "Models" "$(awk -F: '/hw.model/{print substr($2, 2)}' "$1")" + name_val "Speeds" "$(awk '/hw.clockrate/{print $2}' "$file")" + name_val "Models" "$(awk -F: '/hw.model/{print substr($2, 2)}' "$file")" +} + + # ############################################################################## +# Detect cpu info on OpenBSD, and format it as CPU info +# ############################################################################## +parse_sysctl_cpu_openbsd() { + local file="$1" + name_val "Processors" "$(awk '/hw.ncpu/{print $2}' "$file")" + name_val "Speeds" "$(awk '/hw.cpuspeed/{print $2}' "$file")" + name_val "Models" "$(awk '/hw.model/{print $2}' "$file")" } # ############################################################################## # Parse CPU info from psrinfo -v # ############################################################################## parse_psrinfo_cpus() { - name_val Processors $(grep -c 'Status of .* processor' "$1") + local file="$1" + name_val Processors $(grep -c 'Status of .* processor' "$file") awk '/operates at/ { start = index($0, " at ") + 4; end = length($0) - start - 4 @@ -235,17 +1237,18 @@ parse_psrinfo_cpus() { # Parse the output of 'free -b' plus the contents of /proc/meminfo # ############################################################################## parse_free_minus_b () { - local file=$1 + local file="$1" local physical=$(awk '/Mem:/{print $3}' "${file}") - local swap=$(awk '/Swap:/{print $3}' "${file}") - local virtual=$(shorten $(($physical + $swap))) + local swap_alloc=$(awk '/Swap:/{print $2}' "${file}") + local swap_used=$(awk '/Swap:/{print $3}' "${file}") + local virtual=$(shorten $(($physical + $swap_used)) 1) - name_val Total $(shorten $(awk '/Mem:/{print $2}' "${file}")) - name_val Free $(shorten $(awk '/Mem:/{print $4}' "${file}")) - name_val Used "physical = $(shorten ${physical}), swap = $(shorten ${swap}), virtual = ${virtual}" - name_val Buffers $(shorten $(awk '/Mem:/{print $6}' "${file}")) - name_val Caches $(shorten $(awk '/Mem:/{print $7}' "${file}")) + name_val Total $(shorten $(awk '/Mem:/{print $2}' "${file}") 1) + name_val Free $(shorten $(awk '/Mem:/{print $4}' "${file}") 1) + name_val Used "physical = $(shorten ${physical} 1), swap allocated = $(shorten ${swap_alloc} 1), swap used = $(shorten ${swap_used} 1), virtual = ${virtual}" + name_val Buffers $(shorten $(awk '/Mem:/{print $6}' "${file}") 1) + name_val Caches $(shorten $(awk '/Mem:/{print $7}' "${file}") 1) name_val Dirty "$(awk '/Dirty:/ {print $2, $3}' "${file}")" } @@ -253,9 +1256,10 @@ parse_free_minus_b () { # Parse FreeBSD memory info from sysctl output. # ############################################################################## parse_memory_sysctl_freebsd() { - physical=$(awk '/hw.realmem:/{print $2}' "${1}") - mem_hw=$(awk '/hw.physmem:/{print $2}' "${1}") - mem_used=$(awk ' + local file="$1" + local physical=$(awk '/hw.realmem:/{print $2}' "${file}") + local mem_hw=$(awk '/hw.physmem:/{print $2}' "${file}") + local mem_used=$(awk ' /hw.physmem/ { mem_hw = $2; } /vm.stats.vm.v_inactive_count/ { mem_inactive = $2; } /vm.stats.vm.v_cache_count/ { mem_cache = $2; } @@ -273,11 +1277,22 @@ parse_memory_sysctl_freebsd() { name_val Used $(shorten ${mem_used} 1) } + # ############################################################################## +# Parse OpenBSD memory info from sysctl output. +# ############################################################################## +parse_memory_sysctl_openbsd() { + local file="$1" + local swap_mem="$(echo "$(swapctl -s | awk '{print $2;}')*512" | bc -l)" + name_val Total $(shorten "$(awk '/hw.physmem/{print $2}' "$file")" 1) + name_val User $(shorten "$(awk '/hw.usermem/{print $2}' "$file")" 1) + name_val Swap $(shorten ${swap_mem} 1) +} + # ############################################################################## # Parse memory devices from the output of 'dmidecode'. # ############################################################################## parse_dmidecode_mem_devices () { - local file=$1 + local file="$1" echo " Locator Size Speed Form Factor Type Type Detail" echo " ========= ======== ================= ============= ============= ===========" # Print paragraphs containing 'Memory Device\n', extract the desired bits, @@ -295,7 +1310,7 @@ parse_dmidecode_mem_devices () { -e 's//}/g' \ -e 's/[ \t]*\n/\n/g' \ - $file \ + "${file}" \ | awk -F: '/Size|Type|Form.Factor|Type.Detail|[^ ]Locator/{printf("|%s", $2)}/Speed/{print "|" $2}' \ | sed -e 's/No Module Installed/{EMPTY}/' \ | sort \ @@ -303,9 +1318,10 @@ parse_dmidecode_mem_devices () { } # ############################################################################## -# Parse the output of 'netstat -antp' +# Parse the output of 'ip -s link' # ############################################################################## parse_ip_s_link () { + local file="$1" echo " interface rx_bytes rx_packets rx_errors tx_bytes tx_packets tx_errors" echo " ========= ========= ========== ========== ========== ========== ==========" @@ -326,18 +1342,18 @@ parse_ip_s_link () { fuzzy_var = \$3; ${fuzzy_formula} tx_errors = fuzzy_var; printf \" %-8s %10d %10d %10d %10d %10d %10d\\n\", save[\"iface\"], save[\"bytes\"], save[\"packs\"], save[\"errs\"], tx_bytes, tx_packets, tx_errors; } - }" $@ + }" "$file" } # ############################################################################## -# Parse the output of 'netstat -antp' which should be in $TMPDIR/percona-toolkit. +# Parse the output of 'netstat -antp' # ############################################################################## parse_netstat () { - local file=$1 + local file="$1" echo " Connections from remote IP addresses" awk '$1 ~ /^tcp/ && $5 ~ /^[1-9]/ { print substr($5, 0, index($5, ":") - 1); - }' $file | sort | uniq -c \ + }' "${file}" | sort | uniq -c \ | awk "{ fuzzy_var=\$1; ${fuzzy_formula} @@ -347,7 +1363,7 @@ parse_netstat () { echo " Connections to local IP addresses" awk '$1 ~ /^tcp/ && $5 ~ /^[1-9]/ { print substr($4, 0, index($4, ":") - 1); - }' $file | sort | uniq -c \ + }' "${file}" | sort | uniq -c \ | awk "{ fuzzy_var=\$1; ${fuzzy_formula} @@ -357,7 +1373,7 @@ parse_netstat () { echo " Connections to top 10 local ports" awk '$1 ~ /^tcp/ && $5 ~ /^[1-9]/ { print substr($4, index($4, ":") + 1); - }' $file | sort | uniq -c | sort -rn | head -n10 \ + }' "${file}" | sort | uniq -c | sort -rn | head -n10 \ | awk "{ fuzzy_var=\$1; ${fuzzy_formula} @@ -366,7 +1382,7 @@ parse_netstat () { echo " States of connections" awk '$1 ~ /^tcp/ { print $6; - }' $file | sort | uniq -c | sort -rn \ + }' "${file}" | sort | uniq -c | sort -rn \ | awk "{ fuzzy_var=\$1; ${fuzzy_formula} @@ -383,10 +1399,10 @@ parse_filesystems () { # requires two passes through the file. The first pass finds the max size of # these columns and prints out a printf spec, and the second prints out the # file nicely aligned. - local file=$1 - local platform=$2 + local file="$1" + local platform="$2" - local spec=$(awk " + local spec="$(awk " BEGIN { device = 10; fstype = 4; @@ -414,7 +1430,7 @@ parse_filesystems () { END{ print \"%-\" device \"s %5s %4s %-\" fstype \"s %-\" options \"s %s\"; } - " $file) + " "${file}")" awk " BEGIN { @@ -431,7 +1447,7 @@ parse_filesystems () { } printf spec, \$1, \$2, \$5, f_fstype, f_options, \$6; } - " $file + " "${file}" } # ############################################################################## @@ -439,7 +1455,7 @@ parse_filesystems () { # multiple fdisk -l outputs in the file. # ############################################################################## parse_fdisk () { - local file=$1 + local file="$1" awk ' BEGIN { format="%-12s %4s %10s %10s %18s\n"; @@ -465,26 +1481,25 @@ parse_fdisk () { } printf(format, $1, "Part", start, end, sprintf("%.0f", (end - start) * units)); } - ' $file + ' "${file}" } # ############################################################################## -# Parse the output of dmesg, which should be in $TMPDIR/percona-toolkit, and detect -# virtualization. +# Parse the output of dmesg and detect virtualization. # ############################################################################## parse_virtualization_dmesg () { - local file=$1 - if grep -qi -e vmware -e vmxnet -e 'paravirtualized kernel on vmi' $file; then + local file="$1" + if grep -qi -e vmware -e vmxnet -e 'paravirtualized kernel on vmi' "${file}"; then echo "VMWare"; - elif grep -qi -e 'paravirtualized kernel on xen' -e 'Xen virtual console' $file; then + elif grep -qi -e 'paravirtualized kernel on xen' -e 'Xen virtual console' "${file}"; then echo "Xen"; - elif grep -qi qemu $file; then + elif grep -qi qemu "${file}"; then echo "QEmu"; - elif grep -qi 'paravirtualized kernel on KVM' $file; then + elif grep -qi 'paravirtualized kernel on KVM' "${file}"; then echo "KVM"; - elif grep -q VBOX $file; then + elif grep -q VBOX "${file}"; then echo "VirtualBox"; - elif grep -qi 'hd.: Virtual .., ATA.*drive' $file; then + elif grep -qi 'hd.: Virtual .., ATA.*drive' "${file}"; then echo "Microsoft VirtualPC"; fi } @@ -501,36 +1516,34 @@ parse_virtualization_generic() { } # ############################################################################## -# Parse the output of lspci, which should be in $TMPDIR/percona-toolkit, and detect -# Ethernet cards. +# Parse the output of lspci, and detect ethernet cards. # ############################################################################## parse_ethernet_controller_lspci () { - local file=$1 - grep -i ethernet $file | cut -d: -f3 | while read line; do + local file="$1" + grep -i ethernet "${file}" | cut -d: -f3 | while read line; do name_val Controller "${line}" done } # ############################################################################## -# Parse the output of lspci, which should be in $TMPDIR/percona-toolkit, and detect RAID -# controllers. +# Parse the output of lspci and detect RAID controllers. # ############################################################################## parse_raid_controller_lspci () { - local file=$1 - if grep -q "RAID bus controller: LSI Logic / Symbios Logic MegaRAID SAS" $file; then + local file="$1" + if grep -q "RAID bus controller: LSI Logic / Symbios Logic MegaRAID SAS" "${file}"; then echo 'LSI Logic MegaRAID SAS' - elif grep -q "Fusion-MPT SAS" $file; then + elif grep -q "Fusion-MPT SAS" "${file}"; then echo 'Fusion-MPT SAS' - elif grep -q "RAID bus controller: LSI Logic / Symbios Logic Unknown" $file; then + elif grep -q "RAID bus controller: LSI Logic / Symbios Logic Unknown" "${file}"; then echo 'LSI Logic Unknown' - elif grep -q "RAID bus controller: Adaptec AAC-RAID" $file; then + elif grep -q "RAID bus controller: Adaptec AAC-RAID" "${file}"; then echo 'AACRAID' - elif grep -q "3ware [0-9]* Storage Controller" $file; then + elif grep -q "3ware [0-9]* Storage Controller" "${file}"; then echo '3Ware' - elif grep -q "Hewlett-Packard Company Smart Array" $file; then + elif grep -q "Hewlett-Packard Company Smart Array" "${file}"; then echo 'HP Smart Array' - elif grep -q " RAID bus controller: " $file; then - awk -F: '/RAID bus controller\:/ {print $3" "$5" "$6}' $file + elif grep -q " RAID bus controller: " "${file}"; then + awk -F: '/RAID bus controller\:/ {print $3" "$5" "$6}' "${file}" fi } @@ -539,15 +1552,15 @@ parse_raid_controller_lspci () { # controllers. # ############################################################################## parse_raid_controller_dmesg () { - local file=$1 + local file="$1" pat='scsi[0-9].*: .*' - if grep -qi "${pat}megaraid" $file; then + if grep -qi "${pat}megaraid" "${file}"; then echo 'LSI Logic MegaRAID SAS' - elif grep -q "Fusion MPT SAS" $file; then + elif grep -q "Fusion MPT SAS" "${file}"; then echo 'Fusion-MPT SAS' - elif grep -q "${pat}aacraid" $file; then + elif grep -q "${pat}aacraid" "${file}"; then echo 'AACRAID' - elif grep -q "${pat}3ware [0-9]* Storage Controller" $file; then + elif grep -q "${pat}3ware [0-9]* Storage Controller" "${file}"; then echo '3Ware' fi } @@ -557,22 +1570,22 @@ parse_raid_controller_dmesg () { # $TMPDIR/percona-toolkit # ############################################################################## parse_hpacucli () { - local file=$1 - grep 'logicaldrive\|physicaldrive' $file + local file="$1" + grep 'logicaldrive\|physicaldrive' "${file}" } # ############################################################################## # Parse the output of arcconf, which should be stored in $TMPDIR/percona-toolkit # ############################################################################## parse_arcconf () { - local file=$1 - model=$(awk -F: '/Controller Model/{print $2}' $file) - chan="$(awk -F: '/Channel description/{print $2}' $file)" - cache="$(awk -F: '/Installed memory/{print $2}' $file)" - status="$(awk -F: '/Controller Status/{print $2}' $file)" + local file="$1" + model=$(awk -F: '/Controller Model/{print $2}' "${file}") + chan="$(awk -F: '/Channel description/{print $2}' "${file}")" + cache="$(awk -F: '/Installed memory/{print $2}' "${file}")" + status="$(awk -F: '/Controller Status/{print $2}' "${file}")" name_val Specs "${model/ /},${chan},${cache} cache,${status}" - battery=$(grep -A5 'Controller Battery Info' $file \ + battery=$(grep -A5 'Controller Battery Info' "${file}" \ | awk '/Capacity remaining/ {c=$4} /Status/ {s=$3} /Time remaining/ {t=sprintf("%dd%dh%dm", $7, $9, $11)} @@ -585,8 +1598,8 @@ parse_arcconf () { echo echo " LogicalDev Size RAID Disks Stripe Status Cache" echo " ========== ========= ==== ===== ====== ======= =======" - for dev in $(awk '/Logical device number/{print $4}' $file); do - sed -n -e "/^Logical device .* ${dev}$/,/^$\|^Logical device number/p" $file \ + for dev in $(awk '/Logical device number/{print $4}' "${file}"); do + sed -n -e "/^Logical device .* ${dev}$/,/^$\|^Logical device number/p" "${file}" \ | awk ' /Logical device name/ {d=$5} /Size/ {z=$3 " " $4} @@ -612,7 +1625,7 @@ parse_arcconf () { # Find the paragraph with physical devices, tabularize with assoc arrays. tempresult="" - sed -n -e '/Physical Device information/,/^$/p' $file \ + sed -n -e '/Physical Device information/,/^$/p' "${file}" \ | awk -F: ' /Device #[0-9]/ { device=substr($0, index($0, "#")); @@ -664,57 +1677,61 @@ parse_arcconf () { # ############################################################################## # Parse the output of "lsiutil -i -s". # ############################################################################## +# TODO This isn't used anywhere parse_fusionmpt_lsiutil () { - local file=$1 + local file="$1" echo - awk '/LSI.*Firmware/ { print " ", $0 }' $file - grep . $file | sed -n -e '/B___T___L/,$ {s/^/ /; p}' + awk '/LSI.*Firmware/ { print " ", $0 }' "${file}" + grep . "${file}" | sed -n -e '/B___T___L/,$ {s/^/ /; p}' } # ############################################################################## # Parse the output of MegaCli64 -AdpAllInfo -aALL from $TMPDIR/percona-toolkit. # ############################################################################## +# TODO why aren't we printing the latter half? parse_lsi_megaraid_adapter_info () { - local file=$1 - name=$(awk -F: '/Product Name/{print substr($2, 2)}' $file); - int=$(awk '/Host Interface/{print $4}' $file); - prt=$(awk '/Number of Backend Port/{print $5}' $file); - bbu=$(awk '/^BBU :/{print $3}' $file); - mem=$(awk '/Memory Size/{print $4}' $file); - vdr=$(awk '/Virtual Drives/{print $4}' $file); - dvd=$(awk '/Degraded/{print $3}' $file); - phy=$(awk '/^ Disks/{print $3}' $file); - crd=$(awk '/Critical Disks/{print $4}' $file); - fad=$(awk '/Failed Disks/{print $4}' $file); + local file="$1" + + local name="$(awk -F: '/Product Name/{print substr($2, 2)}' "${file}")"; + local int=$(awk '/Host Interface/{print $4}' "${file}"); + local prt=$(awk '/Number of Backend Port/{print $5}' "${file}"); + local bbu=$(awk '/^BBU :/{print $3}' "${file}"); + local mem=$(awk '/Memory Size/{print $4}' "${file}"); + local vdr=$(awk '/Virtual Drives/{print $4}' "${file}"); + local dvd=$(awk '/Degraded/{print $3}' "${file}"); + local phy=$(awk '/^ Disks/{print $3}' "${file}"); + local crd=$(awk '/Critical Disks/{print $4}' "${file}"); + local fad=$(awk '/Failed Disks/{print $4}' "${file}"); + name_val Model "${name}, ${int} interface, ${prt} ports" name_val Cache "${mem} Memory, BBU ${bbu}" } # ############################################################################## -# Parse the output (saved in $TMPDIR/percona-toolkit) of +# Parse the output of # /opt/MegaRAID/MegaCli/MegaCli64 -AdpBbuCmd -GetBbuStatus -aALL # ############################################################################## parse_lsi_megaraid_bbu_status () { - local file=$1 - charge=$(awk '/Relative State/{print $5}' $file); - temp=$(awk '/^Temperature/{print $2}' $file); - soh=$(awk '/isSOHGood:/{print $2}' $file); + local file="$1" + local charge=$(awk '/Relative State/{print $5}' "${file}"); + local temp=$(awk '/^Temperature/{print $2}' "${file}"); + local soh=$(awk '/isSOHGood:/{print $2}' "${file}"); name_val BBU "${charge}% Charged, Temperature ${temp}C, isSOHGood=${soh}" } # ############################################################################## -# Parse physical devices from the output (saved in $TMPDIR/percona-toolkit) of +# Parse physical devices from the output of # /opt/MegaRAID/MegaCli/MegaCli64 -LdPdInfo -aALL # OR, it will also work with the output of # /opt/MegaRAID/MegaCli/MegaCli64 -PDList -aALL # ############################################################################## parse_lsi_megaraid_devices () { - local file=$1 + local file="$1" echo echo " PhysiclDev Type State Errors Vendor Model Size" echo " ========== ==== ======= ====== ======= ============ ===========" - for dev in $(awk '/Device Id/{print $3}' $file); do - sed -e '/./{H;$!d;}' -e "x;/Device Id: ${dev}/!d;" $file \ + for dev in $(awk '/Device Id/{print $3}' "${file}"); do + sed -e '/./{H;$!d;}' -e "x;/Device Id: ${dev}/!d;" "${file}" \ | awk ' /Media Type/ {d=substr($0, index($0, ":") + 2)} /PD Type/ {t=$3} @@ -732,13 +1749,13 @@ parse_lsi_megaraid_devices () { } # ############################################################################## -# Parse virtual devices from the output (saved in $TMPDIR/percona-toolkit) of +# Parse virtual devices from the output of # /opt/MegaRAID/MegaCli/MegaCli64 -LdPdInfo -aALL # OR, it will also work with the output of # /opt/MegaRAID/MegaCli/MegaCli64 -LDInfo -Lall -aAll # ############################################################################## parse_lsi_megaraid_virtual_devices () { - local file=$1 + local file="$1" # Somewhere on the Internet, I found the following guide to understanding the # RAID level, but I don't know the source anymore. # Primary-0, Secondary-0, RAID Level Qualifier-0 = 0 @@ -809,7 +1826,40 @@ parse_lsi_megaraid_virtual_devices () { devices[device ",stripe"], devices[device ",state"], devices[device ",wpolicy"] ", " devices[device ",rpolicy"]); } - }' $file + }' "${file}" +} + +# ############################################################################## +# Reports the output of lvs. Additionally, if the second argument is a file +# that contains the output of 'vgs -o vg_name,vg_size,vg_free', appends the +# total and free space available to each volume. +# ############################################################################## +parse_lvs () { + local lvs_file="$1" + local vgs_file="$2" + + if [ -e "$lvs_file" -a -e "$vgs_file" ]; then + local header="$(head -n1 "$lvs_file")$(head -n1 "$vgs_file" | sed -e 's/^ *VG//')" + + echo $header + tail -n+2 "$lvs_file" | while read lvs_line; do + local current_vg="$(echo $lvs_line | awk '{print $2}')" + while read vgs_line; do + local current_vgs_vg="$(echo $vgs_line | awk '{print $1}' )" + if [ "$current_vg" = "$current_vgs_vg" ]; then + lvs_line="${lvs_line}$(echo $vgs_line | sed -e "s/^ *$current_vg//")" + break + fi + done < "$vgs_file" + echo $lvs_line + done + else + if [ -e "$lvs_file" ]; then + cat "$lvs_file" + else + echo "Cannot execute 'lvs'"; + fi + fi } # ############################################################################## @@ -817,7 +1867,7 @@ parse_lsi_megaraid_virtual_devices () { # system activity is enough. # ############################################################################## format_vmstat () { - local file=$1 + local file="$1" awk " BEGIN { format = \" %2s %2s %4s %4s %5s %5s %6s %6s %3s %3s %3s %3s %3s\n\"; @@ -844,17 +1894,19 @@ format_vmstat () { fuzzy_var = \$17; st = fuzzy_var; printf format, r, b, si, so, bi, bo, ir, cs, us, sy, il, wa, st; } - " $file + " "${file}" } # ############################################################################## # The main() function is called at the end of the script. This makes it # testable. Major bits of parsing are separated into functions for testability. -# As a general rule, we cannot 'cp' files from /proc, because they might be -# empty afterwards. (I've seen 'cp /proc/cpuinfo' create an empty file.) But -# 'cat' works okay. # ############################################################################## main () { + trap sigtrap HUP INT TERM + + local RAN_WITH="--sleep=$OPT_SLEEP --save-data=$OPT_SAVE_DATA" + + _d "Starting $0 $RAN_WITH" # Begin by setting the $PATH to include some common locations that are not # always in the $PATH, including the "sbin" locations, and some common @@ -865,189 +1917,123 @@ main () { # Set up temporary files. mk_tmpdir - temp_files "rm" - temp_files "touch" - section Percona_Toolkit_System_Summary_Report - # ######################################################################## - # Grab a bunch of stuff and put it into temp files for later. - # ######################################################################## - sysctl -a > $TMPDIR/percona-toolkit.sysctl 2>/dev/null + local data_dir="$(setup_data_dir)" + + _d "Temp dir is [$TMPDIR], saving data in [$data_dir]" + + collect_system_data "$data_dir" + + report_summary "$data_dir" + + rm_tmpdir +} + +sigtrap() { + warn "Caught signal, forcing exit" + exit $EXIT_STATUS +} + +processes_section () { + local top_process_file="$1" + local notable_procs_file="$2" + local vmstat_file="$3" + local platform="$4" + + if echo "${PT_SUMMARY_SKIP}" | grep -v PROCESS >/dev/null; then + section Top_Processes + cat "$top_process_file" + section Notable_Processes + cat "$notable_procs_file" + if [ -e "$vmstat_file" ]; then + section "Simplified_and_fuzzy_rounded_vmstat_(wait_please)" + wait # For the process we forked that was gathering vmstat samples + if [ "${platform}" = "Linux" ]; then + format_vmstat "$vmstat_file" + else + # TODO: simplify/format for other platforms + cat "$vmstat_file" + fi + fi + fi +} + +report_summary () { + local data_dir="$1" + + section Percona_Toolkit_System_Summary_Report # ######################################################################## # General date, time, load, etc # ######################################################################## - platform="$(uname -s)" + + local platform="$(uname -s)" name_val "Date" "`date -u +'%F %T UTC'` (local TZ: `date +'%Z %z'`)" - name_val "Hostname" "$(uname -n)" - name_val "Uptime" "$(uptime | awk '{print substr($0, index($0, "up") + 3)}')" - if which dmidecode > /dev/null 2>&1; then - vendor="$(dmidecode -s system-manufacturer 2>/dev/null | sed 's/ *$//g')" - if [ "${vendor}" ]; then - product="$(dmidecode -s system-product-name 2>/dev/null | sed 's/ *$//g')" - version="$(dmidecode -s system-version 2>/dev/null | sed 's/ *$//g')" - chassis="$(dmidecode -s chassis-type 2>/dev/null | sed 's/ *$//g')" - system="${vendor}; ${product}; v${version} (${chassis})" - name_val "System" "${system}"; - servicetag="$(dmidecode -s system-serial-number 2>/dev/null | sed 's/ *$//g')" - name_val "Service Tag" "${servicetag:-Not found}"; - fi + name_val "Hostname" "$(get_var hostname "$data_dir/summary")" + name_val "Uptime" "$(get_var uptime "$data_dir/summary")" + + if [ -n "$(get_var vendor "$data_dir/summary")" ]; then + name_val "System" "$(get_var system "$data_dir/summary")"; + name_val "Service Tag" "$(get_var servicetag "$data_dir/summary")"; fi + name_val "Platform" "${platform}" - if [ "${platform}" = "SunOS" ]; then - if which zonename >/dev/null 2>&1 ; then - name_val "Zonename" "$(zonename)" - fi + local zonename="$(get_var zonename "$data_dir/summary")"; + if [ -n "${zonename}" ]; then + name_val "Zonename" "$zonename" fi - # Try to find all sorts of different files that say what the release is. - if [ "${platform}" = "Linux" ]; then - kernel="$(uname -r)" - if [ -e /etc/fedora-release ]; then - release=$(cat /etc/fedora-release); - elif [ -e /etc/redhat-release ]; then - release=$(cat /etc/redhat-release); - elif [ -e /etc/system-release ]; then - release=$(cat /etc/system-release); - elif which lsb_release >/dev/null 2>&1; then - release="$(lsb_release -ds) ($(lsb_release -cs))" - elif [ -e /etc/lsb-release ]; then - release=$(grep DISTRIB_DESCRIPTION /etc/lsb-release |awk -F'=' '{print $2}' |sed 's#"##g'); - elif [ -e /etc/debian_version ]; then - release="Debian-based version $(cat /etc/debian_version)"; - if [ -e /etc/apt/sources.list ]; then - code=`cat /etc/apt/sources.list |awk '/^deb/ {print $3}' |awk -F/ '{print $1}'| awk 'BEGIN {FS="|"}{print $1}' | sort | uniq -c | sort -rn |head -n1 |awk '{print $2}'` - release="${release} (${code})" - fi - elif ls /etc/*release >/dev/null 2>&1; then - if grep -q DISTRIB_DESCRIPTION /etc/*release; then - release=$(grep DISTRIB_DESCRIPTION /etc/*release | head -n1); - else - release=$(cat /etc/*release | head -n1); - fi - fi - elif [ "${platform}" = "FreeBSD" ]; then - release="$(uname -r)" - kernel="$(sysctl -n kern.osrevision)" - elif [ "${platform}" = "SunOS" ]; then - release="$(head -n1 /etc/release)" - if [ -z "${release}" ]; then - release="$(uname -r)" - fi - kernel="$(uname -v)" - fi - name_val Release "${release}" - name_val Kernel "${kernel}" + name_val Release "$(get_var release "$data_dir/summary")" + name_val Kernel "$(get_var kernel "$data_dir/summary")" - CPU_ARCH='32-bit' - OS_ARCH='32-bit' - if [ "${platform}" = "Linux" ]; then - if grep -q ' lm ' /proc/cpuinfo; then - CPU_ARCH='64-bit' - fi - elif [ "${platform}" = "FreeBSD" ]; then - if sysctl hw.machine_arch | grep -v 'i[36]86' >/dev/null; then - CPU_ARCH='64-bit' - fi - elif [ "${platform}" = "SunOS" ]; then - if isainfo -b | grep 64 >/dev/null ; then - CPU_ARCH="64-bit" - fi - fi - if file /bin/sh | grep '64-bit' >/dev/null; then - OS_ARCH='64-bit' - fi - name_val "Architecture" "CPU = $CPU_ARCH, OS = $OS_ARCH" + name_val "Architecture" "CPU = $(get_var CPU_ARCH "$data_dir/summary"), OS = $(get_var OS_ARCH "$data_dir/summary")" - # Threading library - if [ "${platform}" = "Linux" ]; then - name_val Threading "$(getconf GNU_LIBPTHREAD_VERSION)" - fi - if [ -x /lib/libc.so.6 ]; then - name_val "Compiler" "$(/lib/libc.so.6 | grep 'Compiled by' | cut -c13-)" - fi + local threading="$(get_var threading "$data_dir/summary")" + local compiler="$(get_var compiler "$data_dir/summary")" + [ -n "$threading" ] && name_val Threading "$threading" + [ -n "$compiler" ] && name_val Compiler "$compiler" - if [ "${platform}" = "Linux" ]; then - if getenforce >/dev/null 2>&1; then - getenforce="$(getenforce 2>&1)"; - fi - name_val "SELinux" "${getenforce:-No SELinux detected}"; - fi + local getenforce="$(get_var getenforce "$data_dir/summary")" + [ -n "$getenforce" ] && name_val "SELinux" "${getenforce}"; - # We look in dmesg for virtualization information first, because it's often - # available to non-root users and usually has telltale signs. It's most - # reliable to look at /var/log/dmesg if possible. There are a number of - # other ways to find out if a system is virtualized. - cat /var/log/dmesg > $TMPDIR/percona-toolkit 2>/dev/null - if [ ! -s $TMPDIR/percona-toolkit ]; then - dmesg > $TMPDIR/percona-toolkit 2>/dev/null - fi - if [ -s $TMPDIR/percona-toolkit ]; then - virt="$(parse_virtualization_dmesg $TMPDIR/percona-toolkit)" - fi - if [ -z "${virt}" ]; then - if which lspci >/dev/null 2>&1; then - lspci > $TMPDIR/percona-toolkit 2>/dev/null - if grep -qi virtualbox $TMPDIR/percona-toolkit; then - virt=VirtualBox - elif grep -qi vmware $TMPDIR/percona-toolkit; then - virt=VMWare - elif [ -e /proc/user_beancounters ]; then - virt="OpenVZ/Virtuozzo" - fi - fi - elif [ "${platform}" = "FreeBSD" ]; then - if ps -o stat | grep J ; then - virt="FreeBSD Jail" - fi - elif [ "${platform}" = "SunOS" ]; then - if which prtdiag >/dev/null 2>&1 && prtdiag > $TMPDIR/percona-toolkit.prtdiag 2>/dev/null; then - virt="$(parse_virtualization_generic $TMPDIR/percona-toolkit.prtdiag)" - elif which smbios >/dev/null 2>&1 && smbios > $TMPDIR/percona-toolkit.smbios 2>/dev/null; then - virt="$(parse_virtualization_generic $TMPDIR/percona-toolkit.smbios)" - fi - fi - name_val Virtualized "${virt:-No virtualization detected}" + name_val Virtualized "$(get_var virt "$data_dir/summary")" # ######################################################################## # Processor/CPU, Memory, Swappiness, dmidecode # ######################################################################## section Processor - if [ -f /proc/cpuinfo ]; then - cat /proc/cpuinfo > $TMPDIR/percona-toolkit 2>/dev/null - parse_proc_cpuinfo $TMPDIR/percona-toolkit + if [ -e "$data_dir/proc_cpuinfo_copy" ]; then + parse_proc_cpuinfo "$data_dir/proc_cpuinfo_copy" elif [ "${platform}" = "FreeBSD" ]; then - parse_sysctl_cpu_freebsd $TMPDIR/percona-toolkit.sysctl + parse_sysctl_cpu_freebsd "$data_dir/sysctl" elif [ "${platform}" = "SunOS" ]; then - psrinfo -v > $TMPDIR/percona-toolkit - parse_psrinfo_cpus $TMPDIR/percona-toolkit + parse_psrinfo_cpus "$data_dir/psrinfo_minus_v" # TODO: prtconf -v actually prints the CPU model name etc. fi section Memory if [ "${platform}" = "Linux" ]; then - free -b > $TMPDIR/percona-toolkit - cat /proc/meminfo >> $TMPDIR/percona-toolkit - parse_free_minus_b $TMPDIR/percona-toolkit + parse_free_minus_b "$data_dir/memory" elif [ "${platform}" = "FreeBSD" ]; then - parse_memory_sysctl_freebsd $TMPDIR/percona-toolkit.sysctl + parse_memory_sysctl_freebsd "$data_dir/sysctl" elif [ "${platform}" = "SunOS" ]; then - name_val Memory "$(prtconf | awk -F: '/Memory/{print $2}')" + name_val Memory "$(cat "$data_dir/memory")" fi - rss=$(ps -eo rss 2>/dev/null | awk '/[0-9]/{total += $1 * 1024} END {print total}') + local rss=$( get_var rss "$data_dir/summary" ) name_val UsedRSS "$(shorten ${rss} 1)" if [ "${platform}" = "Linux" ]; then - name_val Swappiness "$(sysctl vm.swappiness 2>&1)" - name_val DirtyPolicy "$(sysctl vm.dirty_ratio 2>&1), $(sysctl vm.dirty_background_ratio 2>&1)" - if sysctl vm.dirty_bytes > /dev/null 2>&1; then - name_val DirtyStatus "$(sysctl vm.dirty_bytes 2>&1), $(sysctl vm.dirty_background_bytes 2>&1)" + name_val Swappiness "$(get_var swappiness "$data_dir/summary")" + name_val DirtyPolicy "$(get_var dirtypolicy "$data_dir/summary")" + local dirty_status="$(get_var dirtystatus "$data_dir/summary")" + if [ -n "$dirty_status" ]; then + name_val DirtyStatus "$dirty_status" fi fi - if which dmidecode >/dev/null 2>&1 && dmidecode > $TMPDIR/percona-toolkit 2>/dev/null; then - parse_dmidecode_mem_devices $TMPDIR/percona-toolkit + if [ -s "$data_dir/dmidecode" ]; then + parse_dmidecode_mem_devices "$data_dir/dmidecode" fi # ######################################################################## @@ -1055,105 +2041,61 @@ main () { # ######################################################################## # TODO: Add info about software RAID - if echo "${PT_SUMMARY_SKIP}" | grep -v MOUNT >/dev/null; then - if [ "${platform}" != "SunOS" ]; then - section "Mounted_Filesystems" - cmd="df -h" - if [ "${platform}" = "Linux" ]; then - cmd="df -h -P" - fi - $cmd | sort > $TMPDIR/percona-toolkit2 - mount | sort | join $TMPDIR/percona-toolkit2 - > $TMPDIR/percona-toolkit - parse_filesystems $TMPDIR/percona-toolkit "${platform}" - fi + if [ -s "$data_dir/mounted_fs" ]; then + section "Mounted_Filesystems" + parse_filesystems "$data_dir/mounted_fs" "${platform}" fi if [ "${platform}" = "Linux" ]; then section "Disk_Schedulers_And_Queue_Size" - echo "" > $TMPDIR/percona-toolkit - for disk in $(ls /sys/block/ | grep -v -e ram -e loop -e 'fd[0-9]'); do - if [ -e "/sys/block/${disk}/queue/scheduler" ]; then - name_val "${disk}" "$(cat /sys/block/${disk}/queue/scheduler | grep -o '\[.*\]') $(cat /sys/block/${disk}/queue/nr_requests)" - fdisk -l "/dev/${disk}" >> $TMPDIR/percona-toolkit 2>/dev/null - fi + + local disks="$( get_var disks "$data_dir/summary" )" + for disk in ${disks}; do + name_val "${disk}" "$( get_var "internal::${disk}" "$data_dir/summary" )" done - # Relies on $TMPDIR/percona-toolkit having data from the Disk Schedulers loop. section "Disk_Partioning" - parse_fdisk $TMPDIR/percona-toolkit + parse_fdisk "$data_dir/partitioning" section "Kernel_Inode_State" for file in dentry-state file-nr inode-nr; do - name_val "${file}" "$(cat /proc/sys/fs/${file} 2>&1)" + name_val "${file}" "$(get_var "${file}" "$data_dir/summary")" done section "LVM_Volumes" - - if which lvs >/dev/null 2>&1 && test -x "$(which lvs)"; then - lvs 2>&1 - else - echo "Cannot execute 'lvs'"; - fi + parse_lvs "$data_dir/lvs" "$data_dir/vgs" fi section "RAID_Controller" - - # ######################################################################## - # We look in lspci first because it's more reliable, then dmesg, because it's - # often available to non-root users. It's most reliable to look at - # /var/log/dmesg if possible. - # ######################################################################## - if which lspci >/dev/null 2>&1 && lspci > $TMPDIR/percona-toolkit 2>/dev/null; then - controller="$(parse_raid_controller_lspci $TMPDIR/percona-toolkit)" - fi - if [ -z "${controller}" ]; then - cat /var/log/dmesg > $TMPDIR/percona-toolkit 2>/dev/null - if [ ! -s $TMPDIR/percona-toolkit ]; then - dmesg > $TMPDIR/percona-toolkit 2>/dev/null - fi - controller="$(parse_raid_controller_dmesg $TMPDIR/percona-toolkit)" - fi - - name_val Controller "${controller:-No RAID controller detected}" - - # ######################################################################## - # Attempt to get, parse, and print RAID controller status from possibly - # proprietary management software. Any executables that are normally stored - # in a weird location, such as /usr/StorMan/arcconf, should have their - # location added to $PATH at the beginning of main(). - # ######################################################################## - notfound="" - if [ "${controller}" = "AACRAID" ]; then - if arcconf getconfig 1 > $TMPDIR/percona-toolkit 2>/dev/null; then - parse_arcconf $TMPDIR/percona-toolkit - elif ! which arcconf >/dev/null 2>&1; then - notfound="e.g. http://www.adaptec.com/en-US/support/raid/scsi_raid/ASR-2120S/" - fi - elif [ "${controller}" = "HP Smart Array" ]; then - if hpacucli ctrl all show config > $TMPDIR/percona-toolkit 2>/dev/null; then - parse_hpacucli $TMPDIR/percona-toolkit - elif ! which hpacucli >/dev/null 2>&1; then - notfound="your package repository or the manufacturer's website" - fi - elif [ "${controller}" = "LSI Logic MegaRAID SAS" ]; then - if MegaCli64 -AdpAllInfo -aALL -NoLog > $TMPDIR/percona-toolkit 2>/dev/null; then - parse_lsi_megaraid_adapter_info $TMPDIR/percona-toolkit - elif ! which MegaCli64 >/dev/null 2>&1; then - notfound="your package repository or the manufacturer's website" - fi - if MegaCli64 -AdpBbuCmd -GetBbuStatus -aALL -NoLog > $TMPDIR/percona-toolkit 2>/dev/null; then - parse_lsi_megaraid_bbu_status $TMPDIR/percona-toolkit - fi - if MegaCli64 -LdPdInfo -aALL -NoLog > $TMPDIR/percona-toolkit 2>/dev/null; then - parse_lsi_megaraid_virtual_devices $TMPDIR/percona-toolkit - parse_lsi_megaraid_devices $TMPDIR/percona-toolkit - fi - fi - - if [ "${notfound}" ]; then - echo " RAID controller software not found; try getting it from" - echo " ${notfound}" - fi + local controller="$(get_var raid_controller "$data_dir/summary")" + name_val Controller "$controller" + local key="$(get_var "internal::raid_opt" "$data_dir/summary")" + case "$key" in + 0) + # Not found + cat "$data_dir/raid-controller" + ;; + 1) + parse_arcconf "$data_dir/raid-controller" + ;; + 2) + parse_hpacucli "$data_dir/raid-controller" + ;; + 3) + # TODO: This is pretty bad form, but seeing how the three forms + # aren't mutually exclusive, I can't come up with a better way. + [ -e "$data_dir/lsi_megaraid_adapter_info.tmp" ] && \ + parse_lsi_megaraid_adapter_info "$data_dir/lsi_megaraid_adapter_info.tmp" + [ -e "$data_dir/lsi_megaraid_bbu_status.tmp" ] && \ + parse_lsi_megaraid_bbu_status "$data_dir/lsi_megaraid_bbu_status.tmp" + if [ -e "$data_dir/lsi_megaraid_devices.tmp" ]; then + parse_lsi_megaraid_virtual_devices "$data_dir/lsi_megaraid_devices.tmp" + parse_lsi_megaraid_devices "$data_dir/lsi_megaraid_devices.tmp" + fi + ;; + *) + die "Invalid option for raid-controller" + esac if echo "${PT_SUMMARY_SKIP}" | grep -v NETWORK >/dev/null; then # ##################################################################### @@ -1161,12 +2103,12 @@ main () { # ##################################################################### if [ "${platform}" = "Linux" ]; then section Network_Config - if which lspci > /dev/null 2>&1 && lspci > $TMPDIR/percona-toolkit 2>/dev/null; then - parse_ethernet_controller_lspci $TMPDIR/percona-toolkit + if [ -s "$data_dir/lspci_file" ]; then + parse_ethernet_controller_lspci "$data_dir/lspci_file" fi - if sysctl net.ipv4.tcp_fin_timeout > /dev/null 2>&1; then - name_val "FIN Timeout" "$(sysctl net.ipv4.tcp_fin_timeout)" - name_val "Port Range" "$(sysctl net.ipv4.ip_local_port_range)" + if grep net.ipv4.tcp_fin_timeout "$data_dir/sysctl" > /dev/null 2>&1; then + name_val "FIN Timeout" "$(awk '/net.ipv4.tcp_fin_timeout/{print $NF}' "$data_dir/sysctl")" + name_val "Port Range" "$(awk '/net.ipv4.ip_local_port_range/{print $NF}' "$data_dir/sysctl")" fi fi @@ -1174,60 +2116,51 @@ main () { # /proc/sys/net/netfilter/nf_conntrack_max or /proc/sys/net/nf_conntrack_max # in new kernels like Fedora 12? - if which ip >/dev/null 2>&1 && ip -s link > $TMPDIR/percona-toolkit 2>/dev/null; then + if [ -s "$data_dir/ip" ]; then section Interface_Statistics - parse_ip_s_link $TMPDIR/percona-toolkit + parse_ip_s_link "$data_dir/ip" fi - if [ "${platform}" = "Linux" ]; then + if [ "${platform}" = "Linux" ] && [ -e "$data_dir/netstat" ]; then section Network_Connections - if netstat -antp > $TMPDIR/percona-toolkit 2>/dev/null; then - parse_netstat $TMPDIR/percona-toolkit - fi + parse_netstat "$data_dir/netstat" fi fi # ######################################################################## # Processes, load, etc # ######################################################################## - if echo "${PT_SUMMARY_SKIP}" | grep -v PROCESS >/dev/null; then - section Top_Processes - if which prstat > /dev/null 2>&1; then - prstat | head - elif which top > /dev/null 2>&1 ; then - cmd="top -bn 1" - if [ "${platform}" = "FreeBSD" ]; then - cmd="top -b -d 1" - fi - $cmd | sed -e 's# *$##g' -e '/./{H;$!d;}' -e 'x;/PID/!d;' | grep . | head - fi - if which vmstat > /dev/null 2>&1 ; then - section "Simplified_and_fuzzy_rounded_vmstat_(wait_please)" - vmstat 1 5 > $TMPDIR/percona-toolkit - if [ "${platform}" = "Linux" ]; then - format_vmstat $TMPDIR/percona-toolkit - else - # TODO: simplify/format for other platforms - cat $TMPDIR/percona-toolkit - fi - fi - fi + processes_section "$data_dir/processes" "$data_dir/notable_procs" "$data_dir/vmstat" "$platform" # ######################################################################## # All done. Signal the end so it's explicit. # ######################################################################## - temp_files "rm" - temp_files "check" - rm_tmpdir section The_End } # Execute the program if it was not included from another file. This makes it # possible to include without executing, and thus test. -if [ "$(basename "$0")" = "pt-summary" ] || [ "$(basename "$0")" = "bash" -a "$_" = "$0" ]; then - main $@ +# Execute the program if it was not included from another file. +# This makes it possible to include without executing, and thus test. +if [ "${0##*/}" = "$TOOL" ] \ + || [ "${0##*/}" = "bash" -a "$_" = "$0" ]; then + + # Set up temporary dir. + mk_tmpdir + # Parse command line options. + REPORT_UNRECOGNIZED_OPTIONS="" + parse_options $0 "$@" + usage_or_errors $0 + po_status=$? + rm_tmpdir + + if [ $po_status -ne 0 ]; then + exit $po_status + fi + main "$@" fi + # ############################################################################ # Documentation # ############################################################################ @@ -1285,7 +2218,36 @@ although some output might not be possible to generate without root. =head1 OPTIONS -This tool does not have any command-line options. +=over + +=item --config + +type: string + +Read this comma-separated list of config files. If specified, this must be the +first option on the command line. + +=item --help + +Print help and exit. + +=item --save-data + +type: string + +Save the data files used to generate the summary in this directory. + +=item --sleep + +type: int; default: 5 + +How much time to sleep when gathering samples from vmstat. + +=item --version + +Print tool's version and exit. + +=back =head1 ENVIRONMENT diff --git a/lib/bash/collect_mysql_info.sh b/lib/bash/collect_mysql_info.sh new file mode 100644 index 00000000..7aa54dbf --- /dev/null +++ b/lib/bash/collect_mysql_info.sh @@ -0,0 +1,192 @@ +# 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 + +# Simply looks for instances of mysqld in the outof of ps. +collect_mysqld_instances () { + local file="$1" + ps auxww 2>/dev/null | grep mysqld > "$file" +} + +# 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 test -n "$port" && grep -- "/mysqld.*--port=$port" "${file}" >/dev/null 2>&1 ; then + cnf_file="$(grep -- "/mysqld.*--port=$port" "${file}" \ + | awk 'BEGIN{RS=" "; FS="=";} $1 ~ /--defaults-file/ { print $2; }' \ + | head -n1)" + else + cnf_file="$(grep '/mysqld' "${file}" \ + | awk 'BEGIN{RS=" "; FS="=";} $1 ~ /--defaults-file/ { print $2; }' \ + | head -n1)" + fi + + if [ ! -n "${cnf_file}" ]; then + _d "Cannot autodetect config file, trying common locations" + cnf_file="/etc/my.cnf"; + if [ ! -e "${cnf_file}" ]; then + cnf_file="/etc/mysql/my.cnf"; + fi + if [ ! -e "${cnf_file}" ]; then + cnf_file="/var/db/mysql/my.cnf"; + fi + fi + + echo "$cnf_file" +} + +collect_mysql_variables () { + local file="$1" + $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW /*!40100 GLOBAL*/ VARIABLES' > "$file" +} + +collect_mysql_status () { + local file="$1" + $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW /*!50000 GLOBAL*/ STATUS' > "$file" +} + +collect_mysql_databases () { + local file="$1" + $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW DATABASES' > "$file" 2>/dev/null +} + +collect_mysql_plugins () { + local file="$1" + $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW PLUGINS' > "$file" 2>/dev/null +} + +collect_mysql_slave_status () { + local file="$1" + $CMD_MYSQL $EXT_ARGV -ssE -e 'SHOW SLAVE STATUS' > "$file" 2>/dev/null +} + +collect_mysql_innodb_status () { + local file="$1" + $CMD_MYSQL $EXT_ARGV -ssE -e 'SHOW /*!50000 ENGINE*/ INNODB STATUS' > "$file" 2>/dev/null +} + +collect_mysql_processlist () { + local file="$1" + $CMD_MYSQL $EXT_ARGV -ssE -e 'SHOW FULL PROCESSLIST' > "$file" 2>/dev/null +} + +collect_mysql_users () { + local file="$1" + $CMD_MYSQL $EXT_ARGV -ssE -e 'SELECT COUNT(*), SUM(user=""), SUM(password=""), SUM(password NOT LIKE "*%") FROM mysql.user' > "$file" 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" + local defer_file="$2" + collect_mysql_status "$TMPDIR/defer_gatherer" + cat "$TMPDIR/defer_gatherer" | join "$status_file" - > "$defer_file" +} + +collect_internal_vars () { + local file="$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) + local has_symbols="$(has_symbols "${CMD_MYSQL}")" + + echo "pt-summary-internal-now $now" >> "$file" + echo "pt-summary-internal-user $user" >> "$file" + echo "pt-summary-internal-FNV_64 $FNV_64" >> "$file" + echo "pt-summary-internal-trigger_count $trigger_count" >> "$file" + echo "pt-summary-internal-symbols $has_symbols" >> "$file" +} + +collect_mysql_info () { + local dir="$1" + local prefix="$2" + + collect_mysqld_instances "$dir/${prefix}-mysqld-instances" + + collect_mysql_variables "$dir/${prefix}-mysql-variables" + collect_mysql_status "$dir/${prefix}-mysql-status" + collect_mysql_databases "$dir/${prefix}-mysql-databases" + collect_mysql_plugins "$dir/${prefix}-mysql-plugins" + collect_mysql_slave_status "$dir/${prefix}-mysql-slave" + collect_mysql_innodb_status "$dir/${prefix}-innodb-status" + collect_mysql_processlist "$dir/${prefix}-mysql-processlist" + collect_mysql_users "$dir/${prefix}-mysql-users" + + local binlog="$(get_var log_bin "$dir/${prefix}-mysql-variables")" + if [ "${binlog}" ]; then + _d "Got a binlog, going to get MASTER LOGS and MASTER STATUS" + collect_master_logs_status "$dir/${prefix}-mysql-master-logs" "$dir/${prefix}-mysql-master-status" + fi + + local uptime="$(get_var Uptime "$dir/${prefix}-mysql-status")" + local current_time="$($CMD_MYSQL $EXT_ARGV -ss -e \ + "SELECT LEFT(NOW() - INTERVAL ${uptime} SECOND, 16)")" + + local port="$(get_var port "$dir/${prefix}-mysql-variables")" + local cnf_file=$(find_my_cnf_file "$dir/${prefix}-mysqld-instances" ${port}); + + # TODO: Do these require a file of their own? + echo "pt-summary-internal-current_time $current_time" >> "$dir/${prefix}-mysql-variables" + echo "pt-summary-internal-Config_File $cnf_file" >> "$dir/${prefix}-mysql-variables" + collect_internal_vars "$dir/${prefix}-mysql-variables" + + if [ -n "${OPT_DUMP_SCHEMAS}" ]; then + _d "--dump-schemas passed in, dumping early" + local trg_arg="$( get_mysqldump_args "$dir/${prefix}-mysql-variables" )" + get_mysqldump_for "$dir/${prefix}-mysqldump" "${trg_arg}" "${OPT_DUMP_SCHEMAS}" + fi + + # TODO: gather this data in the same format as normal: TS line, stats + ( + sleep $OPT_SLEEP + collect_mysql_deferred_status "$dir/${prefix}-mysql-status" "$dir/${prefix}-mysql-status-defer" + ) & + _d "Forked child is $!" +} + +# ########################################################################### +# End collect_mysql_info package +# ########################################################################### diff --git a/lib/bash/collect_system_info.sh b/lib/bash/collect_system_info.sh new file mode 100644 index 00000000..46700f9b --- /dev/null +++ b/lib/bash/collect_system_info.sh @@ -0,0 +1,456 @@ +# 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_system_info package +# ########################################################################### + +# Package: collect_system_info +# collects system information. + +# XXX +# THIS LIB REQUIRES log_warn_die.sh, summary_common.sh, and alt_cmds.sh! +# XXX + + +# While extremely unwieldly, this allows us to fake the commands when testing. +CMD_SYSCTL="$(_which sysctl 2>/dev/null )" +CMD_DMIDECODE="$(_which dmidecode 2>/dev/null )" +CMD_ZONENAME="$(_which zonename 2>/dev/null )" +CMD_DMESG="$(_which dmesg 2>/dev/null )" +CMD_FILE="$(_which file 2>/dev/null )" +CMD_LSPCI="$(_which lspci 2>/dev/null )" +CMD_PRTDIAG="$(_which prtdiag 2>/dev/null )" +CMD_SMBIOS="$(_which smbios 2>/dev/null )" +CMD_GETENFORCE="$(_which getenforce 2>/dev/null )" +CMD_PRTCONF="$(_which prtconf 2>/dev/null )" +CMD_LVS="$(_which lvs 2>/dev/null)" +CMD_VGS="$(_which vgs 2>/dev/null)" +CMD_PRSTAT="$(_which prstat 2>/dev/null)" +CMD_TOP="$(_which top 2>/dev/null)" +CMD_VMSTAT="$(_which vmstat 2>/dev/null)" +CMD_IP="$( _which ip 2>/dev/null )" +CMD_NETSTAT="$( _which netstat 2>/dev/null )" + +collect_system_data () { + local data_dir="$1" + + if [ -r /var/log/dmesg -a -s /var/log/dmesg ]; then + cat "/var/log/dmesg" > "$data_dir/dmesg_file" + fi + + # ######################################################################## + # Grab a bunch of stuff and put it into temp files for later. + # ######################################################################## + $CMD_SYSCTL -a > "$data_dir/sysctl" 2>/dev/null + + if [ -n "${CMD_LSPCI}" ]; then + $CMD_LSPCI > "$data_dir/lspci_file" 2>/dev/null + fi + + local platform="$(uname -s)" + echo "platform $platform" >> "$data_dir/summary" + echo "hostname $(uname -n)" >> "$data_dir/summary" + echo "uptime $(uptime | awk '{print substr($0, index($0, "up") + 3)}')" >> "$data_dir/summary" + + processor_info "$data_dir" + find_release_and_kernel "$data_dir/summary" "$platform" + cpu_and_os_arch "$data_dir/summary" "$platform" + find_virtualization "$data_dir/summary" "$platform" "$data_dir/dmesg_file" "$data_dir/lspci_file" + dmidecode_system_info "$data_dir/summary" + + if [ "${platform}" = "SunOS" ]; then + if [ -n "${CMD_ZONENAME}" ]; then + echo "zonename $($CMD_ZONENAME)" >> "$data_dir/summary" + fi + fi + + # Threading library + if [ "${platform}" = "Linux" ]; then + echo "threading $(getconf GNU_LIBPTHREAD_VERSION)" >> "$data_dir/summary" + fi + if [ -x /lib/libc.so.6 ]; then + echo "compiler $(/lib/libc.so.6 | grep 'Compiled by' | cut -c13-)" >> "$data_dir/summary" + fi + + if [ "${platform}" = "Linux" ]; then + local getenforce="" + if [ -n "$CMD_GETENFORCE" ]; then + getenforce="$($CMD_GETENFORCE 2>&1)"; + fi + echo "getenforce ${getenforce:-No SELinux detected}" >> "$data_dir/summary" + fi + + local rss=$(ps -eo rss 2>/dev/null | awk '/[0-9]/{total += $1 * 1024} END {print total}') + echo "rss ${rss}" >> "$data_dir/summary" + + if [ "${platform}" = "Linux" ]; then + echo "swappiness $(awk '/vm.swappiness/{print $3}' "$data_dir/sysctl")">> "$data_dir/summary" + echo "dirtypolicy $(awk '/vm.dirty_ratio/{print $3}' "$data_dir/sysctl"), $(awk '/vm.dirty_background_ratio/{print $3}' "$data_dir/sysctl")" >> "$data_dir/summary" + if $(awk '/vm.dirty_bytes/{print $3}' "$data_dir/sysctl") > /dev/null 2>&1; then + echo "dirtystatus $(awk '/vm.dirty_bytes/{print $3}' "$data_dir/sysctl"), $(awk '/vm.dirty_background_bytes/{print $3}' "$data_dir/sysctl")" >> "$data_dir/summary" + fi + fi + + if [ -n "$CMD_DMIDECODE" ]; then + $CMD_DMIDECODE > "$data_dir/dmidecode" 2>/dev/null + fi + + find_memory_stats "$data_dir/memory" "$platform" + mounted_fs_info "$data_dir/mounted_fs" "$platform" "$PT_SUMMARY_SKIP" + raid_controller "$data_dir/summary" "$data_dir/dmesg_file" "$data_dir/lspci_file" + + local controller="$(get_var raid_controller "$data_dir/summary")" + propietary_raid_controller "$data_dir/raid-controller" "$data_dir/summary" "$data_dir" "$controller" + + if [ "${platform}" = "Linux" ]; then + schedulers_and_queue_size "$data_dir/summary" "$data_dir/partitioning" + for file in dentry-state file-nr inode-nr; do + echo "${file} $(cat /proc/sys/fs/${file} 2>&1)" >> "$data_dir/summary" + done + + if [ -n "$CMD_LVS" ] && test -x "$CMD_LVS"; then + $CMD_LVS 1>"$data_dir/lvs" 2>&1 + fi + + if [ -n "$CMD_VGS" ] && test -x "$CMD_VGS"; then + $CMD_VGS -o vg_name,vg_size,vg_free 2>/dev/null > "$data_dir/vgs" + fi + + if [ -n "$CMD_NETSTAT" ] && echo "${PT_SUMMARY_SKIP}" | grep -v NETWORK >/dev/null; then + $CMD_NETSTAT -antp > "$data_dir/netstat" 2>/dev/null + fi + + fi + + if [ -n "$CMD_IP" ] && echo "${PT_SUMMARY_SKIP}" | grep -v NETWORK >/dev/null; then + $CMD_IP -s link > "$data_dir/ip" + fi + + top_processes "$data_dir/processes" "$PT_SUMMARY_SKIP" + notable_processes_info "$data_dir/notable_procs" "$PT_SUMMARY_SKIP" + + if [ -n "$CMD_VMSTAT" ]; then + touch "$data_dir/vmstat" + ( + $CMD_VMSTAT 1 $OPT_SLEEP > "$data_dir/vmstat" + ) & + fi +} + +# Try to find all sorts of different files that say what the release is. +find_release_and_kernel () { + local file="$1" + local platform="$2" + + local kernel="" + local release="" + if [ "${platform}" = "Linux" ]; then + kernel="$(uname -r)" + if [ -e /etc/fedora-release ]; then + release=$(cat /etc/fedora-release); + elif [ -e /etc/redhat-release ]; then + release=$(cat /etc/redhat-release); + elif [ -e /etc/system-release ]; then + release=$(cat /etc/system-release); + elif _which lsb_release >/dev/null 2>&1; then + release="$(lsb_release -ds) ($(lsb_release -cs))" + elif [ -e /etc/lsb-release ]; then + release=$(grep DISTRIB_DESCRIPTION /etc/lsb-release |awk -F'=' '{print $2}' |sed 's#"##g'); + elif [ -e /etc/debian_version ]; then + release="Debian-based version $(cat /etc/debian_version)"; + if [ -e /etc/apt/sources.list ]; then + local code=` awk '/^deb/ {print $3}' /etc/apt/sources.list \ + | awk -F/ '{print $1}'| awk 'BEGIN {FS="|"}{print $1}' \ + | sort | uniq -c | sort -rn | head -n1 | awk '{print $2}'` + release="${release} (${code})" + fi + elif ls /etc/*release >/dev/null 2>&1; then + if grep -q DISTRIB_DESCRIPTION /etc/*release; then + release=$(grep DISTRIB_DESCRIPTION /etc/*release | head -n1); + else + release=$(cat /etc/*release | head -n1); + fi + fi + elif [ "${platform}" = "FreeBSD" ]; then + release="$(uname -r)" + kernel="$($CMD_SYSCTL -n kern.osrevision)" + elif [ "${platform}" = "SunOS" ]; then + release="$(head -n1 /etc/release)" + if [ -z "${release}" ]; then + release="$(uname -r)" + fi + kernel="$(uname -v)" + fi + echo "kernel $kernel" >> "$file" + echo "release $release" >> "$file" +} + +cpu_and_os_arch () { + local file="$1" + local platform="$2" + + local CPU_ARCH='32-bit' + local OS_ARCH='32-bit' + if [ "${platform}" = "Linux" ]; then + if [ "$(grep -q ' lm ' /proc/cpuinfo)" ]; then + CPU_ARCH='64-bit' + fi + elif [ "${platform}" = "FreeBSD" ]; then + if $CMD_SYSCTL hw.machine_arch | grep -v 'i[36]86' >/dev/null; then + CPU_ARCH='64-bit' + fi + elif [ "${platform}" = "SunOS" ]; then + if isainfo -b | grep 64 >/dev/null ; then + CPU_ARCH="64-bit" + fi + fi + if [ -z "$CMD_FILE" ]; then + OS_ARCH='N/A' + elif $CMD_FILE /bin/sh | grep '64-bit' >/dev/null; then + OS_ARCH='64-bit' + fi + + echo "CPU_ARCH $CPU_ARCH" >> "$file" + echo "OS_ARCH $OS_ARCH" >> "$file" +} + +# We look in dmesg for virtualization information first, because it's often +# available to non-root users and usually has telltale signs. It's most +# reliable to look at /var/log/dmesg if possible. There are a number of +# other ways to find out if a system is virtualized. +find_virtualization () { + local vars_file="$1" + local platform="$2" + local dmesg_file="$3" + local lspci_file="$4" + + local tempfile="$TMPDIR/find_virtualziation.tmp" + + local virt="" + if [ -s "$dmesg_file" ]; then + virt="$(parse_virtualization_dmesg "$dmesg_file")" + fi + if [ -z "${virt}" ] && [ -s "$lspci_file" ]; then + if grep -qi virtualbox "$lspci_file" ; then + virt=VirtualBox + elif grep -qi vmware "$lspci_file" ; then + virt=VMWare + fi + elif [ "${platform}" = "FreeBSD" ]; then + if ps -o stat | grep J ; then + virt="FreeBSD Jail" + fi + elif [ "${platform}" = "SunOS" ]; then + if [ -n "$CMD_PRTDIAG" ] && $CMD_PRTDIAG > "$tempfile" 2>/dev/null; then + virt="$(parse_virtualization_generic "$tempfile" )" + elif [ -n "$CMD_SMBIOS" ] && $CMD_SMBIOS > "$tempfile" 2>/dev/null; then + virt="$(parse_virtualization_generic "$tempfile" )" + fi + elif [ -e /proc/user_beancounters ]; then + virt="OpenVZ/Virtuozzo" + fi + echo "virt ${virt:-No virtualization detected}" >> "$vars_file" +} + +# TODO: Maybe worth it to just dump dmidecode once and parse that? +dmidecode_system_info () { + local file="$1" + if [ -n "${CMD_DMIDECODE}" ]; then + local vendor="$($CMD_DMIDECODE -s system-manufacturer 2>/dev/null | sed 's/ *$//g')" + echo "vendor ${vendor}" >> "$file" + if [ "${vendor}" ]; then + local product="$($CMD_DMIDECODE -s system-product-name 2>/dev/null | sed 's/ *$//g')" + local version="$($CMD_DMIDECODE -s system-version 2>/dev/null | sed 's/ *$//g')" + local chassis="$($CMD_DMIDECODE -s chassis-type 2>/dev/null | sed 's/ *$//g')" + local servicetag="$($CMD_DMIDECODE -s system-serial-number 2>/dev/null | sed 's/ *$//g')" + local system="${vendor}; ${product}; v${version} (${chassis})" + + echo "system ${system}" >> "$file" + echo "servicetag ${servicetag:-Not found}" >> "$file" + fi + fi +} + +find_memory_stats () { + local file="$1" + local platform="$2" + + if [ "${platform}" = "Linux" ]; then + free -b > "$file" + cat /proc/meminfo >> "$file" + elif [ "${platform}" = "SunOS" ]; then + $CMD_PRTCONF | awk -F: '/Memory/{print $2}' > "$file" + fi +} + +mounted_fs_info () { + local file="$1" + local platform="$2" + local skip="${3:-$PT_SUMMARY_SKIP}" + + if echo "${skip}" | grep -v MOUNT >/dev/null; then + if [ "${platform}" != "SunOS" ]; then + local cmd="df -h" + if [ "${platform}" = "Linux" ]; then + cmd="df -h -P" + fi + $cmd | sort > "$TMPDIR/mounted_fs_info.tmp" + mount | sort | join "$TMPDIR/mounted_fs_info.tmp" - > "$file" + fi + fi +} + +# ######################################################################## +# We look in lspci first because it's more reliable, then dmesg, because it's +# often available to non-root users. It's most reliable to look at +# /var/log/dmesg if possible. +# ######################################################################## +raid_controller () { + local file="$1" + local dmesg_file="$2" + local lspci_file="$3" + + local tempfile="$TMPDIR/raid_controller.tmp" + + local controller="" + if [ -s "$lspci_file" ]; then + controller="$(parse_raid_controller_lspci "$lspci_file")" + fi + if [ -z "${controller}" ] && [ -s "$dmesg_file" ]; then + controller="$(parse_raid_controller_dmesg "$dmesg_file")" + fi + + echo "raid_controller ${controller:-No RAID controller detected}" >> "$file" +} + +schedulers_and_queue_size () { + local file="$1" + local disk_partitioning_file="$2" + + local disks="$(ls /sys/block/ | grep -v -e ram -e loop -e 'fd[0-9]')" + echo "disks $disks" >> "$file" + echo "" > "$disk_partitioning_file" + for disk in $disks; do + if [ -e "/sys/block/${disk}/queue/scheduler" ]; then + echo "internal::${disk} $(cat /sys/block/${disk}/queue/scheduler | grep -o '\[.*\]') $(cat /sys/block/${disk}/queue/nr_requests)" >> "$file" + fdisk -l "/dev/${disk}" >> "$disk_partitioning_file" 2>/dev/null + fi + done +} + +top_processes () { + local top_processes_file="$1" + local skip="${2:-"$PT_SUMMARY_SKIP"}" + + if echo "${skip}" | grep -v PROCESS >/dev/null; then + if [ -n "$CMD_PRSTAT" ]; then + $CMD_PRSTAT | head > "$top_processes_file" + elif [ -n "$CMD_TOP" ]; then + local cmd="$CMD_TOP -bn 1" + if [ "${platform}" = "FreeBSD" ]; then + cmd="$CMD_TOP -b -d 1" + fi + $cmd | sed -e 's# *$##g' -e '/./{H;$!d;}' -e 'x;/PID/!d;' | grep . | head > "$top_processes_file" + fi + fi +} + +notable_processes_info () { + local notable_processes_file="$1" + local skip="${2:-"$PT_SUMMARY_SKIP"}" + + if echo "${skip}" | grep -v PROCESS >/dev/null; then + local sshd_pid=$(_pidof sshd) + + echo " PID OOM COMMAND" > "$notable_processes_file" + + # First, let's find the oom value of sshd + if [ "$sshd_pid" ]; then + echo "$sshd_pid $(get_oom_of_pid $sshd_pid) sshd" >> "$notable_processes_file" + else + _d "sshd doesn't appear to be running" + fi + + # Now, let's find if any process has an oom value of -17 + ps -eo pid,ucomm | tail -n +2 | while read pid proc; do + [ "$proc" = "sshd" ] && continue + local oom=$(get_oom_of_pid $pid) + if [ "$oom" ] && [ "$oom" != "?" ] && [ "$oom" -eq -17 ]; then + printf "%5s %+2d %s\n" $pid $oom $proc >> "$notable_processes_file" + fi + done + fi +} + +processor_info () { + local data_dir="$1" + if [ -f /proc/cpuinfo ]; then + cat /proc/cpuinfo > "$data_dir/proc_cpuinfo_copy" 2>/dev/null + elif [ "${platform}" = "SunOS" ]; then + psrinfo -v > "$data_dir/psrinfo_minus_v" + fi +} + +# ######################################################################## +# Attempt to get, parse, and print RAID controller status from possibly +# proprietary management software. Any executables that are normally stored +# in a weird location, such as /usr/StorMan/arcconf, should have their +# location added to $PATH at the beginning of main(). +# ######################################################################## +propietary_raid_controller () { + local file="$1" + local variable_file="$2" + local data_dir="$3" + local controller="$4" + + rm -f "$file" + touch "$file" + + notfound="" + if [ "${controller}" = "AACRAID" ]; then + if ! _which arcconf >/dev/null 2>&1; then + notfound="e.g. http://www.adaptec.com/en-US/support/raid/scsi_raid/ASR-2120S/" + elif arcconf getconfig 1 > "$file" 2>/dev/null; then + echo "internal::raid_opt 1" >> "$variable_file" + fi + elif [ "${controller}" = "HP Smart Array" ]; then + if ! _which hpacucli >/dev/null 2>&1; then + notfound="your package repository or the manufacturer's website" + elif hpacucli ctrl all show config > "$file" 2>/dev/null; then + echo "internal::raid_opt 2" >> "$variable_file" + fi + elif [ "${controller}" = "LSI Logic MegaRAID SAS" ]; then + if ! _which MegaCli64 >/dev/null 2>&1; then + notfound="your package repository or the manufacturer's website" + else + echo "internal::raid_opt 3" >> "$variable_file" + MegaCli64 -AdpAllInfo -aALL -NoLog > "$data_dir/lsi_megaraid_adapter_info.tmp" 2>/dev/null + MegaCli64 -AdpBbuCmd -GetBbuStatus -aALL -NoLog > "$data_dir/lsi_megaraid_bbu_status.tmp" 2>/dev/null + MegaCli64 -LdPdInfo -aALL -NoLog > "$data_dir/lsi_megaraid_devices.tmp" 2>/dev/null + fi + fi + + if [ "${notfound}" ]; then + echo "internal::raid_opt 0" >> "$variable_file" + echo " RAID controller software not found; try getting it from" > "$file" + echo " ${notfound}" >> "$file" + fi +} + +# ########################################################################### +# End collect_system_info package +# ########################################################################### diff --git a/lib/bash/log_warn_die.sh b/lib/bash/log_warn_die.sh index eaa7fed3..9846fd55 100644 --- a/lib/bash/log_warn_die.sh +++ b/lib/bash/log_warn_die.sh @@ -24,6 +24,7 @@ set -u # Global variables. +PTDEBUG="${PTDEBUG:-""}" EXIT_STATUS=0 log() { @@ -41,6 +42,10 @@ die() { exit 1 } +_d () { + [ "$PTDEBUG" ] && echo "# $(log "$@")" >&2 +} + # ########################################################################### # End log_warn_die package # ########################################################################### diff --git a/lib/bash/report_formatting.sh b/lib/bash/report_formatting.sh new file mode 100644 index 00000000..c88e8bb1 --- /dev/null +++ b/lib/bash/report_formatting.sh @@ -0,0 +1,113 @@ +# This program is copyright 2011 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. +# ########################################################################### +# report_formatting package +# ########################################################################### + +# Package: report_formatting +# Common report formatting functions for the summary tools. + +set -u + +POSIXLY_CORRECT=1 +export POSIXLY_CORRECT + +fuzzy_formula=' + rounded = 0; + if (fuzzy_var <= 10 ) { + rounded = 1; + } + factor = 1; + while ( rounded == 0 ) { + if ( fuzzy_var <= 50 * factor ) { + fuzzy_var = sprintf("%.0f", fuzzy_var / (5 * factor)) * 5 * factor; + rounded = 1; + } + else if ( fuzzy_var <= 100 * factor) { + fuzzy_var = sprintf("%.0f", fuzzy_var / (10 * factor)) * 10 * factor; + rounded = 1; + } + else if ( fuzzy_var <= 250 * factor) { + fuzzy_var = sprintf("%.0f", fuzzy_var / (25 * factor)) * 25 * factor; + rounded = 1; + } + factor = factor * 10; + }' + +# Does fuzzy rounding: rounds to nearest interval, but the interval gets larger +# as the number gets larger. This is to make things easier to diff. +fuzz () { + _d "fuzz: $1" + echo $1 | awk "{fuzzy_var=\$1; ${fuzzy_formula} print fuzzy_var;}" +} + +# Fuzzy computes the percent that $1 is of $2 +fuzzy_pct () { + local pct="$(echo $1 $2 | awk '{ if ($2 > 0) { printf "%d", $1/$2*100; } else {print 0} }')"; + echo "$(fuzz "${pct}")%" +} + +# Prints a section header. All spaces in the string passed in are replaced +# with #'s and all underscores with spaces. +section () { + local str="$1" + local line="$(printf '#_%-60s' "${str}_" | sed -e 's/[[:space:]]/#/g' -e 's/_/ /g')" + printf "%s\n" "${line}" +} + +NAME_VAL_LEN=12 +name_val () { + # We use $NAME_VAL_LEN here because the two summary tools, as well as + # the tests, use diffent widths. + printf "%+*s | %s\n" "${NAME_VAL_LEN}" "$1" "$2" +} + +# Sub: shorten +# Shorten a value in bytes to another representation. +# +shorten() { + local num="$1" + local prec="${2:-2}" + local div="${3:-1024}" + + echo "$num" | awk -v prec="$prec" -v div="$div" ' + { + size = 4; + val = $1; + + unit = val >= 1099511627776 ? "T" : val >= 1073741824 ? "G" : val >= 1048576 ? "M" : val >= 1024 ? "k" : ""; + + while ( int(val) && !(val % 1024) ) { + val /= 1024; + } + + while ( val > 1000 ) { + val /= div; + } + + printf "%.*f%s", prec, val, unit; + } + ' +} + +group_concat () { + sed -e '{H; $!d;}' -e 'x' -e 's/\n[[:space:]]*\([[:digit:]]*\)[[:space:]]*/, \1x/g' -e 's/[[:space:]][[:space:]]*/ /g' -e 's/, //' "${1}" +} + +# ########################################################################### +# End report_formatting package +# ########################################################################### diff --git a/lib/bash/summary_common.sh b/lib/bash/summary_common.sh new file mode 100644 index 00000000..d89375ed --- /dev/null +++ b/lib/bash/summary_common.sh @@ -0,0 +1,157 @@ +# 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. +# ########################################################################### +# summary_common package +# ########################################################################### + +# Package: summary_common +# Common functions between the summary packages. + +set -u + +# Tries to find the niceness of the passed in PID. First with ps, and +# failing that, with a bit of C, using getpriority(). +# Returns the nice for the pid, or "?" if it can't find any. +get_nice_of_pid () { + local pid="$1" + local niceness=$(ps -p $pid -o nice | tail -n+2 | awk '{print $1; exit;}') + + if [ -n "${niceness}" ]; then + echo $niceness + else + local tmpfile="$TMPDIR/nice_through_c.tmp.c" + _d "Getting the niceness from ps failed, somehow. We are about to try this:" + cat < "$tmpfile" +#include +#include +#include +#include + +int main(void) { + int priority = getpriority(PRIO_PROCESS, $pid); + if ( priority == -1 && errno == ESRCH ) { + return 1; + } + else { + printf("%d\\n", priority); + return 0; + } +} + +EOC + local c_comp=$(_which gcc) + if [ -z "${c_comp}" ]; then + c_comp=$(_which cc) + fi + _d "$tmpfile: $( cat "$tmpfile" )" + _d "$c_comp -xc \"$tmpfile\" -o \"$tmpfile\" && eval \"$tmpfile\"" + $c_comp -xc "$tmpfile" -o "$tmpfile" 2>/dev/null && eval "$tmpfile" 2>/dev/null + if [ $? -ne 0 ]; then + echo "?" + _d "Failed to get a niceness value for $pid" + fi + fi +} + +# Fetches the oom value for a given pid. +# To avoi deprecation warnings, tries /proc/PID/oom_score_adj first. +# Will only work if /proc/cpuinfo is available. +get_oom_of_pid () { + local pid="$1" + local oom_adj="" + + if [ -n "${pid}" ] && [ -e /proc/cpuinfo ]; then + if [ -s "/proc/$pid/oom_score_adj" ]; then + oom_adj=$(cat "/proc/$pid/oom_score_adj" 2>/dev/null) + _d "For $pid, the oom value is $oom_adj, retreived from oom_score_adj" + else + oom_adj=$(cat "/proc/$pid/oom_adj" 2>/dev/null) + _d "For $pid, the oom value is $oom_adj, retreived from oom_adj" + fi + fi + + if [ -n "${oom_adj}" ]; then + echo "${oom_adj}" + else + echo "?" + _d "Can't find the oom value for $pid" + fi +} + +CMD_FILE="$( _which file 2>/dev/null )" +CMD_NM="$( _which nm 2>/dev/null )" +CMD_OBJDUMP="$( _which objdump 2>/dev/null )" + +has_symbols () { + local executable="$(_which "$1")" + local has_symbols="" + + if [ "${CMD_FILE}" ] \ + && [ "$($CMD_FILE "${executable}" | grep 'not stripped' )" ]; then + has_symbols=1 + elif [ "${CMD_NM}" ] \ + || [ "${CMD_OBJDMP}" ]; then + if [ "${CMD_NM}" ] \ + && [ !"$("${CMD_NM}" -- "${executable}" 2>&1 | grep 'File format not recognized' )" ]; then + if [ -z "$( $CMD_NM -- "${executable}" 2>&1 | grep ': no symbols' )" ]; then + has_symbols=1 + fi + elif [ -z "$("${CMD_OBJDUMP}" -t -- "${executable}" | grep '^no symbols$' )" ]; then + has_symbols=1 + fi + fi + + if [ "${has_symbols}" ]; then + echo "Yes" + return 0 + else + echo "No" + return 1 + fi +} + +setup_data_dir () { + local data_dir="" + if [ -z "$OPT_SAVE_DATA" ]; then + # User didn't specify a --save-data dir, so use a sub-dir in our tmpdir. + mkdir "$TMPDIR/data" || die "Cannot mkdir $TMPDIR/data" + data_dir="$TMPDIR/data" + else + # Check the user's --save-data dir. + if [ ! -d "$OPT_SAVE_DATA" ]; then + mkdir "$OPT_SAVE_DATA" || die "Cannot mkdir $OPT_SAVE_DATA" + fi + touch "$OPT_SAVE_DATA/test" || die "Cannot write to $OPT_SAVE_DATA" + rm "$OPT_SAVE_DATA/test" || die "Cannot rm $OPT_SAVE_DATA/test" + data_dir="$OPT_SAVE_DATA" + fi + echo "$data_dir" +} + +# gets a value from the passed in file. Returns _GET_VAR_DEFAULT if it doesn't +# exist, which unless changed is 0. +_GET_VAR_DEFAULT=0 +get_var () { + local varname="$1" + local file="$2" + local v="$(awk "\$1 ~ /^${varname}$/ { print \$2 }" "${file}")" + echo "${v:-$_GET_VAR_DEFAULT}" +} + +# ########################################################################### +# End summary_common package +# ########################################################################### diff --git a/t/lib/bash/collect_mysql_info.sh b/t/lib/bash/collect_mysql_info.sh new file mode 100644 index 00000000..91048bbd --- /dev/null +++ b/t/lib/bash/collect_mysql_info.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +plan 3 + +TMPDIR="$TEST_TMPDIR" +PATH="$PATH:$PERCONA_TOOLKIT_SANDBOX/bin" +TOOL="pt-mysql-summary" + +. "$LIB_DIR/log_warn_die.sh" +. "$LIB_DIR/alt_cmds.sh" +. "$LIB_DIR/summary_common.sh" +. "$LIB_DIR/collect_mysql_info.sh" + +# Prefix (with path) for the collect files. +local p="$TMPDIR/collect_mysql_info" + +mkdir "$p" + +parse_options "$BIN_DIR/pt-mysql-summary" --sleep 1 -- --defaults-file=/tmp/12345/my.sandbox.cnf + +CMD_MYSQL="$(_which mysql)" +CMD_MYSQLDUMP="$(_which mysqldump)" + +collect_mysql_info "$p" 1>/dev/null +wait + +file_count=$(ls "$p" | wc -l) + +is $file_count 12 "Creates the correct number of files (without --dump-schemas)" + +grep -v grep "$p/percona-toolkit-mysqld-instances" | awk '{print $2}' > "$TMPDIR/collect_mysqld_instances1.test" +ps auxww 2>/dev/null | grep mysqld | grep -v grep | awk '{print $2}' > "$TMPDIR/collect_mysqld_instances2.test" + +no_diff \ + "$TMPDIR/collect_mysqld_instances1.test" \ + "$TMPDIR/collect_mysqld_instances2.test" \ + "collect_mysql_info() finds the correct instances" + +collect_mysqld_instances "$TMPDIR/collect_mysqld_instances3.test" + +no_diff \ + "$TMPDIR/collect_mysqld_instances3.test" \ + "$TMPDIR/collect_mysqld_instances2.test" \ + "(sanity check) which are the same that collect_mysqld_instances() does" + +$CMD_MYSQL $EXT_ARGV -ss -e 'SHOW /*!50000 GLOBAL*/ STATUS' > "$TMPDIR/collect_mysql_status" +no_diff \ + "$p/percona-toolkit-mysql-status" \ + "$TMPDIR/collect_mysql_status" \ + "collect_mysql_info() finds the correct instances" + diff --git a/t/lib/bash/report_formatting.sh b/t/lib/bash/report_formatting.sh new file mode 100644 index 00000000..1f55167e --- /dev/null +++ b/t/lib/bash/report_formatting.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash + +plan 12 + +. "$LIB_DIR/report_formatting.sh" + +is \ + "$(shorten 10485760 1)" \ + "10.0M" \ + "10485760, 1 precision, default divisor => 10.0M" + +is \ + "$(shorten 3145728000 1)" \ + "2.9G" \ + "10485760, 1 precision, default divisor => 2.9G" + +is \ + "$(shorten 3145728000 1 1000)" \ + "3.0G" \ + "Opt-in to the 2.1 behavior works" + +is \ + "$(shorten 0 0)" \ + "0" \ + "0, 0 precision, default divisor => 0" + +is \ + "$(shorten 1572864000 1)" \ + "1.5G" \ + "1572864000, 1 precision, default divisor => 1.5G" + +is \ + "$(shorten 1572864000 1 1000)" \ + "1.5G" \ + "1572864000, 1 precision, divisor 1000 => 1.5G" + +is \ + "$(shorten 364 0)" \ + "364" \ + "364, 0 precision, default divisor => 364" + +is \ + "$(shorten 364 1)" \ + "364.0" \ + "364, 1 precision, default divisor => 364" + +is \ + "$(shorten 649216 1)" \ + "634.0k" \ + "649216, 1 precision, default divisor => 634.0k" + +is \ + "$(shorten 6492100000006 1)" \ + "5.9T" \ + "6492100000006, 1 precision, default divisor => 5.9T" + +is \ + "$(shorten 6492100000006 1 1000)" \ + "6.5T" \ + "6492100000006, 1 precision, divisor 1000 => 6.5T" + +# section + +is \ + "$(section "A")" \ + "# A ##########################################################" \ + "Sanity check, section works" + +is \ + "$(section "A B C")" \ + "# A#B#C ######################################################" \ + "section replaces all spaces with #s" + +is \ + "$(section "A_B_C")" \ + "# A B C ######################################################" \ + "..and all underscores with spaces" + +# name_val + +NAME_VAL_LEN=0 + +is \ + "$(name_val "A" "B")" \ + "A | B" \ + "name_val and NAME_VAL_LEN work" + +# ########################################################################### +# Done +# ########################################################################### diff --git a/t/pt-mysql-summary/format_innodb.sh b/t/pt-mysql-summary/format_innodb.sh new file mode 100644 index 00000000..1dc5e149 --- /dev/null +++ b/t/pt-mysql-summary/format_innodb.sh @@ -0,0 +1,36 @@ +#/bin/bash + +TESTS=1 +TMPDIR=$TEST_TMPDIR + +test_format_innodb () { + local NAME_VAL_LEN=25 + cat < $TMPDIR/expected + Version | 1.0.17-13.2 + Buffer Pool Size | 128.0M + Buffer Pool Fill | 1% + Buffer Pool Dirty | 0% + File Per Table | OFF + Page Size | 16k + Log File Size | 2 * 1.5G = 3.1G + Log Buffer Size | 8M + Flush Method | 0 + Flush Log At Commit | 1 + XA Support | ON + Checksums | ON + Doublewrite | ON + R/W I/O Threads | 4 4 + I/O Capacity | 200 + Thread Concurrency | 0 + Concurrency Tickets | 500 + Commit Concurrency | 0 + Txn Isolation Level | REPEATABLE-READ + Adaptive Flushing | OFF + Adaptive Checkpoint | estimate +EOF + + _innodb samples/temp001/percona-toolkit-mysql-variables samples/temp001/percona-toolkit-mysql-status > $TMPDIR/got + no_diff $TMPDIR/expected $TMPDIR/got +} + +test_format_innodb diff --git a/t/pt-mysql-summary/get_mysql_info.sh b/t/pt-mysql-summary/get_mysql_info.sh index e50ef0e2..edbc0333 100644 --- a/t/pt-mysql-summary/get_mysql_info.sh +++ b/t/pt-mysql-summary/get_mysql_info.sh @@ -12,7 +12,7 @@ cat < $TMPDIR/expected 2010-05-27 11:38 (up 0+02:08:52) EOF cp samples/mysql-status-001.txt $TMPDIR/percona-toolkit-mysql-status -local uptime="$(get_stat Uptime $TMPDIR/percona-toolkit-mysql-status)" +local uptime="$(get_var Uptime $TMPDIR/percona-toolkit-mysql-status)" local current_time="$(echo -e "2010-05-27 11:38\n")" get_mysql_uptime "${uptime}" "${current_time}" > $TMPDIR/got no_diff $TMPDIR/got $TMPDIR/expected diff --git a/t/pt-mysql-summary/pt-mysql-summary.t b/t/pt-mysql-summary/pt-mysql-summary.t index 836f315a..e6c10f7c 100644 --- a/t/pt-mysql-summary/pt-mysql-summary.t +++ b/t/pt-mysql-summary/pt-mysql-summary.t @@ -23,22 +23,22 @@ use File::Temp qw( tempdir ); local $ENV{PTDEBUG} = ""; # -# --tempdir +# --save-data # my $dir = tempdir( CLEANUP => 1 ); -`$trunk/bin/$tool --sleep 1 --tempdir $dir`; +`$trunk/bin/$tool --sleep 1 --save-data $dir`; ok( -e $dir, - "Using --tempdir doesn't mistakenly delete the target dir" + "Using --save-data doesn't mistakenly delete the target dir" ); my @files = glob("$dir/*"); is( scalar @files, - 14, + 13, "And leaves all files in there" ); @@ -48,7 +48,7 @@ undef($dir); # --dump-schemas # -my $out = `$trunk/bin/$tool --sleep 1 --dump-schemas mysql`; +my $out = `$trunk/bin/$tool --sleep 1 --dump-schemas mysql 2>/dev/null`; like( $out, diff --git a/t/pt-mysql-summary/samples/temp001/percona-toolkit-mysql-status b/t/pt-mysql-summary/samples/temp001/percona-toolkit-mysql-status new file mode 100644 index 00000000..f547de9a --- /dev/null +++ b/t/pt-mysql-summary/samples/temp001/percona-toolkit-mysql-status @@ -0,0 +1,304 @@ +Aborted_clients 0 +Aborted_connects 0 +Binlog_cache_disk_use 0 +Binlog_cache_use 0 +Bytes_received 340 +Bytes_sent 10867 +Com_admin_commands 0 +Com_assign_to_keycache 0 +Com_alter_db 0 +Com_alter_db_upgrade 0 +Com_alter_event 0 +Com_alter_function 0 +Com_alter_procedure 0 +Com_alter_server 0 +Com_alter_table 0 +Com_alter_tablespace 0 +Com_analyze 0 +Com_backup_table 0 +Com_begin 0 +Com_binlog 0 +Com_call_procedure 0 +Com_change_db 0 +Com_change_master 0 +Com_check 0 +Com_checksum 0 +Com_commit 0 +Com_create_db 0 +Com_create_event 0 +Com_create_function 0 +Com_create_index 0 +Com_create_procedure 0 +Com_create_server 0 +Com_create_table 0 +Com_create_trigger 0 +Com_create_udf 0 +Com_create_user 0 +Com_create_view 0 +Com_dealloc_sql 0 +Com_delete 0 +Com_delete_multi 0 +Com_do 0 +Com_drop_db 0 +Com_drop_event 0 +Com_drop_function 0 +Com_drop_index 0 +Com_drop_procedure 0 +Com_drop_server 0 +Com_drop_table 0 +Com_drop_trigger 0 +Com_drop_user 0 +Com_drop_view 0 +Com_empty_query 0 +Com_execute_sql 0 +Com_flush 0 +Com_grant 0 +Com_ha_close 0 +Com_ha_open 0 +Com_ha_read 0 +Com_help 0 +Com_insert 0 +Com_insert_select 0 +Com_install_plugin 0 +Com_kill 0 +Com_load 0 +Com_load_master_data 0 +Com_load_master_table 0 +Com_lock_tables 0 +Com_optimize 0 +Com_preload_keys 0 +Com_prepare_sql 0 +Com_purge 0 +Com_purge_before_date 0 +Com_release_savepoint 0 +Com_rename_table 0 +Com_rename_user 0 +Com_repair 0 +Com_replace 0 +Com_replace_select 0 +Com_reset 0 +Com_restore_table 0 +Com_revoke 0 +Com_revoke_all 0 +Com_rollback 0 +Com_rollback_to_savepoint 0 +Com_savepoint 0 +Com_select 4 +Com_set_option 0 +Com_show_authors 0 +Com_show_binlog_events 0 +Com_show_binlogs 0 +Com_show_charsets 0 +Com_show_client_statistics 0 +Com_show_collations 0 +Com_show_column_types 0 +Com_show_contributors 0 +Com_show_create_db 0 +Com_show_create_event 0 +Com_show_create_func 0 +Com_show_create_proc 0 +Com_show_create_table 0 +Com_show_create_trigger 0 +Com_show_databases 0 +Com_show_engine_logs 0 +Com_show_engine_mutex 0 +Com_show_engine_status 0 +Com_show_events 0 +Com_show_errors 0 +Com_show_fields 0 +Com_show_function_status 0 +Com_show_grants 0 +Com_show_index_statistics 0 +Com_show_keys 0 +Com_show_master_status 0 +Com_show_new_master 0 +Com_show_open_tables 0 +Com_show_patches 0 +Com_show_plugins 0 +Com_show_privileges 0 +Com_show_procedure_status 0 +Com_show_processlist 0 +Com_show_profile 0 +Com_show_profiles 0 +Com_show_slave_hosts 0 +Com_show_slave_status 0 +Com_show_slave_status_nolock 0 +Com_show_status 1 +Com_show_storage_engines 0 +Com_show_table_statistics 0 +Com_show_table_status 0 +Com_show_tables 0 +Com_show_thread_statistics 0 +Com_show_temporary_tables 0 +Com_show_triggers 0 +Com_show_user_statistics 0 +Com_show_variables 1 +Com_show_warnings 0 +Com_slave_start 0 +Com_slave_stop 0 +Com_stmt_close 0 +Com_stmt_execute 0 +Com_stmt_fetch 0 +Com_stmt_prepare 0 +Com_stmt_reprepare 0 +Com_stmt_reset 0 +Com_stmt_send_long_data 0 +Com_truncate 0 +Com_uninstall_plugin 0 +Com_unlock_tables 0 +Com_update 0 +Com_update_multi 0 +Com_xa_commit 0 +Com_xa_end 0 +Com_xa_prepare 0 +Com_xa_recover 0 +Com_xa_rollback 0 +Com_xa_start 0 +Compression OFF +Connections 4 +Created_tmp_disk_tables 0 +Created_tmp_files 5 +Created_tmp_tables 2 +Delayed_errors 0 +Delayed_insert_threads 0 +Delayed_writes 0 +Flashcache_enabled OFF +Flush_commands 1 +Handler_commit 0 +Handler_delete 0 +Handler_discover 0 +Handler_prepare 0 +Handler_read_first 3 +Handler_read_key 0 +Handler_read_next 0 +Handler_read_prev 0 +Handler_read_rnd 0 +Handler_read_rnd_next 364 +Handler_rollback 0 +Handler_savepoint 0 +Handler_savepoint_rollback 0 +Handler_update 0 +Handler_write 348 +Innodb_buffer_pool_pages_data 150 +Innodb_buffer_pool_pages_dirty 0 +Innodb_buffer_pool_pages_flushed 0 +Innodb_buffer_pool_pages_free 8041 +Innodb_buffer_pool_pages_misc 0 +Innodb_buffer_pool_pages_total 8191 +Innodb_buffer_pool_read_ahead_rnd 0 +Innodb_buffer_pool_read_ahead 0 +Innodb_buffer_pool_read_ahead_evicted 0 +Innodb_buffer_pool_read_requests 488 +Innodb_buffer_pool_reads 151 +Innodb_buffer_pool_wait_free 0 +Innodb_buffer_pool_write_requests 0 +Innodb_data_fsyncs 3 +Innodb_data_pending_fsyncs 0 +Innodb_data_pending_reads 0 +Innodb_data_pending_writes 0 +Innodb_data_read 4657152 +Innodb_data_reads 161 +Innodb_data_writes 3 +Innodb_data_written 1536 +Innodb_dblwr_pages_written 0 +Innodb_deadlocks 0 +Innodb_dblwr_writes 0 +Innodb_dict_tables 8 +Innodb_have_atomic_builtins OFF +Innodb_log_waits 0 +Innodb_log_write_requests 0 +Innodb_log_writes 1 +Innodb_os_log_fsyncs 3 +Innodb_os_log_pending_fsyncs 0 +Innodb_os_log_pending_writes 0 +Innodb_os_log_written 512 +Innodb_page_size 16384 +Innodb_pages_created 0 +Innodb_pages_read 150 +Innodb_pages_written 0 +Innodb_row_lock_current_waits 0 +Innodb_row_lock_time 0 +Innodb_row_lock_time_avg 0 +Innodb_row_lock_time_max 0 +Innodb_row_lock_waits 0 +Innodb_rows_deleted 0 +Innodb_rows_inserted 0 +Innodb_rows_read 0 +Innodb_rows_updated 0 +Key_blocks_not_flushed 0 +Key_blocks_unused 14497 +Key_blocks_used 0 +Key_read_requests 0 +Key_reads 0 +Key_write_requests 0 +Key_writes 0 +Last_query_cost 0.000000 +Max_used_connections 1 +Not_flushed_delayed_rows 0 +Open_files 16 +Open_streams 0 +Open_table_definitions 15 +Open_tables 8 +Opened_files 57 +Opened_table_definitions 15 +Opened_tables 15 +Prepared_stmt_count 0 +Qcache_free_blocks 1 +Qcache_free_memory 16768400 +Qcache_hits 0 +Qcache_inserts 0 +Qcache_lowmem_prunes 0 +Qcache_not_cached 4 +Qcache_queries_in_cache 0 +Qcache_total_blocks 1 +Queries 8 +Questions 8 +Rpl_status NULL +Select_full_join 0 +Select_full_range_join 0 +Select_range 0 +Select_range_check 0 +Select_scan 2 +Slave_open_temp_tables 0 +Slave_retried_transactions 0 +Slave_running OFF +Slow_launch_threads 0 +Slow_queries 0 +Sort_merge_passes 0 +Sort_range 0 +Sort_rows 0 +Sort_scan 0 +Ssl_accept_renegotiates 0 +Ssl_accepts 0 +Ssl_callback_cache_hits 0 +Ssl_cipher +Ssl_cipher_list +Ssl_client_connects 0 +Ssl_connect_renegotiates 0 +Ssl_ctx_verify_depth 0 +Ssl_ctx_verify_mode 0 +Ssl_default_timeout 0 +Ssl_finished_accepts 0 +Ssl_finished_connects 0 +Ssl_session_cache_hits 0 +Ssl_session_cache_misses 0 +Ssl_session_cache_mode NONE +Ssl_session_cache_overflows 0 +Ssl_session_cache_size 0 +Ssl_session_cache_timeouts 0 +Ssl_sessions_reused 0 +Ssl_used_session_cache_entries 0 +Ssl_verify_depth 0 +Ssl_verify_mode 0 +Ssl_version +Table_locks_immediate 18 +Table_locks_waited 0 +Tc_log_max_pages_used 0 +Tc_log_page_size 0 +Tc_log_page_waits 0 +Threads_cached 0 +Threads_connected 1 +Threads_created 1 +Threads_running 1 +Uptime 9 +Uptime_since_flush_status 9 diff --git a/t/pt-mysql-summary/samples/temp001/percona-toolkit-mysql-variables b/t/pt-mysql-summary/samples/temp001/percona-toolkit-mysql-variables new file mode 100644 index 00000000..dd2c6ca4 --- /dev/null +++ b/t/pt-mysql-summary/samples/temp001/percona-toolkit-mysql-variables @@ -0,0 +1,355 @@ +auto_increment_increment 1 +auto_increment_offset 1 +autocommit ON +automatic_sp_privileges ON +back_log 50 +basedir /usr/ +big_tables OFF +binlog_cache_size 32768 +binlog_direct_non_transactional_updates OFF +binlog_format STATEMENT +bulk_insert_buffer_size 8388608 +character_set_client latin1 +character_set_connection latin1 +character_set_database latin1 +character_set_filesystem binary +character_set_results latin1 +character_set_server latin1 +character_set_system utf8 +character_sets_dir /usr/share/mysql/charsets/ +collation_connection latin1_swedish_ci +collation_database latin1_swedish_ci +collation_server latin1_swedish_ci +completion_type 0 +concurrent_insert 1 +connect_timeout 10 +datadir /var/lib/mysql/ +date_format %Y-%m-%d +datetime_format %Y-%m-%d %H:%i:%s +default_week_format 0 +delay_key_write ON +delayed_insert_limit 100 +delayed_insert_timeout 300 +delayed_queue_size 1000 +div_precision_increment 4 +enable_query_response_time_stats OFF +engine_condition_pushdown ON +error_count 0 +event_scheduler OFF +expand_fast_index_creation OFF +expire_logs_days 10 +fast_index_creation ON +flush OFF +flush_time 0 +foreign_key_checks ON +ft_boolean_syntax + -><()~*:""&| +ft_max_word_len 84 +ft_min_word_len 4 +ft_query_expansion_limit 20 +ft_stopword_file (built-in) +general_log OFF +general_log_file /var/lib/mysql/hugmeir.log +group_concat_max_len 1024 +have_community_features YES +have_compress YES +have_crypt YES +have_csv YES +have_dynamic_loading YES +have_geometry YES +have_innodb YES +have_ndbcluster NO +have_openssl DISABLED +have_partitioning YES +have_query_cache YES +have_response_time_distribution YES +have_rtree_keys YES +have_ssl DISABLED +have_symlink YES +hostname hugmeir +identity 0 +ignore_builtin_innodb OFF +init_connect +init_file +init_slave +innodb_adaptive_checkpoint estimate +innodb_adaptive_flushing OFF +innodb_adaptive_hash_index ON +innodb_additional_mem_pool_size 8388608 +innodb_auto_lru_dump 0 +innodb_autoextend_increment 8 +innodb_autoinc_lock_mode 1 +innodb_blocking_lru_restore OFF +innodb_buffer_pool_shm_checksum ON +innodb_buffer_pool_shm_key 0 +innodb_buffer_pool_size 134217728 +innodb_change_buffering inserts +innodb_checkpoint_age_target 0 +innodb_checksums ON +innodb_commit_concurrency 0 +innodb_concurrency_tickets 500 +innodb_data_file_path ibdata1:10M:autoextend +innodb_data_home_dir +innodb_dict_size_limit 0 +innodb_doublewrite ON +innodb_doublewrite_file +innodb_enable_unsafe_group_commit 0 +innodb_expand_import 0 +innodb_extra_rsegments 0 +innodb_extra_undoslots OFF +innodb_fake_changes OFF +innodb_fast_checksum OFF +innodb_fast_recovery OFF +innodb_fast_shutdown 1 +innodb_file_format Antelope +innodb_file_format_check Barracuda +innodb_file_per_table OFF +innodb_flush_log_at_trx_commit 1 +innodb_flush_log_at_trx_commit_session 3 +innodb_flush_method +innodb_flush_neighbor_pages 1 +innodb_force_recovery 0 +innodb_ibuf_accel_rate 100 +innodb_ibuf_active_contract 1 +innodb_ibuf_max_size 67092480 +innodb_io_capacity 200 +innodb_kill_idle_transaction 0 +innodb_lazy_drop_table 0 +innodb_lock_wait_timeout 50 +innodb_locks_unsafe_for_binlog OFF +innodb_log_block_size 512 +innodb_log_buffer_size 8388608 +innodb_log_file_size 1572864000 +innodb_log_files_in_group 2 +innodb_log_group_home_dir ./ +innodb_max_dirty_pages_pct 75 +innodb_max_purge_lag 0 +innodb_mirrored_log_groups 1 +innodb_old_blocks_pct 37 +innodb_old_blocks_time 0 +innodb_open_files 300 +innodb_overwrite_relay_log_info OFF +innodb_page_size 16384 +innodb_pass_corrupt_table 0 +innodb_random_read_ahead OFF +innodb_read_ahead linear +innodb_read_ahead_threshold 56 +innodb_read_io_threads 4 +innodb_recovery_stats OFF +innodb_replication_delay 0 +innodb_rollback_on_timeout OFF +innodb_show_locks_held 10 +innodb_show_verbose_locks 0 +innodb_spin_wait_delay 6 +innodb_stats_auto_update 1 +innodb_stats_method nulls_equal +innodb_stats_on_metadata ON +innodb_stats_sample_pages 8 +innodb_stats_update_need_lock 1 +innodb_strict_mode OFF +innodb_support_xa ON +innodb_sync_spin_loops 30 +innodb_table_locks ON +innodb_thread_concurrency 0 +innodb_thread_concurrency_timer_based OFF +innodb_thread_sleep_delay 10000 +innodb_use_purge_thread 1 +innodb_use_sys_malloc ON +innodb_use_sys_stats_table OFF +innodb_version 1.0.17-13.2 +innodb_write_io_threads 4 +insert_id 0 +interactive_timeout 28800 +join_buffer_size 131072 +keep_files_on_create OFF +key_buffer_size 16777216 +key_cache_age_threshold 300 +key_cache_block_size 1024 +key_cache_division_limit 100 +language /usr/share/mysql/english/ +large_files_support ON +large_page_size 0 +large_pages OFF +last_insert_id 0 +lc_time_names en_US +license GPL +local_infile ON +locked_in_memory OFF +log OFF +log_bin OFF +log_bin_trust_function_creators OFF +log_bin_trust_routine_creators OFF +log_error /var/log/mysql/error.log +log_output FILE +log_queries_not_using_indexes OFF +log_slave_updates OFF +log_slow_admin_statements OFF +log_slow_filter +log_slow_queries OFF +log_slow_rate_limit 1 +log_slow_slave_statements OFF +log_slow_sp_statements ON +log_slow_timestamp_every OFF +log_slow_verbosity microtime +log_warnings 1 +long_query_time 10.000000 +low_priority_updates OFF +lower_case_file_system OFF +lower_case_table_names 0 +max_allowed_packet 16777216 +max_binlog_cache_size 4294963200 +max_binlog_size 104857600 +max_connect_errors 10 +max_connections 151 +max_delayed_threads 20 +max_error_count 64 +max_heap_table_size 16777216 +max_insert_delayed_threads 20 +max_join_size 4294967295 +max_length_for_sort_data 1024 +max_long_data_size 16777216 +max_prepared_stmt_count 16382 +max_relay_log_size 0 +max_seeks_for_key 4294967295 +max_sort_length 1024 +max_sp_recursion_depth 0 +max_tmp_tables 32 +max_user_connections 0 +max_write_lock_count 4294967295 +min_examined_row_limit 0 +multi_range_count 256 +myisam_data_pointer_size 6 +myisam_max_sort_file_size 2146435072 +myisam_mmap_size 4294967295 +myisam_recover_options BACKUP +myisam_repair_threads 1 +myisam_sort_buffer_size 8388608 +myisam_stats_method nulls_unequal +myisam_use_mmap OFF +net_buffer_length 16384 +net_read_timeout 30 +net_retry_count 10 +net_write_timeout 60 +new OFF +old OFF +old_alter_table OFF +old_passwords OFF +open_files_limit 1024 +optimizer_fix ON +optimizer_prune_level 1 +optimizer_search_depth 62 +optimizer_switch index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on +pid_file /var/lib/mysql/hugmeir.pid +plugin_dir /usr/lib/mysql/plugin +port 3306 +preload_buffer_size 32768 +profiling OFF +profiling_history_size 15 +profiling_server OFF +profiling_use_getrusage OFF +protocol_version 10 +pseudo_thread_id 0 +query_alloc_block_size 8192 +query_cache_limit 1048576 +query_cache_min_res_unit 4096 +query_cache_size 16777216 +query_cache_strip_comments OFF +query_cache_type ON +query_cache_wlock_invalidate OFF +query_prealloc_size 8192 +query_response_time_range_base 10 +rand_seed1 +rand_seed2 +range_alloc_block_size 4096 +read_buffer_size 131072 +read_only OFF +read_rnd_buffer_size 262144 +relay_log +relay_log_index +relay_log_info_file relay-log.info +relay_log_purge ON +relay_log_space_limit 0 +report_host +report_password +report_port 3306 +report_user +rpl_recovery_rank 0 +secure_auth OFF +secure_file_priv +server_id 0 +skip_external_locking ON +skip_name_resolve OFF +skip_networking OFF +skip_show_database OFF +slave_compressed_protocol OFF +slave_exec_mode STRICT +slave_load_tmpdir /tmp +slave_net_timeout 3600 +slave_skip_errors OFF +slave_transaction_retries 10 +slow_launch_time 2 +slow_query_log OFF +slow_query_log_file /var/lib/mysql/hugmeir-slow.log +slow_query_log_microseconds_timestamp OFF +socket /var/run/mysqld/mysqld.sock +sort_buffer_size 2097144 +sql_auto_is_null ON +sql_big_selects ON +sql_big_tables OFF +sql_buffer_result OFF +sql_log_bin ON +sql_log_off OFF +sql_log_update ON +sql_low_priority_updates OFF +sql_max_join_size 4294967295 +sql_mode +sql_notes ON +sql_quote_show_create ON +sql_safe_updates OFF +sql_select_limit 4294967295 +sql_slave_skip_counter +sql_warnings OFF +ssl_ca +ssl_capath +ssl_cert +ssl_cipher +ssl_key +storage_engine MyISAM +suppress_log_warning_1592 OFF +sync_binlog 0 +sync_frm ON +system_time_zone PST +table_definition_cache 256 +table_lock_wait_timeout 50 +table_open_cache 64 +table_type MyISAM +thread_cache_size 8 +thread_handling one-thread-per-connection +thread_stack 196608 +thread_statistics OFF +time_format %H:%i:%s +time_zone SYSTEM +timed_mutexes OFF +timestamp 1330708900 +tmp_table_size 16777216 +tmpdir /tmp +transaction_alloc_block_size 8192 +transaction_prealloc_size 4096 +tx_isolation REPEATABLE-READ +unique_checks ON +updatable_views_with_limit YES +use_global_log_slow_control none +use_global_long_query_time OFF +userstat_running OFF +version 5.1.61rel13.2 +version_comment Percona Server with XtraDB (GPL), Release rel13.2, Revision 430 +version_compile_machine i686 +version_compile_os pc-linux-gnu +wait_timeout 28800 +warning_count 0 +pt-summary-internal-current_time 2012-03-02 09:21 +pt-summary-internal-Config_File /etc/mysql/my.cnf +pt-summary-internal-now 2012-03-02 09:21:41 +pt-summary-internal-user hugmeir@localhost +pt-summary-internal-FNV_64 Unknown +pt-summary-internal-trigger_count 0 +pt-summary-internal-symbols Yes