#!/bin/bash
#
# Copyright 2009 Red Hat, Inc. and/or its affiliates.
# Released under the GPL
#
# ksmd         Kernel Samepage Merging Daemon
#
# chkconfig: - 85 15
# description: The Kernel Samepage Merging control Daemon is a simple script  \
#          that controls whether (and with what vigor) should ksm search  \
#              duplicated pages.
# processname: ksmd
# config: /etc/ksmd.conf
# pidfile: /var/run/ksmd.pid
#
### BEGIN INIT INFO
# Provides: ksmd
# Required-Start:
# Required-Stop:
# Should-Start:
# Short-Description: tune the speed of ksm
# Description: The Kernel Samepage Merging control Daemon is a simple script
#   that controls whether (and with what vigor) should ksm search duplicated
#   memory pages.
#   needs testing and ironing. contact danken redhat com if something breaks.
### END INIT INFO

###########################
if [ -f /etc/ksmd.conf ]; then
    . /etc/ksmd.conf
fi

KSM_MONITOR_INTERVAL=${KSM_MONITOR_INTERVAL:-60}
KSM_NPAGES_BOOST=${KSM_NPAGES_BOOST:-300}
KSM_NPAGES_DECAY=${KSM_NPAGES_DECAY:--50}

KSM_NPAGES_MIN=${KSM_NPAGES_MIN:-64}
KSM_NPAGES_MAX=${KSM_NPAGES_MAX:-1250}
# microsecond sleep between ksm scans for 16Gb server. Smaller servers sleep
# more, bigger sleep less.
KSM_SLEEP=${KSM_SLEEP:-10000}

KSM_THRES_COEF=${KSM_THRES_COEF:-20}
KSM_THRES_CONST=${KSM_THRES_CONST:-2048}

total=`awk '/^MemTotal:/ {print $2}' /proc/meminfo`
[ -n "$DEBUG" ] && echo total $total

npages=0
sleep=$[KSM_SLEEP * 16 * 1024 * 1024 / total]
[ -n "$DEBUG" ] && echo sleep $sleep
thres=$[total * KSM_THRES_COEF / 100]
if [ $KSM_THRES_CONST -gt $thres ]; then
    thres=$KSM_THRES_CONST
fi
[ -n "$DEBUG" ] && echo thres $thres

KSMCTL () {
    if [ -x /usr/bin/ksmctl ]; then
        /usr/bin/ksmctl $* 2>&1 > /dev/null
    else
        case x$1 in
            xstop)
                echo 0 > /sys/kernel/mm/ksm/run
                ;;
            xstart)
                echo $2 > /sys/kernel/mm/ksm/pages_to_scan
                echo $3 > /sys/kernel/mm/ksm/sleep
                echo 1 > /sys/kernel/mm/ksm/run
                ;;
        esac
    fi
}

committed_memory () {
    # calculate how much memory is committed to running qemu processes
    local progname
    progname=${1:-qemu}
    ps -o vsz `pgrep $progname` | awk '{ sum += $1 }; END { print sum }'
}

increase_napges() {
    local delta
    delta=${1:-0}
    npages=$[npages + delta]
    if [ $npages -lt $KSM_NPAGES_MIN ]; then
        npages=$KSM_NPAGES_MIN
    elif [ $npages -gt $KSM_NPAGES_MAX ]; then
        npages=$KSM_NPAGES_MAX
    fi
    echo $npages
}

adjust () {
    local free committed
    free=`awk '/^MemFree:/ { free += $2}; /^Buffers:/ {free += $2}; /^MemCached:/ {free += $2}; END {print free}' /proc/meminfo`
    committed=`committed_memory`
    [ -n "$DEBUG" ] && echo committed $committed free $free
    if [ $[committed + thres] -lt $total -a $free -gt $thres ]; then
        KSMCTL stop
        [ -n "$DEBUG" ] && echo "$[committed + thres] < $total and free > $thres, stop ksm"
        return 1
    fi
    [ -n "$DEBUG" ] && echo "$[committed + thres] > $total, start ksm"
    if [ $free -lt $thres ]; then
        npages=`increase_napges $KSM_NPAGES_BOOST`
        [ -n "$DEBUG" ] && echo "$free < $thres, boost"
    else
        npages=`increase_napges $KSM_NPAGES_DECAY`
        [ -n "$DEBUG" ] && echo "$free > $thres, decay"
    fi
    KSMCTL start $npages $sleep
    [ -n "$DEBUG" ] && echo "KSMCTL start $npages $sleep"
    return 0
}

loop () {
    while true
    do
        sleep $KSM_MONITOR_INTERVAL
        adjust
    done
}

###########################
. /etc/rc.d/init.d/functions

prog=ksmd
pidfile=${PIDFILE-/var/run/ksmd.pid}
RETVAL=0

start() {
    echo -n $"Starting $prog: "
    daemon --pidfile=${pidfile} $0 loop
    RETVAL=$?
    echo
    return $RETVAL
}

stop() {
    echo -n $"Stopping $prog: "
    killproc -p ${pidfile}
    RETVAL=$?
    echo
}

signal () {
    pkill -P `cat ${pidfile}` sleep
}

case "$1" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  status)
        status -p ${pidfile} $prog
    RETVAL=$?
    ;;
  restart)
    stop
    start
    ;;
  signal)
    signal
    ;;
  loop)
        RETVAL=1
    if [ -w `dirname ${pidfile}` ]; then
            loop &
            echo $! > ${pidfile}
            RETVAL=$?
        fi
    ;;
  *)
    echo $"Usage: $prog {start|stop|status|signal|help}"
    RETVAL=3
esac

exit $RETVAL

