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
57 start_pos = filevar.tell()
58 tokens = filevar.readline().split()
59 for token_index
in expected_tokens:
60 if not tokens[token_index] == expected_tokens[token_index]:
62 filevar.seek(start_pos)
64 return tokens[desired_token_index]
68 return readLogValue(filevar, desired_token_index, expected_tokens)
72 result =
readLogValue(filevar, desired_token_index, expected_tokens)
74 raise Exception(
"Unable to read " + name)
79 if not line.startswith(prefix):
80 raise Exception(
"Expected prefix " + prefix +
" was not found")
85 start_pos = filevar.tell()
86 line = filevar.readline()
87 if not line.startswith(
"<<<|"):
88 filevar.seek(start_pos)
91 line = filevar.readline()
92 while not line.startswith(
"|>>>"):
94 line = filevar.readline()
96 raise Exception(
"Expected token |>>> missing")
103 line = filevar.readline()
104 while not line.startswith(
"|>>>"):
106 line = filevar.readline()
108 raise Exception(
"Expected token |>>> missing")
113 """Parse benchmark log files and store the parsed data in a sqlite3 database."""
115 def isInvalidValue(value):
116 return len(value) == 0
or value
in [
"nan",
"-nan",
"inf",
"-inf"]
118 conn = sqlite3.connect(dbname)
120 c.execute(
"PRAGMA FOREIGN_KEYS = ON")
124 """CREATE TABLE IF NOT EXISTS experiments
125 (id INTEGER PRIMARY KEY ON CONFLICT REPLACE AUTOINCREMENT, name VARCHAR(512),
126 totaltime REAL, timelimit REAL, memorylimit REAL, runcount INTEGER,
127 version VARCHAR(128), hostname VARCHAR(1024), cpuinfo TEXT,
128 date DATETIME, seed INTEGER, setup TEXT);
129 CREATE TABLE IF NOT EXISTS plannerConfigs
130 (id INTEGER PRIMARY KEY AUTOINCREMENT,
131 name VARCHAR(512) NOT NULL, settings TEXT);
132 CREATE TABLE IF NOT EXISTS enums
133 (name VARCHAR(512), value INTEGER, description TEXT,
134 PRIMARY KEY (name, value));
135 CREATE TABLE IF NOT EXISTS runs
136 (id INTEGER PRIMARY KEY AUTOINCREMENT, experimentid INTEGER, plannerid INTEGER,
137 FOREIGN KEY (experimentid) REFERENCES experiments(id) ON DELETE CASCADE,
138 FOREIGN KEY (plannerid) REFERENCES plannerConfigs(id) ON DELETE CASCADE);
139 CREATE TABLE IF NOT EXISTS progress
140 (runid INTEGER, time REAL, PRIMARY KEY (runid, time),
141 FOREIGN KEY (runid) REFERENCES runs(id) ON DELETE CASCADE)"""
145 allExperimentsName =
"all_experiments"
146 allExperimentsValues = {
158 addAllExperiments = len(filenames) > 0
159 if addAllExperiments:
161 "INSERT INTO experiments VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
162 (
None, allExperimentsName) + tuple(allExperimentsValues.values()),
164 allExperimentsId = c.lastrowid
166 for i, filename
in enumerate(filenames):
167 print(
"Processing " + filename)
168 logfile = open(filename,
"r")
169 start_pos = logfile.tell()
173 logfile.seek(start_pos)
178 version =
" ".join([libname, version])
180 "experiment name", logfile, -1, {0:
"Experiment"}
183 date =
" ".join(
ensurePrefix(logfile.readline(),
"Starting").split()[2:])
191 "time limit", logfile, 0, {-3:
"seconds", -2:
"per", -1:
"run"}
196 "memory limit", logfile, 0, {-3:
"MB", -2:
"per", -1:
"run"}
200 logfile, 0, {-3:
"runs", -2:
"per", -1:
"planner"}
203 if nrrunsOrNone !=
None:
204 nrruns = int(nrrunsOrNone)
205 allExperimentsValues[
"runcount"] += nrruns
208 "total time", logfile, 0, {-3:
"collect", -2:
"the", -1:
"data"}
212 allExperimentsValues[
"totaltime"] += totaltime
213 allExperimentsValues[
"memorylimit"] = max(
214 allExperimentsValues[
"memorylimit"], totaltime
216 allExperimentsValues[
"timelimit"] = max(
217 allExperimentsValues[
"timelimit"], totaltime
221 allExperimentsValues[
"version"] = version
222 allExperimentsValues[
"date"] = date
223 allExperimentsValues[
"setup"] = expsetup
224 allExperimentsValues[
"hostname"] = hostname
225 allExperimentsValues[
"cpuinfo"] = cpuinfo
228 if numEnumsOrNone !=
None:
229 numEnums = int(numEnumsOrNone)
230 for i
in range(numEnums):
231 enum = logfile.readline()[:-1].split(
"|")
232 c.execute(
'SELECT * FROM enums WHERE name IS "%s"' % enum[0])
233 if c.fetchone() ==
None:
234 for j
in range(len(enum) - 1):
236 "INSERT INTO enums VALUES (?,?,?)", (enum[0], j, enum[j + 1])
239 "INSERT INTO experiments VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
255 experimentId = c.lastrowid
259 for i
in range(numPlanners):
260 plannerName = logfile.readline()[:-1]
261 print(
"Parsing data for " + plannerName)
264 numCommon = int(logfile.readline().split()[0])
266 for j
in range(numCommon):
267 settings = settings + logfile.readline() +
";"
271 "SELECT id FROM plannerConfigs WHERE (name=? AND settings=?)",
280 "INSERT INTO plannerConfigs VALUES (?,?,?)",
287 plannerId = c.lastrowid
292 c.execute(
"PRAGMA table_info(runs)")
293 columnNames = [col[1]
for col
in c.fetchall()]
296 numProperties = int(logfile.readline().split()[0])
297 propertyNames = [
"experimentid",
"plannerid"]
298 for j
in range(numProperties):
299 field = logfile.readline().split()
300 propertyType = field[-1]
301 propertyName =
"_".join(field[:-1])
302 if propertyName
not in columnNames:
304 "ALTER TABLE runs ADD %s %s" % (propertyName, propertyType)
306 propertyNames.append(propertyName)
310 +
",".join(propertyNames)
312 +
",".join(
"?" * len(propertyNames))
315 numRuns = int(logfile.readline().split()[0])
317 for j
in range(numRuns):
319 None if isInvalidValue(x)
else x
320 for x
in logfile.readline().split(
"; ")[:-1]
322 values = tuple([experimentId, plannerId] + runValues)
323 c.execute(insertFmtStr, values)
326 runIds.append(c.lastrowid)
328 if addAllExperiments:
329 values = tuple([allExperimentsId, plannerId] + runValues)
330 c.execute(insertFmtStr, values)
332 nextLine = logfile.readline().strip()
337 c.execute(
"PRAGMA table_info(progress)")
338 columnNames = [col[1]
for col
in c.fetchall()]
341 numProgressProperties = int(nextLine.split()[0])
342 progressPropertyNames = [
"runid"]
343 for i
in range(numProgressProperties):
344 field = logfile.readline().split()
345 progressPropertyType = field[-1]
346 progressPropertyName =
"_".join(field[:-1])
347 if progressPropertyName
not in columnNames:
349 "ALTER TABLE progress ADD %s %s"
350 % (progressPropertyName, progressPropertyType)
352 progressPropertyNames.append(progressPropertyName)
355 "INSERT INTO progress ("
356 +
",".join(progressPropertyNames)
358 +
",".join(
"?" * len(progressPropertyNames))
361 numRuns = int(logfile.readline().split()[0])
362 for j
in range(numRuns):
363 dataSeries = logfile.readline().split(
";")[:-1]
364 for dataSample
in dataSeries:
368 None if isInvalidValue(x)
else x
369 for x
in dataSample.split(
",")[:-1]
373 c.execute(insertFmtStr, values)
374 except sqlite3.IntegrityError:
376 "Ignoring duplicate progress data. Consider increasing ompl::tools::Benchmark::Request::timeBetweenUpdates."
383 if addAllExperiments:
384 updateString =
"UPDATE experiments SET"
385 for i, (key, val)
in enumerate(allExperimentsValues.items()):
388 updateString +=
" " + str(key) +
"='" + str(val) +
"'"
389 updateString +=
"WHERE id='" + str(allExperimentsId) +
"'"
390 c.execute(updateString)
396 """Create a plot for a particular attribute. It will include data for
397 all planners that have data for this attribute."""
401 if typename ==
"ENUM":
402 cur.execute(
'SELECT description FROM enums where name IS "%s"' % attribute)
403 descriptions = [t[0]
for t
in cur.fetchall()]
404 numValues = len(descriptions)
405 for planner
in planners:
407 "SELECT %s FROM runs WHERE plannerid = %s AND %s IS NOT NULL"
408 % (attribute, planner[0], attribute)
410 measurement = [t[0]
for t
in cur.fetchall()
if t[0] !=
None]
411 if len(measurement) > 0:
413 "SELECT count(*) FROM runs WHERE plannerid = %s AND %s IS NULL"
414 % (planner[0], attribute)
416 nanCounts.append(cur.fetchone()[0])
417 labels.append(planner[1])
418 if typename ==
"ENUM":
419 scale = 100.0 / len(measurement)
421 [measurement.count(i) * scale
for i
in range(numValues)]
424 measurements.append(measurement)
426 if len(measurements) == 0:
427 print(
'Skipping "%s": no available measurements' % attribute)
432 if typename ==
"ENUM":
434 measurements = np.transpose(np.vstack(measurements))
435 colsum = np.sum(measurements, axis=1)
436 rows = np.where(colsum != 0)[0]
437 heights = np.zeros((1, measurements.shape[1]))
438 ind = range(measurements.shape[1])
446 color=matplotlib.cm.hot(int(floor(i * 256 / numValues))),
447 label=descriptions[i],
449 heights = heights + measurements[i]
450 xtickNames = plt.xticks(
451 [x + width / 2.0
for x
in ind], labels, rotation=30, fontsize=8, ha=
"right"
453 ax.set_ylabel(attribute.replace(
"_",
" ") +
" (%)")
454 box = ax.get_position()
455 ax.set_position([box.x0, box.y0, box.width * 0.8, box.height])
456 props = matplotlib.font_manager.FontProperties()
457 props.set_size(
"small")
458 ax.legend(loc=
"center left", bbox_to_anchor=(1, 0.5), prop=props)
459 elif typename ==
"BOOLEAN":
461 measurementsPercentage = [sum(m) * 100.0 / len(m)
for m
in measurements]
462 ind = range(len(measurements))
463 plt.bar(ind, measurementsPercentage, width)
468 xtickNames = plt.xticks(
469 [x + width / 2.0
for x
in ind], labels, rotation=30, fontsize=8, ha=
"right"
471 ax.set_ylabel(attribute.replace(
"_",
" ") +
" (%)")
476 if int(matplotlibversion.split(
".")[0]) < 1:
477 plt.boxplot(measurements, notch=0, sym=
"k+", vert=1, whis=1.5)
480 measurements, notch=0, sym=
"k+", vert=1, whis=1.5, bootstrap=1000
482 ax.set_ylabel(attribute.replace(
"_",
" "))
491 xtickNames = plt.setp(ax, xticklabels=labels)
492 plt.setp(xtickNames, rotation=30, fontsize=8, ha=
"right")
495 )
in ax.xaxis.get_major_ticks():
496 tick.label.set_fontsize(8)
500 ax.set_xlabel(
"Motion planning algorithm", fontsize=12)
501 ax.yaxis.grid(
True, linestyle=
"-", which=
"major", color=
"lightgrey", alpha=0.5)
502 if max(nanCounts) > 0:
503 maxy = max([max(y)
for y
in measurements])
504 for i
in range(len(labels)):
505 x = i + width / 2
if typename ==
"BOOLEAN" else i + 1
512 """Plot data for a single planner progress attribute. Will create an
513 average time-plot with error bars of the attribute over all runs for
516 import numpy.ma
as ma
520 ax.set_xlabel(
"time (s)")
521 ax.set_ylabel(attribute.replace(
"_",
" "))
523 for planner
in planners:
525 """SELECT count(progress.%s) FROM progress INNER JOIN runs
526 ON progress.runid = runs.id AND runs.plannerid=%s
527 AND progress.%s IS NOT NULL"""
528 % (attribute, planner[0], attribute)
530 if cur.fetchone()[0] > 0:
531 plannerNames.append(planner[1])
533 """SELECT DISTINCT progress.runid FROM progress INNER JOIN runs
534 WHERE progress.runid=runs.id AND runs.plannerid=?""",
537 runids = [t[0]
for t
in cur.fetchall()]
543 "SELECT time, %s FROM progress WHERE runid = %s ORDER BY time"
546 (time, data) = zip(*(cur.fetchall()))
547 timeTable.append(time)
548 dataTable.append(data)
553 fewestSamples = min(len(time[:])
for time
in timeTable)
554 times = np.array(timeTable[0][:fewestSamples])
555 dataArrays = np.array([data[:fewestSamples]
for data
in dataTable])
556 filteredData = ma.masked_array(
557 dataArrays, np.equal(dataArrays,
None), dtype=float
560 means = np.mean(filteredData, axis=0)
561 stddevs = np.std(filteredData, axis=0, ddof=1)
565 times, means, yerr=2 * stddevs, errorevery=max(1, len(times) // 20)
567 ax.legend(plannerNames)
568 if len(plannerNames) > 0:
575 """Create a PDF file with box plots for all attributes."""
576 print(
"Generating plots...")
577 conn = sqlite3.connect(dbname)
579 c.execute(
"PRAGMA FOREIGN_KEYS = ON")
580 c.execute(
"SELECT id, name FROM plannerConfigs")
582 (t[0], t[1].replace(
"geometric_",
"").replace(
"control_",
""))
583 for t
in c.fetchall()
585 c.execute(
"PRAGMA table_info(runs)")
586 colInfo = c.fetchall()[3:]
593 or col[2] ==
"INTEGER"
597 pp.savefig(plt.gcf())
599 c.execute(
"PRAGMA table_info(progress)")
600 colInfo = c.fetchall()[2:]
603 pp.savefig(plt.gcf())
608 c.execute(
"""SELECT id, name, timelimit, memorylimit FROM experiments""")
609 experiments = c.fetchall()
610 for experiment
in experiments:
612 """SELECT count(*) FROM runs WHERE runs.experimentid = %d
613 GROUP BY runs.plannerid"""
616 numRuns = [run[0]
for run
in c.fetchall()]
617 numRuns = numRuns[0]
if len(set(numRuns)) == 1
else ",".join(numRuns)
619 plt.figtext(pagex, pagey,
'Experiment "%s"' % experiment[1])
620 plt.figtext(pagex, pagey - 0.05,
"Number of averaged runs: %d" % numRuns)
622 pagex, pagey - 0.10,
"Time limit per run: %g seconds" % experiment[2]
624 plt.figtext(pagex, pagey - 0.15,
"Memory limit per run: %g MB" % experiment[3])
627 pp.savefig(plt.gcf())
635 print(
"Saving as MySQL dump file...")
637 conn = sqlite3.connect(dbname)
638 mysqldump = open(mysqldump,
"w")
642 c.execute(
"SELECT name FROM sqlite_master WHERE type='table'")
643 table_names = [str(t[0])
for t
in c.fetchall()]
645 last = [
"experiments",
"planner_configs"]
646 for table
in table_names:
647 if table.startswith(
"sqlite"):
649 if not table
in last:
650 mysqldump.write(
"DROP TABLE IF EXISTS `%s`;\n" % table)
652 if table
in table_names:
653 mysqldump.write(
"DROP TABLE IF EXISTS `%s`;\n" % table)
655 for line
in conn.iterdump():
661 "CREATE UNIQUE INDEX",
670 line = re.sub(
r"[\n\r\t ]+",
" ", line)
671 m = re.search(
"CREATE TABLE ([a-zA-Z0-9_]*)(.*)", line)
673 name, sub = m.groups()
674 sub = sub.replace(
'"',
"`")
675 line =
"""CREATE TABLE IF NOT EXISTS %(name)s%(sub)s"""
676 line = line % dict(name=name, sub=sub)
678 line = line.rstrip(
"\n\t ;") +
" ENGINE = InnoDB;\n"
680 m = re.search(
'INSERT INTO "([a-zA-Z0-9_]*)"(.*)', line)
682 line =
"INSERT INTO %s%s\n" % m.groups()
683 line = line.replace(
'"',
r"\"")
684 line = line.replace(
'"',
"'")
686 line = re.sub(
r"([^'])'t'(.)",
"\\1THIS_IS_TRUE\\2", line)
687 line = line.replace(
"THIS_IS_TRUE",
"1")
688 line = re.sub(
r"([^'])'f'(.)",
"\\1THIS_IS_FALSE\\2", line)
689 line = line.replace(
"THIS_IS_FALSE",
"0")
690 line = line.replace(
"AUTOINCREMENT",
"AUTO_INCREMENT")
691 mysqldump.write(line)
696 conn = sqlite3.connect(dbname)
698 c.execute(
"PRAGMA FOREIGN_KEYS = ON")
699 c.execute(
"PRAGMA table_info(runs)")
702 if "simplification_time" in [col[1]
for col
in c.fetchall()]:
703 s0 =
"""SELECT plannerid, plannerConfigs.name AS plannerName, experimentid, solved, time + simplification_time AS total_time
704 FROM plannerConfigs INNER JOIN experiments INNER JOIN runs
705 ON plannerConfigs.id=runs.plannerid AND experiments.id=runs.experimentid"""
707 s0 =
"""SELECT plannerid, plannerConfigs.name AS plannerName, experimentid, solved, time AS total_time
708 FROM plannerConfigs INNER JOIN experiments INNER JOIN runs
709 ON plannerConfigs.id=runs.plannerid AND experiments.id=runs.experimentid"""
711 """SELECT plannerid, plannerName, experimentid, AVG(solved) AS avg_solved, AVG(total_time) AS avg_total_time
712 FROM (%s) GROUP BY plannerid, experimentid"""
716 """SELECT plannerid, experimentid, MIN(avg_solved) AS avg_solved, avg_total_time
717 FROM (%s) GROUP BY plannerName, experimentid ORDER BY avg_solved DESC, avg_total_time ASC"""
720 c.execute(
"DROP VIEW IF EXISTS bestPlannerConfigsPerExperiment")
721 c.execute(
"CREATE VIEW IF NOT EXISTS bestPlannerConfigsPerExperiment AS %s" % s2)
724 """SELECT plannerid, plannerName, AVG(solved) AS avg_solved, AVG(total_time) AS avg_total_time
725 FROM (%s) GROUP BY plannerid"""
729 """SELECT plannerid, MIN(avg_solved) AS avg_solved, avg_total_time
730 FROM (%s) GROUP BY plannerName ORDER BY avg_solved DESC, avg_total_time ASC"""
733 c.execute(
"DROP VIEW IF EXISTS bestPlannerConfigs")
734 c.execute(
"CREATE VIEW IF NOT EXISTS bestPlannerConfigs AS %s" % s2)
740 if __name__ ==
"__main__":
741 usage =
"""%prog [options] [<benchmark.log> ...]"""
742 parser = OptionParser(
"A script to parse benchmarking results.\n" + usage)
747 default=
"benchmark.db",
748 help=
"Filename of benchmark database [default: %default]",
756 help=
"Compute the views for best planner configurations",
763 help=
"Create a PDF of plots with the filename provided",
770 help=
"Save SQLite3 database as a MySQL dump file",
772 (options, args) = parser.parse_args()
775 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)
void print(PropagationDistanceField &pdf, int numX, int numY, int numZ)