#!/bin/bash
#
# Copyright (C) 2008 Oracle Corporation and/or its affiliates.
# All rights reserved.
# Written by: Dan Magenheimer <dan.magenheimer@oracle.com>
#
# xenballoond - In-guest engine for Xen memory ballooning
# Original version: 080630
# Updated 0906XX: add tmem preswap auto-shrinking
#
# Two self-ballooning "policies" are implemented:
# - Selfballooning: Adjust memory periodically, with no (or little) input
#     from domain0.  Target memory is determined solely by the
#     Committed_AS line in /proc/meminfo, but parameters may adjust
#     the rate at which the target is achieved.
# - Directed ballooning: Adjust memory solely as directed by domain0
#
# Under some circumstances, "output" may also be generated; the contents
# of /proc/meminfo and /proc/vmstat may be periodically placed on xenbus.
#
# If xenbus is running and the /usr/bin/xenstore-* tools are installed,
# "xenbus is enabled".
#
# Parameters are documented in /etc/sysconfig/xenballoon.conf. Although 
# some are not used with directed ballooning, all must be set properly.
# If xenbus is enabled, some of these parameters may be overridden by values
# set by domain0 via xenbus.

minmb() {
	RETVAL=$XENBALLOON_MINMEM
	if [ $RETVAL -ne 0 ]; then
		return $RETVAL
	fi
	kb=`cat $XENBALLOON_MAXMEMFILE`
	let "mb=$kb/1024"
	let "pages=$kb/4"
	# this algorithm from drivers/xen/balloon/balloon.c:minimum_target()
	# which was added to balloon.c in 2008 to avoid ballooning too small
	# it is unnecessary here except to accomodate pre-2008 balloon drivers
	# note that ranges are adjusted because a VM with "memory=1024"
	# gets somewhat less than 1024MB
	if [ $mb -lt 125 ]; then
		let RETVAL="$(( 8 + ($pages >> 9) ))"
	elif [ $mb -lt 500 ]; then
		let RETVAL="$(( 40 + ($pages >> 10) ))"
	elif [ $mb -lt 2000 ]; then
		let RETVAL="$(( 104 + ($pages >> 11) ))"
	else
		let RETVAL="$(( 296 + ($pages >> 13) ))"
	fi
	return	# value returned in RETVAL in mB
}

curkb() {
	kb=`grep MemTotal /proc/meminfo | sed 's/  */ /' | \
		cut -f2 -d' '`
	RETVAL=$kb
	return  # value returned in RETVAL in kB
}

downhysteresis() {
	RETVAL=$XENBALLOON_AUTO_DOWNHYSTERESIS
	if [ $xenstore_enabled = "true" ]; then
		if xenstore-exists memory/downhysteresis ; then
			RETVAL=`xenstore-read memory/downhysteresis`
		fi
	fi
	return
}

uphysteresis() {
	RETVAL=$XENBALLOON_AUTO_UPHYSTERESIS
	if [ $xenstore_enabled = "true" ]; then
		if xenstore-exists memory/uphysteresis ; then
			RETVAL=`xenstore-read memory/uphysteresis`
		fi
	fi
	return
}

selfballoon_eval() {
	if [ $xenstore_enabled = "true" ]; then
		if xenstore-exists memory/selfballoon; then
			RETVAL=`xenstore-read memory/selfballoon`
			if [ $RETVAL -eq 1 ]; then
				selfballoon_enabled=true
				return
			fi
		fi
	fi
	selfballoon_enabled=$XENBALLOON_SELF
	return
}

selftarget() {
	tgtkb=`grep Committed_AS /proc/meminfo | sed 's/  */ /' | cut -f2 -d' '`
	minmb
	let "minbytes=$RETVAL*1024*1024"
	let "tgtbytes=$tgtkb*1024"
	if [ $tgtbytes -lt $minbytes ]; then
		let "tgtbytes=$minbytes"
	fi
	RETVAL=$tgtbytes  # value returned in RETVAL in bytes
	return
}

# $1 == 1 means use selftarget, else target in kB
balloon_to_target() {
	if [ "$1" -eq 1 ]; then
		selftarget
		tgtbytes=$RETVAL
	else
		let "tgtbytes=$(( $1 * 1024 ))"
	fi
	curkb
	let "curbytes=$RETVAL*1024"
	if [ $curbytes -gt $tgtbytes ]; then
		downhysteresis
		downhys=$RETVAL
		if [ $downhys -ne 0 ]; then
			let "tgtbytes=$(( $curbytes - \
				( ( $curbytes - $tgtbytes ) / $downhys ) ))"
		fi
	else if [ $curbytes -lt $tgtbytes ]; then
		uphysteresis
		uphys=$RETVAL
		let "tgtbytes=$(( $curbytes + \
				( ( $tgtbytes - $curbytes ) / $uphys ) ))"
		fi
	fi
	echo $tgtbytes > /proc/xen/balloon
	if [ $xenstore_enabled = "true" ]; then
		let "tgtkb=$(( $tgtbytes/1024 ))"
		xenstore-write memory/selftarget $tgtkb
	fi
}

