#!/bin/sh
#
# $Id$
#

# PROVIDE: mysql
# REQUIRE: LOGIN sshd
# KEYWORD: shutdown

# -----------------------------------------------------------------------------
#
# This script supports running multiple instances of mysql.
# To run additional instance link this script to something like
# % ln -s mysql mysql_foo
# and define additional mysql_foo_* variables in one of
# /etc/rc.conf, /etc/rc.conf.local or /etc/rc.conf.d/mysql_foo
#
# Below NAME should be substituted with the name of this script. By default
# it is mysql, so read as mysql_enable. If you linked the script to
# mysql_foo, then read as mysql_foo_enable etc.
#
# The following variables are supported (defaults are shown).
# You can place them in any of
# /etc/rc.conf, /etc/rc.conf.local or /etc/rc.conf.d/NAME
#
# NAME_enable="NO"		# set to YES to enable mysql
#
# # optional:
# NAME_limits="NO"		# set to yes to run `limits -e -U mysql`
#				# just before mysql starts
# NAME_dbdir="/var/db/mysql"	# base database directory
# NAME_args=""			# additional command line arguments
# NAME_user="mysql"		# custom user for running mysql
#

. /etc/rc.subr

case "$0" in
/etc/rc*)
	# during boot (shutdown) $0 is /etc/rc (/etc/rc.shutdown),
	# so get the name of the script from $_file
	name=$(basename "$_file" .sh)
	;;
*)
	name=$(basename "$0" .sh)
	;;
esac

rcvar=$(set_rcvar)

load_rc_config $name

eval ": \${${name}_enable:=\"NO\"}"
eval ": \${${name}_limits:=\"NO\"}"
eval ": \${${name}_dbdir:=\"/var/db/${name}\"}"
eval ": \${${name}_args:=\"\"}"
eval ": \${${name}_user:=\"mysql\"}"

mysql_limits="$(eval echo \${${name}_limits})"
mysql_dbdir="$(eval echo \${${name}_dbdir})"
mysql_args="$(eval echo \${${name}_args})"
mysql_user="$(eval echo \${${name}_user})"

mysql_socket="/tmp/${name}.sock"
mysql_defaults_file="${mysql_dbdir}/batch_config"
mysql_limits_args="-e -U ${mysql_user}"

pidfile="${mysql_dbdir}/mysqld.pid"
command="/usr/local/bin/mysqld_safe"
command_args="--defaults-extra-file=${mysql_dbdir}/my.cnf --user=${mysql_user} --datadir=${mysql_dbdir} --pid-file=${pidfile} --socket=${mysql_socket} ${mysql_args} > /dev/null 2>&1 &"
procname="/usr/local/libexec/mysqld"

# RC commands
start_precmd="mysql_prestart"
start_postcmd="mysql_poststart"
stop_precmd="mysql_prestop"
stop_cmd="mysql_stop"

extra_commands="log_rotate dirty_on dirty_off cardinality get_datadir get_socket get_defaults_file"
log_rotate_cmd="mysql_log_rotate"
dirty_on_cmd="mysql_dirty_on"
dirty_off_cmd="mysql_dirty_off"
cardinality_cmd="mysql_cardinality"
get_datadir_cmd="echo ${mysql_dbdir}"
get_socket_cmd="echo ${mysql_socket}"
get_defaults_file_cmd="echo ${mysql_defaults_file}"

# MySQL utilities
mysql_install_db="/usr/local/bin/mysql_install_db"
mysql_install_db_args="--ldata=${mysql_dbdir}"
mysql_admin="/usr/local/bin/mysqladmin"
mysql_admin_args="--defaults-file=${mysql_defaults_file} --socket=${mysql_socket}"
mysql_client="/usr/local/bin/mysql"
mysql_client_args="--defaults-file=${mysql_defaults_file} --socket=${mysql_socket} -NBAe"

# MySQl Logs
mysql_err_log="${mysql_dbdir}/error.log"
mysql_slow_log="${mysql_dbdir}/slow.log"
mysql_err_log_count=35
mysql_slow_log_count=21

# MySQL Dirty Pages
mysql_dirty_pgs_pct=95
mysql_dirty_pgs_threshold=4096
mysql_dirty_timeout=300			# seconds

# MySQL Cardinality
mysql_cardinality_exclude_dbs="information_schema|mysql|tmp"

mysql_create_auth_tables()
{
	eval $mysql_install_db $mysql_install_db_args >/dev/null 2>/dev/null
        [ $? -eq 0 ] && chown -R ${mysql_user}:${mysql_user} ${mysql_dbdir}
}

mysql_prestart()
{
	if [ ! -d "${mysql_dbdir}/mysql/." ]; then
		mysql_create_auth_tables || return 1
	fi
	if checkyesno mysql_limits; then
		eval $(/usr/bin/limits ${mysql_limits_args}) 2>/dev/null
	else
		return 0
	fi
}

mysql_poststart()
{
	local timeout=15
	while [ ! -f "${pidfile}" -a ${timeout} -gt 0 ]; do
		timeout=$(( timeout - 1 ))
		sleep 1
	done
	return 0
}

mysql_prestop()
{
	mysql_dirty_off
}

mysql_stop()
{
	if [ -z "$rc_pid" ]; then
		[ -n "$rc_fast" ] && return 0
		_run_rc_notrunning
		return 1
	fi

	echo "Stopping ${name}."
	if ! $mysql_admin $mysql_admin_args shutdown; then
		return 1
	fi
	return 0
}

