#!/bin/bash
#
# Bootchart logger script
# Ziga Mahkovec  <ziga.mahkovec@klika.si>
# Michael Meeks  <michael.meeks@novell.com>
#
# This script is used for data collection for the bootchart2
# boot performance visualization tool.
#
# To profile the boot process, bootchartd should be called instead of
# /sbin/init.  Modify the kernel command line to include:
# 
# init=/sbin/bootchartd initcall_debug printk.time=y quiet
#
# bootchartd will then start itself in background and exec /sbin/init
# (or an alternative init process if specified using bootchart_init=)
#
# To profile a running system, run:
# $ /sbin/bootchartd start; sleep 30; /sbin/bootchartd stop
#

# Whack everything in a handy tmpfs: /dev ...
JAIL_INITRD="/dev/.bootchart"
JAIL_MAIN="/tmp/.bootchart"

# we need to find our tools
PATH="/sbin:/bin:/usr/sbin:/usr/bin:$PATH"

# Read configuration.
CONF="/etc/bootchartd.conf"
if [ -f $PWD/bootchartd.conf ]; then
	. $PWD/bootchartd.conf
elif [ -f $CONF ]; then
        . $CONF
else
        echo "$CONF missing"
        exit 1
fi

# 'which' rather fails to work, with no PATH set
if [ -x "/sbin/accton" ]; then
    ACCTON=/sbin/accton
elif [ -x "/usr/sbin/accton" ]; then
    ACCTON=/usr/sbin/accton
else
    echo "No accton - though it is configured";
    PROCESS_ACCOUNTING="no"
fi

# Start the boot logger.
start()
{
	# we have no control over the (usually rather mindless) scripts
	# that will mount, and populate /dev - however we have to wait
	# for them; otherwise we can't share the /dev tmpfs for our logs
	# and have to modify each system to mount --move our file-system.
	while [ ! -e /dev/console ] || [ ! -e /dev/random ]; do
	    usleep 5000
	done

	if [ ! -d $JAIL ]; then
		mkdir -p $JAIL
	fi
	cd $JAIL

	# use dmsg for debugging
	if [ ! -c kmsg ]; then
		mknod kmsg c 1 11
	fi
	echo "bootchartd started" >> kmsg

	# Mount a copy of /proc just for us ...
	if [ ! -d "proc" ]; then
		mkdir proc
	fi
	if [ ! -f "proc/stat" ]; then
		mount -n -t proc none proc
	fi

	# If we were started during the initrd, -but- we have no
	# init=/sbin/bootchart, then there will be no-one to stop
	# the collector will just keep
	# running with no-one to stop it, so bail here.
	if [ "$JAIL" == "$JAIL_INITRD" ]; then
	    if ! /bin/grep -q init=/sbin/bootchartd proc/cmdline; then
		echo "aborting initrd bootchart setup - init not set."
		umount proc
		return
	    fi
	fi

	# setup the jail so we can run the collector inside it [ why bother !? ]
	if [ -n "$IN_INIT" ]; then
		echo "bootchartd init" >> kmsg
		if [ "`uname -m`" = "x86_64" ]
		then
			mkdir -p $JAIL/lib64
			cp /lib64/ld-linux-x86-64.so.* /lib64/libc.so.* $JAIL/lib64
		else
			mkdir -p $JAIL/lib
			cp /lib/ld-linux.so.* /lib/libc.so.* $JAIL/lib
		fi

		mkdir -p $JAIL/lib/bootchart
		cp /lib/bootchart/bootchart-collector $JAIL/lib/bootchart
	fi

	# Enable process accounting if configured
	if [ ! -f kernel_pacct ]; then
		if [ "$PROCESS_ACCOUNTING" = "yes" ]; then
			> kernel_pacct
			$ACCTON kernel_pacct
			echo "bootchartd pacct started" >> kmsg
		fi
	fi
    
	# Ensure we don't start ourselves a second time during init
	if [ -e proc_stat.log ]; then
		echo "bootchart collector already running" >> kmsg

	# Otherwise start in a chroot jail
	elif [ -n "$IN_INIT" ]; then
		/bin/chroot $JAIL /lib/bootchart/bootchart-collector -p proc $SAMPLE_HZ

	# Otherwise, manually launched to profile something
	else
		/lib/bootchart/bootchart-collector -p proc $SAMPLE_HZ &

		if [ "$#" -gt 0 ]; then
			# If a command was passed, run it
			# (used for profiling specific applications)
			echo "profile.process = $( basename $1 )" >> header
			$@
			stop
#		else
#			manual start/stop - wait for a 'stop'
		fi
	fi
}

# 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="kdm_greet xterm konsole metacity mutter compiz"
 	# Don't exit until *all* the mandatory procs are running. Only used if exit_proc
 	# is unset.
 	local mandatory_procs="nautilus mutter"

	# Wait for /proc first - without it we have issues
	while [ ! -e /proc/cmdline ]; do
	    usleep 5000
	done

	# 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 [ "x$runlevel" = "x2" -o "x$runlevel" = "x3" -o "$early_login" = "yes" ]; then
		exit_proc="mingetty agetty rungetty getty"
	fi
	while true; do
		if [ -n "$exit_proc" -a -n "$( pidof $exit_proc )" ]; then
			# give an unambiguous settle afterwards - so we get
			# more post-login data for slow systems
			/bin/sleep 20

			# Write / flush the log files
			stop
			return
		fi
		usleep 200000
	done;
}

