Skip to content

Commit d6392cc

Browse files
authored
Merge pull request #20807 from palimondo/and-dreadfully-distinct
[benchmark] Added Benchmark Check Report
2 parents 555f38f + cd47f32 commit d6392cc

File tree

3 files changed

+129
-12
lines changed

3 files changed

+129
-12
lines changed

benchmark/scripts/Benchmark_Driver

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,51 @@ class LoggingReportFormatter(logging.Formatter):
261261
'{0} {1}{2}'.format(record.levelname, category, msg))
262262

263263

264+
class MarkdownReportHandler(logging.StreamHandler):
265+
r"""Write custom formatted messages from BenchmarkDoctor to the stream.
266+
267+
It works around StreamHandler's hardcoded '\n' and handles the custom
268+
level and category formatting for BenchmarkDoctor's check report.
269+
"""
270+
271+
def __init__(self, stream):
272+
"""Initialize the handler and write a Markdown table header."""
273+
super(MarkdownReportHandler, self).__init__(stream)
274+
self.setLevel(logging.INFO)
275+
self.stream.write('\n✅ | Benchmark Check Report\n---|---')
276+
self.stream.flush()
277+
278+
levels = {logging.WARNING: '\n⚠️', logging.ERROR: '\n⛔️',
279+
logging.INFO: ' <br><sub> '}
280+
categories = {'naming': '🔤', 'runtime': '⏱', 'memory': 'Ⓜ️'}
281+
quotes_re = re.compile("'")
282+
283+
def format(self, record):
284+
msg = super(MarkdownReportHandler, self).format(record)
285+
return (self.levels.get(record.levelno, '') +
286+
('' if record.levelno == logging.INFO else
287+
self.categories.get(record.name.split('.')[-1], '') + ' | ') +
288+
self.quotes_re.sub('`', msg))
289+
290+
def emit(self, record):
291+
msg = self.format(record)
292+
stream = self.stream
293+
try:
294+
if (isinstance(msg, unicode) and
295+
getattr(stream, 'encoding', None)):
296+
stream.write(msg.encode(stream.encoding))
297+
else:
298+
stream.write(msg)
299+
except UnicodeError:
300+
stream.write(msg.encode("UTF-8"))
301+
self.flush()
302+
303+
def close(self):
304+
self.stream.write('\n\n')
305+
self.stream.flush()
306+
super(MarkdownReportHandler, self).close()
307+
308+
264309
class BenchmarkDoctor(object):
265310
"""Checks that the benchmark conforms to the standard set of requirements.
266311
@@ -302,8 +347,9 @@ class BenchmarkDoctor(object):
302347
]
303348

304349
def __del__(self):
305-
"""Unregister handler on exit."""
306-
self.log.removeHandler(self.console_handler)
350+
"""Close log handlers on exit."""
351+
for handler in list(self.log.handlers):
352+
handler.close()
307353

308354
capital_words_re = re.compile('[A-Z][a-zA-Z0-9]+')
309355

@@ -407,20 +453,25 @@ class BenchmarkDoctor(object):
407453
range_i1, range_i2 = max_i1 - min_i1, max_i2 - min_i2
408454
normal_range = 15 # pages
409455
name = measurements['name']
456+
more_info = False
410457

411458
if abs(min_i1 - min_i2) > max(range_i1, range_i2, normal_range):
459+
more_info = True
412460
BenchmarkDoctor.log_memory.error(
413461
"'%s' varies the memory footprint of the base "
414462
"workload depending on the `num-iters`.", name)
415463

416464
if max(range_i1, range_i2) > normal_range:
465+
more_info = True
417466
BenchmarkDoctor.log_memory.warning(
418467
"'%s' has very wide range of memory used between "
419468
"independent, repeated measurements.", name)
420469

421-
BenchmarkDoctor.log_memory.debug(
422-
"%s mem_pages [i1, i2]: min=[%d, %d] 𝚫=%d R=[%d, %d]", name,
423-
*[min_i1, min_i2, abs(min_i1 - min_i2), range_i1, range_i2])
470+
if more_info:
471+
BenchmarkDoctor.log_memory.info(
472+
"'%s' mem_pages [i1, i2]: min=[%d, %d] 𝚫=%d R=[%d, %d]",
473+
name,
474+
*[min_i1, min_i2, abs(min_i1 - min_i2), range_i1, range_i2])
424475

425476
@staticmethod
426477
def _adjusted_1s_samples(runtime):

benchmark/scripts/run_smoke_bench

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ def main():
6161
argparser.add_argument(
6262
'-skip-performance', action='store_true',
6363
help="Don't report performance differences")
64+
argparser.add_argument(
65+
'-skip-check-added', action='store_true',
66+
help="Don't validate newly added benchmarks")
6467
argparser.add_argument(
6568
'-o', type=str,
6669
help='In addition to stdout, write the results into a markdown file')
@@ -80,15 +83,10 @@ def main():
8083
argparser.add_argument(
8184
'newbuilddir', nargs=1, type=str,
8285
help='new benchmark build directory')
83-
argparser.add_argument(
84-
'-check-added', action='store_const',
85-
help="Run BenchmarkDoctor's check on newly added benchmarks",
86-
const=lambda args: check_added(args), dest='func')
87-
argparser.set_defaults(func=test_opt_levels)
8886
args = argparser.parse_args()
8987
VERBOSE = args.verbose
9088

91-
return args.func(args)
89+
return test_opt_levels(args)
9290

9391

9492
def test_opt_levels(args):
@@ -119,6 +117,9 @@ def test_opt_levels(args):
119117
args.platform, output_file):
120118
changes = True
121119

120+
if not args.skip_check_added:
121+
check_added(args, output_file)
122+
122123
if output_file:
123124
if changes:
124125
output_file.write(get_info_text())
@@ -339,7 +340,7 @@ class DriverArgs(object):
339340
self.optimization = 'O'
340341

341342

342-
def check_added(args):
343+
def check_added(args, output_file=None):
343344
from imp import load_source
344345
# import Benchmark_Driver # doesn't work because it misses '.py' extension
345346
Benchmark_Driver = load_source(
@@ -348,12 +349,15 @@ def check_added(args):
348349
# from Benchmark_Driver import BenchmarkDriver, BenchmarkDoctor
349350
BenchmarkDriver = Benchmark_Driver.BenchmarkDriver
350351
BenchmarkDoctor = Benchmark_Driver.BenchmarkDoctor
352+
MarkdownReportHandler = Benchmark_Driver.MarkdownReportHandler
351353

352354
old = BenchmarkDriver(DriverArgs(args.oldbuilddir[0]))
353355
new = BenchmarkDriver(DriverArgs(args.newbuilddir[0]))
354356
added = set(new.tests).difference(set(old.tests))
355357
new.tests = list(added)
356358
doctor = BenchmarkDoctor(args, driver=new)
359+
if added and output_file:
360+
doctor.log.addHandler(MarkdownReportHandler(output_file))
357361
doctor.check()
358362

359363

benchmark/scripts/test_Benchmark_Driver.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import time
1919
import unittest
2020

21+
from StringIO import StringIO
2122
from imp import load_source
2223

2324
from compare_perf_tests import PerformanceTestResult
@@ -33,6 +34,7 @@
3334
BenchmarkDriver = Benchmark_Driver.BenchmarkDriver
3435
BenchmarkDoctor = Benchmark_Driver.BenchmarkDoctor
3536
LoggingReportFormatter = Benchmark_Driver.LoggingReportFormatter
37+
MarkdownReportHandler = Benchmark_Driver.MarkdownReportHandler
3638

3739

3840
class Test_parse_args(unittest.TestCase):
@@ -421,6 +423,58 @@ def test_no_prefix_for_base_logging(self):
421423
self.assertEquals(f.format(lr), 'INFO Hi!')
422424

423425

426+
class TestMarkdownReportHandler(unittest.TestCase):
427+
def setUp(self):
428+
super(TestMarkdownReportHandler, self).setUp()
429+
self.stream = StringIO()
430+
self.handler = MarkdownReportHandler(self.stream)
431+
432+
def assert_contains(self, texts):
433+
assert not isinstance(texts, str)
434+
for text in texts:
435+
self.assertIn(text, self.stream.getvalue())
436+
437+
def record(self, level, category, msg):
438+
return logging.makeLogRecord({
439+
'name': 'BenchmarkDoctor.' + category,
440+
'levelno': level, 'msg': msg})
441+
442+
def test_init_writes_table_header(self):
443+
self.assertEquals(self.handler.level, logging.INFO)
444+
self.assert_contains(['Benchmark Check Report\n', '---|---'])
445+
446+
def test_close_writes_final_newlines(self):
447+
self.handler.close()
448+
self.assert_contains(['---|---\n\n'])
449+
450+
def test_errors_and_warnings_start_new_rows_with_icons(self):
451+
self.handler.emit(self.record(logging.ERROR, '', 'Blunder'))
452+
self.handler.emit(self.record(logging.WARNING, '', 'Boo-boo'))
453+
self.assert_contains(['\n⛔️ | Blunder',
454+
'\n⚠️ | Boo-boo'])
455+
456+
def test_category_icons(self):
457+
self.handler.emit(self.record(logging.WARNING, 'naming', 'naming'))
458+
self.handler.emit(self.record(logging.WARNING, 'runtime', 'runtime'))
459+
self.handler.emit(self.record(logging.WARNING, 'memory', 'memory'))
460+
self.assert_contains(['🔤 | naming',
461+
'⏱ | runtime',
462+
'Ⓜ️ | memory'])
463+
464+
def test_info_stays_in_table_cell_breaking_line_row_to_subscript(self):
465+
"""Assuming Infos only follow after Errors and Warnings.
466+
467+
Infos don't emit category icons.
468+
"""
469+
self.handler.emit(self.record(logging.ERROR, 'naming', 'Blunder'))
470+
self.handler.emit(self.record(logging.INFO, 'naming', 'Fixit'))
471+
self.assert_contains(['Blunder <br><sub> Fixit'])
472+
473+
def test_names_in_code_format(self):
474+
self.handler.emit(self.record(logging.WARNING, '', "'QuotedName'"))
475+
self.assert_contains(['| `QuotedName`'])
476+
477+
424478
def _PTR(min=700, mem_pages=1000, setup=None):
425479
"""Create PerformanceTestResult Stub."""
426480
return Stub(samples=Stub(min=min), mem_pages=mem_pages, setup=setup)
@@ -681,10 +735,18 @@ def test_benchmark_has_constant_memory_use(self):
681735
["'VariableMemory' varies the memory footprint of the base "
682736
"workload depending on the `num-iters`."],
683737
self.logs['error'])
738+
self.assert_contains(
739+
["'VariableMemory' "
740+
"mem_pages [i1, i2]: min=[1460, 1750] 𝚫=290 R=[12, 2]"],
741+
self.logs['info'])
684742
self.assert_contains(
685743
["'HighVariance' has very wide range of memory used between "
686744
"independent, repeated measurements."],
687745
self.logs['warning'])
746+
self.assert_contains(
747+
["'HighVariance' "
748+
"mem_pages [i1, i2]: min=[4818, 4674] 𝚫=144 R=[1382, 1570]"],
749+
self.logs['info'])
688750

689751

690752
if __name__ == '__main__':

0 commit comments

Comments
 (0)