#!/bin/sh -u

parseoptions() {
    trace_reads=false
    trace_writes=false
    trace_writepages=false
    pid_view=false

    while [ $# -ge 1 ]
    do
	case $1 in
	    -r)
		trace_reads=true
		;;
	    -w)
		trace_writes=true
		;;
	    -p)
		trace_writepages=true
		;;
	    -v)
		pid_view=true
		;;
	    *)
		usage
		;;
	    esac
	shift
    done
}

usage() {
    echo "Usage: $0 [-r|-w|-p|-v]"
    exit 1
}

getmodel() {
    model=`adb shell getprop ro.product.name`
    # Releases are inconsistent with various trailing characters, remove them all
    model=`echo $model | sed 's/[ \t\r\n]*$//' `
    echo Found $model Device

    case $model in
	aosp_gobo | gobo | gobo_512)
	    get_go_devnames
            ;;
	marlin | sailfish | walleye | taimen | blueline | crosshatch)
	    get_marlin_sailfish_devnames
	    ;;
	angler)
	    get_angler_devnames
	    ;;
	bullhead)
	    get_bullhead_devnames
	    ;;
	volantis | volantisg)
	    get_volantis_devnames
	    ;;
	*)
	    echo Unknown Device $model -- trying Pixel config
	    get_marlin_sailfish_devnames
	    ;;
    esac
}

get_go_devnames () {
    # Hardcoding all of the mmcblk0 device for now
    block_device=mmcblk0
    bdev_set=true
}

get_volantis_devnames() {
    bdev_set=true
    block_device=mmcblk0
}

get_bullhead_devnames() {
    bdev_set=true
    block_device=mmcblk0
}

get_marlin_sailfish_devnames() {
    bdev_set=true
    block_device=sda
}

get_angler_devnames () {
    # Get the underlying bdev from the "by-name" mapping
    block_device=`adb shell 'find /dev/block/platform -name by-name | xargs ls -l' | grep system | awk '{ print $10 }' `
    # extract the last component of the absolute device pathname we got above
    block_device=`echo $block_device | awk 'BEGIN { FS ="/" } ; { print $4 }' | sed 's/p.*//g' `
    bdev_set=true
}

disk_stats_before() {
    if [ $bdev_set == true ]; then
	DISKSTATS=`adb shell 'cat /proc/diskstats' | fgrep -w $block_device `
	# Get BEFORE read stats for bdev
	BEFORE_RD_IOS=`echo $DISKSTATS | awk '{ print $4 }' `
	BEFORE_RD_SECTORS=`echo $DISKSTATS | awk '{ print $6 }' `
	# Get BEFORE write stats for bdev
	BEFORE_WR_IOS=`echo $DISKSTATS | awk '{ print $8 }' `
	BEFORE_WR_SECTORS=`echo $DISKSTATS | awk '{ print $10 }' `
    fi
    if [ $f2fs_fs == 1 ] ; then
	adb shell 'mount -o remount,background_gc=off /data'
	F2FS_GC_SEGMENTS_BEFORE=`adb shell 'cat /sys/kernel/debug/f2fs/status' | grep segments | egrep 'data|node' | awk '{ segments += $5 } END { print segments }' `
    fi
}

disk_stats_after() {
    if [ $bdev_set == true ]; then
	DISKSTATS=`adb shell 'cat /proc/diskstats' | fgrep -w $block_device `
	# Get AFTER read stats for bdev
	AFTER_RD_IOS=`echo $DISKSTATS | awk '{ print $4 }' `
	AFTER_RD_SECTORS=`echo $DISKSTATS | awk '{ print $6 }' `
	# Get BEFORE write stats for bdev
	AFTER_WR_IOS=`echo $DISKSTATS | awk '{ print $8 }' `
	AFTER_WR_SECTORS=`echo $DISKSTATS | awk '{ print $10 }' `
    fi
    if [ $f2fs_fs == 1 ] ; then
	F2FS_GC_SEGMENTS_AFTER=`adb shell 'cat /sys/kernel/debug/f2fs/status' | grep segments | egrep 'data|node' | awk '{ segments += $5 } END { print segments }' `
	adb shell 'mount -o remount,background_gc=on /data'
    fi
}

