blob: 0c15b25117799a75e4bd6c00580c678341eb1100 [file] [log] [blame]
#!/usr/bin/perl
###################################################################################
# This script is used to extract and verify statistics of one or more VTR tasks.
#
# Usage:
# parse_vtr_task.pl <task_name1> <task_name2> ... [OPTIONS]
#
# Options:
# -l <task_list_file>: Used to provide a test file containing a list of tasks
# -create_golden: Will create/overwrite the golden results with those of the
# most recent execution
# -check_golden: Will verify the results of the most recent execution against
# the golden results for each task and report either a
# [Pass] or [Fail]
# -parse_qor: Used for the purposes of parsing quality of results of the
# most recent execution.
# -calc_geomean: Used for the purposes of computing quality of results geomeans
# of the most recent execution.
#
# Authors: Jason Luu and Jeff Goeders
###################################################################################
use strict;
use Cwd;
use File::Spec;
use File::Copy;
use List::Util;
use Math::BigInt;
use POSIX qw/strftime/;
# Function Prototypes
sub trim;
sub parse_single_task;
sub pretty_print_table;
sub summarize_qor;
sub calc_geomean;
sub check_golden;
sub expand_user_path;
sub get_important_file;
# Get Absolute Path of 'vtr_flow
Cwd::abs_path($0) =~ m/(.*vtr_flow)/;
my $vtr_flow_path = $1;
my $run_prefix = "run";
my $FAILED_PARSE_EXIT_CODE = -1;
# Parse Input Arguments
my @tasks;
my @task_files;
my $token;
my $create_golden = 0;
my $check_golden = 0;
my $parse_qor = 1; # QoR file is parsed by default; turned off if
# user does not specify QoR parse file in config.txt
my $calc_geomean = 0; # QoR geomeans are not computed by default;
my $override_exp_id = 0;
my $revision;
my $verbose = 0;
my $pretty_print_results = 1;
while ( $token = shift(@ARGV) ) {
# Check for a task list file
if ( $token =~ /^-l(.+)$/ ) {
push( @task_files, expand_user_path($1) );
}
elsif ( $token eq "-l" ) {
push( @task_files, expand_user_path( shift(@ARGV) ) );
}
elsif ( $token eq "-create_golden" ) {
$create_golden = 1;
}
elsif ( $token eq "-check_golden" ) {
$check_golden = 1;
}
elsif ( $token eq "-parse_qor" ) {
$parse_qor = 1;
}
elsif ( $token eq "-calc_geomean" ) {
$calc_geomean = 1;
}
elsif ( $token eq "-run") {
$override_exp_id = shift(@ARGV);
}
elsif ( $token eq "-revision" ) {
$revision = shift(@ARGV);
}
elsif ( $token eq "-v" ) {
$verbose = 1;
}
elsif ( $token =~ /^-/ ) {
die "Invalid option: $token\n";
}
# must be a task name
else {
if ( $token =~ /(.*)\/$/ ) {
$token = $1;
}
push( @tasks, $token );
}
}
# Read Task Files
foreach (@task_files) {
open( FH, $_ ) or die "$! ($_)\n";
while (<FH>) {
push( @tasks, $_ );
}
close(FH);
}
my $num_golden_failures = 0;
foreach my $task (@tasks) {
chomp($task);
my $failures = parse_single_task($task);
$num_golden_failures += $failures;
}
if ($calc_geomean) {
summarize_qor;
calc_geomean;
}
exit $num_golden_failures;
sub parse_single_task {
my $task_name = shift;
(my $task_path = $task_name) =~ s/\s+$//;
# first see if task_name is the task path
if (! -e "$task_path/config/config.txt") {
($task_path = "$vtr_flow_path/tasks/$task_name") =~ s/\s+$//;
}
open( CONFIG, "<$task_path/config/config.txt" )
or die "Failed to open $task_path/config/config.txt: $!";
my @config_data = <CONFIG>;
close(CONFIG);
my @circuits;
my $parse_file;
my $qor_parse_file;
my $second_parse_file;
my @archs;
my $counter = 0;
foreach my $line (@config_data) {
# Ignore comments
if ( $line =~ /^\s*#.*$/ or $line =~ /^\s*$/ ) { next; }
#Trim off a line-ending comment
$line =~ s/#.*$//;
my @data = split( /=/, $line );
my $key = trim( $data[0] );
my $value = trim( $data[1] );
if ( $key eq "circuit_list_add" ) {
push( @circuits, $value );
}
elsif ( $key eq "arch_list_add" ) {
push( @archs, $value );
}
elsif ( $key eq "parse_file" ) {
if ($counter eq 1){
#second time parse file
$second_parse_file = expand_user_path($value);
$counter = 0;
#don't need to check golden, only compare between two files
$check_golden = 0;
}
else{
$parse_file = expand_user_path($value);
$counter = $counter + 1;
}
}
elsif ( $key eq "qor_parse_file" ) {
$qor_parse_file = expand_user_path($value);
}
}
# PARSE CONFIG FILE
if ( $parse_file eq "" ) {
die "Task $task_name has no parse file specified.\n";
}
$parse_file = get_important_file($task_path, $vtr_flow_path, $parse_file);
if ($second_parse_file){
$second_parse_file = get_important_file($task_path, $vtr_flow_path, $second_parse_file);
}
# Get Max Run #
opendir(DIR, $task_path);
my @folders = readdir(DIR);
closedir(DIR);
# QOR PARSE CONFIG FILE
if ( $qor_parse_file eq "" ) {
print "Task $task_name has no QoR parse file specified. Skipping QoR.\n";
$parse_qor = 0;
$calc_geomean = 0;
}
else {
$qor_parse_file = get_important_file($task_path, $vtr_flow_path, $qor_parse_file);
}
my $exp_id = 0;
if($override_exp_id != 0) {
#explicitely specified via -run parameter
$exp_id = $override_exp_id;
} else {
# haven't explicitely specified via -run parameter
$exp_id = last_exp_id(${task_path});
}
my $run_path = "$task_path/${run_prefix}${exp_id}";
my $first = 1;
open( OUTPUT_FILE, ">$run_path/parse_results.txt" );
foreach my $arch (@archs) {
foreach my $circuit (@circuits) {
my $cmd = "$vtr_flow_path/scripts/parse_vtr_flow.pl $run_path/$arch/$circuit $parse_file > $run_path/$arch/$circuit/parse_results.txt";
my $ret = system($cmd);
if ($ret != 0) {
print "System command '$cmd' failed\n";
exit $FAILED_PARSE_EXIT_CODE;
}
open( RESULTS_FILE, "$run_path/$arch/$circuit/parse_results.txt" );
# first line is heading
my $output = <RESULTS_FILE>;
if ($first) {
print OUTPUT_FILE "arch\tcircuit\t$output";
$first = 0;
}
# second line is actual value
my $output = <RESULTS_FILE>;
close(RESULTS_FILE);
print OUTPUT_FILE $arch . "\t" . $circuit . "\t" . $output;
}
}
close(OUTPUT_FILE);
#parse the second file the same way as the first if checking rr graph
if($second_parse_file){
my $run_path = "$task_path/${run_prefix}${exp_id}";
my $first = 1;
open( OUTPUT_FILE, ">$run_path/parse_results_2.txt" );
foreach my $arch (@archs) {
foreach my $circuit (@circuits) {
my $cmd = "$vtr_flow_path/scripts/parse_vtr_flow.pl $run_path/$arch/$circuit $second_parse_file > $run_path/$arch/$circuit/parse_results_2.txt";
my $ret = system($cmd);
if ($ret != 0) {
print "System command '$cmd' failed\n";
exit $FAILED_PARSE_EXIT_CODE;
}
open( RESULTS_FILE, "$run_path/$arch/$circuit/parse_results_2.txt" );
# first line is heading
my $output = <RESULTS_FILE>;
if ($first) {
print OUTPUT_FILE "arch\tcircuit\t$output";
$first = 0;
}
# second line is actual value
my $output = <RESULTS_FILE>;
close(RESULTS_FILE);
print OUTPUT_FILE $arch . "\t" . $circuit . "\t" . $output;
}
}
close(OUTPUT_FILE);
}
if ($pretty_print_results) {
pretty_print_table("$run_path/parse_results.txt");
if ($second_parse_file) {
pretty_print_table("$run_path/parse_results_2.txt");
}
}
if ($parse_qor) {
my $first = 1;
open( OUTPUT_FILE, ">$run_path/qor_results.txt" );
foreach my $arch (@archs) {
foreach my $circuit (@circuits) {
my $cmd = "$vtr_flow_path/scripts/parse_vtr_flow.pl $run_path/$arch/$circuit $qor_parse_file > $run_path/$arch/$circuit/qor_results.txt";
my $ret = system($cmd);
if ($ret != 0) {
print "System command '$cmd' failed\n";
exit $FAILED_PARSE_EXIT_CODE;
}
open( RESULTS_FILE, "$run_path/$arch/$circuit/qor_results.txt" );
my $output = <RESULTS_FILE>;
if ($first) {
print OUTPUT_FILE "arch\tcircuit\t$output";
$first = 0;
}
my $output = <RESULTS_FILE>;
close(RESULTS_FILE);
print OUTPUT_FILE $arch . "\t" . $circuit . "\t" . $output;
}
}
close(OUTPUT_FILE);
}
if ($create_golden) {
copy( "$run_path/parse_results.txt",
"$run_path/../config/golden_results.txt" );
}
if ($second_parse_file){
#don't check with golden results, just check the two files
return check_two_files ( $task_name, $task_path, $run_path, "$run_path/parse_results.txt", "$run_path/parse_results_2.txt", 0);
}
if ($check_golden) {
#Returns 1 if failed
if ($second_parse_file eq ""){
return check_two_files( $task_name, $task_path, $run_path, "$run_path/parse_results.txt", "$task_path/config/golden_results.txt", $check_golden);
}
}
return 0; #Pass
}
sub summarize_qor {
##############################################################
# Set up output file
##############################################################
my $first = 1;
my $task = @tasks[0];
(my $task_path = "$vtr_flow_path/tasks/$task") =~ s/\s+$//;
my $output_path = $task_path;
my $exp_id = last_exp_id($task_path);
if ( ( ( $#tasks + 1 ) > 1 ) | ( -e "$task_path/../task_list.txt" ) ) {
$output_path = "$task_path/../";
}
if ( !-e "$output_path/task_summary" ) {
mkdir "$output_path/task_summary";
}
if ( -e "$output_path/task_summary/${run_prefix}${exp_id}_summary.txt" ) {
}
open( OUTPUT_FILE, ">$output_path/task_summary/${run_prefix}${exp_id}_summary.txt" );
##############################################################
# Append contents of QoR files to output file
##############################################################
foreach my $task (@tasks) {
chomp($task);
($task_path = "$vtr_flow_path/tasks/$task") =~ s/\s+$//;
$exp_id = last_exp_id($task_path);
(my $run_path = "$task_path/${run_prefix}${exp_id}") =~ s/\s+$//;
open( RESULTS_FILE, "$run_path/qor_results.txt" );
my $output = <RESULTS_FILE>;
if ($first) {
print OUTPUT_FILE "task_name\t$output";
$first = 0;
}
while ($output = <RESULTS_FILE>) {
print OUTPUT_FILE $task . "\t" . $output;
}
close(RESULTS_FILE);
}
close(OUTPUT_FILE);
}
sub calc_geomean {
##############################################################
# Set up output file
##############################################################
my $first = 0;
my $task = @tasks[0];
(my $task_path = "$vtr_flow_path/tasks/$task") =~ s/\s+$//;
my $output_path = $task_path;
my $exp_id = last_exp_id($task_path);
if ( ( ( $#tasks + 1 ) > 1 ) | ( -e "$task_path/../task_list.txt" ) ) {
($output_path = "$task_path/../") =~ s/\s+$//;
}
if ( !-e "$output_path/qor_geomean.txt" ) {
open( OUTPUT_FILE, ">$output_path/qor_geomean.txt" );
$first = 1;
}
else {
open( OUTPUT_FILE, ">>$output_path/qor_geomean.txt" );
}
##############################################################
# Read summary file
##############################################################
my $summary_file = "$output_path/task_summary/${run_prefix}${exp_id}_summary.txt";
if ( !-r $summary_file ) {
print "[ERROR] Failed to open $summary_file: $!";
return;
}
open( SUMMARY_FILE, "<$summary_file" );
my @summary_data = <SUMMARY_FILE>;
close(SUMMARY_FILE);
my $summary_params = shift @summary_data;
my @summary_params = split( /\t/, trim($summary_params) );
if ($first) {
# Hack - remove unwanted labels
my $num = 4;
while ($num) {
shift @summary_params;
--$num;
}
print OUTPUT_FILE "run";
my @temp = @summary_params;
while ( $#temp >= 0 ) {
my $label = shift @temp;
print OUTPUT_FILE "\t" . "$label";
}
print OUTPUT_FILE "\t" . "date" . "\t" . "revision";
$first = 0;
}
else {
}
print OUTPUT_FILE "\n${exp_id}";
##############################################################
# Compute & write geomean to output file
##############################################################
my $index = 4;
my @summary_params = split( /\t/, trim($summary_params) );
while ( $#summary_params >= $index ) {
my $geomean = 1; my $num = 0;
foreach my $line (@summary_data) {
my @first_file_line = split( /\t/, $line );
if ( trim( @first_file_line[$index] ) > 0 ) {
$geomean *= trim( @first_file_line[$index] );
$num++;
}
}
if ($num) {
$geomean **= 1/$num;
print OUTPUT_FILE "\t" . "${geomean}";
}
else {
print OUTPUT_FILE "\t" . "-1";
}
$index++;
}
my $date = strftime( '%D', localtime );
print OUTPUT_FILE "\t" . "$date" . "\t" . "$revision";
close(OUTPUT_FILE);
}
sub max {
my $x = shift;
my $y = shift;
return ($x < $y) ? $y : $x;
}
sub pretty_print_table {
my $file_path = shift;
#Read the input file
my @file_data;
open(INFILE,"<$file_path");
while(<INFILE>) {
chomp;
push(@file_data, [split /\t/])
}
#Determine the maximum column width for pretty formatting
my %col_widths;
for my $row (0 .. $#file_data) {
for my $col (0 .. $#{$file_data[$row]}) {
my $col_width = length $file_data[$row][$col];
#Do we have a valid column width?
if (not exists $col_widths{$col}) {
#Initial width
$col_widths{$col} = $col_width;
} else {
#Max width
$col_widths{$col} = max($col_widths{$col}, $col_width);
}
}
}
#Write out in pretty format
open(OUTFILE,">$file_path");
for my $row (0 .. $#file_data) {
for my $col (0 .. $#{$file_data[$row]}) {
printf OUTFILE "%-*s", $col_widths{$col}, $file_data[$row][$col];
if($col != $#{$file_data[$row]}) {
printf OUTFILE "\t";
}
}
printf OUTFILE "\n";
}
close(OUTFILE);
}
sub last_exp_id {
my $path = shift;
my $num = 0;
my $run_id = "";
my $run_id_no_pad = "";
do {
++$num;
$run_id = sprintf("%03d", $num);
$run_id_no_pad = sprintf("%d", $num);
} while ( -e "$path/${run_prefix}${run_id}" or -e "$path/${run_prefix}${run_id_no_pad}");
--$num;
$run_id = sprintf("%03d", $num);
$run_id_no_pad = sprintf("%d", $num);
if( -e "$path/${run_prefix}${run_id}" ) {
return $run_id;
} elsif (-e "$path/${run_prefix}${run_id_no_pad}") {
return $run_id_no_pad;
}
die("Unknown experiment id");
}
sub check_two_files {
my $task_name = shift;
my $task_path = shift;
my $run_path = shift;
my $first_test_file_dir = shift;
my $second_test_file_dir = shift;
my $is_golden = shift;
#Did this check pass?
my $failed = 0;
print "$task_name...";
print "\n" if $verbose;
# Code to check the results of the two files
(my $test_file_1 = "$first_test_file_dir") =~ s/\s+$//;
(my $test_file_2 = "$second_test_file_dir") =~ s/s+$//;
my $pass_req_file;
open( CONFIG_FILE, "$task_path/config/config.txt" );
my $lines = do { local $/; <CONFIG_FILE>; };
close(CONFIG_FILE);
# Search config file
if ( $lines =~ /^\s*pass_requirements_file\s*=\s*(\S+)\s*$/m ) { }
else {
print
"[ERROR] No 'pass_requirements_file' in task configuration file ($task_path/config/config.txt)\n";
$failed += 1;
return $failed;
}
my $pass_req_filename = $1;
# Search for pass requirement file
$pass_req_filename = expand_user_path($pass_req_filename);
if ( -e "$task_path/config/$pass_req_filename" ) {
$pass_req_file = "$task_path/config/$pass_req_filename";
}
elsif ( -e "$vtr_flow_path/parse/pass_requirements/$pass_req_filename" ) {
$pass_req_file =
"$vtr_flow_path/parse/pass_requirements/$pass_req_filename";
}
elsif ( -e $pass_req_filename ) {
$pass_req_file = $pass_req_filename;
}
else {
print
"[ERROR] Cannot find pass_requirements_file. Checked for $task_path/config/$pass_req_filename or $vtr_flow_path/parse/$pass_req_filename or $pass_req_filename\n";
$failed = 0;
return $failed;
}
my $line;
my @first_test_data;
my @second_test_data;
my @pass_req_data;
my @params;
my %type;
my %min_threshold;
my %max_threshold;
my %abs_diff_threshold;
##############################################################
# Read files
##############################################################
if ( !-r $test_file_2 ) {
print "[ERROR] Failed to open $test_file_2: $!";
$failed += 1;
return $failed;
}
open( GOLDEN_DATA, "<$test_file_2" );
@second_test_data = <GOLDEN_DATA>;
close(GOLDEN_DATA);
if ( !-r $pass_req_file ) {
print "[ERROR] Failed to open $pass_req_file: $!";
$failed += 1;
return $failed;
}
open( PASS_DATA, "<$pass_req_file" );
@pass_req_data = <PASS_DATA>;
close(PASS_DATA);
if ( !-r $test_file_1 ) {
print "[ERROR] Failed to open $test_file_1: $!";
$failed += 1;
return $failed;
}
open( TEST_DATA, "<$test_file_1" );
@first_test_data = <TEST_DATA>;
close(TEST_DATA);
##############################################################
# Process and check all parameters for consistency
##############################################################
my $second_test_params = shift @second_test_data;
my $first_test_params = shift @first_test_data;
my @second_test_params = split( /\t/, $second_test_params ); # get parameters of the second file results
my @first_test_params = split( /\t/, $first_test_params ); # get parameters of the first file results
my @second_test_params = map(trim($_), @second_test_params);
my @first_test_params = map(trim($_), @first_test_params);
# Check to ensure all parameters to compare are consistent
foreach $line (@pass_req_data) {
# Ignore comments
if ( $line =~ /^\s*#.*$/ or $line =~ /^\s*$/ ) { next; }
my @data = split( /;/, $line );
my $name = trim( $data[0] );
$type{$name} = trim( $data[1] );
if ( trim( $data[1] ) eq "Range" ) {
$min_threshold{$name} = trim( $data[2] );
$max_threshold{$name} = trim( $data[3] );
} elsif (trim( $data[1] ) eq "RangeAbs") {
$min_threshold{$name} = trim( $data[2] );
$max_threshold{$name} = trim( $data[3] );
$abs_diff_threshold{$name} = trim( $data[4] ); #Third element is absolute threshold
} elsif (trim( $data[1] ) eq "Equal") {
#Pass
} else {
print "[ERROR] $name has no comparison check specified (e.g. Range, RangeAbs, Equal).\n";
$failed += 1;
return $failed;
}
#Ensure item is in the first file
if ( !grep { $_ eq $name } @second_test_params ) {
if ($is_golden){
print "[ERROR] $name is not in the golden results file.\n";
}else{
print "[ERROR] $name is not in the second parse file.\n";
}
$failed += 1;
}
# Ensure item is in new results
if ( !grep { $_ eq $name } @first_test_params ) {
if ($is_golden){
print "[ERROR] $name is not in the results file.\n";
}else{
print "[ERROR] $name is not in the first parse file.\n";
}
$failed += 1;
}
push( @params, $name );
}
##############################################################
# Compare first file data data with second file data
##############################################################
if ( ( scalar @first_test_data ) != ( scalar @second_test_data ) ) {
print
"[ERROR] Different number of entries in the two files.\n";
$failed += 1;
}
# Iterate through each line of the test results data and compare with the golden data
foreach $line (@first_test_data) {
my @first_file_line = split( /\t/, $line );
my @second_file_line = split( /\t/, shift @second_test_data );
my $second_file_arch = trim(@second_file_line[0]);
my $second_file_circuit = trim(@second_file_line[1]);
my $first_file_arch = trim(@first_file_line[0]);
my $first_file_circuit = trim(@first_file_line[1]);
if ( ( $first_file_circuit ne $first_file_circuit )
or ( $first_file_arch ne $first_file_arch ) ) {
if ($is_golden){
print "[ERROR] Circuit/Architecture mismatch between golden results ($second_file_arch/$second_file_circuit) and result ($first_file_arch/$first_file_circuit).\n";
} else{
print "[ERROR] Circuit/Architecture mismatch between first result file ($first_file_arch/$first_file_circuit) and second result fule ($second_file_arch/$second_file_circuit).\n";
}
$failed += 1;
return $failed;
}
my $circuitarch = "$first_file_arch/$first_file_circuit";
# Check each parameter where the type determines what to check for
foreach my $value (@params) {
my $first_file_index = List::Util::first { $first_test_params[$_] eq $value } 0 .. $#first_test_params;
my $second_file_index = List::Util::first { $second_test_params[$_] eq $value } 0 .. $#second_test_params;
my $first_file_value = trim(@first_file_line[$first_file_index]);
my $second_file_value = trim(@second_file_line[$second_file_index]);
if ( $type{$value} eq "Range" or $type{$value} eq "RangeAbs" ) {
my $abs_diff = abs($first_file_value - $second_file_value);
my $ratio;
if ($second_file_value == 0) {
$ratio = "inf";
} else {
$ratio = $first_file_value / $second_file_value;
}
if($verbose) {
print "\tParam: $value\n";
print "\t\tTest: $first_file_value\n";
print "\t\tGolden Value: $second_file_value\n";
print "\t\tRatio: $ratio\n";
print "\t\tAbsDiff $abs_diff\n";
}
if ( exists $abs_diff_threshold{$value}
and $abs_diff <= $abs_diff_threshold{$value}) {
#Within absolute threshold
next;
}
if ( $ratio >= $min_threshold{$value}
and $ratio <= $max_threshold{$value}) {
#Within relative thershold
next;
}
if ($first_file_value == $second_file_value) {
#Equal (e.g. both zero)
next;
}
if ( $first_file_value eq 'nan'
and $second_file_value eq 'nan') {
#Both literal Not-a-Numbers
next;
}
#Beyond absolute and relative thresholds
if ($is_golden){
print
"[Fail] \n $circuitarch $value: golden = $second_file_value result = $first_file_value\n";
}else{
print
"[Fail] \n $circuitarch $value: first result = $first_file_value second result = $second_file_value\n";
}
$failed += 1;
} elsif ($type{$value} eq "Equal") {
if ( $first_file_value ne $second_file_value ) {
if ($is_golden) {
print "[Fail] \n $circuitarch $value: golden = $second_file_value result = $first_file_value\n";
} else {
print "[Fail] \n $circuitarch $value: first result = $first_file_value second result = $second_file_value\n";
}
$failed += 1;
}
} else {
# If the check type is unknown
$failed += 1;
print "[Fail] \n $circuitarch $value: unrecognized check type '$type{$value}' (e.g. Range, RangeAbs, Equal)\n";
}
}
}
if ($failed == 0) {
print "[Pass]\n";
}
return $failed;
}
sub trim() {
my $string = shift;
$string =~ s/^\s+//;
$string =~ s/\s+$//;
return $string;
}
sub expand_user_path {
my $str = shift;
$str =~ s/^~\//$ENV{"HOME"}\//;
return $str;
}
sub get_important_file {
my $task_path = shift;
my $vtr_flow_path = shift;
my $file = shift;
if ( -e "$task_path/config/$file" ) {
return "$task_path/config/$file";
}
elsif ( -e "$vtr_flow_path/parse/parse_config/$file" ) {
return "$vtr_flow_path/parse/parse_config/$file";
}
elsif ( -e "$vtr_flow_path/parse/qor_config/$file" ) {
return "$vtr_flow_path/parse/qor_config/$file";
}
elsif ( $file !~ /^\/.*/ ) {
die "Important file does not exist ($file)";
}
}