Title: [155307] trunk/Tools
Revision
155307
Author
[email protected]
Date
2013-09-08 11:24:01 -0700 (Sun, 08 Sep 2013)

Log Message

run-jsc-stress-tests should run tests in parallel if possible
https://bugs.webkit.org/show_bug.cgi?id=120996

Reviewed by Oliver Hunt.
        
Run tests in parallel by using Makefile hacks. This lets make take care of all load
balancing. It's a really awesome speed-up for these tests.
        
This reduces the FTL debug build stress test run time from 8m18s to 2m11s. The 
no-FTL debug build stress test time goes from 1m11s to 16s. This patch is pretty
much awesome.

* Scripts/run-jsc-stress-tests:

Modified Paths

Diff

Modified: trunk/Tools/ChangeLog (155306 => 155307)


--- trunk/Tools/ChangeLog	2013-09-08 18:10:56 UTC (rev 155306)
+++ trunk/Tools/ChangeLog	2013-09-08 18:24:01 UTC (rev 155307)
@@ -1,5 +1,21 @@
 2013-09-08  Filip Pizlo  <[email protected]>
 
+        run-jsc-stress-tests should run tests in parallel if possible
+        https://bugs.webkit.org/show_bug.cgi?id=120996
+
+        Reviewed by Oliver Hunt.
+        
+        Run tests in parallel by using Makefile hacks. This lets make take care of all load
+        balancing. It's a really awesome speed-up for these tests.
+        
+        This reduces the FTL debug build stress test run time from 8m18s to 2m11s. The 
+        no-FTL debug build stress test time goes from 1m11s to 16s. This patch is pretty
+        much awesome.
+
+        * Scripts/run-jsc-stress-tests:
+
+2013-09-08  Filip Pizlo  <[email protected]>
+
         run-jsc-stress-tests should be smart enough to call JSRegress "regress/script-tests" rather than "script-tests"
         https://bugs.webkit.org/show_bug.cgi?id=120994
 

Modified: trunk/Tools/Scripts/run-jsc-stress-tests (155306 => 155307)


--- trunk/Tools/Scripts/run-jsc-stress-tests	2013-09-08 18:10:56 UTC (rev 155306)
+++ trunk/Tools/Scripts/run-jsc-stress-tests	2013-09-08 18:24:01 UTC (rev 155307)
@@ -26,23 +26,30 @@
 require 'getoptlong'
 require 'pathname'
 
+$haveShellwords = false
+
 begin
     require 'shellwords'
+    $haveShellwords = true
 rescue => e
     $stderr.puts "Warning: did not find shellwords; some features will be disabled."
     $stderr.puts "Error: #{e.inspect}"
 end
 
+numProcessors = `sysctl -n hw.availcpu`.to_i
+
 $jscPath = nil
 $enableFTL = false
 $collections = []
 $outputDir = Pathname.new("results")
+$parallel = ($haveShellwords and numProcessors > 1)
 
 def usage
     puts "run-jsc-stress-tests -j <shell path> <collections path> [<collections path> ...]"
     puts
     puts "--jsc            (-j)   Path to _javascript_Core. This option is required."
     puts "--ftl-jit               Indicate that we have the FTL JIT."
+    puts "--[no-]parallel         Run in parallel, or not. Default is #{$parallel}."
     puts "--output-dir     (-o)   Path where to put results. Default is #{$outputDir}."
     puts "--help           (-h)   Print this message."
     exit 1
@@ -51,6 +58,8 @@
 GetoptLong.new(['--help', '-h', GetoptLong::NO_ARGUMENT],
                ['--jsc', '-j', GetoptLong::REQUIRED_ARGUMENT],
                ['--ftl-jit', GetoptLong::NO_ARGUMENT],
+               ['--parallel', GetoptLong::NO_ARGUMENT],
+               ['--no-parallel', GetoptLong::NO_ARGUMENT],
                ['--output-dir', '-o', GetoptLong::REQUIRED_ARGUMENT]).each {
     | opt, arg |
     case opt
@@ -62,6 +71,10 @@
         $outputDir = Pathname.new(arg)
     when '--ftl-jit'
         $enableFTL = true
+    when '--parallel'
+        $parallel = true
+    when '--no-parallel'
+        $parallel = false
     end
 }
 