#
# This is all madness; the collector should store it's sample data
# in memory, and write it out only when triggered: after the
# system is up. Then having another mount over the top of it's
# root is no issue.
#
find_jail()
{
    if [ -d $JAIL_INITRD ]; then
	if [ ! -d $JAIL_MAIN ] || [ $JAIL_INITRD -nt $JAIL_MAIN ]; then
	    JAIL=$JAIL_INITRD;
	    return
	fi
    fi
    JAIL=$JAIL_MAIN;
}

# Stop the boot logger.
# Some final log files are created and then all log files
# from the tmpfs are packaged and stored in $BOOTLOG_DEST.
stop()
{
	pkill -f /lib/bootchart/bootchart-collector
	sleep 2
	# try harder in case it hung
	pkill -9 -f /lib/bootchart/bootchart-collector

	# Stop process accounting if configured
	local pacct=
	if [ "$PROCESS_ACCOUNTING" = "yes" ]; then
		accton
		pacct=kernel_pacct
	fi

	find_jail
	if [ ! -d $JAIL ]; then
		echo "Can't find bootchart output in $JAIL - aborting"
		exit 1
	fi
	cd $JAIL

	# Write system information
	log_header

	# get kernel messages for kernel init logging
	dmesg > dmesg

	# cleanup
	rm -f kmsg
	umount proc
	rmdir proc

	tar -zcf "$BOOTLOG_DEST" header dmesg $pacct *.log
	rm -f header dmesg $pacct *.log
	rm -Rf $JAIL/lib
	cd ..
	rmdir $JAIL

	# Render the chart if configured (and the renderer is installed)
	if [ "$AUTO_RENDER" = "yes" -a -x /usr/bin/pybootchartgui ]; then
                cd $AUTO_RENDER_DIR
		/usr/bin/pybootchartgui -o "$AUTO_RENDER_DIR"/bootchart.$AUTO_RENDER_FORMAT -f $AUTO_RENDER_FORMAT "$BOOTLOG_DEST"
        fi
}


# Log some basic information about the system.
log_header()
{
    if [ -x "/usr/bin/dpkg-query" ]; then
        version="$(dpkg-query -f'${Version}' -W bootchart)"
    else
        version="$(rpm -q bootchart --queryformat '%{VERSION}')"
    fi
    (	echo "version = $version"
	echo "title = Boot chart for $(hostname) ($(date))"
	echo "system.uname = $(uname -srvm)"
	echo "system.release = $(lsb_release -sd)"
	echo "system.cpu = $(grep '^model name' proc/cpuinfo)"\
         "($(grep -c '^model name' proc/cpuinfo))"
	echo "system.kernel.options = $(sed q proc/cmdline)"
    ) > header
}

if [ $$ -eq 1 ]; then
        # Either started by the kernel - in which case, we start the
        # logger in background and exec init [ re-using this pid (1) ]
        # Or - started after the initrd has completed, in which case
        # we try to do nothing much.
	IN_INIT="yes"
	echo "Starting bootchart logging"

	JAIL=$JAIL_INITRD

	# Are we running in the initrd ?
	if [ ! -e /dev/random ]; then
		start &
	else # running inside the main system
		echo "acute oddness - why can't we detect this !?" >> /dev/kmsg
		ls -al /dev >> /dev/kmsg
		if [ -c $JAIL_INITRD/kmsg ]; then
			echo "bootchart - has kmsg" >> /dev/kmsg
		fi
		if [ -d $JAIL_INITRD ]; then
			echo "bootchart: start wait for completion" >> /dev/kmsg
			wait_boot &
		else # perhaps we had no initrd
			JAIL=$JAIL_MAIN
			echo "bootchart: no initrd used; starting" >> /dev/kmsg
			start &
			# wait a little, until the collector is going, before allowing
			# the rest of the system to charge ahead, so we catch it
			usleep 500000
			echo "bootchart continuing boot" >> /dev/kmsg
			wait_boot &
		fi
	fi
	
	# Optionally, an alternative init(1) process may be specified using
	# the kernel command line (e.g. "bootchart_init=/sbin/initng")
	init="/sbin/init"
	for i in $@; do
		if [ "${i%%=*}" = "bootchart_init" ]; then
			init="${i#*=}"
			break
		fi
                if [ "${i%%=*}" = "init" ]; then
			_init=${i#*=}
			if test "$_init" != "/sbin/bootchartd"; then
                           init="$_init"
                        fi
                        break
                fi
	done
	export PATH=$OLDPATH

	# switch to - either the initrd's init, or the main system's
	exec $init $*
fi

find_jail
case "$1" in
	"start")
		# Started by the user
		shift
		start $@
		;;
	"wait")
		# Wait for boot
		wait_boot
		;;
	"stop")
		stop
		;;
	*)
		echo $"Usage: $0 {init|start|stop}"
		;;
esac

