#!/usr/bin/perl -w
#
# Copyright 2000-2004 by Hans Reiser, licensing governed by reiserfsprogs/README
#
#
# Mongo.pl is reiserfs benchmark.
#
# ...
#
# Test will format partition /dev/xxxx by 'mkreiserfs' or 'mke2fs'
# mount it and run given number of processes during each phase :
# Create, Copy, Append, Modify, Holes, Symlinks, Read, Stats, Rename and Delete.
#
# Also, the program calculates the fragmentation after Create and Copy phases:
# Fragm = number_of_fragments / number_of_files
# (Current version use the files more than 16KB to calculate Fragm.)
#
$EXTENDED_STATISTICS = 0;
$SHOULD_BE_SET = 1;
$ERR_FILE = "ERR.file";
use POSIX;
use File::stat;
# useful info
sub info {
my @ver;
$ver[++$#ver] = "date : " . `date +%c`;
$ver[++$#ver] = "kernel : " . `uname -rv`;
$ver[++$#ver] = "machine : " . `uname -n`;
@tmp = `free -t`;
@foo = split ' ', $tmp [1];
$ver[++$#ver] = "mem total : " . $foo [1] . "\n";
$ver[++$#ver] = "reiser4 : " . $OPTIONS{"INFO_R4"} if defined $OPTIONS{"INFO_R4"};
$ver[++$#ver] = ".config : PLEASE take care to make .config file available when you publish benchmark results. At namesys, .config should be stored somewhere in the internal benchmark directory (intbenchmarks/mongo/), and link to it is added to the resulting html document.";
foreach (@ver) {
chomp $_;
}
return @ver;
}
sub print_usage {
if ($#_ != -1) {
my ($str) = @_;
&LOG ("ERROR: $str");
}
print "\nUsage: mongo.pl [opt=val | @ ]+ \n";
print "\nRequired options:\n";
foreach my $k (sort keys %OPTION_DEFS) {
my $desc = $OPTION_DEFS{$k}->[2];
print "\t$k\t -- $desc.\n" if ($OPTION_DEFS{$k}->[1]);
}
print "\nOther options:\n";
foreach my $k (sort keys %OPTION_DEFS) {
my $desc = $OPTION_DEFS{$k}->[2];
print "\t$k\t -- $desc.\n" unless ($OPTION_DEFS{$k}->[1]);
}
print "\nSpecial options and commands are:\n";
print "\tLOG=\t -- open a log file for recording test progress.\n";
print "\tRUN\t -- start test.\n";
print "\t\@\t -- include a file with options and commands.\n";
exit -1;
}
# description of all valid options which can be in an input file is a
# hash array each element of it has the following form:
# "key" => [default value, flags, comment string]
#
# currently flags field can be:
#
# 0 --- parameter is optional
#
# 1 --- parameter is mandatory
#
%OPTION_DEFS = (
"FSTYPE" => [ undef, $SHOULD_BE_SET, "filesystem type"],
"DIR" => [ undef, $SHOULD_BE_SET, "fs mount point"],
"MKFS" => [ undef, 0, "mkfs command"],
"DEV" => [ undef, $SHOULD_BE_SET, "device file name"],
"JOURNAL_DEV" => [ undef, 0, "journal device file name"],
"JOURNAL_SIZE" => [ undef, 0, "journal size in 4K blocks"],
"MOUNT_OPTIONS" => [ undef, 0, "mount options"],
"BYTES" => [ undef, $SHOULD_BE_SET,
"file set size (in bytes) created by all instances of reiser_fract_tree in one pass, "],
"FILE_SIZE" => [ undef, $SHOULD_BE_SET,
"median file size (a reiser_fract_tree parameter)"],
"REP_COUNTER" => [3, 1, "number of passes for each mongo phase"],
"NPROC" => [1, 1, "number of concurrent processes"],
"WRITE_BUFFER" => [4096, 1,
"read/write buffer size used for read and write by mongo utilities"],
"GAMMA" => ["0.0", 1,
"the exponent of the core distribution in reiser_fract_tree"],
"MAX_FNAME_LEN" => [24, 0, "maximum generated file name length default (24)"],
"SYNC" => ["off", 1, "using O_SYNC when opening files"],
"DD_MBCOUNT" => [undef, 0, "size of largefile in megabytes"],
"INFO_R4" => [undef, 0, "reiser4 version specification"],
"PHASE_MKDIRS" => [undef, 0, "setting for mkdirs phase: off/on"],
"PHASE_MKFILES" => [undef, 0, "setting for mkfiles phase: off/on"],
"PHASE_CREATE" => [undef, 0, "setting for create phase: off/on"],
"PHASE_COPY" => [undef, 0, "setting for copy phase: off/cp/list"],
"PHASE_APPEND" => [undef, 0, "setting for append phase: off/on"],
"PHASE_MODIFY" => [undef, 0, "setting for modify phase: off/on"],
"PHASE_OVERWRITE" => [undef, 0, "setting for overwrite phase: off/on"],
"PHASE_READ" => [undef, 0, "setting for read phase: off/on"],
"PHASE_STATS" => [undef, 0, "setting for stats phase: off/on"],
"PHASE_DELETE" => [undef, 0, "setting for delete phase: off/rm/list"]
);
# current set of (option, value) pairs.
%OPTIONS=();
#--------------------------------------------
# Set working directories
#--------------------------------------------
chomp($TOPDIR = `pwd`);
# print "topdir is *$TOPDIR* \n";
# $READIT = "${TOPDIR}/mongo_read";
# $SLINKS = "${TOPDIR}/mongo_slinks";
# Keep the largest file to one fifth (100 million bytes)
# of the total tree size.
#-------------------------------------------------------
$max_file_size = 100000000;
# Yuri Shevchuk says that 0 is the median size
# in real life, so I believe him.
#----------------------------------------------
$median_dir_nr_files = 0;
# This should be larger, change once done testing.
#-------------------------------------------------
$max_file_size = 1000000;
$median_dir_nr_files = 1000;
$max_directory_nr_files = 100000;
$median_dir_branching = 3;
$max_dir_branching = 3;
# This should be varying, someday....
#------------------------------------
$append_factor = 0.5;
# ... compile utilities
#---------------------
&compile;
#------- Subroutines --------------------------------------
#----------------------------------------------------------
#
# return number of 1k block used on filesystem $mp
#
sub get_blocks_usage ($) {
my ($mp) = @_;
my $df = `df -k $mp | tail -n 1`;
chomp $df;
my @items = split / +/, $df;
return $items[2];
}
# split a cmd "opt=val" into an array of opt and val elements.
sub split_cmd ($)
{
my ($str) = @_;
my ($beg,@rest) = split /=/, $str;
return ($beg, join ("=", @rest));
}
sub mount_fsys
{
my ($mopts, $fstype, $dev, $dir);
$mopts = "";
$mopts = "-o ". $OPTIONS{"MOUNT_OPTIONS"} if defined $OPTIONS{"MOUNT_OPTIONS"};
$fstype = $OPTIONS{"FSTYPE"};
$dev = $OPTIONS{"DEV"};
$dir = $OPTIONS{"DIR"};
# LOG dmd
&LOG( "mount $mopts -t $fstype $dev $dir");
system("mount $mopts -t $fstype $dev $dir");
&DIE ("can\'t mount fs") if ($?);
}
sub umount_fsys
{
my ($die_on_error) = @_;
my $dir;
$dir = $OPTIONS{"DIR"};
# added sync dmd
system("sync; umount $dir;");
&DIE ("can\'t umount") if ($die_on_error && $?);
}
# Prepare a fs for tests: mkfs and mount
sub init_fsys
{
my ($mkfs, $dir, $fstype);
&umount_fsys(0);
$dir = $OPTIONS{"DIR"};
# replaced by call to umount_fsys above
#system ("umount $dir") ;
$fstype = $OPTIONS{"FSTYPE"};
unless (defined ($OPTIONS{"MKFS"})) {
# we know what mkfs should be for some fs types (reiserfs, ext2)
if ( $fstype eq "reiserfs") {
$mkfs = "echo y | mkreiserfs";
if (defined ($OPTIONS{"JOURNAL_DEV"})){
$mkfs = $mkfs . " -j " . $OPTIONS{"JOURNAL_DEV"};
}
if (defined ($OPTIONS{"JOURNAL_SIZE"})) {
$mkfs = $mkfs . " -s " . $OPTIONS{"JOURNAL_SIZE"};
}
} elsif ($fstype eq "ext2") {
$mkfs = "mke2fs";
} elsif ($fstype eq "reiser4") {
$mkfs = "mkfs.reiser4 -q ";
} else {
$mkfs = "mkfs.$fstype";
}
} else {
$mkfs = $OPTIONS{"MKFS"};
}
$mkfs = "$mkfs " . $OPTIONS{"DEV"};
&LOG("$mkfs");
system ($mkfs);
&DIE ("can\'t create fs") if ($?);
&mount_fsys;
# disk usage statistics gets initialized here
$used0 = get_blocks_usage($dir);
# dmd only do this at the end (see done_fsys)
# &umount_fsys;
}
# Cleanup a fs
sub done_fsys
{
# dmd added sync to umount_fsys
# system("sync");
&umount_fsys(1);
}
# wait all spawned jobs to complete and DIE if one returns not a success.
sub wait_all
{
my $pid = 0;
while (($pid = wait) != -1) {
&DIE ("child process returned error $?") if ($?);
}
}
# a logger
sub LOG ($)
{
my ($msg) = @_;
print ($LOGFILE $msg . "\n") if ($LOGFILE);
print $msg . "\n";
}
# abort with proper log message.
sub DIE ($)
{
my ($e) = @_;
print "ERROR $e \n";
exit (-1);
}
# compile c-files if it is necessary
sub compile
{
system("make all > .mongo.compile.log 2>&1");
&DIE("compile failed") if ($?);
}
sub proc_stat
{
my $t = `cat /proc/stat | grep "^cpu "`;
my @t = split /\s+/,$t;
return splice( @t, 1, 5);
}
# reset statistics before test
sub stats_before
{
my ($test_name) = @_;
@proc_stat0 = &proc_stat;
# ...
}
# gather / / statistics after the
# test completes
sub stats_after ($)
{
my ($test_name) = @_;
@proc_stat1 = &proc_stat;
my ($time_user, $time_nice, $time_system, $time_idle, $time_iowait) =
($proc_stat1[0] - $proc_stat0[0], $proc_stat1[1] - $proc_stat0[1],
$proc_stat1[2] - $proc_stat0[2], $proc_stat1[3] - $proc_stat0[3],
$proc_stat1[4] - $proc_stat0[4]);
my $cpu_utilization = $time_system /
($time_user + $time_nice + $time_system + $time_idle + $time_iowait);
LOG (sprintf ("STAT CPU_UTIL %.2f", $cpu_utilization * 100));
my $dir = $OPTIONS{"DIR"};
$used = get_blocks_usage($dir) - $used0;
LOG "STAT DF ${used}";
if ($EXTENDED_STATISTICS) {
open (FIND_PIPE, "find $dir |") || die "cannnot open pipe from \"find\": $!\n";
my $dirs = 0;
my $files = 0;
my $files16 = 0;
while() {
chomp;
$st = lstat ($_);
if (S_ISDIR($st->mode)) {
$dirs ++;
} elsif (S_ISREG($st->mode)) {
$files ++;
$files16 ++ if ($st->size > 16384);
}
}
close (FIND_PIPE);
&LOG ("NDIRS $dirs");
&LOG ("NFILES $files");
#$f=$frag;
my $f16 = $files16;
my $fr16 =`find ${dir} -type f -size +16k | xargs $TOPDIR/map5 | $TOPDIR/summ | tail -n 1 2>&1`;
my @ff16= split ' ', $f16;
my @ffr16= split ' ', $fr16;
$files16 = $ff16[0];
my $frag = $ffr16[0];
if ( $files16 != 0) {
$procent = $frag / $files16;
} else {
$procent = 0;
}
&LOG ("FRAGM " . sprintf ("%.2f", $procent));
}
}
#------------------------------------------------------------------
# Mongo Launcher mongo_launcher(test_name, test_cmd, do_single_test)
#------------------------------------------------------------------
sub mongo_launcher ()
{
# command string in $test_cmd may containg substrings %rep% and %proc%
# tokens which will be replaced by the ordinal number of this test phase
# run and ordinal number of the working process.
my ($test_name, $test_cmd, $rep_count, $proc_count) = @_;
my ($i, $j);
$rep_count = $OPTIONS{"REP_COUNTER"};
$proc_count = $OPTIONS{"NPROC"};
LOG "PHASE ${test_name}";
# dmd this is now only done once in init_fsys
# &mount_fsys;
&stats_before;
my $total_real = 0;
my $total_cpu = 0;
for (my $i = 0; $i < $rep_count; $i++) {
my $cmd = $test_cmd;
$cmd =~ s/%rep%/$i/g;
#$com is $OPTIONS{"NPROC"} + 1 commands (plus wait) per repetition concatenated by '&'
my $com;
for ($j = 0; $j < $proc_count; $j ++) {
# A local variable $cmd is set from outer $cmd. It is
# strange, but this Perl trick works!
my $cmd = $cmd;
$cmd =~ s/%proc%/$j/g;
$com .= "( $cmd ) & ";
}
$com .= " wait; sync";
LOG "CMNT ${test_name} command: ${com}";
my @time_output=`(/usr/bin/time -p /bin/sh -c \" $com \" ) 2>&1`;
if ( -e ${ERR_FILE}) {
&DIE ("\nEXITED WITH FAIL\n");
}
my $skip = 0;
# skip two lines of output (n+m records in, n+m recourds out )
# for each process in case of dd test
# FIXME(Zam): redirection of dd output should help to get rid of this.
$skip = 2 * $proc_count if ($test_name eq "dd_writing_largefile" ||
$test_name eq "dd_reading_largefile");
my $real = (split ' ', $time_output[$skip])[1];
my $cpu = (split ' ', $time_output[2+$skip])[1];
unless ( $real =~ /\s*\d+/ && $cpu =~ /\s*\d+/) {
LOG "@time_output";
}
$total_real += $real;
$total_cpu += $cpu;
}
LOG "STAT REAL_TIME $total_real";
LOG "STAT CPU_TIME $total_cpu";
&stats_after($test_name);
# dmd: only do this at very end
# &umount_fsys;
# print "\nPress to continue ... ";
# my $tmp = ;
}
sub mongo_phase ()
{
my ($name, $handler, $rep_count, $proc_count) = @_;
my $opt = get_param("PHASE_" . $name);
if ($opt ne "off") {
return &$handler($name, $opt, $rep_count, $proc_count);
}
}
$check_err = " || touch " . $ERR_FILE;
sub mkdir_phase()
{
my ($name, $opt, $rep_count, $proc_count) = @_;
my $cmd =
"cd " . $OPTIONS{"DIR"} . "; $TOPDIR/mongo_mkdirs < $TOPDIR/dirs.txt; cd $TOPDIR";
&mongo_launcher ($name, $cmd);
}
sub mkfile_phase()
{
my ($name, $opt, $rep_count, $proc_count) = @_;
my $cmd =
"cd " . $OPTIONS{"DIR"} . "; $TOPDIR/mongo_mkfiles < $TOPDIR/files.txt; cd $TOPDIR";
&mongo_launcher ($name, $cmd);
}
#
# Handler for CREATE phase of mongo.
#
# Calls reiser_fract_tree with given parameters.
#
sub create_phase ()
{
my ($name, $opt, $rep_count, $proc_count) = @_;
my $cmd =
"$TOPDIR/reiser_fract_tree $bytes_for_one_process " .
$OPTIONS{"FILE_SIZE"} .
" $max_file_size $median_dir_nr_files " .
"$max_directory_nr_files $median_dir_branching $max_dir_branching ".
$OPTIONS{"WRITE_BUFFER"} . " " .
"$DIR0 $print_stats_flag " . $OPTIONS{"MAX_FNAME_LEN"} . " $FLIST0 " .
$OPTIONS{"SYNC"} . " " . $OPTIONS{"GAMMA"} . " $check_err";
&mongo_launcher ($name, $cmd);
}
#
# Handler for COPY phase of mongo.
#
# In "cp" mode, invokes cp(1) to copy files.
#
# In "list" mode (deafult) uses mongo_copy to copy files and update file lists.
#
sub copy_phase ()
{
my ($name, $opt) = @_;
my $write_buffer = $OPTIONS{"WRITE_BUFFER"};
my $sync = $OPTIONS{"SYNC"};
if ($opt eq "cp") {
&mongo_launcher ($name, "$TOPDIR/update-flist.pl $DIR0 $DIR1 $FLIST0 $FLIST1 " .
$check_err);
} elsif (($opt eq "") or ($opt eq "list")) {
&mongo_launcher ($name,
"cat $FLIST0 | " .
"$TOPDIR/mongo_copy $DIR0 $DIR1 $write_buffer $FLIST1 $sync $check_err");
} else {
&DIE("Unknown copy phase option `$opt'\n");
}
}
#
# Handler for APPEND phase of mongo.
#
# Calls mongo_append with given parameters.
#
sub append_phase ()
{
my ($name, $opt) = @_;
my $prefix;
my $write_buffer = $OPTIONS{"WRITE_BUFFER"};
my $sync = $OPTIONS{"SYNC"};
if ($opt eq "find") {
$prefix="find $DIR01 -type f";
} elsif (($opt eq "") or ($opt eq "on") or ($opt eq "list")) {
$prefix="cat $FLIST01 | $DIR_FILTER";
}
&mongo_launcher ($name,
"$prefix | $TOPDIR/mongo_append $append_factor $write_buffer $sync $check_err");
}
#
# Handler for MODIFY phase of mongo.
#
# Calls mongo_modify with given parameters.
#
sub modify_phase ()
{
my ($name, $opt) = @_;
my $prefix;
my $write_buffer = $OPTIONS{"WRITE_BUFFER"};
my $sync = $OPTIONS{"SYNC"};
if ($opt eq "find") {
$prefix="find $DIR01 -type f";
} elsif (($opt eq "") or ($opt eq "on") or ($opt eq "list")) {
$prefix="cat $FLIST01 | $DIR_FILTER";
}
&mongo_launcher ($name,
"$prefix | $TOPDIR/mongo_modify 0.02 $write_buffer $sync" .
$check_err);
}
#
# Handler for OVERWRITE phase of mongo.
#
# Calls mongo_modify with given parameters.
#
sub overwrite_phase ()
{
my ($name, $opt) = @_;
my $prefix;
my $write_buffer = $OPTIONS{"WRITE_BUFFER"};
my $sync = $OPTIONS{"SYNC"};
if ($opt eq "find") {
$prefix="find $DIR01 -type f";
} elsif (($opt eq "") or ($opt eq "on") or ($opt eq "list")) {
$prefix="cat $FLIST01 | $DIR_FILTER";
}
&mongo_launcher ($name,
"$prefix | $TOPDIR/mongo_modify 1 $write_buffer $sync" .
$check_err);
}
#
# Handler for SLINKS phase of mongo.
#
# Calls mongo_slinks.
#
sub slinks_phase ()
{
my ($name, $opt) = @_;
my $prefix;
if ($opt eq "find") {
$prefix="find $DIR01";
} elsif (($opt eq "") or ($opt eq "on") or ($opt eq "list")) {
$prefix="cat $FLIST01";
}
&mongo_launcher ($name,
"$prefix | while read X; do echo \\\$X \\\$X.lnk ; done | $TOPDIR/mongo_slinks");
}
#
# Handler for READ phase of mongo.
#
# Calls mongo_read.
#
sub read_phase ()
{
my ($name, $opt) = @_;
my $prefix;
if ($opt eq "find") {
$prefix="find $DIR01 -type f";
} elsif (($opt eq "") or ($opt eq "on") or ($opt eq "list")) {
$prefix="cat $FLIST01 | $DIR_FILTER";
}
&mongo_launcher ($name,
"$prefix | $TOPDIR/mongo_read" . $check_err);
}
#
# Handler for STATS phase of mongo.
#
# Performs stat(2) of each file in the working set, and, also readdir(2) on each
# directory.
#
sub stats_phase ()
{
my ($name, $opt) = @_;
my $prefix;
if ($opt eq "find") {
$prefix="find $DIR01 -name not_found ; true";
} elsif (($opt eq "") or ($opt eq "on") or ($opt eq "list")) {
$prefix="(cat $FLIST01 | xargs ls -d) > /dev/null";
}
&mongo_launcher ($name, $prefix . $check_err);
}
#
# Handler for RENAME phase of mongo.
#
# Moves files from source directory $DIR0 to target $DIR1.
#
sub rename_phase ()
{
my ($name, $opt) = @_;
my $prefix;
if ($opt eq "find") {
$prefix="find $DIR01 -type f";
} elsif (($opt eq "") or ($opt eq "on") or ($opt eq "list")) {
$prefix="cat $FLIST01 | $DIR_FILTER";
}
&mongo_launcher ($name,
"$prefix | " .
"perl -e \'while (<>) { chomp; \\\$n=\\\$o=\\\$_; \\\$n=~s|$DIR0|$DIR1| ; rename (\\\$o, \\\$n.r); }\'");
}
#
# Handler for DELETE phase of mongo.
#
# In "cp" mode, uses rm(1) to delete working set.
#
# In "list" mode (deafult) uses mongo_delete to delete working set.
#
sub delete_phase ()
{
my ($name, $opt) = @_;
if ($opt eq "rm") {
&mongo_launcher ($name, "rm -r $DIR01" . $check_err);
} elsif (($opt eq "") or ($opt eq "list")) {
&mongo_launcher ($name,
"cat $FLIST01 | $TOPDIR/mongo_delete ". $OPTIONS{"DIR"} .
" $check_err");
} else {
&DIE("Unknown delete phase option `$opt'\n");
}
}
#
# Handler for DD WRITE phase of mongo.
#
sub dd_write_phase ()
{
my ($name, $opt) = @_;
my $dir = $OPTIONS{"DIR"};
my $mbcount = $OPTIONS{"DD_MBCOUNT"};
&mongo_launcher($name, "dd if=/dev/zero of=$dir/largefile bs=1M count=$mbcount");
}
#
# Handler for DD READ phase of mongo.
#
sub dd_read_phase ()
{
my ($name, $opt) = @_;
my $dir = $OPTIONS{"DIR"};
my $mbcount = $OPTIONS{"DD_MBCOUNT"};
&mongo_launcher($name, "dd of=/dev/null if=$dir/largefile bs=1M count=$mbcount");
}
#------------------------------------------------------------------
# The main MONGO routine with does several tests sequentially.
#------------------------------------------------------------------
sub mongo
{
&init_fsys; # make and mount the file system
# my $check_err = " || ( err=\$\?; echo FAILED \$err > " . $ERR_FILE;
`rm ${ERR_FILE}` if (-e ${ERR_FILE});
system("rm -f /var/tmp/mongo.flist*");
&mongo_phase("MKDIRS", \&mkdir_phase);
&mongo_phase("MKFILES", \&mkfile_phase);
&mongo_phase("CREATE", \&create_phase);
&mongo_phase("COPY", \©_phase);
&mongo_phase("APPEND", \&append_phase);
&mongo_phase("MODIFY", \&modify_phase);
&mongo_phase("OVERWRITE", \&overwrite_phase);
# &mongo_phase("SLINKS", \&slinks_phase);
&mongo_phase("READ", \&read_phase);
&mongo_phase("STATS", \&stats_phase);
# &mongo_phase("RENAME", \&rename_phase);
&mongo_phase("DELETE", \&delete_phase);
if ($OPTIONS{"DD_MBCOUNT"}) {
&LOG ("SER DD_MBCOUNT=" . $OPTIONS{"DD_MBCOUNT"});
&mongo_phase ("dd_writing_largefile", \&dd_write_phase);
&mongo_phase ("dd_reading_largefile", \&dd_read_phase);
}
system("rm -f /var/tmp/mongo.flist*");
&done_fsys;
}
# parse a