is_new_version()
{
	# MySQL has two schemes to rotate error logs depending on version:
	# - prior to MySQL 5.5.7 error log is renamed to <name>-old by running
	#   "FLUSH LOGS";
	# - since version 5.5.7 MySQL just reopens error log without renaming.
	# 
	# See http://dev.mysql.com/doc/refman/5.5/en/log-file-maintenance.html
	# for details.
	#
	# In this subroutine we don't look at the minor version to simplify
	# code.

	local _ver

	_ver="$1"

	case "${_ver}" in
		5.5.*) return 0 ;;
		5.[01].*) return 1 ;;
		*) err 1 "Unsupported version of MySQL !" ;;
	esac
}

mysql_log_rotate()
{
	local _delim _date _old_err_logs _old_slow_logs _old_logs

	_delim="."
	_date=$(date -v-1H +%F)

	mysql_version=$($mysql_client $mysql_client_args 'SELECT @@version' | \
		grep -Eo '^[0-9.]+')

	if [ -z "${mysql_version}" ]; then
		err 1 "Can't get MySQL version to choose rotation scheme !"
	fi

	mysql_err_log_btw="${mysql_err_log}-old"
	mysql_err_log_save="${mysql_err_log}${_delim}${_date}"
	mysql_slow_log_save="${mysql_slow_log}${_delim}${_date}"

	if ! mv $mysql_slow_log $mysql_slow_log_save; then
		err 1 "Can't rename ${mysql_slow_log} to ${mysql_slow_log_save} !"
	fi

	if is_new_version $mysql_version; then
		if ! mv $mysql_err_log $mysql_err_log_save; then
			err 1 "Can't rename ${mysql_err_log} to" \
				"${mysql_err_log_save} !"
		fi
	fi

	if ! $mysql_client $mysql_client_args "FLUSH LOCAL LOGS"; then
		err 1 "Can't flush logs !"
	fi

	if ! is_new_version $mysql_version; then
		if ! mv $mysql_err_log_btw $mysql_err_log_save; then
			err 1 "Can't rename ${mysql_err_log_btw} to" \
				"${mysql_err_log_save} !"
		fi
	fi

	_old_err_logs=$(ls -1r ${mysql_err_log}${_delim}* | \
		tail +$((mysql_err_log_count + 1)) 2>/dev/null)
	_old_slow_logs=$(ls -1r ${mysql_slow_log}${_delim}* | \
		tail +$((mysql_slow_log_count + 1)) 2>/dev/null)
	_old_logs="${_old_err_logs} ${_old_slow_logs}"

	if echo $_old_logs | grep -q $mysql_dbdir; then
		if ! rm -f $_old_logs; then
			err 1 "Can't clean old logs (${_old_logs})!"
		fi
	fi

	return 0
}

mysql_get_dirty_pgs()
{
	local _dirty_pgs

	_dirty_pgs=$($mysql_client $mysql_client_args \
		"SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty'" | \
		awk '{print $2}')

	echo $_dirty_pgs
}

mysql_dirty_on()
{
	if ! $mysql_client $mysql_client_args \
		"SET GLOBAL innodb_max_dirty_pages_pct=${mysql_dirty_pgs_pct}"
	then
		err 1 "Can't disable dirty pages !"
	fi
	echo "Set dirty pages pct to ${mysql_dirty_pgs_pct}."

	return 0
}

mysql_dirty_off()
{
	# This command is useful before shutting down the server. See
	# http://www.mysqlperformanceblog.com/2009/04/15/how-to-decrease-innodb-shutdown-times/
	# for details.

	local _dirty_pgs _start_time _threshold_time _now

	if ! { $mysql_client $mysql_client_args \
		"SELECT PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS \
			WHERE PLUGIN_NAME='InnoDB'" | grep -wq "ACTIVE"; }
	then
		# InnoDB is disabled for this instance
		return 0
	fi

	if ! $mysql_client $mysql_client_args \
		"SET GLOBAL innodb_max_dirty_pages_pct=0"
	then
		err 1 "Can't disable dirty pages !"
	fi

	_start_time=$(date +%s)
	_threshold_time=$(($_start_time + $mysql_dirty_timeout))

	echo -n "Clean dirty pages:"
	_dirty_pgs=$(mysql_get_dirty_pgs)
	_now=$(date +%s)
	while [ ${_dirty_pgs:-0} -gt $mysql_dirty_pgs_threshold ]; do
		_now=$(date +%s)
		if [ $_now -gt $_threshold_time ]; then
			echo -n ", timeout is reached"
			break
		fi

		sleep 2

		_dirty_pgs=$(mysql_get_dirty_pgs)
		echo -n " ${_dirty_pgs}"
	done
	echo "."

	return 0
}

mysql_cardinality()
{
	# See http://dev.mysql.com/doc/refman/5.1/en/innodb-tuning.html
	# for details.

	local _dbs _db _tbls _tbl _i

	_dbs=$($mysql_client $mysql_client_args "SHOW DATABASES" | \
		grep -Ev "${mysql_cardinality_exclude_dbs}")

	for _db in $_dbs; do
		echo ">>> Start cardinality for: ${_db}"
		_tbls=$($mysql_client $mysql_client_args "SHOW TABLES" $_db)
		for _tbl in $_tbls; do
			_i=$(($_i + 1))
			if $mysql_client $mysql_client_args \
				"SELECT 1 FROM $_tbl LIMIT 1" $_db >/dev/null
			then
				echo $_i $_tbl
			else
				err 1 "Can't compute cardinality for $_tbl !"
			fi
		done
	done

	return 0
}

run_rc_command "$1"
