#!/bin/bash
# $Id: op_start,v 1.8 2002/07/29 23:40:23 phil_e 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

SYSCTL=do_sysctl

#  A replacement function for the sysctl (procps package) utility which is
# missing on some distribution (e.g. slack 7.0). 
#  Handle only the -w option of sysctl.
do_sysctl()
{
	if [ x$1 != x"-w" ]; then
		echo "$0 unknown option" >&2
		exit 1
	fi

	shift

	arg=`echo $1 | awk -F= '{print $1}'`
	val=`echo $1 | awk -F= '{print $2}'`

	dev_name=`echo $arg | tr . /`

	if [ ! -f /proc/sys/$dev_name ]; then
		echo "/proc/sys/$dev_name does not exist or is not a regular file" >&2
		exit 1
	fi
	echo $val > /proc/sys/$dev_name
}

# 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 (($val >= $OP_MAX_COUNTERS || $val < 0)); 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
	  --hash-table-size=num        number of entries in kernel hash table
	  --note-table-size	       number of notes in kernel notes buffer
	  --kernel-only=[0|1]          profile only the kernel
	  --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
	  --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 range for N is [0-$MAX_COUNTER]
	  --rtc-value=val              RTC value (only if RTC is being used)
	  --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 oprof /proc/devices >/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
} 

