# 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 package
# ###########################################################################

# Package: collect
# collect collects system information.

# XXX
# THIS LIB REQUIRES log_warn_die, safeguards, alt_cmds, and subshell!
# XXX

set -u

# Global variables.
CMD_GDB="${CMD_GDB:-"$(_which gdb)"}"
CMD_IOSTAT="${CMD_IOSTAT:-"$(_which iostat)"}"
CMD_MPSTAT="${CMD_MPSTAT:-"$(_which mpstat)"}"
CMD_MYSQL="${CMD_MYSQL:-"$(_which mysql)"}"
CMD_MYSQLADMIN="${CMD_MYSQLADMIN:-"$(_which mysqladmin)"}"
CMD_OPCONTROL="${CMD_OPCONTROL:-"$(_which opcontrol)"}"
[ -z "$CMD_OPCONTROL" ] && CMD_OPCONTROL=$(_which operf)
CMD_OPREPORT="${CMD_OPREPORT:-"$(_which opreport)"}"
CMD_PMAP="${CMD_PMAP:-"$(_which pmap)"}"
CMD_STRACE="${CMD_STRACE:-"$(_which strace)"}"
CMD_SYSCTL="${CMD_SYSCTL:-"$(_which sysctl)"}"
CMD_TCPDUMP="${CMD_TCPDUMP:-"$(_which tcpdump)"}"
CMD_VMSTAT="${CMD_VMSTAT:-"$(_which vmstat)"}"
CMD_DMESG="${CMD_DMESG:-"$(_which dmesg)"}"

# Try to find command manually.
[ -z "$CMD_SYSCTL" -a -x "/sbin/sysctl" ] && CMD_SYSCTL="/sbin/sysctl"

collect() {
   local d="$1"  # directory to save results in
   local p="$2"  # prefix for each result file

   local cnt=$(($OPT_RUN_TIME / $OPT_SLEEP_COLLECT))

   if [ ! "$OPT_SYSTEM_ONLY" ]; then
      local mysqld_pid=""
      local mysql_version=""
      local mysql_error_log=""
      local tail_error_log_pid=""
      local have_lock_waits_table=""
      local lock_table_p_s=""
      local have_oprofile=""
      local mysqladmin_pid=""
      local mutex=""
      local tcpdump_pid=""
      local ps_instrumentation_enabled=""

      collect_mysql_data_one
   fi

   # Grab a few general things first.  Background all of these so we can start
   # them all up as quickly as possible.
   if [ ! "$OPT_MYSQL_ONLY" ]; then
      collect_system_data
   fi

   # This loop gathers data for the rest of the duration, and defines the time
   # of the whole job.
   log "Loop start: $(date +'TS %s.%N %F %T')"
   local start_time=$(date +'%s')
   local curr_time=$start_time
   local ts="$(date +"TS %s.%N %F %T")"

   while [ $((curr_time - start_time)) -lt $OPT_RUN_TIME ]; do
      if [ ! "$OPT_MYSQL_ONLY" ]; then
         collect_system_data_loop
      fi

      if [ ! "$OPT_SYSTEM_ONLY" ]; then
         collect_mysql_data_loop
      fi

      curr_time=$(date +'%s')
   done
   log "Loop end: $(date +'TS %s.%N %F %T')"

   if [ ! "$OPT_SYSTEM_ONLY" ]; then
      collect_mysql_data_two
   fi

   # Finally, record what system we collected this data from.
   hostname > "$d/$p-hostname"

   # Remove "empty" files, i.e. ones that are truly empty or
   # just contain timestamp lines.  When a command above fails,
   # it may leave an empty file.  But first wait another --run-time
   # seconds for any slow process to finish:
   # https://bugs.launchpad.net/percona-toolkit/+bug/1047701
   wait_for_subshells $OPT_RUN_TIME
   kill_all_subshells
   for file in "$d/$p-"*; do
      # If there's not at least 1 line that's not a TS,
      # then the file is empty.
      if [ -z "$(grep -v '^TS ' --max-count 10 "$file")" ]; then
         log "Removing empty file $file";
         rm "$file"
      fi
   done
}

