#!/bin/bash
# $Id: op_start_25,v 1.9 2003/01/30 17:36:15 movement Exp $
# COPYRIGHT (C) 2000 THE VICTORIA UNIVERSITY OF MANCHESTER and John Levon
# 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.
#

# ensure bash2
if [ "`echo $BASH_VERSION | cut -b1`" -lt 2 ]; then
	exec /bin/bash2 $0 $@
fi

 
# quick and ugly interface
# op_start --help and op_start --list-events have info

# extract the integer field N from --ctr[N]-xxxxxxx
extract_int()
{
	local val=`echo $1 | sed 's,--ctr\([0-9]*\)[-A-Za-z]*,\1,'`

	if test -z "$val"; then 
		echo "Invalid option \"$1\"" >&2
		exit 1
	fi
	if [ ! -d $MOUNT/$val ]; then
		echo "invalid argument $1: bad counter number" >&2
		exit 1
	fi
	echo $val
}

# verbose echo
vecho() {
	if [ "$VERBOSE" == "0" ]; then 
		return;
	fi
	echo $@
}
 
# print help message
do_help() { 
	echo "op_start: usage:
	Module options
	  --buffer-size=num            number of samples in kernel buffer
	  --ctrN-event=name            symbolic event name for ctr N
	  --ctrN-count=val             number of events between samples for ctr N
	  --ctrN-unit-mask=val         unit mask for ctr N (e.g. --ctr0-unit-mask=0xf)
	  --ctrN-kernel=[0|1]          whether to count kernel events for ctr N
	  --ctrN-user=[0|1]            whether to count user events for ctr N
	Allowed counters for N are [$OP_COUNTERS]
	  --pid-filter=pid             Only profile process pid 
	  --pgrp-filter=pgrp           Only profile process tty group pgrp

	Daemon options
	  --separate-samples           separate samples for each distinct application
	  --vmlinux=file               vmlinux kernel image
	  --verbose                    be verbose in the daemon log
	  --kernel-range=start,end     kernel range vma address in hexadecimal

	General options
	  --list-events                list event types and unit masks
	  --help                       this message" >&2
}

load_module() {
	grep oprofilefs /proc/filesystems >/dev/null
	if [ "$?" -ne 0 ]; then
		modprobe oprofile
		if [ "$?" != "0" ]; then
			echo "Couldn't load oprofile.o module" >&2
			exit 1
		fi
		grep oprofile /proc/modules >/dev/null
		if [ "$?" != "0" ]; then
			echo "Couldn't load oprofile.o module" >&2
			exit 1
		fi
	fi
	mkdir /dev/oprofile >/dev/null 2>&1
	grep oprofilefs /etc/mtab >/dev/null
	if test "$?" -ne 0; then
		mount -t oprofilefs nodev /dev/oprofile >/dev/null
	fi
}

# initialise parameters
do_init() {
	# for these three buffer size == 0 means use the default value
	# hard-coded in op_user.h
	BUF_SIZE=0
	IGNORE_MYSELF=0
	VMLINUX=
	PID_FILTER=0
	PGRP_FILTER=0
	VERBOSE=0
	SEPARATE_SAMPLES=0
 
	# as in op_user.h
	DIR="/var/lib/oprofile/"
	LOCK_FILE="/var/lib/oprofile/lock"
	LOG_FILE="$DIR/oprofiled.log"
	SAMPLES_DIR="$DIR/samples/"
	MOUNT="/dev/oprofile"
	DEVICE_FILE="$MOUNT/buffer"

	CPUTYPE=`cat $MOUNT/cpu_type`
	OP_COUNTERS=`ls $MOUNT/ | grep "^[0-9]\+\$" | tr "\n" " "`

	IS_TIMER=0
 
	case "$CPUTYPE" in
		4) 	IS_TIMER=1
			;;
	esac

	if test "$IS_TIMER" -ne 1; then
		# we can now default define individual counter setup variable.
		for f in $OP_COUNTERS ; do
			CTR_USER[$f]=1
			CTR_KERNEL[$f]=1
		done
	fi
} 


# get start and end points of the kernel
get_kernel_range() {
	if test ! -z $KERNEL_RANGE; then
		return;
	fi
	tmp1=`nm $VMLINUX | grep ' A _text'`
	# match start of kernel on ppc64 
	if test -z "$tmp1"; then
		tmp1=`nm $VMLINUX | grep ' T _stext'`
	fi
	tmp2=`nm $VMLINUX | grep ' A _end'`
	if test -z "$tmp1" -o -z "$tmp2"; then
		echo "Couldn't determine kernel start/end" >&2
		echo "Perhaps $VMLINUX is not a proper vmlinux file ?" >&2
		echo "found start as \"$tmp1\", end as \"$tmp2\"" >&2
		exit 1
	fi
	KERNEL_RANGE="`echo $tmp1 | cut -d" " -f 1`,`echo $tmp2 | cut -d" " -f 1`"
}
 