disk_stats_delta_rd() {
    file_data_KB=$1
    if [ $bdev_set == true ]; then
	# Sectors to KB
	READ_KB=`expr $AFTER_RD_SECTORS - $BEFORE_RD_SECTORS`
	READ_KB=`expr $READ_KB / 2`
	echo "Total (ALL) Read KB $block_device = "$READ_KB
	BLOCK_MINUS_FILE=`expr $READ_KB - $file_data_KB`
	echo "READ DELTA: Total Blockdev Reads KB - Total File Data Reads KB = "$BLOCK_MINUS_FILE KB
	echo "Total (ALL) Read IOs $block_device = "`expr $AFTER_RD_IOS - $BEFORE_RD_IOS`
    fi
}

disk_stats_delta_wr() {
    file_data_KB=$1
    if [ $bdev_set == true ]; then
	# Sectors to KB
	WRITE_KB=`expr $AFTER_WR_SECTORS - $BEFORE_WR_SECTORS`
	WRITE_KB=`expr $WRITE_KB / 2`
	BLOCK_MINUS_FILE=`expr $WRITE_KB - $file_data_KB`
	echo "WRITE DELTA: Total Blockdev Writes KB - Total File Data Writes KB = "$BLOCK_MINUS_FILE KB
	echo "Total (ALL) Write IOs $block_device = "`expr $AFTER_WR_IOS - $BEFORE_WR_IOS`
    fi
    if [ $f2fs_fs == 1 ] ; then
	F2FS_GC_SEGMENTS_DELTA=`expr $F2FS_GC_SEGMENTS_AFTER - $F2FS_GC_SEGMENTS_BEFORE`
	F2FS_GC_KB_DELTA=`expr $F2FS_GC_SEGMENTS_DELTA \\* 2048`
    fi
}

# For good measure clean up traces and reenable traces
clean_up_tracepoints() {
    # This is a good point to check if the Android FS tracepoints are enabled in the
    # kernel or not
    tracepoint_exists=`adb shell 'if [ -d /sys/kernel/debug/tracing/events/android_fs ]; then echo 0; else echo 1; fi' `
    if [ $tracepoint_exists == 1 ]; then
	echo "Android FS tracepoints not enabled in kernel. Exiting..."
	exit 1
    fi
    adb shell 'echo 0 > /sys/kernel/debug/tracing/tracing_on'
    adb shell 'echo 0 > /sys/kernel/debug/tracing/trace'
    if [ $trace_reads == true ]; then
	adb shell 'echo 1 > /sys/kernel/debug/tracing/events/android_fs/android_fs_dataread_start/enable'
    fi
    if [ $trace_writes == true ]; then
	adb shell 'echo 1 > /sys/kernel/debug/tracing/events/android_fs/android_fs_datawrite_start/enable'
    fi
    if [ $f2fs_fs == 1 ] ; then
	if [ $trace_writepages == true ]; then
	    adb shell 'echo 1 > /sys/kernel/debug/tracing/events/android_fs/android_fs_writepages/enable'
	fi
    fi
    adb shell 'echo 1 > /sys/kernel/debug/tracing/tracing_on'
}

# stream trace out of trace_pipe
# Start this in the background ('&')
streamtrace_start() {
    adb shell cat /sys/kernel/debug/tracing/trace_pipe > trace_saved
}

# When signal is received, the trace_pipe reader will get killed
# Call this (just to make sure anyway)
streamtrace_end() {
    ps_line=`ps -ef | grep trace_pipe | grep adb `
    if [ $? == 0 ]; then
	echo Killing `echo $ps_line | awk '{s = ""; for (i=8; i <= NF ; i++) s = s $i " "; print s}' `
	kill `echo $ps_line | awk '{print $2}' `
    fi
}

