39 from sys
import argv, exit
40 from os.path
import basename, splitext
46 from matplotlib
import __version__
as matplotlibversion
47 from matplotlib.backends.backend_pdf
import PdfPages
48 import matplotlib.pyplot
as plt
50 from math
import floor
51 from optparse
import OptionParser, OptionGroup
58 start_pos = filevar.tell()
59 tokens = filevar.readline().split()
60 for token_index
in expected_tokens:
61 if not tokens[token_index] == expected_tokens[token_index]:
63 filevar.seek(start_pos)
65 return tokens[desired_token_index]
69 return readLogValue(filevar, desired_token_index, expected_tokens)
73 result =
readLogValue(filevar, desired_token_index, expected_tokens)
75 raise Exception(
"Unable to read " + name)
80 if not line.startswith(prefix):
81 raise Exception(
"Expected prefix " + prefix +
" was not found")
86 start_pos = filevar.tell()
87 line = filevar.readline()
88 if not line.startswith(
"<<<|"):
89 filevar.seek(start_pos)
92 line = filevar.readline()
93 while not line.startswith(
"|>>>"):
95 line = filevar.readline()
97 raise Exception(
"Expected token |>>> missing")
104 line = filevar.readline()
105 while not line.startswith(
"|>>>"):
107 line = filevar.readline()
109 raise Exception(
"Expected token |>>> missing")
114 """Parse benchmark log files and store the parsed data in a sqlite3 database."""
116 def isInvalidValue(value):
117 return len(value) == 0
or value
in [
"nan",
"-nan",
"inf",
"-inf"]
119 conn = sqlite3.connect(dbname)
121 c.execute(
"PRAGMA FOREIGN_KEYS = ON")
125 """CREATE TABLE IF NOT EXISTS experiments
126 (id INTEGER PRIMARY KEY ON CONFLICT REPLACE AUTOINCREMENT, name VARCHAR(512),
127 totaltime REAL, timelimit REAL, memorylimit REAL, runcount INTEGER,
128 version VARCHAR(128), hostname VARCHAR(1024), cpuinfo TEXT,
129 date DATETIME, seed INTEGER, setup TEXT);
130 CREATE TABLE IF NOT EXISTS plannerConfigs
131 (id INTEGER PRIMARY KEY AUTOINCREMENT,
132 name VARCHAR(512) NOT NULL, settings TEXT);
133 CREATE TABLE IF NOT EXISTS enums
134 (name VARCHAR(512), value INTEGER, description TEXT,
135 PRIMARY KEY (name, value));
136 CREATE TABLE IF NOT EXISTS runs
137 (id INTEGER PRIMARY KEY AUTOINCREMENT, experimentid INTEGER, plannerid INTEGER,
138 FOREIGN KEY (experimentid) REFERENCES experiments(id) ON DELETE CASCADE,
139 FOREIGN KEY (plannerid) REFERENCES plannerConfigs(id) ON DELETE CASCADE);
140 CREATE TABLE IF NOT EXISTS progress
141 (runid INTEGER, time REAL, PRIMARY KEY (runid, time),
142 FOREIGN KEY (runid) REFERENCES runs(id) ON DELETE CASCADE)"""
146 allExperimentsName =
"all_experiments"
147 allExperimentsValues = {
159 addAllExperiments = len(filenames) > 0
160 if addAllExperiments:
162 "INSERT INTO experiments VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
163 (
None, allExperimentsName) + tuple(allExperimentsValues.values()),
165 allExperimentsId = c.lastrowid
167 for i, filename
in enumerate(filenames):
168 print(
"Processing " + filename)
169 logfile = open(filename,
"r")
170 start_pos = logfile.tell()
174 logfile.seek(start_pos)
179 version =
" ".join([libname, version])
181 "experiment name", logfile, -1, {0:
"Experiment"}
184 date =
" ".join(
ensurePrefix(logfile.readline(),
"Starting").split()[2:])
192 "time limit", logfile, 0, {-3:
"seconds", -2:
"per", -1:
"run"}
197 "memory limit", logfile, 0, {-3:
"MB", -2:
"per", -1:
"run"}
201 logfile, 0, {-3:
"runs", -2:
"per", -1:
"planner"}
204 if nrrunsOrNone !=
None:
205 nrruns = int(nrrunsOrNone)
206 allExperimentsValues[
"runcount"] += nrruns
209 "total time", logfile, 0, {-3:
"collect", -2:
"the", -1:
"data"}
213 allExperimentsValues[
"totaltime"] += totaltime
214 allExperimentsValues[
"memorylimit"] = max(
215 allExperimentsValues[
"memorylimit"], totaltime
217 allExperimentsValues[
"timelimit"] = max(
218 allExperimentsValues[
"timelimit"], totaltime
222 allExperimentsValues[
"version"] = version
223 allExperimentsValues[
"date"] = date
224 allExperimentsValues[
"setup"] = expsetup
225 allExperimentsValues[
"hostname"] = hostname
226 allExperimentsValues[
"cpuinfo"] = cpuinfo
229 if numEnumsOrNone !=
None:
230 numEnums = int(numEnumsOrNone)
231 for i
in range(numEnums):
232 enum = logfile.readline()[:-1].split(
"|")
233 c.execute(
'SELECT * FROM enums WHERE name IS "%s"' % enum[0])
234 if c.fetchone() ==
None:
235 for j
in range(len(enum) - 1):
237 "INSERT INTO enums VALUES (?,?,?)", (enum[0], j, enum[j + 1])
240 "INSERT INTO experiments VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
256 experimentId = c.lastrowid
260 for i
in range(numPlanners):
261 plannerName = logfile.readline()[:-1]
262 print(
"Parsing data for " + plannerName)
265 numCommon = int(logfile.readline().split()[0])
267 for j
in range(numCommon):
268 settings = settings + logfile.readline() +
";"
272 "SELECT id FROM plannerConfigs WHERE (name=? AND settings=?)",
281 "INSERT INTO plannerConfigs VALUES (?,?,?)",
288 plannerId = c.lastrowid
293 c.execute(
"PRAGMA table_info(runs)")
294 columnNames = [col[1]
for col
in c.fetchall()]
297 numProperties = int(logfile.readline().split()[0])
298 propertyNames = [
"experimentid",
"plannerid"]
299 for j
in range(numProperties):
300 field = logfile.readline().split()
301 propertyType = field[-1]
302 propertyName =
"_".join(field[:-1])
303 if propertyName
not in columnNames:
305 "ALTER TABLE runs ADD %s %s" % (propertyName, propertyType)
307 propertyNames.append(propertyName)
311 +
",".join(propertyNames)
313 +
",".join(
"?" * len(propertyNames))
316 numRuns = int(logfile.readline().split()[0])
318 for j
in range(numRuns):
320 None if isInvalidValue(x)
else x
321 for x
in logfile.readline().split(
"; ")[:-1]
323 values = tuple([experimentId, plannerId] + runValues)
324 c.execute(insertFmtStr, values)
327 runIds.append(c.lastrowid)
329 if addAllExperiments:
330 values = tuple([allExperimentsId, plannerId] + runValues)
331 c.execute(insertFmtStr, values)
333 nextLine = logfile.readline().strip()
338 c.execute(
"PRAGMA table_info(progress)")
339 columnNames = [col[1]
for col
in c.fetchall()]
342 numProgressProperties = int(nextLine.split()[0])
343 progressPropertyNames = [
"runid"]
344 for i
in range(numProgressProperties):
345 field = logfile.readline().split()
346 progressPropertyType = field[-1]
347 progressPropertyName =
"_".join(field[:-1])
348 if progressPropertyName
not in columnNames:
350 "ALTER TABLE progress ADD %s %s"
351 % (progressPropertyName, progressPropertyType)
353 progressPropertyNames.append(progressPropertyName)
356 "INSERT INTO progress ("
357 +
",".join(progressPropertyNames)
359 +
",".join(
"?" * len(progressPropertyNames))
362 numRuns = int(logfile.readline().split()[0])
363 for j
in range(numRuns):
364 dataSeries = logfile.readline().split(
";")[:-1]
365 for dataSample
in dataSeries:
369 None if isInvalidValue(x)
else x
370 for x
in dataSample.split(
",")[:-1]
374 c.execute(insertFmtStr, values)
375 except sqlite3.IntegrityError:
377 "Ignoring duplicate progress data. Consider increasing ompl::tools::Benchmark::Request::timeBetweenUpdates."
384 if addAllExperiments:
385 updateString =
"UPDATE experiments SET"
386 for i, (key, val)
in enumerate(allExperimentsValues.items()):
389 updateString +=
" " + str(key) +
"='" + str(val) +
"'"
390 updateString +=
"WHERE id='" + str(allExperimentsId) +
"'"
391 c.execute(updateString)
397 """Create a plot for a particular attribute. It will include data for
398 all planners that have data for this attribute."""
402 if typename ==
"ENUM":
403 cur.execute(
'SELECT description FROM enums where name IS "%s"' % attribute)
404 descriptions = [t[0]
for t
in cur.fetchall()]
405 numValues = len(descriptions)
406 for planner
in planners:
408 "SELECT %s FROM runs WHERE plannerid = %s AND %s IS NOT NULL"
409 % (attribute, planner[0], attribute)
411 measurement = [t[0]
for t
in cur.fetchall()
if t[0] !=
None]
412 if len(measurement) > 0:
414 "SELECT count(*) FROM runs WHERE plannerid = %s AND %s IS NULL"
415 % (planner[0], attribute)
417 nanCounts.append(cur.fetchone()[0])
418 labels.append(planner[1])
419 if typename ==
"ENUM":
420 scale = 100.0 / len(measurement)
422 [measurement.count(i) * scale
for i
in range(numValues)]
425 measurements.append(measurement)
427 if len(measurements) == 0:
428 print(
'Skipping "%s": no available measurements' % attribute)
433 if typename ==
"ENUM":
435 measurements = np.transpose(np.vstack(measurements))
436 colsum = np.sum(measurements, axis=1)
437 rows = np.where(colsum != 0)[0]
438 heights = np.zeros((1, measurements.shape[1]))
439 ind = range(measurements.shape[1])
447 color=matplotlib.cm.hot(int(floor(i * 256 / numValues))),
448 label=descriptions[i],
450 heights = heights + measurements[i]
451 xtickNames = plt.xticks(
452 [x + width / 2.0
for x
in ind], labels, rotation=30, fontsize=8, ha=
"right"
454 ax.set_ylabel(attribute.replace(
"_",
" ") +
" (%)")
455 box = ax.get_position()
456 ax.set_position([box.x0, box.y0, box.width * 0.8, box.height])
457 props = matplotlib.font_manager.FontProperties()
458 props.set_size(
"small")
459 ax.legend(loc=
"center left", bbox_to_anchor=(1, 0.5), prop=props)
460 elif typename ==
"BOOLEAN":
462 measurementsPercentage = [
sum(m) * 100.0 / len(m)
for m
in measurements]
463 ind = range(len(measurements))
464 plt.bar(ind, measurementsPercentage, width)
470 xtickNames = plt.xticks(
471 [x + width / 2.0
for x
in ind], labels, rotation=30, fontsize=8, ha=
"right"
473 ax.set_ylabel(attribute.replace(
"_",
" ") +
" (%)")
478 if int(matplotlibversion.split(
".")[0]) < 1:
479 plt.boxplot(measurements, notch=0, sym=
"k+", vert=1, whis=1.5)
482 measurements, notch=0, sym=
"k+", vert=1, whis=1.5, bootstrap=1000
484 ax.set_ylabel(attribute.replace(
"_",
" "))
493 xtickNames = plt.setp(ax, xticklabels=labels)
494 plt.setp(xtickNames, rotation=30, fontsize=8, ha=
"right")
497 )
in ax.xaxis.get_major_ticks():
498 tick.label.set_fontsize(8)
502 ax.set_xlabel(
"Motion planning algorithm", fontsize=12)
503 ax.yaxis.grid(
True, linestyle=
"-", which=
"major", color=
"lightgrey", alpha=0.5)
504 if max(nanCounts) > 0:
505 maxy = max([max(y)
for y
in measurements])
506 for i
in range(len(labels)):
507 x = i + width / 2
if typename ==
"BOOLEAN" else i + 1
514 """Plot data for a single planner progress attribute. Will create an
515 average time-plot with error bars of the attribute over all runs for
518 import numpy.ma
as ma
522 ax.set_xlabel(
"time (s)")
523 ax.set_ylabel(attribute.replace(
"_",
" "))
525 for planner
in planners:
527 """SELECT count(progress.%s) FROM progress INNER JOIN runs
528 ON progress.runid = runs.id AND runs.plannerid=%s
529 AND progress.%s IS NOT NULL"""
530 % (attribute, planner[0], attribute)
532 if cur.fetchone()[0] > 0:
533 plannerNames.append(planner[1])
535 """SELECT DISTINCT progress.runid FROM progress INNER JOIN runs
536 WHERE progress.runid=runs.id AND runs.plannerid=?""",
539 runids = [t[0]
for t
in cur.fetchall()]
545 "SELECT time, %s FROM progress WHERE runid = %s ORDER BY time"
548 (time, data) = zip(*(cur.fetchall()))
549 timeTable.append(time)
550 dataTable.append(data)
555 fewestSamples = min(len(time[:])
for time
in timeTable)
556 times = np.array(timeTable[0][:fewestSamples])
557 dataArrays = np.array([data[:fewestSamples]
for data
in dataTable])
558 filteredData = ma.masked_array(
559 dataArrays, np.equal(dataArrays,
None), dtype=float
562 means = np.mean(filteredData, axis=0)
563 stddevs = np.std(filteredData, axis=0, ddof=1)
567 times, means, yerr=2 * stddevs, errorevery=max(1, len(times) // 20)
569 ax.legend(plannerNames)
570 if len(plannerNames) > 0:
577 """Create a PDF file with box plots for all attributes."""
578 print(
"Generating plots...")
579 conn = sqlite3.connect(dbname)
581 c.execute(
"PRAGMA FOREIGN_KEYS = ON")
582 c.execute(
"SELECT id, name FROM plannerConfigs")
584 (t[0], t[1].replace(
"geometric_",
"").replace(
"control_",
""))
585 for t
in c.fetchall()
587 c.execute(
"PRAGMA table_info(runs)")
588 colInfo = c.fetchall()[3:]
595 or col[2] ==
"INTEGER"
599 pp.savefig(plt.gcf())
601 c.execute(
"PRAGMA table_info(progress)")
602 colInfo = c.fetchall()[2:]
605 pp.savefig(plt.gcf())
610 c.execute(
"""SELECT id, name, timelimit, memorylimit FROM experiments""")
611 experiments = c.fetchall()
612 for experiment
in experiments:
614 """SELECT count(*) FROM runs WHERE runs.experimentid = %d
615 GROUP BY runs.plannerid"""
618 numRuns = [run[0]
for run
in c.fetchall()]
619 numRuns = numRuns[0]
if len(set(numRuns)) == 1
else ",".join(numRuns)
621 plt.figtext(pagex, pagey,
'Experiment "%s"' % experiment[1])
622 plt.figtext(pagex, pagey - 0.05,
"Number of averaged runs: %d" % numRuns)
624 pagex, pagey - 0.10,
"Time limit per run: %g seconds" % experiment[2]
626 plt.figtext(pagex, pagey - 0.15,
"Memory limit per run: %g MB" % experiment[3])
629 pp.savefig(plt.gcf())
637 print(
"Saving as MySQL dump file...")
639 conn = sqlite3.connect(dbname)
640 mysqldump = open(mysqldump,
"w")
644 c.execute(
"SELECT name FROM sqlite_master WHERE type='table'")
645 table_names = [str(t[0])
for t
in c.fetchall()]
647 last = [
"experiments",
"planner_configs"]
648 for table
in table_names:
649 if table.startswith(
"sqlite"):
651 if not table
in last:
652 mysqldump.write(
"DROP TABLE IF EXISTS `%s`;\n" % table)
654 if table
in table_names:
655 mysqldump.write(
"DROP TABLE IF EXISTS `%s`;\n" % table)
657 for line
in conn.iterdump():
663 "CREATE UNIQUE INDEX",
672 line = re.sub(
r"[\n\r\t ]+",
" ", line)
673 m = re.search(
"CREATE TABLE ([a-zA-Z0-9_]*)(.*)", line)
675 name, sub = m.groups()
676 sub = sub.replace(
'"',
"`")
677 line =
"""CREATE TABLE IF NOT EXISTS %(name)s%(sub)s"""
678 line = line % dict(name=name, sub=sub)
680 line = line.rstrip(
"\n\t ;") +
" ENGINE = InnoDB;\n"
682 m = re.search(
'INSERT INTO "([a-zA-Z0-9_]*)"(.*)', line)
684 line =
"INSERT INTO %s%s\n" % m.groups()
685 line = line.replace(
'"',
r"\"")
686 line = line.replace(
'"',
"'")
688 line = re.sub(
r"([^'])'t'(.)",
"\\1THIS_IS_TRUE\\2", line)
689 line = line.replace(
"THIS_IS_TRUE",
"1")
690 line = re.sub(
r"([^'])'f'(.)",
"\\1THIS_IS_FALSE\\2", line)
691 line = line.replace(
"THIS_IS_FALSE",
"0")
692 line = line.replace(
"AUTOINCREMENT",
"AUTO_INCREMENT")
693 mysqldump.write(line)
698 conn = sqlite3.connect(dbname)
700 c.execute(
"PRAGMA FOREIGN_KEYS = ON")
701 c.execute(
"PRAGMA table_info(runs)")
704 if "simplification_time" in [col[1]
for col
in c.fetchall()]:
705 s0 =
"""SELECT plannerid, plannerConfigs.name AS plannerName, experimentid, solved, time + simplification_time AS total_time
706 FROM plannerConfigs INNER JOIN experiments INNER JOIN runs
707 ON plannerConfigs.id=runs.plannerid AND experiments.id=runs.experimentid"""
709 s0 =
"""SELECT plannerid, plannerConfigs.name AS plannerName, experimentid, solved, time AS total_time
710 FROM plannerConfigs INNER JOIN experiments INNER JOIN runs
711 ON plannerConfigs.id=runs.plannerid AND experiments.id=runs.experimentid"""
713 """SELECT plannerid, plannerName, experimentid, AVG(solved) AS avg_solved, AVG(total_time) AS avg_total_time
714 FROM (%s) GROUP BY plannerid, experimentid"""
718 """SELECT plannerid, experimentid, MIN(avg_solved) AS avg_solved, avg_total_time
719 FROM (%s) GROUP BY plannerName, experimentid ORDER BY avg_solved DESC, avg_total_time ASC"""
722 c.execute(
"DROP VIEW IF EXISTS bestPlannerConfigsPerExperiment")
723 c.execute(
"CREATE VIEW IF NOT EXISTS bestPlannerConfigsPerExperiment AS %s" % s2)
726 """SELECT plannerid, plannerName, AVG(solved) AS avg_solved, AVG(total_time) AS avg_total_time
727 FROM (%s) GROUP BY plannerid"""
731 """SELECT plannerid, MIN(avg_solved) AS avg_solved, avg_total_time
732 FROM (%s) GROUP BY plannerName ORDER BY avg_solved DESC, avg_total_time ASC"""
735 c.execute(
"DROP VIEW IF EXISTS bestPlannerConfigs")
736 c.execute(
"CREATE VIEW IF NOT EXISTS bestPlannerConfigs AS %s" % s2)
742 if __name__ ==
"__main__":
743 usage =
"""%prog [options] [<benchmark.log> ...]"""
744 parser = OptionParser(
"A script to parse benchmarking results.\n" + usage)
749 default=
"benchmark.db",
750 help=
"Filename of benchmark database [default: %default]",
758 help=
"Compute the views for best planner configurations",
765 help=
"Create a PDF of plots with the filename provided",
772 help=
"Save SQLite3 database as a MySQL dump file",
774 (options, args) = parser.parse_args()
777 parser.error(
"No arguments were provided. Please provide full path of log file")
def saveAsMysql(dbname, mysqldump)
def readOptionalMultilineValue(filevar)
def readBenchmarkLog(dbname, filenames)
def plotAttribute(cur, planners, attribute, typename)
def readLogValue(filevar, desired_token_index, expected_tokens)
def readOptionalLogValue(filevar, desired_token_index, expected_tokens={})
def plotProgressAttribute(cur, planners, attribute)
def readRequiredLogValue(name, filevar, desired_token_index, expected_tokens={})
def readRequiredMultilineValue(filevar)
def ensurePrefix(line, prefix)
def plotStatistics(dbname, fname)
CostFn sum(const std::vector< CostFn > &cost_functions)
void print(PropagationDistanceField &pdf, int numX, int numY, int numZ)