301 lines
8.9 KiB
Python
Executable File
301 lines
8.9 KiB
Python
Executable File
#!/usr/bin/python
|
|
# parse congestion and time per iteration for a given benchmark and output file (defaults to vpr.out)
|
|
from __future__ import print_function, division
|
|
import sys
|
|
from subprocess import call
|
|
import os
|
|
import re
|
|
import shlex
|
|
import argparse
|
|
import textwrap
|
|
import shutil
|
|
import csv
|
|
from datetime import datetime
|
|
# generate images wihtout having a window appear
|
|
import matplotlib
|
|
matplotlib.use('Agg')
|
|
import matplotlib.pyplot as plt
|
|
sys.path.append(os.path.expanduser("~/benchtracker/"))
|
|
from util import (get_trailing_num, immediate_subdir, natural_sort)
|
|
|
|
|
|
NO_PARSE = 1
|
|
NO_OUTPUT = 2
|
|
|
|
def main():
|
|
params = Params()
|
|
parse_args(params)
|
|
|
|
(param_names, param_options) = read_parse_file(params)
|
|
|
|
results = parse_output(param_names, params)
|
|
|
|
plot_results(param_names, param_options, results, params)
|
|
|
|
|
|
def plot_results(param_names, param_options, results, params):
|
|
"""Create a directory based on key parameters and date and plot results vs iteration
|
|
|
|
Each of the parameter in results will receive its own plot, drawn by matplotlib"""
|
|
|
|
# circuit/run_num where run_num is one before the existing one
|
|
directory = params.circuit
|
|
if not os.path.isdir(directory):
|
|
os.mkdir(directory)
|
|
runs = immediate_subdir(directory)
|
|
latest_run = 0
|
|
if runs:
|
|
natural_sort(runs)
|
|
latest_run = get_trailing_num(runs[-1])
|
|
directory = os.path.join(directory, "run" + str(latest_run+1))
|
|
|
|
print(directory)
|
|
if not os.path.isdir(directory):
|
|
os.mkdir(directory)
|
|
|
|
with Chdir(directory):
|
|
|
|
export_results_to_csv(param_names, results, params)
|
|
|
|
x = results.keys()
|
|
y = []
|
|
next_figure = True
|
|
|
|
p = 0
|
|
plt.figure()
|
|
while p < len(param_names):
|
|
print(param_names[p])
|
|
|
|
if param_options[p]:
|
|
nf = True
|
|
for option in param_options[p].split():
|
|
# stopping has veto power (must all be True to pass)
|
|
nf = nf and plot_options(option)
|
|
next_figure = nf
|
|
|
|
if not next_figure:
|
|
# y becomes list of lists (for use with stackable plots)
|
|
y.append([result[p] for result in results.values()])
|
|
p += 1
|
|
continue
|
|
elif not y:
|
|
y = [result[p] for result in results.values()]
|
|
|
|
lx = x[-1]
|
|
ly = y[-1]
|
|
plot_method(x,y)
|
|
plt.xlabel('iteration')
|
|
plt.xlim(xmin=0)
|
|
|
|
plt.ylabel(param_names[p])
|
|
|
|
# annotate the last value
|
|
annotate_last(lx,ly)
|
|
|
|
if next_figure:
|
|
plt.savefig(param_names[p])
|
|
plt.figure()
|
|
|
|
p += 1
|
|
# in case the last figure hasn't been shuffled onto file yet
|
|
if not next_figure:
|
|
plot_method(x,y)
|
|
plt.savefig(param_names[-1])
|
|
|
|
# can be changed by parameter options (always dump y after plotting)
|
|
def default_plot(x,y):
|
|
plt.plot(x,y)
|
|
del y[:]
|
|
|
|
plot_method = default_plot
|
|
|
|
def stack_plot(x,y):
|
|
global plot_method
|
|
plt.stackplot(x,y)
|
|
del y[:]
|
|
plot_method = default_plot
|
|
|
|
|
|
# each plot option returns true or false on whether its ready for the next figure
|
|
def plot_stack():
|
|
global plot_method
|
|
plot_method = stack_plot
|
|
return False
|
|
|
|
def plot_log():
|
|
plt.semilogy(nonposy='mask')
|
|
return True
|
|
|
|
def plot_percent():
|
|
plt.ylim(0,100)
|
|
return True
|
|
|
|
plot_opts = {
|
|
'stackplot':plot_stack,
|
|
'log':plot_log,
|
|
'percent':plot_percent
|
|
}
|
|
|
|
def plot_options(option):
|
|
print(option)
|
|
option_handler = plot_opts.get(option, None)
|
|
if option_handler:
|
|
return option_handler()
|
|
return True
|
|
|
|
def annotate_last(lx, ly):
|
|
if type(ly) == list:
|
|
return
|
|
plt.plot([lx,lx], [0, ly], linestyle="--")
|
|
plt.scatter([lx,],[ly,], 50, color='black')
|
|
|
|
plt.annotate('({},{})'.format(lx, ly),
|
|
xy=(lx, ly), xytext=(0,15), textcoords='offset points')
|
|
|
|
def export_results_to_csv(param_names, results, params):
|
|
param_values = {}
|
|
param_values["circuit"] = params.circuit
|
|
param_values["channel_width"] = params.channel_width
|
|
with open(params.output_file, 'r') as out:
|
|
for regex in params.param_regex:
|
|
match = re.search(regex, out.read())
|
|
if match:
|
|
param_values[regex.split(':')[0]] = match.group(1)
|
|
print("matched:", match.group(1))
|
|
|
|
for param,val in param_values.items():
|
|
print("{}: {}".format(param, val))
|
|
# also move the output file, place file, and pack file
|
|
shutil.copy(params.output_file, os.path.basename(params.output_file))
|
|
shutil.copy(params.pack_file, os.path.basename(params.pack_file))
|
|
shutil.copy(params.place_file, os.path.basename(params.place_file))
|
|
|
|
with open('results.csv', 'wb') as csvfile:
|
|
writer = csv.writer(csvfile)
|
|
# header line
|
|
writer.writerow(['iteration'] + param_names + param_values.keys())
|
|
for iteration, result in results.items():
|
|
writer.writerow([iteration] + result + param_values.values())
|
|
|
|
def parse_output(param_names, params):
|
|
"""Return a dictionary mapping iteration -> [params...]
|
|
|
|
The index of the params list corresponds to the index of param_names
|
|
assuming consecutively matched groups"""
|
|
if not os.path.isfile(params.output_file):
|
|
print("output file does not exist! ({})".format(params.output_file))
|
|
sys.exit(NO_OUTPUT)
|
|
|
|
results = {}
|
|
|
|
with open(params.output_file, 'r') as out:
|
|
iteration_matches = re.findall(params.iteration_regex, out.read())
|
|
for iteration_match in iteration_matches:
|
|
iteration = int(iteration_match[0])
|
|
results[iteration] = [float(iteration_match[m]) for m in range(1,len(iteration_match))]
|
|
|
|
return results
|
|
|
|
|
|
def read_parse_file(params):
|
|
"""Read the parameters to match and their options"""
|
|
param_names = []
|
|
param_options = []
|
|
if not os.path.isfile(params.parse_file):
|
|
print("parse file does not exist! ({})".format(params.parse_file))
|
|
sys.exit(NO_PARSE)
|
|
with open(params.parse_file, 'r') as pf:
|
|
# first line should be iteration regex
|
|
setattr(params, 'iteration_regex', re.compile(pf.readline().strip()))
|
|
for line in pf:
|
|
param_desc = line.split(';')
|
|
param_names.append(param_desc[0])
|
|
param_options.append(param_desc[1])
|
|
|
|
return param_names,param_options
|
|
|
|
|
|
# automatic return and exception safe
|
|
class Chdir:
|
|
def __init__(self, new_path):
|
|
self.prev_path = os.getcwd()
|
|
self.new_path = new_path
|
|
|
|
def __enter__(self):
|
|
os.chdir(self.new_path)
|
|
|
|
def __exit__(self, type, value, traceback):
|
|
os.chdir(self.prev_path)
|
|
|
|
|
|
class Params(object):
|
|
def __iter__(self):
|
|
for attr, value in self.__dict__.iteritems():
|
|
yield attr, value
|
|
|
|
def parse_args(ns=None):
|
|
"""parse arguments from command line and return as namespace object"""
|
|
parser = argparse.ArgumentParser(
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
description=textwrap.dedent("""\
|
|
parse parameters per iteration for a given benchmark and plots them
|
|
|
|
Benchmark:
|
|
This script is designed for VPR, so circuit and channel_width of a benchmark are required.
|
|
The circuit name can be a summary or short name rather than the full name.
|
|
|
|
Parse file:
|
|
The first line should describe how to match iteration, such as:
|
|
'(\d+) (.*) sec (.*) ns'
|
|
and the first group will be the matched value of iteration.
|
|
|
|
Each following line in this file should describe 1 parameter to parse as:
|
|
<parameter_name>;<options>
|
|
for example,
|
|
'time (s);log'
|
|
on the second line, then with the previous iteration regex, it would match the value before 'sec'
|
|
The parameter order should match the group order that they are supposed to match
|
|
|
|
Parameter Options:
|
|
log - make the y-axis log scale for this parameter
|
|
"""),
|
|
usage="%(prog)s <output_file> <circuit> <channel_width> [OPTIONS]")
|
|
|
|
# arguments should either end with _dir or _file for use by other functions
|
|
parser.add_argument("output_file",
|
|
default="vpr.out",
|
|
help="output file to parse; default: %(default)s")
|
|
parser.add_argument("-p", "--parse_file",
|
|
default="nocongestion_parse.txt",
|
|
help="config file where each line describes 1 parameter to parse as:\
|
|
<parameter_name>;<regex_to_match>\
|
|
default: %(default)s")
|
|
parser.add_argument("circuit",
|
|
help="titan circuit to be parsed, similar to task")
|
|
parser.add_argument("channel_width",
|
|
type=int,
|
|
help="channel used to route circuit")
|
|
parser.add_argument("--architecture",
|
|
default="stratixiv_arch_timing",
|
|
help="architecture used to map circuit to; default: %(default)s")
|
|
parser.add_argument("--resfile_dir",
|
|
default="titan",
|
|
help="directory (relative or absolute) of where result files (.pack, .place, .route) are kept")
|
|
parser.add_argument("-r","--param_regex",
|
|
nargs='+',
|
|
default=[],
|
|
help="additional regular expressions to match on the result file")
|
|
params = parser.parse_args(namespace=ns)
|
|
params.output_file = os.path.abspath(params.output_file)
|
|
|
|
resfile_name = os.path.join(params.resfile_dir, '_'.join((params.circuit, params.architecture)))
|
|
print("result file base name: ", resfile_name);
|
|
|
|
setattr(params, 'pack_file', os.path.abspath(resfile_name + '.net'))
|
|
setattr(params, 'place_file', os.path.abspath(resfile_name + '.place'))
|
|
return params;
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|