copyout_trace() {
    streamtrace_end
    if [ $trace_reads == true ]; then
	adb shell 'echo 0 > /sys/kernel/debug/tracing/events/android_fs/android_fs_dataread_start/enable'
    fi
    if [ $trace_writes == true ]; then
	adb shell 'echo 0 > /sys/kernel/debug/tracing/events/android_fs/android_fs_datawrite_start/enable'
    fi
    if [ $f2fs_fs == 1 ] ; then
	if [ $trace_writepages == true ]; then
	    adb shell 'echo 0 > /sys/kernel/debug/tracing/events/android_fs/android_fs_writepages/enable'
	fi
    fi
    adb shell 'echo 0 > /sys/kernel/debug/tracing/tracing_on'
}

prep_tracefile_common() {
    cp trace_saved $infile
    # Strip away all the extraneous stuff first
    fgrep $1 $infile | sed 's/^.* \[.*\] //' | sed s/://g | sed s/,//g > foo
    mv foo $infile
}

prep_tracefile_rd() {
    prep_tracefile_common android_fs_dataread
    # Strip away unnecessary stuff so we can compute latencies easily
    fgrep android_fs_dataread_start $infile > foo0
    # Throw away everything upto and including android_fs_dataread:
    cat foo0 | sed -n -e 's/^.*android_fs_dataread_start //p' > foo1
    mv foo1 $infile
    # At this stage, $infile should the following format :
    # entry_name <filename> offset <offset> bytes <bytes> cmdline <cmdline> pid <pid> i_size <i_size> ino <ino>
    rm foo0
}

prep_tracefile_writepages() {
    prep_tracefile_common android_fs_writepages
    # Throw away everything up to and including android_fs_writepages_start:
    cat $infile | sed -n -e 's/^.*android_fs_writepages //p' > foo1
    mv foo1 $infile
    # At this stage, $infile should the following format :
    # entry_name <filename> bytes <bytes> ino <ino>
}

# Latencies not supported for Writes. 'Write End' is just when the data has been
# written back to page cache.
prep_tracefile_wr() {
    prep_tracefile_common android_fs_datawrite
    fgrep android_fs_datawrite_start $infile > foo0
    # Throw away everything upto and including android_fs_datawrite:
    cat foo0 | sed -n -e 's/^.*android_fs_datawrite_start //p' > foo1
    mv foo1 $infile
    # At this stage, $infile should the following format :
    # entry_name <filename> offset <offset> bytes <bytes> cmdline <cmdline> pid <pid> i_size <i_size> ino <ino>
    rm foo0
}

get_unique_files_rw() {
    # Sort first by filename, then by pid
    cat $infile | sed s/,//g  | sort -d -k2,2 -k8,8 > foo1
    mv foo1 $infile
    # $infile now contains lines sorted by <filename, pid>
    # How many unique files are there ?
    cat $infile | awk '{ print $2 }' > foo1
    cat foo1 | uniq > uniq_files
    rm foo1
}

get_unique_files_writepages() {
    cat $infile | sed s/,//g  | sort -d -k2,2 > foo1
    # $infile now contains lines sorted by <filename>
    mv foo1 $infile
    # How many unique files are there ?
    cat $infile | awk '{ print $2 }' > foo1
    cat foo1 | uniq > uniq_files
    rm foo1
}

get_unique_pids_byfile() {
    # How many unique pids are there reading this file ?
    cat $1 | awk '{ print $8 }' > foo1
    cat foo1 | uniq > uniq_pids_byfile
    rm foo1
}

get_unique_pids() {
    # Sort first by pid, then by filename
    cat $infile | sed s/,//g  | sort -d -k8,8 -k2,2 > foo1
    mv foo1 $infile
    # $infile now contains lines sorted by <pid, filename>
    # How many unique pids are there ?
    cat $infile | awk '{ print $8 }' > foo1
    cat foo1 | uniq > uniq_pids
    rm foo1
}

