#!/bin/sh
#
# Bootchart logger script
# Ziga Mahkovec  <ziga.mahkovec@klika.si>
#
# This script is used for data collection for the bootchart
# boot performance visualization tool (http://www.bootchart.org).
#
# To profile the boot process, bootchartd should be called instead of
# /sbin/init.  Modify the kernel command line to include:
# init=/sbin/bootchartd
#
# bootchartd will then start itself in background and exec /sbin/init.
#
# To profile a running system, run:
# $ /sbin/bootchartd start; sleep 30; /sbin/bootchartd stop
#

PATH="/sbin:/bin:/usr/sbin:/usr/bin"
VERSION="0.8"

# Read configuration.
CMD=$( basename $0 )
if [ -f /etc/$CMD.conf ]; then
        . /etc/$CMD.conf
else
        echo "/etc/$CMD.conf missing"
        exit 1
fi

# The path to GNU accounting utilities (optional)
ACCTON=/sbin/accton
[ -x /usr/sbin/accton ] && ACCTON=/usr/sbin/accton


# Start the boot logger.
start()
{
	# Make sure only a single instance is running
	[ -f "$BOOTLOG_LOCK" ] && return
	
	# Mount the temporary file system for log file storage
	mount -n -t tmpfs -o size=$TMPFS_SIZE none "$TMPFS_MOUNT" >/dev/null 2>&1
	> "$BOOTLOG_LOCK"

	# Enable process accounting if configured
	if [ "$PROCESS_ACCOUNTING" = "yes" -a -x $ACCTON ]; then
		> "$TMPFS_MOUNT/kernel_pacct"
		$ACCTON "$TMPFS_MOUNT/kernel_pacct"
	fi
	
	# Wait for /proc to be mounted
	while [ ! -f /proc/stat ]; do sleep $SAMPLE_PERIOD; done
	sleep $SAMPLE_PERIOD

	#
	# Run loggers in background
	#
	log_output "cat /proc/stat" proc_stat.log &

	# /proc/diskstats is available in 2.6 kernels
	[ -f /proc/diskstats ] && log_output "cat /proc/diskstats" proc_diskstats.log &

	log_output "cat /proc/*/stat" proc_ps.log &
	#log_output "ps -eww -o pid,ppid,s,pcpu,comm,cmd" ps.log &

	if [ -n "$IN_INIT" ]; then
		# If we were called during init, wait for the boot process to end
		wait_boot &
	elif [ "$#" -gt 0 ]; then
		# If a command was passed, run it
		# (used for profiling specific applications)
		$@
		echo "profile.process = $@" >> "$TMPFS_MOUNT"/header
		stop
	fi
}


# Run the command ($1) every $SAMPLE_PERIOD seconds and append output to
# file ($2).  Mostly pure bash, so we avoid creating an excessive number of
# processes (thus polluting the process tree).
log_output()
{
	local cmd="$1"
	local logfile="$2"
	while [ -f "$BOOTLOG_LOCK" ]; do
		# Write the time (in jiffies).
		read cpustat < /proc/stat
		get_uptime $cpustat

		# Log the command output
		eval $cmd 2>/dev/null
		echo
		sleep $SAMPLE_PERIOD
	done  >> "$TMPFS_MOUNT/$logfile" || stop
}

# Retrieves the uptime from the first line of /proc/stat:
# cpu  11562 0 1918 96634 7040 202 0
# All CPU times are summed up.
get_uptime()
{
	shift
        (IFS=+; echo "$(($*))")
}

