#!/bin/bash

# Syscalltimes systemtap script
# Copyright (C) 2007 IBM Corp.
# Copyright (C) 2011,2018 Red Hat, Inc.
#
# This file is part of systemtap, and is free software.  You can
# redistribute it and/or modify it under the terms of the GNU General
# Public License (GPL); either version 2, or (at your option) any
# later version.

###
### syscalltime - Combination shell/systemtap script to measure system call
###               counts and times.  Can be filtered by process IDs, process
###               names and users.
###

# Filter options
F_PIDSTR="";  F_PID=0   # Filter by process ID
F_EXECSTR=""; F_EXEC=0  # Filter by process name
F_UIDSTR="";  F_UID=0   # Filter by user
FILTER=0                # Any filters specified?

# Print options
P_TOTALS=0              # Print totals
P_VERBOSE_STR=""        # Print verbose build output
P_PROCESS_STR=""        # Run process during stap script invocation

DELIM=","

function usage {
	echo "USAGE: syscalltimes [-n pname]... [-p pid]... [-u username]... [-c 'process']"
	echo "                    [-v]... [-t] [-h]"
	echo "    -n pname     # filter by this process name"
	echo "    -p pid       # filter by this process ID"
	echo "    -u username  # filter by this user"
	echo "    -c 'process' # run 'process' then exit to show totals"
	echo "    -t           # print totals (default with filters: OFF"
	echo "                                 default without filters: ON)"
	echo "    -v           # print verbose output during SystemTap module creation"
	echo "    -h           # print this help text"
	echo ""
	echo "NOTE: This script can take long time to build. Use -v[vv] to monitor"
	echo "      the module creation and build process."
}

# Process options
while getopts n:p:u:c:vth option; do
	case $option in
	n)	let "F_EXEC++"
		F_EXECSTR=$OPTARG$DELIM$F_EXECSTR ;;

	p)	let "F_PID++"
		F_PIDSTR=$OPTARG$DELIM$F_PIDSTR ;;

	u)	uid=`awk -F: '$1 == name {print $3}' name=$OPTARG /etc/passwd`
		if [[ $uid != "" ]]; then
			let "F_UID++"
			F_UIDSTR=$uid$DELIM$F_UIDSTR
		else
			echo "ERROR: Unknown user:" $OPTARG
			let "ERROR++"
		fi ;;

	v)	P_VERBOSE_STR="-v "$P_VERBOSE_STR ;;

	t)	P_TOTALS=1 ;;

	c)	P_PROCESS_STR="-c $OPTARG" ;;

	h|?|*)  usage
		exit 1 ;;
	esac
done

if [[ $ERROR > 0 ]]; then
	exit 1
fi

if [[ $F_EXEC > 0 || $F_PID > 0 || $F_UID > 0 ]]; then
	FILTER=1
fi

echo "Creating and building SystemTap module..."

#
# SystemTap script
#
stap $P_VERBOSE_STR -w "$P_PROCESS_STR" -e '
global starttime, timebycall, timebypid, timebyuid, timebyexec
global f_exec_str, f_pid_str, f_uid_str
global f_exec, f_pid, f_uid
global prt_totals, filter_str

probe begin {
	printf("Collecting data - type Ctrl-C to print output and exit...\n")
	
	# If no filters specified, skip filter processing
	if ('$FILTER' == 0) {
		filter_str = " (unfiltered)"
		prt_totals = 1  // On by default if no filtering
		next
	} else
		filter_str = " (filtered)"

	prt_totals   = '$P_TOTALS'
	if ('$F_EXEC') f_exec_str   = "'$F_EXECSTR'"
	if ('$F_PID') f_pid_str    = "'$F_PIDSTR'"
	if ('$F_UID') f_uid_str    = "'$F_UIDSTR'"

	delim = "'$DELIM'"

	# Process names
	if ('$F_EXEC') {
		pname = tokenize(f_exec_str, delim)
		while (pname != "") {
			f_exec[pname] = 1
			pname = tokenize("", delim)
		}
	}

	# Process IDs
	if ('$F_PID') {
		pid = tokenize(f_pid_str, delim)
		while (pid != "") {
			f_pid[strtol(pid, 10)] = 1
			pid = tokenize("", delim)
		}
	}

	# User IDs
	if ('$F_UID') {
		uid = tokenize(f_uid_str, delim)
		while (uid != "") {
			f_uid[strtol(uid, 10)] = 1
			uid = tokenize("", delim)
		}
	}
}