get_unique_files_bypid() {
    # How many unique files are there read by this pid ?
    cat $1 | awk '{ print $2 }' > foo1
    cat foo1 | uniq > uniq_files_bypid
    rm foo1
}

catch_sigint()
{
    echo "signal INT received, killing streaming trace capture"
    streamtrace_end
}


prep_to_do_something() {
#    adb shell "am force-stop com.android.chrome"
#    adb shell "am force-stop com.google.android.gm"
    adb shell 'echo 3 > /proc/sys/vm/drop_caches'
    sleep 1
}

do_something() {
    # Arrange things so that the first SIGINT will kill the
    # child process (sleep), but will return to the parent.
    trap 'catch_sigint'  INT
    echo "OK to kill sleep when test is done"
    sleep 30d
#    adb shell "am start -W -n com.android.chrome/com.google.android.apps.chrome.Main"
#    adb shell "am start -W -n com.google.android.gm/.ConversationListActivityGmail"
}

# Get the aggregate list of files read/written. For each file, break up the IOs by pid
process_files_rw() {
    read_write=$1
    get_unique_files_rw
    # Loop over each file that was involved in IO
    # Find all the pids doing IO on that file
    # Aggregate the IO done by each pid on that file and dump it out
    grand_total_KB=0
    cp $infile tempfile
    for i in `cat uniq_files`
    do
	# Get just the tracepoints for this file
	fgrep -w "$i" tempfile > subtrace
	if [ -s subtrace ]; then
	    echo "File: $i"
	    total_file_KB=0
	    # Remove the tracepoints we just picked up
	    fgrep -v -w "$i" tempfile > foo
	    mv foo tempfile
	    # Get all the pids doing IO on this file
	    get_unique_pids_byfile subtrace
	    for j in `cat uniq_pids_byfile`
	    do
		echo -n "            $j $read_write: "
		pid_KB=`fgrep -w "$j" subtrace | awk '{ bytes += $6 } END { print bytes }' `
		pid_KB=`expr $pid_KB / 1024`
		echo "$pid_KB KB"
		total_file_KB=`expr $total_file_KB + $pid_KB`
	    done
	    i_size=`tail -n1 subtrace  | awk '{ if ($12 > 1024) printf "%d KB", ($12/1024); else printf "%d bytes", $12; }' `
	    echo "            Total $read_write: $total_file_KB KB i_size: $i_size"
	    grand_total_KB=`expr $grand_total_KB + $total_file_KB`
	fi
    done
    echo "Grand Total File DATA KB $read_write $grand_total_KB"
    rm tempfile
}

process_files_writepages() {
    get_unique_files_writepages
    # Loop over each file that was involved in IO
    # Aggregate the IO done on that file and dump it out
    grand_total_KB=0
    cp $infile tempfile
    for i in `cat uniq_files`
    do
	# Get just the tracepoints for this file
	fgrep -w "$i" tempfile > subtrace
	if [ -s subtrace ]; then
	    fgrep -v -w "$i" tempfile > foo
	    mv foo tempfile
	    total_file_KB=`cat subtrace | awk '{ bytes += $4 } END { print bytes }' `
	    total_file_KB=`expr $total_file_KB / 1024`
	    if [ $total_file_KB -gt 0 ]; then
		echo "File: $i Total $read_write: $total_file_KB KB"
		grand_total_KB=`expr $grand_total_KB + $total_file_KB`
	    fi
	fi
    done
    echo "Grand Total File DATA KB Writepages $grand_total_KB"
    rm tempfile
}

