# Generate an ascii summary from lmbench result files BY HOSTNAME # instead of architecture. Sorry, I think of these tools as being # used to measure and prototype particular named systems, not as # being useful to measure once and for all "i686-linux" systems, # which might well have different motherboards, chipsets, memory # clocks, CPU's (anything from PPro through to PIII so far) and # so forth. Linux systems are far to heterogeneous to be categorized # with two or three descriptors, so might as well just use hostname # for shorthand... # # Usage: statsummary file file file... # # Hacked into existence by Larry McVoy (lm@sun.com now lm@sgi.com). # Copyright (c) 1994 Larry McVoy. GPLed software. # # $Id: statsummary,v 1.5 2000/07/08 21:06:49 rgb Exp $ # # # Edit History. I'm starting out with Larry's getsummary. Then I'm # going to splice in a very simple set of stats routines that are # passed an array in his standard form and return a structure containing # max, min, mean, median, unbiased standard deviation and we'll go from # there. However I'll likely print out only mean and SD and will try # to preserve Larry's general layout at that. Oh, and I'm going to add # COMMENTS to the script. Drives me nuts to work on something without # comments. 7/6/00 eval 'exec perl -Ssw $0 "$@"' if 0; # # This segment loops through all the output files and pushes the # specific field values it needs into suitably named arrays. It # counts while it does so so it can check to be sure that all # the input files are complete. $n = 0; @hosts = (); foreach $file (@ARGV) { open(FD, $file) || die "$0: can't open $file"; # I just want @file to contain the hostname, not the path or architecture. # However, we have reason to need the associated filename (no path) to # to help with debugging. # Strip off the path $file =~ s/(.*)\///; # Split the filename from the number. This will probably break if the # hostname contains more "."'s. However, I'm too lazy to figure out # how to make this work totally robustly. It would be easy if the # the host datafiles were all created according to the "hostname.count" # format, because then a simple regexp would pull off just the hostname # or the count. Not so easy when a hostname/count might contain no "."'s # at all... $filecount = ""; ($file,$filecount) = split(/\./,$file); # fix silly bug caused by starting numbering at blank. if(! $filecount){ $filecount = 0; } # Debugging... # print STDERR "Found file $file with count $filecount\n"; push(@file, $file); push(@filecount, $filecount); # This should just push UNIQUE new hosts onto @hosts. $numhosts = @hosts; if($numhosts){ $lasthost = $hosts[$numhosts-1]; } else { $lasthost = ""; } if($lasthost !~ /$file/){ push(@hosts, $file); } $mhz = 0; while () { chop; next if m|scripts/lmbench: /dev/tty|; if (/^\[lmbench/) { push(@uname, $_); if (/lmbench1\./) { $version = 1; } else { $version = 2; } } if (/MHZ/ && !$mhz) { @_ = split; $_[1] =~ s/\]//; push(@misc_mhz, $_[1]); $mhz = 1; } elsif (/Mhz/ && !$mhz) { @_ = split; push(@misc_mhz, $_[0]); $mhz = 1; } if (/^Select on 100 fd/) { @_ = split; push(@lat_select, $_[4]); $tmp = $lat_select[0]; # Just to shut up the error parser } if (/^Simple syscall:/) { @_ = split; push(@lat_syscall, $_[2]); $tmp = $lat_syscall[0]; # Just to shut up the error parser } if (/^Simple read:/) { @_ = split; push(@lat_read, $_[2]); $tmp = $lat_read[0]; # Just to shut up the error parser } if (/^Simple write:/) { @_ = split; push(@lat_write, $_[2]); $tmp = $lat_write[0]; # Just to shut up the error parser } if (/^Simple stat:/) { @_ = split; push(@lat_stat, $_[2]); $tmp = $lat_stat[0]; # Just to shut up the error parser } if (/^Simple open.close:/) { @_ = split; push(@lat_openclose, $_[2]); $tmp = $lat_openclose[0]; # Just to shut up the error parser } if (/^Null syscall:/) { # Old format. @_ = split; push(@lat_write, $_[2]); $tmp = $lat_write[0]; # Just to shut up the error parser } if (/^Signal handler installation:/) { @_ = split; push(@lat_siginstall, $_[3]); $tmp = $lat_siginstall[0]; # Just to shut up the error parser } if (/^Signal handler overhead:/) { @_ = split; push(@lat_sigcatch, $_[3]); $tmp = $lat_sigcatch[0]; # Just to shut up the error parser } if (/^Protection fault:/) { @_ = split; push(@lat_protfault, $_[2]); $tmp = $lat_protfault[0]; # Just to shut up the error parser } if (/^Pipe latency:/) { @_ = split; push(@lat_pipe, $_[2]); $tmp = $lat_pipe[0]; # Just to shut up the error parser } if (/AF_UNIX sock stream latency:/) { @_ = split; push(@lat_unix, $_[4]); $tmp = $lat_unix[0]; # Just to shut up the error parser } if (/^UDP latency using /) { if(/localhost:/) { @_ = split; push(@lat_udp_local, $_[4]); $tmp = $lat_udp_local[0]; # Just to shut up the error parser } else { @_ = split; push(@lat_udp_net, $_[4]); $tmp = $lat_udp_net[0]; # Just to shut up the error parser } } if (/^TCP latency using /) { if(/localhost:/) { @_ = split; push(@lat_tcp_local, $_[4]); $tmp = $lat_tcp_local[0]; # Just to shut up the error parser } else { @_ = split; push(@lat_tcp_net, $_[4]); $tmp = $lat_tcp_net[0]; # Just to shut up the error parser } } if (/^RPC\/udp latency using /) { if(/localhost:/) { @_ = split; push(@lat_rpc_udp_local, $_[4]); $tmp = $lat_rpc_udp_local[0]; # Just to shut up the error parser } else { @_ = split; push(@lat_rpc_udp_net, $_[4]); $tmp = $lat_rpc_udp_net[0]; # Just to shut up the error parser } } if (/^RPC\/tcp latency using /) { if(/localhost:/) { @_ = split; push(@lat_rpc_tcp_local, $_[4]); $tmp = $lat_rpc_tcp_local[0]; # Just to shut up the error parser } else { @_ = split; push(@lat_rpc_tcp_net, $_[4]); $tmp = $lat_rpc_tcp_net[0]; # Just to shut up the error parser } } if (/^TCP\/IP connection cost to /) { if(/localhost:/) { @_ = split; push(@lat_tcp_connect_local, $_[5]); $tmp = $lat_tcp_connect_local[0]; # Just to shut up the error parser } else { @_ = split; push(@lat_tcp_connect_net, $_[5]); $tmp = $lat_tcp_connect_net[0]; # Just to shut up the error parser } } if (/^Socket bandwidth using /) { if(/localhost:/) { @_ = split; push(@bw_tcp_local, $_[4]); $tmp = $bw_tcp_local[0]; # Just to shut up the error parser } else { @_ = split; push(@bw_tcp_net, $_[4]); $tmp = $bw_tcp_net[0]; # Just to shut up the error parser } } if (/^AF_UNIX sock stream bandwidth:/) { @_ = split; push(@bw_unix, $_[4]); $tmp = $bw_unix[0]; # Just to shut up the error parser } if (/^Process fork.exit/) { @_ = split; push(@lat_nullproc, $_[2]); $tmp = $lat_nullproc[0]; # Just to shut up the error parser } if (/^Process fork.execve:/) { @_ = split; push(@lat_simpleproc, $_[2]); $tmp = $lat_simpleproc[0]; # Just to shut up the error parser } if (/^Process fork..bin.sh/) { @_ = split; push(@lat_shproc, $_[3]); $tmp = $lat_shproc[0]; # Just to shut up the error parser } if (/^Pipe bandwidth/) { @_ = split; push(@bw_pipe, $_[2]); $tmp = $bw_pipe[0]; # Just to shut up the error parser } if (/^File .* write bandwidth/) { @_ = split; $bw = sprintf("%.2f", $_[4] / 1024.); push(@bw_file, $bw); $tmp = $bw_file[0]; # Just to shut up the error parser } if (/^Pagefaults on/) { @_ = split; push(@lat_pagefault, $_[3]); $tmp = $lat_pagefault[0]; # Just to shut up the error parser } if (/^"mappings/) { $value = &getbiggest("memory mapping timing"); push(@lat_mappings, $value); $tmp = $lat_mappings[0]; # Just to shut up the error parser } if (/^"read bandwidth/) { $value = &getbiggest("reread timing"); push(@bw_reread, $value); $tmp = $bw_reread[0]; # Just to shut up the error parser } if (/^"Mmap read bandwidth/) { $value = &getbiggest("mmap reread timing"); push(@bw_mmap, $value); $tmp = $bw_mmap[0]; # Just to shut up the error parser } if (/^"libc bcopy unaligned/) { $value = &getbiggest("libc bcopy timing"); push(@bw_bcopy_libc, $value); $tmp = $bw_bcopy_libc[0]; # Just to shut up the error parser } if (/^"unrolled bcopy unaligned/) { $value = &getbiggest("unrolled bcopy timing"); push(@bw_bcopy_unrolled, $value); $tmp = $bw_bcopy_unrolled[0]; # Just to shut up the error parser } if (/^Memory read/) { $value = &getbiggest("memory read & sum timing"); push(@bw_mem_rdsum, $value); $tmp = $bw_mem_rdsum[0]; # Just to shut up the error parser } if (/^Memory write/) { $value = &getbiggest("memory write timing"); push(@bw_mem_wr, $value); $tmp = $bw_mem_wr[0]; # Just to shut up the error parser } if (/^"File system latency/) { while () { next if /Id:/; if (/^0k/) { @_ = split; push(@fs_create_0k, $_[2]); push(@fs_delete_0k, $_[3]); $tmp = $fs_create_0k[0]; # Just to shut up the error parser $tmp = $fs_delete_0k[0]; # Just to shut up the error parser } elsif (/^1k/) { @_ = split; push(@fs_create_1k, $_[2]); push(@fs_delete_1k, $_[3]); $tmp = $fs_create_1k[0]; # Just to shut up the error parser $tmp = $fs_delete_1k[0]; # Just to shut up the error parser } elsif (/^4k/) { @_ = split; push(@fs_create_4k, $_[2]); push(@fs_delete_4k, $_[3]); $tmp = $fs_create_4k[0]; # Just to shut up the error parser $tmp = $fs_delete_4k[0]; # Just to shut up the error parser } elsif (/^10k/) { @_ = split; push(@fs_create_10k, $_[2]); push(@fs_delete_10k, $_[3]); $tmp = $fs_create_10k[0]; # Just to shut up the error parser $tmp = $fs_delete_10k[0]; # Just to shut up the error parser } else { last; } } } if (/size=0/) { while () { if (/^2 /) { @_ = split; push(@lat_ctx0_2, $_[1]); $tmp = $lat_ctx0_2[0]; # Just to shut up the error parser } elsif (/^8 /) { @_ = split; push(@lat_ctx0_8, $_[1]); $tmp = $lat_ctx0_8[0]; # Just to shut up the error parser } elsif (/^16 /) { @_ = split; push(@lat_ctx0_16, $_[1]); $tmp = $lat_ctx0_16[0]; # Just to shut up the error parser } last if /^\s*$/ || /^Memory/; } } if (/size=16/) { while () { if (/^2 /) { @_ = split; push(@lat_ctx16_2, $_[1]); $tmp = $lat_ctx16_2[0]; # Just to shut up the error parser } elsif (/^8 /) { @_ = split; push(@lat_ctx16_8, $_[1]); $tmp = $lat_ctx16_8[0]; # Just to shut up the error parser } elsif (/^16 /) { @_ = split; push(@lat_ctx16_16, $_[1]); $tmp = $lat_ctx16_16[0]; # Just to shut up the error parser } last if /^\s*$/; } } if (/size=64/) { while () { if (/^2 /) { @_ = split; push(@lat_ctx64_2, $_[1]); $tmp = $lat_ctx64_2[0]; # Just to shut up the error parser } elsif (/^8 /) { @_ = split; push(@lat_ctx64_8, $_[1]); $tmp = $lat_ctx64_8[0]; # Just to shut up the error parser } elsif (/^16 /) { @_ = split; push(@lat_ctx64_16, $_[1]); $tmp = $lat_ctx64_16[0]; # Just to shut up the error parser } last if /^\s*$/ || /^20/; } } if (/^"stride=128/) { $save = -1; while () { if (/^0.00098\s/) { @_ = split; push(@lat_l1, $_[1]); $tmp = $lat_l1[0]; # Just to shut up the error parser } elsif (/^0.12500\s/) { @_ = split; push(@lat_l2, $_[1]); $tmp = $lat_l2[0]; # Just to shut up the error parser } elsif (/^[45678].00000\s/) { @_ = split; $size = $_[0]; $save = $_[1]; last if /^8.00000\s/; } elsif (/^\s*$/) { last; } } if (!/^8/) { warn "$file: No 8MB memory latency, using $size\n"; } push(@lat_mem, $save); } } @warn = (); foreach $array ( 'bw_bcopy_libc', 'bw_bcopy_unrolled', 'bw_file', 'bw_mem_rdsum', 'bw_mem_wr', 'bw_mmap', 'bw_pipe', 'bw_reread', 'bw_tcp_local', 'bw_unix', 'fs_create_0k','fs_delete_0k', 'fs_create_1k','fs_delete_1k', 'fs_create_4k','fs_delete_4k', 'fs_create_10k','fs_delete_10k', 'lat_ctx0_16', 'lat_ctx0_2', 'lat_ctx0_8', 'lat_ctx16_16', 'lat_ctx16_2', 'lat_ctx16_8', 'lat_ctx64_16', 'lat_ctx64_2', 'lat_ctx64_8', 'lat_l1', 'lat_l2', 'lat_mappings', 'lat_mem', 'lat_nullproc', 'lat_openclose', 'lat_pagefault', 'lat_pipe', 'lat_protfault', 'lat_read', 'lat_rpc_tcp_local','lat_rpc_udp_local', 'lat_tcp_connect_local', 'lat_tcp_local', 'lat_udp_local', 'lat_rpc_tcp_net','lat_rpc_udp_net', 'lat_tcp_connect_net', 'lat_tcp_net', 'lat_udp_net', 'lat_select', 'lat_shproc', 'lat_sigcatch', 'lat_siginstall', 'lat_simpleproc', 'lat_stat', 'lat_syscall', 'lat_unix', 'lat_write', 'misc_mhz', ) { $last = eval '$#' . $array; if ($last != $n) { #warn "No data for $array in $file\n"; push(@warn, $array); eval 'push(@' . $array . ', -1);'; } } if ($#warn != -1) { warn "Missing data in $file: @warn\n"; } $n++; } # # OK, now all those arrays are packed. Because everything is keyed # on raw hostname, we can do all the stats evaluations using a combination # of @file and the array -- we march through @file and create a stats # object (a % hash) with its name and do the obvious sums and so forth. # should be very simple. # # However, to be fair to Larry, we do want to preserve the general flavor # of the summary. However, the summary is now going to be output BY HOST # and so we need a separate host-description section for each host. # # First we have to evaluate the stats, though. # # # Let's test this with just one small set of values... foreach $array ( 'bw_bcopy_libc', 'bw_bcopy_unrolled', 'bw_file', 'bw_mem_rdsum', 'bw_mem_wr', 'bw_mmap', 'bw_pipe', 'bw_reread', 'bw_tcp_local', 'bw_unix', 'fs_create_0k','fs_delete_0k', 'fs_create_1k','fs_delete_1k', 'fs_create_4k','fs_delete_4k', 'fs_create_10k','fs_delete_10k', 'lat_l1', 'lat_l2', 'lat_mappings', 'lat_mem', 'lat_nullproc', 'lat_openclose', 'lat_pagefault', 'lat_pipe', 'lat_protfault', 'lat_read', 'lat_rpc_tcp_local','lat_rpc_udp_local', 'lat_tcp_connect_local', 'lat_tcp_local', 'lat_udp_local', 'lat_rpc_tcp_net','lat_rpc_udp_net', 'lat_tcp_connect_net', 'lat_tcp_net', 'lat_udp_net', 'lat_select', 'lat_shproc', 'lat_sigcatch', 'lat_siginstall', 'lat_simpleproc', 'lat_stat', 'lat_syscall', 'lat_unix', 'lat_write', 'misc_mhz', ) { } # Empty just to save the full list someplace handy. # # Oops. For some unfathomable reason lat_fs returns something other than # an (average) time in nanoseconds. Why, I cannot imagine -- one could # trivially invert so that it did so. One CANNOT DO STATS on inverse # quantities, so we invert here and convert to nanoseconds # so we can correctly do stats below. foreach $array ( 'fs_create_0k','fs_delete_0k','fs_create_1k','fs_delete_1k', 'fs_create_4k','fs_delete_4k','fs_create_10k','fs_delete_10k', ) { $cnt = 0; foreach $entry (@$array){ $$array[$cnt++] = 1.0e+9/$entry; } } # Working copy. Let's just add things as they turn out to be # appropriate. In fact, we'll add them in presentation order! foreach $array ( 'lat_syscall','lat_read', 'lat_write', 'lat_syscall', 'lat_stat', 'lat_openclose','lat_select','lat_siginstall','lat_sigcatch', 'lat_nullproc','lat_simpleproc','lat_shproc', 'lat_ctx0_2','lat_ctx0_16','lat_ctx0_8', 'lat_ctx16_16','lat_ctx16_2','lat_ctx16_8', 'lat_ctx64_16','lat_ctx64_2','lat_ctx64_8', 'lat_pipe','lat_unix', 'lat_udp_local','lat_tcp_local',lat_tcp_connect_local, 'lat_rpc_udp_local','lat_rpc_tcp_local', 'lat_udp_net','lat_tcp_net',lat_tcp_connect_net, 'lat_rpc_udp_net','lat_rpc_tcp_net', 'fs_create_0k','fs_delete_0k', 'fs_create_1k','fs_delete_1k', 'fs_create_4k','fs_delete_4k', 'fs_create_10k','fs_delete_10k', 'lat_mappings','lat_protfault','lat_pagefault', 'bw_pipe','bw_unix', 'bw_tcp_local', # Note we need bw_udp_local as soon as it exists... 'bw_reread','bw_mmap','bw_bcopy_libc','bw_bcopy_unrolled', 'bw_mem_rdsum','bw_mem_wr', 'bw_tcp_net', 'lat_l1','lat_l2','lat_mem', ) { # # This should do it all, by name and collapsed by hostname # makestats($array); } # # Fine, that seems to work. Now we break up the summary, BY HOST. # For each host we print just ONE TIME key values that don't really # vary (like its architecture information and clock). Then we print # out a modified version of Larry's old summary. # # # First the header # print< 1) { chop($fmt); } for ($i = 0; $i < $fmt; $i++) { $str .= " "; } return ($str); } $str = sprintf($fmt, $val); $str; } # Input looks like # "benchmark name # size value # .... # # # Return the biggest value before the blank line. sub getbiggest { local($msg) = @_; local($line) = 0; undef $save; $value = 0; while () { $line++; #warn "$line $_"; last if /^\s*$/; $save = $_ if /^\d+\./; } if (defined $save) { $_ = $save; @d = split; $value = $d[1]; if (int($d[0]) < 4) { warn "$file: using $d[0] size for $msg\n"; } } else { warn "$file: no data for $msg\n"; } $value; } # Try and create sensible names from uname -a output sub getos { local(@info); @info = split(/\s+/, $_[0]); "$info[3] $info[5]"; } # Return true if the values differe by less than 10% sub same { local($a, $b) = @_; if ($a > $b) { $percent = (($a - $b) / $a) * 100; } else { $percent = (($b - $a) / $b) * 100; } return ($percent <= 20); } sub check_caches { if (!&same($lat_l1[$i], $lat_l2[$i]) && &same($lat_l2[$i], $lat_mem[$i])) { " No L2 cache?"; } elsif (&same($lat_l1[$i], $lat_l2[$i])) { " No L1 cache?"; } } sub makestats { my $cnt=0; my $host; # Debugging # print STDERR "Ready to make stats for array $array\n"; # Zero the counters $numhosts = @hosts; for($i=0;$i<$numhosts;$i++){ $host = $hosts[$i]; $stats{$host}{$array}{mean} = 0.0; $stats{$host}{$array}{stddev} = 0.0; $stats{$host}{$array}{count} = 0; } # Loop through ALL DATA. We use the hash to direct results to # to the appropriate counters. foreach $value (@$array){ $host = $file[$cnt]; if($$array[0] == -1){ $stats{$host}{$array}{mean} = -1; $stats{$host}{$array}{stddev} = -1; # Debugging (and curiosity) print STDERR "Oops. $array is empty.\n"; return; } # Debugging # print STDERR "$host/$array ($cnt): value is $value\n"; $stats{$host}{$array}{mean} += $value; $stats{$host}{$array}{stddev} += $value*$value; $stats{$host}{$array}{count}++; $cnt++; } for($i=0;$i<$numhosts;$i++){ $host = $hosts[$i]; $cnt = $stats{$host}{$array}{count}; # Debugging Only # print STDERR "Evaluating final mean/stddev of $cnt objects in $host/$array\n"; if($cnt>1) { $stats{$host}{$array}{mean} = $stats{$host}{$array}{mean} / $cnt; $stats{$host}{$array}{stddev} = sqrt(($stats{$host}{$array}{stddev} / $cnt - $stats{$host}{$array}{mean}*$stats{$host}{$array}{mean})/($cnt-1)); } elsif($cnt == 1) { # Wish one could assign "infinity". This probably breaks somewhere. $stats{$host}{$array}{stddev} = 1.0e+1000; } else { # print STDERR "Error: Cannot average 0 $array results on $host\n"; } # Debugging Only. # print STDERR "$host/$array (average): $stats{$host}{$array}{mean} +/- $stats{$host}{$array}{stddev}\n"; } }