# initialise parameters
do_init() {
	# for these three buffer size == 0 means use the default value
	# hard-coded in op_user.h
	BUF_SIZE=0
	HASH_SIZE=0
	NOTE_SIZE=0
	KERNEL_ONLY=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/"
	DEVICE_FILE="$DIR/opdev"
	NOTE_DEVICE_FILE="$DIR/opnotedev"
	HASH_MAP_DEVICE_FILE="$DIR/ophashmapdev"

	CPUTYPE=`cat /proc/sys/dev/oprofile/cpu_type`

	IS_RTC=0
 
	case "$CPUTYPE" in
		0|1|2)
			OP_MAX_COUNTERS=2
			MAX_COUNTER=1
			;;
		3|5)
			OP_MAX_COUNTERS=4
			MAX_COUNTER=3
			;;
		4) 	IS_RTC=1
			;;
		*)
			echo "Unknown cpu type \"$CPUTYPE\"" >&2
			exit 1
			;;
	esac

	if test "$IS_RTC" -ne 1; then
		# we can now default define individual counter setup variable.
		f=0
		while (( $f < $OP_MAX_COUNTERS )); do
			CTR_USER[$f]=1
			CTR_KERNEL[$f]=1
			f=$(($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'`
	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`"
}
 
# get and check specified options
do_options() {
	while [ "$#" -ne 0 ]
	do
		arg=`echo $1 | awk -F= '{print $1}'`
		val=`echo $1 | awk -F= '{print $2}'`
 
		if test "$IS_RTC" -eq 1; then
			case "$arg" in
				--ctr*-unit-mask|--ctr*-event|--ctr*-count|--ctr*-user|--ctr*-kernel)
					echo "Cannot use option $arg in RTC mode."
					exit 1;
					;;
			esac
		fi
 
		case "$arg" in
			--buffer-size)
				BUF_SIZE=$val
				;;
			--hash-table-size)
				HASH_SIZE=$val
				;;
			--kernel-only)
				if [ "$val" != "" ]; then
					KERNEL_ONLY=$val
				else
					KERNEL_ONLY=1
				fi
				;;
			--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
				;;
			--rtc-value)
				RTC_VALUE=$val
				;;
			--separate-samples)
				SEPARATE_SAMPLES=1
				;;
			--note-table-size)
				NOTE_SIZE=$val
				;;
			--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_RTC" -ne 1; then
		one_enabled=0
		f=0
		while (( $f < $OP_MAX_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
			f=$(($f+1))
		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
	else
		if test -z "$RTC_VALUE"; then
			echo "No --rtc-value specified on an RTC-only CPU." >&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 [ $HASH_SIZE != 0 ]; then
		vecho "HASH_SIZE $HASH_SIZE"
	else
		vecho "HASH_SIZE default value"
	fi;
	if [ $BUF_SIZE != 0 ]; then
		vecho "BUF_SIZE $BUF_SIZE"
	else
		vecho "BUF_SIZE default value"
	fi;
	if [ $NOTE_SIZE != 0 ]; then
		vecho "NOTE_SIZE $NOTE_SIZE"
	else
		vecho "NOTE_SIZE default value"
	fi;
	if test "$IS_RTC" -ne 1; then
		f=0
		while (( $f < $OP_MAX_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]}"
			f=$(($f+1))
		done
	else
		vecho "RTC_VALUE $RTC_VALUE"
	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() {
	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 [ -c "$DEVICE_FILE" ]; then
		vecho "Removing $DEVICE_FILE"
		rm "$DEVICE_FILE"
	fi

	if [ -c "$NOTE_DEVICE_FILE" ]; then
		vecho "Removing $NOTE_DEVICE_FILE"
		rm "$NOTE_DEVICE_FILE"
	fi
 
	if [ -c "$HASH_MAP_DEVICE_FILE" ]; then
		vecho "Removing $HASH_MAP_DEVICE_FILE"
		rm "$HASH_MAP_DEVICE_FILE"
	fi

	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

	MAJOR_NR=`grep oprof /proc/devices | awk '{print $1}'`

	vecho "Doing mknod $DEVICE_FILE"
	mknod "$DEVICE_FILE" c $MAJOR_NR 0
	if [ "$?" != "0" ]; then
		echo "Couldn't mknod $DEVICE_FILE" >&2
		exit 1
	fi
	chmod 700 "$DEVICE_FILE"
	 
	vecho "Doing mknod $NOTE_DEVICE_FILE"
	mknod "$NOTE_DEVICE_FILE" c $MAJOR_NR 2
	if [ "$?" != "0" ]; then
		echo "Couldn't mknod $NOTE_DEVICE_FILE" >&2
		exit 1
	fi
	chmod 700 "$NOTE_DEVICE_FILE"
 
	vecho "Doing mknod $HASH_MAP_DEVICE_FILE"
	mknod "$HASH_MAP_DEVICE_FILE" c $MAJOR_NR 1
	if [ "$?" != "0" ]; then
		echo "Couldn't mknod $HASH_MAP_DEVICE_FILE" >&2
		exit 1
	fi
	chmod 700 "$HASH_MAP_DEVICE_FILE"
}
 
# initialise sysctl parameters
do_sysctl_setup() {
	if [ $HASH_SIZE != 0 ]; then
		$SYSCTL -w dev.oprofile.hashsize=$HASH_SIZE
	fi
	if [ $BUF_SIZE != 0 ]; then
		$SYSCTL -w dev.oprofile.bufsize=$BUF_SIZE
	fi
	if [ $NOTE_SIZE != 0 ]; then
		$SYSCTL -w dev.oprofile.notesize=$NOTE_SIZE
	fi
	$SYSCTL -w dev.oprofile.kernel_only=$KERNEL_ONLY

	if test "$IS_RTC" -ne 1; then
		# Necessary in this case :
		# op_start ctr0-on ctr1-on then op_start ctr0-on
		f=0
		while (( $f < $OP_MAX_COUNTERS )); do
			$SYSCTL -w dev.oprofile.$f.enabled=0 >/dev/null
			$SYSCTL -w dev.oprofile.$f.event=0 >/dev/null
			f=$(($f+1))
		done

		f=0
		while (( $f < $OP_MAX_COUNTERS )); do
			if [ "${CTR_EVENT[$f]}" != "" ]; then
				$SYSCTL -w dev.oprofile.$f.enabled=1
				$SYSCTL -w dev.oprofile.$f.count=${CTR_COUNT[$f]}
				$SYSCTL -w dev.oprofile.$f.kernel=${CTR_KERNEL[$f]}
				$SYSCTL -w dev.oprofile.$f.user=${CTR_USER[$f]} 
				$SYSCTL -w dev.oprofile.$f.unit_mask=${CTR_UM[$f]} 
				$SYSCTL -w dev.oprofile.$f.event=${CTR_EVENT_VAL[$f]}
			fi
			f=$(($f+1))
		done
	else
		$SYSCTL -w dev.oprofile.rtc_value=$RTC_VALUE
	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
			exit 1
		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"

	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
 
	echo "Daemon started. Remember you may need to use op_dump"
	echo "to retrieve profile data"
}
 
# 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
