#! /bin/sh

#
#  lustre/lustre/tests/kbuild
#
#  Copyright (C) 2005 Cluster File Systems, Inc.
#
#  Author: Nikita Danilov <nikita@clusterfs.com>
#
#  This file is part of Lustre, http://www.lustre.org.
#
#         Lustre is free  software; you can  redistribute it and/or  modify it
#         under the terms of  version 2 of  the GNU General Public License  as
#         published by the Free Software Foundation.
#
#         Lustre  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  Lustre; if not, write to  the Free  Software Foundation,
#         Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
#
#  kbuild is a swiss-army linux kernel build script. Its purpose is to run
#  automated kernel builds on given target file system (presumably Lustre) to
#  measure file system performance and, occasionally, correctness.
#
#  Usual kernel build doesn't not stress file system, because the bottleneck
#  is CPU consumption by the user level (compiler). To work around this,
#  kbuild uses ccache(1) that eliminates most of CPU load by the compiler,
#  once the cache is primed.
#
#  Options:

function usage()
{
        cat <<EOF
       $pname --- builds a kernel.

Usage: $pname [-s <source>]         \\
              [-t <target>]         \\
              [-m <make-options>]   \\
              [-i <iterations>]     \\
              [-v <verbosity>]      \\
              [-c <config-target>]  \\
              [-S]                  \\
              [-C <config-file>]

   -s <source>        source of kernel to build. This can be:

                        . path to directory;

                        . tar.gz, .tgz, or .tar.bz2 archive;

                        . ftp or http URL to the source archive;

                      defaults to "$src".

   -t <target>        target directory, where build process takes place.
                      Defaults to "$tgt".

   -m <make-options>  additional options supplied to each make invocation.
                      Defaults to "$mopt"

   -c <config-target> kernel makefile target to invoke to configure kernel
                      (defconfig, allyesconfig, allmodconfig, etc.). This
                      option conflicts with -C <config-file>. Defaults to
                      "$mconfig".

   -C <config-file>   use given .config file as kernel configuration. Not
                      used by default.

   -S                 skip kernel copying: kernel source is already unpacked
                      in $target. Defaults to false.

   -v                 increase verbosity level.

Examples:

  $pname -s /usr/src/linux-2.6.10-base.tar.gz -t /mnt/lustre2 \\
         -m -j4 -C /usr/src/.config.fc3

  $pname -s ftp://ftp.clusterfs.com/pub/kernels/fc3-2.6/linux-2.6.10-base.tgz \\
         -m -j4 -c defconfig -vvv

EOF
        exit 1
}

#
#  Results:
#
#  The output of kbuild are times as reported by time. First line is for build
#  that fills the ccache cache (that is also located on the target file
#  system). Consecutive times are repeated builds that reuse ccache
#  cache. Number of iteration is set through -i option. Example output:
#  
#  R 783.757 S 319.615 U 281.720
#  R 540.823 S 277.387 U 54.168
#  R 557.762 S 263.566 U 53.222
#  R 543.877 S 278.569 U 54.412
#  R 544.455 S 279.096 U 53.697
#  R 545.445 S 280.546 U 53.943
#
#  Notes:
#
#  Kernel builds can be quite slow as example output above shows. Create your
#  own .config file to build smaller kernel.
#
#

OPTVAL=`getopt -o s:m:i:t:vc:SC:h -n 'kbuild' -- "$@"` || usage

# Note the quotes around `$OPTVAL': they are essential!
eval set -- "$OPTVAL"

LOG_CRIT=0
LOG_ERROR=1
LOG_WARN=2
LOG_INFO=3
LOG_PROGRESS=4
LOG_TRACE=5
LOG_ALL=6
LOG_DEBUG=7

src=/usr/src/linux
tgt=/mnt/lustre
verbose=$LOG_CRIT

pname=$(basename $0)