# Get the aggregate list of pids. For each pid, break up the IOs by file
process_pids() {
    read_write=$1
    get_unique_pids
    list_of_pids=`cat uniq_pids`
    # $list_of_pids is a list of all the pids involved in IO
    #
    # Loop over each pid that was involved in IO
    # Find all the files the pid was doing IO on
    # Aggregate the IO done by the pid for each file and dump it out
    #
    grand_total_KB=0
    for i in $list_of_pids
    do
	echo "PID: $i"
	total_pid_KB=0
	# Get just the tracepoints for this pid
	fgrep -w "$i" $infile > subtrace
	# Get all the pids doing IO on this file
	get_unique_files_bypid subtrace
	list_of_files=`cat uniq_files_bypid`
	# $list_of_files is a list of all the files IO'ed by this pid
	for j in $list_of_files
	do
	    i_size=`fgrep -w "$j" subtrace | tail -n1 | awk '{ if ($12 > 1024) printf "%d KB", ($12/1024); else printf "%d bytes", $12; }' `
	    file_KB=`fgrep -w "$j" subtrace | awk '{ bytes += $6 } END { print bytes }' `
	    file_KB=`expr $file_KB / 1024`
	    echo "            $j $read_write: $file_KB KB i_size: $i_size"
	    total_pid_KB=`expr $total_pid_KB + $file_KB`
	done
	echo "            Total $read_write: $total_pid_KB KB"
	grand_total_KB=`expr $grand_total_KB + $total_pid_KB`
    done
    echo "Grand Total File DATA KB $read_write $grand_total_KB"
}

# main() starts here :

if [ $# -lt 1 ]; then
    usage
fi

bdev_set=false
infile=tracefile.$$

parseoptions $@
adb root && sleep 2
getmodel

found_f2fs=`adb shell 'mount | grep f2fs > /dev/null; echo $?' `

if [ $found_f2fs == 0 ]; then
    f2fs_fs=1
else
    f2fs_fs=0
fi

if [ $f2fs_fs == 0 ] && [ $trace_writepages == true ]; then
    echo "Writepages is only supported with f2fs, please use -r, -w"
    exit 1
fi

prep_to_do_something

clean_up_tracepoints
disk_stats_before
# Start streaming the trace into the tracefile
streamtrace_start &

do_something

streamtrace_end
disk_stats_after

copyout_trace

if [ $trace_reads == true ]; then
    echo
    echo "READS :"
    echo "_______"
    echo
    prep_tracefile_rd
    # Get file specific stats - for each file, how many pids read that file ?
    echo "FILE VIEW:"
    process_files_rw Reads
    if [ $pid_view == true ]; then
	# Get pid specific stats - for each pid, what files do they do IO on ?
	echo "PID VIEW:"
	process_pids Reads
    fi
    disk_stats_delta_rd $grand_total_KB

    debug_FileKB_rd=`cat $infile | awk '{ bytes += $6 } END { printf "%d", bytes/1024 }' `
    echo Debug Grand Total KB READ $debug_FileKB_rd
fi

if [ $trace_writes == true ]; then
    echo
    echo "Writes :"
    echo "_______"
    echo
    prep_tracefile_wr
    # Get file specific stats - for each file, how many pids read that file ?

    echo "FILE VIEW:"
    process_files_rw Writes
    if [ $pid_view == true ]; then
	# Get pid specific stats - for each pid, what files do they do IO on ?
	echo "PID VIEW:"
	process_pids Writes
    fi
    disk_stats_delta_wr $grand_total_KB

    if [ $f2fs_fs == 1 ] ; then
	echo f2fs GC_KB delta = $F2FS_GC_KB_DELTA
    fi
fi

if [ $f2fs_fs == 1 ] && [ $trace_writepages == true ] ; then
    echo
    echo "Writepages :"
    echo "__________"
    echo
    prep_tracefile_writepages
    # Get file specific stats - for each file, how much did we writepage ?

    echo "FILE VIEW:"
    process_files_writepages

    disk_stats_delta_wr $grand_total_KB

    echo f2fs GC_KB delta = $F2FS_GC_KB_DELTA
fi

rm -rf tracefile* uniq_* subtrace trace_saved