# transform into opcontrol command
print_deprecation() {
	ARGS="$*"

	echo "$ARGS" | grep -- --verbose >/dev/null 2>&1
	if test "$?" -eq 0; then
		USEVERBOSE="--verbose"
	fi
	ARGS=`echo $ARGS | sed 's/--verbose//'`

	DEP_MESSAGE="opcontrol --setup $ARGS && opcontrol --start $USEVERBOSE"
 
	echo "op_start is deprecated. Please use $DEP_MESSAGE" >&2
}
 
# get and check specified options
do_options() {
 
	print_deprecation $@
 
	while [ "$#" -ne 0 ]
	do
		arg=`echo $1 | awk -F= '{print $1}'`
		val=`echo $1 | awk -F= '{print $2}'`
 
		if test "$IS_TIMER" -eq 1; then
			case "$arg" in
				--ctr*-unit-mask|--ctr*-event|--ctr*-count|--ctr*-user|--ctr*-kernel)
					echo "Cannot use option $arg in timer mode."
					exit 1;
					;;
			esac
		fi
 
		case "$arg" in
			--buffer-size)
				BUF_SIZE=$val
				;;
			--ctr*-unit-mask)
				CTR_UM[`extract_int $arg`]=$val
				if (($? != 0)); then exit 1; fi
				;;
			--ctr*-event)
				CTR_EVENT[`extract_int $arg`]=$val
				if (($? != 0)); then exit 1; fi
				;;
			--ctr*-count)
				CTR_COUNT[`extract_int $arg`]=$val
				if (($? != 0)); then exit 1; fi
				;;
			--ctr*-user)
				CTR_USER[`extract_int $arg`]=$val
				if (($? != 0)); then exit 1; fi
				;;
			--ctr*-kernel)
				CTR_KERNEL[`extract_int $arg`]=$val
				if (($? != 0)); then exit 1; fi
				;;
			--separate-samples)
				SEPARATE_SAMPLES=1
				;;
			--vmlinux)
				VMLINUX=$val
				;;
			--kernel-range)
				KERNEL_RANGE=$val
				;;
			--pid-filter)
				PID_FILTER=$val
				;;
			--pgrp-filter)
				PGRP_FILTER=$val
				;;
			--verbose)
				VERBOSE=1
				;;
			--help)
				do_help
				exit 0
				;;

			--list-events)
				exec op_help
				;;
			*)
				echo "Unknown option \"$arg\". See op_start --help" >&2
				exit 1
				;; 
		esac
		shift
	done

	if test "$IS_TIMER" -ne 1; then
		one_enabled=0
		for f in $OP_COUNTERS ; do
			if [[ ${#CTR_EVENT[$f]} != 0 ]]; then
				CTR_EVENT_VAL[$f]=`op_help ${CTR_EVENT[$f]}`
				if [ "$?" != 0 ] || [ -z "${CTR_EVENT_VAL[$f]}" -a ! -z "${CTR_EVENT[$f]}" ]; then
					echo "Unknown event \"${CTR_EVENT[$f]}\"" >&2
					exit 1
				fi
				if [ -z "${CTR_COUNT[$f]}" ]; then
					echo "Event but no count specified for counter $f" >&2
					exit 1
				fi
				one_enabled=1
			else 
				if [ ! -z "${CTR_COUNT[$f]}" ]; then
					echo "Count but no event specified for counter $f" >&2
					exit 1
				fi
			fi
		done
	 
		if [ "$one_enabled" == "0" ]; then
			echo "You haven't specified what events you would like to count, e.g." >&2
			echo "op_start ... --ctr0-event=CPU_CLK_UNHALTED --ctr0-count=600000" >&2
			echo "Enter op_start --help for full options" >&2
			exit 1
		fi
	fi

	if [ -z "$VMLINUX" ]; then
		echo "No vmlinux file specified. You must specify the correct vmlinux file, e.g." >&2
		echo "op_start --vmlinux=/path/to/vmlinux" >&2
		echo "Enter op_start --help for full options" >&2
		exit 1
	fi

	if [ ! -f "$VMLINUX" ]; then
		echo "The specified vmlinux file \"$VMLINUX\" doesn't exist." >&2
		exit 1
	fi

	get_kernel_range
 
	vecho "Parameters used:"
	vecho "CPUTYPE $CPUTYPE"
	if [ $BUF_SIZE != 0 ]; then
		vecho "BUF_SIZE $BUF_SIZE"
	else
		vecho "BUF_SIZE default value"
	fi;
	if test "$IS_TIMER" -ne 1; then
		for f in $OP_COUNTERS ; do
			vecho "CTR${f}_EVENT ${CTR_EVENT[$f]}"
			vecho "CTR${f}_COUNT ${CTR_COUNT[$f]}"
			vecho "CTR${f}_UM ${CTR_UM[$f]}"
			vecho "CTR${f}_USER ${CTR_USER[$f]}"
			vecho "CTR${f}_KERNEL ${CTR_KERNEL[$f]}"
		done
	fi
 
	vecho "IGNORE_MYSELF $IGNORE_MYSELF"
	vecho "SEPARATE_SAMPLES $SEPARATE_SAMPLES"
	vecho "VMLINUX $VMLINUX"
	vecho "KERNEL_RANGE $KERNEL_RANGE"
}

# stop any existing daemon
do_stop() {
	: # FIXME op_stop
}

# setup and start module
do_setup() {
	if [ ! -d "$DIR" ]; then
	       mkdir -p "$DIR"
	       if [ "$?" != "0" ]; then
		       echo "Couldn't mkdir -p $DIR" >&2
		       exit 1
	       fi
	       chmod 755 "$DIR"
	fi

	>$LOG_FILE

	if [ ! -d "$SAMPLES_DIR" ]; then
		mkdir -p "$SAMPLES_DIR"
		if [ "$?" != "0" ]; then
			echo "Couldn't mkdir -p $SAMPLES_DIR" >&2
			exit 1
		fi
		chmod 755 "$SAMPLES_DIR"
	fi
}
 
# initialise sysctl parameters
do_sysctl_setup() {
	if [ $BUF_SIZE != 0 ]; then
		echo $BUF_SIZE >$MOUNT/buffer_size
	fi

	if test "$IS_TIMER" -ne 1; then
		# Necessary in this case :
		# op_start ctr0-on ctr1-on then op_start ctr0-on
		for f in $OP_COUNTERS ; do
			echo 0 >$MOUNT/$f/enabled
			echo 0 >$MOUNT/$f/event
			echo 0 >$MOUNT/$f/count
		done

		for f in $OP_COUNTERS ; do
			if [ "${CTR_EVENT[$f]}" != "" ]; then
				echo 1 >$MOUNT/$f/enabled
				echo ${CTR_COUNT[$f]} >$MOUNT/$f/count
				echo ${CTR_KERNEL[$f]} >$MOUNT/$f/kernel
				echo ${CTR_USER[$f]} >$MOUNT/$f/user
				echo ${CTR_UM[$f]} >$MOUNT/$f/unit_mask
				echo ${CTR_EVENT_VAL[$f]} >$MOUNT/$f/event
			fi
		done
	fi
}
 
# start the daemon
do_start() {
 
	if test -f $LOCK_FILE; then
		kill -s 0 `cat $LOCK_FILE` 2>/dev/null
		if test "$?" -eq 0; then
			echo "oprofiled appears to be running already." >&2
			echo "delete $LOCK_FILE if this is not the case." >&2
		fi
	fi
 
	OPD_ARGS="--vmlinux=$VMLINUX --kernel-range=$KERNEL_RANGE \
		--separate-samples=$SEPARATE_SAMPLES \
		--pid-filter=$PID_FILTER --pgrp-filter=$PGRP_FILTER"

	if [ "$VERBOSE" = "1" ]; then
		OPD_ARGS="$OPD_ARGS --verbose"
	fi

	cpu_speed=`grep "cpu MHz" /proc/cpuinfo | tail -1 | awk -F": " '{print $2}'`
	OPD_ARGS="$OPD_ARGS --cpu-speed=$cpu_speed"

	vecho "cpu speed (estimation) : $cpu_speed"

	if test ! -f $LOCK_FILE; then
		vecho "executing oprofiled $OPD_ARGS"
		oprofiled $OPD_ARGS
	 
		COUNT=0
		while ! test -f "$DIR/lock"
		do
			sleep 1
			COUNT=`expr $COUNT + 1`
			if [ "$COUNT" -eq 30 ]; then
				echo "Couldn't start oprofiled." >&2
				echo "Check the log file \"$LOG_FILE\" and /var/log/messages" >&2
				exit 1
			fi
		done
	fi
 
	echo 1 >$MOUNT/enable
 
	echo "Profiler running."
}
 
# main

if [ "$UID" != "0" ]; then
	echo "Must be root to start oprofile." >&2
	exit 1
fi
 
load_module
do_init
do_options $@
do_stop
do_setup
do_sysctl_setup
do_start