mopt=""
mconfig=allyesconfig
it=3
lfile=/tmp/$pname-tmp-log.$$
skip_copy=0
conf_file=""

while : ;do
        case "$1" in
                -s)
                        src="$2"
                        shift 2
                ;;
                -t)
                        tgt="$2"
                        shift 2
                ;;
                -m)
                        mopt="$2"
                        shift 2
                ;;
                -C)
                        conf_file="$2"
                        shift 2
                ;;
                -i)
                        it="$2"
                        shift 2
                ;;
                -c)
                        mconfig="$2"
                        shift 2
                ;;
                -S)
                        skip_copy=1
                        shift
                ;;
                -v)
                        verbose=$(($verbose + 1))
                        shift
                ;;
                -h)
                        usage
                ;;
                --) 
                        shift 
                        break 
                ;;
                *) 
                        echo "Internal error!" 
                        usage
                ;;
        esac
done

[ $verbose -ge $LOG_ALL ] && set -x


function warning()
{
        echo WARNING $pname: $*
}

function fail()
{
        local rc

        rc=$1
        shift
        warning $* ... failing.
        exit $rc
}

function log()
{
        local level

        level=$1
        shift
        if [ $verbose -ge $level ] ;then
               echo $*
        fi
}

function doquiet()
{
        local cmd

        cmd="$*"
        echo >> $lfile
        echo ---- start: $(date +"%Y-%m-%d %H:%M:%S") ---- >> $lfile
        for i in $cmd ;do
                echo "ARG: $i" >> $lfile
        done
        log $LOG_PROGRESS "Running '$cmd'..."
        $cmd >>$lfile 2>&1 || \
                fail 1 "Errors while running '$cmd'. See $lfile for transcript"
        log $LOG_PROGRESS "Finished '$cmd'."
        echo ---- done: $(date +"%Y-%m-%d %H:%M:%S") ---- >> $lfile
}

function dotime()
{
        local cmd

        cmd="$*"
        export TIMEFORMAT="R %3R S %3S U %3U"
        time $cmd
}

ccache_dir=$tgt/ccache_dir
cc_script=$tgt/cc_script

which ccache >/dev/null || fail 2 "No ccache found"
mkdir -p $ccache_dir || fail 3 "Cannot create $ccache_dir"

export CCACHE_DIR=$ccache_dir

# start the stuff

cd $tgt || fail 4 "Cannot cd into $tgt"

echo '#! /bin/sh'   >  $cc_script || fail 5 "Cannot write into $cc_script"
echo 'ccache cc $*' >> $cc_script || fail 6 "Cannot append to $cc_script"
chmod u+rx $cc_script || fail 7 "Cannot chmod u+rx $cc_script"

cc_opt="CC=$cc_script"

[ $verbose -ge $LOG_TRACE ] && vopt=-v

if [ $skip_copy -eq 0 ] ;then
        case "$src" in
        ftp://*|http://*)
                wget -c $src
                src=$(basename $src)
                ;;
        esac

        case "$src" in
        */)
                log $LOG_PROGRESS "Copying directory $src into $tgt"
                cp -a$vopt "$src" .
                ;;
        *.tar.gz|*.tgz)
                tar xzf "$src" $vopt
                ;;
        *.tar.bz2)
                tar xjf "$src" $vopt
                ;;
        *)
                fail 10 "No $src"
                ;;
        esac
fi

cd linux-* || fail 20 "Cannot change to linux-* from $PWD"

function dokernel()
{
        doquiet make $mopt mrproper
        if [ x$conf_file = x ] ;then
                doquiet make $mopt $mconfig
        else
                cp $conf_file .config
                doquiet make $mopt oldconfig
        fi

        dotime doquiet make $mopt $cc_opt bzImage modules
}

log $LOG_PROGRESS Fill the cache...

dokernel

for i in $(seq 1 $it) ;do
        log $LOG_PROGRESS Iteration $i...
        dokernel
done
