Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(412)

Unified Diff: pydir/bisection-tool.py

Issue 2162123002: Bisection debugging helper script (Closed) Base URL: https://chromium.googlesource.com/native_client/pnacl-subzero.git@master
Patch Set: Address Comments Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « pydir/bisection-test.py ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pydir/bisection-tool.py
diff --git a/pydir/bisection-tool.py b/pydir/bisection-tool.py
new file mode 100755
index 0000000000000000000000000000000000000000..6e6c6662f4b09ba4a421440fcf77dc59fe41f135
--- /dev/null
+++ b/pydir/bisection-tool.py
@@ -0,0 +1,201 @@
+#!/usr/bin/env python2
+
+import argparse
+import math
+import os
+import re
+import signal
+import subprocess
+
+class Runner(object):
+ def __init__(self, input_cmd, timeout, comma_join, template, find_all):
+ self._input_cmd = input_cmd
+ self._timeout = timeout
+ self._num_tries = 0
+ self._comma_join = comma_join
+ self._template = template
+ self._find_all = find_all
+
+ def estimate(self, included_ranges):
+ result = 0
+ for i in included_ranges:
+ if isinstance(i, int):
+ result += 1
+ else:
+ if i[1] - i[0] > 2:
+ result += int(math.log(i[1] - i[0], 2))
+ else:
+ result += (i[1] - i[0])
+ if self._find_all:
+ return 2 * result
+ else:
+ return result
+
+ def Run(self, included_ranges):
+ def timeout_handler(signum, frame):
+ raise RuntimeError('Timeout')
+
+ self._num_tries += 1
+ cmd_addition = ''
+ for i in included_ranges:
+ if isinstance(i, int):
+ range_str = str(i)
+ else:
+ range_str = '{start}:{end}'.format(start=i[0], end=i[1])
+ if self._comma_join:
+ cmd_addition += ',' + range_str
+ else:
+ cmd_addition += ' -i ' + range_str
+
+ if self._template:
+ cmd = cmd_addition.join(re.split(r'%i' ,self._input_cmd))
+ else:
+ cmd = self._input_cmd + cmd_addition
+
+ print cmd
+ p = subprocess.Popen(cmd, shell = True, cwd = None,
+ stdout = subprocess.PIPE, stderr = subprocess.PIPE, env = None)
+ if self._timeout != -1:
+ signal.signal(signal.SIGALRM, timeout_handler)
+ signal.alarm(self._timeout)
+
+ try:
+ _, _ = p.communicate()
+ if self._timeout != -1:
+ signal.alarm(0)
+ except:
+ try:
+ os.kill(p.pid, signal.SIGKILL)
+ except OSError:
+ pass
+ print 'Timeout'
+ return -9
+ print '===Return Code===: ' + str(p.returncode)
+ print '===Remaining Steps (approx)===: ' \
+ + str(self.estimate(included_ranges))
+ return p.returncode
+
+def flatten(tree):
+ if isinstance(tree, list):
+ result = []
+ for node in tree:
+ result.extend(flatten(node))
+ return result
+ else:
+ return [tree] # leaf
+
+def find_failures(runner, current_interval, include_ranges, find_all):
+ if current_interval[0] == current_interval[1]:
+ return []
+ mid = (current_interval[0] + current_interval[1]) / 2
+
+ first_half = (current_interval[0], mid)
+ second_half = (mid, current_interval[1])
+
+ exit_code_2 = 0
+
+ exit_code_1 = runner.Run([first_half] + include_ranges)
+ if find_all or exit_code_1 == 0:
+ exit_code_2 = runner.Run([second_half] + include_ranges)
+
+ if exit_code_1 == 0 and exit_code_2 == 0:
+ # Whole range fails but both halves pass
+ # So, some conjunction of functions cause a failure, but none individually.
+ partial_result = flatten(find_failures(runner, first_half, [second_half]
+ + include_ranges, find_all))
+ # Heavy list concatenation, but this is insignificant compared to the
+ # process run times
+ partial_result.extend(flatten(find_failures(runner, second_half,
+ partial_result + include_ranges, find_all)))
+ return [partial_result]
+ else:
+ result = []
+ if exit_code_1 != 0:
+ if first_half[1] == first_half[0] + 1:
+ result.append(first_half[0])
+ else:
+ result.extend(find_failures(runner, first_half,
+ include_ranges, find_all))
+ if exit_code_2 != 0:
+ if second_half[1] == second_half[0] + 1:
+ result.append(second_half[0])
+ else:
+ result.extend(find_failures(runner, second_half,
+ include_ranges, find_all))
+ return result
+
+
+def main():
+ '''
+ Helper Script for Automating Bisection Debugging
+
+ Example Invocation:
+ bisection-tool.py --cmd 'bisection-test.py -c 2x3' --end 1000 --timeout 60
+
+ This will invoke 'bisection-test.py -c 2x3' starting with the range -i 0:1000
+ If that fails, it will subdivide the range (initially 0:500 and 500:1000)
+ recursively to pinpoint a combination of singletons that are needed to cause
+ the input to return a non zero exit code or timeout.
+
+ For investigating an error in the generated code:
+ bisection-tool.py --cmd './pydir/szbuild_spec2k.py --run 188.ammp'
+
+ For Subzero itself crashing,
+ bisection-tool.py --cmd 'pnacl-sz -translate-only=' --comma-join=1
+ The --comma-join flag ensures the ranges are formatted in the manner pnacl-sz
+ expects.
+
+ If the range specification is not to be appended on the input:
+ bisection-tool.py --cmd 'echo %i; cmd-main %i; cmd-post' --template=1
+
+ '''
+ argparser = argparse.ArgumentParser(main.__doc__)
+ argparser.add_argument('--cmd', required=True, dest='cmd',
+ help='Runnable command')
+
+ argparser.add_argument('--start', dest='start', default=0,
+ help='Start of initial range')
+
+ argparser.add_argument('--end', dest='end', default=50000,
+ help='End of initial range')
+
+ argparser.add_argument('--timeout', dest='timeout', default=60,
+ help='Timeout for each invocation of the input')
+
+ argparser.add_argument('--all', type=int, choices=[0,1], default=1,
+ dest='all', help='Find all failures')
+
+ argparser.add_argument('--comma-join', type=int, choices=[0,1], default=0,
+ dest='comma_join', help='Use comma to join ranges')
+
+ argparser.add_argument('--template', type=int, choices=[0,1], default=0,
+ dest='template',
+ help='Replace %%i in the cmd string with the ranges')
+
+
+ args = argparser.parse_args()
+
+ fail_list = []
+
+ initial_range = (int(args.start), int(args.end))
+ timeout = int(args.timeout)
+ runner = Runner(args.cmd, timeout, args.comma_join, args.template, args.all)
+ if runner.Run([initial_range]) != 0:
+ fail_list = find_failures(runner, initial_range, [], args.all)
+ else:
+ print 'Pass'
+ # The whole input range works, maybe check subzero build flags?
+ # Also consider widening the initial range (control with --start and --end)
+
+ if fail_list:
+ print 'Failing Items:'
+ for fail in fail_list:
+ if isinstance(fail, list):
+ fail.sort()
+ print '[' + ','.join(str(x) for x in fail) + ']'
+ else:
+ print fail
+ print 'Number of tries: ' + str(runner._num_tries)
+
+if __name__ == '__main__':
+ main()
« no previous file with comments | « pydir/bisection-test.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698