# # add_file "contrib/parse-accounting.pl" # # add_file "contrib/perf-test.sh" # # patch "ChangeLog" # from [9e9daa4c088a4f485b7487510115847c26295e05] # to [63c37b88656196a7dd549876c6f79062f756db93] # # patch "contrib/parse-accounting.pl" # from [] # to [4b8d042f81fbd18ab8efeedaf705fb5854e31e18] # # patch "contrib/perf-test.sh" # from [] # to [532119a8ca3587ec6d9f4daa887d225b9db0cb1f] # ======================================================================== --- ChangeLog 9e9daa4c088a4f485b7487510115847c26295e05 +++ ChangeLog 63c37b88656196a7dd549876c6f79062f756db93 @@ -1,3 +1,9 @@ +2005-08-09 Eric Anderson + + * tests/perf-test.sh: A repeatable performance test harness + * tests/parse-accounting.pl: A script that parses the accounting + output into a nice tabular format + 2005-08-31 Matthew Gregan * paths.cc (test_bookkeeping_path, test_system_path): Second ======================================================================== --- contrib/parse-accounting.pl +++ contrib/parse-accounting.pl 4b8d042f81fbd18ab8efeedaf705fb5854e31e18 @@ -0,0 +1,101 @@ +#!/usr/bin/perl -w +use strict; +use FileHandle; + +# possible values that can be extracted from the file... +my ($cpus, $max_size, $max_resident, $copied, $malloc); + +die "Usage: " unless @ARGV == 3; + +my ($test,$what,$file) = @ARGV; + +parse_file($file); + +if (defined $copied && defined $malloc && defined $max_resident && defined $max_size) { + if ($test eq 'header') { + print <<'END_OF_HEADER'; + Maximum (MiB) Copied Malloc + *Test* Operation CPU(s) Size Resident (MiB) (MiB) +---------------- --------- ------ ------- ------- -------- -------- +END_OF_HEADER + } + eval ' + format STDOUT = +@<<<<<<<<<<<<<<< @<<<<<<<< @###.# @###.## @###.## @####### @####### +$test, $what, $cpus, $max_size, $max_resident, $copied, $malloc +. + '; die $@ if $@ +} elsif (defined $max_resident && defined $max_size) { + if ($test eq 'header') { + print <<'END_OF_HEADER'; + Maximum (MiB) + *Test* Operation CPU(s) Size Resident +---------------- --------- ------ ------- ------- +END_OF_HEADER + } + eval ' + format STDOUT = +@<<<<<<<<<<<<<<< @<<<<<<<< @###.# @###.## @###.## +$test, $what, $cpus, $max_size, $max_resident, +. + '; die $@ if $@ +} else { + if ($test eq 'header') { + print <<'END_OF_HEADER'; + *Test* Operation CPU(s) +---------------- --------- ------ +END_OF_HEADER + } + eval ' + format STDOUT = +@<<<<<<<<<<<<<<< @<<<<<<<< @###.# +$test, $what, $cpus +. + '; die $@ if $@ +} + +exit(0) if $test eq 'header'; + +$max_size ||= -1; +$max_resident ||= -1; +$copied ||= -1; +$malloc ||= -1; + +write; + +sub parse_file { + my($file) = @_; + + my $fh = new FileHandle($file) or die "Can't open $file for read: $!"; + my ($usertime,$systime); + while(<$fh>) { + # parse builtin accounting... + $cpus = $1 + $2 + if /^STATS: User time: (\d+\.\d+)s, System time: (\d+\.\d+)s$/o; + ($max_size,$max_resident) = ($1,$2) + if /^STATS: Max Size MiB: (\d+\.\d+), Max Resident MiB: (\d+\.\d+)$/o; + $copied = $1 + if /^STATS: MiB copied: (\d+\.\d+), Total/o; + $malloc = $1 + if /^STATS: MiB malloced: (\d+\.\d+), Malloc/o; + + # parse external accounting by /usr/bin/time on debian... + $usertime = $1 * 60 + $2 + if /^user\s+(\d+)m(\d+\.\d+)s$/o; + $systime = $1 * 60 + $2 + if /^sys\s+(\d+)m(\d+\.\d+)s$/o; + # parse external accounting by time in zsh... + ($usertime,$systime) = ($1,$2) + if /^(\d+\.\d+)user (\d+\.\d+)system .*CPU/o; + } + + if (defined $usertime || defined $systime) { + die "both internal and external statistics?? log in $file" + if defined $cpus; + die "missing user or system time?? log in $file" + unless defined $usertime && defined $systime; + $cpus = $usertime + $systime; + } + die "internal, didn't get cpu seconds from $file?!" unless defined $cpus; +} + ======================================================================== --- contrib/perf-test.sh +++ contrib/perf-test.sh 532119a8ca3587ec6d9f4daa887d225b9db0cb1f @@ -0,0 +1,316 @@ +#!/bin/sh +set -e +if [ "$1" = "" ]; then + MONOTONE=`pwd`/monotone +elif [ -x "$1" ]; then + MONOTONE="$1" +else + echo "Usage: $0 [monotone-binary-to-test [test-to-run ...]]" + exit 1 +fi + +if [ ! -x $MONOTONE ]; then + echo "$MONOTONE doesn't exist?!" + exit 1 +fi + +PARSE_ACCOUNT=`pwd`/tests/parse-accounting.pl +if [ -x $PARSE_ACCOUNT ]; then + : +elif [ -x `dirname $MONOTONE`/tests/parse-accounting.pl ]; then + PARSE_ACCOUNT=`dirname $MONOTONE`/tests/parse-accounting.pl +else + echo "can't find parse-accounting.pl. Looked in `pwd`/tests, and `dirname $MONOTONE`/tests" + exit 1 +fi + +MONOTONE_DB=`cat MT/options | grep database | awk '{print $2}' | sed 's/^.//' | sed 's/.$//'` +if [ -z "$MONOTONE_DB" -o ! -f "$MONOTONE_DB" ]; then + echo "Couldn't auto-determine monotone db?!" + exit 1 +fi +[ -d /tmp/mt-perf-test ] || mkdir /tmp/mt-perf-test +cd /tmp/mt-perf-test + +# figure out if binary has timing built in... +ENABLE_MONOTONE_STATISTICS=1 $MONOTONE --help >timing-check.out 2>&1 +if [ `grep '^STATS: ' timing-check.out | wc -l` -gt 1 ]; then + MEASURE= + PIDFILE_ARG= + KILLBY=child + echo "Using builtin statistics..." +else + MEASURE=time + PIDFILE_ARG=--pid-file=/tmp/mt-perf-test/pid-file + KILLBY=file + echo "Using external statistics..." +fi + +[ -d staging ] || mkdir staging +cd staging + +# Rebuild all of the various files for testing ... + +if [ ! -f random.large ]; then + echo "rebuilding random.large (this takes a long time)..." + dd if=/dev/urandom of=random.large-new bs=1024k count=100 >/dev/null 2>&1 + mv random.large-new random.large +fi + +for i in 0 1 2; do + for j in 0 1 2 3 4 5 6 7 8 9; do + if [ ! -f random.medium.$i$j ]; then + echo "rebuilding random.medium.$i$j..." + dd if=/dev/urandom of=random.medium-new bs=1024k count=10 >/dev/null 2>&1 + mv random.medium-new random.medium.$i$j + fi + done +done + +if [ ! -f halfzero.large ]; then + echo "rebuilding halfzero.large..." + dd if=/dev/zero of=halfzero.large-new bs=1024k count=50 >/dev/null 2>&1 + dd if=/dev/urandom of=halfzero.large-new bs=1024k seek=50 count=50 >/dev/null 2>&1 + mv halfzero.large-new halfzero.large +fi + +if [ ! -d monotone ]; then + [ ! -d monotone-new ] || rm -rf monotone-new + mkdir monotone-new + # revisions 0.10 .. 0.22 + for i in 713ed1966baced883ed865a931f97259522f90da fdc32bcc09e2714350fb514990bd26acb607264b 3cd6b8cc947ddab015fd945d3c305fc748bb6d0a 95a1a16c0941cc1ae51e9eb5d64d075ef35c5b19 fdf1335b4dfd8c1529fef8db58e5b819b03f7c8a 20b36b747dcce1230a6e7a0b1554bd7874f0fbe7 35da5df64546301d332303bbf63b6799d70932c8 e8c9e4eb0534a4c6e538935576330de34ec42052 168adf9537ff136c9b7fe7faad5991f92859390d 44ed8807bead656889fb5022f974e13a7169098c e65bc11b6670a0b2ed8e72214cb81d94e6a9a2d1 28058ae3e850229a5d8fae65415cbbf82b435377; do + echo "checking out monotone rev $i..." + $MONOTONE --db $MONOTONE_DB checkout --revision $i monotone-new/mt-$i + done + # Version 0.17, had to specify the branch explicitly to get checkout to work. + i=337d62e5cbd50c36e2f2c2bda489a98de3a8aeb7 + echo "checking out monotone rev $i..." + $MONOTONE --db $MONOTONE_DB checkout --branch net.venge.monotone --revision $i monotone-new/mt-$i + mv monotone-new/mt-168adf9537ff136c9b7fe7faad5991f92859390d monotone-new/mt-0.19 + rm -rf monotone-new/*/MT + mv monotone-new monotone +fi + +cd /tmp/mt-perf-test +if [ ! -d dbdir ]; then + [ ! -d dbdir-new ] || rm -rf dbdir-new + mkdir dbdir-new + cd dbdir-new + cat >monotonerc <keys </dev/null 2>&1 +} + +load_zero_large() { + dd if=/dev/zero of=zero.large bs=1024k count=100 >/dev/null 2>&1 +} + +load_random_medium() { + cp -rp ../staging/random.medium.00 . +} + +load_random_medium_20() { + cp -rp ../staging/random.medium.[01]? . +} + +load_halfzero_large() { + cp -rp ../staging/halfzero.large . +} + +load_random_large() { + cp -rp ../staging/random.large . +} + +load_monotone() { + cp -rp ../staging/monotone/mt-0.19 . +} + +load_mt_multiple() { + cp -rp ../staging/monotone . +} + +load_mt_bigfiles() { + for i in ../staging/monotone/mt-*; do + j=`basename $i` + find $i -type f -exec cat '{}' \; >$j.txt + done +} + +load_mixed() { + if [ "$1" = "" ]; then + echo "Usage load_mixed #" + exit 1 + fi + RANDOM_LIST=(`ls ../staging/random.medium.??`) + MONOTONE_LIST=(`ls -d ../staging/monotone/mt-*`) + dd if=/dev/zero of=zero.med bs=1024k count=10 >/dev/null 2>&1 + i=0; + j=0; + while [ $i -lt $1 ]; do + MT_PATH=${MONOTONE_LIST[$i]} + MT_NAME=`basename $MT_PATH` + [ -d $MT_NAME ] || cp -rp $MT_PATH . + cp ${RANDOM_LIST[$j]} $MT_NAME + cat zero.med >>$MT_NAME/`basename ${RANDOM_LIST[$j]}` + j=`expr $j + 1` + cp ${RANDOM_LIST[$j]} $MT_NAME + cat zero.med >>$MT_NAME/`basename ${RANDOM_LIST[$j]}` + j=`expr $j + 1` + i=`expr $i + 1` + done +} + +load_mixed_1() { + load_mixed 1 +} + +load_mixed_4() { + load_mixed 4 +} + +load_mixed_12() { + load_mixed 12 +} + +load_everything() { + # print something out here even though it means removing + # from the output because it takes so long. + echo -n "load everything..."; + load_zero_small + echo -n "." + load_zero_large + echo -n "." + load_random_medium_20 + echo -n "." + load_halfzero_large + echo -n "." + load_random_large + echo -n "." + load_mt_multiple + echo -n "." + load_mt_bigfiles + echo -n "." + load_mixed_12 + echo "." +} + +prep_test() { + cd /tmp/mt-perf-test/dbdir + [ ! -f checkin.db ] || rm checkin.db + [ ! -f netsync.db ] || rm netsync.db + [ ! -f checkin.db-journal ] || rm checkin.db-journal + [ ! -f netsync.db-journal ] || rm netsync.db-journal + + $MONOTONE --db checkin.db db init >/tmp/mt-perf-test/log 2>&1 + $MONOTONE --db netsync.db db init >>/tmp/mt-perf-test/log 2>&1 + cat keys | $MONOTONE --db checkin.db read >>/tmp/mt-perf-test/log 2>&1 + cat keys | $MONOTONE --db netsync.db read >>/tmp/mt-perf-test/log 2>&1 + + cd /tmp/mt-perf-test + [ ! -d testdir ] || rm -rf testdir + $MONOTONE --db dbdir/checkin.db setup --branch test testdir >>/tmp/mt-perf-test/log 2>&1 + cd testdir +} + +dotest() { + prep_test + load_$1 + export ENABLE_MONOTONE_STATISTICS=1 + $MEASURE $MONOTONE add . >/tmp/mt-perf-test/add.log 2>&1 + $PARSE_ACCOUNT "$1" "add files" /tmp/mt-perf-test/add.log + + cp ../dbdir/monotonerc MT/monotonerc + $MEASURE $MONOTONE commit -m foo >/tmp/mt-perf-test/commit.log 2>&1 + $PARSE_ACCOUNT "$1" "commit" /tmp/mt-perf-test/commit.log + + cd .. + rm -rf testdir + $MEASURE $MONOTONE --db dbdir/checkin.db checkout --branch test testdir >/tmp/mt-perf-test/checkout.log 2>&1 + $PARSE_ACCOUNT "$1" "checkout" /tmp/mt-perf-test/checkout.log + + cd testdir + cp ../dbdir/monotonerc MT/monotonerc + [ -f /tmp/mt-perf-test/pid-file ] && rm /tmp/mt-perf-test/pid-file + $MEASURE $MONOTONE --db ../dbdir/checkin.db $PIDFILE_ARG serve localhost:7318 test >/tmp/mt-perf-test/serve.log 2>&1 & + SERVER=$! + sleep 1 + $MEASURE $MONOTONE --db ../dbdir/netsync.db pull localhost:7318 test >/tmp/mt-perf-test/pull.log 2>&1 + # SEGV here is intentional, it causes the server to exit through an + # assertion which prints out the accounting information + case $KILLBY in + child) kill -SEGV $SERVER ;; + file) kill -SEGV `cat /tmp/mt-perf-test/pid-file` ;; + *) echo "internal error, unknown killby '$KILLBY'" ;; + esac + wait $SERVER || true + $PARSE_ACCOUNT "$1" "serve" /tmp/mt-perf-test/serve.log + $PARSE_ACCOUNT "$1" "pull" /tmp/mt-perf-test/pull.log + echo + unset ENABLE_MONOTONE_STATISTICS +} + +echo -n "Test CPU: " +grep 'model name' /proc/cpuinfo | sed 's/model name.: //' | head -1 +$MONOTONE --version 2>&1 | grep 'base revision' +ENABLE_MONOTONE_STATISTICS=1 $MEASURE $MONOTONE --help >/tmp/mt-perf-test/help.log 2>&1 +$PARSE_ACCOUNT header header /tmp/mt-perf-test/help.log +if [ "$2" != "" ]; then + shift + while [ "$1" != "" ]; do + dotest "$1" + shift + done + exit 0 +fi +dotest zero_small +dotest zero_large +dotest random_medium +dotest random_medium_20 +dotest halfzero_large +dotest random_large +dotest monotone +dotest mt_multiple +dotest mt_bigfiles +dotest mixed_1 +dotest mixed_4 +dotest mixed_12 +dotest everything +