initial import
[vuplus_webkit] / Tools / Scripts / bencher
1 #!/usr/bin/env ruby
2
3 # Copyright (C) 2011 Apple Inc. All rights reserved.
4 #
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions
7 # are met:
8 # 1. Redistributions of source code must retain the above copyright
9 #    notice, this list of conditions and the following disclaimer.
10 # 2. Redistributions in binary form must reproduce the above copyright
11 #    notice, this list of conditions and the following disclaimer in the
12 #    documentation and/or other materials provided with the distribution.
13 #
14 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16 # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24 # THE POSSIBILITY OF SUCH DAMAGE.
25
26 require 'rubygems'
27
28 require 'getoptlong'
29 require 'pathname'
30 require 'tempfile'
31
32 begin
33   require 'json'
34 rescue LoadError => e
35   $stderr.puts "It does not appear that you have the 'json' package installed.  Try running 'sudo gem install json'."
36   exit 1
37 end
38
39 # Configuration
40
41 CONFIGURATION_FLNM = ENV["HOME"]+"/.bencher"
42
43 unless FileTest.exist? CONFIGURATION_FLNM
44   $stderr.puts "Error: no configuration file at ~/.bencher."
45   $stderr.puts "This file should contain paths to SunSpider, V8, and Kraken, in JSON"
46   $stderr.puts "format.  For example:"
47   $stderr.puts "{ \"sunSpiderPath\": \"/Volumes/Data/pizlo/OpenSource/PerformanceTests/SunSpider/tests/sunspider-1.0\", \"v8Path\": \"/Volumes/Data/pizlo/OpenSource/PerformanceTests/SunSpider/tests/v8-v6\", \"krakenPath\": \"/Volumes/Data/pizlo/kraken/kraken-e119421cb325/tests/kraken-1.1\" }"
48   exit 1
49 end
50
51 CONFIGURATION = JSON.parse(File::read(CONFIGURATION_FLNM))
52
53 SUNSPIDER_PATH = CONFIGURATION["sunSpiderPath"]
54 V8_PATH = CONFIGURATION["v8Path"]
55 KRAKEN_PATH = CONFIGURATION["krakenPath"]
56
57 IBR_LOOKUP=[0.00615583, 0.0975, 0.22852, 0.341628, 0.430741, 0.500526, 0.555933, 
58             0.600706, 0.637513, 0.668244, 0.694254, 0.716537, 0.735827, 0.752684, 
59             0.767535, 0.780716, 0.792492, 0.803074, 0.812634, 0.821313, 0.829227, 
60             0.836472, 0.843129, 0.849267, 0.854943, 0.860209, 0.865107, 0.869674, 
61             0.873942, 0.877941, 0.881693, 0.885223, 0.888548, 0.891686, 0.894652, 
62             0.897461, 0.900124, 0.902652, 0.905056, 0.907343, 0.909524, 0.911604, 
63             0.91359, 0.91549, 0.917308, 0.919049, 0.920718, 0.92232, 0.923859, 0.925338, 
64             0.926761, 0.92813, 0.929449, 0.930721, 0.931948, 0.933132, 0.934275, 0.93538, 
65             0.936449, 0.937483, 0.938483, 0.939452, 0.940392, 0.941302, 0.942185, 
66             0.943042, 0.943874, 0.944682, 0.945467, 0.94623, 0.946972, 0.947694, 
67             0.948396, 0.94908, 0.949746, 0.950395, 0.951027, 0.951643, 0.952244, 
68             0.952831, 0.953403, 0.953961, 0.954506, 0.955039, 0.955559, 0.956067, 
69             0.956563, 0.957049, 0.957524, 0.957988, 0.958443, 0.958887, 0.959323, 
70             0.959749, 0.960166, 0.960575, 0.960975, 0.961368, 0.961752, 0.962129, 
71             0.962499, 0.962861, 0.963217, 0.963566, 0.963908, 0.964244, 0.964574, 
72             0.964897, 0.965215, 0.965527, 0.965834, 0.966135, 0.966431, 0.966722, 
73             0.967007, 0.967288, 0.967564, 0.967836, 0.968103, 0.968366, 0.968624, 
74             0.968878, 0.969128, 0.969374, 0.969617, 0.969855, 0.97009, 0.970321, 
75             0.970548, 0.970772, 0.970993, 0.97121, 0.971425, 0.971636, 0.971843, 
76             0.972048, 0.97225, 0.972449, 0.972645, 0.972839, 0.973029, 0.973217, 
77             0.973403, 0.973586, 0.973766, 0.973944, 0.97412, 0.974293, 0.974464, 
78             0.974632, 0.974799, 0.974963, 0.975125, 0.975285, 0.975443, 0.975599, 
79             0.975753, 0.975905, 0.976055, 0.976204, 0.97635, 0.976495, 0.976638, 
80             0.976779, 0.976918, 0.977056, 0.977193, 0.977327, 0.97746, 0.977592, 
81             0.977722, 0.97785, 0.977977, 0.978103, 0.978227, 0.978349, 0.978471, 
82             0.978591, 0.978709, 0.978827, 0.978943, 0.979058, 0.979171, 0.979283, 
83             0.979395, 0.979504, 0.979613, 0.979721, 0.979827, 0.979933, 0.980037, 
84             0.98014, 0.980242, 0.980343, 0.980443, 0.980543, 0.980641, 0.980738, 
85             0.980834, 0.980929, 0.981023, 0.981116, 0.981209, 0.9813, 0.981391, 0.981481, 
86             0.981569, 0.981657, 0.981745, 0.981831, 0.981916, 0.982001, 0.982085, 
87             0.982168, 0.982251, 0.982332, 0.982413, 0.982493, 0.982573, 0.982651, 
88             0.982729, 0.982807, 0.982883, 0.982959, 0.983034, 0.983109, 0.983183, 
89             0.983256, 0.983329, 0.983401, 0.983472, 0.983543, 0.983613, 0.983683, 
90             0.983752, 0.98382, 0.983888, 0.983956, 0.984022, 0.984089, 0.984154, 
91             0.984219, 0.984284, 0.984348, 0.984411, 0.984474, 0.984537, 0.984599, 
92             0.98466, 0.984721, 0.984782, 0.984842, 0.984902, 0.984961, 0.985019, 
93             0.985077, 0.985135, 0.985193, 0.985249, 0.985306, 0.985362, 0.985417, 
94             0.985472, 0.985527, 0.985582, 0.985635, 0.985689, 0.985742, 0.985795, 
95             0.985847, 0.985899, 0.985951, 0.986002, 0.986053, 0.986103, 0.986153, 
96             0.986203, 0.986252, 0.986301, 0.98635, 0.986398, 0.986446, 0.986494, 
97             0.986541, 0.986588, 0.986635, 0.986681, 0.986727, 0.986773, 0.986818, 
98             0.986863, 0.986908, 0.986953, 0.986997, 0.987041, 0.987084, 0.987128, 
99             0.987171, 0.987213, 0.987256, 0.987298, 0.98734, 0.987381, 0.987423, 
100             0.987464, 0.987504, 0.987545, 0.987585, 0.987625, 0.987665, 0.987704, 
101             0.987744, 0.987783, 0.987821, 0.98786, 0.987898, 0.987936, 0.987974, 
102             0.988011, 0.988049, 0.988086, 0.988123, 0.988159, 0.988196, 0.988232, 
103             0.988268, 0.988303, 0.988339, 0.988374, 0.988409, 0.988444, 0.988479, 
104             0.988513, 0.988547, 0.988582, 0.988615, 0.988649, 0.988682, 0.988716, 
105             0.988749, 0.988782, 0.988814, 0.988847, 0.988879, 0.988911, 0.988943, 
106             0.988975, 0.989006, 0.989038, 0.989069, 0.9891, 0.989131, 0.989161, 0.989192, 
107             0.989222, 0.989252, 0.989282, 0.989312, 0.989342, 0.989371, 0.989401, 
108             0.98943, 0.989459, 0.989488, 0.989516, 0.989545, 0.989573, 0.989602, 0.98963, 
109             0.989658, 0.989685, 0.989713, 0.98974, 0.989768, 0.989795, 0.989822, 
110             0.989849, 0.989876, 0.989902, 0.989929, 0.989955, 0.989981, 0.990007, 
111             0.990033, 0.990059, 0.990085, 0.99011, 0.990136, 0.990161, 0.990186, 
112             0.990211, 0.990236, 0.990261, 0.990285, 0.99031, 0.990334, 0.990358, 
113             0.990383, 0.990407, 0.99043, 0.990454, 0.990478, 0.990501, 0.990525, 
114             0.990548, 0.990571, 0.990594, 0.990617, 0.99064, 0.990663, 0.990686, 
115             0.990708, 0.990731, 0.990753, 0.990775, 0.990797, 0.990819, 0.990841, 
116             0.990863, 0.990885, 0.990906, 0.990928, 0.990949, 0.99097, 0.990991, 
117             0.991013, 0.991034, 0.991054, 0.991075, 0.991096, 0.991116, 0.991137, 
118             0.991157, 0.991178, 0.991198, 0.991218, 0.991238, 0.991258, 0.991278, 
119             0.991298, 0.991317, 0.991337, 0.991356, 0.991376, 0.991395, 0.991414, 
120             0.991433, 0.991452, 0.991471, 0.99149, 0.991509, 0.991528, 0.991547, 
121             0.991565, 0.991584, 0.991602, 0.99162, 0.991639, 0.991657, 0.991675, 
122             0.991693, 0.991711, 0.991729, 0.991746, 0.991764, 0.991782, 0.991799, 
123             0.991817, 0.991834, 0.991851, 0.991869, 0.991886, 0.991903, 0.99192, 
124             0.991937, 0.991954, 0.991971, 0.991987, 0.992004, 0.992021, 0.992037, 
125             0.992054, 0.99207, 0.992086, 0.992103, 0.992119, 0.992135, 0.992151, 
126             0.992167, 0.992183, 0.992199, 0.992215, 0.99223, 0.992246, 0.992262, 
127             0.992277, 0.992293, 0.992308, 0.992324, 0.992339, 0.992354, 0.992369, 
128             0.992384, 0.9924, 0.992415, 0.992429, 0.992444, 0.992459, 0.992474, 0.992489, 
129             0.992503, 0.992518, 0.992533, 0.992547, 0.992561, 0.992576, 0.99259, 
130             0.992604, 0.992619, 0.992633, 0.992647, 0.992661, 0.992675, 0.992689, 
131             0.992703, 0.992717, 0.99273, 0.992744, 0.992758, 0.992771, 0.992785, 
132             0.992798, 0.992812, 0.992825, 0.992839, 0.992852, 0.992865, 0.992879, 
133             0.992892, 0.992905, 0.992918, 0.992931, 0.992944, 0.992957, 0.99297, 
134             0.992983, 0.992995, 0.993008, 0.993021, 0.993034, 0.993046, 0.993059, 
135             0.993071, 0.993084, 0.993096, 0.993109, 0.993121, 0.993133, 0.993145, 
136             0.993158, 0.99317, 0.993182, 0.993194, 0.993206, 0.993218, 0.99323, 0.993242, 
137             0.993254, 0.993266, 0.993277, 0.993289, 0.993301, 0.993312, 0.993324, 
138             0.993336, 0.993347, 0.993359, 0.99337, 0.993382, 0.993393, 0.993404, 
139             0.993416, 0.993427, 0.993438, 0.993449, 0.99346, 0.993472, 0.993483, 
140             0.993494, 0.993505, 0.993516, 0.993527, 0.993538, 0.993548, 0.993559, 
141             0.99357, 0.993581, 0.993591, 0.993602, 0.993613, 0.993623, 0.993634, 
142             0.993644, 0.993655, 0.993665, 0.993676, 0.993686, 0.993697, 0.993707, 
143             0.993717, 0.993727, 0.993738, 0.993748, 0.993758, 0.993768, 0.993778, 
144             0.993788, 0.993798, 0.993808, 0.993818, 0.993828, 0.993838, 0.993848, 
145             0.993858, 0.993868, 0.993877, 0.993887, 0.993897, 0.993907, 0.993916, 
146             0.993926, 0.993935, 0.993945, 0.993954, 0.993964, 0.993973, 0.993983, 
147             0.993992, 0.994002, 0.994011, 0.99402, 0.99403, 0.994039, 0.994048, 0.994057, 
148             0.994067, 0.994076, 0.994085, 0.994094, 0.994103, 0.994112, 0.994121, 
149             0.99413, 0.994139, 0.994148, 0.994157, 0.994166, 0.994175, 0.994183, 
150             0.994192, 0.994201, 0.99421, 0.994218, 0.994227, 0.994236, 0.994244, 
151             0.994253, 0.994262, 0.99427, 0.994279, 0.994287, 0.994296, 0.994304, 
152             0.994313, 0.994321, 0.994329, 0.994338, 0.994346, 0.994354, 0.994363, 
153             0.994371, 0.994379, 0.994387, 0.994395, 0.994404, 0.994412, 0.99442, 
154             0.994428, 0.994436, 0.994444, 0.994452, 0.99446, 0.994468, 0.994476, 
155             0.994484, 0.994492, 0.9945, 0.994508, 0.994516, 0.994523, 0.994531, 0.994539, 
156             0.994547, 0.994554, 0.994562, 0.99457, 0.994577, 0.994585, 0.994593, 0.9946, 
157             0.994608, 0.994615, 0.994623, 0.994631, 0.994638, 0.994645, 0.994653, 
158             0.99466, 0.994668, 0.994675, 0.994683, 0.99469, 0.994697, 0.994705, 0.994712, 
159             0.994719, 0.994726, 0.994734, 0.994741, 0.994748, 0.994755, 0.994762, 
160             0.994769, 0.994777, 0.994784, 0.994791, 0.994798, 0.994805, 0.994812, 
161             0.994819, 0.994826, 0.994833, 0.99484, 0.994847, 0.994854, 0.99486, 0.994867, 
162             0.994874, 0.994881, 0.994888, 0.994895, 0.994901, 0.994908, 0.994915, 
163             0.994922, 0.994928, 0.994935, 0.994942, 0.994948, 0.994955, 0.994962, 
164             0.994968, 0.994975, 0.994981, 0.994988, 0.994994, 0.995001, 0.995007, 
165             0.995014, 0.99502, 0.995027, 0.995033, 0.99504, 0.995046, 0.995052, 0.995059, 
166             0.995065, 0.995071, 0.995078, 0.995084, 0.99509, 0.995097, 0.995103, 
167             0.995109, 0.995115, 0.995121, 0.995128, 0.995134, 0.99514, 0.995146, 
168             0.995152, 0.995158, 0.995164, 0.995171, 0.995177, 0.995183, 0.995189, 
169             0.995195, 0.995201, 0.995207, 0.995213, 0.995219, 0.995225, 0.995231, 
170             0.995236, 0.995242, 0.995248, 0.995254, 0.99526, 0.995266, 0.995272, 
171             0.995277, 0.995283, 0.995289, 0.995295, 0.995301, 0.995306, 0.995312, 
172             0.995318, 0.995323, 0.995329, 0.995335, 0.99534, 0.995346, 0.995352, 
173             0.995357, 0.995363, 0.995369, 0.995374, 0.99538, 0.995385, 0.995391, 
174             0.995396, 0.995402, 0.995407, 0.995413, 0.995418, 0.995424, 0.995429, 
175             0.995435, 0.99544, 0.995445, 0.995451, 0.995456, 0.995462, 0.995467, 
176             0.995472, 0.995478, 0.995483, 0.995488, 0.995493, 0.995499, 0.995504, 
177             0.995509, 0.995515, 0.99552, 0.995525, 0.99553, 0.995535, 0.995541, 0.995546, 
178             0.995551, 0.995556, 0.995561, 0.995566, 0.995571, 0.995577, 0.995582, 
179             0.995587, 0.995592, 0.995597, 0.995602, 0.995607, 0.995612, 0.995617, 
180             0.995622, 0.995627, 0.995632, 0.995637, 0.995642, 0.995647, 0.995652, 
181             0.995657, 0.995661, 0.995666, 0.995671, 0.995676, 0.995681, 0.995686, 
182             0.995691, 0.995695, 0.9957, 0.995705, 0.99571, 0.995715, 0.995719, 0.995724, 
183             0.995729, 0.995734, 0.995738, 0.995743, 0.995748, 0.995753, 0.995757, 
184             0.995762, 0.995767, 0.995771, 0.995776, 0.995781, 0.995785, 0.99579, 
185             0.995794, 0.995799, 0.995804, 0.995808, 0.995813, 0.995817, 0.995822, 
186             0.995826, 0.995831, 0.995835, 0.99584, 0.995844, 0.995849, 0.995853, 
187             0.995858, 0.995862, 0.995867, 0.995871, 0.995876, 0.99588, 0.995885, 
188             0.995889, 0.995893, 0.995898, 0.995902, 0.995906, 0.995911, 0.995915, 
189             0.99592, 0.995924, 0.995928, 0.995932, 0.995937, 0.995941, 0.995945, 0.99595, 
190             0.995954, 0.995958, 0.995962, 0.995967, 0.995971, 0.995975, 0.995979, 
191             0.995984, 0.995988, 0.995992, 0.995996, 0.996, 0.996004, 0.996009, 0.996013, 
192             0.996017, 0.996021, 0.996025, 0.996029, 0.996033, 0.996037, 0.996041, 
193             0.996046, 0.99605, 0.996054, 0.996058, 0.996062, 0.996066, 0.99607, 0.996074, 
194             0.996078, 0.996082, 0.996086, 0.99609, 0.996094, 0.996098, 0.996102, 
195             0.996106, 0.99611, 0.996114, 0.996117, 0.996121, 0.996125, 0.996129, 
196             0.996133, 0.996137, 0.996141, 0.996145, 0.996149, 0.996152, 0.996156, 
197             0.99616, 0.996164]
198
199 # Run-time configuration parameters (can be set with command-line options)
200
201 $inner=3
202 $warmup=1
203 $outer=4
204 $includeSunSpider=true
205 $includeV8=true
206 $includeKraken=true
207 $benchmarkPattern=nil
208 $verbosity=0
209 $innerMode=:reload
210 $timeMode=:auto
211 $keepFiles=false
212
213 # Helpful functions and classes
214
215 def smallUsage
216   puts "Use the --help option to get basic usage information."
217   exit 1
218 end
219
220 def usage
221   puts "bencher [options] <vm1> [<vm2> ...]"
222   puts
223   puts "Runs one or more JavaScript runtimes against SunSpider, V8, and/or Kraken"
224   puts "benchmarks, and reports detailed statistics.  What makes bencher special is"
225   puts "that each benchmark/VM configuration is run in a single VM invocation, and"
226   puts "the invocations are run in random order.  This minimizes systematics due to"
227   puts "one benchmark polluting the running time of another.  The fine-grained"
228   puts "interleaving of VM invocations further minimizes systematics due to changes in"
229   puts "the performance or behavior of your machine."
230   puts 
231   puts "Bencher is highly configurable.  You can compare as many VMs as you like.  You"
232   puts "can change the amount of warm-up iterations, number of iterations executed per"
233   puts "VM invocation, and the number of VM invocations per benchmark.  By default,"
234   puts "SunSpider, VM, and Kraken are all run; but you can run any combination of these"
235   puts "suites."
236   puts
237   puts "The <vm> should be either a path to a JavaScript runtime executable (such as"
238   puts "jsc), or a string of the form <name>:<path>, where the <path> is the path to"
239   puts "the executable and <name> is the name that you would like to give the"
240   puts "configuration for the purposeof reporting.  If no name is given, a generic name"
241   puts "of the form Conf#<n> will be ascribed to the configuration automatically."
242   puts 
243   puts "Options:"
244   puts "--inner <n>          Set the number of inner (per-runtime-invocation)"
245   puts "                     iterations.  Default is #{$inner}."
246   puts "--outer <n>          Set the number of runtime invocations for each benchmark."
247   puts "                     Default is #{$outer}."
248   puts "--warmup <n>         Set the number of warm-up runs per invocation.  Default"
249   puts "                     is #{$warmup}."
250   puts "--timing-mode        Set the way that bencher measures time.  Possible values"
251   puts "                     are 'preciseTime', 'date', and 'auto'.  Default is"
252   puts "                     'auto', which automatically detects the best way."
253   puts "--v8-only            Only run V8."
254   puts "--sunspider-only     Only run SunSpider."
255   puts "--kraken-only        Only run Kraken."
256   puts "--exclude-v8         Exclude V8 (only run SunSpider and Kraken)."
257   puts "--exclude-sunspider  Exclude SunSpider (only run V8 and Kraken)."
258   puts "--exclude-kraken     Exclude SunSpider (only run SunSpider and V8)."
259   puts "--benchmarks         Only run benchmarks matching the given regular expression."
260   puts "--keep-files         Keep temporary files.  Useful for debugging."
261   puts "--verbose or -v      Print more stuff."
262   puts "--help or -h         Display this message."
263   puts
264   puts "Example:"
265   puts "bencher TipOfTree:/Volumes/Data/pizlo/OpenSource/WebKitBuild/Release/jsc MyChanges:/Volumes/Data/pizlo/secondary/OpenSource/WebKitBuild/Release/jsc"
266   exit 1
267 end
268
269 def fail(reason)
270   if reason.respond_to? :backtrace
271     puts "FAILED: #{reason}"
272     puts "Stack trace:"
273     puts reason.backtrace.join("\n")
274   else
275     puts "FAILED: #{reason}"
276   end
277   smallUsage
278 end
279
280 def quickFail(r1,r2)
281   $stderr.puts "#{$0}: #{r1}"
282   puts
283   fail(r2)
284 end
285
286 def intArg(argName,arg,min,max)
287   result=arg.to_i
288   unless result.to_s == arg
289     quickFail("Expected an integer value for #{argName}, but got #{arg}.",
290               "Invalid argument for command-line option")
291   end
292   if min and result<min
293     quickFail("Argument for #{argName} cannot be smaller than #{min}.",
294               "Invalid argument for command-line option")
295   end
296   if max and result>max
297     quickFail("Argument for #{argName} cannot be greater than #{max}.",
298               "Invalid argument for command-line option")
299   end
300   result
301 end
302
303 def computeMean(array)
304   sum=0.0
305   array.each {
306     | value |
307     sum += value
308   }
309   sum/array.length
310 end
311
312 def computeGeometricMean(array)
313   mult=1.0
314   array.each {
315     | value |
316     mult*=value
317   }
318   mult**(1.0/array.length)
319 end
320
321 def computeHarmonicMean(array)
322   1.0 / computeMean(array.collect{ | value | 1.0 / value })
323 end
324
325 def computeStdDev(array)
326   case array.length
327   when 0
328     0.0/0.0
329   when 1
330     0.0
331   else
332     begin
333       mean=computeMean(array)
334       sum=0.0
335       array.each {
336         | value |
337         sum += (value-mean)**2
338       }
339       Math.sqrt(sum/(array.length-1))
340     rescue
341       0.0/0.0
342     end
343   end
344 end
345
346 class Array
347   def shuffle!
348     size.downto(1) { |n| push delete_at(rand(n)) }
349     self
350   end
351 end
352
353 def inverseBetaRegularized(n)
354   IBR_LOOKUP[n-1]
355 end
356
357 def numToStr(num)
358   "%.4f"%(num.to_f)
359 end
360   
361 class NoChange
362   attr_reader :amountFaster
363   
364   def initialize(amountFaster)
365     @amountFaster = amountFaster
366   end
367   
368   def shortForm
369     " "
370   end
371   
372   def to_s
373     if @amountFaster < 1.01
374       ""
375     else
376       "  might be #{numToStr(@amountFaster)}x faster"
377     end
378   end
379 end
380
381 class Faster
382   attr_reader :amountFaster
383   
384   def initialize(amountFaster)
385     @amountFaster = amountFaster
386   end
387   
388   def shortForm
389     "^"
390   end
391   
392   def to_s
393     "^ definitely #{numToStr(@amountFaster)}x faster"
394   end
395 end
396
397 class Slower
398   attr_reader :amountSlower
399   
400   def initialize(amountSlower)
401     @amountSlower = amountSlower
402   end
403   
404   def shortForm
405     "!"
406   end
407   
408   def to_s
409     "! definitely #{numToStr(@amountSlower)}x slower"
410   end
411 end
412
413 class MayBeSlower
414   attr_reader :amountSlower
415   
416   def initialize(amountSlower)
417     @amountSlower = amountSlower
418   end
419   
420   def shortForm
421     "?"
422   end
423   
424   def to_s
425     if @amountSlower < 1.01
426       "?"
427     else
428       "? might be #{numToStr(@amountSlower)}x slower"
429     end
430   end
431 end
432
433 class Stats
434   def initialize
435     @array = []
436   end
437   
438   def add(value)
439     if value.is_a? Stats
440       add(value.array)
441     elsif value.respond_to? :each
442       value.each {
443         | v |
444         add(v)
445       }
446     else
447       @array << value.to_f
448     end
449   end
450     
451   def array
452     @array
453   end
454   
455   def sum
456     result=0
457     @array.each {
458       | value |
459       result += value
460     }
461     result
462   end
463   
464   def min
465     @array.min
466   end
467   
468   def max
469     @array.max
470   end
471   
472   def size
473     @array.length
474   end
475   
476   def mean
477     computeMean(array)
478   end
479   
480   def arithmeticMean
481     mean
482   end
483   
484   def stdDev
485     computeStdDev(array)
486   end
487
488   def stdErr
489     stdDev/Math.sqrt(size)
490   end
491   
492   # Computes a 95% Student's t distribution confidence interval
493   def confInt
494     if size < 2
495       0.0/0.0
496     else
497       raise if size > 1000
498       Math.sqrt(size-1.0)*stdErr*Math.sqrt(-1.0+1.0/inverseBetaRegularized(size-1))
499     end
500   end
501   
502   def lower
503     mean-confInt
504   end
505   
506   def upper
507     mean+confInt
508   end
509   
510   def geometricMean
511     computeGeometricMean(array)
512   end
513   
514   def harmonicMean
515     computeHarmonicMean(array)
516   end
517   
518   def compareTo(other)
519     if upper < other.lower
520       Faster.new(other.mean/mean)
521     elsif lower > other.upper
522       Slower.new(mean/other.mean)
523     elsif mean > other.mean
524       MayBeSlower.new(mean/other.mean)
525     else
526       NoChange.new(other.mean/mean)
527     end
528   end
529   
530   def to_s
531     "size = #{size}, mean = #{mean}, stdDev = #{stdDev}, stdErr = #{stdErr}, confInt = #{confInt}"
532   end
533 end
534
535 def doublePuts(out1,out2,msg)
536   out1.puts "#{out2.path}: #{msg}" if $verbosity>=3
537   out2.puts msg
538 end
539
540 def benchRunHarness(vm, benchpath)
541   $stderr.puts "running #{benchpath} with #{vm}..." if $verbosity>=1
542   result=nil
543   Tempfile.open("bencher") {
544     | file |
545     yield(file)
546     file.flush
547     begin
548       result=vm.runAndReport(file.path)
549     rescue => e
550       $stderr.puts "Could not run #{file.path}:"
551       File.open(file.path,"r") {
552         | inp |
553         inp.each_line {
554           | line |
555           $stderr.puts "#{line}"
556         }
557       }
558     end
559     file.unlink unless $keepFiles
560   }
561   raise unless result and result.size == $inner
562   result
563 end
564
565 def emitTimeHelpers(file)
566   case $timeMode
567   when :preciseTime
568     doublePuts($stderr,file,"function __bencher_curTimeMS() {")
569     doublePuts($stderr,file,"   return preciseTime()*1000")
570     doublePuts($stderr,file,"}")
571   when :date
572     doublePuts($stderr,file,"function __bencher_curTimeMS() {")
573     doublePuts($stderr,file,"   return Date.now()")
574     doublePuts($stderr,file,"}")
575   else
576     raise
577   end
578   doublePuts($stderr,file,"function __bencher_run(__bencher_what) {")
579   doublePuts($stderr,file,"   var __bencher_before = __bencher_curTimeMS();")
580   doublePuts($stderr,file,"   run(__bencher_what);")
581   doublePuts($stderr,file,"   var __bencher_after = __bencher_curTimeMS();")
582   doublePuts($stderr,file,"   return __bencher_after - __bencher_before;")
583   doublePuts($stderr,file,"}")
584 end
585
586 def emitBenchRunCode(vm, file, benchpath)
587   emitTimeHelpers(file)
588   case $innerMode
589   when :reload
590     case vm.vmType
591     when :jsc
592       $warmup.times {
593         doublePuts($stderr,file,"__bencher_run(#{benchpath.inspect})")
594         doublePuts($stderr,file,"gc();")
595       }
596       $inner.times {
597         doublePuts($stderr,file,"print(\"Time: \"+__bencher_run(#{benchpath.inspect}));")
598         doublePuts($stderr,file,"gc();")
599       }
600     when :dumpRenderTree
601       doublePuts($stderr,file,"__bencher_count = 0;")
602       doublePuts($stderr,file,"function __bencher_doNext(result) {")
603       doublePuts($stderr,file,"    if (__bencher_count >= #{$warmup})")
604       doublePuts($stderr,file,"        debug(\"Time: \"+result);")
605       doublePuts($stderr,file,"    __bencher_count++;")
606       doublePuts($stderr,file,"    if (__bencher_count < #{$inner+$warmup})")
607       doublePuts($stderr,file,"        __bencher_runImpl(#{benchpath.inspect}, __bencher_doNext);")
608       doublePuts($stderr,file,"    else")
609       doublePuts($stderr,file,"        quit();")
610       doublePuts($stderr,file,"}")
611       doublePuts($stderr,file,"__bencher_runImpl(#{benchpath.inspect}, __bencher_doNext);")
612     else
613       raise vm.vmType
614     end
615   when :loadOnce
616     doublePuts($stderr,file,"function runit() {")
617     File.open(benchpath,'r') {
618       | inp |
619       inp.each_line {
620         | line |
621         doublePuts($stderr,file,line)
622       }
623     }
624     doublePuts($stderr,file,"}")
625     $warmup.times {
626       doublePuts($stderr,file,"runit();")
627       doublePuts($stderr,file,"gc();")
628     }
629     $inner.times {
630       doublePuts($stderr,file,"before = __bencher_curTimeMS();")
631       doublePuts($stderr,file,"runit();")
632       doublePuts($stderr,file,"after = __bencher_curTimeMS();")
633       doublePuts($stderr,file,"print(\"Time: \"+(after-before));")
634       doublePuts($stderr,file,"gc();")
635     }
636     if vm.vmType == :dumpRenderTree
637       doublePuts($stderr,file,"quit();")
638     end
639   else
640     raise "bad $innerMode: #{$innerMode}"
641   end
642 end
643
644 def runBenchmarkAndReport(vm, benchpath)
645   benchRunHarness(vm, benchpath) {
646     | file |
647     emitBenchRunCode(vm, file, benchpath)
648   }
649 end
650
651 class StatsAccumulator
652   def initialize
653     @stats = []
654     ($outer*$inner).times {
655       @stats << Stats.new
656     }
657   end
658   
659   def statsForIteration(outerIteration, innerIteration)
660     @stats[outerIteration*$inner + innerIteration]
661   end
662   
663   def stats
664     result = Stats.new
665     @stats.each {
666       | stat |
667       result.add(yield stat)
668     }
669     result
670   end
671   
672   def geometricMeanStats
673     stats {
674       | stat |
675       stat.geometricMean
676     }
677   end
678   
679   def arithmeticMeanStats
680     stats {
681       | stat |
682       stat.arithmeticMean
683     }
684   end
685 end
686
687 class VM < StatsAccumulator
688   def initialize(path, name, nameKind)
689     super()
690     @path = path
691     @name = name
692     @nameKind = nameKind
693     
694     Tempfile.open("bencher-vmtest") {
695       | file |
696       file.puts "print(\"here\");"
697       file.flush
698       
699       result = nil
700       @vmType = :jsc
701       run(file.path) {
702         | inp |
703         result = inp.read
704       }
705       
706       if result.chomp == "here"
707         $stderr.puts "#{@name} is definitely a jsc-style VM." if $verbosity>=1
708         @vmType = :jsc
709       else
710         $stderr.puts "Assuming that #{@name} is a DumpRenderTree-style VM." if $verbosity>=1
711         @vmType = :dumpRenderTree
712       end
713     }
714   end
715   
716   def to_s
717     @name
718   end
719   
720   def name
721     @name
722   end
723   
724   def path
725     @path
726   end
727   
728   def nameKind
729     @nameKind
730   end
731   
732   def vmType
733     @vmType
734   end
735   
736   def printFunction
737     case @vmType
738     when :jsc
739       "print"
740     when :dumpRenderTree
741       "debug"
742     else
743       raise @vmType
744     end
745   end
746   
747   def run(filename)
748     case @vmType
749     when :jsc
750       cmd = "#{@path} #{filename}"
751       $stderr.puts ">> #{cmd}" if $verbosity>=2
752       IO.popen(cmd,"r") {
753         | inp |
754         yield inp
755       }
756       $?.success?
757     when :dumpRenderTree
758       result = nil
759       Tempfile.open(["bencher-htmldoc",".html"]) {
760         | htmlFile |
761         Tempfile.open("bencher-css") {
762           | cssFile |
763           doublePuts($stderr,cssFile,".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}")
764           Tempfile.open("bencher-pre") {
765             | preFile |
766             doublePuts($stderr,preFile,
767                        "if (window.layoutTestController) {\n"+
768                        "    layoutTestController.dumpAsText(window.enablePixelTesting);\n"+
769                        "    layoutTestController.waitUntilDone();\n"+
770                        "}\n"+
771                        "\n"+
772                        "function debug(msg)\n"+
773                        "{\n"+
774                        "    var span = document.createElement(\"span\");\n"+
775                        "    document.getElementById(\"console\").appendChild(span); // insert it first so XHTML knows the namespace\n"+
776                        "    span.innerHTML = msg + '<br />';\n"+
777                        "}\n"+
778                        "\n"+
779                        "function quit() {\n"+
780                        "    layoutTestController.notifyDone();\n"+
781                        "}\n"+
782                        "\n"+
783                        "__bencher_continuation=null;\n"+
784                        "\n"+
785                        "function reportResult(result) {\n"+
786                        "    __bencher_continuation(result);\n"+
787                        "}\n"+
788                        "\n"+
789                        "function __bencher_runImpl(filename, continuation) {\n"+
790                        "    function doit() {\n"+
791                        "        document.getElementById(\"frameparent\").innerHTML = \"\";\n"+
792                        "        document.getElementById(\"frameparent\").innerHTML = \"<iframe id='testframe'>\";\n"+
793                        "        var testFrame = document.getElementById(\"testframe\");\n"+
794                        "        testFrame.contentDocument.open();\n"+
795                        "        testFrame.contentDocument.write(\"<!DOCTYPE html>\\n<head></head><body><div id=\\\"console\\\"></div><script type=\\\"text/javascript\\\">__bencher_before = Date.now();</script><script src=\\\"file://\"+filename+\"\\\"></script><script type=\\\"text/javascript\\\">window.parent.reportResult(Date.now() - __bencher_before);</script></body></html>\");\n"+
796                        "        testFrame.contentDocument.close();\n"+
797                        "    }\n"+
798                        "    __bencher_continuation = continuation;\n"+
799                        "    window.setTimeout(doit, 10);\n"+
800                        "}\n")
801             doublePuts($stderr,htmlFile,"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n<html><head><link rel=\"stylesheet\" href=\"file://#{cssFile.path}\"><script src=\"file://#{preFile.path}\"></script></head><body><div id=\"console\"></div><div id=\"frameparent\"></div><script src=\"file://#{filename}\"></script></body></html>")
802             
803             htmlFile.flush
804             preFile.flush
805             cssFile.flush
806             
807             cmd = "#{path} #{htmlFile.path}"
808             $stderr.puts ">> #{cmd}" if $verbosity>=2
809             IO.popen(cmd, "r") {
810               | inp |
811               yield inp
812             }
813             result = $?
814             
815             #sleep 10000
816             
817             htmlFile.unlink unless $keepFiles
818             preFile.unlink unless $keepFiles
819             cssFile.unlink unless $keepFiles
820           }
821         }
822       }
823       result.success?
824     else
825       raise @vmType
826     end
827   end
828   
829   def runAndReport(filename)
830     result = nil
831     5.times {
832       result = Stats.new
833       run(filename) {
834         | inp |
835         inp.each_line {
836           | line |
837           $stderr.puts "stdout: #{line}" if $verbosity>=2
838           if line =~ /^Time: /
839             result.add($~.post_match.to_f)
840           end
841         }
842       }
843       break if result.size == $inner
844       if $verbosity == 0
845         $stderr.print "   "
846       end
847       $stderr.puts "Failed to run #{filename} on #{self}, retrying..."
848     }
849     raise "Failed to run #{filename} on #{self}" unless result and result.size == $inner
850     result
851   end
852 end
853
854 class SunSpiderBenchmark
855   attr_accessor :benchmarkSuite
856   
857   def initialize(name)
858     @name = name
859   end
860   
861   def to_s
862     @name
863   end
864   
865   def runAndReport(vm)
866     runBenchmarkAndReport(vm, "#{SUNSPIDER_PATH}/#{@name}.js")
867   end
868 end
869
870 class V8Benchmark
871   attr_accessor :benchmarkSuite
872   
873   def initialize(name)
874     @name = name
875   end
876   
877   def to_s
878     @name
879   end
880   
881   def runAndReport(vm)
882     runBenchmarkAndReport(vm, "#{V8_PATH}/v8-#{@name}.js")
883   end
884 end
885
886 class KrakenBenchmark
887   attr_accessor :benchmarkSuite
888   
889   def initialize(name)
890     @name = name
891   end
892   
893   def to_s
894     @name
895   end
896   
897   def runAndReport(vm)
898     # Kraken requires some special-casing
899     
900     dataPath="#{KRAKEN_PATH}/#{@name}-data.js"
901     benchPath="#{KRAKEN_PATH}/#{@name}.js"
902     
903     benchRunHarness(vm, benchPath) {
904       | file |
905       emitTimeHelpers(file)
906       doublePuts($stderr,file,"load(#{dataPath.inspect});")
907       doublePuts($stderr,file,"gc();");
908       if $innerMode == :reload
909         case vm.vmType
910         when :jsc
911           doublePuts($stderr,file,"for (var __bencher_index = 0; __bencher_index < #{$warmup+$inner}; ++__bencher_index) {")
912           doublePuts($stderr,file,"   before = __bencher_curTimeMS();")
913           doublePuts($stderr,file,"   load(#{benchPath.inspect});")
914           doublePuts($stderr,file,"   after = __bencher_curTimeMS();")
915           doublePuts($stderr,file,"   if (__bencher_index >= #{$warmup}) #{vm.printFunction}(\"Time: \"+(after-before));");
916           doublePuts($stderr,file,"   gc();");
917           doublePuts($stderr,file,"}")
918         when :dumpRenderTree
919           raise "Kraken in DumpRenderTree is currently unsupported."
920         else
921           raise vm.vmType
922         end
923       else
924         emitBenchRunCode(vm, file, benchPath)
925       end
926     }
927   end
928 end
929
930 class BenchmarkSuite
931   def initialize(name, path)
932     @name = name
933     @path = path
934     @benchmarks = []
935   end
936   
937   def name
938     @name
939   end
940   
941   def to_s
942     @name
943   end
944   
945   def path
946     @path
947   end
948   
949   def add(benchmark)
950     if not $benchmarkPattern or "#{@name}/#{benchmark.to_s}" =~ $benchmarkPattern
951       benchmark.benchmarkSuite = self
952       @benchmarks << benchmark
953     end
954   end
955   
956   def benchmarks
957     @benchmarks
958   end
959   
960   def empty?
961     @benchmarks.empty?
962   end
963   
964   def retain_if
965     @benchmarks.delete_if {
966       | benchmark |
967       not yield benchmark
968     }
969   end
970 end
971
972 class BenchmarkOnVM
973   def initialize(vm, benchmark, suiteOnVM)
974     @vm = vm
975     @benchmark = benchmark
976     @suiteOnVM = suiteOnVM
977     @stats = Stats.new
978   end
979   
980   def to_s
981     "#{@benchmark} on #{@vm}"
982   end
983   
984   def benchmark
985     @benchmark
986   end
987   
988   def vm
989     @vm
990   end
991   
992   def suite
993     @benchmark.benchmarkSuite
994   end
995   
996   def suiteOnVM
997     @suiteOnVM
998   end
999   
1000   def stats
1001     @stats
1002   end
1003   
1004   def runRecordAndReport
1005     result = @benchmark.runAndReport(@vm)
1006     @stats.add(result)
1007     result
1008   end
1009 end
1010
1011 class SuiteOnVM < StatsAccumulator
1012   def initialize(vm, suite)
1013     super()
1014     @vm = vm
1015     @suite = suite
1016   end
1017   
1018   def to_s
1019     "#{@suite} on #{@vm}"
1020   end
1021   
1022   def suite
1023     @suite
1024   end
1025   
1026   def vm
1027     @vm
1028   end
1029 end
1030
1031 class BenchPlan
1032   def initialize(benchmarkOnVM, iteration)
1033     @benchmarkOnVM = benchmarkOnVM
1034     @iteration = iteration
1035   end
1036   
1037   def to_s
1038     "#{@benchmarkOnVM} \##{@iteration+1}"
1039   end
1040   
1041   def benchmarkOnVM
1042     @benchmarkOnVM
1043   end
1044   
1045   def benchmark
1046     @benchmarkOnVM.benchmark
1047   end
1048   
1049   def suite
1050     @benchmarkOnVM.suite
1051   end
1052   
1053   def vm
1054     @benchmarkOnVM.vm
1055   end
1056   
1057   def iteration
1058     @iteration
1059   end
1060   
1061   def runAndRecord
1062     @benchmarkOnVM.runRecordAndReport.array.each_with_index {
1063       | value, index |
1064       @benchmarkOnVM.vm.statsForIteration(@iteration, index).add(value)
1065       @benchmarkOnVM.suiteOnVM.statsForIteration(@iteration, index).add(value)
1066     }
1067   end
1068 end
1069
1070 def lpad(str,chars)
1071   if str.length>chars
1072     str
1073   else
1074     "%#{chars}s"%(str)
1075   end
1076 end
1077
1078 def rpad(str,chars)
1079   while str.length<chars
1080     str+=" "
1081   end
1082   str
1083 end
1084
1085 def center(str,chars)
1086   while str.length<chars
1087     str+=" "
1088     if str.length<chars
1089       str=" "+str
1090     end
1091   end
1092   str
1093 end
1094
1095 def statsToStr(stats)
1096   lpad(numToStr(stats.mean),11)+"+-"+rpad(numToStr(stats.confInt),9)
1097 end
1098   
1099 begin
1100   GetoptLong.new(['--inner', GetoptLong::REQUIRED_ARGUMENT],
1101                  ['--outer', GetoptLong::REQUIRED_ARGUMENT],
1102                  ['--warmup', GetoptLong::REQUIRED_ARGUMENT],
1103                  ['--time-mode', GetoptLong::REQUIRED_ARGUMENT],
1104                  ['--sunspider-only', GetoptLong::NO_ARGUMENT],
1105                  ['--v8-only', GetoptLong::NO_ARGUMENT],
1106                  ['--kraken-only', GetoptLong::NO_ARGUMENT],
1107                  ['--exclude-sunspider', GetoptLong::NO_ARGUMENT],
1108                  ['--exclude-v8', GetoptLong::NO_ARGUMENT],
1109                  ['--exclude-kraken', GetoptLong::NO_ARGUMENT],
1110                  ['--benchmarks', GetoptLong::REQUIRED_ARGUMENT],
1111                  ['--load-once', GetoptLong::NO_ARGUMENT],
1112                  ['--keep-files', GetoptLong::NO_ARGUMENT],
1113                  ['--verbose', '-v', GetoptLong::NO_ARGUMENT],
1114                  ['--help', '-h', GetoptLong::NO_ARGUMENT]).each {
1115     | opt, arg |
1116     case opt
1117     when '--inner'
1118       $inner = intArg(opt,arg,1,nil)
1119     when '--outer'
1120       $outer = intArg(opt,arg,1,nil)
1121     when '--warmup'
1122       $warmup = intArg(opt,arg,0,nil)
1123     when '--time-mode'
1124       if arg.upcase == "PRECISETIME"
1125         $timeMode = :preciseTime
1126       elsif arg.upcase == "DATE"
1127         $timeMode = :date
1128       elsif arg.upcase == "AUTO"
1129         $timeMode = :auto
1130       else
1131         quickFail("Expected either 'preciseTime', 'date', or 'auto' for --time-mode, but got '#{arg}'.",
1132                   "Invalid argument for command-line option")
1133       end
1134     when '--sunspider-only'
1135       $includeV8 = false
1136       $includeKraken = false
1137     when '--v8-only'
1138       $includeSunSpider = false
1139       $includeKraken = false
1140     when '--kraken-only'
1141       $includeSunSpider = false
1142       $includeV8 = false
1143     when '--exclude-sunspider'
1144       $includeSunSpider = false
1145     when '--exclude-v8'
1146       $includeV8 = false
1147     when '--exclude-kraken'
1148       $includeKraken = false
1149     when '--benchmarks'
1150       $benchmarkPattern = Regexp.new(arg)
1151     when '--load-once'
1152       $innerMode = :loadOnce
1153     when '--keep-files'
1154       $keepFiles = true
1155     when '--verbose'
1156       $verbosity += 1
1157     when '--help'
1158       usage
1159     else
1160       raise "bad option: #{opt}"
1161     end
1162   }
1163   
1164   if ARGV.empty?
1165     quickFail("Please specify at least on configuraiton on the command line.",
1166               "Insufficient arguments")
1167   end
1168   
1169   SUNSPIDER = BenchmarkSuite.new("SunSpider", SUNSPIDER_PATH)
1170   ["3d-cube", "3d-morph", "3d-raytrace", "access-binary-trees",
1171    "access-fannkuch", "access-nbody", "access-nsieve",
1172    "bitops-3bit-bits-in-byte", "bitops-bits-in-byte", "bitops-bitwise-and",
1173    "bitops-nsieve-bits", "controlflow-recursive", "crypto-aes",
1174    "crypto-md5", "crypto-sha1", "date-format-tofte", "date-format-xparb",
1175    "math-cordic", "math-partial-sums", "math-spectral-norm", "regexp-dna",
1176    "string-base64", "string-fasta", "string-tagcloud",
1177    "string-unpack-code", "string-validate-input"].each {
1178     | name |
1179     SUNSPIDER.add SunSpiderBenchmark.new(name)
1180   }
1181
1182   V8 = BenchmarkSuite.new("V8", V8_PATH)
1183   ["crypto", "deltablue", "earley-boyer", "raytrace",
1184    "regexp", "richards", "splay"].each {
1185     | name |
1186     V8.add V8Benchmark.new(name)
1187   }
1188
1189   KRAKEN = BenchmarkSuite.new("Kraken", KRAKEN_PATH)
1190   ["ai-astar", "audio-beat-detection", "audio-dft", "audio-fft",
1191    "audio-oscillator", "imaging-darkroom", "imaging-desaturate",
1192    "imaging-gaussian-blur", "json-parse-financial",
1193    "json-stringify-tinderbox", "stanford-crypto-aes",
1194    "stanford-crypto-ccm", "stanford-crypto-pbkdf2",
1195    "stanford-crypto-sha256-iterative"].each {
1196     | name |
1197     KRAKEN.add KrakenBenchmark.new(name)
1198   }
1199
1200   $vms = []
1201   
1202   ARGV.each {
1203     | vm |
1204     if vm =~ /([a-zA-Z0-9_ ]+):/
1205       name = $1
1206       nameKind = :given
1207       vm = $~.post_match
1208     else
1209       name = "Conf\##{$vms.length+1}"
1210       nameKind = :auto
1211     end
1212     $stderr.puts "#{name}: #{vm}" if $verbosity >= 1
1213     $vms << VM.new(vm, name, nameKind)
1214   }
1215   
1216   if $timeMode == :auto
1217     havePreciseTime = true
1218     $vms.each {
1219       | vm |
1220       if vm.vmType == :dumpRenderTree
1221         $stderr.puts "Warning: #{vm} does not have preciseTime() because it is DumpRenderTree; using Date.now instead."
1222         havePreciseTime = false
1223       else
1224         Tempfile.open("bencher-timetest") {
1225           | file |
1226           doublePuts($stderr,file,"#{vm.printFunction}(\"Time: \"+preciseTime());")
1227           file.flush
1228           result = nil
1229           vm.run(file.path) {
1230             | inp |
1231             result = inp.read
1232           }
1233           thisVMHasPreciseTime = false
1234           if result =~ /Time: /
1235             secs = $~.post_match.to_i
1236             if (secs - Time.now.to_i).abs < 5
1237               thisVMHasPreciseTime = true
1238             end
1239           end
1240           unless thisVMHasPreciseTime
1241             $stderr.puts "Warning: #{vm} does not have preciseTime(); using Date.now instead."
1242             havePreciseTime = false
1243           end
1244           file.unlink unless $keepFiles
1245         }
1246       end
1247     }
1248     
1249     if havePreciseTime
1250       $timeMode = :preciseTime
1251     else
1252       $timeMode = :date
1253     end
1254   end
1255   
1256   if $outer*$inner == 1
1257     $stderr.puts "Warning: will only collect one sample per benchmark/VM.  Confidence interval calculation will fail."
1258   end
1259   
1260   $stderr.puts "Using timeMode = #{$timeMode}." if $verbosity >= 1
1261   
1262   $suites = []
1263   
1264   if $includeSunSpider and not SUNSPIDER.empty?
1265     $suites << SUNSPIDER
1266   end
1267   
1268   if $includeV8 and not V8.empty?
1269     $suites << V8
1270   end
1271   
1272   if $includeKraken and not KRAKEN.empty?
1273     $suites << KRAKEN
1274   end
1275   
1276   $benchmarks = []
1277   $suites.each {
1278     | suite |
1279     $benchmarks += suite.benchmarks
1280   }
1281   
1282   $suitesOnVMs = []
1283   $suitesOnVMsForSuite = {}
1284   $suites.each {
1285     | suite |
1286     $suitesOnVMsForSuite[suite] = []
1287   }
1288   
1289   $benchmarksOnVMs = []
1290   $benchmarksOnVMsForBenchmark = {}
1291   $benchmarks.each {
1292     | benchmark |
1293     $benchmarksOnVMsForBenchmark[benchmark] = []
1294   }
1295   
1296   $vms.each {
1297     | vm |
1298     $suites.each {
1299       | suite |
1300       suiteOnVM = SuiteOnVM.new(vm, suite)
1301       $suitesOnVMs << suiteOnVM
1302       $suitesOnVMsForSuite[suite] << suiteOnVM
1303       suite.benchmarks.each {
1304         | benchmark |
1305         benchmarkOnVM = BenchmarkOnVM.new(vm, benchmark, suiteOnVM)
1306         $benchmarksOnVMs << benchmarkOnVM
1307         $benchmarksOnVMsForBenchmark[benchmark] << benchmarkOnVM
1308       }
1309     }
1310   }
1311
1312   $plans = []
1313   $benchmarksOnVMs.each {
1314     | benchmarkOnVM |
1315     $outer.times {
1316       | iteration |
1317       $plans << BenchPlan.new(benchmarkOnVM, iteration)
1318     }
1319   }
1320   
1321   $plans.shuffle!
1322   
1323   $suitepad = $suites.collect {
1324     | suite |
1325     suite.to_s.size
1326   }.max + 1
1327   
1328   $benchpad = ($benchmarks +
1329                ["<arithmetic>", "<geometric>", "<harmonic>"]).collect {
1330     | benchmark |
1331     benchmark.to_s.size
1332   }.max + 1
1333
1334   $vmpad = $vms.collect {
1335     | vm |
1336     vm.to_s.size
1337   }.max + 1
1338   
1339   $plans.each_with_index {
1340     | plan, idx |
1341     if $verbosity == 0
1342       text1 = lpad(idx.to_s,$plans.size.to_s.size)+"/"+$plans.size.to_s
1343       text2 = plan.suite.to_s+"/"+plan.benchmark.to_s+"/"+plan.vm.to_s
1344       $stderr.print "\r#{text1} #{rpad(text2,$suitepad+1+$benchpad+1+$vmpad)}"
1345       $stderr.print "\r#{text1} #{text2}"
1346       $stderr.flush
1347     end
1348     plan.runAndRecord
1349   }
1350   
1351   if $verbosity == 0
1352     $stderr.print "\r#{$plans.size}/#{$plans.size} #{' '*($suitepad+1+$benchpad+1+$vmpad)}"
1353     $stderr.puts "\r#{$plans.size}/#{$plans.size}"
1354   end
1355   
1356   if $verbosity >= 2
1357     $benchmarksOnVMs.each {
1358       | benchmarkOnVM |
1359       $stderr.puts "#{benchmarkOnVM}: #{benchmarkOnVM.stats}"
1360     }
1361     
1362     $vms.each {
1363       | vm |
1364       $stderr.puts "#{vm} (arithmeticMean): #{vm.arithmeticMeanStats}"
1365       $stderr.puts "#{vm} (geometricMean): #{vm.geometricMeanStats}"
1366     }
1367   end
1368   
1369   reportName =
1370     (if ($vms.collect {
1371            | vm |
1372            vm.nameKind
1373          }.index :auto)
1374        ""
1375      else
1376        $vms.collect {
1377          | vm |
1378          vm.to_s
1379        }.join("_") + "_"
1380      end) +
1381     ($suites.collect {
1382        | suite |
1383        suite.to_s
1384      }.join("")) + "_" +
1385     (begin
1386        time = Time.now
1387        "%04d%02d%02d_%02d%02d" %
1388          [ time.year, time.month, time.day,
1389            time.hour, time.min ]
1390      end) +
1391     "_benchReport.txt"
1392   
1393   $stderr.puts "Generating benchmark report at #{reportName}"
1394   
1395   outp = $stdout
1396   begin
1397     outp = File.open(reportName,"w")
1398   rescue => e
1399     $stderr.puts "Error: could not save report to #{reportName}: #{e}"
1400     $stderr.puts
1401   end
1402   
1403   def createVMsString
1404     result = ""
1405     result += "   " if $suites.size > 1
1406     result += rpad("", $benchpad)
1407     result += " "
1408     $vms.size.times {
1409       | index |
1410       if index != 0
1411         result += " "+NoChange.new(0).shortForm
1412       end
1413       result += lpad(center($vms[index].name, 9+9+2), 11+9+2)
1414     }
1415     result += "    "
1416     if $vms.size >= 3
1417       result += center("#{$vms[-1].name} v. #{$vms[0].name}",26)
1418     else
1419       result += " "*26
1420     end
1421     result
1422   end
1423   
1424   $columns = createVMsString.size
1425   
1426   def plural(num)
1427     if num == 1
1428       ""
1429     else
1430       "s"
1431     end
1432   end
1433   
1434   def wrap(str)
1435     array = str.split
1436     result = ""
1437     curLine = array.shift
1438     array.each {
1439       | curStr |
1440       if (curLine + " " + curStr).size > $columns
1441         result += curLine + "\n"
1442         curLine = curStr
1443       else
1444         curLine += " " + curStr
1445       end
1446     }
1447     result + curLine + "\n"
1448   end
1449   
1450   outp.print "Benchmark report for "
1451   if $suites.size == 1
1452     outp.print $suites[0].to_s
1453   elsif $suites.size == 2
1454     outp.print "#{$suites[0]} and #{$suites[1]}"
1455   else
1456     outp.print "#{$suites[0..-2].join(', ')}, and #{$suites[-1]}"
1457   end
1458   outp.puts "."
1459   outp.puts
1460   
1461   # This looks stupid; revisit later.
1462   if false
1463     $suites.each {
1464       | suite |
1465       outp.puts "#{suite} at #{suite.path}"
1466     }
1467     
1468     outp.puts
1469   end
1470   
1471   outp.puts "VMs tested:"
1472   $vms.each {
1473     | vm |
1474     outp.puts "\"#{vm.name}\" at #{Pathname.new(vm.path).realpath}"
1475   }
1476   
1477   outp.puts
1478   
1479   outp.puts wrap("Collected #{$outer*$inner} sample#{plural($outer*$inner)} per benchmark/VM, "+
1480                  "with #{$outer} VM invocation#{plural($outer)} per benchmark."+
1481                  (if $warmup == 0 then (" Did not include any warm-up iterations; measurements "+
1482                                         "began with the very first iteration.")
1483                   else (" Used #{$warmup} benchmark iteration#{plural($warmup)} per VM "+
1484                         "invocation for warm-up.") end)+
1485                  (case $timeMode
1486                   when :preciseTime then (" Used the jsc-specific preciseTime() function to get "+
1487                                           "microsecond-level timing.")
1488                   when :date then (" Used the portable Date.now() method to get millisecond-"+
1489                                    "level timing.")
1490                   else raise end)+
1491                  (case $innerMode
1492                   when :reload then "" # nothing interesting to say
1493                   when :loadOnce then (" Ran benchmarks using an experimental mode that "+
1494                                        "ensures that the benchmark code is loaded only once "+
1495                                        "during each VM invocation, which enables more VM "+
1496                                        "warm-up.")
1497                   else raise end)+
1498                  " Reporting benchmark execution times with 95% confidence "+
1499                  "intervals in milliseconds.")
1500   
1501   outp.puts
1502   
1503   def printVMs(outp)
1504     outp.puts createVMsString
1505   end
1506   
1507   def summaryStats(outp, accumulators, name, &proc)
1508     outp.print "   " if $suites.size > 1
1509     outp.print rpad(name, $benchpad)
1510     outp.print " "
1511     accumulators.size.times {
1512       | index |
1513       if index != 0
1514         outp.print " "+accumulators[index].stats(&proc).compareTo(accumulators[index-1].stats(&proc)).shortForm
1515       end
1516       outp.print statsToStr(accumulators[index].stats(&proc))
1517     }
1518     if accumulators.size>=2
1519       outp.print("    "+accumulators[-1].stats(&proc).compareTo(accumulators[0].stats(&proc)).to_s)
1520     end
1521     outp.puts
1522   end
1523   
1524   def allSummaryStats(outp, accumulators)
1525     summaryStats(outp, accumulators, "<arithmetic>") {
1526       | stat |
1527       stat.arithmeticMean
1528     }
1529     
1530     summaryStats(outp, accumulators, "<geometric>") {
1531       | stat |
1532       stat.geometricMean
1533     }
1534     
1535     summaryStats(outp, accumulators, "<harmonic>") {
1536       | stat |
1537       stat.harmonicMean
1538     }
1539   end
1540   
1541   $suites.each {
1542     | suite |
1543     printVMs(outp)
1544     if $suites.size > 1
1545       outp.puts "#{suite.name}:"
1546     else
1547       outp.puts
1548     end
1549     suite.benchmarks.each {
1550       | benchmark |
1551       outp.print "   " if $suites.size > 1
1552       outp.print rpad(benchmark.to_s, $benchpad)
1553       outp.print " "
1554       myConfigs = $benchmarksOnVMsForBenchmark[benchmark]
1555       myConfigs.size.times {
1556         | index |
1557         if index != 0
1558           outp.print " "+myConfigs[index].stats.compareTo(myConfigs[index-1].stats).shortForm
1559         end
1560         outp.print statsToStr(myConfigs[index].stats)
1561       }
1562       if $vms.size>=2
1563         outp.print("    "+myConfigs[-1].stats.compareTo(myConfigs[0].stats).to_s)
1564       end
1565       outp.puts
1566     }
1567     outp.puts
1568     allSummaryStats(outp, $suitesOnVMsForSuite[suite])
1569     outp.puts if $suites.size > 1
1570   }
1571   
1572   if $suites.size > 1
1573     printVMs(outp)
1574     outp.puts "All benchmarks:"
1575     allSummaryStats(outp, $vms)
1576   end
1577   
1578   if outp != $stdout
1579     outp.close
1580     puts
1581     File.open(reportName) {
1582       | inp |
1583       puts inp.read
1584     }
1585   end
1586   
1587 rescue => e
1588   fail(e)
1589 end
1590   
1591