# Wait for the boot process to end.
wait_boot()
{
	local runlevel=$( sed -n 's/.*:\(.*\):initdefault:.*/\1/gp' /etc/inittab )

	# The processes we have to wait for
	local exit_proc="gdmgreeter gdm-binary kdm_greet xterm konsole gnome-terminal"
	# early_login in FC4 starts gdm early, so fall back to mingetty
	local early_login="no"
	grep -q early_login /proc/cmdline && early_login="yes"
	if [ "$runlevel" -eq "2" -o "$runlevel" -eq "3" -o "$early_login" = "yes" ]; then
		exit_proc="mingetty agetty rungetty getty"
	fi
	while [ -f "$BOOTLOG_LOCK" ]; do
		if [ -n "$( pidof $exit_proc )" ]; then
			# Give the exit process some time to start
			sleep 2
			# Flush the log files
			stop
			return
		fi
		usleep 300000
	done;
}


# Stop the boot logger.  The lock file is removed to force the loggers in
# background to exit.  Some final log files are created and then all log files
# from the tmpfs are packaged and stored in /var/log. 
stop()
{
	if [ ! -f "$BOOTLOG_LOCK" ]; then
		echo "${0##*/} not running"
		return
	fi
	rm -f "$BOOTLOG_LOCK"
	sleep $SAMPLE_PERIOD
	sleep $SAMPLE_PERIOD

	# Stop process accounting if configured
	local pacct=
	if [ "$PROCESS_ACCOUNTING" = "yes" -a -x $ACCTON ]; then
		$ACCTON
		pacct=kernel_pacct
	fi

	# Write system information
	log_header

	# Package log files
	(cd "$TMPFS_MOUNT"; tar -zcf "$BOOTLOG_DEST" header $pacct *.log)
	umount "$TMPFS_MOUNT"

	# Render the chart if configured (and the renderer is installed)
	[ "$AUTO_RENDER" = "yes" -a -x /usr/bin/bootchart ] && \
		/usr/bin/bootchart -o /var/log/ -f $AUTO_RENDER_FORMAT
}


# Log some basic information about the system.
log_header()
{
	(
		echo "version = $VERSION"
		echo "title = Boot chart for $( hostname | sed q ) ($( date ))"
		echo "system.uname = $( uname -srvm | sed q )"
		if [ -f /etc/gentoo-release ]; then
			echo "system.release = $( sed q /etc/gentoo-release )"
		elif [ -f /etc/SuSE-release ]; then
			echo "system.release = $( sed q /etc/SuSE-release )"
		elif [ -f /etc/debian_version ]; then
			echo "system.release = Debian GNU/$( uname -s ) $( cat /etc/debian_version )" >> "$HEADER"
		elif [ -f /etc/frugalware-release ]; then
			echo "system.release = $( sed q /etc/frugalware-release )"
		else
			echo "system.release = $( sed 's/\\.//g;q' /etc/issue )"
		fi

		# Get CPU count
		local cpucount=$(grep -c '^processor' /proc/cpuinfo)
		if [ $cpucount -gt 1 -a -n "$(grep 'sibling.*2' /proc/cpuinfo)" ]; then
			# Hyper-Threading enabled
			cpucount=$(( $cpucount / 2 ))
		fi
		if grep -q '^model name' /proc/cpuinfo; then
			echo "system.cpu = $( grep '^model name' /proc/cpuinfo | sed q )"\
			     "($cpucount)"
		else
			echo "system.cpu = $( grep '^cpu' /proc/cpuinfo | sed q )"\
			     "($cpucount)"
		fi

		echo "system.kernel.options = $( sed q /proc/cmdline )"
	) >> "$TMPFS_MOUNT/header"
}


if [ $$ = 1 ]; then
	# Started by the kernel.  Start the logger in background and exec
	# init(1).
	IN_INIT="yes"
	echo "Starting bootchart logging"
	start &
	if [ -n "$INIT" ]; then
		exec $INIT
	else
		exec /sbin/init
	fi
fi

case "$1" in
	"init")
		# Started by the init script
		IN_INIT="yes"
		echo "Starting bootchart logging"
		start &
		;;
	"start")
		# Started by the user
		shift
		start $@
		;;
	"stop")
		stop
		;;
	*)
		echo $"Usage: $0 {init|start|stop}"
		;;
esac