@@ -74,32 +87,35 @@
 
 EAGER_OPTIONS = ["--enableConcurrentJIT=false", "--thresholdForJITAfterWarmUp=10", "--thresholdForJITSoon=10", "--thresholdForOptimizeAfterWarmUp=20", "--thresholdForOptimizeAfterLongWarmUp=20", "--thresholdForOptimizeSoon=20", "--thresholdForFTLOptimizeAfterWarmUp=20", "--thresholdForFTLOptimizeSoon=20"]
 
-def run(kind, *options)
-    name = "#{$benchmark}.#{kind}"
-    print "#{$collectionName}/#{name}: "
-    arguments = [$jscPath.to_s] + options + [$benchmark]
-    if system(*arguments)
-        puts "OK."
-    else
-        puts "FAIL: #{$?.inspect}"
-        File.open($outputDir + "failed", "a") {
+$runlist = []
+
+class Plan
+    attr_reader :directory, :arguments, :name
+    attr_accessor :index
+    
+    def initialize(directory, arguments, name)
+        @directory = directory.realpath
+        @arguments = arguments
+        @name = name
+    end
+    
+    def writeTestScript(filename, failCommand)
+        File.open(filename, "w") {
             | outp |
-            outp.puts "#{$collectionName}/#{name}"
+            outp.puts "echo Running #{Shellwords.shellescape(@name)}"
+            outp.puts("(cd #{Shellwords.shellescape(@directory)} && " +
+                      @arguments.map{|v| Shellwords.shellescape(v)}.join(' ') +
+                      ") || #{failCommand}")
         }
-        $numFailures += 1
-        filename = $outputDir + $collectionName + name
-        begin
-            File.open(filename, "w") {
-                | outp |
-                outp.puts("cd #{Shellwords.shellescape(Dir.pwd)}")
-                outp.puts(arguments.map{|v| Shellwords.shellescape(v)}.join(' '))
-            }
-        rescue => e
-            $stderr.puts "Warning: failed to create repro file at #{filename}: #{e.inspect}"
-        end
     end
 end
 
+def run(kind, *options)
+    $runlist << Plan.new($collection,
+                         [$jscPath.to_s] + options + [$benchmark],
+                         "#{$collectionName}/#{$benchmark}.#{kind}")
+end
+
 def runDefault
     run("default")
 end
@@ -174,28 +190,157 @@
         dir = dir + filename
         Dir.mkdir(dir) unless dir.directory?
     }