collect_mysql_data_one() {
   # Get pidof mysqld.
   if [ ! "$OPT_MYSQL_ONLY" ]; then
      port=$($CMD_MYSQL $EXT_ARGV -ss -e 'SELECT @@port')
      mysqld_pid=$(lsof -i ":${port}" | grep -i listen | tr -s ' ' | cut -f 2 -d" ")
   fi

   # Get memory allocation info before anything else.
   if [ "$CMD_PMAP" -a "$mysqld_pid" ]; then
      if $CMD_PMAP --help 2>&1 | grep -- -x >/dev/null 2>&1 ; then
         $CMD_PMAP -x $mysqld_pid > "$d/$p-pmap"
      else
         # Some pmap's apparently don't support -x (issue 116).
         $CMD_PMAP $mysqld_pid > "$d/$p-pmap"
      fi
   fi

   # Getting a GDB stacktrace can be an intensive operation,
   # so do this only if necessary (and possible).
   if [ "$CMD_GDB" -a "$OPT_COLLECT_GDB" -a "$mysqld_pid" ]; then
      $CMD_GDB                     \
         -ex "set pagination 0"    \
         -ex "thread apply all bt" \
         --batch -p $mysqld_pid    \
         >> "$d/$p-stacktrace"
   fi

   # Get MySQL's variables if possible.  Then sleep long enough that we probably
   # complete SHOW VARIABLES if all's well.  (We don't want to run mysql in the
   # foreground, because it could hang.)
   collect_mysql_variables "$d/$p-variables" &
   sleep .5

   # Get the major.minor version number.  Version 3.23 doesn't matter for our
   # purposes, and other releases have x.x.x* version conventions so far.
   mysql_version="$(awk '/^version[^_]/{print substr($2,1,3)}' "$d/$p-variables")"

   # Is MySQL logging its errors to a file?  If so, tail that file.
   mysql_error_log="$(awk '/^log_error\s/{print $2}' "$d/$p-variables")"
   if [ -z "$mysql_error_log" -a "$mysqld_pid" ]; then
      log $mysqld_pid
      # Try getting it from the open filehandle...
      mysql_error_log="$(ls -l /proc/$mysqld_pid/fd | awk '/ 2 ->/{print $NF}')"
   fi

   if [ "$mysql_error_log" -a ! "$OPT_MYSQL_ONLY" ]; then
      log "The MySQL error log seems to be $mysql_error_log"
      tail -f "$mysql_error_log" >"$d/$p-log_error" &
      tail_error_log_pid=$!

      # Send a mysqladmin debug to the server so we can potentially learn about
      # locking etc.
      $CMD_MYSQLADMIN $EXT_ARGV
   else
      log "Could not find the MySQL error log"
   fi
   # Get a sample of these right away, so we can get these without interaction
   # with the other commands we're about to run.
   if [ "${mysql_version}" '>' "5.1" ]; then
      mutex="SHOW ENGINE INNODB MUTEX"
   else
      mutex="SHOW MUTEX STATUS"
   fi
   innodb_status 1
   tokudb_status 1
   rocksdb_status 1

   $CMD_MYSQL $EXT_ARGV -e "$mutex" >> "$d/$p-mutex-status1" &
   open_tables                      >> "$d/$p-opentables1"   &

   # If TCP dumping is specified, start that on the server's port.
   if [ "$CMD_TCPDUMP" -a  "$OPT_COLLECT_TCPDUMP" ]; then
      local port=$(awk '/^port/{print $2}' "$d/$p-variables")
      if [ "$port" ]; then
         $CMD_TCPDUMP -i any -s 4096 -w "$d/$p-tcpdump" port ${port} &
         tcpdump_pid=$!
      fi
   fi

   # Next, start oprofile gathering data during the whole rest of this process.
   # The --init should be a no-op if it has already been init-ed.
   if [ "$CMD_OPCONTROL" -a "$OPT_COLLECT_OPROFILE" ]; then
      if [ $(echo $CMD_OPCONTROL | grep -cv operf) -gt 0 ]; then 
	      # use legacy or custom opcontrol
	      if $CMD_OPCONTROL --init; then
	         $CMD_OPCONTROL --start --no-vmlinux
	         have_oprofile="yes"
	      fi
      else
	      have_oprofile="yes"
         local tmpfile="$PT_TMPDIR/oprofile"
         mkdir "$d/pt_collect_$p"
         # use operf, may fail under VirtualBox or old processor models (see http://oprofile.sourceforge.net/doc/perf_events.html)
         $CMD_OPCONTROL -p "$mysqld_pid" -d "$d/pt_collect_$p" > "$tmpfile" &
         sleep 1
         OPERF_PID=$(grep -Eo "kill -SIGINT [[:digit:]]+" "$tmpfile" | grep -Eo "[[:digit:]]+")
         rm "$tmpfile"
      fi
   elif [ "$CMD_STRACE" -a "$OPT_COLLECT_STRACE" -a "$mysqld_pid" ]; then
      # Don't run oprofile and strace at the same time.
      $CMD_STRACE -T -s 0 -f -p $mysqld_pid -o "$d/$p-strace" &
      local strace_pid=$!
   fi

   $CMD_MYSQL $EXT_ARGV -e "SHOW TABLES FROM INFORMATION_SCHEMA" \
      | grep -i "INNODB_LOCK_WAITS" >/dev/null 2>&1
   if [ $? -eq 0 ]; then
      have_lock_waits_table="yes"
   else
      # We cannot simply check version here, because MariaDB uses
      # Information Schema in its 10.x series
      $CMD_MYSQL $EXT_ARGV -e "SHOW TABLES FROM performance_schema" \
         | grep -i "data_lock_waits" >/dev/null 2>&1
      if [ $? -eq 0 ]; then
         have_lock_waits_table="yes"
         lock_table_p_s="yes"
      fi
   fi

   # Collect multiple snapshots of the status variables.  We use
   # mysqladmin -c even though it is buggy and won't stop on its
   # own in 5.1 and newer, because there is a chance that we will
   # get and keep a connection to the database; in troubled times
   # the database tends to exceed max_connections, so reconnecting
   # in the loop tends not to work very well.
   if ! _should_skip "mysqladmin"; then
      $CMD_MYSQLADMIN $EXT_ARGV ext -i$OPT_SLEEP_COLLECT -c$cnt >>"$d/$p-mysqladmin" &
      mysqladmin_pid=$!
   fi

   ps_instrumentation_enabled=$($CMD_MYSQL $EXT_ARGV -e 'SELECT ENABLED FROM performance_schema.setup_instruments WHERE NAME = "transaction";' \
                                      | sed "2q;d" | sed 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/')

   if [ $ps_instrumentation_enabled != "yes" ]; then
      log "Performance Schema instrumentation is disabled"
   fi
}

collect_system_data() {
   ps -eaF  >> "$d/$p-ps"  &
   top -bn${OPT_RUN_TIME} >> "$d/$p-top" &

   [ "$mysqld_pid" ] && _lsof $mysqld_pid >> "$d/$p-lsof" &

   if [ "$CMD_SYSCTL" ]; then
      $CMD_SYSCTL -a >> "$d/$p-sysctl" &
   fi

   # collect dmesg events from 60 seconds ago until present
   if [ "$CMD_DMESG" ]; then
      local UPTIME=`cat /proc/uptime | awk '{ print $1 }'`
      local START_TIME=$(echo "$UPTIME 60" | awk '{print ($1 - $2)}')
      $CMD_DMESG  | perl -ne 'm/\[\s*(\d+)\./; if ($1 > '${START_TIME}') { print }' >> "$d/$p-dmesg" &
   fi

   if [ "$CMD_VMSTAT" ]; then
      $CMD_VMSTAT $OPT_SLEEP_COLLECT $cnt >> "$d/$p-vmstat" &
      $CMD_VMSTAT $OPT_RUN_TIME 2 >> "$d/$p-vmstat-overall" &
   fi
   if [ "$CMD_IOSTAT" ]; then
      $CMD_IOSTAT -dx $OPT_SLEEP_COLLECT $cnt >> "$d/$p-iostat" &
      $CMD_IOSTAT -dx $OPT_RUN_TIME 2 >> "$d/$p-iostat-overall" &
   fi
   if [ "$CMD_MPSTAT" ]; then
      $CMD_MPSTAT -P ALL $OPT_SLEEP_COLLECT $cnt >> "$d/$p-mpstat" &
      $CMD_MPSTAT -P ALL $OPT_RUN_TIME 1 >> "$d/$p-mpstat-overall" &
   fi
}

collect_mysql_data_loop() {

   # SHOW FULL PROCESSLIST duplicates information in performance_schema.threads we collecting now
   # Keeping it for backward compatibility and may remove in the future
   if ! _should_skip "processlist"; then
      (echo $ts; $CMD_MYSQL $EXT_ARGV -e "SHOW FULL PROCESSLIST\G") \
      >> "$d/$p-processlist" &
   fi

   (echo $ts; $CMD_MYSQL $EXT_ARGV -e "SELECT * FROM performance_schema.threads\G") \
   >> "$d/$p-threads" &

   if [ "$have_lock_waits_table" ]; then
      if ! _should_skip "lock-waits"; then
         (echo $ts; lock_waits "$d/lock_waits.running")   >>"$d/$p-lock-waits" &
      fi
      if ! _should_skip "transactions"; then
         (echo $ts; transactions) >>"$d/$p-transactions" &
      fi
   fi

   if [ "${mysql_version}" '>' "5.6" ] && [ $ps_instrumentation_enabled == "yes" ] \
      && ! _should_skip "ps-locks-transactions"; then
      ps_locks_transactions "$d/$p-ps-locks-transactions"
   fi

   if [ "${mysql_version}" '>' "5.6" ]; then
      (echo $ts; ps_prepared_statements "$d/prepared_statements.isrunnning") >> "$d/$p-prepared-statements" &
   fi

   local replica_name='replica'
   if [ "${mysql_version}" '<' "8.1" ]; then
      replica_name="slave"
   fi
   replica_status "$d/$p-${replica_name}-status" "${mysql_version}"
}

collect_system_data_loop() {
   # We check the disk, but don't exit, because we need to stop jobs if we
   # need to exit.
   disk_space $d > $d/$p-disk-space
   check_disk_space          \
      $d/$p-disk-space       \
      "$OPT_DISK_BYTES_FREE" \
      "$OPT_DISK_PCT_FREE"   \
      || break

   # Sleep between collect cycles.
   # Synchronize ourselves onto the clock tick, so the sleeps are 1-second
   sleep $(date +'%s.%N' | awk "{print $OPT_SLEEP_COLLECT - (\$1 % $OPT_SLEEP_COLLECT)}")
   ts="$(date +"TS %s.%N %F %T")"

   # #####################################################################
   # Collect data for this cycle.
   # #####################################################################
   if [ -d "/proc" ]; then
      if [ -f "/proc/diskstats" ]; then
         (echo $ts; cat /proc/diskstats) >> "$d/$p-diskstats" &
      fi
      if [ -f "/proc/stat" ]; then
         (echo $ts; cat /proc/stat) >> "$d/$p-procstat" &
      fi
      if [ -f "/proc/vmstat" ]; then
         (echo $ts; cat /proc/vmstat) >> "$d/$p-procvmstat" &
      fi
      if [ -f "/proc/meminfo" ]; then
         (echo $ts; cat /proc/meminfo) >> "$d/$p-meminfo" &
      fi
      if [ -f "/proc/slabinfo" ]; then
         (echo $ts; cat /proc/slabinfo) >> "$d/$p-slabinfo" &
      fi
      if [ -f "/proc/interrupts" ]; then
         (echo $ts; cat /proc/interrupts) >> "$d/$p-interrupts" &
      fi
   fi
   (echo $ts; df -k) >> "$d/$p-df" &
   (echo $ts; netstat -antp) >> "$d/$p-netstat"   &
   (echo $ts; netstat -s)    >> "$d/$p-netstat_s" &
   (echo $ts;
   for node in `ls -d /sys/devices/system/node/node*`; do
      echo `basename $node`; cat "$node/numastat"
   done)                     >> "$d/$p-numastat"   &
}

collect_mysql_data_two() {
   if [ "$have_oprofile" ]; then
      local session="--session-dir=$d/pt_collect_$p"

      if [ $(echo $CMD_OPCONTROL | grep -cv operf) -gt 0 ]; then
	      # use legacy or custom opcontrol
         $CMD_OPCONTROL --stop
         $CMD_OPCONTROL --dump

         local oprofiled_pid=$(_pidof oprofiled | awk '{print $1; exit;}')
         if [ "$oprofiled_pid" ]; then
            kill $oprofiled_pid
         else
            warn "Cannot kill oprofiled because its PID cannot be determined"
         fi

         session="session:pt_collect_$p"

         $CMD_OPCONTROL --save="pt_collect_$p"
      else
         log "$OPERF_PID"
	      kill -SIGINT "$OPERF_PID"
      fi


      # Attempt to generate a report; if this fails, then just tell the user
      # how to generate the report.
      local mysqld_path=$(readlink -f "/proc/$mysqld_pid/exe");
      if [ "$mysqld_path" -a -f "$mysqld_path" ]; then
         $CMD_OPREPORT            \
            --demangle=smart      \
            --symbols             \
            --merge tgid          \
            "$session"            \
            "$mysqld_path"        \
            > "$d/$p-opreport"
      else
         log "oprofile data saved to pt_collect_$p; you should be able"       \
              "to get a report by running something like 'opreport"           \
              "--demangle=smart --symbols --merge tgid session:pt_collect_$p" \
              "/path/to/mysqld'"                                              \
            > "$d/$p-opreport"
      fi
   elif [ "$CMD_STRACE" -a "$OPT_COLLECT_STRACE" ]; then
      kill -s 2 $strace_pid
      sleep 1
      kill -s 15 $strace_pid
      # Sometimes strace leaves threads/processes in T status.
      [ "$mysqld_pid" ] && kill -s 18 $mysqld_pid
   fi
   
   innodb_status 2
   tokudb_status 2
   rocksdb_status 2

   $CMD_MYSQL $EXT_ARGV -e "$mutex" >> "$d/$p-mutex-status2" &
   open_tables                      >> "$d/$p-opentables2"   &

   # Kill backgrounded tasks.
   [ "$mysqladmin_pid" ] &&  kill $mysqladmin_pid
   [ "$tail_error_log_pid" ] && kill $tail_error_log_pid
   [ "$tcpdump_pid" ]        && kill $tcpdump_pid
}

open_tables() {
   local open_tables=$($CMD_MYSQLADMIN $EXT_ARGV ext | grep "Open_tables" | grep -v "Open_tables_with_triggers" | awk '{print $4}')
   if [ -n "$open_tables" -a $open_tables -le 1000 ]; then
      $CMD_MYSQL $EXT_ARGV -e 'SHOW OPEN TABLES' &
   else
      log "Logging disabled due to having over 1000 tables open. Number of tables currently open: $open_tables"
   fi
}

lock_waits() {
   local flag_file=$1
   if test -f "$flag_file"; then
      echo "Lock collection already running, skipping this iteration"
   else
      touch "$flag_file"
      local sql1=""
      local sql2=""
      if [ "${lock_table_p_s}" != "yes" ]; then
         sql1="SELECT SQL_NO_CACHE
            CONCAT('thread ', b.trx_mysql_thread_id, ' from ', p.host) AS who_blocks,
            MAX(IF(p.command = \"Sleep\", p.time, 0)) AS idle_in_trx,
            MAX(TIMESTAMPDIFF(SECOND, r.trx_wait_started, CURRENT_TIMESTAMP)) AS max_wait_time,
            COUNT(*) AS num_waiters
         FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS AS w
         INNER JOIN INFORMATION_SCHEMA.INNODB_TRX AS b ON b.trx_id = w.blocking_trx_id
         INNER JOIN INFORMATION_SCHEMA.INNODB_TRX AS r ON r.trx_id = w.requesting_trx_id
         LEFT JOIN INFORMATION_SCHEMA.PROCESSLIST AS p ON p.id = b.trx_mysql_thread_id
         GROUP BY who_blocks ORDER BY num_waiters DESC\G"

         sql2="SELECT SQL_NO_CACHE
            r.trx_id AS waiting_trx_id,
            r.trx_mysql_thread_id AS waiting_thread,
            TIMESTAMPDIFF(SECOND, r.trx_wait_started, CURRENT_TIMESTAMP) AS wait_time,
            r.trx_query AS waiting_query,
            l.lock_table AS waiting_table_lock,
            b.trx_id AS blocking_trx_id, b.trx_mysql_thread_id AS blocking_thread,
            SUBSTRING(p.host, 1, INSTR(p.host, ':') - 1) AS blocking_host,
            SUBSTRING(p.host, INSTR(p.host, ':') +1) AS blocking_port,
            IF(p.command = \"Sleep\", p.time, 0) AS idle_in_trx,
            b.trx_query AS blocking_query
         FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS AS w
         INNER JOIN INFORMATION_SCHEMA.INNODB_TRX AS b ON b.trx_id = w.blocking_trx_id
         INNER JOIN INFORMATION_SCHEMA.INNODB_TRX AS r ON r.trx_id = w.requesting_trx_id
         INNER JOIN INFORMATION_SCHEMA.INNODB_LOCKS AS l ON w.requested_lock_id = l.lock_id
         LEFT JOIN INFORMATION_SCHEMA.PROCESSLIST AS p ON p.id = b.trx_mysql_thread_id
         ORDER BY wait_time DESC\G"
      else
         sql1="SELECT SQL_NO_CACHE
            CONCAT('thread ', b.trx_mysql_thread_id, ' from ', p.host) AS who_blocks,
            MAX(IF(p.command = \"Sleep\", p.time, 0)) AS idle_in_trx,
            MAX(TIMESTAMPDIFF(SECOND, r.trx_wait_started, CURRENT_TIMESTAMP)) AS max_wait_time,
            COUNT(*) AS num_waiters
         FROM performance_schema.data_lock_waits AS w
         INNER JOIN INFORMATION_SCHEMA.INNODB_TRX AS b ON b.trx_id = w.BLOCKING_ENGINE_TRANSACTION_ID
         INNER JOIN INFORMATION_SCHEMA.INNODB_TRX AS r ON r.trx_id = w.REQUESTING_ENGINE_TRANSACTION_ID
         LEFT JOIN INFORMATION_SCHEMA.PROCESSLIST AS p ON p.id = b.trx_mysql_thread_id
         GROUP BY who_blocks ORDER BY num_waiters DESC\G"

         sql2="SELECT SQL_NO_CACHE
            r.trx_id AS waiting_trx_id,
            r.trx_mysql_thread_id AS waiting_thread,
            TIMESTAMPDIFF(SECOND, r.trx_wait_started, CURRENT_TIMESTAMP) AS wait_time,
            r.trx_query AS waiting_query,
            CONCAT('\`', l.OBJECT_SCHEMA, '\`.\`', l.OBJECT_NAME, '\`') AS waiting_table_lock,
            b.trx_id AS blocking_trx_id, b.trx_mysql_thread_id AS blocking_thread,
            SUBSTRING(p.host, 1, INSTR(p.host, ':') - 1) AS blocking_host,
            SUBSTRING(p.host, INSTR(p.host, ':') +1) AS blocking_port,
            IF(p.command = \"Sleep\", p.time, 0) AS idle_in_trx,
            b.trx_query AS blocking_query
         FROM performance_schema.data_lock_waits AS w
         INNER JOIN INFORMATION_SCHEMA.INNODB_TRX AS b ON b.trx_id = w.BLOCKING_ENGINE_TRANSACTION_ID
         INNER JOIN INFORMATION_SCHEMA.INNODB_TRX AS r ON r.trx_id = w.REQUESTING_ENGINE_TRANSACTION_ID
         INNER JOIN performance_schema.data_locks AS l ON w.REQUESTING_ENGINE_LOCK_ID = l.ENGINE_LOCK_ID
         LEFT JOIN INFORMATION_SCHEMA.PROCESSLIST AS p ON p.id = b.trx_mysql_thread_id
         ORDER BY wait_time DESC\G"
      fi

      $CMD_MYSQL $EXT_ARGV -e "$sql1"
      $CMD_MYSQL $EXT_ARGV -e "$sql2"

      rm "$flag_file"
   fi
}

transactions() {
   $CMD_MYSQL $EXT_ARGV -e "SELECT SQL_NO_CACHE * FROM INFORMATION_SCHEMA.INNODB_TRX ORDER BY trx_id\G"
   if [ "${lock_table_p_s}" != "yes" ]; then
      $CMD_MYSQL $EXT_ARGV -e "SELECT SQL_NO_CACHE * FROM INFORMATION_SCHEMA.INNODB_LOCKS ORDER BY lock_trx_id\G"
      $CMD_MYSQL $EXT_ARGV -e "SELECT SQL_NO_CACHE * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS ORDER BY blocking_trx_id, requesting_trx_id\G"
   else
      $CMD_MYSQL $EXT_ARGV -e "SELECT SQL_NO_CACHE * FROM performance_schema.data_locks ORDER BY ENGINE_TRANSACTION_ID\G"
      $CMD_MYSQL $EXT_ARGV -e "SELECT SQL_NO_CACHE * FROM performance_schema.data_lock_waits ORDER BY BLOCKING_ENGINE_TRANSACTION_ID, REQUESTING_ENGINE_TRANSACTION_ID\G"
   fi
}

tokudb_status() {
    local n=$1

    has_tokudb=`$CMD_MYSQL $EXT_ARGV -e "SHOW ENGINES" | grep -i 'tokudb'`
    exit_code=$?

    if [ $exit_code -eq 0 ]; then
       $CMD_MYSQL $EXT_ARGV -e "SHOW ENGINE TOKUDB STATUS\G" \
         >> "$d/$p-tokudbstatus$n" || rm -f "$d/$p-tokudbstatus$n"
    fi
}

innodb_status() {
   local n=$1

   local innostat=""

   if ! _should_skip "innodbstatus"; then
      $CMD_MYSQL $EXT_ARGV -e "SHOW /*!40100 ENGINE*/ INNODB STATUS\G" \
         >> "$d/$p-innodbstatus$n"
      grep "END OF INNODB" "$d/$p-innodbstatus$n" >/dev/null || {
         if [ -d /proc -a -d /proc/$mysqld_pid ]; then
            for fd in /proc/$mysqld_pid/fd/*; do
               file $fd | grep deleted >/dev/null && {
                  grep 'INNODB' $fd >/dev/null && {
                     cat $fd > "$d/$p-innodbstatus$n"
                     break
                  }
               }
            done
         fi
      }
   fi
}

rocksdb_status() {
    local n=$1

    has_rocksdb=`$CMD_MYSQL $EXT_ARGV -e "SHOW ENGINES" | grep -i 'rocksdb'`
    exit_code=$?

    if [ $exit_code -eq 0 ] && ! _should_skip "rocksdbstatus"; then
        $CMD_MYSQL $EXT_ARGV -e "SHOW ENGINE ROCKSDB STATUS\G" \
                   >> "$d/$p-rocksdbstatus$n" || rm -f "$d/$p-rocksdbstatus$n"
    fi
}

ps_locks_transactions() {
   local outfile=$1

   $CMD_MYSQL $EXT_ARGV -e 'select @@performance_schema' | grep "1" &>/dev/null

   if [ $? -eq 0 ]; then
      local status="select t.processlist_id, ml.* from performance_schema.metadata_locks ml join performance_schema.threads t on (ml.owner_thread_id=t.thread_id)\G"
      echo -e "\n$status\n" >> $outfile
      $CMD_MYSQL $EXT_ARGV -e "$status" >> $outfile

      local status="select t.processlist_id, th.* from performance_schema.table_handles th left join performance_schema.threads t on (th.owner_thread_id=t.thread_id)\G"
      echo -e "\n$status\n" >> $outfile
      $CMD_MYSQL $EXT_ARGV -e "$status" >> $outfile

      local status="select t.processlist_id, et.* from performance_schema.events_transactions_current et join performance_schema.threads t using(thread_id)\G"
      echo -e "\n$status\n" >> $outfile
      $CMD_MYSQL $EXT_ARGV -e "$status" >> $outfile

      local status="select t.processlist_id, et.* from performance_schema.events_transactions_history_long et join performance_schema.threads t using(thread_id)\G"
      echo -e "\n$status\n" >> $outfile
      $CMD_MYSQL $EXT_ARGV -e "$status" >> $outfile
  else
      echo "Performance schema is not enabled" >> $outfile
   fi

}

ps_prepared_statements() {
# PS-2033:
# If no flag file exists, create it, then collect data
# After data collected, remove the file
# If flag file exists, skip current iteration
   local flag_file=$1
   if test -f "$flag_file"; then
      echo "Prepared statements collection already running, skipping this iteration"
   else
      touch "$flag_file"
      $CMD_MYSQL $EXT_ARGV -e "SELECT t.processlist_id, pse.* \
                               FROM performance_schema.prepared_statements_instances pse \
                               JOIN performance_schema.threads t \
                               ON (pse.OWNER_THREAD_ID=t.thread_id)\G"
      rm "$flag_file"
   fi
}

replica_status() {
   local outfile=$1
   local mysql_version=$2

   local sql="SHOW REPLICA STATUS\G"
   if [ "${mysql_version}" '<' "8.1" ]; then
      sql="SHOW SLAVE STATUS\G"
   fi

   echo -e "\n$sql\n" >> $outfile
   $CMD_MYSQL $EXT_ARGV -e "$sql" >> $outfile

   if [ "${mysql_version}" '>' "5.6" ]; then
      local sql="SELECT * FROM performance_schema.replication_connection_configuration JOIN performance_schema.replication_applier_configuration USING(channel_name)\G"
      echo -e "\n$sql\n" >> $outfile
      $CMD_MYSQL $EXT_ARGV -e "$sql" >> $outfile

      sql="SELECT * FROM performance_schema.replication_connection_status\G"
      echo -e "\n$sql\n" >> $outfile
      $CMD_MYSQL $EXT_ARGV -e "$sql" >> $outfile

      sql="SELECT * FROM performance_schema.replication_applier_status JOIN performance_schema.replication_applier_status_by_coordinator USING(channel_name)\G"
      echo -e "\n$sql\n" >> $outfile
      $CMD_MYSQL $EXT_ARGV -e "$sql" >> $outfile
   fi

}

collect_mysql_variables() {
   local outfile=$1

   local sql="SHOW GLOBAL VARIABLES"
   echo -e "\n$sql\n" >> $outfile
   $CMD_MYSQL $EXT_ARGV -e "$sql" >> $outfile

   if ! _should_skip "thread-variables"; then
      sql="select * from performance_schema.variables_by_thread order by thread_id, variable_name;"
      echo -e "\n$sql\n" >> $outfile
      $CMD_MYSQL $EXT_ARGV -e "$sql" >> $outfile

      sql="select * from performance_schema.user_variables_by_thread order by thread_id, variable_name;"
      echo -e "\n$sql\n" >> $outfile
      $CMD_MYSQL $EXT_ARGV -e "$sql" >> $outfile

      sql="select * from performance_schema.status_by_thread order by thread_id, variable_name; "
      echo -e "\n$sql\n" >> $outfile
      $CMD_MYSQL $EXT_ARGV -e "$sql" >> $outfile
   fi

}

_should_skip() {
   local name=$1

   for item in "${OPT_SKIP_COLLECTION[@]}"; do
      if [ "$item" == "$name" ]; then
         return 0
      fi
   done

   return 1
}

# ###########################################################################
# End collect package
# ###########################################################################
