#!/bin/sh
#
# Script for showing system and given process memory usage at
# fixed interval.  This file is part of sp-memusage.
#
# Copyright (C) 2008 by Nokia Corporation
#
# Contact: Eero Tamminen <eero.tamminen@nokia.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License 
# version 2 as published by the Free Software Foundation. 
#
# 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., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA

cd /proc

only_changed=
default_interval=2
interval=$default_interval

exit_with_error()
{
	name=${0##*/}
	echo
	echo "Show system and optionally process memory usage statistics"
	echo "at a fixed interval."
	echo
	echo "usage: $name [-c] [-i <interval>] [-p <process>]"
	echo
	echo "With -c, lines where change column has value zero are skipped."
	echo "<interval> in secs between memory usage checks, >0, default=$default_interval."
	echo "<process> can be specified either by its PID or name"
	echo "(if it's not unique, latest PID will be used)."
	echo
	echo "example: $name -c -i 10 -p esd"
	echo
	if [ \! -z "$1" ]; then
		echo "ERROR: $1"
		echo
	fi
	exit 1
}

while true; do
	# args done?
	if [ $# -eq 0 ]; then
		break
	fi
	# options...
	case "$1" in
		-c)	only_changed="yes"
			shift
			continue
			;;
		-i)
			interval=$2
			;;
		-p)
			# last pid for given application?
			pid=$(pidof $2|cut -d' ' -f1)
			if [ -z "$pid" ]; then
				if [ -d $2 ]; then
					pid=$2
				else
					exit_with_error "no process with PID or name '$2'"
				fi
			else
				# PID found for name -> skip value checks
				shift; shift
				continue
			fi
			;;
		*)
			exit_with_error "unknown option '$1'"
			;;
	esac
	# option was without value?
	if [ $# -eq 1 ]; then
		exit_with_error "option '$1' is lacking a value"
	fi
	# non-numeric or zero/negative value?
	if [ "${2#[0-9]}" == "$2" ] || [ $2 -lt 1 ]; then
	 	exit_with_error "invalid option '$1' value '$2'"
	fi
	# next pair of args
	shift; shift
done

system_memory()
{
	echo -e "\nSystem memory usage information, including swap."
	echo -e "(avail = free+cached+buffers for RAM and swap)\n"
	# headings
	echo -e "time:\t\ttotal:\tavail:\tused:\tchange:\t\tuse-%:"
	# whole system free
	prevused=0
	while true; do
		output=$(awk -v prevused=$prevused -v time=$(date +"%T") '
		/(Mem|Swap)Total:/ { total += $2 }
		/(Mem|Swap|^)(Free|Buffers|Cached):/ { free += $2 }
		END { used = total-free;
		      if(prevused) change = used-prevused; else change = 0;
		      printf("%s\t%d\t%d\t%d\t%+5d kB\t%d%%", time, total, free, used, change, used*100/total)
		}' meminfo)
		current=$(echo "$output"|awk '{print $4}')
		if [ -z "$only_changed" ] || [ "$current" != "$prevused" ]; then
			prevused=$current
			echo "$output"
		fi
		sleep $interval
       done
       exit 0
}

process_memory()
{
	app=$(tr '\0' ' '<$pid/cmdline|cut -d' ' -f1)
	echo -e "\nList available system memory and given process memory usage"
       	echo "for $app[$pid] according to SMAPS."
	echo -e "(without swap as SMAPS doesn't report swap correctly)\n"
	# headings
	echo -e "\t\tsystem\tprocess\t\tprivate\t/------ dirty ---------\\"
	echo -e "time:\t\tavail:\tsize:\trss:\tclean:\tshared:\tprivate: change:"
	# system and process memory
	prevdirty=0
	while true; do
		if [ \! -d "$pid" ]; then
			echo "Process $pid disappeared. Exiting."
			break
		fi
		# process memory usage according to SMAPS
		output=$(awk -v prevdirty=$prevdirty '
		/Size:/ { size += $2 }
		/Rss:/ { rss += $2 }
		/Shared_Dirty:/ { sdirty += $2 }
		/Private_Clean:/ { pclean += $2 }
		/Private_Dirty:/ { pdirty += $2 }
		END {
		if (prevdirty) change = pdirty-prevdirty; else change = 0;
		printf("%d\t%d\t%d\t%d\t%d\t%+5d kB\n", size, rss, pclean, sdirty, pdirty, change)
		}' $pid/smaps)
		current=$(echo "$output"|awk '{print $5}')
		if [ -z "$only_changed" ] || [ "$current" != "$prevdirty" ]; then
			prevdirty=$current
			# time + whole system free memory
			awk -v time=$(date +"%T") '
			/^(MemFree|Buffers|Cached):/ { free += $2 }
			END { printf("%s\t%d\t", time, free) }' meminfo
			echo "$output"
		fi
		sleep $interval
	done
}

if [ -d "$pid" ]; then
	process_memory
else
	system_memory
fi
