#!/bin/sh
#
# Copyright (c) 2006 MySQL AB
# 
# 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; either version 2 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# 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

echo "########################################################################"
echo "#"
echo "# Test Bug#17332 - changing key_buffer_size on a running server"
echo "#                  can crash under load"
echo "#"
echo

########################################################################
#
# Settings. CAUTION: Paths are not space safe.
#

# BASEDIR="install directory"
# DATADIR="databases directory e.g. $BASEDIR/var"
DATADIR="${DATADIR:-$BASEDIR/var}"
TREEROOT="${TREEROOT:-$HOME}"

MYSQLD="$BASEDIR/libexec/mysqld"
MYSQLC="$BASEDIR/bin/mysql"
MYSQLA="$BASEDIR/bin/mysqladmin"
MYSQLT="$BASEDIR/bin/mysqltest"

PORT_1="${MYSQL_TCP_PORT:+--port=$MYSQL_TCP_PORT}"
SOCK_1="${MYSQL_UNIX_PORT:+--socket=$MYSQL_UNIX_PORT}"
USER_1="-u root -D test"
#CLNT_1="-A -v -f"
 CLNT_1="-A -v --show-warnings"
DATA_1="--basedir=$BASEDIR --datadir=$DATADIR"
SERV_1="--log-error --core"
#SERV_1="$SERV_1 --skip-innodb --skip-ndbcluster"
#
########################################################################
# These parameters influence the behaviour of the test:
#
 NUMBER_OF_TABLES=4
 CLIENTS_PER_TABLE=4
#
#BLOCK_SIZE="1200"  # force 1024 bytes cache blocks
#BLOCK_SIZE="2200"  # force 2048 bytes cache blocks
#BLOCK_SIZE="4300"  # force 4096 bytes cache blocks
 BLOCK_SIZE="8400"  # force 8192 bytes cache blocks
#
#CACHE_SIZE="0"       # disable key cache
#CACHE_SIZE="12288"   # force evictions with empty LRU ring (min size for 1K)
#CACHE_SIZE="20480"   # force evictions with empty LRU ring (min size for 2K)
#CACHE_SIZE="36864"   # force evictions with empty LRU ring (min size for 4K)
#CACHE_SIZE="69632"   # force evictions with empty LRU ring (min size for 8K)
#CACHE_SIZE="36864"   # small  cache (for 1K blocks)
#CACHE_SIZE="73728"   # small  cache (for 2K blocks)
#CACHE_SIZE="147456"  # small  cache (for 4K blocks)
 CACHE_SIZE="294912"  # small  cache (for 8K blocks)
#CACHE_SIZE="8388608" # medium cache
#
#RESIZE_MEM="0       0"       # min/max for disabled resize of disabled cache
#RESIZE_MEM="2000    8000"    # min/max for  enabled resize of disabled cache
#RESIZE_MEM="36864   36864"   # min/max for disabled resize for 1K blocks
#RESIZE_MEM="73728   73728"   # min/max for disabled resize for 2K blocks
#RESIZE_MEM="147456  147456"  # min/max for disabled resize for 4K blocks
#RESIZE_MEM="294912  294912"  # min/max for disabled resize for 8K blocks
#RESIZE_MEM="8388608 8388608" # min/max for disabled resize for medium cache
#RESIZE_MEM="0       36864"   # min/max for normal resize for 1K blocks
#RESIZE_MEM="0       73728"   # min/max for normal resize for 2K blocks
#RESIZE_MEM="0       147456"  # min/max for normal resize for 4K blocks
 RESIZE_MEM="0       294912"  # min/max for normal resize for 8K blocks
#
 RESIZE_BLK="0 3"    # min/max for 1K*power(2, X)
#
# Some machines need a break to cool down. Seconds.
# Can be overridden by contents of file "$DATADIR/sleep_time".
 SLEEP_TIME=60
#
########################################################################
#
SERV_1="$SERV_1 --key_cache_block_size=$BLOCK_SIZE"
SERV_1="$SERV_1 --key_buffer_size=$CACHE_SIZE"
#SERV_1="$SERV_1 --delay-key-write=all"
#SERV_1="$SERV_1 --skip-concurrent-insert"
#
#DBUG_1="--debug=t:d:i:O,$DATADIR/mysqld.trace"
#DBUG_1="--debug=d,thrlock:i:O,$DATADIR/mysqld.trace"

