- add third_party src.
[platform/framework/web/crosswalk.git] / src / v8 / tools / run-deopt-fuzzer.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2012 the V8 project authors. All rights reserved.
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 #     * Redistributions of source code must retain the above copyright
9 #       notice, this list of conditions and the following disclaimer.
10 #     * Redistributions in binary form must reproduce the above
11 #       copyright notice, this list of conditions and the following
12 #       disclaimer in the documentation and/or other materials provided
13 #       with the distribution.
14 #     * Neither the name of Google Inc. nor the names of its
15 #       contributors may be used to endorse or promote products derived
16 #       from this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30
31 import json
32 import math
33 import multiprocessing
34 import optparse
35 import os
36 from os.path import join
37 import random
38 import shlex
39 import subprocess
40 import sys
41 import time
42
43 from testrunner.local import execution
44 from testrunner.local import progress
45 from testrunner.local import testsuite
46 from testrunner.local import utils
47 from testrunner.local import verbose
48 from testrunner.objects import context
49
50
51 ARCH_GUESS = utils.DefaultArch()
52 DEFAULT_TESTS = ["mjsunit", "webkit"]
53 TIMEOUT_DEFAULT = 60
54 TIMEOUT_SCALEFACTOR = {"debug"   : 4,
55                        "release" : 1 }
56
57 MODE_FLAGS = {
58     "debug"   : ["--nobreak-on-abort", "--nodead-code-elimination",
59                  "--nofold-constants", "--enable-slow-asserts",
60                  "--debug-code", "--verify-heap",
61                  "--noconcurrent-recompilation"],
62     "release" : ["--nobreak-on-abort", "--nodead-code-elimination",
63                  "--nofold-constants", "--noconcurrent-recompilation"]}
64
65 SUPPORTED_ARCHS = ["android_arm",
66                    "android_ia32",
67                    "arm",
68                    "ia32",
69                    "mipsel",
70                    "nacl_ia32",
71                    "nacl_x64",
72                    "x64"]
73 # Double the timeout for these:
74 SLOW_ARCHS = ["android_arm",
75               "android_ia32",
76               "arm",
77               "mipsel",
78               "nacl_ia32",
79               "nacl_x64"]
80 MAX_DEOPT = 1000000000
81 DISTRIBUTION_MODES = ["smooth", "random"]
82
83
84 class RandomDistribution:
85   def __init__(self, seed=None):
86     seed = seed or random.randint(1, sys.maxint)
87     print "Using random distribution with seed %d" % seed
88     self._random = random.Random(seed)
89
90   def Distribute(self, n, m):
91     if n > m:
92       n = m
93     return self._random.sample(xrange(1, m + 1), n)
94
95
96 class SmoothDistribution:
97   """Distribute n numbers into the interval [1:m].
98   F1: Factor of the first derivation of the distribution function.
99   F2: Factor of the second derivation of the distribution function.
100   With F1 and F2 set to 0, the distribution will be equal.
101   """
102   def __init__(self, factor1=2.0, factor2=0.2):
103     self._factor1 = factor1
104     self._factor2 = factor2
105
106   def Distribute(self, n, m):
107     if n > m:
108       n = m
109     if n <= 1:
110       return [ 1 ]
111
112     result = []
113     x = 0.0
114     dx = 1.0
115     ddx = self._factor1
116     dddx = self._factor2
117     for i in range(0, n):
118       result += [ x ]
119       x += dx
120       dx += ddx
121       ddx += dddx
122
123     # Project the distribution into the interval [0:M].
124     result = [ x * m / result[-1] for x in result ]
125
126     # Equalize by n. The closer n is to m, the more equal will be the
127     # distribution.
128     for (i, x) in enumerate(result):
129       # The value of x if it was equally distributed.
130       equal_x = i / float(n - 1) * float(m - 1) + 1
131
132       # Difference factor between actual and equal distribution.
133       diff = 1 - (x / equal_x)
134
135       # Equalize x dependent on the number of values to distribute.
136       result[i] = int(x + (i + 1) * diff)
137     return result
138
139
140 def Distribution(options):
141   if options.distribution_mode == "random":
142     return RandomDistribution(options.seed)
143   if options.distribution_mode == "smooth":
144     return SmoothDistribution(options.distribution_factor1,
145                               options.distribution_factor2)
146
147
148 def BuildOptions():
149   result = optparse.OptionParser()
150   result.add_option("--arch",
151                     help=("The architecture to run tests for, "
152                           "'auto' or 'native' for auto-detect"),
153                     default="ia32,x64,arm")
154   result.add_option("--arch-and-mode",
155                     help="Architecture and mode in the format 'arch.mode'",
156                     default=None)
157   result.add_option("--buildbot",
158                     help="Adapt to path structure used on buildbots",
159                     default=False, action="store_true")
160   result.add_option("--command-prefix",
161                     help="Prepended to each shell command used to run a test",
162                     default="")
163   result.add_option("--coverage", help=("Exponential test coverage "
164                     "(range 0.0, 1.0) -- 0.0: one test, 1.0 all tests (slow)"),
165                     default=0.4, type="float")
166   result.add_option("--coverage-lift", help=("Lifts test coverage for tests "
167                     "with a small number of deopt points (range 0, inf)"),
168                     default=20, type="int")
169   result.add_option("--download-data", help="Download missing test suite data",
170                     default=False, action="store_true")
171   result.add_option("--distribution-factor1", help=("Factor of the first "
172                     "derivation of the distribution function"), default=2.0,
173                     type="float")
174   result.add_option("--distribution-factor2", help=("Factor of the second "
175                     "derivation of the distribution function"), default=0.7,
176                     type="float")
177   result.add_option("--distribution-mode", help=("How to select deopt points "
178                     "for a given test (smooth|random)"),
179                     default="smooth")
180   result.add_option("--dump-results-file", help=("Dump maximum number of "
181                     "deopt points per test to a file"))
182   result.add_option("--extra-flags",
183                     help="Additional flags to pass to each test command",
184                     default="")
185   result.add_option("--isolates", help="Whether to test isolates",
186                     default=False, action="store_true")
187   result.add_option("-j", help="The number of parallel tasks to run",
188                     default=0, type="int")
189   result.add_option("-m", "--mode",
190                     help="The test modes in which to run (comma-separated)",
191                     default="release,debug")
192   result.add_option("--outdir", help="Base directory with compile output",
193                     default="out")
194   result.add_option("-p", "--progress",
195                     help=("The style of progress indicator"
196                           " (verbose, dots, color, mono)"),
197                     choices=progress.PROGRESS_INDICATORS.keys(),
198                     default="mono")
199   result.add_option("--shard-count",
200                     help="Split testsuites into this number of shards",
201                     default=1, type="int")
202   result.add_option("--shard-run",
203                     help="Run this shard from the split up tests.",
204                     default=1, type="int")
205   result.add_option("--shell-dir", help="Directory containing executables",
206                     default="")
207   result.add_option("--seed", help="The seed for the random distribution",
208                     type="int")
209   result.add_option("-t", "--timeout", help="Timeout in seconds",
210                     default= -1, type="int")
211   result.add_option("-v", "--verbose", help="Verbose output",
212                     default=False, action="store_true")
213   return result
214
215
216 def ProcessOptions(options):
217   global VARIANT_FLAGS
218
219   # Architecture and mode related stuff.
220   if options.arch_and_mode:
221     tokens = options.arch_and_mode.split(".")
222     options.arch = tokens[0]
223     options.mode = tokens[1]
224   options.mode = options.mode.split(",")
225   for mode in options.mode:
226     if not mode.lower() in ["debug", "release"]:
227       print "Unknown mode %s" % mode
228       return False
229   if options.arch in ["auto", "native"]:
230     options.arch = ARCH_GUESS
231   options.arch = options.arch.split(",")
232   for arch in options.arch:
233     if not arch in SUPPORTED_ARCHS:
234       print "Unknown architecture %s" % arch
235       return False
236
237   # Special processing of other options, sorted alphabetically.
238   options.command_prefix = shlex.split(options.command_prefix)
239   options.extra_flags = shlex.split(options.extra_flags)
240   if options.j == 0:
241     options.j = multiprocessing.cpu_count()
242   if not options.distribution_mode in DISTRIBUTION_MODES:
243     print "Unknown distribution mode %s" % options.distribution_mode
244     return False
245   if options.distribution_factor1 < 0.0:
246     print ("Distribution factor1 %s is out of range. Defaulting to 0.0"
247         % options.distribution_factor1)
248     options.distribution_factor1 = 0.0
249   if options.distribution_factor2 < 0.0:
250     print ("Distribution factor2 %s is out of range. Defaulting to 0.0"
251         % options.distribution_factor2)
252     options.distribution_factor2 = 0.0
253   if options.coverage < 0.0 or options.coverage > 1.0:
254     print ("Coverage %s is out of range. Defaulting to 0.4"
255         % options.coverage)
256     options.coverage = 0.4
257   if options.coverage_lift < 0:
258     print ("Coverage lift %s is out of range. Defaulting to 0"
259         % options.coverage_lift)
260     options.coverage_lift = 0
261   return True
262
263
264 def ShardTests(tests, shard_count, shard_run):
265   if shard_count < 2:
266     return tests
267   if shard_run < 1 or shard_run > shard_count:
268     print "shard-run not a valid number, should be in [1:shard-count]"
269     print "defaulting back to running all tests"
270     return tests
271   count = 0
272   shard = []
273   for test in tests:
274     if count % shard_count == shard_run - 1:
275       shard.append(test)
276     count += 1
277   return shard
278
279
280 def Main():
281   parser = BuildOptions()
282   (options, args) = parser.parse_args()
283   if not ProcessOptions(options):
284     parser.print_help()
285     return 1
286
287   exit_code = 0
288   workspace = os.path.abspath(join(os.path.dirname(sys.argv[0]), ".."))
289
290   suite_paths = utils.GetSuitePaths(join(workspace, "test"))
291
292   if len(args) == 0:
293     suite_paths = [ s for s in suite_paths if s in DEFAULT_TESTS ]
294   else:
295     args_suites = set()
296     for arg in args:
297       suite = arg.split(os.path.sep)[0]
298       if not suite in args_suites:
299         args_suites.add(suite)
300     suite_paths = [ s for s in suite_paths if s in args_suites ]
301
302   suites = []
303   for root in suite_paths:
304     suite = testsuite.TestSuite.LoadTestSuite(
305         os.path.join(workspace, "test", root))
306     if suite:
307       suites.append(suite)
308
309   if options.download_data:
310     for s in suites:
311       s.DownloadData()
312
313   for mode in options.mode:
314     for arch in options.arch:
315       code = Execute(arch, mode, args, options, suites, workspace)
316       exit_code = exit_code or code
317   return exit_code
318
319
320 def CalculateNTests(m, options):
321   """Calculates the number of tests from m deopt points with exponential
322   coverage.
323   The coverage is expected to be between 0.0 and 1.0.
324   The 'coverage lift' lifts the coverage for tests with smaller m values.
325   """
326   c = float(options.coverage)
327   l = float(options.coverage_lift)
328   return int(math.pow(m, (m * c + l) / (m + l)))
329
330
331 def Execute(arch, mode, args, options, suites, workspace):
332   print(">>> Running tests for %s.%s" % (arch, mode))
333
334   dist = Distribution(options)
335
336   shell_dir = options.shell_dir
337   if not shell_dir:
338     if options.buildbot:
339       shell_dir = os.path.join(workspace, options.outdir, mode)
340       mode = mode.lower()
341     else:
342       shell_dir = os.path.join(workspace, options.outdir,
343                                "%s.%s" % (arch, mode))
344   shell_dir = os.path.relpath(shell_dir)
345
346   # Populate context object.
347   mode_flags = MODE_FLAGS[mode]
348   timeout = options.timeout
349   if timeout == -1:
350     # Simulators are slow, therefore allow a longer default timeout.
351     if arch in SLOW_ARCHS:
352       timeout = 2 * TIMEOUT_DEFAULT;
353     else:
354       timeout = TIMEOUT_DEFAULT;
355
356   timeout *= TIMEOUT_SCALEFACTOR[mode]
357   ctx = context.Context(arch, mode, shell_dir,
358                         mode_flags, options.verbose,
359                         timeout, options.isolates,
360                         options.command_prefix,
361                         options.extra_flags,
362                         False)
363
364   # Find available test suites and read test cases from them.
365   variables = {
366     "mode": mode,
367     "arch": arch,
368     "system": utils.GuessOS(),
369     "isolates": options.isolates,
370     "deopt_fuzzer": True,
371     "no_i18n": False,
372   }
373   all_tests = []
374   num_tests = 0
375   test_id = 0
376
377   # Remember test case prototypes for the fuzzing phase.
378   test_backup = dict((s, []) for s in suites)
379
380   for s in suites:
381     s.ReadStatusFile(variables)
382     s.ReadTestCases(ctx)
383     if len(args) > 0:
384       s.FilterTestCasesByArgs(args)
385     all_tests += s.tests
386     s.FilterTestCasesByStatus(False)
387     test_backup[s] = s.tests
388     analysis_flags = ["--deopt-every-n-times", "%d" % MAX_DEOPT,
389                       "--print-deopt-stress"]
390     s.tests = [ t.CopyAddingFlags(analysis_flags) for t in s.tests ]
391     num_tests += len(s.tests)
392     for t in s.tests:
393       t.id = test_id
394       test_id += 1
395
396   if num_tests == 0:
397     print "No tests to run."
398     return 0
399
400   try:
401     print(">>> Collection phase")
402     progress_indicator = progress.PROGRESS_INDICATORS[options.progress]()
403     runner = execution.Runner(suites, progress_indicator, ctx)
404
405     exit_code = runner.Run(options.j)
406     if runner.terminate:
407       return exit_code
408
409   except KeyboardInterrupt:
410     return 1
411
412   print(">>> Analysis phase")
413   num_tests = 0
414   test_id = 0
415   for s in suites:
416     test_results = {}
417     for t in s.tests:
418       for line in t.output.stdout.splitlines():
419         if line.startswith("=== Stress deopt counter: "):
420           test_results[t.path] = MAX_DEOPT - int(line.split(" ")[-1])
421     for t in s.tests:
422       if t.path not in test_results:
423         print "Missing results for %s" % t.path
424     if options.dump_results_file:
425       results_dict = dict((t.path, n) for (t, n) in test_results.iteritems())
426       with file("%s.%d.txt" % (dump_results_file, time.time()), "w") as f:
427         f.write(json.dumps(results_dict))
428
429     # Reset tests and redistribute the prototypes from the collection phase.
430     s.tests = []
431     if options.verbose:
432       print "Test distributions:"
433     for t in test_backup[s]:
434       max_deopt = test_results.get(t.path, 0)
435       if max_deopt == 0:
436         continue
437       n_deopt = CalculateNTests(max_deopt, options)
438       distribution = dist.Distribute(n_deopt, max_deopt)
439       if options.verbose:
440         print "%s %s" % (t.path, distribution)
441       for i in distribution:
442         fuzzing_flags = ["--deopt-every-n-times", "%d" % i]
443         s.tests.append(t.CopyAddingFlags(fuzzing_flags))
444     num_tests += len(s.tests)
445     for t in s.tests:
446       t.id = test_id
447       test_id += 1
448
449   if num_tests == 0:
450     print "No tests to run."
451     return 0
452
453   try:
454     print(">>> Deopt fuzzing phase (%d test cases)" % num_tests)
455     progress_indicator = progress.PROGRESS_INDICATORS[options.progress]()
456     runner = execution.Runner(suites, progress_indicator, ctx)
457
458     exit_code = runner.Run(options.j)
459     if runner.terminate:
460       return exit_code
461
462   except KeyboardInterrupt:
463     return 1
464
465   return exit_code
466
467
468 if __name__ == "__main__":
469   sys.exit(Main())