probe syscall_any {
	starttime[name, tid()] = gettimeofday_ns()
}

probe syscall_any.return {
	# Skip if we have not seen this before
	if (!([name, tid()] in starttime)) next

	delta = gettimeofday_ns() - starttime[name, tid()]

	# Check filters
	if ('$FILTER') {
		target = 0
		if (('$F_PID') && (pid() in f_pid)) {
			timebypid[name, pid()] <<< delta
			target = 1
		}
		if (('$F_UID') && (uid() in f_uid)) {
			timebyuid[name, uid()] <<< delta
			target = 1
		}
		if (('$F_EXEC') && (execname() in f_exec)) {
			timebyexec[name, execname()] <<< delta
			target = 1
		}
	} else
		target = 1

	# Totals
	if (target && prt_totals)
		timebycall[name] <<< delta

	delete starttime[name, tid()]
}

function print_header() {
	printf("%22s %10s %12s %12s %12s %12s\n",
	       "System Call", "Count", "Total ns",
	       "Avg ns", "Min ns", "Max ns")
}

probe end {
	if (prt_totals) {
		printf("\nTimes for all processes%s:\n\n", filter_str)
		print_header()
		foreach (call in timebycall)
			printf("%22s %10d %12d %12d %12d %12d\n", call,
			       @count(timebycall[call]),
			       @sum(timebycall[call]),
			       @avg(timebycall[call]),
		               @min(timebycall[call]),
			       @max(timebycall[call]))
	}

	if ('$F_PID') {
		curpid = -1
		foreach ([call, pid-] in timebypid) {
			if (curpid != pid) {
				curpid = pid
				printf("\nTimes for process ID %d:\n\n", curpid)
				print_header()
			}
			printf("%22s %10d %12d %12d %12d %12d\n", call,
			       @count(timebypid[call, pid]),
			       @sum(timebypid[call, pid]),
			       @avg(timebypid[call, pid]),
			       @min(timebypid[call, pid]),
			       @max(timebypid[call, pid]))
		}
		printf("\n")
	}

	if ('$F_EXEC') {
		curexec = ""
		foreach ([call, exec-] in timebyexec) {
			if (curexec != exec) {
				curexec = exec
				printf("\nTimes for process name %s:\n\n", curexec)
				print_header()
			}
			printf("%22s %10d %12d %12d %12d %12d\n", call,
			       @count(timebyexec[call, exec]),
			       @sum(timebyexec[call, exec]),
			       @avg(timebyexec[call, exec]),
			       @min(timebyexec[call, exec]),
			       @max(timebyexec[call, exec]))
		}
		printf("\n")
	}

	if ('$F_UID') {
		curuid = -1
		foreach ([call, uid-] in timebyuid) {
			if (curuid != uid) {
				curuid = uid
				printf("\nTimes for user ID %d:\n\n", curuid)
				print_header()
			}
			printf("%22s %10d %12d %12d %12d %12d\n", call,
			       @count(timebyuid[call, uid]),
			       @sum(timebyuid[call, uid]),
			       @avg(timebyuid[call, uid]),
			       @min(timebyuid[call, uid]),
			       @max(timebyuid[call, uid]))
		}
		printf("\n")
	}

	delete timebycall
	if ('$F_PID') delete timebypid
	if ('$F_UID') delete timebyuid
	if ('$F_EXEC') delete timebyexec
}'
