#!/usr/bin/perl use strict; use warnings; use Data::Dumper; sub analyze_run_data { my $test_name = shift; my $data = shift; my $i; my $deviation; my $cnt; my $averages = {}; my $k; my $avg; $cnt = scalar @{$data}; for ($i = 0; $i < $cnt; ++$i) { if ($data->[$i]->{'swaps'} != 0) { # Did we run out of memory? warn(sprintf( "%s: run_try=%d: swaps is non-zero: %d\n", $test_name, $i + 1, $data->[$i]->{'swaps'} )); } if ($data->[$i]->{'sys_t'} + $data->[$i]->{'user_t'} > $data->[$i]->{'real_t'}) { warn(sprintf( "%s: run_try=%d: CPU time (%.1f) is bigger than wall-clock time (%.1f); multi-threaded?\n", $test_name, $i + 1, $data->[$i]->{'sys_t'} + $data->[$i]->{'user_t'}, $data->[$i]->{'real_t'} )); } } foreach $k (qw/ real_t sys_t user_t cpu_t /) { $averages->{$k} = 0; } for ($i = 0; $i < $cnt; ++$i) { $data->[$i]->{'cpu_t'} = $data->[$i]->{'sys_t'} + $data->[$i]->{'user_t'}; $data->[$i]->{'per_10_lines'} = {}; foreach $k (qw/ real_t sys_t user_t cpu_t /) { $data->[$i]->{'per_10_lines'}->{$k} = $data->[$i]->{$k} / $data->[$i]->{'nlines'} * 10; # XXX: Average values for 10 lines of output. # XXX: So that we can compare with the previous test method. $averages->{$k} += $data->[$i]->{'per_10_lines'}->{$k}; } } foreach $k (qw/ real_t sys_t user_t cpu_t /) { $averages->{$k} = $averages->{$k} / $cnt; } #print Dumper($data)."\n"; #print Dumper($averages)."\n"; for ($i = 0; $i < $cnt; ++$i) { foreach $k (qw/ real_t cpu_t /) { $avg = $averages->{$k}; $deviation = (($avg - $data->[$i]->{'per_10_lines'}->{$k}) / $avg) * 100; if (abs($deviation) > 5) { # percents warn(sprintf( "%s: run_try=%d: Measurement deviations are too high for '%s': %.1f\n", $test_name, $i + 1, $k, $deviation )); } } } return $averages; } sub _print_results_as_html_header { print <<'EOF' EOF } sub _print_results_as_html_footer { print <<'EOF'
Language CPU time Slower than Language
version
Source
code
User System Total C++ previous
EOF } sub _print_results_as_html_template { return <<'EOF' %s %.3f %.3f %.3f %s %s %s link EOF } sub _print_results_as_html_enrich_test_name { my $test_name = shift; if ($test_name eq 'C++ (optimized with -O2)') { return 'C++ (optimized with -O2)'; } if ($test_name =~ /^Java (\d)$/) { return 'Java '.$1.' (see notes)'; } if ($test_name =~ /^Java (\d) \(non-std lib\)$/) { return 'Java '.$1.' (non-std lib)'; } if ($test_name =~ /^Python (\d\.\d) \+ PyPy$/) { return 'Python '.$1.' + PyPy'; } if ($test_name eq 'JavaScript (nodejs)') { return 'Javascript (nodejs)'; } if ($test_name eq 'C++ (not optimized)') { return 'C++ (not optimized)'; } if ($test_name =~ /^C# .NET Core(.*)$/) { return 'C# .NET Core'.$1; } return $test_name; } sub _print_results_as_html { my $test_name = shift; my $averages = shift; my @slower = @{ $_[0] }; shift; my $test_data = shift; printf( _print_results_as_html_template(), _print_results_as_html_enrich_test_name($test_name), $averages->{'user_t'}, $averages->{'sys_t'}, $averages->{'cpu_t'}, $slower[0], $slower[1], $test_data->{'meta'}->{'version'}, $test_data->{'meta'}->{'src_file'} ); } sub _print_results_as_text { my $test_name = shift; my $averages = shift; my @slower = @{ $_[0] }; shift; my $test_data = shift; printf( "%-30s: user_t=%6.3f sys_t=%6.3f cpu_t=%6.3f to_CPP=%5s to_prev=%5s version=%s\n", $test_name, $averages->{'user_t'}, $averages->{'sys_t'}, $averages->{'cpu_t'}, $slower[0], $slower[1], $test_data->{'meta'}->{'version'} ); } sub print_results { my $output = shift; my @sorted_all = @{ $_[0] }; shift; my $test_data; my $test_name; my $averages; my @slower = (); my $idx = -1; my ($sl_idx, $src_idx); my $ratio; my @args; foreach (@sorted_all) { $test_name = $_->{'test_name'}; $test_data = $_->{'test_data'}; $averages = $_->{'averages'}; ++$idx; #print Dumper($test_data->{'run_data'})."\n"; #print Dumper($averages)."\n"; if ($idx == 0) { $slower[0] = '-'; $slower[1] = '-'; } else { foreach $sl_idx (0,1) { if ($sl_idx == 0) { $src_idx = 0; } else { $src_idx = $idx - 1; } $ratio = $averages->{'cpu_t'} / $sorted_all[$src_idx]->{'averages'}->{'cpu_t'}; $slower[$sl_idx] = sprintf('%d%%', ($ratio - 1.00) * 100); } } @args = ($test_name, $averages, \@slower, $test_data); if ($output eq 'text') { _print_results_as_text(@args); } elsif ($output eq 'html') { _print_results_as_html(@args); } else { die("Unknown output: $output"); } } } sub usage { die("Usage: $0 [text|html]\nRead results at STDIN and format them as specified.\n"); } sub parse_ARGV { my $output_mode = shift @ARGV; if (!defined($output_mode)) { usage(); } if ($output_mode !~ /^(text|html)$/) { usage(); } return $output_mode; } sub main { my $line; my $data = {}; my $averages; my $k; my $test_data; my $test_name; my @sorted_all = (); my $output_mode; $output_mode = parse_ARGV(); while ($line = ) { chomp($line); if ($line =~ /^(\s*#|SKIPPING)/) { next; } if ($line !~ /^real_TIME:(\d+\.\d+)sec user_CPU:(\d+\.\d+)sec sys_CPU:(\d+\.\d+)sec max_RSS:(\d+)kb swaps:(\d+) ctx_sw:(\d+)\+(\d+) nlines:(\d+) run_try:(\d+) header:'([^']+)' version:'([^']+)' src_file:(\S+)$/) { die("Unable to parse line: $line\n"); } my $row = {}; # new reference every time my $run_try; my $test_name; $row->{'real_t'} = $1; $row->{'user_t'} = $2; $row->{'sys_t'} = $3; $row->{'max_RSS'} = $4; $row->{'swaps'} = $5; $row->{'ctx_sw_unvol'} = $6; $row->{'ctx_sw_vol'} = $7; $row->{'nlines'} = $8; $run_try = $9 - 1; # XXX: fix index # $test_name = $10; $row->{'version'} = $11; $row->{'src_file'} = $12; # trim white-space $row->{'version'} =~ s/^\s*//; $row->{'version'} =~ s/\s*$//; if (!exists($data->{$test_name})) { $data->{$test_name} = { 'meta' => {}, 'run_data' => [], }; foreach $k (qw/ version src_file /) { $data->{$test_name}->{'meta'}->{$k} = $row->{$k}; } } foreach $k (qw/ version src_file /) { if ($data->{$test_name}->{'meta'}->{$k} ne $row->{$k}) { die("Sanity check failed for meta: $line"); } } if (exists($data->{$test_name}->{'run_data'}->[$run_try])) { die("Duplicate run#: $line"); } delete($row->{'version'}); delete($row->{'src_file'}); $data->{$test_name}->{'run_data'}->[$run_try] = $row; foreach $k (qw/ version src_file /) { delete($data->{$test_name}->{'run_data'}->[$run_try]->{$k}); } } while (($test_name, $test_data) = each %{$data}) { $averages = analyze_run_data($test_name, $test_data->{'run_data'}); push(@sorted_all, { 'test_name' => $test_name, 'test_data' => $test_data, 'averages' => $averages, }); } @sorted_all = sort { $a->{'averages'}->{'cpu_t'} <=> $b->{'averages'}->{'cpu_t'}; } @sorted_all; if ($output_mode eq 'html') { _print_results_as_html_header(); } print_results($output_mode, \@sorted_all); if ($output_mode eq 'html') { _print_results_as_html_footer(); } } main();