#!/usr/bin/env ruby # Copyright (C) 2011-2015 Apple Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF # THE POSSIBILITY OF SUCH DAMAGE. require 'rubygems' require 'getoptlong' require 'pathname' require 'shellwords' require 'socket' begin require 'json' rescue LoadError => e $stderr.puts "It does not appear that you have the 'json' package installed. Try running 'sudo gem install json'." exit 1 end SCRIPT_PATH = Pathname.new(__FILE__).realpath raise unless SCRIPT_PATH.dirname.basename.to_s == "Scripts" raise unless SCRIPT_PATH.dirname.dirname.basename.to_s == "Tools" OPENSOURCE_PATH = SCRIPT_PATH.dirname.dirname.dirname PERFORMANCETESTS_PATH = OPENSOURCE_PATH + "PerformanceTests" SUNSPIDER_PATH = PERFORMANCETESTS_PATH + "SunSpider" + "tests" + "sunspider-1.0" LONGSPIDER_PATH = PERFORMANCETESTS_PATH + "LongSpider" V8_PATH = PERFORMANCETESTS_PATH + "SunSpider" + "tests" + "v8-v6" JSREGRESS_PATH = OPENSOURCE_PATH + "LayoutTests" + "js" + "regress" + "script-tests" OCTANE_WRAPPER_PATH = PERFORMANCETESTS_PATH + "Octane" + "wrappers" JSBENCH_PATH = PERFORMANCETESTS_PATH + "JSBench" TEMP_PATH = OPENSOURCE_PATH + "BenchmarkTemp" if TEMP_PATH.exist? raise unless TEMP_PATH.directory? else Dir.mkdir(TEMP_PATH) end BENCH_DATA_PATH = TEMP_PATH + "benchdata" IBR_LOOKUP=[0.00615583, 0.0975, 0.22852, 0.341628, 0.430741, 0.500526, 0.555933, 0.600706, 0.637513, 0.668244, 0.694254, 0.716537, 0.735827, 0.752684, 0.767535, 0.780716, 0.792492, 0.803074, 0.812634, 0.821313, 0.829227, 0.836472, 0.843129, 0.849267, 0.854943, 0.860209, 0.865107, 0.869674, 0.873942, 0.877941, 0.881693, 0.885223, 0.888548, 0.891686, 0.894652, 0.897461, 0.900124, 0.902652, 0.905056, 0.907343, 0.909524, 0.911604, 0.91359, 0.91549, 0.917308, 0.919049, 0.920718, 0.92232, 0.923859, 0.925338, 0.926761, 0.92813, 0.929449, 0.930721, 0.931948, 0.933132, 0.934275, 0.93538, 0.936449, 0.937483, 0.938483, 0.939452, 0.940392, 0.941302, 0.942185, 0.943042, 0.943874, 0.944682, 0.945467, 0.94623, 0.946972, 0.947694, 0.948396, 0.94908, 0.949746, 0.950395, 0.951027, 0.951643, 0.952244, 0.952831, 0.953403, 0.953961, 0.954506, 0.955039, 0.955559, 0.956067, 0.956563, 0.957049, 0.957524, 0.957988, 0.958443, 0.958887, 0.959323, 0.959749, 0.960166, 0.960575, 0.960975, 0.961368, 0.961752, 0.962129, 0.962499, 0.962861, 0.963217, 0.963566, 0.963908, 0.964244, 0.964574, 0.964897, 0.965215, 0.965527, 0.965834, 0.966135, 0.966431, 0.966722, 0.967007, 0.967288, 0.967564, 0.967836, 0.968103, 0.968366, 0.968624, 0.968878, 0.969128, 0.969374, 0.969617, 0.969855, 0.97009, 0.970321, 0.970548, 0.970772, 0.970993, 0.97121, 0.971425, 0.971636, 0.971843, 0.972048, 0.97225, 0.972449, 0.972645, 0.972839, 0.973029, 0.973217, 0.973403, 0.973586, 0.973766, 0.973944, 0.97412, 0.974293, 0.974464, 0.974632, 0.974799, 0.974963, 0.975125, 0.975285, 0.975443, 0.975599, 0.975753, 0.975905, 0.976055, 0.976204, 0.97635, 0.976495, 0.976638, 0.976779, 0.976918, 0.977056, 0.977193, 0.977327, 0.97746, 0.977592, 0.977722, 0.97785, 0.977977, 0.978103, 0.978227, 0.978349, 0.978471, 0.978591, 0.978709, 0.978827, 0.978943, 0.979058, 0.979171, 0.979283, 0.979395, 0.979504, 0.979613, 0.979721, 0.979827, 0.979933, 0.980037, 0.98014, 0.980242, 0.980343, 0.980443, 0.980543, 0.980641, 0.980738, 0.980834, 0.980929, 0.981023, 0.981116, 0.981209, 0.9813, 0.981391, 0.981481, 0.981569, 0.981657, 0.981745, 0.981831, 0.981916, 0.982001, 0.982085, 0.982168, 0.982251, 0.982332, 0.982413, 0.982493, 0.982573, 0.982651, 0.982729, 0.982807, 0.982883, 0.982959, 0.983034, 0.983109, 0.983183, 0.983256, 0.983329, 0.983401, 0.983472, 0.983543, 0.983613, 0.983683, 0.983752, 0.98382, 0.983888, 0.983956, 0.984022, 0.984089, 0.984154, 0.984219, 0.984284, 0.984348, 0.984411, 0.984474, 0.984537, 0.984599, 0.98466, 0.984721, 0.984782, 0.984842, 0.984902, 0.984961, 0.985019, 0.985077, 0.985135, 0.985193, 0.985249, 0.985306, 0.985362, 0.985417, 0.985472, 0.985527, 0.985582, 0.985635, 0.985689, 0.985742, 0.985795, 0.985847, 0.985899, 0.985951, 0.986002, 0.986053, 0.986103, 0.986153, 0.986203, 0.986252, 0.986301, 0.98635, 0.986398, 0.986446, 0.986494, 0.986541, 0.986588, 0.986635, 0.986681, 0.986727, 0.986773, 0.986818, 0.986863, 0.986908, 0.986953, 0.986997, 0.987041, 0.987084, 0.987128, 0.987171, 0.987213, 0.987256, 0.987298, 0.98734, 0.987381, 0.987423, 0.987464, 0.987504, 0.987545, 0.987585, 0.987625, 0.987665, 0.987704, 0.987744, 0.987783, 0.987821, 0.98786, 0.987898, 0.987936, 0.987974, 0.988011, 0.988049, 0.988086, 0.988123, 0.988159, 0.988196, 0.988232, 0.988268, 0.988303, 0.988339, 0.988374, 0.988409, 0.988444, 0.988479, 0.988513, 0.988547, 0.988582, 0.988615, 0.988649, 0.988682, 0.988716, 0.988749, 0.988782, 0.988814, 0.988847, 0.988879, 0.988911, 0.988943, 0.988975, 0.989006, 0.989038, 0.989069, 0.9891, 0.989131, 0.989161, 0.989192, 0.989222, 0.989252, 0.989282, 0.989312, 0.989342, 0.989371, 0.989401, 0.98943, 0.989459, 0.989488, 0.989516, 0.989545, 0.989573, 0.989602, 0.98963, 0.989658, 0.989685, 0.989713, 0.98974, 0.989768, 0.989795, 0.989822, 0.989849, 0.989876, 0.989902, 0.989929, 0.989955, 0.989981, 0.990007, 0.990033, 0.990059, 0.990085, 0.99011, 0.990136, 0.990161, 0.990186, 0.990211, 0.990236, 0.990261, 0.990285, 0.99031, 0.990334, 0.990358, 0.990383, 0.990407, 0.99043, 0.990454, 0.990478, 0.990501, 0.990525, 0.990548, 0.990571, 0.990594, 0.990617, 0.99064, 0.990663, 0.990686, 0.990708, 0.990731, 0.990753, 0.990775, 0.990797, 0.990819, 0.990841, 0.990863, 0.990885, 0.990906, 0.990928, 0.990949, 0.99097, 0.990991, 0.991013, 0.991034, 0.991054, 0.991075, 0.991096, 0.991116, 0.991137, 0.991157, 0.991178, 0.991198, 0.991218, 0.991238, 0.991258, 0.991278, 0.991298, 0.991317, 0.991337, 0.991356, 0.991376, 0.991395, 0.991414, 0.991433, 0.991452, 0.991471, 0.99149, 0.991509, 0.991528, 0.991547, 0.991565, 0.991584, 0.991602, 0.99162, 0.991639, 0.991657, 0.991675, 0.991693, 0.991711, 0.991729, 0.991746, 0.991764, 0.991782, 0.991799, 0.991817, 0.991834, 0.991851, 0.991869, 0.991886, 0.991903, 0.99192, 0.991937, 0.991954, 0.991971, 0.991987, 0.992004, 0.992021, 0.992037, 0.992054, 0.99207, 0.992086, 0.992103, 0.992119, 0.992135, 0.992151, 0.992167, 0.992183, 0.992199, 0.992215, 0.99223, 0.992246, 0.992262, 0.992277, 0.992293, 0.992308, 0.992324, 0.992339, 0.992354, 0.992369, 0.992384, 0.9924, 0.992415, 0.992429, 0.992444, 0.992459, 0.992474, 0.992489, 0.992503, 0.992518, 0.992533, 0.992547, 0.992561, 0.992576, 0.99259, 0.992604, 0.992619, 0.992633, 0.992647, 0.992661, 0.992675, 0.992689, 0.992703, 0.992717, 0.99273, 0.992744, 0.992758, 0.992771, 0.992785, 0.992798, 0.992812, 0.992825, 0.992839, 0.992852, 0.992865, 0.992879, 0.992892, 0.992905, 0.992918, 0.992931, 0.992944, 0.992957, 0.99297, 0.992983, 0.992995, 0.993008, 0.993021, 0.993034, 0.993046, 0.993059, 0.993071, 0.993084, 0.993096, 0.993109, 0.993121, 0.993133, 0.993145, 0.993158, 0.99317, 0.993182, 0.993194, 0.993206, 0.993218, 0.99323, 0.993242, 0.993254, 0.993266, 0.993277, 0.993289, 0.993301, 0.993312, 0.993324, 0.993336, 0.993347, 0.993359, 0.99337, 0.993382, 0.993393, 0.993404, 0.993416, 0.993427, 0.993438, 0.993449, 0.99346, 0.993472, 0.993483, 0.993494, 0.993505, 0.993516, 0.993527, 0.993538, 0.993548, 0.993559, 0.99357, 0.993581, 0.993591, 0.993602, 0.993613, 0.993623, 0.993634, 0.993644, 0.993655, 0.993665, 0.993676, 0.993686, 0.993697, 0.993707, 0.993717, 0.993727, 0.993738, 0.993748, 0.993758, 0.993768, 0.993778, 0.993788, 0.993798, 0.993808, 0.993818, 0.993828, 0.993838, 0.993848, 0.993858, 0.993868, 0.993877, 0.993887, 0.993897, 0.993907, 0.993916, 0.993926, 0.993935, 0.993945, 0.993954, 0.993964, 0.993973, 0.993983, 0.993992, 0.994002, 0.994011, 0.99402, 0.99403, 0.994039, 0.994048, 0.994057, 0.994067, 0.994076, 0.994085, 0.994094, 0.994103, 0.994112, 0.994121, 0.99413, 0.994139, 0.994148, 0.994157, 0.994166, 0.994175, 0.994183, 0.994192, 0.994201, 0.99421, 0.994218, 0.994227, 0.994236, 0.994244, 0.994253, 0.994262, 0.99427, 0.994279, 0.994287, 0.994296, 0.994304, 0.994313, 0.994321, 0.994329, 0.994338, 0.994346, 0.994354, 0.994363, 0.994371, 0.994379, 0.994387, 0.994395, 0.994404, 0.994412, 0.99442, 0.994428, 0.994436, 0.994444, 0.994452, 0.99446, 0.994468, 0.994476, 0.994484, 0.994492, 0.9945, 0.994508, 0.994516, 0.994523, 0.994531, 0.994539, 0.994547, 0.994554, 0.994562, 0.99457, 0.994577, 0.994585, 0.994593, 0.9946, 0.994608, 0.994615, 0.994623, 0.994631, 0.994638, 0.994645, 0.994653, 0.99466, 0.994668, 0.994675, 0.994683, 0.99469, 0.994697, 0.994705, 0.994712, 0.994719, 0.994726, 0.994734, 0.994741, 0.994748, 0.994755, 0.994762, 0.994769, 0.994777, 0.994784, 0.994791, 0.994798, 0.994805, 0.994812, 0.994819, 0.994826, 0.994833, 0.99484, 0.994847, 0.994854, 0.99486, 0.994867, 0.994874, 0.994881, 0.994888, 0.994895, 0.994901, 0.994908, 0.994915, 0.994922, 0.994928, 0.994935, 0.994942, 0.994948, 0.994955, 0.994962, 0.994968, 0.994975, 0.994981, 0.994988, 0.994994, 0.995001, 0.995007, 0.995014, 0.99502, 0.995027, 0.995033, 0.99504, 0.995046, 0.995052, 0.995059, 0.995065, 0.995071, 0.995078, 0.995084, 0.99509, 0.995097, 0.995103, 0.995109, 0.995115, 0.995121, 0.995128, 0.995134, 0.99514, 0.995146, 0.995152, 0.995158, 0.995164, 0.995171, 0.995177, 0.995183, 0.995189, 0.995195, 0.995201, 0.995207, 0.995213, 0.995219, 0.995225, 0.995231, 0.995236, 0.995242, 0.995248, 0.995254, 0.99526, 0.995266, 0.995272, 0.995277, 0.995283, 0.995289, 0.995295, 0.995301, 0.995306, 0.995312, 0.995318, 0.995323, 0.995329, 0.995335, 0.99534, 0.995346, 0.995352, 0.995357, 0.995363, 0.995369, 0.995374, 0.99538, 0.995385, 0.995391, 0.995396, 0.995402, 0.995407, 0.995413, 0.995418, 0.995424, 0.995429, 0.995435, 0.99544, 0.995445, 0.995451, 0.995456, 0.995462, 0.995467, 0.995472, 0.995478, 0.995483, 0.995488, 0.995493, 0.995499, 0.995504, 0.995509, 0.995515, 0.99552, 0.995525, 0.99553, 0.995535, 0.995541, 0.995546, 0.995551, 0.995556, 0.995561, 0.995566, 0.995571, 0.995577, 0.995582, 0.995587, 0.995592, 0.995597, 0.995602, 0.995607, 0.995612, 0.995617, 0.995622, 0.995627, 0.995632, 0.995637, 0.995642, 0.995647, 0.995652, 0.995657, 0.995661, 0.995666, 0.995671, 0.995676, 0.995681, 0.995686, 0.995691, 0.995695, 0.9957, 0.995705, 0.99571, 0.995715, 0.995719, 0.995724, 0.995729, 0.995734, 0.995738, 0.995743, 0.995748, 0.995753, 0.995757, 0.995762, 0.995767, 0.995771, 0.995776, 0.995781, 0.995785, 0.99579, 0.995794, 0.995799, 0.995804, 0.995808, 0.995813, 0.995817, 0.995822, 0.995826, 0.995831, 0.995835, 0.99584, 0.995844, 0.995849, 0.995853, 0.995858, 0.995862, 0.995867, 0.995871, 0.995876, 0.99588, 0.995885, 0.995889, 0.995893, 0.995898, 0.995902, 0.995906, 0.995911, 0.995915, 0.99592, 0.995924, 0.995928, 0.995932, 0.995937, 0.995941, 0.995945, 0.99595, 0.995954, 0.995958, 0.995962, 0.995967, 0.995971, 0.995975, 0.995979, 0.995984, 0.995988, 0.995992, 0.995996, 0.996, 0.996004, 0.996009, 0.996013, 0.996017, 0.996021, 0.996025, 0.996029, 0.996033, 0.996037, 0.996041, 0.996046, 0.99605, 0.996054, 0.996058, 0.996062, 0.996066, 0.99607, 0.996074, 0.996078, 0.996082, 0.996086, 0.99609, 0.996094, 0.996098, 0.996102, 0.996106, 0.99611, 0.996114, 0.996117, 0.996121, 0.996125, 0.996129, 0.996133, 0.996137, 0.996141, 0.996145, 0.996149, 0.996152, 0.996156, 0.99616, 0.996164] # Run-time configuration parameters (can be set with command-line options) $rerun=1 $inner=1 $warmup=1 $outer=4 $quantum=1000 $includeSunSpider=true $includeLongSpider=true $includeV8=true $includeKraken=true $includeJSBench=true $includeJSRegress=true $includeAsmBench=true $includeDSPJS=true $includeBrowsermarkJS=false $includeBrowsermarkDOM=false $includeOctane=true $includeCompressionBench = true $measureGC=false $benchmarkPattern=nil $verbosity=0 $timeMode=:preciseTime $forceVMKind=nil $brief=false $silent=false $remoteHosts=[] $alsoLocal=false $sshOptions=[] $vms = [] $environment = {} $dependencies = [] $needToCopyVMs = false $dontCopyVMs = false $allDRT = true $outputName = nil $sunSpiderWarmup = true $configPath = Pathname.new(ENV["HOME"]) + ".run-jsc-benchmarks" $prepare = true $run = true $analyze = [] # Helpful functions and classes def smallUsage puts "Use the --help option to get basic usage information." exit 1 end def usage puts "run-jsc-benchmarks [options] [ ...]" puts puts "Runs one or more JavaScript runtimes against SunSpider, V8, and/or Kraken" puts "benchmarks, and reports detailed statistics. What makes run-jsc-benchmarks" puts "special is that each benchmark/VM configuration is run in a single VM invocation," puts "and the invocations are run in random order. This minimizes systematics due to" puts "one benchmark polluting the running time of another. The fine-grained" puts "interleaving of VM invocations further minimizes systematics due to changes in" puts "the performance or behavior of your machine." puts puts "Run-jsc-benchmarks is highly configurable. You can compare as many VMs as you" puts "like. You can change the amount of warm-up iterations, number of iterations" puts "executed per VM invocation, and the number of VM invocations per benchmark." puts puts "The should be either a path to a JavaScript runtime executable (such as" puts "jsc), or a string of the form :, where the is the path to" puts "the executable and is the name that you would like to give the" puts "configuration for the purposeof reporting. If no name is given, a generic name" puts "of the form Conf# will be ascribed to the configuration automatically." puts puts "It's also possible to specify per-VM environment variables. For example, you" puts "might specify a VM like Foo:JSC_useJIT=false:/path/to/jsc, in which case the" puts "harness will set the JSC_useJIT environment variable to false just before running" puts "the given VM. Note that the harness will not unset the environment variable, so" puts "you must ensure that your other VMs will use the opposite setting" puts "(JSC_useJIT=true in this case)." puts puts "Options:" puts "--rerun Set the number of iterations of the benchmark that" puts " contribute to the measured run time. Default is #{$rerun}." puts "--inner Set the number of inner (per-runtime-invocation)" puts " iterations. Default is #{$inner}." puts "--outer Set the number of runtime invocations for each benchmark." puts " Default is #{$outer}." puts "--warmup Set the number of warm-up runs per invocation. Default" puts " is #{$warmup}. This has a different effect on different kinds" puts " benchmarks. Some benchmarks have no notion of warm-up." puts "--no-ss-warmup Disable SunSpider-based warm-up runs." puts "--quantum Set the duration in milliseconds for which an iteration of" puts " a throughput benchmark should be run. Default is #{$quantum}." puts "--timing-mode Set the way that time is measured. Possible values" puts " are 'preciseTime' and 'date'. Default is 'preciseTime'." puts "--force-vm-kind Turn off auto-detection of VM kind, and assume that it is" puts " the one specified. Valid arguments are 'jsc', " puts " 'DumpRenderTree', or 'WebKitTestRunner'." puts "--force-vm-copy Force VM builds to be copied to the working directory." puts " This may reduce pathologies resulting from path names." puts "--dont-copy-vms Don't copy VMs even when doing a remote benchmarking run;" puts " instead assume that they are already there." puts "--sunspider Only run SunSpider." puts "--v8-spider Only run V8." puts "--kraken Only run Kraken." puts "--js-bench Only run JSBench." puts "--js-regress Only run JSRegress." puts "--dsp Only run DSP." puts "--asm-bench Only run AsmBench." puts "--browsermark-js Only run browsermark-js." puts "--browsermark-dom Only run browsermark-dom." puts "--octane Only run Octane." puts "--compression-bench Only run compression bench" puts " The default is to run all benchmarks. The above options can" puts " be combined to run any subset (so --sunspider --dsp will run" puts " both SunSpider and DSP)." puts "--benchmarks Only run benchmarks matching the given regular expression." puts "--measure-gc Turn off manual calls to gc(), so that GC time is measured." puts " Works best with large values of --inner. You can also say" puts " --measure-gc , which turns this on for one" puts " configuration only." puts "--verbose or -v Print more stuff." puts "--brief Print only the final result for each VM." puts "--silent Don't print progress. This might slightly reduce some" puts " performance perturbation." puts "--remote Perform performance measurements remotely, on the given" puts " SSH host(s). Easiest way to use this is to specify the SSH" puts " user@host string. However, you can also supply a comma-" puts " separated list of SSH hosts. Alternatively, you can use this" puts " option multiple times to specify multiple hosts. This" puts " automatically copies the WebKit release builds of the VMs" puts " you specified to all of the hosts." puts "--ssh-options Pass additional options to SSH." puts "--local Also do a local benchmark run even when doing --remote." puts "--vms Use a JSON file to specify which VMs to run, as opposed to" puts " specifying them on the command line." puts "--prepare-only Only prepare the runscript (a shell script that" puts " invokes the VMs to run benchmarks) but don't run it." puts "--analyze Only read the output of the runscript but don't do anything" puts " else. This requires passing the same arguments that you" puts " passed when running --prepare-only." puts "--output-name Base of the filenames to put results into. Will write a file" puts " called _report.txt and .json. By default this" puts " name is automatically synthesized from the machine name," puts " date, set of benchmarks run, and set of configurations." puts "--environment JSON file that specifies the environment variables that should" puts " be used for particular VMs and benchmarks." puts "--config Specify the path of the configuration file. Defaults to" puts " ~/.run-jsc-benchmarks" puts "--dependencies Additional dependent library paths." puts "--help or -h Display this message." puts puts "Example:" puts "run-jsc-benchmarks TipOfTree:/Volumes/Data/pizlo/OpenSource/WebKitBuild/Release/jsc MyChanges:/Volumes/Data/pizlo/secondary/OpenSource/WebKitBuild/Release/jsc" exit 1 end def fail(reason) if reason.respond_to? :backtrace puts "FAILED: #{reason.inspect}" puts "Stack trace:" puts reason.backtrace.join("\n") else puts "FAILED: #{reason.inspect}" end smallUsage end def quickFail(r1,r2) $stderr.puts "#{$0}: #{r1}" puts fail(r2) end def intArg(argName,arg,min,max) result=arg.to_i unless result.to_s == arg quickFail("Expected an integer value for #{argName}, but got #{arg}.", "Invalid argument for command-line option") end if min and resultmax quickFail("Argument for #{argName} cannot be greater than #{max}.", "Invalid argument for command-line option") end result end def computeMean(array) sum=0.0 array.each { | value | sum += value } sum/array.length end def computeGeometricMean(array) sum = 0.0 array.each { | value | sum += Math.log(value) } Math.exp(sum * (1.0/array.length)) end def computeHarmonicMean(array) 1.0 / computeMean(array.collect{ | value | 1.0 / value }) end def computeStdDev(array) case array.length when 0 0.0/0.0 when 1 0.0 else begin mean=computeMean(array) sum=0.0 array.each { | value | sum += (value-mean)**2 } Math.sqrt(sum/(array.length-1)) rescue 0.0/0.0 end end end class Array def shuffle! size.downto(1) { |n| push delete_at(rand(n)) } self end end def inverseBetaRegularized(n) IBR_LOOKUP[n-1] end def numToStr(num, decimalShift) ("%." + (4 + decimalShift).to_s + "f") % (num.to_f) end class CantSay def initialize end def shortForm " " end def longForm "" end def to_s "" end end class NoChange attr_reader :amountFaster def initialize(amountFaster) @amountFaster = amountFaster end def shortForm " " end def longForm " might be #{numToStr(@amountFaster, 0)}x faster" end def to_s if @amountFaster < 1.01 "" else longForm end end end class Faster attr_reader :amountFaster def initialize(amountFaster) @amountFaster = amountFaster end def shortForm "^" end def longForm "^ definitely #{numToStr(@amountFaster, 0)}x faster" end def to_s longForm end end class Slower attr_reader :amountSlower def initialize(amountSlower) @amountSlower = amountSlower end def shortForm "!" end def longForm "! definitely #{numToStr(@amountSlower, 0)}x slower" end def to_s longForm end end class MayBeSlower attr_reader :amountSlower def initialize(amountSlower) @amountSlower = amountSlower end def shortForm "?" end def longForm "? might be #{numToStr(@amountSlower, 0)}x slower" end def to_s if @amountSlower < 1.01 "?" else longForm end end end def jsonSanitize(value) if value.is_a? Fixnum value elsif value.is_a? Float if value.nan? or value.infinite? value.to_s else value end elsif value.is_a? Array value.map{|v| jsonSanitize(v)} elsif value.nil? value else raise "Unrecognized value #{value.inspect}" end end class Stats def initialize @array = [] end def add(value) if not value or not @array @array = nil elsif value.is_a? Float if value.nan? or value.infinite? @array = nil else @array << value end elsif value.is_a? Stats add(value.array) elsif value.respond_to? :each value.each { | v | add(v) } else @array << value.to_f end end def status if @array :ok else :error end end def error? # TODO: We're probably still not handling this case correctly. not @array or @array.empty? end def ok? not not @array end def array @array end def sum result=0 @array.each { | value | result += value } result end def min @array.min end def max @array.max end def size @array.length end def mean computeMean(array) end def arithmeticMean mean end def stdDev computeStdDev(array) end def stdErr stdDev/Math.sqrt(size) end # Computes a 95% Student's t distribution confidence interval def confInt if size < 2 0.0/0.0 else raise if size > 1000 Math.sqrt(size-1.0)*stdErr*Math.sqrt(-1.0+1.0/inverseBetaRegularized(size-1)) end end def lower mean-confInt end def upper mean+confInt end def geometricMean computeGeometricMean(array) end def harmonicMean computeHarmonicMean(array) end def compareTo(other) return CantSay.new unless ok? and other.ok? if upper < other.lower Faster.new(other.mean/mean) elsif lower > other.upper Slower.new(mean/other.mean) elsif mean > other.mean MayBeSlower.new(mean/other.mean) else NoChange.new(other.mean/mean) end end def to_s "size = #{size}, mean = #{mean}, stdDev = #{stdDev}, stdErr = #{stdErr}, confInt = #{confInt}" end def jsonMap if ok? {"data"=>jsonSanitize(@array), "mean"=>jsonSanitize(mean), "confInt"=>jsonSanitize(confInt)} else "ERROR" end end end def doublePuts(out1,out2,msg) out1.puts "#{out2.path}: #{msg}" if $verbosity>=3 out2.puts msg end class Benchfile < File @@counter = 0 attr_reader :filename, :basename def initialize(name) @basename, @filename = Benchfile.uniqueFilename(name) super(@filename, "w") end def self.uniqueFilename(name) if name.is_a? Array basename = name[0] + @@counter.to_s + name[1] else basename = name + @@counter.to_s end filename = BENCH_DATA_PATH + basename @@counter += 1 raise "Benchfile #{filename} already exists" if FileTest.exist?(filename) [basename, filename] end def self.create(name) file = Benchfile.new(name) yield file file.close file.basename end end $dataFiles={} def ensureFile(key, filename) unless $dataFiles[key] $dataFiles[key] = Benchfile.create(key) { | outp | doublePuts($stderr,outp,IO::read(filename)) } end $dataFiles[key] end # Helper for files that cannot be renamed. $absoluteFiles={} def ensureAbsoluteFile(filename, basedir=nil) return if $absoluteFiles[filename] filename = Pathname.new(filename) directory = Pathname.new('') if basedir and filename.dirname != basedir remainingPath = filename.dirname while remainingPath != basedir directory = remainingPath.basename + directory remainingPath = remainingPath.dirname end if not $absoluteFiles[directory] cmd = "mkdir -p #{Shellwords.shellescape((BENCH_DATA_PATH + directory).to_s)}" $stderr.puts ">> #{cmd}" if $verbosity >= 2 raise unless system(cmd) intermediateDirectory = Pathname.new(directory) while intermediateDirectory.basename.to_s != "." $absoluteFiles[intermediateDirectory] = true intermediateDirectory = intermediateDirectory.dirname end end end cmd = "cp #{Shellwords.shellescape(filename.to_s)} #{Shellwords.shellescape((BENCH_DATA_PATH + directory + filename.basename).to_s)}" $stderr.puts ">> #{cmd}" if $verbosity >= 2 raise unless system(cmd) $absoluteFiles[filename] = true end # Helper for large benchmarks with lots of files and directories. def ensureBenchmarkFiles(rootdir) toProcess = [rootdir] while not toProcess.empty? currdir = toProcess.pop Dir.foreach(currdir.to_s) { | filename | path = currdir + filename next if filename.match(/^\./) toProcess.push(path) if File.directory?(path.to_s) ensureAbsoluteFile(path, rootdir) if File.file?(path.to_s) } end end class JSCommand attr_reader :js, :html def initialize(js, html) @js = js @html = html end end def loadCommandForFile(key, filename) file = ensureFile(key, filename) JSCommand.new("load(#{file.inspect});", "") end def simpleCommand(command) JSCommand.new(command, "") end # Benchmark that consists of a single file and must be loaded in its own global object each # time (i.e. run()). class SingleFileTimedBenchmarkParameters attr_reader :benchPath def initialize(benchPath) @benchPath = benchPath end def kind :singleFileTimedBenchmark end end # Benchmark that consists of one or more data files that should be loaded globally, followed # by a command to run the benchmark. class MultiFileTimedBenchmarkParameters attr_reader :dataPaths, :command def initialize(dataPaths, command) @dataPaths = dataPaths @command = command end def kind :multiFileTimedBenchmark end end # Benchmark that consists of one or more data files that should be loaded globally, followed # by a command to run a short tick of the benchmark. The benchmark should be run for as many # ticks as possible, for one quantum (quantum is 1000ms by default). class ThroughputBenchmarkParameters attr_reader :dataPaths, :setUpCommand, :command, :tearDownCommand, :doWarmup, :deterministic, :minimumIterations def initialize(dataPaths, setUpCommand, command, tearDownCommand, doWarmup, deterministic, minimumIterations) @dataPaths = dataPaths @setUpCommand = setUpCommand @command = command @tearDownCommand = tearDownCommand @doWarmup = doWarmup @deterministic = deterministic @minimumIterations = minimumIterations end def kind :throughputBenchmark end end # Benchmark that can only run in DumpRenderTree or WebKitTestRunner, that has its own callback for reporting # results. Other than that it's just like SingleFileTimedBenchmark. class SingleFileTimedCallbackBenchmarkParameters attr_reader :callbackDecl, :benchPath def initialize(callbackDecl, benchPath) @callbackDecl = callbackDecl @benchPath = benchPath end def kind :singleFileTimedCallbackBenchmark end end def emitTimerFunctionCode(file) case $timeMode when :preciseTime doublePuts($stderr,file,"function __bencher_curTimeMS() {") doublePuts($stderr,file," return preciseTime()*1000") doublePuts($stderr,file,"}") when :date doublePuts($stderr,file,"function __bencher_curTimeMS() {") doublePuts($stderr,file," return Date.now()") doublePuts($stderr,file,"}") else raise end end def emitBenchRunCodeFile(name, plan, benchParams) case plan.vm.vmType when :jsc Benchfile.create("bencher") { | file | emitTimerFunctionCode(file) if benchParams.kind == :multiFileTimedBenchmark benchParams.dataPaths.each { | path | doublePuts($stderr,file,"load(#{path.inspect});") } doublePuts($stderr,file,"gc();") doublePuts($stderr,file,"for (var __bencher_index = 0; __bencher_index < #{$warmup+$inner}; ++__bencher_index) {") doublePuts($stderr,file," var __before = __bencher_curTimeMS();") $rerun.times { doublePuts($stderr,file," #{benchParams.command.js}") } doublePuts($stderr,file," var __after = __bencher_curTimeMS();") doublePuts($stderr,file," if (__bencher_index >= #{$warmup}) print(\"#{name}: #{plan.vm}: #{plan.iteration}: \" + (__bencher_index - #{$warmup}) + \": Time: \"+(__after-__before));"); doublePuts($stderr,file," gc();") unless plan.vm.shouldMeasureGC doublePuts($stderr,file,"}") elsif benchParams.kind == :throughputBenchmark emitTimerFunctionCode(file) benchParams.dataPaths.each { | path | doublePuts($stderr,file,"load(#{path.inspect});") } doublePuts($stderr,file,"#{benchParams.setUpCommand.js}") if benchParams.doWarmup warmup = $warmup else warmup = 0 end doublePuts($stderr,file,"for (var __bencher_index = 0; __bencher_index < #{warmup + $inner}; __bencher_index++) {") doublePuts($stderr,file," var __before = __bencher_curTimeMS();") doublePuts($stderr,file," var __after = __before;") doublePuts($stderr,file," var __runs = 0;") doublePuts($stderr,file," var __expected = #{$quantum};") doublePuts($stderr,file," while (true) {") $rerun.times { doublePuts($stderr,file," #{benchParams.command.js}") } doublePuts($stderr,file," __runs++;") doublePuts($stderr,file," __after = __bencher_curTimeMS();") if benchParams.deterministic doublePuts($stderr,file," if (true) {") else doublePuts($stderr,file," if (__after - __before >= __expected) {") end doublePuts($stderr,file," if (__runs >= #{benchParams.minimumIterations} || __bencher_index < #{warmup})") doublePuts($stderr,file," break;") doublePuts($stderr,file," __expected += #{$quantum}") doublePuts($stderr,file," }") doublePuts($stderr,file," }") doublePuts($stderr,file," if (__bencher_index >= #{warmup}) print(\"#{name}: #{plan.vm}: #{plan.iteration}: \" + (__bencher_index - #{warmup}) + \": Time: \"+((__after-__before)/__runs));") doublePuts($stderr,file,"}") doublePuts($stderr,file,"#{benchParams.tearDownCommand.js}") else raise unless benchParams.kind == :singleFileTimedBenchmark doublePuts($stderr,file,"function __bencher_run(__bencher_what) {") doublePuts($stderr,file," var __bencher_before = __bencher_curTimeMS();") $rerun.times { doublePuts($stderr,file," run(__bencher_what);") } doublePuts($stderr,file," var __bencher_after = __bencher_curTimeMS();") doublePuts($stderr,file," return __bencher_after - __bencher_before;") doublePuts($stderr,file,"}") $warmup.times { doublePuts($stderr,file,"__bencher_run(#{benchParams.benchPath.inspect})") doublePuts($stderr,file,"gc();") unless plan.vm.shouldMeasureGC } $inner.times { | innerIndex | doublePuts($stderr,file,"print(\"#{name}: #{plan.vm}: #{plan.iteration}: #{innerIndex}: Time: \"+__bencher_run(#{benchParams.benchPath.inspect}));") doublePuts($stderr,file,"gc();") unless plan.vm.shouldMeasureGC } end } when :dumpRenderTree, :webkitTestRunner case $timeMode when :preciseTime curTime = "(testRunner.preciseTime()*1000)" when :date curTime = "(Date.now())" else raise end mainCode = Benchfile.create("bencher") { | file | doublePuts($stderr,file,"__bencher_count = 0;") doublePuts($stderr,file,"function __bencher_doNext(result) {") doublePuts($stderr,file," if (__bencher_count >= #{$warmup})") doublePuts($stderr,file," debug(\"#{name}: #{plan.vm}: #{plan.iteration}: \" + (__bencher_count - #{$warmup}) + \": Time: \" + result);") doublePuts($stderr,file," __bencher_count++;") doublePuts($stderr,file," if (__bencher_count < #{$inner+$warmup})") doublePuts($stderr,file," __bencher_runImpl(__bencher_doNext);") doublePuts($stderr,file," else") doublePuts($stderr,file," quit();") doublePuts($stderr,file,"}") doublePuts($stderr,file,"__bencher_runImpl(__bencher_doNext);") } cssCode = Benchfile.create("bencher-css") { | file | doublePuts($stderr,file,".pass {\n font-weight: bold;\n color: green;\n}\n.fail {\n font-weight: bold;\n color: red;\n}\n\#console {\n white-space: pre-wrap;\n font-family: monospace;\n}") } preCode = Benchfile.create("bencher-pre") { | file | doublePuts($stderr,file,"if (window.testRunner) {") doublePuts($stderr,file," testRunner.dumpAsText(window.enablePixelTesting);") doublePuts($stderr,file," testRunner.waitUntilDone();") doublePuts($stderr,file,"}") doublePuts($stderr,file,"") doublePuts($stderr,file,"function debug(msg)") doublePuts($stderr,file,"{") doublePuts($stderr,file," var span = document.createElement(\"span\");") doublePuts($stderr,file," document.getElementById(\"console\").appendChild(span); // insert it first so XHTML knows the namespace") doublePuts($stderr,file," span.innerHTML = msg + '
';") doublePuts($stderr,file,"}") doublePuts($stderr,file,"") doublePuts($stderr,file,"function quit() {") doublePuts($stderr,file," testRunner.notifyDone();") doublePuts($stderr,file,"}") doublePuts($stderr,file,"") doublePuts($stderr,file,"__bencher_continuation=null;") doublePuts($stderr,file,"") doublePuts($stderr,file,"function reportResult(result) {") doublePuts($stderr,file," __bencher_continuation(result);") doublePuts($stderr,file,"}") doublePuts($stderr,file,"") doublePuts($stderr,file,"function currentTimeInMS(msg)") doublePuts($stderr,file,"{") doublePuts($stderr,file," return #{curTime};") doublePuts($stderr,file,"}") if benchParams.kind == :singleFileTimedCallbackBenchmark doublePuts($stderr,file,"") doublePuts($stderr,file,benchParams.callbackDecl) end doublePuts($stderr,file,"") doublePuts($stderr,file,"function __bencher_runImpl(continuation) {") doublePuts($stderr,file," function doit() {") doublePuts($stderr,file," document.getElementById(\"frameparent\").innerHTML = \"\";") doublePuts($stderr,file," document.getElementById(\"frameparent\").innerHTML = \"