Skip to content

Commit 78dcf54

Browse files
committed
Runtest.py massif/memcheck parsing support
1 parent 3867215 commit 78dcf54

File tree

1 file changed

+158
-20
lines changed

1 file changed

+158
-20
lines changed

util/runtest.py

+158-20
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import md5
3333
import json
3434
import yaml
35+
import xml.etree.ElementTree as ET
3536

3637
#
3738
# Platform detection
@@ -160,6 +161,8 @@ def blue(text):
160161
return ansi_color(text, '0;37;44')
161162
def yellow(text):
162163
return ansi_color(text, '0;33;40')
164+
def grey(text):
165+
return ansi_color(text, '6;37;40')
163166

164167
# Parse lines. Split a text input into lines using LF as the line separator.
165168
# Assume last line is terminated with a LF and ignore an "empty line" that
@@ -200,6 +203,72 @@ def clipline(x):
200203
def remove_cr(data):
201204
return data.replace('\r', '')
202205

206+
#
207+
# Valgrind result processing
208+
#
209+
210+
def parse_massif_result(f, res):
211+
# Allocated bytes.
212+
re_heap_b = re.compile(r'^mem_heap_B=(\d+)$')
213+
214+
# Allocation overhead. Matters for desktop environments, for efficient
215+
# zero overhead pool allocators this is not usually a concern (the waste
216+
# in a pool allocator behaves very differently than a libc allocator).
217+
re_heap_extra_b = re.compile(r'^mem_heap_extra_B=(\d+)$')
218+
219+
# Stacks.
220+
re_stacks_b = re.compile(r'^mem_stacks_B=(\d+)$')
221+
222+
peak_heap = 0
223+
peak_stack = 0
224+
225+
for line in f:
226+
line = line.strip()
227+
#print(line)
228+
m1 = re_heap_b.match(line)
229+
m2 = re_heap_extra_b.match(line)
230+
m3 = re_stacks_b.match(line)
231+
232+
heap = None
233+
if m1 is not None:
234+
heap = int(m1.group(1))
235+
stack = None
236+
if m3 is not None:
237+
stack = int(m3.group(1))
238+
239+
if heap is not None:
240+
peak_heap = max(peak_heap, heap)
241+
if stack is not None:
242+
peak_stack = max(peak_stack, stack)
243+
244+
res['massif_peak_heap_bytes'] = peak_heap
245+
res['massif_peak_stack_bytes'] = peak_stack
246+
247+
def parse_memcheck_result(f, res):
248+
try:
249+
tree = ET.parse(f)
250+
except ET.ParseError:
251+
res['errors'].append('memcheck-parse-failed')
252+
return
253+
root = tree.getroot()
254+
if root.tag != 'valgrindoutput':
255+
raise Exception('invalid valgrind xml format')
256+
257+
def parse_error(node):
258+
err = {}
259+
for child in node.findall('kind'):
260+
err['kind'] = child.text
261+
for child in node.findall('xwhat'):
262+
for child2 in child.findall('text'):
263+
err['text'] = child2.text
264+
265+
res['errors'].append(err['kind'])
266+
# XXX: make res['errors'] structured rather than text list?
267+
# 'err' is now ignored.
268+
269+
for child in root.findall('error'):
270+
parse_error(child)
271+
203272
#
204273
# Test execution and result interpretation helpers.
205274
#
@@ -384,13 +453,13 @@ def parse_known_issue(data):
384453
def find_known_issues():
385454
for dirname in [
386455
os.path.join(os.path.dirname(testcase_filename), '..', 'knownissues'),
387-
os.path.join(os.path.dirname(script_path), '..', 'tests', 'knownissues'),
388-
os.path.join(os.path.dirname(entry_cwd), 'tests', 'knownissues')
456+
os.path.join(script_path, '..', 'tests', 'knownissues'),
457+
os.path.join(entry_cwd, 'tests', 'knownissues')
389458
]:
390459
#print('Find known issues, try: %r' % dirname)
391460
if os.path.isdir(dirname):
392461
return dirname
393-
raise Exception('failed to locate testcases')
462+
raise Exception('failed to locate known issues')
394463

395464
# Check known issues against output data.
396465
def check_known_issues(dirname, output):
@@ -423,20 +492,35 @@ def execute_ecmascript_testcase(res, data, name):
423492
test_fn = os.path.abspath(os.path.join(tempdir, name))
424493
write_file(test_fn, data)
425494

