blob: 8d0a997ae84d90b44da07b7ad0c4c4ac22ff52c4 [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";
# 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 $failed = parse_single_task($task);
if($failed) {
$num_golden_failures += 1;
}
}
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 @archs;
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" ) {
$parse_file = expand_user_path($value);
}
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);
# 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) {
system(
"$vtr_flow_path/scripts/parse_vtr_flow.pl $run_path/$arch/$circuit $parse_file > $run_path/$arch/$circuit/parse_results.txt"
);
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);
if ($pretty_print_results) {
pretty_print_table("$run_path/parse_results.txt")
}
if ($parse_qor) {
my $first = 1;
open( OUTPUT_FILE, ">$run_path/qor_results.txt" );
foreach my $arch (@archs) {
foreach my $circuit (@circuits) {
system(
"$vtr_flow_path/scripts/parse_vtr_flow.pl $run_path/$arch/$circuit $qor_parse_file > $run_path/$arch/$circuit/qor_results.txt"
);
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 ($check_golden) {
#Returns 1 if failed
return check_golden( $task_name, $task_path, $run_path );
}
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 @test_line = split( /\t/, $line );
if ( trim( @test_line[$index] ) > 0 ) {
$geomean *= trim( @test_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_golden {
my $task_name = shift;
my $task_path = shift;
my $run_path = shift;
#Did this golden check pass?
my $failed = 0;
print "$task_name...";
print "\n" if $verbose;
# Code to check the results against the golden results
(my $golden_file = "$task_path/config/golden_results.txt") =~ s/\s+$//;
(my $test_file = "$run_path/parse_results.txt") =~ 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 @golden_data;
my @test_data;
my @pass_req_data;
my @params;
my %type;
my %min_threshold;
my %max_threshold;
##############################################################
# Read files
##############################################################
if ( !-r $golden_file ) {
print "[ERROR] Failed to open $golden_file: $!";
$failed = 1;
return $failed;
}
open( GOLDEN_DATA, "<$golden_file" );
@golden_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 ) {
print "[ERROR] Failed to open $test_file: $!";
$failed = 1;
return $failed;
}
open( TEST_DATA, "<$test_file" );
@test_data = <TEST_DATA>;
close(TEST_DATA);
##############################################################
# Process and check all parameters for consistency
##############################################################
my $golden_params = shift @golden_data;
my $test_params = shift @test_data;
my @golden_params = split( /\t/, $golden_params ); # get parameters of golden results
my @test_params = split( /\t/, $test_params ); # get parameters of test results
my @golden_params = map(trim($_), @golden_params);
my @test_params = map(trim($_), @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] );
}
#Ensure item is in golden results
if ( !grep { $_ eq $name } @golden_params ) {
print "[ERROR] $name is not in the golden file.\n";
$failed = 1;
return $failed;
}
# Ensure item is in new results
if ( !grep { $_ eq $name } @test_params ) {
print "[ERROR] $name is not in the results file.\n";
$failed = 1;
}
push( @params, $name );
}
##############################################################
# Compare test data with golden data
##############################################################
if ( ( scalar @test_data ) != ( scalar @golden_data ) ) {
print
"[ERROR] Different number of entries in golden and result files.\n";
$failed = 1;
}
# Iterate through each line of the test results data and compare with the golden data
foreach $line (@test_data) {
my @test_line = split( /\t/, $line );
my @golden_line = split( /\t/, shift @golden_data );
my $golden_arch = trim(@golden_line[0]);
my $golden_circuit = trim(@golden_line[1]);
my $test_arch = trim(@test_line[0]);
my $test_circuit = trim(@test_line[1]);
if ( ( $test_circuit ne $test_circuit )
or ( $test_arch ne $test_arch ) ) {
print "[ERROR] Circuit/Architecture mismatch between golden ($golden_arch/$golden_circuit) and result ($test_arch/$test_circuit).\n";
$failed = 1;
return $failed;
}
my $circuitarch = "$test_arch/$test_circuit";
# Check each parameter where the type determines what to check for
foreach my $value (@params) {
my $test_index = List::Util::first { $test_params[$_] eq $value } 0 .. $#test_params;
my $golden_index = List::Util::first { $golden_params[$_] eq $value } 0 .. $#golden_params;
my $test_value = trim(@test_line[$test_index]);
my $golden_value = trim(@golden_line[$golden_index]);
if ( $type{$value} eq "Range" ) {
# Check because of division by 0
if ( $golden_value == 0 ) {
if ( $test_value != 0 ) {
print
"[Fail] $circuitarch $value: result = $test_value golden = $golden_value\n";
$failed = 1;
return $failed;
}
}
else {
my $ratio = $test_value / $golden_value;
if($verbose) {
print "\tParam: $value\n";
print "\t\tTest: $test_value\n";
print "\t\tGolden Value: $golden_value\n";
print "\t\tRatio: $ratio\n";
}
if ( $ratio < $min_threshold{$value}
or $ratio > $max_threshold{$value} )
{
print
"[Fail] $circuitarch $value: result = $test_value golden = $golden_value\n";
$failed = 1;
return $failed;
}
}
}
else {
# If the type is unknown, check for an exact match
if ( $test_value ne $golden_value ) {
$failed = 1;
print
"[Fail] $circuitarch $value: result = $test_value golden = $golden_value\n";
}
}
}
}
if (!$failed) {
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)";
}
}