-    Dir.chdir($collection) {
-        Dir.foreach('.') {
-            | benchmark |
-            next unless benchmark =~ /\.js$/
-            next unless FileTest.file? benchmark
-            
-            $benchmark = benchmark
-            
-            didRun = false
-            File.open(benchmark) {
-                | inp |
-                inp.each_line {
-                    | line |
-                    next unless line =~ /^\/\/@/
-                    eval $~.post_match
-                    didRun = true
-                }
+
+    Dir.foreach($collection) {
+        | benchmark |
+        next unless benchmark =~ /\.js$/
+        next unless ($collection + benchmark).file?
+        
+        $benchmark = benchmark
+        
+        didRun = false
+        File.open($collection + benchmark) {
+            | inp |
+            inp.each_line {
+                | line |
+                next unless line =~ /^\/\/@/
+                eval $~.post_match
+                didRun = true
             }
-            
-            defaultRun unless didRun
         }
+        
+        defaultRun unless didRun
     }
 }
 
+def appendFailure(plan)
+    File.open($outputDir + "failed", "a") {
+        | outp |
+        outp.puts plan.name
+    }
+    filename = $outputDir + plan.name
+    begin
+        plan.writeTestScript(filename, "exit 1")
+    rescue => e
+        $stderr.puts "Warning: failed to create repro file at #{filename}: #{e.inspect}"
+    end
+    $numFailures += 1
+end
+
+if $enableFTL and ENV["JSC_timeout"]
+    # Currently, using the FTL is a performance regression particularly in real
+    # (i.e. non-loopy) benchmarks. Account for this in the timeout.
+    ENV["JSC_timeout"] = (ENV["JSC_timeout"].to_i * 2).to_s
+end
+
+if $parallel
+    if ENV["JSC_timeout"]
+        # In the worst case, the processors just interfere with each other.
+        # Increase the timeout proportionally to the number of processors.
+        ENV["JSC_timeout"] = (ENV["JSC_timeout"].to_i.to_f * Math.sqrt(numProcessors)).to_i.to_s
+    end
+    
+    # The goals of our parallel test runner are scalability and simplicity. The
+    # simplicity part is particularly important. We don't want to have to have
+    # a full-time contributor just philosophising about parallel testing.
+    #
+    # As such, we just pass off all of the hard work to 'make'. This creates a
+    # dummy directory ("$outputDir/.parallel") in which we create a dummy
+    # Makefile. The Makefile has an 'all' rule that depends on all of the tests.
+    # That is, for each test we know we will run, there is a rule in the
+    # Makefile and 'all' depends on it. Running 'make -j <whatever>' on this
+    # Makefile results in 'make' doing all of the hard work:
+    #
+    # - Load balancing just works. Most systems have a great load balancer in
+    #   'make'. If your system doesn't then just install a real 'make'.
+    #
+    # - Interruptions just work. For example Ctrl-C handling in 'make' is
+    #   exactly right. You don't have to worry about zombie processes.
+    #
+    # We then do some tricks to make failure detection work and to make this
+    # totally sound. If a test fails, we don't want the whole 'make' job to
+    # stop. We also don't have any facility for makefile-escaping of path names.
+    # We do have such a thing for shell-escaping, though. We fix both problems
+    # by having the actual work for each of the test rules be done in a shell
+    # script on the side. There is one such script per test. The script responds
+    # to failure by printing something on the console and then touching a
+    # failure file for that test, but then still returns 0. This makes 'make'
+    # continue past that failure and complete all the tests anyway.
+    #
+    # In the end, this script collects all of the failures by searching for
+    # files in the .parallel directory whose name matches /^test_fail_/, where
+    # the thing after the 'fail_' is the test index. Those are the files that
+    # would be created by the test scripts if they detect failure. We're
+    # basically using the filesystem as a concurrent database of test failures.
+    # Even if two tests fail at the same time, since they're touching different
+    # files we won't miss any failures.
+    
+    runIndices = []
+    $runlist.each_with_index {
+        | plan, index |
+        runIndices << index
+        plan.index = index
+    }
+    
+    parallelDir = $outputDir + ".parallel"
+    Dir.mkdir(parallelDir) unless parallelDir.directory?
+    toDelete = []
+    Dir.foreach(parallelDir) {
+        | filename |
+        if filename =~ /^test_/
+            toDelete << filename
+        end
+    }
+    
+    toDelete.each {
+        | filename |
+        File.unlink(parallelDir + filename)
+    }
+    
+    $runlist.each {
+        | plan |
+        failCommand  = "{\n"
+        failCommand += "    echo FAIL: #{Shellwords.shellescape(plan.name)}\n"
+        failCommand += "    touch test_fail_#{plan.index}\n"
+        failCommand += "}"
+        plan.writeTestScript(parallelDir + "test_script_#{plan.index}", failCommand)
+    }
+    
+    File.open(parallelDir + "Makefile", "w") {
+        | outp |
+        outp.puts("all: " + runIndices.map{|v| "test_done_#{v}"}.join(' '))
+        runIndices.each {
+            | index |
+            plan = $runlist[index]
+            outp.puts "test_done_#{index}:"
+            outp.puts "\tsh test_script_#{plan.index}"
+            outp.puts "\ttouch test_done_#{index}"
+        }
+    }
+    
+    Dir.chdir(parallelDir) {
+        system("make", "-j", numProcessors.to_s, "-s", "-f", "Makefile")
+    }
+    
+    Dir.foreach(parallelDir) {
+        | filename |
+        next unless filename =~ /test_fail_/
+        appendFailure($runlist[$~.post_match.to_i])
+    }
+else
+    $runlist.each {
+        | plan |
+        print "#{plan.name}: "
+        
+        Dir.chdir(plan.directory) {
+            if system(*plan.arguments)
+                puts "OK."
+            else
+                puts "FAIL: #{$?.inspect}"
+                appendFailure(plan)
+            end
+        }
+    }
+end
+
 puts "Failed #{$numFailures} tests."
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to