495+
valgrind_output = None
496+
426497
cmd = []
427498
try:
428499
start_time = time.time()
429500
try:
430501
if opts.valgrind:
431502
res['valgrind'] = True
432-
cmd += [ 'valgrind', path_to_platform(opts.duk) ]
503+
res['valgrind_tool'] = opts.valgrind_tool
504+
cmd += [ 'valgrind' ]
505+
cmd += [ '--tool=' + opts.valgrind_tool ]
506+
507+
valgrind_output = os.path.abspath(os.path.join(tempdir, 'valgrind.out'))
508+
if opts.valgrind_tool == 'massif':
509+
cmd += [ '--massif-out-file=' + path_to_platform(valgrind_output) ]
510+
#cmd += [ '--peak-inaccuracy=0.0' ]
511+
#cmd += [ '--stacks=yes' ]
512+
elif opts.valgrind_tool == 'memcheck':
513+
cmd += [ '--xml=yes', '--xml-file=' + path_to_platform(valgrind_output) ]
514+
else:
515+
raise Exception('invalid valgrind tool %r' % opts.valgrind_tool)
516+
cmd += [ path_to_platform(os.path.abspath(opts.duk)) ]
433517
else:
434-
cmd += [ opts.duk ]
435-
cmd += [ path_to_platform(test_fn) ]
518+
cmd += [ os.path.abspath(opts.duk) ]
519+
cmd += [ path_to_platform(os.path.abspath(test_fn)) ]
436520
res['command'] = cmd
437521