HOSTNAM="`expr \`uname -n\` : '\([^\.]*\)'`"
ERRFILE="$HOSTNAM.err"
PIDFILE="$HOSTNAM.pid"

########################################################################
#
# Go to DATADIR.
#
cd "$DATADIR" || exit $?

########################################################################
#
# Cleanup.
#
rm -f *.trace *.err *.pid core* test/#sql* bug17332* test_stop
rm -rf tmp/*
test -d test || mkdir test
test -d tmp  || mkdir tmp

########################################################################
#
# Functions.

sleep_until_pidfile_created ()
{
  file="$1"
  loop="$2"
  org_time="$2"

  while [ "$loop" -gt 0 ]
  do
    if [ -r "$file" ]
    then
      return
    fi
    sleep 1
    loop="`expr \"$loop\" - 1`"
  done
  echo "ERROR: $file was not created in $org_time seconds;  Aborting"
  exit 1;
}

sleep_until_pidfile_deleted ()
{
  file="$1"
  loop="$2"

  while [ "$loop" -gt 0 ]
  do
    if [ -r "$file" ]
    then
      if kill -0 "`cat \"$file\"`"
      then
        sleep 1
      else
        break
      fi
    else
      break
    fi
    loop="`expr \"$loop\" - 1`"
  done
}

########################################################################
#
# Preparing scripts.
#
cat > bug17332-create.pl <<'EOF'
#!/usr/bin/perl -w

    my $table= $ARGV[0];


    print "DROP TABLE IF EXISTS $table;\n";
    print "CREATE TABLE $table (";
    print "  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,";
    print "  v1 CHAR(150),";
    print "  v2 CHAR(150),";
    print "  v3 CHAR(150),";
    print "  KEY(id,v1,v2,v3),";
    print "  KEY(v2,v3,v1),";
    print "  KEY(v3,v1,v2)";
    print "  ) ENGINE=MyISAM;\n";
EOF

cat > bug17332-worker.pl <<'EOF'
#!/usr/bin/perl -w

    my $table= $ARGV[0];

    #
    # Loop on insert and flush.
    #
    for (my $insert= 50; $insert; $insert--)
    {
        print "SHOW VARIABLES LIKE '%key\\_%';\n";
        print "SHOW STATUS LIKE 'key\\_%';\n";

        #
        # Load keycache.
        #
        print "INSERT INTO ${table} (v1,v2,v3) VALUES ('a','b','c')";
        for (my $idx= int(rand(512)); $idx > 0; $idx--)
        {
            print ",('a','b','c')";
        }
        print ";\n";

        #
        # Miscellaneous operations executed randomly.
        #
        if ( $insert % int(rand(5) + 1) == 1 )
        {
            if ( int(rand(7)) == 0 )
            {
                print "UPDATE ${table} SET v1='a', v2='b', v3='c'".
                      "WHERE v2='e';\n";
            }
            else
            {
                print "UPDATE ${table} SET v1='d', v2='e', v3='f'".
                      "WHERE id MOD 13 = ". int(rand(13)) .";\n";
            }
        }
        if ( $insert % int(rand(20) + 1) == 1 )
        {
            if ( int(rand(4)) == 0 )
            {
                print "FLUSH TABLE;\n";
            }
            else
            {
                print "FLUSH TABLE ${table};\n";
            }
        }
        if ( $insert % int(rand(20) + 1) == 1 )
        {
            print "CHECK TABLE ${table};\n";
        }
        if ( $insert % int(rand(20) + 1) == 1 )
        {
            print "CREATE TEMPORARY TABLE tt1".
                  "(KEY(id), KEY(v1,v2,v3)) SELECT * FROM ${table};\n";
            print "UPDATE tt1 SET v1='d', v2='e', v3='f'".
                  "WHERE id MOD 13 = ". int(rand(13)) .";\n";
            print "DROP TABLE tt1;\n";
        }
        if ( $insert % int(rand(20) + 1) == 1 )
        {
            print "LOAD INDEX INTO CACHE ${table};\n";
        }
    }

    #
    # Check table.
    #
    if ( int(rand(2))  == 0 )
    {
        print "FLUSH TABLE ${table};\n";
    }
    print "CHECK TABLE ${table};\n";

    #
    # End of test.
    #
    print "SHOW VARIABLES LIKE '%key\\_%';\n";
    print "SHOW STATUS LIKE 'key\\_%';\n";
    print "SELECT 'END OF TEST';\n";
EOF

cat > bug17332-resizer.pl <<'EOF'
#!/usr/bin/perl -w

    my $resize_mem_min=    $ARGV[0];
    my $resize_mem_max=    $ARGV[1];
    my $resize_blk_min=    $ARGV[2];
    my $resize_blk_max=    $ARGV[3];
    my $number_of_tables=  $ARGV[4];
    my $clients_per_table= $ARGV[5];
    my $resize_mem_var= $resize_mem_max - $resize_mem_min + 1;
    my $resize_blk_var= $resize_blk_max - $resize_blk_min + 1;
    my $loop_min= 4 * $number_of_tables * $clients_per_table;
    my $loop_var= 240 * $number_of_tables * $clients_per_table;
    my $delay_key_write= 0;

    #
    # Loop on resize.
    #
    for (my $idx= $loop_min + int(rand($loop_var)); $idx; $idx--)
    {
        #
        # Change key_buffer_size in every iteration.
        #
        print "SHOW VARIABLES LIKE '%key\\_%';\n";
        print "SHOW STATUS LIKE 'key\\_%';\n";

        print "SET GLOBAL key_buffer_size=".
              ($resize_mem_min + int(rand($resize_mem_var))) .";\n";

        #
        # Sometimes sleep after resize
        #
        if ( $idx % int(rand(10) + 1) == 1 )
        {
            print "SHOW VARIABLES LIKE '%key\\_%';\n";
            print "SHOW STATUS LIKE 'key\\_%';\n";

            print "SELECT SLEEP(". int(rand(4) + 1) .");\n";
        }

        #
        # Sometimes change key_cache_block_size
        #
        if ( $idx % int(rand(10) + 1) == 1 )
        {
            print "SHOW VARIABLES LIKE '%key\\_%';\n";
            print "SHOW STATUS LIKE 'key\\_%';\n";

            my $curr_block_size= 1024 * (1 << ($resize_blk_min +
                                               int(rand($resize_blk_var))));

            print "SET GLOBAL key_cache_block_size=". $curr_block_size .";\n";

            #
            # Sometimes sleep after resize
            #
            if ( $idx % int(rand(4) + 1) == 1 )
            {
                print "SHOW VARIABLES LIKE '%key\\_%';\n";
                print "SHOW STATUS LIKE 'key\\_%';\n";

                print "SELECT SLEEP(". int(rand(4) + 1) .");\n";
            }
        }

        #
        # Sometimes toggle delay_key_write
        #
        if ( $idx % int(rand(20) + 1) == 1 )
        {
            print "SHOW VARIABLES LIKE '%key\\_%';\n";
            print "SHOW STATUS LIKE 'key\\_%';\n";

            $delay_key_write= !$delay_key_write;
            if ($delay_key_write)
            {
                print "SET GLOBAL delay_key_write= ALL;\n";
            }
            else
            {
                print "SET GLOBAL delay_key_write= OFF;\n";
            }
        }
    }
    print "SHOW VARIABLES LIKE '%key\\_%';\n";
    print "SHOW STATUS LIKE 'key\\_%';\n";
    print "SELECT 'END OF TEST';\n";
EOF

########################################################################
#
# Run in a loop until it fails.
#
ROUND=1
while :
do

  echo "######################################################################"
  echo "#"
  echo "# `date '+%Y-%m-%d %H:%M:%S'` Starting round $ROUND."
  echo "#"
  echo "# key_cache_block_size: $BLOCK_SIZE"
  echo "# key_buffer_size:      $CACHE_SIZE"
  echo "# resize_mem min/max:   $RESIZE_MEM"
  echo "# resize_blk min/max:   $RESIZE_BLK"
  echo
  echo "######################################################################"
  echo "#"
  echo "# Starting database server."
  #
  # Use a subshell here, to detach the server.
  #
  (
    nice "$MYSQLD" --no-defaults $PORT_1 $SOCK_1 $DATA_1 $SERV_1 $DBUG_1 &
    SERV_1_PID=$!
    echo "# Process_id $SERV_1_PID"
    sleep_until_pidfile_created "$PIDFILE" 400
  )
  echo

  echo "######################################################################"
  echo "#"
  echo "# Creating tables."
  TABLE_NO="1"
  while [ "$TABLE_NO" -le "$NUMBER_OF_TABLES" ]
  do
        perl bug17332-create.pl "t$TABLE_NO" 2>/dev/null | \
            "$MYSQLC" $PORT_1 $SOCK_1 $USER_1 $CLNT_1 \
            > "bug17332-$TABLE_NO-0.log" 2>&1
    TABLE_NO="`expr \"$TABLE_NO\" + 1`"
  done
  sleep 2
  echo

  echo "######################################################################"
  echo "#"
  echo "# Flush tables."
  "$MYSQLA" $PORT_1 $SOCK_1 -u root refresh
  echo

  TABLE_NO="1"
  while [ "$TABLE_NO" -le "$NUMBER_OF_TABLES" ]
  do
    CLIENT_NO="1"
    while [ "$CLIENT_NO" -le "$CLIENTS_PER_TABLE" ]
    do
      (
        CLIENT_ID="$TABLE_NO-$CLIENT_NO"
        echo "################################################################"
        echo "#"
        echo "# `date '+%Y-%m-%d %H:%M:%S'` Starting client $CLIENT_ID."
        perl bug17332-worker.pl "t$TABLE_NO" 2>/dev/null | \
            "$MYSQLC" $PORT_1 $SOCK_1 $USER_1 $CLNT_1 \
            > "bug17332-$CLIENT_ID.log" 2>&1
        echo "# `date '+%Y-%m-%d %H:%M:%S'` Client $CLIENT_ID finished" \
             "round $ROUND."
      ) &
      sleep 2
      echo
      CLIENT_NO="`expr \"$CLIENT_NO\" + 1`"
    done
    TABLE_NO="`expr \"$TABLE_NO\" + 1`"
  done

  #
  # There is no use for more than one resizers as only one can be started 
  # from set_var.cc (see key_cache->in_init).
  #
  (
    echo "################################################################"
    echo "#"
    echo "# `date '+%Y-%m-%d %H:%M:%S'` Starting resizer."
    perl bug17332-resizer.pl $RESIZE_MEM $RESIZE_BLK \
         $NUMBER_OF_TABLES $CLIENTS_PER_TABLE 2>/dev/null | \
        "$MYSQLC" $PORT_1 $SOCK_1 $USER_1 $CLNT_1 \
            > bug17332-0-resizer.log 2>&1
    echo "# `date '+%Y-%m-%d %H:%M:%S'` Resizer finished round $ROUND."
  ) &
  sleep 2
  echo

  echo "######################################################################"
  echo "#"
  echo "# `date '+%Y-%m-%d %H:%M:%S'` Waiting for completion of round $ROUND..."
  wait
  echo

  echo "######################################################################"
  echo "#"
  echo "# Stopping database server."
  "$MYSQLA" $PORT_1 $SOCK_1 -u root shutdown
  RC=$?
  sleep_until_pidfile_deleted "$PIDFILE" 60
  echo

  if [ "`echo core*`" != "core*" ]
  then
      ls -l core*
      RC=1
  fi
  if grep -Hi "corrupt|crash|incorrect key file" "$ERRFILE" \
     || egrep -Hi "corrupt|crash|incorrect key file" bug17332-*.log
  then
      # Messages are printed by grep/egrep
      RC=1
  fi
  if grep -H "ERROR" "$ERRFILE" \
     || egrep -Hi "error" bug17332-*.log
  then
      # Messages are printed by grep/egrep
      : # RC=1
  fi
  if [ "$RC" -ne 0 ]
  then
      break
  fi
  if [ -f "test_stop" ]
  then
      echo "Test stopped by existing file 'test_stop' after round $ROUND."
      break
  fi

  echo "Let the machine cool down after round $ROUND..."
  if [ -f "sleep_time" ]
  then
      sleep `cat sleep_time`
  else
      sleep $SLEEP_TIME
  fi
  echo
  echo
  ROUND=`expr $ROUND + 1`

done


echo "# End of Test."
echo "#"
echo "########################################################################"