send_memory_stats() {
	if [ ! $xenstore_enabled = "true" ]; then
		return
	fi
	if [ $XENBALLOON_SEND_MEMINFO ]; then
		xenstore-write memory/meminfo "`cat /proc/meminfo`"
	fi
	if [ $XENBALLOON_SEND_VMSTAT ]; then
		xenstore-write memory/vmstat "`cat /proc/vmstat`"
	fi
	if [ $XENBALLOON_SEND_UPTIME ]; then
		xenstore-write memory/uptime "`cat /proc/uptime`"
	fi
}


curpreswappages() {
	pages=$(cat $XENBALLOON_PRESWAP_SYSFILE)
	RETVAL=$pages
	return  # value returned in RETVAL in pages
}

preswaphysteresis() {
	RETVAL=$XENBALLOON_PRESWAP_HYSTERESIS
	if [ $xenstore_enabled = "true" ]; then
		if xenstore-exists memory/preswaphysteresis ; then
			RETVAL=`xenstore-read memory/preswaphysteresis`
		fi
	fi
	return
}

preswapinertia() {
	RETVAL=$XENBALLOON_PRESWAP_INERTIA
	if [ $xenstore_enabled = "true" ]; then
		if xenstore-exists memory/preswapinertia ; then
			RETVAL=`xenstore-read memory/preswapinertia`
		fi
	fi
	return
}

send_preswap_stats() {
	if [ ! $xenstore_enabled = "true" ]; then
		return
	fi
	curpreswappages
	preswap_pgs=$RETVAL
	if [ $XENBALLOON_SEND_PRESWAP ]; then
		xenstore-write memory/preswap "$preswap_pgs"
	fi
}

shrink_preswap() {
	if [ "$XENBALLOON_PRESWAP_SHRINK" = "false" ]; then
		return
	fi
	if [ ! -f "$XENBALLOON_PRESWAP_SYSFILE" ]; then
		return
	fi
	curpreswappages
	preswaplast=$preswapnow
	preswapnow=$RETVAL
	if [ $preswapnow -eq 0 -o $preswapnow -ne $preswaplast ]; then
		preswapinertia
		preswapinertiacounter=$RETVAL
		return
	fi
	let "preswapinertiacounter=$preswapinertiacounter-1"
	if [ $preswapinertiacounter -ne 0 ]; then
		return
	fi
	preswaphysteresis
	preswaphys=$RETVAL
	if [ $preswaphys -eq 0 ]; then
		return
	fi
	let "tgtpreswappages=$(( $preswapnow - \
				( $preswapnow / $preswaphys ) ))"
	preswapinertia
	preswapinertiacounter=$RETVAL
	echo $tgtpreswappages > "$XENBALLOON_PRESWAP_SYSFILE"
}

if [ ! -f /proc/xen/balloon ]; then
	echo "$0: no balloon driver installed"
	exit 0
fi
if [ ! -f /proc/meminfo ]; then
	echo "$0: can't read /proc/meminfo"
	exit 0
fi
xenstore_enabled=true
if [ -f /usr/bin/xenstore-exists -a -f /usr/bin/xenstore-read -a \
     -f /usr/bin/xenstore-write ]; then
	xenstore_enabled=true
else
	echo "$0: missing /usr/bin/xenstore-* tools, disabling directed ballooning"
	xenstore_enabled=false
fi
preswapnow=0

. /etc/sysconfig/xenballoon.conf

while true;
do
	# handle special case for PV domains with hot-add memory
	if [ ! -f $XENBALLOON_MAXMEMFILE ]; then
		maxkb=0
	else
		maxkb=`cat $XENBALLOON_MAXMEMFILE`
	fi
	curkb=`grep MemTotal /proc/meminfo | sed 's/  */ /' | cut -f2 -d' '`
	if [ $curkb -gt $maxkb ]; then
		echo $curkb > $XENBALLOON_MAXMEMFILE
	fi
	interval=$XENBALLOON_INTERVAL
	# do self-ballooning
	selfballoon_eval
	if [ $selfballoon_enabled = "true" ]; then
		balloon_to_target 1
		interval=$XENBALLOON_SELF_INTERVAL
	# or do directed ballooning
	elif [ $xenstore_enabled = "true" ]; then
		if xenstore-exists memory/target ; then
			tgtkb=`xenstore-read memory/target`
			balloon_to_target $tgtkb
		fi
		interval=$XENBALLOON_INTERVAL
	fi
	shrink_preswap
	send_memory_stats
	send_preswap_stats
	if [ $xenstore_enabled = "true" ]; then
		if xenstore-exists memory/interval ; then
			interval=`xenstore-read memory/interval`
		fi
	fi
	sleep $interval
done &