438522
#print('Executing: %r' % cmd)
439-
proc = subprocess.Popen(cmd, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
523+
proc = subprocess.Popen(cmd, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=os.path.abspath(tempdir))
440524

441525
timeout_sec = opts.timeout
442526
def kill_proc(p):
@@ -458,6 +542,16 @@ def kill_proc(p):
458542
if opts.valgrind:
459543
res['valgrind_output'] = ret[1]
460544
res['stderr'] = '' # otherwise interpreted as an error
545+
if valgrind_output is not None and os.path.exists(valgrind_output):
546+
with open(valgrind_output, 'rb') as f:
547+
res['valgrind_output'] += f.read()
548+
with open(valgrind_output, 'rb') as f:
549+
if opts.valgrind_tool == 'massif':
550+
parse_massif_result(f, res)
551+
elif opts.valgrind_tool == 'memcheck':
552+
parse_memcheck_result(f, res)
553+
else:
554+
res['errors'].append('no-valgrind-output')
461555
except:
462556
exc_type, exc_value, exc_traceback = sys.exc_info()
463557
print('Command execution failed for %r:\n%s' % (cmd, traceback.format_exc(exc_traceback)))
@@ -477,7 +571,7 @@ def execute_api_testcase(data):
477571
def interpret_test_result(doc, expect):
478572
meta = doc['metadata']
479573

480-
errors = []
574+
errors = doc['errors']
481575

482576
known_meta = check_known_issues(opts.known_issues, doc['stdout'])
483577

@@ -506,6 +600,8 @@ def interpret_test_result(doc, expect):
506600
success = False
507601
doc['knownissue'] = known_meta.get('summary', 'no summary')
508602
doc['knownissue_meta'] = known_meta
603+
if len(errors) > 0:
604+
success = False
509605

510606
doc['success'] = success
511607
doc['errors'] = errors
@@ -519,41 +615,70 @@ def interpret_test_result(doc, expect):
519615
def print_summary(doc):
520616
meta = doc['metadata']
521617

522-
parts = []
523-
test_time = '[%.1f sec]' % float(doc['duration'])
618+
def fmt_time(x):
619+
if x >= 60:
620+
return '%.1f m' % (float(x) / 60.0)
621+
else:
622+
return '%.1f s' % float(x)
623+
624+
def fmt_size(x):
625+
if x < 1024 * 1024:
626+
return '%.2f k' % (float(x) / 1024.0)
627+
else:
628+
return '%.2f M' % (float(x) / (1024.0 * 1024.0))
524629

630+
parts = []
631+
issues = []
632+
test_result = '???'
633+
test_name = doc['testcase_name'].ljust(50)
525634
print_diff = True # print diff if it is nonzero
526635

636+
test_time = fmt_time(doc['duration'])
637+
test_time = '[%s]' % (test_time.rjust(6))
638+
527639
if doc['skipped']:
528-
parts += [ 'SKIPPED', doc['testcase_name'] ]
640+
test_result = 'SKIPPED'
529641
elif doc['success']:
530642
if doc['timeout']:
531-
parts += [ red('TIMEOUT'), doc['testcase_name'] ]
643+
test_result = red('TIMEOUT')
532644
else:
533-
parts += [ green('SUCCESS'), doc['testcase_name'] ]
645+
test_result = green('SUCCESS')
534646
else:
535647
if doc['timeout']:
536-
parts += [ red('TIMEOUT'), doc['testcase_name'] ]
648+
test_result = red('TIMEOUT')
537649
elif doc['knownissue'] != '':
538-
parts += [ blue('KNOWN '), doc['testcase_name'] ]
650+
test_result = blue('KNOWN ')
539651
print_diff = False
540652
else:
541-
parts += [ red('FAILURE'), doc['testcase_name'] ]
653+
test_result = red('FAILURE')
542654

543-
parts += [ '[%d diff lines]' % count_lines(doc['diff_expect']) ]
655+
issues += [ '[%d diff lines]' % count_lines(doc['diff_expect']) ]
544656
if doc['knownissue'] != '':
545-
parts += [ '[known: ' + doc['knownissue'] + ']' ]
657+
issues += [ '[known: ' + doc['knownissue'] + ']' ]
546658

547659
if len(doc['errors']) > 0:
548-
parts += [ '[errors: ' + ','.join(doc['errors']) + ']' ]
660+
issues += [ '[errors: ' + ','.join(doc['errors']) + ']' ]
661+
662+
parts += [ test_result, test_name ]
549663

550-
if doc['duration'] >= 30.0:
664+
if doc['duration'] >= 60.0:
551665
parts += [ blue(test_time) ]
552666
elif doc['duration'] >= 5.0:
553667
parts += [ yellow(test_time) ]
554668
else:
555669
parts += [ test_time ]
556670

671+
if doc.has_key('massif_peak_heap_bytes'):
672+
tmp = []
673+
tmp += [ '%s heap' % fmt_size(doc['massif_peak_heap_bytes']) ]
674+
#tmp += [ '%s stack' % fmt_size(doc['massif_peak_stack_bytes']) ]
675+
parts += [ '[%s]' % (', '.join(tmp).rjust(14)) ]
676+
677+
if doc.has_key('valgrind_tool'):
678+
parts += [ grey('[%s]' % doc['valgrind_tool']) ]
679+
680+
parts += issues
681+
557682
print(' '.join(parts))
558683

559684
if doc['stderr'] != '' and not meta.get('intended_uncaught', False):
@@ -604,6 +729,9 @@ def main():
604729
parser.add_option('--duk', dest='duk', default=None, help='Path to "duk" command, default is autodetect')
605730
parser.add_option('--timeout', dest='timeout', type='int', default=15*60, help='Test execution timeout (seconds), default 15min')
606731
parser.add_option('--valgrind', dest='valgrind', action='store_true', default=False, help='Run test inside valgrind')
732+
parser.add_option('--valgrind-tool', dest='valgrind_tool', default=None, help='Valgrind tool to use (implies --valgrind)')
733+
parser.add_option('--memcheck', dest='memcheck', default=False, action='store_true', help='Shorthand for --valgrind-tool memcheck')
734+
parser.add_option('--massif', dest='massif', default=False, action='store_true', help='Shorthand for --valgrind-tool massif')
607735
parser.add_option('--prepare-only', dest='prepare_only', action='store_true', default=False, help='Only prepare a testcase without running it')
608736
parser.add_option('--clip-lines', dest='clip_lines', type='int', default=10, help='Number of lines for stderr/diff summaries')
609737
parser.add_option('--clip-columns', dest='clip_columns', type='int', default=80, help='Number of columns for stderr/diff summaries')
@@ -623,6 +751,16 @@ def main():
623751
if opts.known_issues is None:
624752
opts.known_issues = find_known_issues()
625753
#print('Autodetect known issues directory: %r' % opts.known_issues)
754+
if opts.memcheck:
755+
opts.valgrind = True
756+
opts.valgrind_tool = 'memcheck'
757+
if opts.massif:
758+
opts.valgrind = True
759+
opts.valgrind_tool = 'massif'
760+
if opts.valgrind_tool is not None:
761+
opts.valgrind = True
762+
if opts.valgrind and opts.valgrind_tool is None:
763+
opts.valgrind_tool = 'memcheck'
626764

627765
# Create a temporary directory for anything test related, automatic
628766
# atexit deletion. Plumbed through globals to minimize argument passing.

0 commit comments

Comments
 (0)